sol2uml 2.1.2 → 2.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -53,12 +53,13 @@ The three subcommands:
53
53
  The Solidity code can be pulled from verified source code on Blockchain explorers like Etherscan or from local Solidity files.
54
54
 
55
55
  Options:
56
+ -V, --version output the version number
56
57
  -sf, --subfolders <value> number of subfolders that will be recursively searched for Solidity files. (default: all)
57
58
  -f, --outputFormat <value> output file format. (choices: "svg", "png", "dot", "all", default: "svg")
58
59
  -o, --outputFileName <value> output file name
59
60
  -i, --ignoreFilesOrFolders <filesOrFolders> comma separated list of files or folders to ignore
60
- -n, --network <network> Ethereum network (choices: "mainnet", "polygon", "bsc", "arbitrum", "ropsten", "kovan", "rinkeby", "goerli", "sepolia", default: "mainnet")
61
- -k, --apiKey <key> Etherscan, Polygonscan or BscScan API key
61
+ -n, --network <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", default: "mainnet", env: ETH_NETWORK)
62
+ -k, --apiKey <key> Blockchain explorer API key. eg Etherscan, Arbiscan, BscScan, CronoScan, FTMScan, PolygonScan or SnowTrace API key (env: SCAN_API_KEY)
62
63
  -v, --verbose run with debugging statements (default: false)
63
64
  -h, --help display help for command
64
65
 
@@ -118,12 +119,13 @@ Arguments:
118
119
  fileFolderAddress file name, base folder or contract address
119
120
 
120
121
  Options:
