sol2uml 2.5.22 → 2.5.24

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
 
@@ -58,7 +58,7 @@ Options:
58
58
  -o, --outputFileName <value> output file name
59
59
  -i, --ignoreFilesOrFolders <names> comma-separated list of files or folders to ignore
60
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 Mumbai testnet https://api-testnet.polygonscan.com/api (env: EXPLORER_URL)
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
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")
@@ -225,10 +225,10 @@ To generate a diagram of EtherDelta's contract from the verified source code on
225
225
  sol2uml class 0x8d12A197cB00D4747a1fe03395095ce2A5CC6819
226
226
  ```
227
227
 
228
- 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.
229
229
 
230
230
  ```bash
231
- sol2uml class 0xa19833bd291b66aB0E17b9C6d46D2Ec5fEC15190 -n ropsten
231
+ sol2uml class 0x8d62350d6DfC8A928bBF5efD2e44c66034Afa7C6 -n hoodi
232
232
  ```
233
233
 
234
234
  To generate all Solidity files under some root folder and output the svg file to a specific location
@@ -354,7 +354,7 @@ npm run clean
354
354
  npm run package-lock
355
355
  npm run build
356
356
  npm run permit
357
- # make tx2uml globally available for local testing
357
+ # make sol2uml globally available for local testing
358
358
  npm link
359
359
  # check all the files are included in the npm package
360
360
  npm pack --dry-run
@@ -1,4 +1,4 @@
1
- import { BigNumberish } from '@ethersproject/bignumber';
1
+ import { BigNumberish } from 'ethers';
2
2
  /**
3
3
  * Singleton that caches a mapping of slot keys to values.
4
4
  * Assumes all data is read from the same block and contract
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SlotValueCache = void 0;
4
- const bignumber_1 = require("@ethersproject/bignumber");
4
+ const ethers_1 = require("ethers");
5
5
  const debug = require('debug')('sol2uml');
6
6
  /**
7
7
  * Singleton that caches a mapping of slot keys to values.
@@ -16,8 +16,8 @@ class SlotValueCache {
16
16
  static readSlotValues(slotKeys) {
17
17
  const cachedValues = [];
18
18
  const missingKeys = [];
19
- slotKeys.forEach((slotKey, i) => {
20
- const key = bignumber_1.BigNumber.from(slotKey).toHexString();
19
+ slotKeys.forEach((slotKey) => {
20
+ const key = (0, ethers_1.toBeHex)(BigInt(slotKey));
21
21
  if (this.slotCache[key]) {
22
22
  cachedValues.push(this.slotCache[key]);
23
23
  }
@@ -46,7 +46,7 @@ class SlotValueCache {
46
46
  }
47
47
  });
48
48
  return slotKeys.map((slotKey) => {
49
- const key = bignumber_1.BigNumber.from(slotKey).toHexString();
49
+ const key = (0, ethers_1.toBeHex)(BigInt(slotKey));
50
50
  // it should find the slot value in the cache. if not it'll return undefined
51
51
  return this.slotCache[key];
52
52
  });
@@ -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[]) => UmlClass | undefined;
@@ -2,34 +2,18 @@
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 = []) => {
6
- const umlClass = umlClasses.find((targetUmlClass) => {
7
- const targetParentClass = association.parentUmlClassName &&
8
- targetUmlClass.parentId !== undefined
9
- ? umlClasses[targetUmlClass.parentId]
10
- : undefined;
11
- return isAssociated(association, sourceUmlClass, targetUmlClass, targetParentClass);
12
- });
13
- // If a link was found
14
- if (umlClass)
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(
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
5
+ const findAssociatedClass = (association, sourceUmlClass, umlClasses) => {
6
+ // Phase 1: Iterative BFS through import chain, trying direct match at each level
7
+ const { result, visitedSources } = _findViaImportChain(association, sourceUmlClass, umlClasses);
8
+ if (result)
9
+ return result;
10
+ // Phase 2: Try inherited types for each source visited during import chain traversal
11
+ const visitedClassIds = new Set();
12
+ for (const source of visitedSources) {
13
+ const inherited = _findInheritedType(association, source, umlClasses, visitedClassIds);
14
+ if (inherited)
15
+ return inherited;
16
+ }
33
17
  return undefined;
34
18
  };
35
19
  exports.findAssociatedClass = findAssociatedClass;
@@ -83,15 +67,91 @@ const isAssociated = (association, sourceUmlClass, targetUmlClass, targetParentU
83
67
  importedClass.alias &&
84
68
  importedClass.className === targetUmlClass.name))));
85
69
  };
86
- const findInheritedType = (association, sourceUmlClass, umlClasses) => {
87
- // Get all realized associations.
70
+ // Try to find a direct match for the association from the given source class
71
+ const _tryDirectMatch = (association, sourceUmlClass, umlClasses) => {
72
+ return umlClasses.find((targetUmlClass) => {
73
+ const targetParentClass = association.parentUmlClassName &&
74
+ targetUmlClass.parentId !== undefined
75
+ ? umlClasses[targetUmlClass.parentId]
76
+ : undefined;
77
+ return isAssociated(association, sourceUmlClass, targetUmlClass, targetParentClass);
78
+ });
79
+ };
80
+ // Iterative BFS through import chain, trying direct match at each level.
81
+ // Returns the matched class (if found) and the list of source classes visited.
82
+ const _findViaImportChain = (association, sourceUmlClass, umlClasses) => {
83
+ const searched = new Set();
84
+ const visitedSources = [];
85
+ const visitedPaths = new Set();
86
+ const queue = [
87
+ {
88
+ source: sourceUmlClass,
89
+ targetName: association.targetUmlClassName,
90
+ },
91
+ ];
92
+ while (queue.length > 0) {
93
+ const { source, targetName } = queue.shift();
94
+ const key = `${source.absolutePath}::${targetName}`;
95
+ if (searched.has(key))
96
+ continue;
97
+ searched.add(key);
98
+ // Track unique visited sources for phase 2 inherited type lookup
99
+ if (!visitedPaths.has(source.absolutePath)) {
100
+ visitedPaths.add(source.absolutePath);
101
+ visitedSources.push(source);
102
+ }
103
+ // Build association with potentially de-aliased target name
104
+ const currentAssoc = {
105
+ ...association,
106
+ targetUmlClassName: targetName,
107
+ };
108
+ // Try direct match from this source
109
+ const match = _tryDirectMatch(currentAssoc, source, umlClasses);
110
+ if (match)
111
+ return { result: match, visitedSources };
112
+ // Get imports that could lead to the target
113
+ const imports = source.imports.filter((i) => i.classNames.length === 0 ||
114
+ i.classNames.some((cn) => (targetName === cn.className && !cn.alias) ||
115
+ targetName === cn.alias));
116
+ for (const importDetail of imports) {
117
+ const importedClass = umlClasses.find((c) => c.absolutePath === importDetail.absolutePath);
118
+ if (!importedClass)
119
+ continue;
120
+ // Queue with current target name to continue the chain
121
+ const origKey = `${importedClass.absolutePath}::${targetName}`;
122
+ if (!searched.has(origKey)) {
123
+ queue.push({ source: importedClass, targetName });
124
+ }
125
+ // Queue with de-aliased names for aliased imports
126
+ for (const cn of importDetail.classNames) {
127
+ if (cn.alias && targetName === cn.alias) {
128
+ const deAliasedKey = `${importedClass.absolutePath}::${cn.className}`;
129
+ if (!searched.has(deAliasedKey)) {
130
+ queue.push({
131
+ source: importedClass,
132
+ targetName: cn.className,
133
+ });
134
+ }
135
+ }
136
+ }
137
+ }
138
+ }
139
+ return { visitedSources };
140
+ };
141
+ // Walk the inheritance chain to find types (structs, enums) defined on parent contracts.
142
+ // Uses visitedClassIds to prevent re-processing in diamond inheritance hierarchies.
143
+ const _findInheritedType = (association, sourceUmlClass, umlClasses, visitedClassIds) => {
144
+ if (visitedClassIds.has(sourceUmlClass.id))
145
+ return undefined;
146
+ visitedClassIds.add(sourceUmlClass.id);
88
147
  const parentAssociations = sourceUmlClass.getParentContracts();
89
- // For each parent association
90
148
  for (const parentAssociation of parentAssociations) {
91
- const parent = (0, exports.findAssociatedClass)(parentAssociation, sourceUmlClass, umlClasses);
149
+ // Resolve the parent class using import chain only (no inherited types)
150
+ // to avoid mutual recursion between findAssociatedClass and _findInheritedType
151
+ const { result: parent } = _findViaImportChain(parentAssociation, sourceUmlClass, umlClasses);
92
152
  if (!parent)
93
153
  continue;
94
- // For each struct on the parent
154
+ // Check parent's structs for the target type
95
155
  for (const structId of parent.structs) {
96
156
  const structUmlClass = umlClasses.find((c) => c.id === structId);
97
157
  if (!structUmlClass)
@@ -100,7 +160,7 @@ const findInheritedType = (association, sourceUmlClass, umlClasses) => {
100
160
  return structUmlClass;
101
161
  }
102
162
  }
103
- // For each enum on the parent
163
+ // Check parent's enums for the target type
104
164
  for (const enumId of parent.enums) {
105
165
  const enumUmlClass = umlClasses.find((c) => c.id === enumId);
106
166
  if (!enumUmlClass)
@@ -109,69 +169,11 @@ const findInheritedType = (association, sourceUmlClass, umlClasses) => {
109
169
  return enumUmlClass;
110
170
  }
111
171
  }
112
- // Recursively look for inherited types
113
- const targetClass = findInheritedType(association, parent, umlClasses);
172
+ // Recursively check parent's parents
173
+ const targetClass = _findInheritedType(association, parent, umlClasses, visitedClassIds);
114
174
  if (targetClass)
115
175
  return targetClass;
116
176
  }
117
177
  return undefined;
118
178
  };
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
- // }
177
179
  //# sourceMappingURL=associations.js.map
@@ -55,7 +55,7 @@ function convertAST2UmlClasses(node, relativePath, remappings, filesystem = fals
55
55
  if (node.type === 'SourceUnit') {
56
56
  node.children.forEach((childNode) => {
57
57
  if (childNode.type === 'ContractDefinition') {
58
- let umlClass = new umlClass_1.UmlClass({
58
+ const umlClass = new umlClass_1.UmlClass({
59
59
  name: childNode.name,
60
60
  absolutePath: filesystem
61
61
  ? path.resolve(relativePath) // resolve the absolute path
@@ -68,7 +68,7 @@ function convertAST2UmlClasses(node, relativePath, remappings, filesystem = fals
68
68
  }
69
69
  else if (childNode.type === 'StructDefinition') {
70
70
  debug(`Adding file level struct ${childNode.name}`);
71
- let umlClass = new umlClass_1.UmlClass({
71
+ const umlClass = new umlClass_1.UmlClass({
72
72
  name: childNode.name,
73
73
  stereotype: umlClass_1.ClassStereotype.Struct,
74
74
  absolutePath: filesystem
@@ -82,7 +82,7 @@ function convertAST2UmlClasses(node, relativePath, remappings, filesystem = fals
82
82
  }
83
83
  else if (childNode.type === 'EnumDefinition') {
84
84
  debug(`Adding file level enum ${childNode.name}`);
85
- let umlClass = new umlClass_1.UmlClass({
85
+ const umlClass = new umlClass_1.UmlClass({
86
86
  name: childNode.name,
87
87
  stereotype: umlClass_1.ClassStereotype.Enum,
88
88
  absolutePath: filesystem
@@ -116,7 +116,7 @@ function convertAST2UmlClasses(node, relativePath, remappings, filesystem = fals
116
116
  debug(`Added filesystem import ${newImport.absolutePath} with class names ${newImport.classNames.map((i) => i.className)}`);
117
117
  imports.push(newImport);
118
118
  }
119
- catch (err) {
119
+ catch {
120
120
  debug(`Failed to resolve import ${childNode.path} from file ${relativePath}`);
121
121
  }
122
122
  }
@@ -626,7 +626,7 @@ function parseTypeName(typeName) {
626
626
  case 'FunctionTypeName':
627
627
  // TODO add params and return type
628
628
  return [typeName.type + '\\(\\)', umlClass_1.AttributeType.Function];
629
- case 'ArrayTypeName':
629
+ case 'ArrayTypeName': {
630
630
  const [arrayElementType] = parseTypeName(typeName.baseTypeName);
631
631
  let length = '';
632
632
  if (Number.isInteger(typeName.length)) {
@@ -640,7 +640,8 @@ function parseTypeName(typeName) {
640
640
  }
641
641
  // TODO does not currently handle Expression types like BinaryOperation
642
642
  return [arrayElementType + '[' + length + ']', umlClass_1.AttributeType.Array];
643
- case 'Mapping':
643
+ }
644
+ case 'Mapping': {
644
645
  const key = typeName.keyType?.name ||
645
646
  typeName.keyType?.namePath;
646
647
  const [valueType] = parseTypeName(typeName.valueType);
@@ -648,6 +649,7 @@ function parseTypeName(typeName) {
648
649
  'mapping\\(' + key + '=\\>' + valueType + '\\)',
649
650
  umlClass_1.AttributeType.Mapping,
650
651
  ];
652
+ }
651
653
  default:
652
654
  throw Error(`Invalid typeName ${typeName}`);
653
655
  }
@@ -661,7 +663,7 @@ function parseParameters(params) {
661
663
  if (!params || !params) {
662
664
  return [];
663
665
  }
664
- let parameters = [];
666
+ const parameters = [];
665
667
  for (const param of params) {
666
668
  const [type] = parseTypeName(param.typeName);
667
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,5 +1,5 @@
1
1
  import { Attribute, AttributeType, UmlClass } from './umlClass';
2
- import { BigNumberish } from '@ethersproject/bignumber';
2
+ import { BigNumberish } from 'ethers';
3
3
  export declare enum StorageSectionType {
4
4
  Contract = "Contract",
5
5
  Struct = "Struct",
@@ -6,7 +6,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.addDynamicVariables = exports.findDimensionLength = exports.calcSectionOffset = exports.isElementary = exports.calcStorageByteSize = exports.parseStorageSectionFromAttribute = exports.optionStorageVariables = exports.convertClasses2StorageSections = exports.StorageSectionType = void 0;
7
7
  const umlClass_1 = require("./umlClass");
8
8
  const associations_1 = require("./associations");
9
- const utils_1 = require("ethers/lib/utils");
10
9
  const ethers_1 = require("ethers");
11
10
  const path_1 = __importDefault(require("path"));
12
11
  const slotValues_1 = require("./slotValues");
@@ -151,8 +150,8 @@ const parseVariables = (umlClass, umlClasses, variables, storageSections, inheri
151
150
  const getValue = calcGetValue(attribute.attributeType, mapping);
152
151
  // Get the toSlot of the last storage item
153
152
  const lastVariable = variables[variables.length - 1];
154
- let lastToSlot = lastVariable ? lastVariable.toSlot : 0;
155
- let nextOffset = lastVariable
153
+ const lastToSlot = lastVariable ? lastVariable.toSlot : 0;
154
+ const nextOffset = lastVariable
156
155
  ? lastVariable.byteOffset + lastVariable.byteSize
157
156
  : 0;
158
157
  let fromSlot;
@@ -347,7 +346,7 @@ const parseStorageSectionFromAttribute = (attribute, umlClass, otherClasses, sto
347
346
  // Find UserDefined type can be a contract, struct or enum
348
347
  const typeClass = findTypeClass(result[1], attribute, umlClass, otherClasses);
349
348
  if (typeClass.stereotype === umlClass_1.ClassStereotype.Struct) {
350
- let variables = parseVariables(typeClass, otherClasses, [], storageSections, [], true, arrayItems, noExpandVariables);
349
+ const variables = parseVariables(typeClass, otherClasses, [], storageSections, [], true, arrayItems, noExpandVariables);
351
350
  const storageSection = {
352
351
  id: storageId++,
353
352
  name: typeClass.name,
@@ -535,7 +534,7 @@ const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
535
534
  case umlClass_1.ClassStereotype.Interface:
536
535
  case umlClass_1.ClassStereotype.Library:
537
536
  return { size: 20, dynamic: false };
538
- case umlClass_1.ClassStereotype.Struct:
537
+ case umlClass_1.ClassStereotype.Struct: {
539
538
  let structByteSize = 0;
540
539
  attributeTypeClass.attributes.forEach((structAttribute) => {
541
540
  // If next attribute is an array, then we need to start in a new slot
@@ -569,6 +568,7 @@ const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
569
568
  size: Math.ceil(structByteSize / 32) * 32,
570
569
  dynamic: false,
571
570
  };
571
+ }
572
572
  default:
573
573
  return { size: 20, dynamic: false };
574
574
  }
@@ -593,7 +593,7 @@ const calcElementaryTypeSize = (type) => {
593
593
  case 'ufixed':
594
594
  case 'fixed':
595
595
  return { size: 32, dynamic: false };
596
- default:
596
+ default: {
597
597
  const result = type.match(/[u]*(int|fixed|bytes)([0-9]+)/);
598
598
  if (result === null || !result[2]) {
599
599
  throw Error(`Failed size elementary type "${type}"`);
@@ -606,6 +606,7 @@ const calcElementaryTypeSize = (type) => {
606
606
  // If an int
607
607
  const bitSize = parseInt(result[2]);
608
608
  return { size: bitSize / 8, dynamic: false };
609
+ }
609
610
  }
610
611
  };
611
612
  const isElementary = (type) => {
@@ -619,18 +620,19 @@ const isElementary = (type) => {
619
620
  case 'ufixed':
620
621
  case 'fixed':
621
622
  return true;
622
- default:
623
+ default: {
623
624
  const result = type.match(/^[u]?(int|fixed|bytes)([0-9]+)$/);
624
625
  return result !== null;
626
+ }
625
627
  }
626
628
  };
627
629
  exports.isElementary = isElementary;
628
630
  const calcSectionOffset = (variable, sectionOffset = '0') => {
629
631
  if (variable.dynamic) {
630
- const hexStringOf32Bytes = (0, utils_1.hexZeroPad)(ethers_1.BigNumber.from(variable.fromSlot).add(sectionOffset).toHexString(), 32);
631
- return (0, utils_1.keccak256)(hexStringOf32Bytes);
632
+ const hexStringOf32Bytes = (0, ethers_1.zeroPadValue)((0, ethers_1.toBeHex)(BigInt(variable.fromSlot) + BigInt(sectionOffset)), 32);
633
+ return (0, ethers_1.keccak256)(hexStringOf32Bytes);
632
634
  }
633
- return ethers_1.BigNumber.from(variable.fromSlot).add(sectionOffset).toHexString();
635
+ return (0, ethers_1.toBeHex)(BigInt(variable.fromSlot) + BigInt(sectionOffset));
634
636
  };
635
637
  exports.calcSectionOffset = calcSectionOffset;
636
638
  const findDimensionLength = (umlClass, dimension, otherClasses) => {
@@ -788,7 +790,7 @@ const addDynamicVariables = async (storageSection, storageSections, url, contrac
788
790
  continue;
789
791
  }
790
792
  // Add missing dynamic array variables
791
- const arrayLength = ethers_1.BigNumber.from(variable.slotValue).toNumber();
793
+ const arrayLength = Number(BigInt(variable.slotValue));
792
794
  if (arrayLength > 1) {
793
795
  // Add missing array variables to the referenced dynamic array
794
796
  addArrayVariables(arrayLength, arrayItems, referenceStorageSection.variables);
@@ -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);
@@ -90,7 +90,7 @@ class EtherscanParser {
90
90
  return;
91
91
  }
92
92
  if (!apiKey) {
93
- console.error(`The apiKey option must be set when getting verified source code from an Etherscan like explorer`);
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
94
  process.exit(1);
95
95
  }
96
96
  const chainId = (0, exports.setChainId)(network);
@@ -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
  };
@@ -1,5 +1,5 @@
1
- import { BigNumberish } from '@ethersproject/bignumber';
2
1
  import { StorageSection, Variable } from './converterClasses2Storage';
2
+ import { BigNumberish } from 'ethers';
3
3
  /**
4
4
  * Adds the slot values to the variables in the storage section.
5
5
  * This can be rerun for a section as it will only get if the slot value
package/lib/slotValues.js CHANGED
@@ -4,13 +4,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.escapeString = exports.convert2String = exports.dynamicSlotSize = exports.getSlotValue = exports.getSlotValues = exports.parseValue = exports.addSlotValues = void 0;
7
- const bignumber_1 = require("@ethersproject/bignumber");
8
7
  const axios_1 = __importDefault(require("axios"));
9
8
  const umlClass_1 = require("./umlClass");
10
- const utils_1 = require("ethers/lib/utils");
11
- const SlotValueCache_1 = require("./SlotValueCache");
12
9
  const ethers_1 = require("ethers");
10
+ const SlotValueCache_1 = require("./SlotValueCache");
13
11
  const debug = require('debug')('sol2uml');
12
+ const commify = (value) => {
13
+ const match = value.match(/^(-?)(\d+)(\.(.*))?$/);
14
+ if (!match)
15
+ return value;
16
+ const neg = match[1];
17
+ const whole = match[2];
18
+ const frac = match[4] ? '.' + match[4] : '';
19
+ return neg + whole.replace(/\B(?=(\d{3})+(?!\d))/g, ',') + frac;
20
+ };
14
21
  /**
15
22
  * Adds the slot values to the variables in the storage section.
16
23
  * This can be rerun for a section as it will only get if the slot value
@@ -30,7 +37,7 @@ const addSlotValues = async (url, contractAddress, storageSection, arrayItems, b
30
37
  const slots = [];
31
38
  valueVariables.forEach((variable) => {
32
39
  if (variable.offset) {
33
- slots.push(bignumber_1.BigNumber.from(variable.offset));
40
+ slots.push(BigInt(variable.offset));
34
41
  }
35
42
  else {
36
43
  for (let i = 0; variable.fromSlot + i <= variable.toSlot; i++) {
@@ -45,12 +52,12 @@ const addSlotValues = async (url, contractAddress, storageSection, arrayItems, b
45
52
  });
46
53
  // remove duplicate slot numbers
47
54
  const uniqueFromSlots = [...new Set(slots)];
48
- // Convert slot numbers to BigNumbers and offset dynamic arrays
49
- let slotKeys = uniqueFromSlots.map((fromSlot) => {
55
+ // Convert slot numbers to BigInts and offset dynamic arrays
56
+ const slotKeys = uniqueFromSlots.map((fromSlot) => {
50
57
  if (storageSection.offset) {
51
- return bignumber_1.BigNumber.from(storageSection.offset).add(fromSlot);
58
+ return BigInt(storageSection.offset) + BigInt(fromSlot);
52
59
  }
53
- return bignumber_1.BigNumber.from(fromSlot);
60
+ return BigInt(fromSlot);
54
61
  });
55
62
  // Get the contract slot values from the node provider
56
63
  const values = await (0, exports.getSlotValues)(url, contractAddress, slotKeys, blockTag);
@@ -62,7 +69,7 @@ const addSlotValues = async (url, contractAddress, storageSection, arrayItems, b
62
69
  for (const variable of storageSection.variables) {
63
70
  if (variable.getValue &&
64
71
  variable.offset &&
65
- bignumber_1.BigNumber.from(variable.offset).eq(fromSlot)) {
72
+ BigInt(variable.offset) === BigInt(fromSlot)) {
66
73
  debug(`Set slot value ${value} for section "${storageSection.name}", var type ${variable.type}, slot ${variable.offset}`);
67
74
  variable.slotValue = value;
68
75
  // parse variable value from slot data
@@ -80,7 +87,7 @@ const addSlotValues = async (url, contractAddress, storageSection, arrayItems, b
80
87
  }
81
88
  // if variable is past the slot that has the value
82
89
  else if (variable.toSlot &&
83
- bignumber_1.BigNumber.from(variable.toSlot).gt(fromSlot)) {
90
+ BigInt(variable.toSlot) > BigInt(fromSlot)) {
84
91
  break;
85
92
  }
86
93
  }
@@ -103,7 +110,7 @@ const parseValue = (variable) => {
103
110
  // dynamic arrays
104
111
  if (variable.attributeType === umlClass_1.AttributeType.Array &&
105
112
  variable.dynamic) {
106
- return (0, utils_1.formatUnits)('0x' + variableValue, 0);
113
+ return (0, ethers_1.formatUnits)('0x' + variableValue, 0);
107
114
  }
108
115
  return undefined;
109
116
  }
@@ -118,12 +125,12 @@ const parseUserDefinedValue = (variable, variableValue) => {
118
125
  // https://blog.soliditylang.org/2021/09/27/user-defined-value-types/
119
126
  // using byteSize is crude and will be incorrect for aliases types like int160 or uint160
120
127
  if (variable.byteSize === 20) {
121
- return (0, utils_1.getAddress)('0x' + variableValue);
128
+ return (0, ethers_1.getAddress)('0x' + variableValue);
122
129
  }
123
130
  // this will also be wrong if the alias is to a 1 byte type. eg bytes1, int8 or uint8
124
131
  if (variable.byteSize === 1) {
125
132
  // assume 1 byte is an enum so convert value to enum index number
126
- const index = bignumber_1.BigNumber.from('0x' + variableValue).toNumber();
133
+ const index = Number(BigInt('0x' + variableValue));
127
134
  // lookup enum value if its available
128
135
  return variable?.enumValues ? variable?.enumValues[index] : undefined;
129
136
  }
@@ -142,17 +149,14 @@ const parseElementaryValue = (variable, variableValue) => {
142
149
  if (variable.type === 'string' || variable.type === 'bytes') {
143
150
  if (variable.dynamic) {
144
151
  const lastByte = variable.slotValue.slice(-2);
145
- const size = bignumber_1.BigNumber.from('0x' + lastByte);
152
+ const size = BigInt('0x' + lastByte);
146
153
  // Check if the last bit is set by AND the size with 0x01
147
- if (size.and(1).eq(1)) {
154
+ if ((size & 1n) === 1n) {
148
155
  // Return the number of chars or bytes
149
- return bignumber_1.BigNumber.from(variable.slotValue)
150
- .sub(1)
151
- .div(2)
152
- .toString();
156
+ return ((BigInt(variable.slotValue) - 1n) / 2n).toString();
153
157
  }
154
158
  // The last byte holds the length of the string or bytes in the slot
155
- const valueHex = '0x' + variableValue.slice(0, size.toNumber());
159
+ const valueHex = '0x' + variableValue.slice(0, Number(size));
156
160
  if (variable.type === 'bytes')
157
161
  return valueHex;
158
162
  return `\\"${(0, exports.convert2String)(valueHex)}\\"`;
@@ -162,34 +166,34 @@ const parseElementaryValue = (variable, variableValue) => {
162
166
  return `\\"${(0, exports.convert2String)('0x' + variableValue)}\\"`;
163
167
  }
164
168
  if (variable.type === 'address') {
165
- return (0, utils_1.getAddress)('0x' + variableValue);
169
+ return (0, ethers_1.getAddress)('0x' + variableValue);
166
170
  }
167
171
  if (variable.type.match(/^uint([0-9]*)$/)) {
168
- const parsedValue = (0, utils_1.formatUnits)('0x' + variableValue, 0);
169
- return (0, utils_1.commify)(parsedValue);
172
+ const parsedValue = (0, ethers_1.formatUnits)('0x' + variableValue, 0);
173
+ return commify(parsedValue);
170
174
  }
171
175
  if (variable.type.match(/^bytes([0-9]+)$/)) {
172
176
  return '0x' + variableValue;
173
177
  }
174
178
  if (variable.type.match(/^int([0-9]*)/)) {
175
179
  // parse variable value as an unsigned number
176
- let rawValue = bignumber_1.BigNumber.from('0x' + variableValue);
180
+ let rawValue = BigInt('0x' + variableValue);
177
181
  // parse the number of bits
178
182
  const result = variable.type.match(/^int([0-9]*$)/);
179
- const bitSize = result[1] ? result[1] : 256;
183
+ const bitSize = result[1] ? Number(result[1]) : 256;
180
184
  // Convert the number of bits to the number of hex characters
181
- const hexSize = bignumber_1.BigNumber.from(bitSize).div(4).toNumber();
185
+ const hexSize = bitSize / 4;
182
186
  // bit mask has a leading 1 and the rest 0. 0x8 = 1000 binary
183
- const mask = '0x80' + '0'.repeat(hexSize - 2);
187
+ const mask = BigInt('0x80' + '0'.repeat(hexSize - 2));
184
188
  // is the first bit a 1?
185
- const negative = rawValue.and(mask);
186
- if (negative.gt(0)) {
189
+ const negative = rawValue & mask;
190
+ if (negative > 0n) {
187
191
  // Convert unsigned number to a signed negative
188
- const negativeOne = '0xFF' + 'F'.repeat(hexSize - 2);
189
- rawValue = bignumber_1.BigNumber.from(negativeOne).sub(rawValue).add(1).mul(-1);
192
+ const negativeOne = BigInt('0xFF' + 'F'.repeat(hexSize - 2));
193
+ rawValue = (negativeOne - rawValue + 1n) * -1n;
190
194
  }
191
- const parsedValue = (0, utils_1.formatUnits)(rawValue, 0);
192
- return (0, utils_1.commify)(parsedValue);
195
+ const parsedValue = (0, ethers_1.formatUnits)(rawValue, 0);
196
+ return commify(parsedValue);
193
197
  }
194
198
  // add fixed point numbers when they are supported by Solidity
195
199
  return undefined;
@@ -209,9 +213,7 @@ const getSlotValues = async (url, contractAddress, slotKeys, blockTag = 'latest'
209
213
  if (slotKeys.length === 0) {
210
214
  return [];
211
215
  }
212
- const block = blockTag === 'latest'
213
- ? blockTag
214
- : (0, utils_1.hexValue)(bignumber_1.BigNumber.from(blockTag));
216
+ const block = blockTag === 'latest' ? blockTag : (0, ethers_1.toQuantity)(BigInt(blockTag));
215
217
  // get cached values and missing slot keys from the cache
216
218
  const { cachedValues, missingKeys } = SlotValueCache_1.SlotValueCache.readSlotValues(slotKeys);
217
219
  // If all values are in the cache then just return the cached values
@@ -219,7 +221,7 @@ const getSlotValues = async (url, contractAddress, slotKeys, blockTag = 'latest'
219
221
  return cachedValues;
220
222
  }
221
223
  // Check we are pointing to the correct chain by checking the contract has code
222
- const provider = new ethers_1.ethers.providers.JsonRpcProvider(url);
224
+ const provider = new ethers_1.JsonRpcProvider(url);
223
225
  const code = await provider.getCode(contractAddress, block);
224
226
  if (!code || code === '0x') {
225
227
  const msg = `Address ${contractAddress} has no code. Check your "-u, --url" option or "NODE_URL" environment variable is pointing to the correct node.\nurl: ${url}`;
@@ -242,7 +244,7 @@ const getSlotValues = async (url, contractAddress, slotKeys, blockTag = 'latest'
242
244
  throw Error(`Requested ${missingKeys.length} storage slot values but only got ${response.data.length}`);
243
245
  }
244
246
  const responseData = response.data;
245
- const sortedResponses = responseData.sort((a, b) => bignumber_1.BigNumber.from(a.id).gt(b.id) ? 1 : -1);
247
+ const sortedResponses = responseData.sort((a, b) => Number(a.id) - Number(b.id));
246
248
  const missingValues = sortedResponses.map((data) => {
247
249
  if (data.error) {
248
250
  throw Error(`json rpc call with id ${data.id} failed to get storage values: ${data.error?.message}`);
@@ -284,11 +286,11 @@ const dynamicSlotSize = (variable) => {
284
286
  if (!variable?.slotValue)
285
287
  throw Error(`Missing slot value.`);
286
288
  const last4bits = '0x' + variable.slotValue.slice(-1);
287
- const last4bitsNum = bignumber_1.BigNumber.from(last4bits).toNumber();
289
+ const last4bitsNum = Number(BigInt(last4bits));
288
290
  // If the last 4 bits is an even number then it's not a dynamic slot
289
291
  if (last4bitsNum % 2 === 0)
290
292
  return 0;
291
- const sizeRaw = bignumber_1.BigNumber.from(variable.slotValue).toNumber();
293
+ const sizeRaw = Number(BigInt(variable.slotValue));
292
294
  // Adjust the size to bytes
293
295
  return (sizeRaw - 1) / 2;
294
296
  }
@@ -302,7 +304,7 @@ const convert2String = (bytes) => {
302
304
  '0x0000000000000000000000000000000000000000000000000000000000000000') {
303
305
  return '';
304
306
  }
305
- const rawString = (0, utils_1.toUtf8String)(bytes);
307
+ const rawString = (0, ethers_1.toUtf8String)(bytes);
306
308
  return (0, exports.escapeString)(rawString);
307
309
  };
308
310
  exports.convert2String = convert2String;
package/lib/sol2uml.js CHANGED
@@ -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);
@@ -47,14 +47,14 @@ const squashUmlClasses = (umlClasses, baseContractNames) => {
47
47
  let removedClassIds = [];
48
48
  for (const baseContractName of baseContractNames) {
49
49
  // Find the base UML Class to squash
50
- let baseIndex = umlClasses.findIndex(({ name }) => {
50
+ const baseIndex = umlClasses.findIndex(({ name }) => {
51
51
  return name === baseContractName;
52
52
  });
53
53
  if (baseIndex === undefined) {
54
54
  throw Error(`Failed to find contract with name "${baseContractName}" to squash`);
55
55
  }
56
56
  const baseClass = umlClasses[baseIndex];
57
- let squashedClass = new umlClass_1.UmlClass({
57
+ const squashedClass = new umlClass_1.UmlClass({
58
58
  name: baseClass.name,
59
59
  absolutePath: baseClass.absolutePath,
60
60
  relativePath: baseClass.relativePath,
@@ -6,12 +6,12 @@ const debug = require('debug')('sol2uml');
6
6
  const getBlock = async (options) => {
7
7
  if (options.block === 'latest') {
8
8
  try {
9
- const provider = new ethers_1.ethers.providers.JsonRpcProvider(options.url);
9
+ const provider = new ethers_1.JsonRpcProvider(options.url);
10
10
  const block = await provider.getBlockNumber();
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
@@ -44,7 +44,7 @@ const SkippedLinesMarker = `\n---`;
44
44
  * @param lineBuff the number of lines to display before and after each change.
45
45
  */
46
46
  const diffCode = (codeA, codeB, lineBuff) => {
47
- // @ts-ignore
47
+ // @ts-ignore diff_match_patch constructor has no type declarations
48
48
  const dmp = new diff_match_patch_1.default();
49
49
  const diff = dmp.diff_main(codeA, codeB);
50
50
  dmp.diff_cleanupSemantic(diff);
@@ -70,18 +70,20 @@ const diff_pretty = (diffs, lines, lineBuff = 2) => {
70
70
  const op = diff[0]; // Operation (insert, delete, equal)
71
71
  const text = diff[1]; // Text of change.
72
72
  switch (op) {
73
- case diff_match_patch_1.DIFF_INSERT:
73
+ case diff_match_patch_1.DIFF_INSERT: {
74
74
  // If first diff then we need to add the first line number
75
75
  const linesInserted = addLineNumbers(text, lineCount, linePad);
76
76
  output += initialLineNumber + clc.green(linesInserted);
77
77
  lineCount += countLines(text);
78
78
  break;
79
- case diff_match_patch_1.DIFF_DELETE:
79
+ }
80
+ case diff_match_patch_1.DIFF_DELETE: {
80
81
  // zero start line means blank line numbers are used
81
82
  const linesDeleted = addLineNumbers(text, 0, linePad);
82
83
  output += initialLineNumber + clc.red(linesDeleted);
83
84
  break;
84
- case diff_match_patch_1.DIFF_EQUAL:
85
+ }
86
+ case diff_match_patch_1.DIFF_EQUAL: {
85
87
  const eolPositions = findEOLPositions(text);
86
88
  // If no changes yet
87
89
  if (diffIndex <= 1) {
@@ -97,6 +99,7 @@ const diff_pretty = (diffs, lines, lineBuff = 2) => {
97
99
  }
98
100
  lineCount += eolPositions.length;
99
101
  break;
102
+ }
100
103
  }
101
104
  }
102
105
  output += '\n';
@@ -178,7 +181,7 @@ const countLines = (text) => (text.match(/\n/g) || '').length;
178
181
  const addLineNumbers = (text, lineStart, linePad) => {
179
182
  let lineCount = lineStart;
180
183
  let textWithLineNumbers = '';
181
- text.split('').forEach((c, i) => {
184
+ text.split('').forEach((c) => {
182
185
  if (c === '\n') {
183
186
  if (lineStart > 0) {
184
187
  textWithLineNumbers += `\n${(++lineCount)
@@ -3,15 +3,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validateTypes = exports.validateSlotNames = exports.validateLineBuffer = exports.validateNames = exports.validateAddress = void 0;
4
4
  const regEx_1 = require("./regEx");
5
5
  const commander_1 = require("commander");
6
- const utils_1 = require("ethers/lib/utils");
6
+ const ethers_1 = require("ethers");
7
7
  const converterClasses2Storage_1 = require("../converterClasses2Storage");
8
8
  const debug = require('debug')('sol2uml');
9
9
  const validateAddress = (address) => {
10
10
  try {
11
11
  if (typeof address === 'string' && address?.match(regEx_1.ethereumAddress))
12
- return (0, utils_1.getAddress)(address);
12
+ return (0, ethers_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;
@@ -46,7 +46,7 @@ const validateSlotNames = (slotNames) => {
46
46
  offset: slot,
47
47
  };
48
48
  }
49
- const offset = (0, utils_1.keccak256)((0, utils_1.toUtf8Bytes)(slot));
49
+ const offset = (0, ethers_1.keccak256)((0, ethers_1.toUtf8Bytes)(slot));
50
50
  debug(`Slot name "${slot}" has hash "${offset}"`);
51
51
  return {
52
52
  name: slot,
@@ -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;
@@ -13,6 +13,7 @@ const fs_1 = require("fs");
13
13
  const path_1 = __importDefault(require("path"));
14
14
  const sync_1 = __importDefault(require("@aduh95/viz.js/sync"));
15
15
  const { convert } = require('convert-svg-to-png');
16
+ const puppeteer = require('puppeteer');
16
17
  const debug = require('debug')('sol2uml');
17
18
  /**
18
19
  * Writes output files to the file system based on the provided input and options.
@@ -39,7 +40,9 @@ const writeOutputFiles = async (dot, contractName, outputFormat = 'svg', outputF
39
40
  outputExt;
40
41
  }
41
42
  }
42
- catch (err) { } // we can ignore errors as it just means outputFilename does not exist yet
43
+ catch {
44
+ /* outputFilename does not exist yet */
45
+ }
43
46
  }
44
47
  if (outputFormat === 'dot' || outputFormat === 'all') {
45
48
  writeDot(dot, outputFilename);
@@ -140,7 +143,7 @@ async function writePng(svg, filename) {
140
143
  debug(`About to write png file ${pngFilename}`);
141
144
  try {
142
145
  const png = await convert(svg, {
143
- outputFilePath: pngFilename,
146
+ launch: { executablePath: puppeteer.executablePath() },
144
147
  });
145
148
  return new Promise((resolve, reject) => {
146
149
  (0, fs_1.writeFile)(pngFilename, png, (err) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sol2uml",
3
- "version": "2.5.22",
3
+ "version": "2.5.24",
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",
@@ -28,23 +29,28 @@
28
29
  "axios-debug-log": "^1.0.0",
29
30
  "cli-color": "^2.0.4",
30
31
  "commander": "^12.1.0",
31
- "convert-svg-to-png": "^0.6.4",
32
+ "convert-svg-to-png": "^0.7.1",
32
33
  "debug": "^4.4.1",
33
34
  "diff-match-patch": "^1.0.5",
34
- "ethers": "^5.8.0",
35
+ "ethers": "^6.16.0",
35
36
  "js-graph-algorithms": "^1.0.18",
36
- "klaw": "^4.1.0"
37
+ "klaw": "^4.1.0",
38
+ "puppeteer": "^24.37.3"
37
39
  },
38
40
  "devDependencies": {
41
+ "@eslint/js": "^9.39.2",
39
42
  "@openzeppelin/contracts": "^4.9.3",
40
43
  "@types/diff-match-patch": "^1.0.36",
41
- "@types/jest": "^29.5.14",
44
+ "@types/jest": "^30.0.0",
42
45
  "@types/klaw": "^3.0.7",
43
- "jest": "^30.0.0",
44
- "prettier": "^3.5.3",
45
- "ts-jest": "^29.4.0",
46
+ "eslint": "^9.39.2",
47
+ "eslint-config-prettier": "^10.1.8",
48
+ "jest": "^30.2.0",
49
+ "prettier": "^3.8.1",
50
+ "ts-jest": "^29.4.6",
46
51
  "ts-node": "^10.9.2",
47
- "typescript": "^5.8.3"
52
+ "typescript": "^5.9.3",
53
+ "typescript-eslint": "^8.55.0"
48
54
  },
49
55
  "files": [
50
56
  "lib/*.js",