diff --git a/.circleci/config.yml b/.circleci/config.yml index 49b2fd5b40..fec3b7752f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -30,16 +30,31 @@ jobs: - checkout - yarn-install - run: yarn workspaces foreach --topological-dev --verbose run build:ts - - run: yarn docgen + + - run: + name: "Generate docs for each contract" + command: yarn workspaces foreach --verbose run docgen + - run: + name: "Generate combined smart-contracts.md" + command: yarn workspace @synthetixio/docgen run docgen:contracts + - run: + name: "Generate ABIs for each supported network" + command: yarn workspace @synthetixio/docgen run abis + - run: + name: "Generate combined addresses-+-abis.md" + command: yarn workspace @synthetixio/docgen run docgen:abis + - store_artifacts: - path: "docs/smart-contracts.md" - destination: "smart-contracts.md" + path: "docs" + destination: "." - run: working_directory: ~/synthetix-gitbook-v3 name: "Update docs and push to smart-contracts branch" command: | cp ~/synthetix-v3/docs/smart-contracts.md ./for-developers/smart-contracts.md + cp ~/synthetix-v3/docs/addresses-+-abis.md ./for-developers/addresses-+-abis.md + cp ~/synthetix-v3/docs/abis/*.json ./for-developers/abis/ STATUS=$(git status) if [[ $STATUS == *"nothing to commit, working tree clean"* ]]; then @@ -54,6 +69,8 @@ jobs: git config --global user.name Noisekit git add ./for-developers/smart-contracts.md + git add ./for-developers/addresses-+-abis.md + git add ./for-developers/abis git commit -m "Update Smart Contracts" git push --set-upstream --force origin smart-contracts diff --git a/package.json b/package.json index 3d1d1b173e..244a17f290 100644 --- a/package.json +++ b/package.json @@ -37,8 +37,9 @@ "publish:dev": "lerna publish --force-publish --canary --dist-tag dev --preid dev.$(git rev-parse --short HEAD)", "publish-contracts": "yarn workspaces foreach --verbose run publish-contracts", "prepublishOnly": "node ./prepublishOnly.js", - "docgen": "yarn workspaces foreach --verbose run docgen && yarn docgen:contracts", - "docgen:contracts": "yarn workspace @synthetixio/docgen docgen:contracts", + "docgen": "yarn docgen:contracts && yarn docgen:abis", + "docgen:contracts": "yarn workspaces foreach --verbose run docgen && yarn workspace @synthetixio/docgen run docgen:contracts", + "docgen:abis": "yarn workspace @synthetixio/docgen run abis && yarn workspace @synthetixio/docgen run docgen:abis", "subgraphgen": "yarn workspaces foreach --verbose run subgraphgen" }, "devDependencies": { diff --git a/utils/docgen/.gitignore b/utils/docgen/.gitignore new file mode 100644 index 0000000000..eba984afcc --- /dev/null +++ b/utils/docgen/.gitignore @@ -0,0 +1,3 @@ +abis/ +docs/ +deployments/ diff --git a/utils/docgen/abis.js b/utils/docgen/abis.js index d9e26ee533..736f2340ad 100644 --- a/utils/docgen/abis.js +++ b/utils/docgen/abis.js @@ -3,31 +3,47 @@ const { inspect } = require('@usecannon/cli'); const fs = require('fs/promises'); const prettier = require('prettier'); -const CHAIN_IDS = [1, 5, 10, 420, 84531, 11155111]; -const PROXIES = { - CoreProxy: 'SynthetixCore', - AccountProxyCore: 'snxAccountNFT', - AccountProxyPerps: 'PerpsAccountNFT', - Proxy: 'OracleManager', - USDProxy: 'snxUSDToken', - SpotMarketProxy: 'SpotMarket', - PerpsMarketProxy: 'PerpsMarket', -}; - -function deepFind(obj, key) { - if (key in obj) return obj[key]; - - for (let i = 0; i < Object.keys(obj).length; i++) { - if (typeof obj[Object.keys(obj)[i]] === 'object') { - let result = deepFind(obj[Object.keys(obj)[i]], key); - if (result) return result; - } - } +const CHAIN_IDS = [1, 5, 10, 420, 80001, 84531, 11155111]; + +function noop() {} - return null; +function etherscanLink(chain, address) { + switch (chain) { + case 1: + return `https://etherscan.io/address/${address}`; + case 5: + return `https://goerli.etherscan.io/address/${address}`; + case 11155111: + return `https://sepolia.etherscan.io/address/${address}`; + case 10: + return `https://optimistic.etherscan.io/address/${address}`; + case 420: + return `https://goerli-optimism.etherscan.io/address/${address}`; + case 80001: + return `https://mumbai.polygonscan.com/address/${address}`; + case 84531: + return `https://goerli.basescan.org/address/${address}`; + } } -function noop() {} +function chainName(chain) { + switch (chain) { + case 1: + return 'Mainnet'; + case 5: + return 'Goerli'; + case 11155111: + return 'Sepolia'; + case 10: + return 'Optimism'; + case 420: + return 'Optimistic Goerli'; + case 80001: + return 'Polygon Mumbai'; + case 84531: + return 'Base Goerli'; + } +} function overrideStdoutWrite(callback = noop) { const _write = process.stdout.write; @@ -54,48 +70,173 @@ function overrideConsole(callback = noop) { }; } +async function fetchDeployments(chainId) { + const unhookStdout = overrideStdoutWrite(); + const unhookConsole = overrideConsole(); + const deployments = await inspect('synthetix-omnibus', chainId, 'main', true); + unhookStdout(); + unhookConsole(); + return deployments; +} + async function run() { await fs.mkdir(`${__dirname}/abis`, { recursive: true }); + await fs.mkdir(`${__dirname}/docs`, { recursive: true }); + await fs.mkdir(`${__dirname}/deployments`, { recursive: true }); const prettierOptions = JSON.parse(await fs.readFile(`${__dirname}/../../.prettierrc`, 'utf8')); + const prettyJson = (obj) => + prettier.format(JSON.stringify(obj, null, 2), { parser: 'json', ...prettierOptions }); + for (const chainId of CHAIN_IDS) { - const unhookStdout = overrideStdoutWrite(); - const unhookConsole = overrideConsole(); - const jsonOutput = await inspect('synthetix-omnibus', chainId, 'main', true); - unhookStdout(); - unhookConsole(); - - const files = Object.entries(PROXIES) - .map(([proxyKey, proxyName]) => { - if (proxyKey.startsWith('AccountProxy')) { - const getCoreAccountProxy = proxyKey === 'AccountProxyCore'; - const specificJsonOutput = getCoreAccountProxy - ? jsonOutput.state['provision.system'] - : jsonOutput.state['provision.perpsFactory']?.artifacts?.imports?.perpsFactory; - if (specificJsonOutput) { - const abi = deepFind(specificJsonOutput, 'AccountProxy'); - if (abi) { - return { chainId, proxyKey, proxyName, abi }; - } - } - } else { - const abi = deepFind(jsonOutput, proxyKey); - if (abi) { - return { chainId, proxyKey, proxyName, abi }; - } - } - }) - .filter(Boolean); - - for (const { chainId, proxyName, abi } of files) { - const filename = `./abis/${chainId}-${proxyName}.json`; - console.log('Writing', filename); + console.log(`Fetching deployments for "${chainName(chainId)} - ${chainId}"`); + const deployments = await fetchDeployments(chainId); + if (!deployments) { + console.log(`No deployments for "${chainName(chainId)} - ${chainId}"`); + continue; + } + await fs.writeFile(`./deployments/${chainId}.json`, prettyJson(deployments), 'utf8'); + + await fs.writeFile(`./docs/${chainId}.md`, `## ${chainName(chainId)}\n\n`, 'utf8'); + await fs.appendFile(`./docs/${chainId}.md`, `Chain ID: ${chainId}\n\n`, 'utf8'); + await fs.appendFile(`./docs/${chainId}.md`, '| System | Address | ABI |\n', 'utf8'); + await fs.appendFile(`./docs/${chainId}.md`, '| --- | --- | --- |\n', 'utf8'); + const system = deployments?.state?.['provision.system']?.artifacts?.imports?.system; + if (system) { + console.log(`Writing ${chainId}-SynthetixCore.json`); + await fs.writeFile( + `./abis/${chainId}-SynthetixCore.json`, + prettyJson(system.contracts.CoreProxy), + 'utf8' + ); + await fs.appendFile( + `./docs/${chainId}.md`, + `| Synthetix Core | [${system.contracts.CoreProxy.address}](${etherscanLink( + chainId, + system.contracts.CoreProxy.address + )}) | [View/Download](./abis/${chainId}-SynthetixCore.json) |\n`, + 'utf8' + ); + + console.log(`Writing ${chainId}-snxAccountNFT.json`); + await fs.writeFile( + `./abis/${chainId}-snxAccountNFT.json`, + prettyJson(system.contracts.AccountProxy), + 'utf8' + ); + await fs.appendFile( + `./docs/${chainId}.md`, + `| snxAccount NFT | [${system.contracts.AccountProxy.address}](${etherscanLink( + chainId, + system.contracts.AccountProxy.address + )}) | [View/Download](./abis/${chainId}-snxAccountNFT.json) |\n`, + 'utf8' + ); + + console.log(`Writing ${chainId}-snxUSDToken.json`); + await fs.writeFile( + `./abis/${chainId}-snxUSDToken.json`, + prettyJson(system.contracts.USDProxy), + 'utf8' + ); + await fs.appendFile( + `./docs/${chainId}.md`, + `| snxUSD Token | [${system.contracts.USDProxy.address}](${etherscanLink( + chainId, + system.contracts.USDProxy.address + )}) | [View/Download](./abis/${chainId}-snxUSDToken.json) |\n`, + 'utf8' + ); + + const { oracle_manager: oracleManager } = system.imports; + if (oracleManager) { + console.log(`Writing ${chainId}-OracleManager.json`); + await fs.writeFile( + `./abis/${chainId}-OracleManager.json`, + prettyJson(oracleManager.contracts.Proxy), + 'utf8' + ); + await fs.appendFile( + `./docs/${chainId}.md`, + `| Oracle Manager | [${oracleManager.contracts.Proxy.address}](${etherscanLink( + chainId, + oracleManager.contracts.Proxy.address + )}) | [View/Download](./abis/${chainId}-OracleManager.json) |\n`, + 'utf8' + ); + } + } + + const spotFactory = + deployments?.state?.['provision.spotFactory']?.artifacts?.imports?.spotFactory; + if (spotFactory) { + console.log(`Writing ${chainId}-SpotMarket.json`); + await fs.writeFile( + `./abis/${chainId}-SpotMarket.json`, + prettyJson(spotFactory.contracts.SpotMarketProxy), + 'utf8' + ); + await fs.appendFile( + `./docs/${chainId}.md`, + `| Spot Market | [${spotFactory.contracts.SpotMarketProxy.address}](${etherscanLink( + chainId, + spotFactory.contracts.SpotMarketProxy.address + )}) | [View/Download](./abis/${chainId}-SpotMarket.json) |\n`, + 'utf8' + ); + } + + const perpsFactory = + deployments?.state?.['provision.perpsFactory']?.artifacts?.imports?.perpsFactory; + if (perpsFactory) { + console.log(`Writing ${chainId}-PerpsMarket.json`); + await fs.writeFile( + `./abis/${chainId}-PerpsMarket.json`, + prettyJson(perpsFactory.contracts.PerpsMarketProxy), + 'utf8' + ); + await fs.appendFile( + `./docs/${chainId}.md`, + `| Perps Market | [${perpsFactory.contracts.PerpsMarketProxy.address}](${etherscanLink( + chainId, + perpsFactory.contracts.PerpsMarketProxy.address + )}) | [View/Download](./abis/${chainId}-PerpsMarket.json) |\n`, + 'utf8' + ); + + console.log(`Writing ${chainId}-PerpsAccountNFT.json`); await fs.writeFile( - filename, - prettier.format(JSON.stringify(abi, null, 2), { - parser: 'json', - ...prettierOptions, - }), + `./abis/${chainId}-PerpsAccountNFT.json`, + prettyJson(perpsFactory.contracts.AccountProxy), + 'utf8' + ); + await fs.appendFile( + `./docs/${chainId}.md`, + `| Perps Market Account NFT | [${ + perpsFactory.contracts.AccountProxy.address + }](${etherscanLink( + chainId, + perpsFactory.contracts.AccountProxy.address + )}) | [View/Download](./abis/${chainId}-PerpsAccountNFT.json) |\n`, + 'utf8' + ); + } + + console.log(`Writing ${chainId}.md`); + // SNX token + const configureSnxCollateral = + deployments?.state?.['invoke.configureSnxCollateral']?.artifacts?.txns + ?.configureSnxCollateral; + const [snxCollateralConfiguredEvent] = + configureSnxCollateral?.events?.CollateralConfigured ?? []; + const [snxAddress] = snxCollateralConfiguredEvent?.args ?? []; + if (snxAddress) { + await fs.appendFile( + `./docs/${chainId}.md`, + `| SNX Token | [${snxAddress}](${etherscanLink( + chainId, + snxAddress + )}) | _ERC-20 compliant_ |\n`, 'utf8' ); } diff --git a/utils/docgen/addresses-+-abis.md b/utils/docgen/addresses-+-abis.md new file mode 100644 index 0000000000..40bf1601ee --- /dev/null +++ b/utils/docgen/addresses-+-abis.md @@ -0,0 +1,9 @@ +# Addresses + ABIs + +See the [synthetix-deployments](https://github.com/synthetixio/synthetix-deployments) GitOps repository for more information. + +To download the Addresses + ABIs for Synthetix via the command line, you can run the following command: + +```bash +npx @usecannon/cli inspect synthetix-omnibus --write-deployments ./deployments --chain-id +``` diff --git a/utils/docgen/docgen-abis.sh b/utils/docgen/docgen-abis.sh new file mode 100755 index 0000000000..df866424a4 --- /dev/null +++ b/utils/docgen/docgen-abis.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +echo "Docgen ABIs..." + +ROOT=$(yarn workspace synthetix-v3 exec pwd) +OUT="$ROOT/docs/addresses-+-abis.md" +mkdir -p $ROOT/docs +rm -rf $ROOT/docs/abis +rm -rf $OUT +touch $OUT + +cat "./addresses-+-abis.md" > $OUT + +cat ./docs/1.md >> $OUT +echo "" >> $OUT + +cat ./docs/5.md >> $OUT +echo "" >> $OUT + +cat ./docs/11155111.md >> $OUT +echo "" >> $OUT + +cat ./docs/10.md >> $OUT +echo "" >> $OUT + +cat ./docs/420.md >> $OUT +echo "" >> $OUT + +cat ./docs/80001.md >> $OUT +echo "" >> $OUT + +cat ./docs/84531.md >> $OUT +echo "" >> $OUT + +cp -r ./abis $ROOT/docs/abis + +echo "OK Docgen ABIs" diff --git a/utils/docgen/docgen:contracts.sh b/utils/docgen/docgen-contracts.sh similarity index 95% rename from utils/docgen/docgen:contracts.sh rename to utils/docgen/docgen-contracts.sh index 1b58b76858..ba33dcf6bd 100755 --- a/utils/docgen/docgen:contracts.sh +++ b/utils/docgen/docgen-contracts.sh @@ -1,8 +1,11 @@ #!/bin/bash +echo "Docgen contracts..." + ROOT=$(yarn workspace synthetix-v3 exec pwd) OUT="$ROOT/docs/smart-contracts.md" mkdir -p $ROOT/docs +rm -rf $OUT touch $OUT echo "# Smart Contracts" > $OUT @@ -49,3 +52,5 @@ echo "" >> $OUT echo "- [Back to TOC](#synthetix-core)" >> $OUT echo "" >> $OUT cat $ROOT/protocol/oracle-manager/docs/index.md >> $OUT + +echo "OK Docgen contracts" diff --git a/utils/docgen/package.json b/utils/docgen/package.json index a9d1066e79..2e9a1f2412 100644 --- a/utils/docgen/package.json +++ b/utils/docgen/package.json @@ -8,7 +8,8 @@ "types": "index.d.ts", "scripts": { "abis": "node ./abis.js", - "docgen:contracts": "./docgen:contracts.sh" + "docgen:abis": "./docgen-abis.sh", + "docgen:contracts": "./docgen-contracts.sh" }, "devDependencies": { "@usecannon/cli": "^2.4.21",