From 7916a40b3a97354eb218238509be8099aef26b1d Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Fri, 10 Mar 2023 17:48:24 +1100 Subject: [PATCH] Release v2.5.2 (#143) fix: depth of squashed contracts by squashing before filtering to base contracts chore: Updated usage and argument descriptions chore: removed test chains chore: bumped version to 2.5.2 --- README.md | 78 +++++++++++++++++-------------- lib/parserEtherscan.d.ts | 2 +- lib/parserEtherscan.js | 39 ++-------------- lib/sol2uml.js | 68 ++++++++++++--------------- package-lock.json | 4 +- package.json | 7 ++- src/ts/parserEtherscan.ts | 34 ++------------ src/ts/sol2uml.ts | 97 +++++++++++++++------------------------ 8 files changed, 130 insertions(+), 199 deletions(-) mode change 100644 => 100755 lib/sol2uml.js diff --git a/README.md b/README.md index de6d806d..9bda7aff 100644 --- a/README.md +++ b/README.md @@ -45,22 +45,17 @@ npm ls sol2uml -g ## Command Line Interface (CLI) ``` -Usage: sol2uml [subcommand] -The three subcommands: -* class: Generates a UML class diagram from Solidity source code. default -* storage: Generates a diagram of a contract's storage slots. -* flatten: Merges verified source files from a Blockchain explorer into one local file. -* diff: Compares the flattened Solidity code from a Blockchain explorer for two contracts. +Usage: sol2uml [command] -The Solidity code can be pulled from verified source code on Blockchain explorers like Etherscan or from local Solidity files. +Generate UML class or storage diagrams from local Solidity code or verified Solidity code on Etherscan-like explorers. +Can also flatten or compare verified source files on Etherscan-like explorers. Options: -sf, --subfolders number of subfolders that will be recursively searched for Solidity files. (default: all) -f, --outputFormat output file format. (choices: "svg", "png", "dot", "all", default: "svg") -o, --outputFileName output file name -i, --ignoreFilesOrFolders comma separated list of files or folders to ignore - -n, --network Ethereum network (choices: "mainnet", "ropsten", "kovan", "rinkeby", "goerli", "sepolia", "polygon", "testnet.polygon", "arbitrum", "testnet.arbitrum", "avalanche", "testnet.avalanche", "bsc", "testnet.bsc", "crono", "fantom", - "testnet.fantom", "moonbeam", "optimistic", "kovan-optimistic", "gnosisscan", default: "mainnet", env: ETH_NETWORK) + -n, --network Ethereum network (choices: "mainnet", "goerli", "sepolia", "polygon", "arbitrum", "avalanche", "bsc", "crono", "fantom", "moonbeam", "optimism", "gnosis", default: "mainnet", env: ETH_NETWORK) -k, --apiKey Blockchain explorer API key. eg Etherscan, Arbiscan, Optimism, BscScan, CronoScan, FTMScan, PolygonScan or SnowTrace API key (env: SCAN_API_KEY) -bc, --backColor Canvas background color. "none" will use a transparent canvas. (default: "white") -sc, --shapeColor Basic drawing color for graphics, not text (default: "black") @@ -71,9 +66,24 @@ Options: -h, --help display help for command Commands: - class [options] [fileFolderAddress] Generates a UML class diagram from Solidity source code. - storage [options] output a contracts storage slots - flatten get all verified source code for a contract from the Blockchain explorer into one local file + class [options] Generates a UML class diagram from Solidity source code. + storage [options] Visually display a contract's storage slots. + + WARNING: sol2uml does not use the Solidity compiler so may differ with solc. A known example is fixed-sized arrays declared with an expression will fail to be sized. + flatten Merges verified source files for a contract from a Blockchain explorer into one local file. + + In order for the merged code to compile, the following is done: + 1. pragma solidity is set using the compiler of the verified contract. + 2. All pragma solidity lines in the source files are commented out. + 3. File imports are commented out. + 4. "SPDX-License-Identifier" is renamed to "SPDX--License-Identifier". + 5. Contract dependencies are analysed so the files are merged in an order that will compile. + diff [options] Compare verified Solidity code differences between two contracts. + + The results show the comparison of contract A to B. + The green sections are additions to contract B that are not in contract A. + The red sections are removals from contract A that are not in contract B. + The line numbers are from contract B. There are no line numbers for the red sections as they are not in contract B. help [command] display help for command ``` @@ -82,20 +92,15 @@ Commands: ``` Usage: sol2uml class [options] -Generates UML diagrams from Solidity source code. - -If no file, folder or address is passed as the first argument, the working folder is used. -When a folder is used, all *.sol files are found in that folder and all sub folders. -A comma separated list of files and folders can also be used. For example - sol2uml contracts,node_modules/openzeppelin-solidity - -If an Ethereum address with a 0x prefix is passed, the verified source code from Etherscan will be used. For example - sol2uml 0x79fEbF6B9F76853EDBcBc913e6aAE8232cFB9De9 - Generates a UML class diagram from Solidity source code. Arguments: - fileFolderAddress file name, base folder or contract address (default: "/Users/nicholasaddison/Documents/workspaces/sol2uml") + fileFolderAddress file name, folder(s) or contract address. + When a folder is used, all *.sol files in that folder and all sub folders are used. + A comma-separated list of files and folders can also be used. For example + sol2uml contracts,node_modules/openzeppelin-solidity + If an Ethereum address with a 0x prefix is passed, the verified source code from Etherscan will be used. For example + sol2uml 0x79fEbF6B9F76853EDBcBc913e6aAE8232cFB9De9 Options: -b, --baseContractNames only output contracts connected to these comma separated base contract names @@ -123,12 +128,17 @@ Options: ``` Usage: sol2uml storage [options] -WARNING: sol2uml does not use the Solidity compiler so may differ with solc. A known example is fixed-sized arrays declared with an expression will fail to be sized. - Visually display a contract's storage slots. +WARNING: sol2uml does not use the Solidity compiler so may differ with solc. A known example is fixed-sized arrays declared with an expression will fail to be sized. + Arguments: - fileFolderAddress file name, base folder or contract address + fileFolderAddress file name, folder(s) or contract address. + When a folder is used, all *.sol files in that folder and all sub folders are used. + A comma-separated list of files and folders can also be used. For example + sol2uml contracts,node_modules/openzeppelin-solidity + If an Ethereum address with a 0x prefix is passed, the verified source code from Etherscan will be used. For example + sol2uml 0x79fEbF6B9F76853EDBcBc913e6aAE8232cFB9De9 Options: -c, --contract Contract name in the local Solidity files. Not needed when using an address as the first argument as the contract name can be derived from Etherscan. @@ -138,7 +148,7 @@ Options: -u, --url URL of the Ethereum node to get storage values if the `data` option is used. (default: "http://localhost:8545", env: NODE_URL) -bn, --block Block number to get the contract storage values from. (default: "latest") -a, --array Number of slots to display at the start and end of arrays. (default: "2") - -hv, --hideValue Hide storage slot value column. + -hv, --hideValue Hide storage slot value column. (default: false) -h, --help display help for command ``` @@ -147,6 +157,8 @@ Options: ``` Usage: sol2uml flatten +Merges verified source files for a contract from a Blockchain explorer into one local Solidity file. + In order for the merged code to compile, the following is done: 1. pragma solidity is set using the compiler of the verified contract. 2. All pragma solidity lines in the source files are commented out. @@ -154,8 +166,6 @@ In order for the merged code to compile, the following is done: 4. "SPDX-License-Identifier" is renamed to "SPDX--License-Identifier". 5. Contract dependencies are analysed so the files are merged in an order that will compile. -Merges verified source files for a contract from a Blockchain explorer into one local file. - Arguments: contractAddress Contract address in hexadecimal format with a 0x prefix. @@ -168,16 +178,16 @@ Options: ``` Usage: sol2uml diff [options] -The results show the comparison of contracts A to B. +Compare verified Solidity code differences between two contracts. + +The results show the comparison of contract A to B. The green sections are additions to contract B that are not in contract A. The red sections are removals from contract A that are not in contract B. The line numbers are from contract B. There are no line numbers for the red sections as they are not in contract B. -Compare verified Solidity code differences between two contracts. - Arguments: - addressA Contract address in hexadecimal format with a 0x prefix. - addressB Contract address in hexadecimal format with a 0x prefix. + addressA Contract address in hexadecimal format with a 0x prefix of the first contract. + addressB Contract address in hexadecimal format with a 0x prefix of the second contract. Options: -l, --lineBuffer Minimum number of lines before and after changes (default: "4") diff --git a/lib/parserEtherscan.d.ts b/lib/parserEtherscan.d.ts index 30c30b24..2de2c24f 100644 --- a/lib/parserEtherscan.d.ts +++ b/lib/parserEtherscan.d.ts @@ -4,7 +4,7 @@ export interface Remapping { from: RegExp; to: string; } -export declare const networks: readonly ["mainnet", "ropsten", "kovan", "rinkeby", "goerli", "sepolia", "polygon", "testnet.polygon", "arbitrum", "testnet.arbitrum", "avalanche", "testnet.avalanche", "bsc", "testnet.bsc", "crono", "fantom", "testnet.fantom", "moonbeam", "optimistic", "kovan-optimistic", "gnosisscan"]; +export declare const networks: readonly ["mainnet", "goerli", "sepolia", "polygon", "arbitrum", "avalanche", "bsc", "crono", "fantom", "moonbeam", "optimism", "gnosis"]; export type Network = (typeof networks)[number]; export declare class EtherscanParser { protected apikey: string; diff --git a/lib/parserEtherscan.js b/lib/parserEtherscan.js index 54b5bdd0..315bb0a7 100644 --- a/lib/parserEtherscan.js +++ b/lib/parserEtherscan.js @@ -13,26 +13,17 @@ require('axios-debug-log'); const debug = require('debug')('sol2uml'); exports.networks = [ 'mainnet', - 'ropsten', - 'kovan', - 'rinkeby', 'goerli', 'sepolia', 'polygon', - 'testnet.polygon', 'arbitrum', - 'testnet.arbitrum', 'avalanche', - 'testnet.avalanche', 'bsc', - 'testnet.bsc', 'crono', 'fantom', - 'testnet.fantom', 'moonbeam', - 'optimistic', - 'kovan-optimistic', - 'gnosisscan', + 'optimism', + 'gnosis', ]; class EtherscanParser { constructor(apikey = 'ZAD4UI2RCXCQTP38EXS3UY2MPHFU5H9KB1', network = 'mainnet') { @@ -48,34 +39,18 @@ class EtherscanParser { this.url = 'https://api.polygonscan.com/api'; this.apikey = 'AMHGNTV5A7XYGX2M781JB3RC1DZFVRWQEB'; } - else if (network === 'testnet.polygon') { - this.url = 'https://api-testnet.polygonscan.com/api'; - this.apikey = 'AMHGNTV5A7XYGX2M781JB3RC1DZFVRWQEB'; - } else if (network === 'arbitrum') { this.url = 'https://api.arbiscan.io/api'; this.apikey = 'ZGTK2TAGWMAB6IAC12BMK8YYPNCPIM8VDQ'; } - else if (network === 'testnet.arbitrum') { - this.url = 'https://api-testnet.arbiscan.io/api'; - this.apikey = 'ZGTK2TAGWMAB6IAC12BMK8YYPNCPIM8VDQ'; - } else if (network === 'avalanche') { this.url = 'https://api.snowtrace.io/api'; this.apikey = 'U5FAN98S5XNH5VI83TI4H35R9I4TDCKEJY'; } - else if (network === 'testnet.avalanche') { - this.url = 'https://api-testnet.snowtrace.io/api'; - this.apikey = 'U5FAN98S5XNH5VI83TI4H35R9I4TDCKEJY'; - } else if (network === 'bsc') { this.url = 'https://api.bscscan.com/api'; this.apikey = 'APYH49FXVY9UA3KTDI6F4WP3KPIC86NITN'; } - else if (network === 'testnet.bsc') { - this.url = 'https://api-testnet.bscscan.com/api'; - this.apikey = 'APYH49FXVY9UA3KTDI6F4WP3KPIC86NITN'; - } else if (network === 'crono') { this.url = 'https://api.cronoscan.com/api'; this.apikey = '76A3RG5WHTPMMR66E9SFI2EIDT6MP976W2'; @@ -84,19 +59,15 @@ class EtherscanParser { this.url = 'https://api.ftmscan.com/api'; this.apikey = '71KRX13XPZMGR3D1Q85W78G2DSZ4JPMAEX'; } - else if (network === 'testnet.fantom') { - this.url = 'https://api-testnet.ftmscan.com/api'; - this.apikey = '71KRX13XPZMGR3D1Q85W78G2DSZ4JPMAEX'; - } - else if (network === 'optimistic' || network === 'kovan-optimistic') { - this.url = `https://api-${network}.etherscan.io/api`; + else if (network === 'optimism') { + this.url = `https://api-optimistic.etherscan.io/api`; this.apikey = 'FEXS1HXVA4Y2RNTMEA8V1UTK21S4JWHH9U'; } else if (network === 'moonbeam') { this.url = 'https://api-moonbeam.moonscan.io/api'; this.apikey = '5EUFXW6TDC16VERF3D9SCWRRU6AEMTBHNJ'; } - else if (network === 'gnosisscan') { + else if (network === 'gnosis') { this.url = 'https://api.gnosisscan.io/api'; this.apikey = '2RWGXIWK538EJ8XSP9DE2JUINSCG7UCSJB'; } diff --git a/lib/sol2uml.js b/lib/sol2uml.js old mode 100644 new mode 100755 index 75cf482b..a916400b --- a/lib/sol2uml.js +++ b/lib/sol2uml.js @@ -20,15 +20,9 @@ const program = new commander_1.Command(); const debugControl = require('debug'); const debug = require('debug')('sol2uml'); program - .usage(`[subcommand] - -sol2uml comes with three subcommands: -* class: Generates a UML class diagram from Solidity source code. (default) -* storage: Generates a diagram of a contract's storage slots. -* flatten: Merges verified Solidity files from a Blockchain explorer into one local file. -* diff: Compares the flattened Solidity code from a Blockchain explorer for two contracts. - -The Solidity code can be pulled from verified source code on Blockchain explorers like Etherscan or from local Solidity files.`) + .usage('[command] ') + .description(`Generate UML class or storage diagrams from local Solidity code or verified Solidity code on Etherscan-like explorers. +Can also flatten or compare verified source files on Etherscan-like explorers.`) .addOption(new commander_1.Option('-sf, --subfolders ', 'number of subfolders that will be recursively searched for Solidity files.').default('-1', 'all')) .addOption(new commander_1.Option('-f, --outputFormat ', 'output file format.') .choices(['svg', 'png', 'dot', 'all']) @@ -49,21 +43,17 @@ const version = (0, path_1.basename)(__dirname) === 'lib' ? require('../package.json').version // used when run from compile js in /lib : require('../../package.json').version; // used when run from TypeScript source files under src/ts via ts-node program.version(version); +const argumentText = `file name, folder(s) or contract address. +\t\t\t\t When a folder is used, all *.sol files in that folder and all sub folders are used. +\t\t\t\t A comma-separated list of files and folders can also be used. For example +\t\t\t\t\tsol2uml contracts,node_modules/openzeppelin-solidity +\t\t\t\t If an Ethereum address with a 0x prefix is passed, the verified source code from Etherscan will be used. For example +\t\t\t\t\tsol2uml 0x79fEbF6B9F76853EDBcBc913e6aAE8232cFB9De9`; program .command('class', { isDefault: true }) + .usage('[options] ') .description('Generates a UML class diagram from Solidity source code.') - .usage(`sol2uml [options] - -Generates UML diagrams from Solidity source code. - -If no file, folder or address is passed as the first argument, the working folder is used. -When a folder is used, all *.sol files are found in that folder and all sub folders. -A comma separated list of files and folders can also be used. For example - sol2uml contracts,node_modules/openzeppelin-solidity - -If an Ethereum address with a 0x prefix is passed, the verified source code from Etherscan will be used. For example - sol2uml 0x79fEbF6B9F76853EDBcBc913e6aAE8232cFB9De9`) - .argument('[fileFolderAddress]', 'file name, base folder or contract address', process.cwd()) + .argument('fileFolderAddress', argumentText) .option('-b, --baseContractNames ', 'only output contracts connected to these comma separated base contract names') .addOption(new commander_1.Option('-d, --depth ', 'depth of connected classes to the base contracts. 1 will only show directly connected contracts, interfaces, libraries, structs and enums.').default('100', 'all')) .option('-c, --clusterFolders', 'cluster contracts into source folders', false) @@ -94,18 +84,20 @@ If an Ethereum address with a 0x prefix is passed, the verified source code from !(options.baseContractNames || contractName)) { throw Error('Must specify base contract(s) when using the squash option against local Solidity files.'); } - // Filter out any class stereotypes that are to be hidden - let filteredUmlClasses = (0, filterClasses_1.filterHiddenClasses)(umlClasses, options); const baseContractNames = options.baseContractNames?.split(','); if (baseContractNames) { - // Find all the classes connected to the base classes - filteredUmlClasses = (0, filterClasses_1.classesConnectedToBaseContracts)(filteredUmlClasses, baseContractNames, options.depth); contractName = baseContractNames[0]; } + // Filter out any class stereotypes that are to be hidden + let filteredUmlClasses = (0, filterClasses_1.filterHiddenClasses)(umlClasses, options); // squash contracts if (options.squash) { filteredUmlClasses = (0, squashClasses_1.squashUmlClasses)(filteredUmlClasses, baseContractNames || [contractName]); } + if (baseContractNames || options.squash) { + // Find all the classes connected to the base classes after they have been squashed + filteredUmlClasses = (0, filterClasses_1.classesConnectedToBaseContracts)(filteredUmlClasses, baseContractNames || [contractName], options.depth); + } // Convert UML classes to Graphviz dot format. const dotString = (0, converterClasses2Dot_1.convertUmlClasses2Dot)(filteredUmlClasses, combinedOptions.clusterFolders, combinedOptions); // Convert Graphviz dot format to file formats. eg svg or png @@ -119,11 +111,11 @@ If an Ethereum address with a 0x prefix is passed, the verified source code from }); program .command('storage') - .description("Visually display a contract's storage slots.") - .usage(`[options] + .usage('[options] ') + .description(`Visually display a contract's storage slots. -WARNING: sol2uml does not use the Solidity compiler so may differ with solc. A known example is fixed-sized arrays declared with an expression will fail to be sized.`) - .argument('', 'file name, base folder or contract address') +WARNING: sol2uml does not use the Solidity compiler so may differ with solc. A known example is fixed-sized arrays declared with an expression will fail to be sized.\n`) + .argument('fileFolderAddress', argumentText) .option('-c, --contract ', 'Contract name in the local Solidity files. Not needed when using an address as the first argument as the contract name can be derived from Etherscan.') .option('-cf, --contractFile ', 'Filename the contract is located in. This can include the relative path to the desired file.') .option('-d, --data', 'Gets the values in the storage slots from an Ethereum node.', false) @@ -188,15 +180,15 @@ WARNING: sol2uml does not use the Solidity compiler so may differ with solc. A k }); program .command('flatten') - .description('Merges verified source files for a contract from a Blockchain explorer into one local file.') - .usage(` + .usage('') + .description(`Merges verified source files for a contract from a Blockchain explorer into one local Solidity file. In order for the merged code to compile, the following is done: 1. pragma solidity is set using the compiler of the verified contract. 2. All pragma solidity lines in the source files are commented out. 3. File imports are commented out. 4. "SPDX-License-Identifier" is renamed to "SPDX--License-Identifier". -5. Contract dependencies are analysed so the files are merged in an order that will compile.`) +5. Contract dependencies are analysed so the files are merged in an order that will compile.\n`) .argument('', 'Contract address in hexadecimal format with a 0x prefix.') .action(async (contractAddress, options, command) => { try { @@ -218,15 +210,15 @@ In order for the merged code to compile, the following is done: }); program .command('diff') - .description('Compare verified Solidity code differences between two contracts.') - .usage(`[options] + .usage('[options] ') + .description(`Compare verified Solidity code differences between two contracts. -The results show the comparison of contracts A to B. +The results show the comparison of contract A to B. The ${clc.green('green')} sections are additions to contract B that are not in contract A. The ${clc.red('red')} sections are removals from contract A that are not in contract B. -The line numbers are from contract B. There are no line numbers for the red sections as they are not in contract B.`) - .argument('', 'Contract address in hexadecimal format with a 0x prefix.') - .argument('', 'Contract address in hexadecimal format with a 0x prefix.') +The line numbers are from contract B. There are no line numbers for the red sections as they are not in contract B.\n`) + .argument('', 'Contract address in hexadecimal format with a 0x prefix of the first contract.') + .argument('', 'Contract address in hexadecimal format with a 0x prefix of the second contract.') .addOption(new commander_1.Option('-l, --lineBuffer ', 'Minimum number of lines before and after changes').default('4')) .option('-s, --saveFiles', 'Save the flattened contract code to the filesystem. The file names will be the contract address with a .sol extension.', false) .action(async (addressA, addressB, options, command) => { diff --git a/package-lock.json b/package-lock.json index 40d6c4e1..9556bc5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sol2uml", - "version": "2.5.1", + "version": "2.5.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "sol2uml", - "version": "2.5.1", + "version": "2.5.2", "license": "MIT", "dependencies": { "@aduh95/viz.js": "^3.7.0", diff --git a/package.json b/package.json index a4020495..522dab3f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sol2uml", - "version": "2.5.1", + "version": "2.5.2", "description": "Solidity contract visualisation tool.", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -8,9 +8,12 @@ "buildSol": "cd ./src/contracts && solc **/*.sol", "build": "tsc --build ./tsconfig.json", "clean": "tsc --build --clean ./tsconfig.json", + "package-lock": "npm i --package-lock-only", + "permit": " chmod 775 lib/sol2uml.js", "prettier": "prettier --write src/**/*.ts **/*.md", "prettier:check": "prettier --check src/**/*.ts **/*.md", - "test": "npx jest" + "test": "npx jest", + "prepublishOnly": "npm run clean && npm run package-lock && npm run build && npm run permit" }, "preferGlobal": true, "contributors": [ diff --git a/src/ts/parserEtherscan.ts b/src/ts/parserEtherscan.ts index 087f4ec4..b0c22e11 100644 --- a/src/ts/parserEtherscan.ts +++ b/src/ts/parserEtherscan.ts @@ -17,26 +17,17 @@ export interface Remapping { export const networks = [ 'mainnet', - 'ropsten', - 'kovan', - 'rinkeby', 'goerli', 'sepolia', 'polygon', - 'testnet.polygon', 'arbitrum', - 'testnet.arbitrum', 'avalanche', - 'testnet.avalanche', 'bsc', - 'testnet.bsc', 'crono', 'fantom', - 'testnet.fantom', 'moonbeam', - 'optimistic', - 'kovan-optimistic', - 'gnosisscan', + 'optimism', + 'gnosis', ] export type Network = (typeof networks)[number] @@ -56,43 +47,28 @@ export class EtherscanParser { } else if (network === 'polygon') { this.url = 'https://api.polygonscan.com/api' this.apikey = 'AMHGNTV5A7XYGX2M781JB3RC1DZFVRWQEB' - } else if (network === 'testnet.polygon') { - this.url = 'https://api-testnet.polygonscan.com/api' - this.apikey = 'AMHGNTV5A7XYGX2M781JB3RC1DZFVRWQEB' } else if (network === 'arbitrum') { this.url = 'https://api.arbiscan.io/api' this.apikey = 'ZGTK2TAGWMAB6IAC12BMK8YYPNCPIM8VDQ' - } else if (network === 'testnet.arbitrum') { - this.url = 'https://api-testnet.arbiscan.io/api' - this.apikey = 'ZGTK2TAGWMAB6IAC12BMK8YYPNCPIM8VDQ' } else if (network === 'avalanche') { this.url = 'https://api.snowtrace.io/api' this.apikey = 'U5FAN98S5XNH5VI83TI4H35R9I4TDCKEJY' - } else if (network === 'testnet.avalanche') { - this.url = 'https://api-testnet.snowtrace.io/api' - this.apikey = 'U5FAN98S5XNH5VI83TI4H35R9I4TDCKEJY' } else if (network === 'bsc') { this.url = 'https://api.bscscan.com/api' this.apikey = 'APYH49FXVY9UA3KTDI6F4WP3KPIC86NITN' - } else if (network === 'testnet.bsc') { - this.url = 'https://api-testnet.bscscan.com/api' - this.apikey = 'APYH49FXVY9UA3KTDI6F4WP3KPIC86NITN' } else if (network === 'crono') { this.url = 'https://api.cronoscan.com/api' this.apikey = '76A3RG5WHTPMMR66E9SFI2EIDT6MP976W2' } else if (network === 'fantom') { this.url = 'https://api.ftmscan.com/api' this.apikey = '71KRX13XPZMGR3D1Q85W78G2DSZ4JPMAEX' - } else if (network === 'testnet.fantom') { - this.url = 'https://api-testnet.ftmscan.com/api' - this.apikey = '71KRX13XPZMGR3D1Q85W78G2DSZ4JPMAEX' - } else if (network === 'optimistic' || network === 'kovan-optimistic') { - this.url = `https://api-${network}.etherscan.io/api` + } else if (network === 'optimism') { + this.url = `https://api-optimistic.etherscan.io/api` this.apikey = 'FEXS1HXVA4Y2RNTMEA8V1UTK21S4JWHH9U' } else if (network === 'moonbeam') { this.url = 'https://api-moonbeam.moonscan.io/api' this.apikey = '5EUFXW6TDC16VERF3D9SCWRRU6AEMTBHNJ' - } else if (network === 'gnosisscan') { + } else if (network === 'gnosis') { this.url = 'https://api.gnosisscan.io/api' this.apikey = '2RWGXIWK538EJ8XSP9DE2JUINSCG7UCSJB' } else { diff --git a/src/ts/sol2uml.ts b/src/ts/sol2uml.ts index 3ca77408..f5c09515 100644 --- a/src/ts/sol2uml.ts +++ b/src/ts/sol2uml.ts @@ -28,16 +28,10 @@ const debugControl = require('debug') const debug = require('debug')('sol2uml') program - .usage( - `[subcommand] - -sol2uml comes with three subcommands: -* class: Generates a UML class diagram from Solidity source code. (default) -* storage: Generates a diagram of a contract's storage slots. -* flatten: Merges verified Solidity files from a Blockchain explorer into one local file. -* diff: Compares the flattened Solidity code from a Blockchain explorer for two contracts. - -The Solidity code can be pulled from verified source code on Blockchain explorers like Etherscan or from local Solidity files.` + .usage('[command] ') + .description( + `Generate UML class or storage diagrams from local Solidity code or verified Solidity code on Etherscan-like explorers. +Can also flatten or compare verified source files on Etherscan-like explorers.` ) .addOption( new Option( @@ -91,27 +85,17 @@ const version = : require('../../package.json').version // used when run from TypeScript source files under src/ts via ts-node program.version(version) +const argumentText = `file name, folder(s) or contract address. +\t\t\t\t When a folder is used, all *.sol files in that folder and all sub folders are used. +\t\t\t\t A comma-separated list of files and folders can also be used. For example +\t\t\t\t\tsol2uml contracts,node_modules/openzeppelin-solidity +\t\t\t\t If an Ethereum address with a 0x prefix is passed, the verified source code from Etherscan will be used. For example +\t\t\t\t\tsol2uml 0x79fEbF6B9F76853EDBcBc913e6aAE8232cFB9De9` program .command('class', { isDefault: true }) + .usage('[options] ') .description('Generates a UML class diagram from Solidity source code.') - .usage( - `sol2uml [options] - -Generates UML diagrams from Solidity source code. - -If no file, folder or address is passed as the first argument, the working folder is used. -When a folder is used, all *.sol files are found in that folder and all sub folders. -A comma separated list of files and folders can also be used. For example - sol2uml contracts,node_modules/openzeppelin-solidity - -If an Ethereum address with a 0x prefix is passed, the verified source code from Etherscan will be used. For example - sol2uml 0x79fEbF6B9F76853EDBcBc913e6aAE8232cFB9De9` - ) - .argument( - '[fileFolderAddress]', - 'file name, base folder or contract address', - process.cwd() - ) + .argument('fileFolderAddress', argumentText) .option( '-b, --baseContractNames ', 'only output contracts connected to these comma separated base contract names' @@ -191,21 +175,14 @@ If an Ethereum address with a 0x prefix is passed, the verified source code from 'Must specify base contract(s) when using the squash option against local Solidity files.' ) } - - // Filter out any class stereotypes that are to be hidden - let filteredUmlClasses = filterHiddenClasses(umlClasses, options) - const baseContractNames = options.baseContractNames?.split(',') if (baseContractNames) { - // Find all the classes connected to the base classes - filteredUmlClasses = classesConnectedToBaseContracts( - filteredUmlClasses, - baseContractNames, - options.depth - ) contractName = baseContractNames[0] } + // Filter out any class stereotypes that are to be hidden + let filteredUmlClasses = filterHiddenClasses(umlClasses, options) + // squash contracts if (options.squash) { filteredUmlClasses = squashUmlClasses( @@ -214,6 +191,15 @@ If an Ethereum address with a 0x prefix is passed, the verified source code from ) } + if (baseContractNames || options.squash) { + // Find all the classes connected to the base classes after they have been squashed + filteredUmlClasses = classesConnectedToBaseContracts( + filteredUmlClasses, + baseContractNames || [contractName], + options.depth + ) + } + // Convert UML classes to Graphviz dot format. const dotString = convertUmlClasses2Dot( filteredUmlClasses, @@ -238,16 +224,13 @@ If an Ethereum address with a 0x prefix is passed, the verified source code from program .command('storage') - .description("Visually display a contract's storage slots.") - .usage( - `[options] + .usage('[options] ') + .description( + `Visually display a contract's storage slots. -WARNING: sol2uml does not use the Solidity compiler so may differ with solc. A known example is fixed-sized arrays declared with an expression will fail to be sized.` - ) - .argument( - '', - 'file name, base folder or contract address' +WARNING: sol2uml does not use the Solidity compiler so may differ with solc. A known example is fixed-sized arrays declared with an expression will fail to be sized.\n` ) + .argument('fileFolderAddress', argumentText) .option( '-c, --contract ', 'Contract name in the local Solidity files. Not needed when using an address as the first argument as the contract name can be derived from Etherscan.' @@ -385,18 +368,16 @@ WARNING: sol2uml does not use the Solidity compiler so may differ with solc. A k program .command('flatten') + .usage('') .description( - 'Merges verified source files for a contract from a Blockchain explorer into one local file.' - ) - .usage( - ` + `Merges verified source files for a contract from a Blockchain explorer into one local Solidity file. In order for the merged code to compile, the following is done: 1. pragma solidity is set using the compiler of the verified contract. 2. All pragma solidity lines in the source files are commented out. 3. File imports are commented out. 4. "SPDX-License-Identifier" is renamed to "SPDX--License-Identifier". -5. Contract dependencies are analysed so the files are merged in an order that will compile.` +5. Contract dependencies are analysed so the files are merged in an order that will compile.\n` ) .argument( '', @@ -431,28 +412,26 @@ In order for the merged code to compile, the following is done: program .command('diff') + .usage('[options] ') .description( - 'Compare verified Solidity code differences between two contracts.' - ) - .usage( - `[options] + `Compare verified Solidity code differences between two contracts. -The results show the comparison of contracts A to B. +The results show the comparison of contract A to B. The ${clc.green( 'green' )} sections are additions to contract B that are not in contract A. The ${clc.red( 'red' )} sections are removals from contract A that are not in contract B. -The line numbers are from contract B. There are no line numbers for the red sections as they are not in contract B.` +The line numbers are from contract B. There are no line numbers for the red sections as they are not in contract B.\n` ) .argument( '', - 'Contract address in hexadecimal format with a 0x prefix.' + 'Contract address in hexadecimal format with a 0x prefix of the first contract.' ) .argument( '', - 'Contract address in hexadecimal format with a 0x prefix.' + 'Contract address in hexadecimal format with a 0x prefix of the second contract.' ) .addOption( new Option(