sol2uml 2.5.21 → 2.5.23

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
@@ -1,7 +1,7 @@
1
1
  # Solidity 2 UML
2
2
 
3
3
  [![npm (tag)](https://img.shields.io/npm/v/sol2uml)](https://www.npmjs.com/package/sol2uml)
4
- [![Twitter Follow](https://img.shields.io/twitter/follow/naddison?style=social)](https://twitter.com/naddison)
4
+ [![X Follow](https://img.shields.io/twitter/follow/naddison?style=social)](https://x.com/naddison)
5
5
 
6
6
  A visualisation tool for [Solidity](https://solidity.readthedocs.io/) contracts featuring:
7
7
  1. [Unified Modeling Language (UML)](https://en.wikipedia.org/wiki/Unified_Modeling_Language) [class diagram](https://en.wikipedia.org/wiki/Class_diagram) generator for Solidity contracts.
@@ -22,7 +22,7 @@ See an explanation of how storage diagrams work with lots of examples [here](./e
22
22
  # Install
23
23
 
24
24
  The following installation assumes [Node.js](https://nodejs.org/en/download/) has already been installed which comes with [Node Package Manager (NPM)](https://www.npmjs.com/).
25
- `sol2uml` works with node 14 or above.
25
+ `sol2uml` works with node 20 or above.
26
26
 
27
27
  To install globally so you can run `sol2uml` from anywhere
28
28
 
@@ -57,9 +57,9 @@ Options:
57
57
  -f, --outputFormat <value> output file format. (choices: "svg", "png", "dot", "all", default: "svg")
58
58
  -o, --outputFileName <value> output file name
59
59
  -i, --ignoreFilesOrFolders <names> comma-separated list of files or folders to ignore
60
- -n, --network <network> Ethereum network which maps to a blockchain explorer (choices: "mainnet", "holesky", "sepolia", "polygon", "arbitrum", "avalanche", "bsc", "crono", "fantom", "moonbeam", "optimism", "gnosis", "celo", "scroll", "base", "sonic", default: "mainnet", env: ETH_NETWORK)
61
- -e, --explorerUrl <url> Override the `network` option with a custom blockchain explorer API URL. eg Polygon Mumbai testnet https://api-testnet.polygonscan.com/api (env: EXPLORER_URL)
62
- -k, --apiKey <key> Blockchain explorer API key. eg Etherscan, Arbiscan, Optimism, BscScan, CronoScan, FTMScan, PolygonScan, SonicScan or SnowTrace API key (env: SCAN_API_KEY)
60
+ -n, --network <network> Name or chain id of the blockchain explorer. A name like `ethereum` or `base` will map to a chain id, eg 1 or 8453. Alternatively, use an integer of the chain id. Supported names: ethereum, sepolia, holesky, hoodi, arbitrum, optimism, polygon, avalanche, base, bsc, crono, fantom, sonic, gnosis, moonbeam, celo, scroll, linea, blast, berachain, zksync (default: "ethereum", env: ETH_NETWORK)
61
+ -e, --explorerUrl <url> Override the `network` option with a custom blockchain explorer API URL. eg Polygon Amoy testnet https://api-amoy.polygonscan.com/api (env: EXPLORER_URL)
62
+ -k, --apiKey <key> Blockchain explorer API key. (env: SCAN_API_KEY)
63
63
  -bc, --backColor <color> Canvas background color. "none" will use a transparent canvas. (default: "white")
64
64
  -sc, --shapeColor <color> Basic drawing color for graphics, not text (default: "black")
65
65
  -fc, --fillColor <color> Color used to fill the background of a node (default: "gray95")
@@ -81,7 +81,7 @@ Commands:
81
81
  3. File imports are commented out.
82
82
  4. "SPDX-License-Identifier" is renamed to "SPDX--License-Identifier".
83
83
  5. Contract dependencies are analysed so the files are merged in an order that will compile.
84
- diff [options] <addressA> <fileFoldersAddress> Compare verified Solidity code to another verified contract, a local file or local source files.
84
+ diff [options] <addressA> <fileFoldersAddress> Compare verified contract code on Etherscan-like explorers to another verified contract, a local file or multiple local files.
85
85
 
86
86
  The results show the comparison of contract A to B.
87
87
  The green sections are additions to contract B that are not in contract A.
@@ -106,7 +106,7 @@ Arguments:
106
106
  sol2uml 0x79fEbF6B9F76853EDBcBc913e6aAE8232cFB9De9
107
107
 
108
108
  Options:
109
- -b, --baseContractNames <names> only output contracts connected to these comma separated base contract names
109
+ -b, --baseContractNames <names> only output contracts connected to these comma-separated base contract names
110
110
  -d, --depth <value> depth of connected classes to the base contracts. 1 will only show directly connected contracts, interfaces, libraries, structs and enums. (default: all)
111
111
  -c, --clusterFolders cluster contracts into source folders (default: false)
112
112
  -hv, --hideVariables hide variables from contracts, interfaces, structs and enums (default: false)
@@ -139,8 +139,8 @@ WARNING: sol2uml does not use the Solidity compiler so may differ with solc. A k
139
139
  Arguments:
140
140
  fileFolderAddress file name, folder(s) or contract address.
141
141
  When a folder is used, all *.sol files in that folder and all sub folders are used.
142
- A comma-separated list of files and folders can also be used. For example
143
- sol2uml contracts,node_modules/openzeppelin-solidity
142
+ A comma-separated list of files and folders can also be used. For example,
143
+ sol2uml contracts,node_modules/@openzeppelin
144
144
  If an Ethereum address with a 0x prefix is passed, the verified source code from Etherscan will be used. For example
145
145
  sol2uml 0x79fEbF6B9F76853EDBcBc913e6aAE8232cFB9De9
146
146
 
@@ -203,13 +203,12 @@ Options:
203
203
  -s, --summary Only show a summary of the file differences (default: false)
204
204
  -af --aFile <value> Limit code compare to contract A source file with the full path and extension as displayed in the file summary (default: compares all source files)
205
205
  -bf --bFile <value> Contract B source file with the full path and extension as displayed in the file summary. Used if aFile is specified and the source file has been renamed (default: aFile if specified)
206
- -bn, --bNetwork <network> Ethereum network which maps to a blockchain explorer for contract B if on a different blockchain to contract A. Contract A uses the `network` option (default: value of `network` option) (choices: "mainnet", "holesky", "sepolia", "polygon", "arbitrum", "avalanche", "bsc", "crono", "fantom", "moonbeam", "optimism", "gnosis", "celo", "base")
207
- -be, --bExplorerUrl <url> Override the `bNetwork` option with custom blockchain explorer API URL for contract B if on a different blockchain to contract A. Contract A uses the `explorerUrl` (default: value of `explorerUrl` option)
208
- -bk, --bApiKey <key> Blockchain explorer API key for contract B if on a different blockchain to contract A. Contract A uses the `apiKey` option (default: value of `apiKey` option)
206
+ -bn, --bNetwork <network> Ethereum network which maps to a blockchain explorer for contract B if on a different blockchain to contract A. Contract A uses the `network` option (default: value of `network` option) (choices: "ethereum", "sepolia", "holesky", "hoodi", "arbitrum", "optimism", "polygon", "avalanche", "base", "bsc", "crono", "fantom", "sonic", "gnosis", "moonbeam", "celo", "scroll", "linea", "blast", "berachain", "zksync")
209
207
  --flatten Flatten into a single file before comparing. Only works when comparing two verified contracts, not to local files (default: false)
210
208
  --saveFiles Save the flattened contract code to the filesystem when using the `flatten` option. The file names will be the contract address with a .sol extension (default: false)
211
209
  -l, --lineBuffer <value> Minimum number of lines before and after changes (default: 4)
212
210
  -h, --help display help for command
211
+
213
212
  ```
214
213
 
215
214
  ## UML Class diagram examples
@@ -226,10 +225,10 @@ To generate a diagram of EtherDelta's contract from the verified source code on
226
225
  sol2uml class 0x8d12A197cB00D4747a1fe03395095ce2A5CC6819
227
226
  ```
228
227
 
229
- To generate a diagram of EtherDelta's contract from the verified source code on [Etherscan Ropsten](https://ropsten.etherscan.io/address/0xa19833bd291b66aB0E17b9C6d46D2Ec5fEC15190#code). The output will be a svg file `0xa19833bd291b66aB0E17b9C6d46D2Ec5fEC15190.svg` in the working folder.
228
+ To generate a diagram of a contract on [Hoodi](https://hoodi.etherscan.io/address/0x8d62350d6DfC8A928bBF5efD2e44c66034Afa7C6#code). The output will be a svg file `0x8d62350d6DfC8A928bBF5efD2e44c66034Afa7C6.svg` in the working folder.
230
229
 
231
230
  ```bash
232
- sol2uml class 0xa19833bd291b66aB0E17b9C6d46D2Ec5fEC15190 -n ropsten
231
+ sol2uml class 0x8d62350d6DfC8A928bBF5efD2e44c66034Afa7C6 -n hoodi
233
232
  ```
234
233
 
235
234
  To generate all Solidity files under some root folder and output the svg file to a specific location
@@ -355,7 +354,7 @@ npm run clean
355
354
  npm run package-lock
356
355
  npm run build
357
356
  npm run permit
358
- # make tx2uml globally available for local testing
357
+ # make sol2uml globally available for local testing
359
358
  npm link
360
359
  # check all the files are included in the npm package
361
360
  npm pack --dry-run
@@ -16,7 +16,7 @@ class SlotValueCache {
16
16
  static readSlotValues(slotKeys) {
17
17
  const cachedValues = [];
18
18
  const missingKeys = [];
19
- slotKeys.forEach((slotKey, i) => {
19
+ slotKeys.forEach((slotKey) => {
20
20
  const key = bignumber_1.BigNumber.from(slotKey).toHexString();
21
21
  if (this.slotCache[key]) {
22
22
  cachedValues.push(this.slotCache[key]);
@@ -1,2 +1,2 @@
1
1
  import { Association, UmlClass } from './umlClass';
2
- export declare const findAssociatedClass: (association: Association, sourceUmlClass: UmlClass, umlClasses: readonly UmlClass[], searchedAbsolutePaths?: string[]) => UmlClass | undefined;
2
+ export declare const findAssociatedClass: (association: Association, sourceUmlClass: UmlClass, umlClasses: readonly UmlClass[], _searchedAbsolutePaths?: string[]) => UmlClass | undefined;
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.findAssociatedClass = void 0;
4
4
  // Find the UML class linked to the association
5
- const findAssociatedClass = (association, sourceUmlClass, umlClasses, searchedAbsolutePaths = []) => {
5
+ const findAssociatedClass = (association, sourceUmlClass, umlClasses, _searchedAbsolutePaths = []) => {
6
6
  const umlClass = umlClasses.find((targetUmlClass) => {
7
7
  const targetParentClass = association.parentUmlClassName &&
8
8
  targetUmlClass.parentId !== undefined
@@ -13,16 +13,23 @@ const findAssociatedClass = (association, sourceUmlClass, umlClasses, searchedAb
13
13
  // If a link was found
14
14
  if (umlClass)
15
15
  return umlClass;
16
- // Could not find association so now need to recursively look at imports of imports
17
- // add to already recursively processed files to avoid getting stuck in circular imports
18
- searchedAbsolutePaths.push(sourceUmlClass.absolutePath);
19
- const importedType = findChainedImport(association, sourceUmlClass, umlClasses, searchedAbsolutePaths);
20
- if (importedType)
21
- return importedType;
22
- // Still could not find association so now need to recursively look for inherited types
23
- const inheritedType = findInheritedType(association, sourceUmlClass, umlClasses);
24
- if (inheritedType)
25
- return inheritedType;
16
+ // // Could not find association so now need to recursively look at imports of imports
17
+ // // add to already recursively processed files to avoid getting stuck in circular imports
18
+ // searchedAbsolutePaths.push(sourceUmlClass.absolutePath)
19
+ // const importedType = findChainedImport(
20
+ // association,
21
+ // sourceUmlClass,
22
+ // umlClasses,
23
+ // searchedAbsolutePaths,
24
+ // )
25
+ // if (importedType) return importedType
26
+ // // Still could not find association so now need to recursively look for inherited types
27
+ // const inheritedType = findInheritedType(
28
+ // association,
29
+ // sourceUmlClass,
30
+ // umlClasses,
31
+ // )
32
+ // if (inheritedType) return inheritedType
26
33
  return undefined;
27
34
  };
28
35
  exports.findAssociatedClass = findAssociatedClass;
@@ -76,7 +83,7 @@ const isAssociated = (association, sourceUmlClass, targetUmlClass, targetParentU
76
83
  importedClass.alias &&
77
84
  importedClass.className === targetUmlClass.name))));
78
85
  };
79
- const findInheritedType = (association, sourceUmlClass, umlClasses) => {
86
+ const _findInheritedType = (association, sourceUmlClass, umlClasses) => {
80
87
  // Get all realized associations.
81
88
  const parentAssociations = sourceUmlClass.getParentContracts();
82
89
  // For each parent association
@@ -103,45 +110,68 @@ const findInheritedType = (association, sourceUmlClass, umlClasses) => {
103
110
  }
104
111
  }
105
112
  // Recursively look for inherited types
106
- const targetClass = findInheritedType(association, parent, umlClasses);
113
+ const targetClass = _findInheritedType(association, parent, umlClasses);
107
114
  if (targetClass)
108
115
  return targetClass;
109
116
  }
110
117
  return undefined;
111
118
  };
112
- const findChainedImport = (association, sourceUmlClass, umlClasses, searchedRelativePaths) => {
113
- // Get all valid imports. That is, imports that do not explicitly import contracts or interfaces
114
- // or explicitly import the source class
115
- const imports = sourceUmlClass.imports.filter((i) => i.classNames.length === 0 ||
116
- i.classNames.some((cn) => (association.targetUmlClassName === cn.className &&
117
- !cn.alias) ||
118
- association.targetUmlClassName === cn.alias));
119
- // For each import
120
- for (const importDetail of imports) {
121
- // Find a class with the same absolute path as the import so we can get the new imports
122
- const newSourceUmlClass = umlClasses.find((c) => c.absolutePath === importDetail.absolutePath);
123
- if (!newSourceUmlClass) {
124
- // Could not find a class in the import file so just move onto the next loop
125
- continue;
126
- }
127
- // Avoid circular imports
128
- if (searchedRelativePaths.includes(newSourceUmlClass.absolutePath)) {
129
- // Have already recursively looked for imports of imports in this file
130
- continue;
131
- }
132
- // find class linked to the association without aliased imports
133
- const umlClass = (0, exports.findAssociatedClass)(association, newSourceUmlClass, umlClasses, searchedRelativePaths);
134
- if (umlClass)
135
- return umlClass;
136
- // find all aliased imports
137
- const aliasedImports = importDetail.classNames.filter((cn) => cn.alias);
138
- // For each aliased import
139
- for (const aliasedImport of aliasedImports) {
140
- const umlClass = (0, exports.findAssociatedClass)({ ...association, targetUmlClassName: aliasedImport.className }, newSourceUmlClass, umlClasses, searchedRelativePaths);
141
- if (umlClass)
142
- return umlClass;
143
- }
144
- }
145
- return undefined;
146
- };
119
+ // const findChainedImport = (
120
+ // association: Association,
121
+ // sourceUmlClass: UmlClass,
122
+ // umlClasses: readonly UmlClass[],
123
+ // searchedRelativePaths: string[],
124
+ // ): UmlClass | undefined => {
125
+ // // Get all valid imports. That is, imports that do not explicitly import contracts or interfaces
126
+ // // or explicitly import the source class
127
+ // const imports = sourceUmlClass.imports.filter(
128
+ // (i) =>
129
+ // i.classNames.length === 0 ||
130
+ // i.classNames.some(
131
+ // (cn) =>
132
+ // (association.targetUmlClassName === cn.className &&
133
+ // !cn.alias) ||
134
+ // association.targetUmlClassName === cn.alias,
135
+ // ),
136
+ // )
137
+ // // For each import
138
+ // for (const importDetail of imports) {
139
+ // // Find a class with the same absolute path as the import so we can get the new imports
140
+ // const newSourceUmlClass = umlClasses.find(
141
+ // (c) => c.absolutePath === importDetail.absolutePath,
142
+ // )
143
+ // if (!newSourceUmlClass) {
144
+ // // Could not find a class in the import file so just move onto the next loop
145
+ // continue
146
+ // }
147
+ // // Avoid circular imports
148
+ // if (searchedRelativePaths.includes(newSourceUmlClass.absolutePath)) {
149
+ // // Have already recursively looked for imports of imports in this file
150
+ // continue
151
+ // }
152
+ //
153
+ // // find class linked to the association without aliased imports
154
+ // const umlClass = findAssociatedClass(
155
+ // association,
156
+ // newSourceUmlClass,
157
+ // umlClasses,
158
+ // searchedRelativePaths,
159
+ // )
160
+ // if (umlClass) return umlClass
161
+ //
162
+ // // find all aliased imports
163
+ // const aliasedImports = importDetail.classNames.filter((cn) => cn.alias)
164
+ // // For each aliased import
165
+ // for (const aliasedImport of aliasedImports) {
166
+ // const umlClass = findAssociatedClass(
167
+ // { ...association, targetUmlClassName: aliasedImport.className },
168
+ // newSourceUmlClass,
169
+ // umlClasses,
170
+ // searchedRelativePaths,
171
+ // )
172
+ // if (umlClass) return umlClass
173
+ // }
174
+ // }
175
+ // return undefined
176
+ // }
147
177
  //# sourceMappingURL=associations.js.map
@@ -15,15 +15,26 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
15
15
  }) : function(o, v) {
16
16
  o["default"] = v;
17
17
  });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
25
35
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.renameFile = exports.convertAST2UmlClasses = void 0;
36
+ exports.renameFile = void 0;
37
+ exports.convertAST2UmlClasses = convertAST2UmlClasses;
27
38
  const path = __importStar(require("path"));
28
39
  const path_1 = require("path");
29
40
  const umlClass_1 = require("./umlClass");
@@ -44,11 +55,11 @@ function convertAST2UmlClasses(node, relativePath, remappings, filesystem = fals
44
55
  if (node.type === 'SourceUnit') {
45
56
  node.children.forEach((childNode) => {
46
57
  if (childNode.type === 'ContractDefinition') {
47
- let umlClass = new umlClass_1.UmlClass({
58
+ const umlClass = new umlClass_1.UmlClass({
48
59
  name: childNode.name,
49
60
  absolutePath: filesystem
50
61
  ? path.resolve(relativePath) // resolve the absolute path
51
- : relativePath,
62
+ : relativePath, // from Etherscan so don't resolve
52
63
  relativePath,
53
64
  });
54
65
  umlClasses.push(umlClass);
@@ -57,12 +68,12 @@ function convertAST2UmlClasses(node, relativePath, remappings, filesystem = fals
57
68
  }
58
69
  else if (childNode.type === 'StructDefinition') {
59
70
  debug(`Adding file level struct ${childNode.name}`);
60
- let umlClass = new umlClass_1.UmlClass({
71
+ const umlClass = new umlClass_1.UmlClass({
61
72
  name: childNode.name,
62
73
  stereotype: umlClass_1.ClassStereotype.Struct,
63
74
  absolutePath: filesystem
64
75
  ? path.resolve(relativePath) // resolve the absolute path
65
- : relativePath,
76
+ : relativePath, // from Etherscan so don't resolve
66
77
  relativePath,
67
78
  });
68
79
  parseStructDefinition(childNode, umlClass);
@@ -71,12 +82,12 @@ function convertAST2UmlClasses(node, relativePath, remappings, filesystem = fals
71
82
  }
72
83
  else if (childNode.type === 'EnumDefinition') {
73
84
  debug(`Adding file level enum ${childNode.name}`);
74
- let umlClass = new umlClass_1.UmlClass({
85
+ const umlClass = new umlClass_1.UmlClass({
75
86
  name: childNode.name,
76
87
  stereotype: umlClass_1.ClassStereotype.Enum,
77
88
  absolutePath: filesystem
78
89
  ? path.resolve(relativePath) // resolve the absolute path
79
- : relativePath,
90
+ : relativePath, // from Etherscan so don't resolve
80
91
  relativePath,
81
92
  });
82
93
  debug(`Added enum ${umlClass.name}`);
@@ -105,7 +116,7 @@ function convertAST2UmlClasses(node, relativePath, remappings, filesystem = fals
105
116
  debug(`Added filesystem import ${newImport.absolutePath} with class names ${newImport.classNames.map((i) => i.className)}`);
106
117
  imports.push(newImport);
107
118
  }
108
- catch (err) {
119
+ catch {
109
120
  debug(`Failed to resolve import ${childNode.path} from file ${relativePath}`);
110
121
  }
111
122
  }
@@ -143,7 +154,7 @@ function convertAST2UmlClasses(node, relativePath, remappings, filesystem = fals
143
154
  stereotype: umlClass_1.ClassStereotype.Constant,
144
155
  absolutePath: filesystem
145
156
  ? path.resolve(relativePath) // resolve the absolute path
146
- : relativePath,
157
+ : relativePath, // from Etherscan so don't resolve
147
158
  relativePath,
148
159
  attributes: [
149
160
  {
@@ -181,7 +192,7 @@ function convertAST2UmlClasses(node, relativePath, remappings, filesystem = fals
181
192
  stereotype: umlClass_1.ClassStereotype.Import,
182
193
  absolutePath: filesystem
183
194
  ? path.resolve(relativePath) // resolve the absolute path
184
- : relativePath,
195
+ : relativePath, // from Etherscan so don't resolve
185
196
  relativePath,
186
197
  });
187
198
  importUmlClass.imports = imports;
@@ -189,7 +200,6 @@ function convertAST2UmlClasses(node, relativePath, remappings, filesystem = fals
189
200
  }
190
201
  return umlClasses;
191
202
  }
192
- exports.convertAST2UmlClasses = convertAST2UmlClasses;
193
203
  /**
194
204
  * Parse struct definition for UML attributes and associations.
195
205
  * @param node defined in ASTNode as `StructDefinition`
@@ -616,7 +626,7 @@ function parseTypeName(typeName) {
616
626
  case 'FunctionTypeName':
617
627
  // TODO add params and return type
618
628
  return [typeName.type + '\\(\\)', umlClass_1.AttributeType.Function];
619
- case 'ArrayTypeName':
629
+ case 'ArrayTypeName': {
620
630
  const [arrayElementType] = parseTypeName(typeName.baseTypeName);
621
631
  let length = '';
622
632
  if (Number.isInteger(typeName.length)) {
@@ -630,7 +640,8 @@ function parseTypeName(typeName) {
630
640
  }
631
641
  // TODO does not currently handle Expression types like BinaryOperation
632
642
  return [arrayElementType + '[' + length + ']', umlClass_1.AttributeType.Array];
633
- case 'Mapping':
643
+ }
644
+ case 'Mapping': {
634
645
  const key = typeName.keyType?.name ||
635
646
  typeName.keyType?.namePath;
636
647
  const [valueType] = parseTypeName(typeName.valueType);
@@ -638,6 +649,7 @@ function parseTypeName(typeName) {
638
649
  'mapping\\(' + key + '=\\>' + valueType + '\\)',
639
650
  umlClass_1.AttributeType.Mapping,
640
651
  ];
652
+ }
641
653
  default:
642
654
  throw Error(`Invalid typeName ${typeName}`);
643
655
  }
@@ -651,7 +663,7 @@ function parseParameters(params) {
651
663
  if (!params || !params) {
652
664
  return [];
653
665
  }
654
- let parameters = [];
666
+ const parameters = [];
655
667
  for (const param of params) {
656
668
  const [type] = parseTypeName(param.typeName);
657
669
  parameters.push({
@@ -166,7 +166,7 @@ const dotOperators = (umlClass, vizGroup, operators, options) => {
166
166
  return b.stereotype - a.stereotype;
167
167
  });
168
168
  // Filter out any modifiers or events if options are flagged to hide them
169
- let operatorsFiltered = operatorsSortedByStereotype.filter((o) => !((options.hideModifiers === true &&
169
+ const operatorsFiltered = operatorsSortedByStereotype.filter((o) => !((options.hideModifiers === true &&
170
170
  o.stereotype === umlClass_1.OperatorStereotype.Modifier) ||
171
171
  (options.hideEvents === true &&
172
172
  o.stereotype === umlClass_1.OperatorStereotype.Event)));
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.addAssociationsToDot = exports.convertUmlClasses2Dot = void 0;
3
+ exports.convertUmlClasses2Dot = convertUmlClasses2Dot;
4
+ exports.addAssociationsToDot = addAssociationsToDot;
4
5
  const path_1 = require("path");
5
6
  const converterClass2Dot_1 = require("./converterClass2Dot");
6
7
  const umlClass_1 = require("./umlClass");
@@ -49,7 +50,6 @@ label="${codeFolder}"`;
49
50
  debug(dotString);
50
51
  return dotString;
51
52
  }
52
- exports.convertUmlClasses2Dot = convertUmlClasses2Dot;
53
53
  let subGraphCount = 0;
54
54
  function getSubGraphName(clusterFolders = false) {
55
55
  if (clusterFolders) {
@@ -104,7 +104,6 @@ function addAssociationsToDot(umlClasses, classOptions = {}) {
104
104
  }
105
105
  return dotString;
106
106
  }
107
- exports.addAssociationsToDot = addAssociationsToDot;
108
107
  function addAssociationToDot(sourceUmlClass, targetUmlClass, association, classOptions = {}) {
109
108
  // do not include library or interface associations if hidden
110
109
  // Or associations to Structs, Enums or Constants if they are hidden
@@ -151,8 +151,8 @@ const parseVariables = (umlClass, umlClasses, variables, storageSections, inheri
151
151
  const getValue = calcGetValue(attribute.attributeType, mapping);
152
152
  // Get the toSlot of the last storage item
153
153
  const lastVariable = variables[variables.length - 1];
154
- let lastToSlot = lastVariable ? lastVariable.toSlot : 0;
155
- let nextOffset = lastVariable
154
+ const lastToSlot = lastVariable ? lastVariable.toSlot : 0;
155
+ const nextOffset = lastVariable
156
156
  ? lastVariable.byteOffset + lastVariable.byteSize
157
157
  : 0;
158
158
  let fromSlot;
@@ -347,7 +347,7 @@ const parseStorageSectionFromAttribute = (attribute, umlClass, otherClasses, sto
347
347
  // Find UserDefined type can be a contract, struct or enum
348
348
  const typeClass = findTypeClass(result[1], attribute, umlClass, otherClasses);
349
349
  if (typeClass.stereotype === umlClass_1.ClassStereotype.Struct) {
350
- let variables = parseVariables(typeClass, otherClasses, [], storageSections, [], true, arrayItems, noExpandVariables);
350
+ const variables = parseVariables(typeClass, otherClasses, [], storageSections, [], true, arrayItems, noExpandVariables);
351
351
  const storageSection = {
352
352
  id: storageId++,
353
353
  name: typeClass.name,
@@ -535,7 +535,7 @@ const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
535
535
  case umlClass_1.ClassStereotype.Interface:
536
536
  case umlClass_1.ClassStereotype.Library:
537
537
  return { size: 20, dynamic: false };
538
- case umlClass_1.ClassStereotype.Struct:
538
+ case umlClass_1.ClassStereotype.Struct: {
539
539
  let structByteSize = 0;
540
540
  attributeTypeClass.attributes.forEach((structAttribute) => {
541
541
  // If next attribute is an array, then we need to start in a new slot
@@ -569,6 +569,7 @@ const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
569
569
  size: Math.ceil(structByteSize / 32) * 32,
570
570
  dynamic: false,
571
571
  };
572
+ }
572
573
  default:
573
574
  return { size: 20, dynamic: false };
574
575
  }
@@ -593,7 +594,7 @@ const calcElementaryTypeSize = (type) => {
593
594
  case 'ufixed':
594
595
  case 'fixed':
595
596
  return { size: 32, dynamic: false };
596
- default:
597
+ default: {
597
598
  const result = type.match(/[u]*(int|fixed|bytes)([0-9]+)/);
598
599
  if (result === null || !result[2]) {
599
600
  throw Error(`Failed size elementary type "${type}"`);
@@ -606,6 +607,7 @@ const calcElementaryTypeSize = (type) => {
606
607
  // If an int
607
608
  const bitSize = parseInt(result[2]);
608
609
  return { size: bitSize / 8, dynamic: false };
610
+ }
609
611
  }
610
612
  };
611
613
  const isElementary = (type) => {
@@ -619,9 +621,10 @@ const isElementary = (type) => {
619
621
  case 'ufixed':
620
622
  case 'fixed':
621
623
  return true;
622
- default:
624
+ default: {
623
625
  const result = type.match(/^[u]?(int|fixed|bytes)([0-9]+)$/);
624
626
  return result !== null;
627
+ }
625
628
  }
626
629
  };
627
630
  exports.isElementary = isElementary;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.convertStorage2Dot = exports.convertStorages2Dot = void 0;
3
+ exports.convertStorages2Dot = void 0;
4
+ exports.convertStorage2Dot = convertStorage2Dot;
4
5
  const converterClasses2Storage_1 = require("./converterClasses2Storage");
5
6
  const umlClass_1 = require("./umlClass");
6
7
  const formatters_1 = require("./utils/formatters");
@@ -113,7 +114,6 @@ function convertStorage2Dot(storageSection, dotString, options) {
113
114
  dotString += '}}"]\n';
114
115
  return dotString;
115
116
  }
116
- exports.convertStorage2Dot = convertStorage2Dot;
117
117
  const dotVariable = (variable, contractName) => {
118
118
  const port = variable.referenceSectionId !== undefined ? `<${variable.id}>` : '';
119
119
  const contractNamePrefix = variable.contractName !== contractName
@@ -27,7 +27,7 @@ export declare const compareFlattenContracts: (addressA: string, addressB: strin
27
27
  contractNameB: string;
28
28
  }>;
29
29
  export declare const diffVerified2Local: (addressA: string, etherscanParserA: EtherscanParser, fileOrBaseFolders: string[], ignoreFilesOrFolders?: string[]) => Promise<CompareContracts>;
30
- export declare const diffVerifiedContracts: (addressA: string, addressB: string, etherscanParserA: EtherscanParser, etherscanParserB: EtherscanParser, options: DiffOptions) => Promise<CompareContracts>;
30
+ export declare const diffVerifiedContracts: (addressA: string, addressB: string, etherscanParserA: EtherscanParser, etherscanParserB: EtherscanParser, _options: DiffOptions) => Promise<CompareContracts>;
31
31
  export declare const displayFileDiffSummary: (fileDiffs: DiffFiles[]) => void;
32
32
  export declare const displayFileDiffs: (fileDiffs: DiffFiles[], options?: {
33
33
  lineBuffer?: number;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.displayFileDiffs = exports.displayFileDiffSummary = exports.diffVerifiedContracts = exports.diffVerified2Local = exports.compareFlattenContracts = exports.compareVerified2Local = exports.compareVerifiedContracts = void 0;
4
4
  const clc = require('cli-color');
5
+ const fs_1 = require("fs");
5
6
  const path_1 = require("path");
6
7
  const parserFiles_1 = require("./parserFiles");
7
8
  const writerFiles_1 = require("./writerFiles");
@@ -91,12 +92,20 @@ const diffVerified2Local = async (addressA, etherscanParserA, fileOrBaseFolders,
91
92
  let bFile;
92
93
  // for each of the base folders
93
94
  for (const baseFolder of fileOrBaseFolders) {
94
- bFile = bFiles.find((bFile) => {
95
- const resolvedPath = (0, path_1.resolve)(process.cwd(), baseFolder, aFile.filename);
96
- return bFile === resolvedPath;
97
- });
98
- if (bFile) {
99
- break;
95
+ const aFileResolvedPath = (0, path_1.resolve)(process.cwd(), baseFolder, aFile.filename);
96
+ try {
97
+ // Resolve full path including symlinks. For example,
98
+ // node_modules/@openzeppelin/contracts is a symlink to
99
+ // node_modules/.pnpm/@openzeppelin+contracts@x.y.z/node_modules/@openzeppelin/contracts
100
+ const aFileRealPath = (0, fs_1.realpathSync)(aFileResolvedPath);
101
+ bFile = bFiles.find((bFile) => bFile === aFileRealPath);
102
+ if (bFile) {
103
+ // Found match of aFile in bFiles, break out of loop to try next aFile
104
+ break;
105
+ }
106
+ }
107
+ catch {
108
+ // Do nothing, continue to next bFile in loop
100
109
  }
101
110
  }
102
111
  if (bFile) {
@@ -139,7 +148,7 @@ const diffVerified2Local = async (addressA, etherscanParserA, fileOrBaseFolders,
139
148
  };
140
149
  };
141
150
  exports.diffVerified2Local = diffVerified2Local;
142
- const diffVerifiedContracts = async (addressA, addressB, etherscanParserA, etherscanParserB, options) => {
151
+ const diffVerifiedContracts = async (addressA, addressB, etherscanParserA, etherscanParserB, _options) => {
143
152
  const files = [];
144
153
  const { files: aFiles, contractName: contractNameA } = await etherscanParserA.getSourceCode(addressA);
145
154
  const { files: bFiles, contractName: contractNameB } = await etherscanParserB.getSourceCode(addressB);
@@ -4,13 +4,14 @@ export interface Remapping {
4
4
  from: RegExp;
5
5
  to: string;
6
6
  }
7
- export declare const networks: readonly ["mainnet", "holesky", "sepolia", "polygon", "arbitrum", "avalanche", "bsc", "crono", "fantom", "moonbeam", "optimism", "gnosis", "celo", "scroll", "base", "sonic"];
7
+ export declare const networks: readonly ["ethereum", "sepolia", "holesky", "hoodi", "arbitrum", "optimism", "polygon", "avalanche", "base", "bsc", "crono", "fantom", "sonic", "gnosis", "moonbeam", "celo", "scroll", "linea", "blast", "berachain", "zksync"];
8
8
  export type Network = (typeof networks)[number];
9
+ export declare const setChainId: (network: string) => number;
9
10
  export declare class EtherscanParser {
10
- protected apikey: string;
11
+ protected apiKey?: string;
11
12
  network: Network;
12
13
  readonly url: string;
13
- constructor(apikey?: string, network?: Network, url?: string);
14
+ constructor(apiKey?: string, network?: Network, url?: string);
14
15
  /**
15
16
  * Parses the verified source code files from Etherscan
16
17
  * @param contractAddress Ethereum contract address with a 0x prefix
@@ -3,7 +3,7 @@ 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.parseRemapping = exports.parseRemappings = exports.EtherscanParser = exports.networks = void 0;
6
+ exports.parseRemapping = exports.parseRemappings = exports.EtherscanParser = exports.setChainId = 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");
@@ -13,92 +13,89 @@ const path_1 = __importDefault(require("path"));
13
13
  require('axios-debug-log');
14
14
  const debug = require('debug')('sol2uml');
15
15
  exports.networks = [
16
- 'mainnet',
17
- 'holesky',
16
+ 'ethereum',
18
17
  'sepolia',
19
- 'polygon',
18
+ 'holesky',
19
+ 'hoodi',
20
20
  'arbitrum',
21
+ 'optimism',
22
+ 'polygon',
21
23
  'avalanche',
24
+ 'base',
22
25
  'bsc',
23
26
  'crono',
24
27
  'fantom',
25
- 'moonbeam',
26
- 'optimism',
28
+ 'sonic',
27
29
  'gnosis',
30
+ 'moonbeam',
28
31
  'celo',
29
32
  'scroll',
30
- 'base',
31
- 'sonic',
33
+ 'linea',
34
+ 'blast',
35
+ 'berachain',
36
+ 'zksync',
32
37
  ];
38
+ const setChainId = (network) =>
39
+ // If an integer is passed, return it as is
40
+ /^-?(0|[1-9]\d*)$/.test(network)
41
+ ? parseInt(network)
42
+ : network === 'sepolia'
43
+ ? 11155111
44
+ : network === 'holesky'
45
+ ? 17000
46
+ : network === 'hoodi'
47
+ ? 560048
48
+ : network === 'arbitrum'
49
+ ? 42161
50
+ : network === 'optimism'
51
+ ? 10
52
+ : network === 'polygon'
53
+ ? 137
54
+ : network === 'avalanche'
55
+ ? 43114
56
+ : network === 'base'
57
+ ? 8453
58
+ : network === 'bsc'
59
+ ? 56
60
+ : network === 'crono'
61
+ ? 25
62
+ : network === 'fantom'
63
+ ? 250
64
+ : network === 'sonic'
65
+ ? 146
66
+ : network === 'gnosis'
67
+ ? 100
68
+ : network === 'moonbeam'
69
+ ? 1284
70
+ : network === 'celo'
71
+ ? 42220
72
+ : network === 'scroll'
73
+ ? 534352
74
+ : network === 'linea'
75
+ ? 59144
76
+ : network === 'blast'
77
+ ? 81457
78
+ : network === 'berachain'
79
+ ? 80094
80
+ : network === 'zksync'
81
+ ? 324
82
+ : 1;
83
+ exports.setChainId = setChainId;
33
84
  class EtherscanParser {
34
- constructor(apikey = 'ZAD4UI2RCXCQTP38EXS3UY2MPHFU5H9KB1', network = 'mainnet', url) {
35
- this.apikey = apikey;
85
+ constructor(apiKey, network = 'ethereum', url) {
86
+ this.apiKey = apiKey;
36
87
  this.network = network;
37
88
  if (url) {
38
89
  this.url = url;
39
90
  return;
40
91
  }
41
- if (!exports.networks.includes(network)) {
42
- throw new Error(`Invalid network "${network}". Must be one of ${exports.networks}`);
43
- }
44
- else if (network === 'mainnet') {
45
- this.url = 'https://api.etherscan.io/api';
46
- }
47
- else if (network === 'polygon') {
48
- this.url = 'https://api.polygonscan.com/api';
49
- this.apikey = 'AMHGNTV5A7XYGX2M781JB3RC1DZFVRWQEB';
50
- }
51
- else if (network === 'arbitrum') {
52
- this.url = 'https://api.arbiscan.io/api';
53
- this.apikey = 'ZGTK2TAGWMAB6IAC12BMK8YYPNCPIM8VDQ';
54
- }
55
- else if (network === 'avalanche') {
56
- this.url = 'https://api.snowtrace.io/api';
57
- this.apikey = 'U5FAN98S5XNH5VI83TI4H35R9I4TDCKEJY';
58
- }
59
- else if (network === 'bsc') {
60
- this.url = 'https://api.bscscan.com/api';
61
- this.apikey = 'APYH49FXVY9UA3KTDI6F4WP3KPIC86NITN';
62
- }
63
- else if (network === 'crono') {
64
- this.url = 'https://api.cronoscan.com/api';
65
- this.apikey = '76A3RG5WHTPMMR66E9SFI2EIDT6MP976W2';
66
- }
67
- else if (network === 'fantom') {
68
- this.url = 'https://api.ftmscan.com/api';
69
- this.apikey = '71KRX13XPZMGR3D1Q85W78G2DSZ4JPMAEX';
70
- }
71
- else if (network === 'optimism') {
72
- this.url = `https://api-optimistic.etherscan.io/api`;
73
- this.apikey = 'FEXS1HXVA4Y2RNTMEA8V1UTK21S4JWHH9U';
74
- }
75
- else if (network === 'moonbeam') {
76
- this.url = 'https://api-moonbeam.moonscan.io/api';
77
- this.apikey = '5EUFXW6TDC16VERF3D9SCWRRU6AEMTBHNJ';
78
- }
79
- else if (network === 'gnosis') {
80
- this.url = 'https://api.gnosisscan.io/api';
81
- this.apikey = '2RWGXIWK538EJ8XSP9DE2JUINSCG7UCSJB';
82
- }
83
- else if (network === 'scroll') {
84
- this.url = 'https://api.scrollscan.com/api';
85
- this.apikey = '4V37ZJFIN9AURJSU9YG1RP3MSVTPH6D6Z4';
86
- }
87
- else if (network === 'celo') {
88
- this.url = 'https://api.celoscan.io/api';
89
- this.apikey = 'JBV78T5KP15W7WKKKD6KC4J8RX2F4PK8AF';
90
- }
91
- else if (network === 'base') {
92
- this.url = 'https://api.basescan.org/api';
93
- this.apikey = '9I5HUJHPD4ZNXJ4M8TZJ1HD2QBVP1U3M3J';
94
- }
95
- else if (network === 'sonic') {
96
- this.url = 'https://api.sonicscan.org/api';
97
- this.apikey = 'STCM7CPYP341C66C4IVV1IFMWDYRUTI1QY';
98
- }
99
- else {
100
- this.url = `https://api-${network}.etherscan.io/api`;
92
+ if (!apiKey) {
93
+ console.error(`The -k, --apiKey option or SCAN_API_KEY env var must be set when getting verified source code from an Etherscan like explorer`);
94
+ process.exit(1);
101
95
  }
96
+ const chainId = (0, exports.setChainId)(network);
97
+ debug(`Chain id ${chainId} for network ${network}`);
98
+ this.url = `https://api.etherscan.io/v2/api?chainid=${chainId}`;
102
99
  }
103
100
  /**
104
101
  * Parses the verified source code files from Etherscan
@@ -200,7 +197,7 @@ class EtherscanParser {
200
197
  module: 'contract',
201
198
  action: 'getsourcecode',
202
199
  address: contractAddress,
203
- apikey: this.apikey,
200
+ apikey: this.apiKey,
204
201
  },
205
202
  });
206
203
  if (!Array.isArray(response?.data?.result)) {
@@ -3,7 +3,10 @@ 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.isFolder = exports.isFile = exports.readFile = exports.parseSolidityFile = exports.getSolidityFilesFromFolderOrFile = exports.getSolidityFilesFromFolderOrFiles = exports.parseUmlClassesFromFiles = void 0;
6
+ exports.isFolder = exports.isFile = exports.readFile = exports.parseUmlClassesFromFiles = void 0;
7
+ exports.getSolidityFilesFromFolderOrFiles = getSolidityFilesFromFolderOrFiles;
8
+ exports.getSolidityFilesFromFolderOrFile = getSolidityFilesFromFolderOrFile;
9
+ exports.parseSolidityFile = parseSolidityFile;
7
10
  const fs_1 = require("fs");
8
11
  const path_1 = require("path");
9
12
  const klaw_1 = __importDefault(require("klaw"));
@@ -30,7 +33,6 @@ async function getSolidityFilesFromFolderOrFiles(folderOrFilePaths, ignoreFilesO
30
33
  }
31
34
  return files;
32
35
  }
33
- exports.getSolidityFilesFromFolderOrFiles = getSolidityFilesFromFolderOrFiles;
34
36
  function getSolidityFilesFromFolderOrFile(folderOrFilePath, ignoreFilesOrFolders = [], depthLimit = -1) {
35
37
  debug(`About to get Solidity files under ${folderOrFilePath}`);
36
38
  return new Promise((resolve, reject) => {
@@ -87,7 +89,6 @@ function getSolidityFilesFromFolderOrFile(folderOrFilePath, ignoreFilesOrFolders
87
89
  }
88
90
  });
89
91
  }
90
- exports.getSolidityFilesFromFolderOrFile = getSolidityFilesFromFolderOrFile;
91
92
  function parseSolidityFile(fileName) {
92
93
  const solidityCode = (0, exports.readFile)(fileName);
93
94
  try {
@@ -99,7 +100,6 @@ function parseSolidityFile(fileName) {
99
100
  });
100
101
  }
101
102
  }
102
- exports.parseSolidityFile = parseSolidityFile;
103
103
  const readFile = (fileName, extension) => {
104
104
  try {
105
105
  // try to read file with no extension
@@ -128,7 +128,7 @@ const isFile = (fileName) => {
128
128
  const file = (0, fs_1.lstatSync)(fileName);
129
129
  return file.isFile();
130
130
  }
131
- catch (err) {
131
+ catch {
132
132
  return false;
133
133
  }
134
134
  };
@@ -138,7 +138,7 @@ const isFolder = (fileName) => {
138
138
  const file = (0, fs_1.lstatSync)(fileName);
139
139
  return file.isDirectory();
140
140
  }
141
- catch (err) {
141
+ catch {
142
142
  return false;
143
143
  }
144
144
  };
@@ -16,7 +16,7 @@ const parserUmlClasses = async (fileFolderAddress, options) => {
16
16
  };
17
17
  if ((0, regEx_1.isAddress)(fileFolderAddress)) {
18
18
  debug(`argument ${fileFolderAddress} is an Ethereum address so checking Etherscan for the verified source code`);
19
- const etherscanApiKey = options.apiKey || 'ZAD4UI2RCXCQTP38EXS3UY2MPHFU5H9KB1';
19
+ const etherscanApiKey = options.apiKey;
20
20
  const etherscanParser = new parserEtherscan_1.EtherscanParser(etherscanApiKey, options.network, options.explorerUrl);
21
21
  result = await etherscanParser.getUmlClasses(fileFolderAddress);
22
22
  }
@@ -22,7 +22,7 @@ export declare const parseValue: (variable: Variable) => string;
22
22
  * @param blockTag block number or `latest`
23
23
  * @return slotValues array of 32 byte slot values as hexadecimal strings
24
24
  */
25
- export declare const getSlotValues: (url: string, contractAddress: string, slotKeys: readonly BigNumberish[], blockTag?: BigNumberish | 'latest') => Promise<string[]>;
25
+ export declare const getSlotValues: (url: string, contractAddress: string, slotKeys: readonly BigNumberish[], blockTag?: BigNumberish | "latest") => Promise<string[]>;
26
26
  /**
27
27
  * Get storage slot values from JSON-RPC API provider.
28
28
  * @param url of Ethereum JSON-RPC API provider. eg Infura or Alchemy
@@ -32,7 +32,7 @@ export declare const getSlotValues: (url: string, contractAddress: string, slotK
32
32
  * @param blockTag block number or `latest`
33
33
  * @return slotValue 32 byte slot value as hexadecimal string
34
34
  */
35
- export declare const getSlotValue: (url: string, contractAddress: string, slotKey: BigNumberish, blockTag: BigNumberish | 'latest') => Promise<string>;
35
+ export declare const getSlotValue: (url: string, contractAddress: string, slotKey: BigNumberish, blockTag: BigNumberish | "latest") => Promise<string>;
36
36
  /**
37
37
  * Calculates the number of string characters or bytes of a string or bytes type.
38
38
  * See the following for how string and bytes are stored in storage slots
package/lib/slotValues.js CHANGED
@@ -46,7 +46,7 @@ const addSlotValues = async (url, contractAddress, storageSection, arrayItems, b
46
46
  // remove duplicate slot numbers
47
47
  const uniqueFromSlots = [...new Set(slots)];
48
48
  // Convert slot numbers to BigNumbers and offset dynamic arrays
49
- let slotKeys = uniqueFromSlots.map((fromSlot) => {
49
+ const slotKeys = uniqueFromSlots.map((fromSlot) => {
50
50
  if (storageSection.offset) {
51
51
  return bignumber_1.BigNumber.from(storageSection.offset).add(fromSlot);
52
52
  }
package/lib/sol2uml.js CHANGED
@@ -30,12 +30,12 @@ Can also flatten or compare verified source files on Etherscan-like explorers.`)
30
30
  .default('svg'))
31
31
  .option('-o, --outputFileName <value>', 'output file name')
32
32
  .option('-i, --ignoreFilesOrFolders <names>', 'comma-separated list of files or folders to ignore', validators_1.validateNames)
33
- .addOption(new commander_1.Option('-n, --network <network>', 'Ethereum network which maps to a blockchain explorer')
34
- .choices(parserEtherscan_1.networks)
35
- .default('mainnet')
33
+ .addOption(new commander_1.Option('-n, --network <network>', 'Name or chain id of the blockchain explorer. A name like `ethereum` or `base` will map to a chain id, eg 1 or 8453. Alternatively, use an integer of the chain id. Supported names: ' +
34
+ parserEtherscan_1.networks.join(', '))
35
+ .default('ethereum')
36
36
  .env('ETH_NETWORK'))
37
37
  .addOption(new commander_1.Option('-e, --explorerUrl <url>', 'Override the `network` option with a custom blockchain explorer API URL. eg Polygon Mumbai testnet https://api-testnet.polygonscan.com/api').env('EXPLORER_URL'))
38
- .addOption(new commander_1.Option('-k, --apiKey <key>', 'Blockchain explorer API key. eg Etherscan, Arbiscan, Optimism, BscScan, CronoScan, FTMScan, PolygonScan, SonicScan or SnowTrace API key').env('SCAN_API_KEY'))
38
+ .addOption(new commander_1.Option('-k, --apiKey <key>', 'Blockchain explorer API key.').env('SCAN_API_KEY'))
39
39
  .option('-bc, --backColor <color>', 'Canvas background color. "none" will use a transparent canvas.', 'white')
40
40
  .option('-sc, --shapeColor <color>', 'Basic drawing color for graphics, not text', 'black')
41
41
  .option('-fc, --fillColor <color>', 'Color used to fill the background of a node', 'gray95')
@@ -171,7 +171,7 @@ WARNING: sol2uml does not use the Solidity compiler so may differ with solc. A k
171
171
  }
172
172
  storageAddress = fileFolderAddress;
173
173
  }
174
- let block = await (0, block_1.getBlock)(combinedOptions);
174
+ const block = await (0, block_1.getBlock)(combinedOptions);
175
175
  // Get slot values for each storage section
176
176
  for (const storageSection of storageSections) {
177
177
  await (0, slotValues_1.addSlotValues)(combinedOptions.url, storageAddress, storageSection, arrayItems, block);
@@ -235,8 +235,6 @@ The line numbers are from contract B. There are no line numbers for the red sect
235
235
  .option('-af --aFile <value>', 'Limit code compare to contract A source file with the full path and extension as displayed in the file summary (default: compares all source files)')
236
236
  .option('-bf --bFile <value>', 'Contract B source file with the full path and extension as displayed in the file summary. Used if aFile is specified and the source file has been renamed (default: aFile if specified)')
237
237
  .addOption(new commander_1.Option('-bn, --bNetwork <network>', 'Ethereum network which maps to a blockchain explorer for contract B if on a different blockchain to contract A. Contract A uses the `network` option (default: value of `network` option)').choices(parserEtherscan_1.networks))
238
- .option('-be, --bExplorerUrl <url>', 'Override the `bNetwork` option with custom blockchain explorer API URL for contract B if on a different blockchain to contract A. Contract A uses the `explorerUrl` (default: value of `explorerUrl` option)')
239
- .option('-bk, --bApiKey <key>', 'Blockchain explorer API key for contract B if on a different blockchain to contract A. Contract A uses the `apiKey` option (default: value of `apiKey` option)')
240
238
  .option('--flatten', 'Flatten into a single file before comparing. Only works when comparing two verified contracts, not to local files', false)
241
239
  .option('--saveFiles', 'Save the flattened contract code to the filesystem when using the `flatten` option. The file names will be the contract address with a .sol extension', false)
242
240
  .option('-l, --lineBuffer <value>', 'Minimum number of lines before and after changes (default: 4)', validators_1.validateLineBuffer)
@@ -250,7 +248,7 @@ The line numbers are from contract B. There are no line numbers for the red sect
250
248
  const aEtherscanParser = new parserEtherscan_1.EtherscanParser(combinedOptions.apiKey, combinedOptions.network, combinedOptions.explorerUrl);
251
249
  if ((0, regEx_1.isAddress)(fileFoldersAddress)) {
252
250
  const addressB = fileFoldersAddress;
253
- const bEtherscanParser = new parserEtherscan_1.EtherscanParser(combinedOptions.bApiKey || combinedOptions.apiKey, combinedOptions.bNetwork || combinedOptions.network, combinedOptions.bExplorerUrl || combinedOptions.explorerUrl);
251
+ const bEtherscanParser = new parserEtherscan_1.EtherscanParser(combinedOptions.apiKey, combinedOptions.bNetwork || combinedOptions.network, combinedOptions.explorerUrl);
254
252
  // If flattening
255
253
  if (options.flatten) {
256
254
  await (0, diffContracts_1.compareFlattenContracts)(addressA, addressB, aEtherscanParser, bEtherscanParser, combinedOptions);
@@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
15
15
  }) : function(o, v) {
16
16
  o["default"] = v;
17
17
  });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
25
35
  Object.defineProperty(exports, "__esModule", { value: true });
26
36
  exports.squashUmlClasses = void 0;
27
37
  const umlClass_1 = require("./umlClass");
@@ -37,14 +47,14 @@ const squashUmlClasses = (umlClasses, baseContractNames) => {
37
47
  let removedClassIds = [];
38
48
  for (const baseContractName of baseContractNames) {
39
49
  // Find the base UML Class to squash
40
- let baseIndex = umlClasses.findIndex(({ name }) => {
50
+ const baseIndex = umlClasses.findIndex(({ name }) => {
41
51
  return name === baseContractName;
42
52
  });
43
53
  if (baseIndex === undefined) {
44
54
  throw Error(`Failed to find contract with name "${baseContractName}" to squash`);
45
55
  }
46
56
  const baseClass = umlClasses[baseIndex];
47
- let squashedClass = new umlClass_1.UmlClass({
57
+ const squashedClass = new umlClass_1.UmlClass({
48
58
  name: baseClass.name,
49
59
  absolutePath: baseClass.absolutePath,
50
60
  relativePath: baseClass.relativePath,
@@ -11,7 +11,7 @@ const getBlock = async (options) => {
11
11
  debug(`Latest block is ${block}. All storage slot values will be from this block.`);
12
12
  return block;
13
13
  }
14
- catch (err) {
14
+ catch {
15
15
  const defaultMessage = options.url === 'http://localhost:8545'
16
16
  ? 'This is the default url. Use the `-u, --url` option or `NODE_URL` environment variable to set the url of your blockchain node.'
17
17
  : `Check your --url option or NODE_URL environment variable is pointing to the correct node for the "${options.network}" blockchain.`;
@@ -21,7 +21,7 @@ const getBlock = async (options) => {
21
21
  try {
22
22
  return parseInt(options.block);
23
23
  }
24
- catch (err) {
24
+ catch {
25
25
  throw Error(`Invalid block number: ${options.block}`);
26
26
  }
27
27
  };
package/lib/utils/diff.js CHANGED
@@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
15
15
  }) : function(o, v) {
16
16
  o["default"] = v;
17
17
  });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
25
35
  Object.defineProperty(exports, "__esModule", { value: true });
26
36
  exports.diffCode = void 0;
27
37
  const diff_match_patch_1 = __importStar(require("diff-match-patch"));
@@ -34,7 +44,7 @@ const SkippedLinesMarker = `\n---`;
34
44
  * @param lineBuff the number of lines to display before and after each change.
35
45
  */
36
46
  const diffCode = (codeA, codeB, lineBuff) => {
37
- // @ts-ignore
47
+ // @ts-ignore diff_match_patch constructor has no type declarations
38
48
  const dmp = new diff_match_patch_1.default();
39
49
  const diff = dmp.diff_main(codeA, codeB);
40
50
  dmp.diff_cleanupSemantic(diff);
@@ -60,18 +70,20 @@ const diff_pretty = (diffs, lines, lineBuff = 2) => {
60
70
  const op = diff[0]; // Operation (insert, delete, equal)
61
71
  const text = diff[1]; // Text of change.
62
72
  switch (op) {
63
- case diff_match_patch_1.DIFF_INSERT:
73
+ case diff_match_patch_1.DIFF_INSERT: {
64
74
  // If first diff then we need to add the first line number
65
75
  const linesInserted = addLineNumbers(text, lineCount, linePad);
66
76
  output += initialLineNumber + clc.green(linesInserted);
67
77
  lineCount += countLines(text);
68
78
  break;
69
- case diff_match_patch_1.DIFF_DELETE:
79
+ }
80
+ case diff_match_patch_1.DIFF_DELETE: {
70
81
  // zero start line means blank line numbers are used
71
82
  const linesDeleted = addLineNumbers(text, 0, linePad);
72
83
  output += initialLineNumber + clc.red(linesDeleted);
73
84
  break;
74
- case diff_match_patch_1.DIFF_EQUAL:
85
+ }
86
+ case diff_match_patch_1.DIFF_EQUAL: {
75
87
  const eolPositions = findEOLPositions(text);
76
88
  // If no changes yet
77
89
  if (diffIndex <= 1) {
@@ -87,6 +99,7 @@ const diff_pretty = (diffs, lines, lineBuff = 2) => {
87
99
  }
88
100
  lineCount += eolPositions.length;
89
101
  break;
102
+ }
90
103
  }
91
104
  }
92
105
  output += '\n';
@@ -168,7 +181,7 @@ const countLines = (text) => (text.match(/\n/g) || '').length;
168
181
  const addLineNumbers = (text, lineStart, linePad) => {
169
182
  let lineCount = lineStart;
170
183
  let textWithLineNumbers = '';
171
- text.split('').forEach((c, i) => {
184
+ text.split('').forEach((c) => {
172
185
  if (c === '\n') {
173
186
  if (lineStart > 0) {
174
187
  textWithLineNumbers += `\n${(++lineCount)
@@ -11,7 +11,7 @@ const validateAddress = (address) => {
11
11
  if (typeof address === 'string' && address?.match(regEx_1.ethereumAddress))
12
12
  return (0, utils_1.getAddress)(address);
13
13
  }
14
- catch (err) { }
14
+ catch { /* validation failed */ }
15
15
  throw new commander_1.InvalidArgumentError(`Address must be in hexadecimal format with a 0x prefix.`);
16
16
  };
17
17
  exports.validateAddress = validateAddress;
@@ -22,7 +22,7 @@ const validateNames = (variables) => {
22
22
  variables.match(regEx_1.commaSeparatedList))
23
23
  return variables.split(',');
24
24
  }
25
- catch (err) { }
25
+ catch { /* validation failed */ }
26
26
  throw new commander_1.InvalidArgumentError(`Must be a comma-separate list of names with no white spaces.`);
27
27
  };
28
28
  exports.validateNames = validateNames;
@@ -32,7 +32,7 @@ const validateLineBuffer = (lineBufferParam) => {
32
32
  if (lineBuffer >= 0)
33
33
  return lineBuffer;
34
34
  }
35
- catch (err) { }
35
+ catch { /* validation failed */ }
36
36
  throw new commander_1.InvalidOptionArgumentError(`Must be a zero or a positive integer.`);
37
37
  };
38
38
  exports.validateLineBuffer = validateLineBuffer;
@@ -56,7 +56,7 @@ const validateSlotNames = (slotNames) => {
56
56
  console.log(results.length);
57
57
  return results;
58
58
  }
59
- catch (err) { }
59
+ catch { /* validation failed */ }
60
60
  throw new commander_1.InvalidOptionArgumentError(`Must be a comma-separate list of slots with no white spaces.`);
61
61
  };
62
62
  exports.validateSlotNames = validateSlotNames;
@@ -72,7 +72,7 @@ const validateTypes = (typesString) => {
72
72
  return types;
73
73
  }
74
74
  }
75
- catch (err) { }
75
+ catch { /* validation failed */ }
76
76
  throw new commander_1.InvalidArgumentError(`Slot type must be an elementary type which includes dynamic and fixed size arrays. eg address, address[], uint256, int256[2], bytes32, string, bool`);
77
77
  };
78
78
  exports.validateTypes = validateTypes;
@@ -3,7 +3,12 @@ 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.writePng = exports.writeSVG = exports.writeDot = exports.writeSourceCode = exports.convertDot2Svg = exports.writeOutputFiles = void 0;
6
+ exports.writeOutputFiles = void 0;
7
+ exports.convertDot2Svg = convertDot2Svg;
8
+ exports.writeSourceCode = writeSourceCode;
9
+ exports.writeDot = writeDot;
10
+ exports.writeSVG = writeSVG;
11
+ exports.writePng = writePng;
7
12
  const fs_1 = require("fs");
8
13
  const path_1 = __importDefault(require("path"));
9
14
  const sync_1 = __importDefault(require("@aduh95/viz.js/sync"));
@@ -34,7 +39,7 @@ const writeOutputFiles = async (dot, contractName, outputFormat = 'svg', outputF
34
39
  outputExt;
35
40
  }
36
41
  }
37
- catch (err) { } // we can ignore errors as it just means outputFilename does not exist yet
42
+ catch { /* outputFilename does not exist yet */ }
38
43
  }
39
44
  if (outputFormat === 'dot' || outputFormat === 'all') {
40
45
  writeDot(dot, outputFilename);
@@ -63,7 +68,6 @@ function convertDot2Svg(dot) {
63
68
  throw new Error(`Failed to parse dot string`, { cause: err });
64
69
  }
65
70
  }
66
- exports.convertDot2Svg = convertDot2Svg;
67
71
  function writeSourceCode(code, filename = 'source', extension = '.sol') {
68
72
  const fileExtension = path_1.default.extname(filename);
69
73
  const outputFile = fileExtension === extension ? filename : filename + extension;
@@ -79,7 +83,6 @@ function writeSourceCode(code, filename = 'source', extension = '.sol') {
79
83
  }
80
84
  });
81
85
  }
82
- exports.writeSourceCode = writeSourceCode;
83
86
  function writeDot(dot, filename) {
84
87
  const dotFilename = changeFileExtension(filename, 'dot');
85
88
  debug(`About to write Dot file to ${dotFilename}`);
@@ -94,7 +97,6 @@ function writeDot(dot, filename) {
94
97
  }
95
98
  });
96
99
  }
97
- exports.writeDot = writeDot;
98
100
  /**
99
101
  * Writes an SVG file to the file system.
100
102
  * @param svg The SVG input to be written to the file system.
@@ -127,7 +129,6 @@ function writeSVG(svg, svgFilename = 'classDiagram.svg', outputFormats = 'png')
127
129
  });
128
130
  });
129
131
  }
130
- exports.writeSVG = writeSVG;
131
132
  /**
132
133
  * Asynchronously writes a PNG file to the file system from an SVG input.
133
134
  * @param svg - The SVG input to be converted to a PNG file.
@@ -161,7 +162,6 @@ async function writePng(svg, filename) {
161
162
  });
162
163
  }
163
164
  }
164
- exports.writePng = writePng;
165
165
  // put a new file extension on a filename
166
166
  const changeFileExtension = (filename, extension) => {
167
167
  const parsedFile = path_1.default.parse(filename);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sol2uml",
3
- "version": "2.5.21",
3
+ "version": "2.5.23",
4
4
  "description": "Solidity contract visualisation tool.",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./lib/index.d.ts",
@@ -8,6 +8,7 @@
8
8
  "buildSol": "cd ./src/contracts && solc **/*.sol",
9
9
  "build": "tsc --build ./tsconfig.json",
10
10
  "clean": "tsc --build --clean ./tsconfig.json",
11
+ "lint": "eslint src/ts",
11
12
  "package-lock": "npm i --package-lock-only",
12
13
  "permit": " chmod 775 lib/sol2uml.js",
13
14
  "prettier": "prettier --write src/**/*.ts **/*.md",
@@ -23,28 +24,32 @@
23
24
  "license": "MIT",
24
25
  "dependencies": {
25
26
  "@aduh95/viz.js": "^3.7.0",
26
- "@solidity-parser/parser": "^0.16.1",
27
- "axios": "^1.6.0",
27
+ "@solidity-parser/parser": "^0.20.1",
28
+ "axios": "^1.10.0",
28
29
  "axios-debug-log": "^1.0.0",
29
- "cli-color": "^2.0.3",
30
- "commander": "^11.1.0",
30
+ "cli-color": "^2.0.4",
31
+ "commander": "^12.1.0",
31
32
  "convert-svg-to-png": "^0.6.4",
32
- "debug": "^4.3.4",
33
+ "debug": "^4.4.1",
33
34
  "diff-match-patch": "^1.0.5",
34
- "ethers": "^5.7.2",
35
+ "ethers": "^5.8.0",
35
36
  "js-graph-algorithms": "^1.0.18",
36
37
  "klaw": "^4.1.0"
37
38
  },
38
39
  "devDependencies": {
40
+ "@eslint/js": "^9.39.2",
39
41
  "@openzeppelin/contracts": "^4.9.3",
40
- "@types/diff-match-patch": "^1.0.35",
41
- "@types/jest": "^29.5.7",
42
- "@types/klaw": "^3.0.5",
43
- "jest": "^29.7.0",
44
- "prettier": "^3.0.3",
45
- "ts-jest": "^29.1.1",
46
- "ts-node": "^10.9.1",
47
- "typescript": "^5.2.2"
42
+ "@types/diff-match-patch": "^1.0.36",
43
+ "@types/jest": "^29.5.14",
44
+ "@types/klaw": "^3.0.7",
45
+ "eslint": "^9.39.2",
46
+ "eslint-config-prettier": "^10.1.8",
47
+ "jest": "^30.0.0",
48
+ "prettier": "^3.5.3",
49
+ "ts-jest": "^29.4.0",
50
+ "ts-node": "^10.9.2",
51
+ "typescript": "^5.8.3",
52
+ "typescript-eslint": "^8.55.0"
48
53
  },
49
54
  "files": [
50
55
  "lib/*.js",