sol2uml 2.1.1 → 2.1.2

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,12 +44,11 @@ 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
 
@@ -73,7 +72,6 @@ Commands:
73
72
  ### Class usage
74
73
 
75
74
  ```
76
- $sol2uml class --help
77
75
  Usage: sol2uml class <fileFolderAddress> [options]
78
76
 
79
77
  Generates UML diagrams from Solidity source code.
@@ -110,20 +108,19 @@ Options:
110
108
  ### Storage usage
111
109
 
112
110
  ```
113
- Usage: sol2uml storage [options] <fileFolderAddress>
114
-
115
- Visually display a contract's storage slots.
111
+ Usage: sol2uml storage <fileFolderAddress> [options]
116
112
 
117
113
  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
114
 
115
+ Visually display a contract's storage slots.
116
+
119
117
  Arguments:
120
118
  fileFolderAddress file name, base folder or contract address
121
119
 
122
120
  Options:
123
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.
124
122
  -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.
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.
127
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)
128
125
  -bn, --block <number> Block number to get the contract storage values from. (default: "latest")
129
126
  -h, --help display help for command
@@ -132,17 +129,20 @@ Options:
132
129
  ### Flatten usage
133
130
 
134
131
  ```
135
- $sol2uml flatten --help
136
- Usage: sol2uml flatten [options] <contractAddress>
132
+ Usage: sol2uml flatten <contractAddress> [options]
133
+
134
+ In order for the merged code to compile, the following is done:
135
+ 1. File imports are commented out.
136
+ 2. "SPDX-License-Identifier" is renamed to "SPDX--License-Identifier".
137
+ 3. Contract dependencies are analysed so the files are merged in an order that will compile.
137
138
 
138
- get all verified source code for a contract from the Blockchain explorer into one local file
139
+ Merges verified source files for a contract from a Blockchain explorer into one local file.
139
140
 
140
141
  Arguments:
141
- contractAddress Contract address
142
+ contractAddress Contract address in hexadecimal format with a 0x prefix.
142
143
 
143
144
  Options:
144
145
  -h, --help display help for command
145
-
146
146
  ```
147
147
 
148
148
  ## 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
@@ -7,6 +7,7 @@ exports.EtherscanParser = 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 filterClasses_1 = require("./filterClasses");
10
11
  const networks = [
11
12
  'mainnet',
12
13
  'ropsten',
@@ -69,8 +70,31 @@ class EtherscanParser {
69
70
  */
70
71
  async getSolidityCode(contractAddress) {
71
72
  const { files, contractName } = await this.getSourceCode(contractAddress);
73
+ // Parse the UmlClasses from the Solidity code in each file
74
+ let umlClasses = [];
75
+ for (const file of files) {
76
+ const node = await this.parseSourceCode(file.code);
77
+ const umlClass = (0, converterAST2Classes_1.convertAST2UmlClasses)(node, file.filename);
78
+ umlClasses = umlClasses.concat(umlClass);
79
+ }
80
+ // Sort the classes so dependent code is first
81
+ const topologicalSortedClasses = (0, filterClasses_1.topologicalSortClasses)(umlClasses);
82
+ // Get a list of filenames the classes are in
83
+ const sortedFilenames = topologicalSortedClasses.map((umlClass) => umlClass.relativePath);
84
+ // Remove duplicate filenames from the list
85
+ const dependentFilenames = [...new Set(sortedFilenames)];
86
+ // find any files that didn't have dependencies found
87
+ const nonDependentFiles = files.filter((f) => !dependentFilenames.includes(f.filename));
88
+ const nonDependentFilenames = nonDependentFiles.map((f) => f.filename);
72
89
  let solidityCode = '';
73
- files.forEach((file) => {
90
+ // output non dependent code before the dependent files just in case sol2uml missed some dependencies
91
+ const filenames = [...nonDependentFilenames, ...dependentFilenames];
92
+ // For each filename
93
+ filenames.forEach((filename) => {
94
+ // Lookup the file that contains the Solidity code
95
+ const file = files.find((f) => f.filename === filename);
96
+ if (!file)
97
+ throw Error(`Failed to find file with filename "${filename}"`);
74
98
  // comment out any import statements
75
99
  // match whitespace before import
76
100
  // and characters after import up to ;
package/lib/sol2uml.js CHANGED
@@ -15,10 +15,11 @@ const debugControl = require('debug');
15
15
  const debug = require('debug')('sol2uml');
16
16
  program
17
17
  .usage(`[subcommand] <options>
18
- The three subcommands:
19
- * class: Generates a UML class diagram from Solidity source code. default
18
+
19
+ sol2uml comes with three subcommands:
20
+ * class: Generates a UML class diagram from Solidity source code. (default)
20
21
  * 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.
22
+ * flatten: Merges verified source files from a Blockchain explorer into one local file.
22
23
 
23
24
  The Solidity code can be pulled from verified source code on Blockchain explorers like Etherscan or from local Solidity files.`)
24
25
  .addOption(new commander_1.Option('-sf, --subfolders <value>', 'number of subfolders that will be recursively searched for Solidity files.').default('-1', 'all'))
@@ -93,7 +94,10 @@ If an Ethereum address with a 0x prefix is passed, the verified source code from
93
94
  });
94
95
  program
95
96
  .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.")
97
+ .description("Visually display a contract's storage slots.")
98
+ .usage(`<fileFolderAddress> [options]
99
+
100
+ 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
101
  .argument('<fileFolderAddress>', 'file name, base folder or contract address')
98
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.')
99
103
  .option('-d, --data', 'Gets the values in the storage slots from an Ethereum node.', false)
@@ -143,8 +147,14 @@ program
143
147
  });
144
148
  program
145
149
  .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')
150
+ .description('Merges verified source files for a contract from a Blockchain explorer into one local file.')
151
+ .usage(`<contractAddress> [options]
152
+
153
+ In order for the merged code to compile, the following is done:
154
+ 1. File imports are commented out.
155
+ 2. "SPDX-License-Identifier" is renamed to "SPDX--License-Identifier".
156
+ 3. Contract dependencies are analysed so the files are merged in an order that will compile.`)
157
+ .argument('<contractAddress>', 'Contract address in hexadecimal format with a 0x prefix.')
148
158
  .action(async (contractAddress, options, command) => {
149
159
  try {
150
160
  debug(`About to flatten ${contractAddress}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sol2uml",
3
- "version": "2.1.1",
3
+ "version": "2.1.2",
4
4
  "description": "Unified Modeling Language (UML) class diagram generator for Solidity contracts",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./lib/index.d.ts",