sol2uml 2.5.4 → 2.5.6
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 +24 -1
- package/lib/converterAST2Classes.js +1 -1
- package/lib/filterClasses.js +1 -1
- package/lib/parserEtherscan.d.ts +5 -4
- package/lib/parserEtherscan.js +24 -5
- package/lib/parserGeneral.d.ts +1 -0
- package/lib/parserGeneral.js +1 -1
- package/lib/sol2uml.js +8 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -55,7 +55,9 @@ Options:
|
|
|
55
55
|
-f, --outputFormat <value> output file format. (choices: "svg", "png", "dot", "all", default: "svg")
|
|
56
56
|
-o, --outputFileName <value> output file name
|
|
57
57
|
-i, --ignoreFilesOrFolders <filesOrFolders> comma separated list of files or folders to ignore
|
|
58
|
-
-n, --network <network> Ethereum network (choices: "mainnet", "goerli", "sepolia", "polygon", "arbitrum", "avalanche", "bsc", "crono", "fantom", "moonbeam",
|
|
58
|
+
-n, --network <network> Ethereum network which maps to a blockchain explorer (choices: "mainnet", "goerli", "sepolia", "polygon", "arbitrum", "avalanche", "bsc", "crono", "fantom", "moonbeam",
|
|
59
|
+
"optimism", "gnosis", "celo", default: "mainnet", env: ETH_NETWORK)
|
|
60
|
+
-e, --explorerUrl <url> Override network with custom blockchain explorer API URL. eg Polygon Mumbai testnet https://api-testnet.polygonscan.com/api (env: EXPLORER_URL)
|
|
59
61
|
-k, --apiKey <key> Blockchain explorer API key. eg Etherscan, Arbiscan, Optimism, BscScan, CronoScan, FTMScan, PolygonScan or SnowTrace API key (env: SCAN_API_KEY)
|
|
60
62
|
-bc, --backColor <color> Canvas background color. "none" will use a transparent canvas. (default: "white")
|
|
61
63
|
-sc, --shapeColor <color> Basic drawing color for graphics, not text (default: "black")
|
|
@@ -192,6 +194,8 @@ Arguments:
|
|
|
192
194
|
|
|
193
195
|
Options:
|
|
194
196
|
-l, --lineBuffer <value> Minimum number of lines before and after changes (default: "4")
|
|
197
|
+
--aFile <value> Contract A source code filename without the .sol extension. (default: compares all source files)
|
|
198
|
+
--bFile <value> Contract B source code filename without the .sol extension. (default: aFile if specified)
|
|
195
199
|
-s, --saveFiles Save the flattened contract code to the filesystem. The file names will be the contract address with a .sol extension. (default: false)
|
|
196
200
|
-h, --help display help for command
|
|
197
201
|
```
|
|
@@ -329,6 +333,25 @@ To access your local changes on your machine globally.
|
|
|
329
333
|
|
|
330
334
|
`npm link`
|
|
331
335
|
|
|
336
|
+
# Publish
|
|
337
|
+
|
|
338
|
+
Commands to publish a new package version.
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
npm run prettier
|
|
342
|
+
npm run clean
|
|
343
|
+
npm run package-lock
|
|
344
|
+
npm run build
|
|
345
|
+
npm run permit
|
|
346
|
+
# make tx2uml globally available for local testing
|
|
347
|
+
npm link
|
|
348
|
+
# check all the files are included in the npm package
|
|
349
|
+
npm pack --dry-run
|
|
350
|
+
npm publish
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Then create a new release on GitHub https://github.com/naddison36/sol2uml/releases
|
|
354
|
+
|
|
332
355
|
# About
|
|
333
356
|
|
|
334
357
|
This is a rewrite of the Richard Ramos's [solidity-diagram-gen](https://github.com/richard-ramos/solidity-diagram-gen) tool which no longer works as it uses [solidity-parser](https://www.npmjs.com/package/solidity-parser/v/0.4.0) which cannot handle newer Solidity syntax like `constructor`.
|
|
@@ -102,7 +102,7 @@ function convertAST2UmlClasses(node, relativePath, remappings, filesystem = fals
|
|
|
102
102
|
})
|
|
103
103
|
: [],
|
|
104
104
|
};
|
|
105
|
-
debug(`Added filesystem import ${newImport.absolutePath} with class names ${newImport.classNames}`);
|
|
105
|
+
debug(`Added filesystem import ${newImport.absolutePath} with class names ${newImport.classNames.map((i) => i.className)}`);
|
|
106
106
|
imports.push(newImport);
|
|
107
107
|
}
|
|
108
108
|
catch (err) {
|
package/lib/filterClasses.js
CHANGED
|
@@ -87,7 +87,7 @@ function loadWeightedDirectedGraph(umlClasses) {
|
|
|
87
87
|
continue;
|
|
88
88
|
}
|
|
89
89
|
const isTarget = umlClasses.find((u) => u.id === targetUmlClass.id);
|
|
90
|
-
debug(`isTarget ${isTarget} Adding edge from ${sourceUmlClass.name} with id ${sourceUmlClass.id} to ${targetUmlClass.name} with id ${targetUmlClass.id} and type ${targetUmlClass.stereotype}`);
|
|
90
|
+
debug(`isTarget ${!!isTarget}: Adding edge from ${sourceUmlClass.name} with id ${sourceUmlClass.id} to ${targetUmlClass.name} with id ${targetUmlClass.id} and type ${targetUmlClass.stereotype}`);
|
|
91
91
|
weightedDirectedGraph.addEdge(new js_graph_algorithms_1.Edge(sourceUmlClass.id, targetUmlClass.id, 1));
|
|
92
92
|
}
|
|
93
93
|
}
|
package/lib/parserEtherscan.d.ts
CHANGED
|
@@ -4,13 +4,13 @@ export interface Remapping {
|
|
|
4
4
|
from: RegExp;
|
|
5
5
|
to: string;
|
|
6
6
|
}
|
|
7
|
-
export declare const networks: readonly ["mainnet", "goerli", "sepolia", "polygon", "arbitrum", "avalanche", "bsc", "crono", "fantom", "moonbeam", "optimism", "gnosis"];
|
|
7
|
+
export declare const networks: readonly ["mainnet", "goerli", "sepolia", "polygon", "arbitrum", "avalanche", "bsc", "crono", "fantom", "moonbeam", "optimism", "gnosis", "celo"];
|
|
8
8
|
export type Network = (typeof networks)[number];
|
|
9
9
|
export declare class EtherscanParser {
|
|
10
10
|
protected apikey: string;
|
|
11
11
|
network: Network;
|
|
12
12
|
readonly url: string;
|
|
13
|
-
constructor(apikey?: string, network?: Network);
|
|
13
|
+
constructor(apikey?: string, network?: Network, url?: string);
|
|
14
14
|
/**
|
|
15
15
|
* Parses the verified source code files from Etherscan
|
|
16
16
|
* @param contractAddress Ethereum contract address with a 0x prefix
|
|
@@ -26,7 +26,7 @@ export declare class EtherscanParser {
|
|
|
26
26
|
* @param contractAddress Ethereum contract address with a 0x prefix
|
|
27
27
|
* @return Promise string of Solidity code
|
|
28
28
|
*/
|
|
29
|
-
getSolidityCode(contractAddress: string): Promise<{
|
|
29
|
+
getSolidityCode(contractAddress: string, filename?: string): Promise<{
|
|
30
30
|
solidityCode: string;
|
|
31
31
|
contractName: string;
|
|
32
32
|
}>;
|
|
@@ -39,8 +39,9 @@ export declare class EtherscanParser {
|
|
|
39
39
|
/**
|
|
40
40
|
* Calls Etherscan to get the verified source code for the specified contract address
|
|
41
41
|
* @param contractAddress Ethereum contract address with a 0x prefix
|
|
42
|
+
* @oaram filename optional, case-sensitive name of the source file without the .sol
|
|
42
43
|
*/
|
|
43
|
-
getSourceCode(contractAddress: string): Promise<{
|
|
44
|
+
getSourceCode(contractAddress: string, filename?: string): Promise<{
|
|
44
45
|
files: readonly {
|
|
45
46
|
code: string;
|
|
46
47
|
filename: string;
|
package/lib/parserEtherscan.js
CHANGED
|
@@ -9,6 +9,7 @@ const parser_1 = require("@solidity-parser/parser");
|
|
|
9
9
|
const converterAST2Classes_1 = require("./converterAST2Classes");
|
|
10
10
|
const filterClasses_1 = require("./filterClasses");
|
|
11
11
|
const regEx_1 = require("./utils/regEx");
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
12
13
|
require('axios-debug-log');
|
|
13
14
|
const debug = require('debug')('sol2uml');
|
|
14
15
|
exports.networks = [
|
|
@@ -24,11 +25,16 @@ exports.networks = [
|
|
|
24
25
|
'moonbeam',
|
|
25
26
|
'optimism',
|
|
26
27
|
'gnosis',
|
|
28
|
+
'celo',
|
|
27
29
|
];
|
|
28
30
|
class EtherscanParser {
|
|
29
|
-
constructor(apikey = 'ZAD4UI2RCXCQTP38EXS3UY2MPHFU5H9KB1', network = 'mainnet') {
|
|
31
|
+
constructor(apikey = 'ZAD4UI2RCXCQTP38EXS3UY2MPHFU5H9KB1', network = 'mainnet', url) {
|
|
30
32
|
this.apikey = apikey;
|
|
31
33
|
this.network = network;
|
|
34
|
+
if (url) {
|
|
35
|
+
this.url = url;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
32
38
|
if (!exports.networks.includes(network)) {
|
|
33
39
|
throw new Error(`Invalid network "${network}". Must be one of ${exports.networks}`);
|
|
34
40
|
}
|
|
@@ -71,6 +77,10 @@ class EtherscanParser {
|
|
|
71
77
|
this.url = 'https://api.gnosisscan.io/api';
|
|
72
78
|
this.apikey = '2RWGXIWK538EJ8XSP9DE2JUINSCG7UCSJB';
|
|
73
79
|
}
|
|
80
|
+
else if (network === 'celo') {
|
|
81
|
+
this.url = 'https://api.celoscan.io/api';
|
|
82
|
+
this.apikey = 'JBV78T5KP15W7WKKKD6KC4J8RX2F4PK8AF';
|
|
83
|
+
}
|
|
74
84
|
else {
|
|
75
85
|
this.url = `https://api-${network}.etherscan.io/api`;
|
|
76
86
|
}
|
|
@@ -100,8 +110,8 @@ class EtherscanParser {
|
|
|
100
110
|
* @param contractAddress Ethereum contract address with a 0x prefix
|
|
101
111
|
* @return Promise string of Solidity code
|
|
102
112
|
*/
|
|
103
|
-
async getSolidityCode(contractAddress) {
|
|
104
|
-
const { files, contractName, compilerVersion, remappings } = await this.getSourceCode(contractAddress);
|
|
113
|
+
async getSolidityCode(contractAddress, filename) {
|
|
114
|
+
const { files, contractName, compilerVersion, remappings } = await this.getSourceCode(contractAddress, filename);
|
|
105
115
|
// Parse the UmlClasses from the Solidity code in each file
|
|
106
116
|
let umlClasses = [];
|
|
107
117
|
for (const file of files) {
|
|
@@ -164,8 +174,9 @@ class EtherscanParser {
|
|
|
164
174
|
/**
|
|
165
175
|
* Calls Etherscan to get the verified source code for the specified contract address
|
|
166
176
|
* @param contractAddress Ethereum contract address with a 0x prefix
|
|
177
|
+
* @oaram filename optional, case-sensitive name of the source file without the .sol
|
|
167
178
|
*/
|
|
168
|
-
async getSourceCode(contractAddress) {
|
|
179
|
+
async getSourceCode(contractAddress, filename) {
|
|
169
180
|
const description = `get verified source code for address ${contractAddress} from Etherscan API.`;
|
|
170
181
|
try {
|
|
171
182
|
debug(`About to get Solidity source code for ${contractAddress} from ${this.url}`);
|
|
@@ -226,8 +237,16 @@ class EtherscanParser {
|
|
|
226
237
|
filename: contractAddress,
|
|
227
238
|
};
|
|
228
239
|
});
|
|
240
|
+
let files = results.flat(1);
|
|
241
|
+
const filenameWithExt = filename + '.sol';
|
|
242
|
+
if (filename) {
|
|
243
|
+
files = files.filter((r) => path_1.default.parse(r.filename).base == filenameWithExt);
|
|
244
|
+
if (!files?.length) {
|
|
245
|
+
throw new Error(`Failed to find source file "${filename}" for contract ${contractAddress}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
229
248
|
return {
|
|
230
|
-
files
|
|
249
|
+
files,
|
|
231
250
|
contractName: response.data.result[0].ContractName,
|
|
232
251
|
compilerVersion: response.data.result[0].CompilerVersion,
|
|
233
252
|
remappings,
|
package/lib/parserGeneral.d.ts
CHANGED
package/lib/parserGeneral.js
CHANGED
|
@@ -17,7 +17,7 @@ const parserUmlClasses = async (fileFolderAddress, options) => {
|
|
|
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
19
|
const etherscanApiKey = options.apiKey || 'ZAD4UI2RCXCQTP38EXS3UY2MPHFU5H9KB1';
|
|
20
|
-
const etherscanParser = new parserEtherscan_1.EtherscanParser(etherscanApiKey, options.network);
|
|
20
|
+
const etherscanParser = new parserEtherscan_1.EtherscanParser(etherscanApiKey, options.network, options.explorerUrl);
|
|
21
21
|
result = await etherscanParser.getUmlClasses(fileFolderAddress);
|
|
22
22
|
}
|
|
23
23
|
else {
|
package/lib/sol2uml.js
CHANGED
|
@@ -29,10 +29,11 @@ Can also flatten or compare verified source files on Etherscan-like explorers.`)
|
|
|
29
29
|
.default('svg'))
|
|
30
30
|
.option('-o, --outputFileName <value>', 'output file name')
|
|
31
31
|
.option('-i, --ignoreFilesOrFolders <filesOrFolders>', 'comma separated list of files or folders to ignore')
|
|
32
|
-
.addOption(new commander_1.Option('-n, --network <network>', 'Ethereum network')
|
|
32
|
+
.addOption(new commander_1.Option('-n, --network <network>', 'Ethereum network which maps to a blockchain explorer')
|
|
33
33
|
.choices(parserEtherscan_1.networks)
|
|
34
34
|
.default('mainnet')
|
|
35
35
|
.env('ETH_NETWORK'))
|
|
36
|
+
.addOption(new commander_1.Option('-e, --explorerUrl <url>', 'Override network with custom blockchain explorer API URL. eg Polygon Mumbai testnet https://api-testnet.polygonscan.com/api').env('EXPLORER_URL'))
|
|
36
37
|
.addOption(new commander_1.Option('-k, --apiKey <key>', 'Blockchain explorer API key. eg Etherscan, Arbiscan, Optimism, BscScan, CronoScan, FTMScan, PolygonScan or SnowTrace API key').env('SCAN_API_KEY'))
|
|
37
38
|
.option('-bc, --backColor <color>', 'Canvas background color. "none" will use a transparent canvas.', 'white')
|
|
38
39
|
.option('-sc, --shapeColor <color>', 'Basic drawing color for graphics, not text', 'black')
|
|
@@ -202,7 +203,7 @@ In order for the merged code to compile, the following is done:
|
|
|
202
203
|
...command.parent._optionValues,
|
|
203
204
|
...options,
|
|
204
205
|
};
|
|
205
|
-
const etherscanParser = new parserEtherscan_1.EtherscanParser(combinedOptions.apiKey, combinedOptions.network);
|
|
206
|
+
const etherscanParser = new parserEtherscan_1.EtherscanParser(combinedOptions.apiKey, combinedOptions.network, combinedOptions.explorerUrl);
|
|
206
207
|
const { solidityCode, contractName } = await etherscanParser.getSolidityCode(contractAddress);
|
|
207
208
|
// Write Solidity to the contract address
|
|
208
209
|
const outputFilename = combinedOptions.outputFileName || contractName;
|
|
@@ -225,6 +226,8 @@ The line numbers are from contract B. There are no line numbers for the red sect
|
|
|
225
226
|
.argument('<addressA>', 'Contract address in hexadecimal format with a 0x prefix of the first contract.')
|
|
226
227
|
.argument('<addressB>', 'Contract address in hexadecimal format with a 0x prefix of the second contract.')
|
|
227
228
|
.addOption(new commander_1.Option('-l, --lineBuffer <value>', 'Minimum number of lines before and after changes').default('4'))
|
|
229
|
+
.addOption(new commander_1.Option('-af --aFile <value>', 'Contract A source code filename without the .sol extension. (default: compares all source files)'))
|
|
230
|
+
.addOption(new commander_1.Option('-bf --bFile <value>', 'Contract B source code filename without the .sol extension. (default: aFile if specified)'))
|
|
228
231
|
.option('-s, --saveFiles', 'Save the flattened contract code to the filesystem. The file names will be the contract address with a .sol extension.', false)
|
|
229
232
|
.action(async (addressA, addressB, options, command) => {
|
|
230
233
|
try {
|
|
@@ -237,10 +240,10 @@ The line numbers are from contract B. There are no line numbers for the red sect
|
|
|
237
240
|
const lineBuffer = parseInt(options.lineBuffer);
|
|
238
241
|
if (isNaN(lineBuffer))
|
|
239
242
|
throw Error(`Invalid line buffer "${options.lineBuffer}". Must be a number`);
|
|
240
|
-
const etherscanParser = new parserEtherscan_1.EtherscanParser(combinedOptions.apiKey, combinedOptions.network);
|
|
243
|
+
const etherscanParser = new parserEtherscan_1.EtherscanParser(combinedOptions.apiKey, combinedOptions.network, combinedOptions.explorerUrl);
|
|
241
244
|
// Get verified Solidity code from Etherscan and flatten
|
|
242
|
-
const { solidityCode: codeA, contractName: contractNameA } = await etherscanParser.getSolidityCode(addressA);
|
|
243
|
-
const { solidityCode: codeB, contractName: contractNameB } = await etherscanParser.getSolidityCode(addressB);
|
|
245
|
+
const { solidityCode: codeA, contractName: contractNameA } = await etherscanParser.getSolidityCode(addressA, combinedOptions.aFile);
|
|
246
|
+
const { solidityCode: codeB, contractName: contractNameB } = await etherscanParser.getSolidityCode(addressB, combinedOptions.bFile || combinedOptions.aFile);
|
|
244
247
|
console.log(`Difference between`);
|
|
245
248
|
console.log(`A. ${addressA} ${contractNameA} on ${combinedOptions.network}`);
|
|
246
249
|
console.log(`B. ${addressB} ${contractNameB} on ${combinedOptions.network}\n`);
|