sol2uml 2.0.3 → 2.1.0
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/LICENSE +1 -1
- package/README.md +43 -26
- package/lib/converterAST2Classes.js +13 -1
- package/lib/converterClasses2Storage.d.ts +14 -7
- package/lib/converterClasses2Storage.js +79 -52
- package/lib/converterStorage2Dot.d.ts +3 -3
- package/lib/converterStorage2Dot.js +64 -55
- package/lib/slotValues.d.ts +3 -0
- package/lib/slotValues.js +51 -0
- package/lib/sol2uml.js +36 -12
- package/lib/umlClass.d.ts +6 -0
- package/lib/umlClass.js +1 -0
- package/lib/writerFiles.d.ts +2 -2
- package/lib/writerFiles.js +25 -22
- package/package.json +4 -3
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -20,16 +20,19 @@ The following installation assumes [Node.js](https://nodejs.org/en/download/) ha
|
|
|
20
20
|
`sol2uml` works with node 14 or above.
|
|
21
21
|
|
|
22
22
|
To install globally so you can run `sol2uml` from anywhere
|
|
23
|
+
|
|
23
24
|
```bash
|
|
24
25
|
npm link sol2uml --only=production
|
|
25
26
|
```
|
|
26
27
|
|
|
27
28
|
To upgrade run
|
|
29
|
+
|
|
28
30
|
```bash
|
|
29
31
|
npm upgrade sol2uml -g
|
|
30
32
|
```
|
|
31
33
|
|
|
32
34
|
To see which version you are using
|
|
35
|
+
|
|
33
36
|
```bash
|
|
34
37
|
npm ls sol2uml -g
|
|
35
38
|
```
|
|
@@ -109,15 +112,19 @@ Usage: sol2uml storage [options] <fileFolderAddress>
|
|
|
109
112
|
|
|
110
113
|
Visually display a contract's storage slots.
|
|
111
114
|
|
|
112
|
-
WARNING: sol2uml does not use the Solidity compiler so may differ with solc. A known example is
|
|
115
|
+
WARNING: sol2uml does not use the Solidity compiler so may differ with solc. A known example is fixed-sized arrays declared with an expression will fail to be sized.
|
|
113
116
|
|
|
114
117
|
Arguments:
|
|
115
|
-
fileFolderAddress
|
|
118
|
+
fileFolderAddress file name, base folder or contract address
|
|
116
119
|
|
|
117
120
|
Options:
|
|
118
|
-
-c, --
|
|
119
|
-
-
|
|
120
|
-
|
|
121
|
+
-c, --contract <name> Contract name in local Solidity files. Not needed when using an address as the first argument as the contract name can be derived from Etherscan.
|
|
122
|
+
-d, --data Gets the values in the storage slots from an Ethereum node. (default: false)
|
|
123
|
+
-s, --storage <address> The address of the contract with the storage values. This will be different from the contract with the code if a proxy contract is used. This is not needed if `fileFolderAddress` is an address and
|
|
124
|
+
the contract is not proxied.
|
|
125
|
+
-u, --url <url> URL of the Ethereum node to get storage values if the `data` option is used. (default: "http://localhost:8545", env: NODE_URL)
|
|
126
|
+
-bn, --block <number> Block number to get the contract storage values from. (default: "latest")
|
|
127
|
+
-h, --help display help for command
|
|
121
128
|
```
|
|
122
129
|
|
|
123
130
|
### Flatten usage
|
|
@@ -139,36 +146,43 @@ Options:
|
|
|
139
146
|
## UML Class diagram examples
|
|
140
147
|
|
|
141
148
|
To generate a diagram of all contracts under the contracts folder and its sub folders
|
|
149
|
+
|
|
142
150
|
```bash
|
|
143
151
|
sol2uml class ./contracts
|
|
144
152
|
```
|
|
145
153
|
|
|
146
154
|
To generate a diagram of EtherDelta's contract from the verified source code on [Etherscan](https://etherscan.io/address/0x8d12A197cB00D4747a1fe03395095ce2A5CC6819#code). The output wil be a svg file `0x8d12A197cB00D4747a1fe03395095ce2A5CC6819.svg` in the working folder.
|
|
155
|
+
|
|
147
156
|
```bash
|
|
148
157
|
sol2uml class 0x8d12A197cB00D4747a1fe03395095ce2A5CC6819
|
|
149
158
|
```
|
|
150
159
|
|
|
151
160
|
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 wil be a svg file `0xa19833bd291b66aB0E17b9C6d46D2Ec5fEC15190.svg` in the working folder.
|
|
161
|
+
|
|
152
162
|
```bash
|
|
153
163
|
sol2uml class 0xa19833bd291b66aB0E17b9C6d46D2Ec5fEC15190 -n ropsten
|
|
154
164
|
```
|
|
155
165
|
|
|
156
166
|
To generate all Solidity files under some root folder and output the svg file to a specific location
|
|
167
|
+
|
|
157
168
|
```bash
|
|
158
169
|
sol2uml class path/to/contracts/root/folder -o ./outputFile.svg
|
|
159
170
|
```
|
|
160
171
|
|
|
161
172
|
To generate a diagram of all contracts in a single Solidity file, the output file in png format to output file `./someFile.png`
|
|
173
|
+
|
|
162
174
|
```bash
|
|
163
175
|
sol2uml class path/to/contracts/root/folder/solidity/file.sol -f png -o ./someFile.png
|
|
164
176
|
```
|
|
165
177
|
|
|
166
|
-
To generate a diagram of all Solidity files under the `contracts` and `node_modules/openzeppelin-solidity` folders.
|
|
178
|
+
To generate a diagram of all Solidity files under the `contracts` and `node_modules/openzeppelin-solidity` folders. The output will be `contracts.svg` and `contracts.png` files in the working folder.
|
|
179
|
+
|
|
167
180
|
```bash
|
|
168
181
|
sol2uml class ./contracts,node_modules/openzeppelin-solidity -f all -v
|
|
169
182
|
```
|
|
170
183
|
|
|
171
184
|
To generate a diagram of all Solidity files under the working folder ignoring and files under the `solparse`, `@solidity-parser` and `ethlint` folders, which will be under the `node_modules` folder.
|
|
185
|
+
|
|
172
186
|
```bash
|
|
173
187
|
sol2uml class -i solparse,@solidity-parser,ethlint
|
|
174
188
|
```
|
|
@@ -176,8 +190,9 @@ sol2uml class -i solparse,@solidity-parser,ethlint
|
|
|
176
190
|
# UML Class Diagram Syntax
|
|
177
191
|
|
|
178
192
|
Good online resources for learning UML
|
|
179
|
-
|
|
180
|
-
|
|
193
|
+
|
|
194
|
+
- [UML 2 Class Diagramming Guidelines](http://www.agilemodeling.com/style/classDiagram.htm)
|
|
195
|
+
- [Creating class diagrams with UML](https://www.ionos.com/digitalguide/websites/web-development/class-diagrams-with-uml/)
|
|
181
196
|
|
|
182
197
|
## Terminology differences
|
|
183
198
|
|
|
@@ -187,33 +202,35 @@ A Solidity variable becomes an attribute in UML and a Solidity function becomes
|
|
|
187
202
|
|
|
188
203
|
### Class stereotypes
|
|
189
204
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
205
|
+
- Interface
|
|
206
|
+
- Abstract - if any of the contract's functions are abstract, the class will have an Abstract stereotype. Child contracts of abstract contracts that do not implement all the abstract functions are currently not marked as Abstract.
|
|
207
|
+
- Library
|
|
193
208
|
|
|
194
209
|
### Operator stereotypes
|
|
195
210
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
211
|
+
- event
|
|
212
|
+
- modifier
|
|
213
|
+
- abstract - if there is no function body on a contract, the operator is marked as abstract. Operators on an Interface do not have an abstract stereotype as all operators are abstract.
|
|
214
|
+
- fallback - abstract fallback functions will just have an abstract stereotype.
|
|
215
|
+
- payable - payable fallback functions will just have a fallback stereotype.
|
|
201
216
|
|
|
202
217
|
## UML Associations
|
|
203
218
|
|
|
204
219
|
Lines:
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
-
|
|
208
|
-
-
|
|
209
|
-
-
|
|
210
|
-
|
|
211
|
-
-
|
|
220
|
+
|
|
221
|
+
- Solid lines for
|
|
222
|
+
- link the contract types of storage (state) variables. This can be linked to contracts, interfaces, libraries or file level structs and enums.
|
|
223
|
+
- generalisations of contracts and abstract contracts.
|
|
224
|
+
- aggregated contract level structs and enums.
|
|
225
|
+
- Dashed lines for
|
|
226
|
+
- generalisations of interfaces.
|
|
227
|
+
- types of memory variables.
|
|
212
228
|
|
|
213
229
|
Heads/Tails:
|
|
214
|
-
|
|
215
|
-
-
|
|
216
|
-
-
|
|
230
|
+
|
|
231
|
+
- An empty triangle head for generalisations of contracts, interfaces and abstract contracts.
|
|
232
|
+
- An open arrow head for storage or memory variable dependencies
|
|
233
|
+
- A diamond tail for aggregations of contract level structs and enums
|
|
217
234
|
|
|
218
235
|
## Storage diagram
|
|
219
236
|
|
|
@@ -98,7 +98,9 @@ function convertAST2UmlClasses(node, relativePath, filesystem = false) {
|
|
|
98
98
|
}
|
|
99
99
|
else {
|
|
100
100
|
// this has come from Etherscan
|
|
101
|
-
const importPath =
|
|
101
|
+
const importPath = childNode.path[0] === '@'
|
|
102
|
+
? childNode.path
|
|
103
|
+
: path.join(codeFolder, childNode.path);
|
|
102
104
|
imports.push({
|
|
103
105
|
absolutePath: importPath,
|
|
104
106
|
classNames: childNode.symbolAliases
|
|
@@ -172,6 +174,16 @@ function parseContractDefinition(umlClass, node) {
|
|
|
172
174
|
attributeType,
|
|
173
175
|
compiled: valueStore,
|
|
174
176
|
});
|
|
177
|
+
// Is the variable a constant that could be used in declaring fixed sized arrays
|
|
178
|
+
if (variable.isDeclaredConst) {
|
|
179
|
+
if (variable?.expression.type === 'NumberLiteral') {
|
|
180
|
+
umlClass.constants.push({
|
|
181
|
+
name: variable.name,
|
|
182
|
+
value: parseInt(variable.expression.number),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
// TODO handle expressions. eg N_COINS * 2
|
|
186
|
+
}
|
|
175
187
|
});
|
|
176
188
|
// Recursively parse variables for associations
|
|
177
189
|
umlClass = addAssociations(subNode.variables, umlClass);
|
|
@@ -3,7 +3,7 @@ export declare enum StorageType {
|
|
|
3
3
|
Contract = 0,
|
|
4
4
|
Struct = 1
|
|
5
5
|
}
|
|
6
|
-
export interface
|
|
6
|
+
export interface Variable {
|
|
7
7
|
id: number;
|
|
8
8
|
fromSlot: number;
|
|
9
9
|
toSlot: number;
|
|
@@ -12,18 +12,25 @@ export interface Storage {
|
|
|
12
12
|
type: string;
|
|
13
13
|
variable: string;
|
|
14
14
|
contractName?: string;
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
values: string[];
|
|
16
|
+
structStorageId?: number;
|
|
17
17
|
enumId?: number;
|
|
18
18
|
}
|
|
19
|
-
export interface
|
|
19
|
+
export interface Storage {
|
|
20
20
|
id: number;
|
|
21
21
|
name: string;
|
|
22
22
|
address?: string;
|
|
23
23
|
type: StorageType;
|
|
24
|
-
|
|
24
|
+
variables: Variable[];
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
/**
|
|
27
|
+
*
|
|
28
|
+
* @param url
|
|
29
|
+
* @param contractAddress Contract address to get the storage slot values from
|
|
30
|
+
* @param storage is mutated with the storage values
|
|
31
|
+
*/
|
|
32
|
+
export declare const addStorageValues: (url: string, contractAddress: string, storage: Storage, blockTag: string) => Promise<void>;
|
|
33
|
+
export declare const convertClasses2Storages: (contractName: string, umlClasses: UmlClass[]) => Storage[];
|
|
34
|
+
export declare const parseStructStorage: (attribute: Attribute, otherClasses: UmlClass[], storages: Storage[]) => Storage | undefined;
|
|
28
35
|
export declare const calcStorageByteSize: (attribute: Attribute, umlClass: UmlClass, otherClasses: UmlClass[]) => number;
|
|
29
36
|
export declare const isElementary: (type: string) => boolean;
|
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isElementary = exports.calcStorageByteSize = exports.
|
|
3
|
+
exports.isElementary = exports.calcStorageByteSize = exports.parseStructStorage = exports.convertClasses2Storages = exports.addStorageValues = exports.StorageType = void 0;
|
|
4
4
|
const umlClass_1 = require("./umlClass");
|
|
5
5
|
const associations_1 = require("./associations");
|
|
6
|
+
const slotValues_1 = require("./slotValues");
|
|
6
7
|
var StorageType;
|
|
7
8
|
(function (StorageType) {
|
|
8
9
|
StorageType[StorageType["Contract"] = 0] = "Contract";
|
|
9
10
|
StorageType[StorageType["Struct"] = 1] = "Struct";
|
|
10
11
|
})(StorageType = exports.StorageType || (exports.StorageType = {}));
|
|
11
|
-
let storageObjectId = 1;
|
|
12
12
|
let storageId = 1;
|
|
13
|
-
|
|
13
|
+
let variableId = 1;
|
|
14
|
+
/**
|
|
15
|
+
*
|
|
16
|
+
* @param url
|
|
17
|
+
* @param contractAddress Contract address to get the storage slot values from
|
|
18
|
+
* @param storage is mutated with the storage values
|
|
19
|
+
*/
|
|
20
|
+
const addStorageValues = async (url, contractAddress, storage, blockTag) => {
|
|
21
|
+
const slots = storage.variables.map((s) => s.fromSlot);
|
|
22
|
+
const values = await (0, slotValues_1.getStorageValues)(url, contractAddress, slots, blockTag);
|
|
23
|
+
storage.variables.forEach((storage, i) => {
|
|
24
|
+
storage.values = [values[i]];
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
exports.addStorageValues = addStorageValues;
|
|
28
|
+
const convertClasses2Storages = (contractName, umlClasses) => {
|
|
14
29
|
// Find the base UML Class from the base contract name
|
|
15
30
|
const umlClass = umlClasses.find(({ name }) => {
|
|
16
31
|
return name === contractName;
|
|
@@ -18,25 +33,25 @@ const convertClasses2StorageObjects = (contractName, umlClasses) => {
|
|
|
18
33
|
if (!umlClass) {
|
|
19
34
|
throw Error(`Failed to find contract with name "${contractName}"`);
|
|
20
35
|
}
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
id:
|
|
36
|
+
const storages = [];
|
|
37
|
+
const variables = parseVariables(umlClass, umlClasses, [], storages, []);
|
|
38
|
+
storages.unshift({
|
|
39
|
+
id: storageId++,
|
|
25
40
|
name: contractName,
|
|
26
41
|
type: StorageType.Contract,
|
|
27
|
-
|
|
42
|
+
variables: variables,
|
|
28
43
|
});
|
|
29
|
-
return
|
|
44
|
+
return storages;
|
|
30
45
|
};
|
|
31
|
-
exports.
|
|
46
|
+
exports.convertClasses2Storages = convertClasses2Storages;
|
|
32
47
|
/**
|
|
33
|
-
* Recursively parses the storage for a given contract.
|
|
48
|
+
* Recursively parses the storage variables for a given contract.
|
|
34
49
|
* @param umlClass contract or file level struct
|
|
35
50
|
* @param umlClasses other contracts, structs and enums that may be a type of a storage variable.
|
|
36
|
-
* @param
|
|
37
|
-
* @param
|
|
51
|
+
* @param variables mutable array of storage slots that is appended to
|
|
52
|
+
* @param storages mutable array of storages that is appended with structs
|
|
38
53
|
*/
|
|
39
|
-
const
|
|
54
|
+
const parseVariables = (umlClass, umlClasses, variables, storages, inheritedContracts) => {
|
|
40
55
|
// Add storage slots from inherited contracts first.
|
|
41
56
|
// Get immediate parent contracts that the class inherits from
|
|
42
57
|
const parentContracts = umlClass.getParentContracts();
|
|
@@ -50,7 +65,7 @@ const parseStorage = (umlClass, umlClasses, storages, storageObjects, inheritedC
|
|
|
50
65
|
if (!parentClass)
|
|
51
66
|
throw Error(`Failed to find parent contract ${parent.targetUmlClassName} of ${umlClass.absolutePath}`);
|
|
52
67
|
// recursively parse inherited contract
|
|
53
|
-
|
|
68
|
+
parseVariables(parentClass, umlClasses, variables, storages, inheritedContracts);
|
|
54
69
|
});
|
|
55
70
|
// Parse storage for each attribute
|
|
56
71
|
umlClass.attributes.forEach((attribute) => {
|
|
@@ -59,20 +74,20 @@ const parseStorage = (umlClass, umlClasses, storages, storageObjects, inheritedC
|
|
|
59
74
|
return;
|
|
60
75
|
const byteSize = (0, exports.calcStorageByteSize)(attribute, umlClass, umlClasses);
|
|
61
76
|
// find any dependent structs
|
|
62
|
-
const linkedStruct = (0, exports.
|
|
63
|
-
const
|
|
77
|
+
const linkedStruct = (0, exports.parseStructStorage)(attribute, umlClasses, storages);
|
|
78
|
+
const structStorageId = linkedStruct?.id;
|
|
64
79
|
// Get the toSlot of the last storage item
|
|
65
80
|
let lastToSlot = 0;
|
|
66
81
|
let nextOffset = 0;
|
|
67
|
-
if (
|
|
68
|
-
const lastStorage =
|
|
82
|
+
if (variables.length > 0) {
|
|
83
|
+
const lastStorage = variables[variables.length - 1];
|
|
69
84
|
lastToSlot = lastStorage.toSlot;
|
|
70
85
|
nextOffset = lastStorage.byteOffset + lastStorage.byteSize;
|
|
71
86
|
}
|
|
72
87
|
if (nextOffset + byteSize > 32) {
|
|
73
|
-
const nextFromSlot =
|
|
74
|
-
|
|
75
|
-
id:
|
|
88
|
+
const nextFromSlot = variables.length > 0 ? lastToSlot + 1 : 0;
|
|
89
|
+
variables.push({
|
|
90
|
+
id: variableId++,
|
|
76
91
|
fromSlot: nextFromSlot,
|
|
77
92
|
toSlot: nextFromSlot + Math.floor((byteSize - 1) / 32),
|
|
78
93
|
byteSize,
|
|
@@ -80,12 +95,13 @@ const parseStorage = (umlClass, umlClasses, storages, storageObjects, inheritedC
|
|
|
80
95
|
type: attribute.type,
|
|
81
96
|
variable: attribute.name,
|
|
82
97
|
contractName: umlClass.name,
|
|
83
|
-
|
|
98
|
+
structStorageId,
|
|
99
|
+
values: [],
|
|
84
100
|
});
|
|
85
101
|
}
|
|
86
102
|
else {
|
|
87
|
-
|
|
88
|
-
id:
|
|
103
|
+
variables.push({
|
|
104
|
+
id: variableId++,
|
|
89
105
|
fromSlot: lastToSlot,
|
|
90
106
|
toSlot: lastToSlot,
|
|
91
107
|
byteSize,
|
|
@@ -93,18 +109,19 @@ const parseStorage = (umlClass, umlClasses, storages, storageObjects, inheritedC
|
|
|
93
109
|
type: attribute.type,
|
|
94
110
|
variable: attribute.name,
|
|
95
111
|
contractName: umlClass.name,
|
|
96
|
-
|
|
112
|
+
structStorageId,
|
|
113
|
+
values: [],
|
|
97
114
|
});
|
|
98
115
|
}
|
|
99
116
|
});
|
|
100
|
-
return
|
|
117
|
+
return variables;
|
|
101
118
|
};
|
|
102
|
-
const
|
|
119
|
+
const parseStructStorage = (attribute, otherClasses, storages) => {
|
|
103
120
|
if (attribute.attributeType === umlClass_1.AttributeType.UserDefined) {
|
|
104
|
-
// Have we already created the
|
|
105
|
-
const
|
|
106
|
-
if (
|
|
107
|
-
return
|
|
121
|
+
// Have we already created the storage?
|
|
122
|
+
const existingStorage = storages.find((dep) => dep.name === attribute.type);
|
|
123
|
+
if (existingStorage) {
|
|
124
|
+
return existingStorage;
|
|
108
125
|
}
|
|
109
126
|
// Is the user defined type linked to another Contract, Struct or Enum?
|
|
110
127
|
const dependentClass = otherClasses.find(({ name }) => {
|
|
@@ -114,15 +131,15 @@ const parseStructStorageObject = (attribute, otherClasses, storageObjects) => {
|
|
|
114
131
|
throw Error(`Failed to find user defined type "${attribute.type}"`);
|
|
115
132
|
}
|
|
116
133
|
if (dependentClass.stereotype === umlClass_1.ClassStereotype.Struct) {
|
|
117
|
-
const
|
|
118
|
-
const
|
|
119
|
-
id:
|
|
134
|
+
const variables = parseVariables(dependentClass, otherClasses, [], storages, []);
|
|
135
|
+
const newStorage = {
|
|
136
|
+
id: storageId++,
|
|
120
137
|
name: attribute.type,
|
|
121
138
|
type: StorageType.Struct,
|
|
122
|
-
|
|
139
|
+
variables,
|
|
123
140
|
};
|
|
124
|
-
|
|
125
|
-
return
|
|
141
|
+
storages.push(newStorage);
|
|
142
|
+
return newStorage;
|
|
126
143
|
}
|
|
127
144
|
return undefined;
|
|
128
145
|
}
|
|
@@ -135,10 +152,10 @@ const parseStructStorageObject = (attribute, otherClasses, storageObjects) => {
|
|
|
135
152
|
? attribute.type.match(/=\\>((?!mapping)\w*)[\\[]/)
|
|
136
153
|
: attribute.type.match(/(\w+)\[/);
|
|
137
154
|
if (result !== null && result[1] && !(0, exports.isElementary)(result[1])) {
|
|
138
|
-
// Have we already created the
|
|
139
|
-
const
|
|
140
|
-
if (
|
|
141
|
-
return
|
|
155
|
+
// Have we already created the storage?
|
|
156
|
+
const existingStorage = storages.find(({ name }) => name === result[1] || name === result[1].split('.')[1]);
|
|
157
|
+
if (existingStorage) {
|
|
158
|
+
return existingStorage;
|
|
142
159
|
}
|
|
143
160
|
// Find UserDefined type
|
|
144
161
|
const typeClass = otherClasses.find(({ name }) => name === result[1] || name === result[1].split('.')[1]);
|
|
@@ -146,22 +163,22 @@ const parseStructStorageObject = (attribute, otherClasses, storageObjects) => {
|
|
|
146
163
|
throw Error(`Failed to find user defined type "${result[1]}" in attribute type "${attribute.type}"`);
|
|
147
164
|
}
|
|
148
165
|
if (typeClass.stereotype === umlClass_1.ClassStereotype.Struct) {
|
|
149
|
-
const
|
|
150
|
-
const
|
|
151
|
-
id:
|
|
166
|
+
const variables = parseVariables(typeClass, otherClasses, [], storages, []);
|
|
167
|
+
const newStorage = {
|
|
168
|
+
id: storageId++,
|
|
152
169
|
name: typeClass.name,
|
|
153
170
|
type: StorageType.Struct,
|
|
154
|
-
|
|
171
|
+
variables,
|
|
155
172
|
};
|
|
156
|
-
|
|
157
|
-
return
|
|
173
|
+
storages.push(newStorage);
|
|
174
|
+
return newStorage;
|
|
158
175
|
}
|
|
159
176
|
}
|
|
160
177
|
return undefined;
|
|
161
178
|
}
|
|
162
179
|
return undefined;
|
|
163
180
|
};
|
|
164
|
-
exports.
|
|
181
|
+
exports.parseStructStorage = parseStructStorage;
|
|
165
182
|
// Calculates the storage size of an attribute in bytes
|
|
166
183
|
const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
|
|
167
184
|
if (attribute.attributeType === umlClass_1.AttributeType.Mapping ||
|
|
@@ -170,7 +187,7 @@ const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
|
|
|
170
187
|
}
|
|
171
188
|
if (attribute.attributeType === umlClass_1.AttributeType.Array) {
|
|
172
189
|
// All array dimensions must be fixed. eg [2][3][8].
|
|
173
|
-
const result = attribute.type.match(/(\w+)(\[([
|
|
190
|
+
const result = attribute.type.match(/(\w+)(\[([\w][\w]*)\])+$/);
|
|
174
191
|
// The above will not match any dynamic array dimensions, eg [],
|
|
175
192
|
// as there needs to be one or more [0-9]+ in the square brackets
|
|
176
193
|
if (result === null) {
|
|
@@ -180,9 +197,19 @@ const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
|
|
|
180
197
|
}
|
|
181
198
|
// All array dimensions are fixes so we now need to multiply all the dimensions
|
|
182
199
|
// to get a total number of array elements
|
|
183
|
-
const arrayDimensions = attribute.type.match(/\[\
|
|
200
|
+
const arrayDimensions = attribute.type.match(/\[\w+/g);
|
|
184
201
|
const dimensionsStr = arrayDimensions.map((d) => d.slice(1));
|
|
185
|
-
const dimensions = dimensionsStr.map((
|
|
202
|
+
const dimensions = dimensionsStr.map((dimension) => {
|
|
203
|
+
const dimensionNum = parseInt(dimension);
|
|
204
|
+
if (!isNaN(dimensionNum))
|
|
205
|
+
return dimensionNum;
|
|
206
|
+
// Try and size array dimension from declared constants
|
|
207
|
+
const constant = umlClass.constants.find((constant) => constant.name === dimension);
|
|
208
|
+
if (constant) {
|
|
209
|
+
return constant.value;
|
|
210
|
+
}
|
|
211
|
+
throw Error(`Could not size fixed sized array with dimension "${dimension}"`);
|
|
212
|
+
});
|
|
186
213
|
let elementSize;
|
|
187
214
|
// If a fixed sized array
|
|
188
215
|
if ((0, exports.isElementary)(result[1])) {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare const
|
|
3
|
-
export declare function
|
|
1
|
+
import { Storage } from './converterClasses2Storage';
|
|
2
|
+
export declare const convertStorages2Dot: (storages: Storage[]) => string;
|
|
3
|
+
export declare function convertStorage2Dot(storage: Storage, dotString: string): string;
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.convertStorage2Dot = exports.convertStorages2Dot = void 0;
|
|
4
4
|
const converterClasses2Storage_1 = require("./converterClasses2Storage");
|
|
5
5
|
const debug = require('debug')('sol2uml');
|
|
6
|
-
const
|
|
6
|
+
const convertStorages2Dot = (storages) => {
|
|
7
7
|
let dotString = `
|
|
8
8
|
digraph StorageDiagram {
|
|
9
9
|
rankdir=LR
|
|
10
10
|
color=black
|
|
11
11
|
arrowhead=open
|
|
12
12
|
node [shape=record, style=filled, fillcolor=gray95]`;
|
|
13
|
-
// process contract and the struct
|
|
14
|
-
|
|
15
|
-
dotString =
|
|
13
|
+
// process contract and the struct storages
|
|
14
|
+
storages.forEach((storage) => {
|
|
15
|
+
dotString = convertStorage2Dot(storage, dotString);
|
|
16
16
|
});
|
|
17
17
|
// link contract and structs to structs
|
|
18
|
-
|
|
19
|
-
slot.
|
|
20
|
-
if (storage.
|
|
21
|
-
dotString += `\n ${slot.id}:${storage.id} -> ${storage.
|
|
18
|
+
storages.forEach((slot) => {
|
|
19
|
+
slot.variables.forEach((storage) => {
|
|
20
|
+
if (storage.structStorageId) {
|
|
21
|
+
dotString += `\n ${slot.id}:${storage.id} -> ${storage.structStorageId}`;
|
|
22
22
|
}
|
|
23
23
|
});
|
|
24
24
|
});
|
|
@@ -27,63 +27,72 @@ node [shape=record, style=filled, fillcolor=gray95]`;
|
|
|
27
27
|
debug(dotString);
|
|
28
28
|
return dotString;
|
|
29
29
|
};
|
|
30
|
-
exports.
|
|
31
|
-
function
|
|
32
|
-
const steorotype =
|
|
33
|
-
// write
|
|
34
|
-
dotString += `\n${
|
|
30
|
+
exports.convertStorages2Dot = convertStorages2Dot;
|
|
31
|
+
function convertStorage2Dot(storage, dotString) {
|
|
32
|
+
const steorotype = storage.type === converterClasses2Storage_1.StorageType.Struct ? 'Struct' : 'Contract';
|
|
33
|
+
// write storage header with name and optional address
|
|
34
|
+
dotString += `\n${storage.id} [label="${storage.name} \\<\\<${steorotype}\\>\\>\\n${storage.address || ''} | {`;
|
|
35
|
+
const startingVariables = storage.variables.filter((s) => s.byteOffset === 0);
|
|
35
36
|
// write slot numbers
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
dotString += '{ slot';
|
|
38
|
+
startingVariables.forEach((variable, i) => {
|
|
39
|
+
if (variable.fromSlot === variable.toSlot) {
|
|
40
|
+
dotString += `| ${variable.fromSlot} `;
|
|
39
41
|
}
|
|
40
|
-
else
|
|
41
|
-
|
|
42
|
-
dotString += `| ${storage.fromSlot}`;
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
dotString += `| ${storage.fromSlot}-${storage.toSlot}`;
|
|
46
|
-
}
|
|
42
|
+
else {
|
|
43
|
+
dotString += `| ${variable.fromSlot}-${variable.toSlot} `;
|
|
47
44
|
}
|
|
48
45
|
});
|
|
49
|
-
// write
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
46
|
+
// write slot values if available
|
|
47
|
+
if (startingVariables[0]?.values[0]) {
|
|
48
|
+
dotString += '} | {value';
|
|
49
|
+
startingVariables.forEach((variable, i) => {
|
|
50
|
+
dotString += ` | ${variable.values[0]}`;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
const contractVariablePrefix = storage.type === converterClasses2Storage_1.StorageType.Contract ? '\\<inherited contract\\>.' : '';
|
|
54
|
+
dotString += `} | { type: ${contractVariablePrefix}variable (bytes)`;
|
|
55
|
+
// For each slot
|
|
56
|
+
startingVariables.forEach((variable) => {
|
|
57
|
+
// Get all the storage variables in this slot
|
|
58
|
+
const slotVariables = storage.variables.filter((s) => s.fromSlot === variable.fromSlot);
|
|
59
|
+
const usedBytes = slotVariables.reduce((acc, s) => acc + s.byteSize, 0);
|
|
60
|
+
if (usedBytes < 32) {
|
|
61
|
+
slotVariables.push({
|
|
62
|
+
id: 0,
|
|
63
|
+
fromSlot: variable.fromSlot,
|
|
64
|
+
toSlot: variable.fromSlot,
|
|
65
|
+
byteSize: 32 - usedBytes,
|
|
66
|
+
byteOffset: usedBytes,
|
|
67
|
+
type: 'unallocated',
|
|
68
|
+
contractName: variable.contractName,
|
|
69
|
+
variable: '',
|
|
70
|
+
values: [],
|
|
71
|
+
});
|
|
75
72
|
}
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
const slotVariablesReversed = slotVariables.reverse();
|
|
74
|
+
// For each variable in the slot
|
|
75
|
+
slotVariablesReversed.forEach((variable, i) => {
|
|
76
|
+
if (i === 0) {
|
|
77
|
+
dotString += ` | { ${dotVariable(variable, storage.name)} `;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
dotString += ` | ${dotVariable(variable, storage.name)} `;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
dotString += '}';
|
|
78
84
|
});
|
|
79
85
|
// Need to close off the last label
|
|
80
86
|
dotString += '}}"]\n';
|
|
81
87
|
return dotString;
|
|
82
88
|
}
|
|
83
|
-
exports.
|
|
89
|
+
exports.convertStorage2Dot = convertStorage2Dot;
|
|
84
90
|
const dotVariable = (storage, contractName) => {
|
|
85
|
-
const port = storage.
|
|
91
|
+
const port = storage.structStorageId !== undefined ? `<${storage.id}>` : '';
|
|
86
92
|
const contractNamePrefix = storage.contractName !== contractName ? `${storage.contractName}.` : '';
|
|
87
|
-
|
|
93
|
+
const variable = storage.variable
|
|
94
|
+
? `: ${contractNamePrefix}${storage.variable}`
|
|
95
|
+
: '';
|
|
96
|
+
return `${port} ${storage.type}${variable} (${storage.byteSize})`;
|
|
88
97
|
};
|
|
89
98
|
//# sourceMappingURL=converterStorage2Dot.js.map
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { BigNumberish } from '@ethersproject/bignumber';
|
|
2
|
+
export declare const getStorageValue: (url: string, contractAddress: string, slot: BigNumberish, blockTag?: BigNumberish | 'latest') => Promise<string>;
|
|
3
|
+
export declare const getStorageValues: (url: string, contractAddress: string, slots: BigNumberish[], blockTag?: BigNumberish | 'latest') => Promise<string[]>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getStorageValues = exports.getStorageValue = void 0;
|
|
7
|
+
const bignumber_1 = require("@ethersproject/bignumber");
|
|
8
|
+
const axios_1 = __importDefault(require("axios"));
|
|
9
|
+
const debug = require('debug')('sol2uml');
|
|
10
|
+
const getStorageValue = async (url, contractAddress, slot, blockTag = 'latest') => {
|
|
11
|
+
debug(`About to get storage slot ${slot} value for ${contractAddress}`);
|
|
12
|
+
const values = await (0, exports.getStorageValues)(url, contractAddress, [slot], blockTag);
|
|
13
|
+
debug(`Got slot ${slot} value: ${values[0]}`);
|
|
14
|
+
return values[0];
|
|
15
|
+
};
|
|
16
|
+
exports.getStorageValue = getStorageValue;
|
|
17
|
+
let jsonRpcId = 0;
|
|
18
|
+
const getStorageValues = async (url, contractAddress, slots, blockTag = 'latest') => {
|
|
19
|
+
try {
|
|
20
|
+
debug(`About to get ${slots.length} storage values for ${contractAddress} at block ${blockTag}`);
|
|
21
|
+
const block = blockTag === 'latest'
|
|
22
|
+
? blockTag
|
|
23
|
+
: bignumber_1.BigNumber.from(blockTag).toHexString();
|
|
24
|
+
const payload = slots.map((slot) => ({
|
|
25
|
+
id: (jsonRpcId++).toString(),
|
|
26
|
+
jsonrpc: '2.0',
|
|
27
|
+
method: 'eth_getStorageAt',
|
|
28
|
+
params: [
|
|
29
|
+
contractAddress,
|
|
30
|
+
bignumber_1.BigNumber.from(slot).toHexString(),
|
|
31
|
+
block,
|
|
32
|
+
],
|
|
33
|
+
}));
|
|
34
|
+
const response = await axios_1.default.post(url, payload);
|
|
35
|
+
console.log(response.data);
|
|
36
|
+
if (response.data?.error?.message) {
|
|
37
|
+
throw new Error(response.data.error.message);
|
|
38
|
+
}
|
|
39
|
+
if (response.data.length !== slots.length) {
|
|
40
|
+
throw new Error(`Requested ${slots.length} storage slot values but only got ${response.data.length}`);
|
|
41
|
+
}
|
|
42
|
+
const responseData = response.data;
|
|
43
|
+
const sortedResponses = responseData.sort((a, b) => bignumber_1.BigNumber.from(a.id).gt(b.id) ? 1 : -1);
|
|
44
|
+
return sortedResponses.map((data) => data.result);
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
throw new Error(`Failed to get ${slots.length} storage values for ${contractAddress} from ${url}`, { cause: err });
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
exports.getStorageValues = getStorageValues;
|
|
51
|
+
//# sourceMappingURL=slotValues.js.map
|
package/lib/sol2uml.js
CHANGED
|
@@ -75,14 +75,14 @@ If an Ethereum address with a 0x prefix is passed, the verified source code from
|
|
|
75
75
|
...command.parent._optionValues,
|
|
76
76
|
...options,
|
|
77
77
|
};
|
|
78
|
-
const { umlClasses } = await (0, parserGeneral_1.parserUmlClasses)(fileFolderAddress, combinedOptions);
|
|
78
|
+
const { umlClasses, contractName } = await (0, parserGeneral_1.parserUmlClasses)(fileFolderAddress, combinedOptions);
|
|
79
79
|
let filteredUmlClasses = umlClasses;
|
|
80
80
|
if (options.baseContractNames) {
|
|
81
81
|
const baseContractNames = options.baseContractNames.split(',');
|
|
82
82
|
filteredUmlClasses = (0, filterClasses_1.classesConnectedToBaseContracts)(umlClasses, baseContractNames, options.depth);
|
|
83
83
|
}
|
|
84
84
|
const dotString = (0, converterClasses2Dot_1.convertUmlClasses2Dot)(filteredUmlClasses, combinedOptions.clusterFolders, combinedOptions);
|
|
85
|
-
await (0, writerFiles_1.writeOutputFiles)(dotString, fileFolderAddress, combinedOptions.outputFormat, combinedOptions.outputFileName);
|
|
85
|
+
await (0, writerFiles_1.writeOutputFiles)(dotString, fileFolderAddress, contractName, combinedOptions.outputFormat, combinedOptions.outputFileName);
|
|
86
86
|
debug(`Finished generating UML`);
|
|
87
87
|
}
|
|
88
88
|
catch (err) {
|
|
@@ -91,25 +91,49 @@ If an Ethereum address with a 0x prefix is passed, the verified source code from
|
|
|
91
91
|
});
|
|
92
92
|
program
|
|
93
93
|
.command('storage')
|
|
94
|
-
.description("Visually display a contract's storage slots.\n\nWARNING: sol2uml does not use the Solidity compiler so may differ with solc. A known example is
|
|
94
|
+
.description("Visually display a contract's storage slots.\n\nWARNING: sol2uml does not use the Solidity compiler so may differ with solc. A known example is fixed-sized arrays declared with an expression will fail to be sized.")
|
|
95
95
|
.argument('<fileFolderAddress>', 'file name, base folder or contract address')
|
|
96
|
-
.option('-c, --
|
|
97
|
-
|
|
96
|
+
.option('-c, --contract <name>', 'Contract name in local Solidity files. Not needed when using an address as the first argument as the contract name can be derived from Etherscan.')
|
|
97
|
+
.option('-d, --data', 'Gets the values in the storage slots from an Ethereum node.', false)
|
|
98
|
+
.option('-s, --storage <address>', 'The address of the contract with the storage values. This will be different from the contract with the code if a proxy contract is used. This is not needed if `fileFolderAddress` is an address and the contract is not proxied.')
|
|
99
|
+
.addOption(new commander_1.Option('-u, --url <url>', 'URL of the Ethereum node to get storage values if the `data` option is used.')
|
|
100
|
+
.env('NODE_URL')
|
|
101
|
+
.default('http://localhost:8545'))
|
|
102
|
+
.option('-bn, --block <number>', 'Block number to get the contract storage values from.', 'latest')
|
|
98
103
|
.action(async (fileFolderAddress, options, command) => {
|
|
99
104
|
try {
|
|
100
105
|
const combinedOptions = {
|
|
101
106
|
...command.parent._optionValues,
|
|
102
107
|
...options,
|
|
103
108
|
};
|
|
104
|
-
|
|
105
|
-
|
|
109
|
+
let { umlClasses, contractName } = await (0, parserGeneral_1.parserUmlClasses)(fileFolderAddress, combinedOptions);
|
|
110
|
+
contractName = combinedOptions.contract || contractName;
|
|
111
|
+
const storages = (0, converterClasses2Storage_1.convertClasses2Storages)(contractName, umlClasses);
|
|
106
112
|
if ((0, regEx_1.isAddress)(fileFolderAddress)) {
|
|
107
|
-
// The first
|
|
108
|
-
|
|
113
|
+
// The first storage is the contract
|
|
114
|
+
storages[0].address = fileFolderAddress;
|
|
115
|
+
}
|
|
116
|
+
debug(storages);
|
|
117
|
+
if (combinedOptions.data) {
|
|
118
|
+
let storageAddress = combinedOptions.storage;
|
|
119
|
+
if (storageAddress) {
|
|
120
|
+
if (!(0, regEx_1.isAddress)(storageAddress)) {
|
|
121
|
+
throw Error(`Invalid address to get storage data from "${storageAddress}"`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
if (!(0, regEx_1.isAddress)(fileFolderAddress)) {
|
|
126
|
+
throw Error(`Can not get storage slot values if first param is not an address and the \`address\` option is not used.`);
|
|
127
|
+
}
|
|
128
|
+
storageAddress = fileFolderAddress;
|
|
129
|
+
}
|
|
130
|
+
const storage = storages.find((so) => so.name === contractName);
|
|
131
|
+
if (!storageAddress)
|
|
132
|
+
throw Error(`Could not find the "${contractName}" contract in list of parsed storages`);
|
|
133
|
+
await (0, converterClasses2Storage_1.addStorageValues)(combinedOptions.url, storageAddress, storage, combinedOptions.blockNumber);
|
|
109
134
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
await (0, writerFiles_1.writeOutputFiles)(dotString, fileFolderAddress, combinedOptions.outputFormat, combinedOptions.outputFileName);
|
|
135
|
+
const dotString = (0, converterStorage2Dot_1.convertStorages2Dot)(storages);
|
|
136
|
+
await (0, writerFiles_1.writeOutputFiles)(dotString, fileFolderAddress, contractName, combinedOptions.outputFormat, combinedOptions.outputFileName);
|
|
113
137
|
}
|
|
114
138
|
catch (err) {
|
|
115
139
|
console.error(`Failed to generate storage diagram ${err}`);
|
package/lib/umlClass.d.ts
CHANGED
|
@@ -63,6 +63,10 @@ export interface Association {
|
|
|
63
63
|
targetUmlClassStereotype?: ClassStereotype;
|
|
64
64
|
realization?: boolean;
|
|
65
65
|
}
|
|
66
|
+
export interface Constants {
|
|
67
|
+
name: string;
|
|
68
|
+
value: number;
|
|
69
|
+
}
|
|
66
70
|
export interface ClassProperties {
|
|
67
71
|
name: string;
|
|
68
72
|
absolutePath: string;
|
|
@@ -76,6 +80,7 @@ export interface ClassProperties {
|
|
|
76
80
|
associations?: {
|
|
77
81
|
[name: string]: Association;
|
|
78
82
|
};
|
|
83
|
+
constants?: Constants[];
|
|
79
84
|
}
|
|
80
85
|
export declare class UmlClass implements ClassProperties {
|
|
81
86
|
static idCounter: number;
|
|
@@ -85,6 +90,7 @@ export declare class UmlClass implements ClassProperties {
|
|
|
85
90
|
relativePath: string;
|
|
86
91
|
imports?: Import[];
|
|
87
92
|
stereotype?: ClassStereotype;
|
|
93
|
+
constants: Constants[];
|
|
88
94
|
attributes: Attribute[];
|
|
89
95
|
operators: Operator[];
|
|
90
96
|
enums: number[];
|
package/lib/umlClass.js
CHANGED
package/lib/writerFiles.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export declare type OutputFormats = 'svg' | 'png' | 'dot' | 'all';
|
|
2
|
-
export declare const writeOutputFiles: (dot: string,
|
|
2
|
+
export declare const writeOutputFiles: (dot: string, fileFolderAddress: string, contractName: string, outputFormat?: OutputFormats, outputFilename?: string) => Promise<void>;
|
|
3
3
|
export declare function convertDot2Svg(dot: string): any;
|
|
4
4
|
export declare function writeSolidity(code: string, filename?: string): void;
|
|
5
|
-
export declare function writeDot(dot: string, filename
|
|
5
|
+
export declare function writeDot(dot: string, filename: string): void;
|
|
6
6
|
export declare function writeSVG(svg: any, svgFilename?: string, outputFormats?: OutputFormats): Promise<void>;
|
|
7
7
|
export declare function writePng(svg: any, filename: string): Promise<void>;
|
package/lib/writerFiles.js
CHANGED
|
@@ -9,7 +9,26 @@ const path_1 = __importDefault(require("path"));
|
|
|
9
9
|
const sync_1 = __importDefault(require("@aduh95/viz.js/sync"));
|
|
10
10
|
const { convert } = require('convert-svg-to-png');
|
|
11
11
|
const debug = require('debug')('sol2uml');
|
|
12
|
-
const writeOutputFiles = async (dot,
|
|
12
|
+
const writeOutputFiles = async (dot, fileFolderAddress, contractName, outputFormat = 'svg', outputFilename) => {
|
|
13
|
+
// If all output then extension is svg
|
|
14
|
+
const outputExt = outputFormat === 'all' ? 'svg' : outputFormat;
|
|
15
|
+
if (!outputFilename) {
|
|
16
|
+
outputFilename =
|
|
17
|
+
path_1.default.join(process.cwd(), contractName) + '.' + outputExt;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
// check if outputFilename is a folder
|
|
21
|
+
try {
|
|
22
|
+
const folderOrFile = (0, fs_1.lstatSync)(outputFilename);
|
|
23
|
+
if (folderOrFile.isDirectory()) {
|
|
24
|
+
outputFilename =
|
|
25
|
+
path_1.default.join(process.cwd(), outputFilename, contractName) +
|
|
26
|
+
'.' +
|
|
27
|
+
outputExt;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch (err) { } // we can ignore errors as it just means outputFilename does not exist yet
|
|
31
|
+
}
|
|
13
32
|
if (outputFormat === 'dot' || outputFormat === 'all') {
|
|
14
33
|
writeDot(dot, outputFilename);
|
|
15
34
|
// No need to continue if only generating a dot file
|
|
@@ -17,20 +36,6 @@ const writeOutputFiles = async (dot, outputBaseName, outputFormat = 'svg', outpu
|
|
|
17
36
|
return;
|
|
18
37
|
}
|
|
19
38
|
}
|
|
20
|
-
if (!outputFilename) {
|
|
21
|
-
// If all output then extension is svg
|
|
22
|
-
const outputExt = outputFormat === 'all' ? 'svg' : outputFormat;
|
|
23
|
-
// if outputBaseName is a folder
|
|
24
|
-
try {
|
|
25
|
-
const folderOrFile = (0, fs_1.lstatSync)(outputBaseName);
|
|
26
|
-
if (folderOrFile.isDirectory()) {
|
|
27
|
-
const parsedDir = path_1.default.parse(process.cwd());
|
|
28
|
-
outputBaseName = path_1.default.join(process.cwd(), parsedDir.name);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
catch (err) { } // we can ignore errors as it just means outputBaseName is not a folder
|
|
32
|
-
outputFilename = outputBaseName + '.' + outputExt;
|
|
33
|
-
}
|
|
34
39
|
const svg = convertDot2Svg(dot);
|
|
35
40
|
if (outputFormat === 'svg' || outputFormat === 'all') {
|
|
36
41
|
await writeSVG(svg, outputFilename, outputFormat);
|
|
@@ -68,18 +73,16 @@ function writeSolidity(code, filename = 'solidity') {
|
|
|
68
73
|
});
|
|
69
74
|
}
|
|
70
75
|
exports.writeSolidity = writeSolidity;
|
|
71
|
-
function writeDot(dot, filename
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
debug(`About to write Dot file to ${outputFile}`);
|
|
75
|
-
(0, fs_1.writeFile)(outputFile, dot, (err) => {
|
|
76
|
+
function writeDot(dot, filename) {
|
|
77
|
+
debug(`About to write Dot file to ${filename}`);
|
|
78
|
+
(0, fs_1.writeFile)(filename, dot, (err) => {
|
|
76
79
|
if (err) {
|
|
77
|
-
throw new Error(`Failed to write Dot file to ${
|
|
80
|
+
throw new Error(`Failed to write Dot file to ${filename}`, {
|
|
78
81
|
cause: err,
|
|
79
82
|
});
|
|
80
83
|
}
|
|
81
84
|
else {
|
|
82
|
-
console.log(`Dot file written to ${
|
|
85
|
+
console.log(`Dot file written to ${filename}`);
|
|
83
86
|
}
|
|
84
87
|
});
|
|
85
88
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sol2uml",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Unified Modeling Language (UML) class diagram generator for Solidity contracts",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"types": "./lib/index.d.ts",
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"buildSol": "cd ./src/contracts && solc **/*.sol",
|
|
9
9
|
"build": "tsc --build ./tsconfig.json",
|
|
10
10
|
"clean": "tsc --build --clean ./tsconfig.json",
|
|
11
|
-
"prettier": "prettier --write
|
|
12
|
-
"prettier:check": "prettier --check
|
|
11
|
+
"prettier": "prettier --write src/**/*.ts **/*.md",
|
|
12
|
+
"prettier:check": "prettier --check src/**/*.ts **/*.md",
|
|
13
13
|
"test": "npx jest"
|
|
14
14
|
},
|
|
15
15
|
"preferGlobal": true,
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"commander": "^9.4.0",
|
|
26
26
|
"convert-svg-to-png": "^0.6.4",
|
|
27
27
|
"debug": "^4.3.4",
|
|
28
|
+
"ethers": "^5.6.9",
|
|
28
29
|
"js-graph-algorithms": "^1.0.18",
|
|
29
30
|
"klaw": "^4.0.1"
|
|
30
31
|
},
|