121
- -c, --contract <name> Contract name in local Solidity files. Not needed when using an address as the first argument as the contract name can be derived from Etherscan.
122
- -d, --data Gets the values in the storage slots from an Ethereum node. (default: false)
123
- -s, --storage <address> The address of the contract with the storage values. This will be different from the contract with the code if a proxy contract is used. This is not needed if `fileFolderAddress` is an address and the contract is not proxied.
124
- -u, --url <url> URL of the Ethereum node to get storage values if the `data` option is used. (default: "http://localhost:8545", env: NODE_URL)
125
- -bn, --block <number> Block number to get the contract storage values from. (default: "latest")
126
- -h, --help display help for command
122
+ -c, --contract <name> 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.
123
+ -cf, --contractFile <filename> Filename the contract is located in. This can include the relative path to the desired file.
124
+ -d, --data Gets the values in the storage slots from an Ethereum node. (default: false)
125
+ -s, --storage <address> The address of the contract with the storage values. This will be different from the contract with the code if a proxy contract is used. This is not needed if `fileFolderAddress` is an address and the contract is not proxied.
126
+ -u, --url <url> URL of the Ethereum node to get storage values if the `data` option is used. (default: "http://localhost:8545", env: NODE_URL)
127
+ -bn, --block <number> Block number to get the contract storage values from. (default: "latest")
128
+ -h, --help display help for command
127
129
  ```
128
130
 
129
131
  ### Flatten usage
@@ -36,7 +36,7 @@ export interface Storage {
36
36
  * @param storage is mutated with the storage values
37
37
  */
38
38
  export declare const addStorageValues: (url: string, contractAddress: string, storage: Storage, blockTag: string) => Promise<void>;
39
- export declare const convertClasses2Storages: (contractName: string, umlClasses: UmlClass[]) => Storage[];
39
+ export declare const convertClasses2Storages: (contractName: string, umlClasses: UmlClass[], contractFilename?: string) => Storage[];
40
40
  export declare const parseReferenceStorage: (attribute: Attribute, umlClass: UmlClass, otherClasses: UmlClass[], storages: Storage[]) => Storage | undefined;
41
41
  export declare const calcStorageByteSize: (attribute: Attribute, umlClass: UmlClass, otherClasses: UmlClass[]) => {
42
42
  size: number;
@@ -1,4 +1,7 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.findDimensionLength = exports.offsetStorageSlots = exports.calcSlotKey = exports.isElementary = exports.calcStorageByteSize = exports.parseReferenceStorage = exports.convertClasses2Storages = exports.addStorageValues = exports.StorageType = void 0;
4
7
  const umlClass_1 = require("./umlClass");
@@ -6,6 +9,8 @@ const associations_1 = require("./associations");
6
9
  const slotValues_1 = require("./slotValues");
7
10
  const utils_1 = require("ethers/lib/utils");
8
11
  const ethers_1 = require("ethers");
12
+ const path_1 = __importDefault(require("path"));
13
+ const debug = require('debug')('sol2uml');
9
14
  var StorageType;
10
15
  (function (StorageType) {
11
16
  StorageType["Contract"] = "Contract";
@@ -29,14 +34,23 @@ const addStorageValues = async (url, contractAddress, storage, blockTag) => {
29
34
  });
30
35
  };
31
36
  exports.addStorageValues = addStorageValues;
32
- const convertClasses2Storages = (contractName, umlClasses) => {
37
+ const convertClasses2Storages = (contractName, umlClasses, contractFilename) => {
33
38
  // Find the base UML Class from the base contract name
34
- const umlClass = umlClasses.find(({ name }) => {
35
- return name === contractName;
39
+ const umlClass = umlClasses.find(({ name, relativePath }) => {
40
+ if (!contractFilename) {
41
+ return name === contractName;
42
+ }
43
+ return (name === contractName &&
44
+ (relativePath == contractFilename ||
45
+ path_1.default.basename(relativePath) === contractFilename));
36
46
  });
37
47
  if (!umlClass) {
38
- throw Error(`Failed to find contract with name "${contractName}"`);
48
+ const contractFilenameError = contractFilename
49
+ ? ` in filename "${contractFilename}"`
50
+ : '';
51
+ throw Error(`Failed to find contract with name "${contractName}"${contractFilenameError}`);
39
52
  }
53
+ debug(`Found contract "${contractName}" in ${umlClass.absolutePath}`);
40
54
  const storages = [];
41
55
  const variables = parseVariables(umlClass, umlClasses, [], storages, []);
42
56
  storages.unshift({
@@ -1,6 +1,6 @@
1
1
  import { ASTNode } from '@solidity-parser/parser/dist/src/ast-types';
2
2
  import { UmlClass } from './umlClass';
3
- declare const networks: readonly ["mainnet", "ropsten", "kovan", "rinkeby", "goerli", "sepolia", "polygon", "bsc", "arbitrum"];
3
+ 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"];
4
4
  declare type Network = typeof networks[number];
5
5
  export declare class EtherscanParser {
6
6
  protected apikey: string;
@@ -3,12 +3,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.EtherscanParser = void 0;
6
+ exports.EtherscanParser = exports.networks = void 0;
7
7
  const axios_1 = __importDefault(require("axios"));
8
8
  const parser_1 = require("@solidity-parser/parser");
9
9
  const converterAST2Classes_1 = require("./converterAST2Classes");
10
10
  const filterClasses_1 = require("./filterClasses");
11
- const networks = [
11
+ const debug = require('debug')('sol2uml');
12
+ exports.networks = [
12
13
  'mainnet',
13
14
  'ropsten',
14
15
  'kovan',
@@ -16,15 +17,26 @@ const networks = [
16
17
  'goerli',
17
18
  'sepolia',
18
19
  'polygon',
19
- 'bsc',
20
+ 'testnet.polygon',
20
21
  'arbitrum',
22
+ 'testnet.arbitrum',
23
+ 'avalanche',
24
+ 'testnet.avalanche',
25
+ 'bsc',
26
+ 'testnet.bsc',
27
+ 'crono',
28
+ 'fantom',
29
+ 'testnet.fantom',
30
+ 'moonbeam',
31
+ 'optimistic',
32
+ 'kovan-optimistic',
21
33
  ];
22
34
  class EtherscanParser {
23
35
  constructor(apikey = 'ZAD4UI2RCXCQTP38EXS3UY2MPHFU5H9KB1', network = 'mainnet') {
24
36
  this.apikey = apikey;
25
37
  this.network = network;
26
- if (!networks.includes(network)) {
27
- throw new Error(`Invalid network "${network}". Must be one of ${networks}`);
38
+ if (!exports.networks.includes(network)) {
39
+ throw new Error(`Invalid network "${network}". Must be one of ${exports.networks}`);
28
40
  }
29
41
  else if (network === 'mainnet') {
30
42
  this.url = 'https://api.etherscan.io/api';
@@ -33,12 +45,52 @@ class EtherscanParser {
33
45
  this.url = 'https://api.polygonscan.com/api';
34
46
  this.apikey = 'AMHGNTV5A7XYGX2M781JB3RC1DZFVRWQEB';
35
47
  }
48
+ else if (network === 'testnet.polygon') {
49
+ this.url = 'https://api-testnet.polygonscan.com/api';
50
+ this.apikey = 'AMHGNTV5A7XYGX2M781JB3RC1DZFVRWQEB';
51
+ }
52
+ else if (network === 'arbitrum') {
53
+ this.url = 'https://api.arbiscan.io/api';
54
+ this.apikey = 'ZGTK2TAGWMAB6IAC12BMK8YYPNCPIM8VDQ';
55
+ }
56
+ else if (network === 'testnet.arbitrum') {
57
+ this.url = 'https://api-testnet.arbiscan.io/api';
58
+ this.apikey = 'ZGTK2TAGWMAB6IAC12BMK8YYPNCPIM8VDQ';
59
+ }
60
+ else if (network === 'avalanche') {
61
+ this.url = 'https://api.snowtrace.io/api';
62
+ this.apikey = 'U5FAN98S5XNH5VI83TI4H35R9I4TDCKEJY';
63
+ }
64
+ else if (network === 'testnet.avalanche') {
65
+ this.url = 'https://api-testnet.snowtrace.io/api';
66
+ this.apikey = 'U5FAN98S5XNH5VI83TI4H35R9I4TDCKEJY';
67
+ }
36
68
  else if (network === 'bsc') {
37
69
  this.url = 'https://api.bscscan.com/api';
70
+ }
71
+ else if (network === 'testnet.bsc') {
72
+ this.url = 'https://api-testnet.bscscan.com/api';
73
+ }
74
+ else if (network === 'crono') {
75
+ this.url = 'https://api.cronoscan.com/api';
76
+ this.apikey = '76A3RG5WHTPMMR66E9SFI2EIDT6MP976W2';
38
77
  this.apikey = 'APYH49FXVY9UA3KTDI6F4WP3KPIC86NITN';
39
78
  }
40
- else if (network === 'arbitrum') {
41
- this.url = 'https://api.arbiscan.io/api';
79
+ else if (network === 'fantom') {
80
+ this.url = 'https://api.ftmscan.com/api';
81
+ this.apikey = '71KRX13XPZMGR3D1Q85W78G2DSZ4JPMAEX';
82
+ }
83
+ else if (network === 'testnet.fantom') {
84
+ this.url = 'https://api-testnet.ftmscan.com/api';
85
+ this.apikey = '71KRX13XPZMGR3D1Q85W78G2DSZ4JPMAEX';
86
+ }
87
+ else if (network === 'optimistic' || network === 'kovan-optimistic') {
88
+ this.url = `https://api-${network}.etherscan.io/api`;
89
+ this.apikey = 'FEXS1HXVA4Y2RNTMEA8V1UTK21S4JWHH9U';
90
+ }
91
+ else if (network === 'moonbeam') {
92
+ this.url = 'https://api-moonbeam.moonscan.io/api';
93
+ this.apikey = '5EUFXW6TDC16VERF3D9SCWRRU6AEMTBHNJ';
42
94
  }
