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 +14 -15
- package/lib/SlotValueCache.js +1 -1
- package/lib/associations.d.ts +1 -1
- package/lib/associations.js +78 -48
- package/lib/converterAST2Classes.js +33 -21
- package/lib/converterClass2Dot.js +1 -1
- package/lib/converterClasses2Dot.js +2 -3
- package/lib/converterClasses2Storage.js +9 -6
- package/lib/converterStorage2Dot.js +2 -2
- package/lib/diffContracts.d.ts +1 -1
- package/lib/diffContracts.js +16 -7
- package/lib/parserEtherscan.d.ts +4 -3
- package/lib/parserEtherscan.js +68 -71
- package/lib/parserFiles.js +6 -6
- package/lib/parserGeneral.js +1 -1
- package/lib/slotValues.d.ts +2 -2
- package/lib/slotValues.js +1 -1
- package/lib/sol2uml.js +6 -8
- package/lib/squashClasses.js +19 -9
- package/lib/utils/block.js +2 -2
- package/lib/utils/diff.js +25 -12
- package/lib/utils/validators.js +5 -5
- package/lib/writerFiles.js +7 -7
- package/package.json +20 -15
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Solidity 2 UML
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/sol2uml)
|
|
4
|
-
[](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
|
|
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>
|
|
61
|
-
-e, --explorerUrl <url> Override the `network` option with a custom blockchain explorer API URL. eg Polygon
|
|
62
|
-
-k, --apiKey <key> Blockchain explorer 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
|
|
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
|
|
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
|
|
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: "
|
|
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
|
|
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
|
|
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
|
|
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
|
package/lib/SlotValueCache.js
CHANGED
|
@@ -16,7 +16,7 @@ class SlotValueCache {
|
|
|
16
16
|
static readSlotValues(slotKeys) {
|
|
17
17
|
const cachedValues = [];
|
|
18
18
|
const missingKeys = [];
|
|
19
|
-
slotKeys.forEach((slotKey
|
|
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]);
|
package/lib/associations.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { Association, UmlClass } from './umlClass';
|
|
2
|
-
export declare const findAssociatedClass: (association: Association, sourceUmlClass: UmlClass, umlClasses: readonly UmlClass[],
|
|
2
|
+
export declare const findAssociatedClass: (association: Association, sourceUmlClass: UmlClass, umlClasses: readonly UmlClass[], _searchedAbsolutePaths?: string[]) => UmlClass | undefined;
|
package/lib/associations.js
CHANGED
|
@@ -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,
|
|
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(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
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 =
|
|
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 = (
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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 (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
155
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
package/lib/diffContracts.d.ts
CHANGED
|
@@ -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,
|
|
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;
|
package/lib/diffContracts.js
CHANGED
|
@@ -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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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,
|
|
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);
|
package/lib/parserEtherscan.d.ts
CHANGED
|
@@ -4,13 +4,14 @@ export interface Remapping {
|
|
|
4
4
|
from: RegExp;
|
|
5
5
|
to: string;
|
|
6
6
|
}
|
|
7
|
-
export declare const networks: readonly ["
|
|
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
|
|
11
|
+
protected apiKey?: string;
|
|
11
12
|
network: Network;
|
|
12
13
|
readonly url: string;
|
|
13
|
-
constructor(
|
|
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
|
package/lib/parserEtherscan.js
CHANGED
|
@@ -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
|
-
'
|
|
17
|
-
'holesky',
|
|
16
|
+
'ethereum',
|
|
18
17
|
'sepolia',
|
|
19
|
-
'
|
|
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
|
-
'
|
|
26
|
-
'optimism',
|
|
28
|
+
'sonic',
|
|
27
29
|
'gnosis',
|
|
30
|
+
'moonbeam',
|
|
28
31
|
'celo',
|
|
29
32
|
'scroll',
|
|
30
|
-
'
|
|
31
|
-
'
|
|
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(
|
|
35
|
-
this.
|
|
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 (!
|
|
42
|
-
|
|
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.
|
|
200
|
+
apikey: this.apiKey,
|
|
204
201
|
},
|
|
205
202
|
});
|
|
206
203
|
if (!Array.isArray(response?.data?.result)) {
|
package/lib/parserFiles.js
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
|
141
|
+
catch {
|
|
142
142
|
return false;
|
|
143
143
|
}
|
|
144
144
|
};
|
package/lib/parserGeneral.js
CHANGED
|
@@ -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
|
|
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
|
}
|
package/lib/slotValues.d.ts
CHANGED
|
@@ -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 |
|
|
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 |
|
|
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
|
-
|
|
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>', '
|
|
34
|
-
|
|
35
|
-
.default('
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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);
|
package/lib/squashClasses.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 (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
57
|
+
const squashedClass = new umlClass_1.UmlClass({
|
|
48
58
|
name: baseClass.name,
|
|
49
59
|
absolutePath: baseClass.absolutePath,
|
|
50
60
|
relativePath: baseClass.relativePath,
|
package/lib/utils/block.js
CHANGED
|
@@ -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
|
|
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
|
|
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 (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
184
|
+
text.split('').forEach((c) => {
|
|
172
185
|
if (c === '\n') {
|
|
173
186
|
if (lineStart > 0) {
|
|
174
187
|
textWithLineNumbers += `\n${(++lineCount)
|
package/lib/utils/validators.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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;
|
package/lib/writerFiles.js
CHANGED
|
@@ -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.
|
|
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
|
|
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.
|
|
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.
|
|
27
|
-
"axios": "^1.
|
|
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.
|
|
30
|
-
"commander": "^
|
|
30
|
+
"cli-color": "^2.0.4",
|
|
31
|
+
"commander": "^12.1.0",
|
|
31
32
|
"convert-svg-to-png": "^0.6.4",
|
|
32
|
-
"debug": "^4.
|
|
33
|
+
"debug": "^4.4.1",
|
|
33
34
|
"diff-match-patch": "^1.0.5",
|
|
34
|
-
"ethers": "^5.
|
|
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.
|
|
41
|
-
"@types/jest": "^29.5.
|
|
42
|
-
"@types/klaw": "^3.0.
|
|
43
|
-
"
|
|
44
|
-
"prettier": "^
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
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",
|