sol2uml 2.1.1 → 2.1.4

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
@@ -44,22 +44,22 @@ npm ls sol2uml -g
44
44
  ## Command Line Interface (CLI)
45
45
 
46
46
  ```
47
- $ sol2uml --help
48
47
  Usage: sol2uml [subcommand] <options>
49
48
  The three subcommands:
50
49
  * class: Generates a UML class diagram from Solidity source code. default
51
50
  * storage: Generates a diagram of a contract's storage slots.
52
- * flatten: Pulls verified source files from a Blockchain explorer into one, flat, local Solidity file.
51
+ * flatten: Merges verified source files from a Blockchain explorer into one local file.
53
52
 
54
53
  The Solidity code can be pulled from verified source code on Blockchain explorers like Etherscan or from local Solidity files.
55
54
 
56
55
  Options:
56
+ -V, --version output the version number
57
57
  -sf, --subfolders <value> number of subfolders that will be recursively searched for Solidity files. (default: all)
58
58
  -f, --outputFormat <value> output file format. (choices: "svg", "png", "dot", "all", default: "svg")
59
59
  -o, --outputFileName <value> output file name
60
60
  -i, --ignoreFilesOrFolders <filesOrFolders> comma separated list of files or folders to ignore
61
- -n, --network <network> Ethereum network (choices: "mainnet", "polygon", "bsc", "arbitrum", "ropsten", "kovan", "rinkeby", "goerli", "sepolia", default: "mainnet")
62
- -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)
63
63
  -v, --verbose run with debugging statements (default: false)
64
64
  -h, --help display help for command
65
65
 
@@ -73,7 +73,6 @@ Commands:
73
73
  ### Class usage
74
74
 
75
75
  ```
76
- $sol2uml class --help
77
76
  Usage: sol2uml class <fileFolderAddress> [options]
78
77
 
79
78
  Generates UML diagrams from Solidity source code.
@@ -110,20 +109,19 @@ Options:
110
109
  ### Storage usage
111
110
 
112
111
  ```
113
- Usage: sol2uml storage [options] <fileFolderAddress>
114
-
115
- Visually display a contract's storage slots.
112
+ Usage: sol2uml storage <fileFolderAddress> [options]
116
113
 
117
114
  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.
118
115
 
116
+ Visually display a contract's storage slots.
117
+
119
118
  Arguments:
120
119
  fileFolderAddress file name, base folder or contract address
121
120
 
122
121
  Options:
123
122
  -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.
124
123
  -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
126
- the contract is not proxied.
124
+ -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.
127
125
  -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)
128
126
  -bn, --block <number> Block number to get the contract storage values from. (default: "latest")
129
127
  -h, --help display help for command
@@ -132,17 +130,20 @@ Options:
132
130
  ### Flatten usage
133
131
 
134
132
  ```
135
- $sol2uml flatten --help
136
- Usage: sol2uml flatten [options] <contractAddress>
133
+ Usage: sol2uml flatten <contractAddress> [options]
134
+
135
+ In order for the merged code to compile, the following is done:
136
+ 1. File imports are commented out.
137
+ 2. "SPDX-License-Identifier" is renamed to "SPDX--License-Identifier".
138
+ 3. Contract dependencies are analysed so the files are merged in an order that will compile.
137
139
 
138
- get all verified source code for a contract from the Blockchain explorer into one local file
140
+ Merges verified source files for a contract from a Blockchain explorer into one local file.
139
141
 
140
142
  Arguments:
141
- contractAddress Contract address
143
+ contractAddress Contract address in hexadecimal format with a 0x prefix.
142
144
 
143
145
  Options:
144
146
  -h, --help display help for command