43
95
  else {
44
96
  this.url = `https://api-${network}.etherscan.io/api`;
@@ -130,6 +182,7 @@ class EtherscanParser {
130
182
  async getSourceCode(contractAddress) {
131
183
  const description = `get verified source code for address ${contractAddress} from Etherscan API.`;
132
184
  try {
185
+ debug(`About to get Solidity source code for ${contractAddress} from ${this.url}`);
133
186
  const response = await axios_1.default.get(this.url, {
134
187
  params: {
135
188
  module: 'contract',
package/lib/sol2uml.js CHANGED
@@ -10,7 +10,12 @@ const converterClasses2Storage_1 = require("./converterClasses2Storage");
10
10
  const converterStorage2Dot_1 = require("./converterStorage2Dot");
11
11
  const regEx_1 = require("./utils/regEx");
12
12
  const writerFiles_1 = require("./writerFiles");
13
+ const path_1 = require("path");
13
14
  const program = new commander_1.Command();
15
+ const version = (0, path_1.basename)(__dirname) === 'lib'
16
+ ? require('../package.json').version // used when run from compile js in /lib
17
+ : require('../../package.json').version; // used when run from TypeScript source files under src/ts via ts-node
18
+ program.version(version);
14
19
  const debugControl = require('debug');
15
20
  const debug = require('debug')('sol2uml');
16
21
  program
@@ -29,20 +34,10 @@ The Solidity code can be pulled from verified source code on Blockchain explorer
29
34
  .option('-o, --outputFileName <value>', 'output file name')
30
35
  .option('-i, --ignoreFilesOrFolders <filesOrFolders>', 'comma separated list of files or folders to ignore')
31
36
  .addOption(new commander_1.Option('-n, --network <network>', 'Ethereum network')
32
- .choices([
33
- 'mainnet',
34
- 'polygon',
35
- 'bsc',
36
- 'arbitrum',
37
- 'ropsten',
38
- 'kovan',
39
- 'rinkeby',
40
- 'goerli',
41
- 'sepolia',
42
- ])
37
+ .choices(parserEtherscan_1.networks)
43
38
  .default('mainnet')
44
39
  .env('ETH_NETWORK'))
45
- .addOption(new commander_1.Option('-k, --apiKey <key>', 'Etherscan, Polygonscan, BscScan or Arbiscan API key').env('SCAN_API_KEY'))
40
+ .addOption(new commander_1.Option('-k, --apiKey <key>', 'Blockchain explorer API key. eg Etherscan, Arbiscan, BscScan, CronoScan, FTMScan, PolygonScan or SnowTrace API key').env('SCAN_API_KEY'))
46
41
  .option('-v, --verbose', 'run with debugging statements', false);
47
42
  program
48
43
  .command('class', { isDefault: true })
@@ -89,7 +84,8 @@ If an Ethereum address with a 0x prefix is passed, the verified source code from
89
84
  debug(`Finished generating UML`);
90
85
  }
91
86
  catch (err) {
92
- console.error(`Failed to generate UML diagram\n${err.stack}`);
87
+ console.error(err);
88
+ process.exit(2);
93
89
  }
94
90
  });
95
91
  program
@@ -99,7 +95,8 @@ program
99
95
 
100
96
  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.`)
101
97
  .argument('<fileFolderAddress>', 'file name, base folder or contract address')
102
- .option('-c, --contract <name>', 'Contract name in local Solidity files. Not needed when using an address as the first argument as the contract name can be derived from Etherscan.')
98
+ .option('-c, --contract <name>', '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.')
99
+ .option('-cf, --contractFile <filename>', 'Filename the contract is located in. This can include the relative path to the desired file.')
103
100
  .option('-d, --data', 'Gets the values in the storage slots from an Ethereum node.', false)
104
101
  .option('-s, --storage <address>', 'The address of the contract with the storage values. This will be different from the contract with the code if a proxy contract is used. This is not needed if `fileFolderAddress` is an address and the contract is not proxied.')
105
102
  .addOption(new commander_1.Option('-u, --url <url>', 'URL of the Ethereum node to get storage values if the `data` option is used.')
@@ -114,7 +111,7 @@ WARNING: sol2uml does not use the Solidity compiler so may differ with solc. A k
114
111
  };
115
112
  let { umlClasses, contractName } = await (0, parserGeneral_1.parserUmlClasses)(fileFolderAddress, combinedOptions);
116
113
  contractName = combinedOptions.contract || contractName;
117
- const storages = (0, converterClasses2Storage_1.convertClasses2Storages)(contractName, umlClasses);
114
+ const storages = (0, converterClasses2Storage_1.convertClasses2Storages)(contractName, umlClasses, combinedOptions.contractFile);
118
115
  if ((0, regEx_1.isAddress)(fileFolderAddress)) {
119
116
  // The first storage is the contract
120
117
  storages[0].address = fileFolderAddress;
@@ -129,7 +126,7 @@ WARNING: sol2uml does not use the Solidity compiler so may differ with solc. A k
129
126
  }
130
127
  else {
131
128
  if (!(0, regEx_1.isAddress)(fileFolderAddress)) {
132
- throw Error(`Can not get storage slot values if first param is not an address and the \`address\` option is not used.`);
129
+ throw Error(`Can not get storage slot values if first param is not an address and the \`--storage\` option is not used.`);
133
130
  }
134
131
  storageAddress = fileFolderAddress;
135
132
  }
@@ -142,7 +139,8 @@ WARNING: sol2uml does not use the Solidity compiler so may differ with solc. A k
142
139
  await (0, writerFiles_1.writeOutputFiles)(dotString, fileFolderAddress, contractName || 'storageDiagram', combinedOptions.outputFormat, combinedOptions.outputFileName);
143
140
  }
144
141
  catch (err) {
145
- console.error(`Failed to generate storage diagram.\n${err.stack}`);
142
+ console.error(err.stack);
143
+ process.exit(2);
146
144
  }
147
145
  });
148
146
  program
@@ -169,7 +167,8 @@ In order for the merged code to compile, the following is done:
169
167
  await (0, writerFiles_1.writeSolidity)(solidityCode, outputFilename);
170
168
  }
171
169
  catch (err) {
172
- console.error(`Failed to flatten files.\n${err.stack}`);
170
+ console.error(err);
171
+ process.exit(2);
173
172
  }
174
173
  });
175
174
  program.on('option:verbose', () => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sol2uml",
3
- "version": "2.1.2",
4
- "description": "Unified Modeling Language (UML) class diagram generator for Solidity contracts",
3
+ "version": "2.1.5",
4
+ "description": "Solidity contract visualisation tool.",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./lib/index.d.ts",
7
7
  "scripts": {
@@ -54,14 +54,15 @@
54
54
  "UML",
55
55
  "Solidity",
56
56
  "Ethereum",
57
- "Class diagram",
58
- "Class",
59
57
  "diagram",
60
- "Contract",
61
- "Interface",
62
- "Relationship",
63
- "SVG",
58
+ "class",
59
+ "diagram",
60
+ "contract",
64
61
  "Blockchain",
65
- "Model"
62
+ "storage",
63
+ "flatten",
64
+ "visual",
65
+ "tool",
66
+ "cli"
66
67
  ]
67
68
  }