145
-
146
147
  ```
147
148
 
148
149
  ## UML Class diagram examples
@@ -162,11 +162,10 @@ const parseReferenceStorage = (attribute, umlClass, otherClasses, storages) => {
162
162
  attributeType: baseAttributeType,
163
163
  };
164
164
  const { size: arrayItemSize } = (0, exports.calcStorageByteSize)(baseAttribute, umlClass, otherClasses);
165
- const slotSize = arrayItemSize > 16 ? 32 : arrayItemSize;
166
165
  const firstVariable = {
167
166
  id: variableId++,
168
167
  fromSlot: 0,
169
- toSlot: Math.floor((slotSize - 1) / 32),
168
+ toSlot: Math.floor((arrayItemSize - 1) / 32),
170
169
  byteSize: arrayItemSize,
171
170
  byteOffset: 0,
172
171
  type: baseType,
@@ -178,10 +177,10 @@ const parseReferenceStorage = (attribute, umlClass, otherClasses, storages) => {
178
177
  for (let i = 1; i < arrayLength; i++) {
179
178
  variables.push({
180
179
  id: variableId++,
181
- fromSlot: Math.floor((i * slotSize) / 32),
182
- toSlot: Math.floor(((i + 1) * slotSize - 1) / 32),
180
+ fromSlot: Math.floor((i * arrayItemSize) / 32),
181
+ toSlot: Math.floor(((i + 1) * arrayItemSize - 1) / 32),
183
182
  byteSize: arrayItemSize,
184
- byteOffset: (i * slotSize) % 32,
183
+ byteOffset: (i * arrayItemSize) % 32,
185
184
  type: baseType,
186
185
  dynamic,
187
186
  noValue: false,
@@ -1,6 +1,7 @@
1
1
  import { WeightedDiGraph } from 'js-graph-algorithms';
2
2
  import { UmlClass } from './umlClass';
3
3
  export declare const classesConnectedToBaseContracts: (umlClasses: UmlClass[], baseContractNames: string[], depth?: number) => UmlClass[];
4
- export declare const classesConnectedToBaseContract: (umlClasses: UmlClass[], baseContractName: string, graph: WeightedDiGraph, depth?: number) => {
4
+ export declare const classesConnectedToBaseContract: (umlClasses: UmlClass[], baseContractName: string, weightedDirectedGraph: WeightedDiGraph, depth?: number) => {
5
5
  [contractName: string]: UmlClass;
6
6
  };
7
+ export declare const topologicalSortClasses: (umlClasses: UmlClass[]) => UmlClass[];
@@ -1,21 +1,21 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.classesConnectedToBaseContract = exports.classesConnectedToBaseContracts = void 0;
3
+ exports.topologicalSortClasses = exports.classesConnectedToBaseContract = exports.classesConnectedToBaseContracts = void 0;
4
4
  const js_graph_algorithms_1 = require("js-graph-algorithms");
5
5
  const associations_1 = require("./associations");
6
6
  const classesConnectedToBaseContracts = (umlClasses, baseContractNames, depth) => {
7
7
  let filteredUmlClasses = {};
8
- const graph = loadGraph(umlClasses);
8
+ const weightedDirectedGraph = loadWeightedDirectedGraph(umlClasses);
9
9
  for (const baseContractName of baseContractNames) {
10
10
  filteredUmlClasses = {
11
11
  ...filteredUmlClasses,
12
- ...(0, exports.classesConnectedToBaseContract)(umlClasses, baseContractName, graph, depth),
12
+ ...(0, exports.classesConnectedToBaseContract)(umlClasses, baseContractName, weightedDirectedGraph, depth),
13
13
  };
14
14
  }
15
15
  return Object.values(filteredUmlClasses);
16
16
  };
17
17
  exports.classesConnectedToBaseContracts = classesConnectedToBaseContracts;
18
- const classesConnectedToBaseContract = (umlClasses, baseContractName, graph, depth = 1000) => {
18
+ const classesConnectedToBaseContract = (umlClasses, baseContractName, weightedDirectedGraph, depth = 1000) => {
19
19
  // Find the base UML Class from the base contract name
20
20
  const baseUmlClass = umlClasses.find(({ name }) => {
21
21
  return name === baseContractName;
@@ -23,7 +23,7 @@ const classesConnectedToBaseContract = (umlClasses, baseContractName, graph, dep
23
23
  if (!baseUmlClass) {
24
24
  throw Error(`Failed to find base contract with name "${baseContractName}"`);
25
25
  }
26
- const dfs = new js_graph_algorithms_1.Dijkstra(graph, baseUmlClass.id);
26
+ const dfs = new js_graph_algorithms_1.Dijkstra(weightedDirectedGraph, baseUmlClass.id);
27
27
  // Get all the UML Classes that are connected to the base contract
28
28
  const filteredUmlClasses = {};
29
29
  for (const umlClass of umlClasses) {
@@ -34,8 +34,8 @@ const classesConnectedToBaseContract = (umlClasses, baseContractName, graph, dep
34
34
  return filteredUmlClasses;
35
35
  };
36
36
  exports.classesConnectedToBaseContract = classesConnectedToBaseContract;
37
- function loadGraph(umlClasses) {
38
- const graph = new js_graph_algorithms_1.WeightedDiGraph(umlClasses.length); // 6 is the number vertices in the graph
37
+ function loadWeightedDirectedGraph(umlClasses) {
38
+ const weightedDirectedGraph = new js_graph_algorithms_1.WeightedDiGraph(umlClasses.length); // the number vertices in the graph
39
39
  for (const sourceUmlClass of umlClasses) {
40
40
  for (const association of Object.values(sourceUmlClass.associations)) {
41
41
  // Find the first UML Class that matches the target class name
@@ -43,9 +43,34 @@ function loadGraph(umlClasses) {
43
43
  if (!targetUmlClass) {
44
44
  continue;
45
45
  }
46
- graph.addEdge(new js_graph_algorithms_1.Edge(sourceUmlClass.id, targetUmlClass.id, 1));
46
+ weightedDirectedGraph.addEdge(new js_graph_algorithms_1.Edge(sourceUmlClass.id, targetUmlClass.id, 1));
47
47
  }
48
48
  }
49
- return graph;
49
+ return weightedDirectedGraph;
50
50
  }
51
+ const topologicalSortClasses = (umlClasses) => {
52
+ const directedAcyclicGraph = loadDirectedAcyclicGraph(umlClasses);
53
+ const topologicalSort = new js_graph_algorithms_1.TopologicalSort(directedAcyclicGraph);
54
+ // Topological sort the class ids
55
+ const sortedUmlClassIds = topologicalSort.order().reverse();
56
+ const sortedUmlClasses = sortedUmlClassIds.map((umlClassId) =>
57
+ // Lookup the UmlClass for each class id
58
+ umlClasses.find((umlClass) => umlClass.id === umlClassId));
59
+ return sortedUmlClasses;
60
+ };
61
+ exports.topologicalSortClasses = topologicalSortClasses;
62
+ const loadDirectedAcyclicGraph = (umlClasses) => {
63
+ const directedAcyclicGraph = new js_graph_algorithms_1.DiGraph(umlClasses.length); // the number vertices in the graph
64
+ for (const sourceUmlClass of umlClasses) {
65
+ for (const association of Object.values(sourceUmlClass.associations)) {
66
+ // Find the first UML Class that matches the target class name
67
+ const targetUmlClass = (0, associations_1.findAssociatedClass)(association, sourceUmlClass, umlClasses);
68
+ if (!targetUmlClass) {
69
+ continue;
70
+ }
71
+ directedAcyclicGraph.addEdge(sourceUmlClass.id, targetUmlClass.id);
72
+ }
73
+ }
74
+ return directedAcyclicGraph;
75
+ };
51
76
  //# sourceMappingURL=filterClasses.js.map
@@ -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,11 +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
- const networks = [
10
+ const filterClasses_1 = require("./filterClasses");
11
+ const debug = require('debug')('sol2uml');
12
+ exports.networks = [
11
13
  'mainnet',
12
14
  'ropsten',
13
15
  'kovan',
@@ -15,15 +17,26 @@ const networks = [
15
17
  'goerli',
16
18
  'sepolia',
17
19
  'polygon',
18
- 'bsc',
20
+ 'testnet.polygon',
19
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',
20
33
  ];
21
34
  class EtherscanParser {
22
35
  constructor(apikey = 'ZAD4UI2RCXCQTP38EXS3UY2MPHFU5H9KB1', network = 'mainnet') {
23
36
  this.apikey = apikey;
24
37
  this.network = network;
25
- if (!networks.includes(network)) {
26
- 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}`);
27
40
  }
28
41
  else if (network === 'mainnet') {
29
42
  this.url = 'https://api.etherscan.io/api';
@@ -32,12 +45,52 @@ class EtherscanParser {
32
45
  this.url = 'https://api.polygonscan.com/api';
33
46
  this.apikey = 'AMHGNTV5A7XYGX2M781JB3RC1DZFVRWQEB';
34
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
+ }
35
68
  else if (network === 'bsc') {
36
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';
37
77
  this.apikey = 'APYH49FXVY9UA3KTDI6F4WP3KPIC86NITN';
38
78
  }
39
- else if (network === 'arbitrum') {
40
- 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';
41
94
  }
42
95
  else {
43
96
  this.url = `https://api-${network}.etherscan.io/api`;
@@ -69,8 +122,31 @@ class EtherscanParser {
69
122
  */
70
123
  async getSolidityCode(contractAddress) {
71
124
  const { files, contractName } = await this.getSourceCode(contractAddress);
125
+ // Parse the UmlClasses from the Solidity code in each file
126
+ let umlClasses = [];
127
+ for (const file of files) {
128
+ const node = await this.parseSourceCode(file.code);
129
+ const umlClass = (0, converterAST2Classes_1.convertAST2UmlClasses)(node, file.filename);
130
+ umlClasses = umlClasses.concat(umlClass);
131
+ }
132
+ // Sort the classes so dependent code is first
133
+ const topologicalSortedClasses = (0, filterClasses_1.topologicalSortClasses)(umlClasses);
134
+ // Get a list of filenames the classes are in
135
+ const sortedFilenames = topologicalSortedClasses.map((umlClass) => umlClass.relativePath);
136
+ // Remove duplicate filenames from the list
137
+ const dependentFilenames = [...new Set(sortedFilenames)];
138
+ // find any files that didn't have dependencies found
139
+ const nonDependentFiles = files.filter((f) => !dependentFilenames.includes(f.filename));
140
+ const nonDependentFilenames = nonDependentFiles.map((f) => f.filename);
72
141
  let solidityCode = '';
73
- files.forEach((file) => {
142
+ // output non dependent code before the dependent files just in case sol2uml missed some dependencies
143
+ const filenames = [...nonDependentFilenames, ...dependentFilenames];
144
+ // For each filename
145
+ filenames.forEach((filename) => {
146
+ // Lookup the file that contains the Solidity code
147
+ const file = files.find((f) => f.filename === filename);
148
+ if (!file)
149
+ throw Error(`Failed to find file with filename "${filename}"`);
74
150
  // comment out any import statements
75
151
  // match whitespace before import
76
152
  // and characters after import up to ;
@@ -106,6 +182,7 @@ class EtherscanParser {
106
182
  async getSourceCode(contractAddress) {
107
183
  const description = `get verified source code for address ${contractAddress} from Etherscan API.`;
108
184
  try {
185
+ debug(`About to get Solidity source code for ${contractAddress} from ${this.url}`);
109
186
  const response = await axios_1.default.get(this.url, {
110
187
  params: {
111
188
  module: 'contract',
package/lib/sol2uml.js CHANGED
@@ -10,15 +10,21 @@ 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
17
22
  .usage(`[subcommand] <options>
18
- The three subcommands:
19
- * class: Generates a UML class diagram from Solidity source code. default
23
+
24
+ sol2uml comes with three subcommands:
25
+ * class: Generates a UML class diagram from Solidity source code. (default)
20
26
  * storage: Generates a diagram of a contract's storage slots.
21
- * flatten: Pulls verified source files from a Blockchain explorer into one, flat, local Solidity file.
27
+ * flatten: Merges verified source files from a Blockchain explorer into one local file.
22
28
 
23
29
  The Solidity code can be pulled from verified source code on Blockchain explorers like Etherscan or from local Solidity files.`)
24
30
  .addOption(new commander_1.Option('-sf, --subfolders <value>', 'number of subfolders that will be recursively searched for Solidity files.').default('-1', 'all'))
@@ -28,20 +34,10 @@ The Solidity code can be pulled from verified source code on Blockchain explorer
28
34
  .option('-o, --outputFileName <value>', 'output file name')
29
35
  .option('-i, --ignoreFilesOrFolders <filesOrFolders>', 'comma separated list of files or folders to ignore')
30
36
  .addOption(new commander_1.Option('-n, --network <network>', 'Ethereum network')
31
- .choices([
32
- 'mainnet',
33
- 'polygon',
34
- 'bsc',
35
- 'arbitrum',
36
- 'ropsten',
37
- 'kovan',
38
- 'rinkeby',
39
- 'goerli',
40
- 'sepolia',
41
- ])
37
+ .choices(parserEtherscan_1.networks)
42
38
  .default('mainnet')
43
39
  .env('ETH_NETWORK'))
44
- .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'))
45
41
  .option('-v, --verbose', 'run with debugging statements', false);
46
42
  program
47
43
  .command('class', { isDefault: true })
@@ -88,12 +84,16 @@ If an Ethereum address with a 0x prefix is passed, the verified source code from
88
84
  debug(`Finished generating UML`);
89
85
  }
90
86
  catch (err) {
91
- console.error(`Failed to generate UML diagram\n${err.stack}`);
87
+ console.error(err);
88
+ process.exit(2);
92
89
  }
93
90
  });
94
91
  program
95
92
  .command('storage')
96
- .description("Visually display a contract's storage slots.\n\nWARNING: 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.")
93
+ .description("Visually display a contract's storage slots.")
94
+ .usage(`<fileFolderAddress> [options]
95
+
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.`)
97
97
  .argument('<fileFolderAddress>', 'file name, base folder or contract address')
98
98
  .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.')
99
99
  .option('-d, --data', 'Gets the values in the storage slots from an Ethereum node.', false)
@@ -138,13 +138,20 @@ program
138
138
  await (0, writerFiles_1.writeOutputFiles)(dotString, fileFolderAddress, contractName || 'storageDiagram', combinedOptions.outputFormat, combinedOptions.outputFileName);
139
139
  }
140
140
  catch (err) {
141
- console.error(`Failed to generate storage diagram.\n${err.stack}`);
141
+ console.error(err.stack);
142
+ process.exit(2);
142
143
  }
143
144
  });
144
145
  program
145
146
  .command('flatten')
146
- .description('get all verified source code for a contract from the Blockchain explorer into one local file')
147
- .argument('<contractAddress>', 'Contract address')
147
+ .description('Merges verified source files for a contract from a Blockchain explorer into one local file.')
148
+ .usage(`<contractAddress> [options]
149
+
150
+ In order for the merged code to compile, the following is done:
151
+ 1. File imports are commented out.
152
+ 2. "SPDX-License-Identifier" is renamed to "SPDX--License-Identifier".
153
+ 3. Contract dependencies are analysed so the files are merged in an order that will compile.`)
154
+ .argument('<contractAddress>', 'Contract address in hexadecimal format with a 0x prefix.')
148
155
  .action(async (contractAddress, options, command) => {
149
156
  try {
150
157
  debug(`About to flatten ${contractAddress}`);
@@ -159,7 +166,8 @@ program
159
166
  await (0, writerFiles_1.writeSolidity)(solidityCode, outputFilename);
160
167
  }
161
168
  catch (err) {
162
- console.error(`Failed to flatten files.\n${err.stack}`);
169
+ console.error(err);
170
+ process.exit(2);
163
171
  }
164
172
  });
165
173
  program.on('option:verbose', () => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sol2uml",
3
- "version": "2.1.1",
4
- "description": "Unified Modeling Language (UML) class diagram generator for Solidity contracts",
3
+ "version": "2.1.4",
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
  }