sol2uml 2.1.0 → 2.1.1
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 +7 -5
- package/lib/associations.d.ts +1 -1
- package/lib/associations.js +65 -21
- package/lib/converterAST2Classes.js +1 -1
- package/lib/converterClasses2Storage.d.ts +19 -7
- package/lib/converterClasses2Storage.js +212 -82
- package/lib/converterStorage2Dot.d.ts +6 -2
- package/lib/converterStorage2Dot.js +14 -12
- package/lib/parserEtherscan.js +8 -1
- package/lib/slotValues.js +1 -1
- package/lib/sol2uml.js +25 -18
- package/lib/umlClass.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,17 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/js/sol2uml)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
A visualisation tool for [Solidity](https://solidity.readthedocs.io/) contracts featuring:
|
|
6
|
+
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.
|
|
7
|
+
2. Contract storage layout diagrams.
|
|
6
8
|
|
|
7
|
-
Open Zeppelin's ERC20 token contracts generated from [version 2.5.1](https://github.com/OpenZeppelin/openzeppelin-solidity/tree/v2.5.1/contracts/token/ERC20)
|
|
9
|
+
UML class diagram of Open Zeppelin's ERC20 token contracts generated from [version 2.5.1](https://github.com/OpenZeppelin/openzeppelin-solidity/tree/v2.5.1/contracts/token/ERC20)
|
|
8
10
|

|
|
9
11
|
|
|
10
12
|
See more contract diagrams [here](./examples/README.md).
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
 on Etherscan.
|
|
15
|
+

|
|
14
16
|
|
|
15
|
-
See more storage
|
|
17
|
+
See more contract storage diagram examples [here](./examples/storage/README.md).
|
|
16
18
|
|
|
17
19
|
# Install
|
|
18
20
|
|
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: UmlClass[]) => UmlClass;
|
|
2
|
+
export declare const findAssociatedClass: (association: Association, sourceUmlClass: UmlClass, umlClasses: UmlClass[]) => UmlClass | undefined;
|
package/lib/associations.js
CHANGED
|
@@ -1,31 +1,75 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.findAssociatedClass = void 0;
|
|
4
|
+
const umlClass_1 = require("./umlClass");
|
|
4
5
|
// Find the UML class linked to the association
|
|
5
6
|
const findAssociatedClass = (association, sourceUmlClass, umlClasses) => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
let umlClass = umlClasses.find((targetUmlClass) => {
|
|
8
|
+
// is the source class link via the association to the target class?
|
|
9
|
+
if (isAssociated(association, sourceUmlClass, targetUmlClass))
|
|
10
|
+
return true;
|
|
11
|
+
// Not linked so now try linking to target under the node_modules folder.
|
|
12
|
+
// eg remove node_modules from node_modules/@openzeppelin/contracts-upgradeable/proxy/Initializable.sol
|
|
13
|
+
// is the target class under node_modules?
|
|
14
|
+
if (targetUmlClass.relativePath.match(/^node_modules\//)) {
|
|
15
|
+
// clone the target and updated absolutePath and relativePath so it's no longer under node_modules
|
|
16
|
+
const clonedTargetClass = new umlClass_1.UmlClass(targetUmlClass);
|
|
17
|
+
clonedTargetClass.absolutePath =
|
|
18
|
+
targetUmlClass.absolutePath.replace(/^node_modules\//, '');
|
|
19
|
+
clonedTargetClass.relativePath =
|
|
20
|
+
targetUmlClass.relativePath.replace(/^node_modules\//, '');
|
|
21
|
+
// is the source class link via the association to the target class?
|
|
22
|
+
return isAssociated(association, sourceUmlClass, clonedTargetClass);
|
|
23
|
+
}
|
|
24
|
+
// could not find a link from the source to target via the association
|
|
25
|
+
return false;
|
|
26
|
+
});
|
|
27
|
+
// If a link was found
|
|
28
|
+
if (umlClass)
|
|
29
|
+
return umlClass;
|
|
30
|
+
// Could not find a link so now need to recursively look at imports of imports
|
|
31
|
+
return findImplicitImport(association, sourceUmlClass, umlClasses);
|
|
32
|
+
};
|
|
33
|
+
exports.findAssociatedClass = findAssociatedClass;
|
|
34
|
+
// Tests if source class can be linked to the target class via an association
|
|
35
|
+
const isAssociated = (association, sourceUmlClass, targetUmlClass) => {
|
|
36
|
+
return (
|
|
37
|
+
// class is in the same source file
|
|
38
|
+
(association.targetUmlClassName === targetUmlClass.name &&
|
|
39
|
+
sourceUmlClass.absolutePath === targetUmlClass.absolutePath) ||
|
|
40
|
+
// imported classes with no explicit import names
|
|
9
41
|
(association.targetUmlClassName === targetUmlClass.name &&
|
|
10
|
-
sourceUmlClass.absolutePath === targetUmlClass.absolutePath) ||
|
|
11
|
-
// imported classes with no explicit import names
|
|
12
|
-
(association.targetUmlClassName === targetUmlClass.name &&
|
|
13
|
-
sourceUmlClass.imports.find((i) => i.absolutePath === targetUmlClass.absolutePath &&
|
|
14
|
-
i.classNames.length === 0)) ||
|
|
15
|
-
// imported classes with explicit import names or import aliases
|
|
16
42
|
sourceUmlClass.imports.find((i) => i.absolutePath === targetUmlClass.absolutePath &&
|
|
17
|
-
i.classNames.
|
|
18
|
-
|
|
43
|
+
i.classNames.length === 0)) ||
|
|
44
|
+
// imported classes with explicit import names or import aliases
|
|
45
|
+
sourceUmlClass.imports.find((i) => i.absolutePath === targetUmlClass.absolutePath &&
|
|
46
|
+
i.classNames.find((importedClass) =>
|
|
47
|
+
// no import alias
|
|
48
|
+
(association.targetUmlClassName ===
|
|
49
|
+
importedClass.className &&
|
|
50
|
+
importedClass.className === targetUmlClass.name &&
|
|
51
|
+
importedClass.alias == undefined) ||
|
|
52
|
+
// import alias
|
|
19
53
|
(association.targetUmlClassName ===
|
|
20
|
-
importedClass.
|
|
21
|
-
importedClass.className ===
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
54
|
+
importedClass.alias &&
|
|
55
|
+
importedClass.className === targetUmlClass.name))));
|
|
56
|
+
};
|
|
57
|
+
const findImplicitImport = (association, sourceUmlClass, umlClasses) => {
|
|
58
|
+
// Get all implicit imports. That is, imports that do not explicitly import contracts or interfaces.
|
|
59
|
+
const implicitImports = sourceUmlClass.imports.filter((i) => i.classNames.length === 0);
|
|
60
|
+
// For each implicit import
|
|
61
|
+
for (const importDetail of implicitImports) {
|
|
62
|
+
// Find a class with the same absolute path as the import so we can get the new imports
|
|
63
|
+
const newSourceUmlClass = umlClasses.find((c) => c.absolutePath === importDetail.absolutePath);
|
|
64
|
+
if (!newSourceUmlClass) {
|
|
65
|
+
// Could not find a class in the import file so just move onto the next loop
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
// TODO need to handle imports that use aliases as the association will not be found
|
|
69
|
+
const umlClass = (0, exports.findAssociatedClass)(association, newSourceUmlClass, umlClasses);
|
|
70
|
+
if (umlClass)
|
|
71
|
+
return umlClass;
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
29
74
|
};
|
|
30
|
-
exports.findAssociatedClass = findAssociatedClass;
|
|
31
75
|
//# sourceMappingURL=associations.js.map
|
|
@@ -176,7 +176,7 @@ function parseContractDefinition(umlClass, node) {
|
|
|
176
176
|
});
|
|
177
177
|
// Is the variable a constant that could be used in declaring fixed sized arrays
|
|
178
178
|
if (variable.isDeclaredConst) {
|
|
179
|
-
if (variable?.expression
|
|
179
|
+
if (variable?.expression?.type === 'NumberLiteral') {
|
|
180
180
|
umlClass.constants.push({
|
|
181
181
|
name: variable.name,
|
|
182
182
|
value: parseInt(variable.expression.number),
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Attribute, UmlClass } from './umlClass';
|
|
2
2
|
export declare enum StorageType {
|
|
3
|
-
Contract =
|
|
4
|
-
Struct =
|
|
3
|
+
Contract = "Contract",
|
|
4
|
+
Struct = "Struct",
|
|
5
|
+
Array = "Array"
|
|
5
6
|
}
|
|
6
7
|
export interface Variable {
|
|
7
8
|
id: number;
|
|
@@ -10,17 +11,22 @@ export interface Variable {
|
|
|
10
11
|
byteSize: number;
|
|
11
12
|
byteOffset: number;
|
|
12
13
|
type: string;
|
|
13
|
-
|
|
14
|
+
dynamic: boolean;
|
|
15
|
+
variable?: string;
|
|
14
16
|
contractName?: string;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
noValue: boolean;
|
|
18
|
+
value?: string;
|
|
19
|
+
referenceStorageId?: number;
|
|
17
20
|
enumId?: number;
|
|
18
21
|
}
|
|
19
22
|
export interface Storage {
|
|
20
23
|
id: number;
|
|
21
24
|
name: string;
|
|
22
25
|
address?: string;
|
|
26
|
+
slotKey?: string;
|
|
23
27
|
type: StorageType;
|
|
28
|
+
arrayLength?: number;
|
|
29
|
+
arrayDynamic?: boolean;
|
|
24
30
|
variables: Variable[];
|
|
25
31
|
}
|
|
26
32
|
/**
|
|
@@ -31,6 +37,12 @@ export interface Storage {
|
|
|
31
37
|
*/
|
|
32
38
|
export declare const addStorageValues: (url: string, contractAddress: string, storage: Storage, blockTag: string) => Promise<void>;
|
|
33
39
|
export declare const convertClasses2Storages: (contractName: string, umlClasses: UmlClass[]) => Storage[];
|
|
34
|
-
export declare const
|
|
35
|
-
export declare const calcStorageByteSize: (attribute: Attribute, umlClass: UmlClass, otherClasses: UmlClass[]) =>
|
|
40
|
+
export declare const parseReferenceStorage: (attribute: Attribute, umlClass: UmlClass, otherClasses: UmlClass[], storages: Storage[]) => Storage | undefined;
|
|
41
|
+
export declare const calcStorageByteSize: (attribute: Attribute, umlClass: UmlClass, otherClasses: UmlClass[]) => {
|
|
42
|
+
size: number;
|
|
43
|
+
dynamic: boolean;
|
|
44
|
+
};
|
|
36
45
|
export declare const isElementary: (type: string) => boolean;
|
|
46
|
+
export declare const calcSlotKey: (variable: Variable) => string | undefined;
|
|
47
|
+
export declare const offsetStorageSlots: (storage: Storage, slots: number, storages: Storage[]) => void;
|
|
48
|
+
export declare const findDimensionLength: (umlClass: UmlClass, dimension: string) => number;
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isElementary = exports.calcStorageByteSize = exports.
|
|
3
|
+
exports.findDimensionLength = exports.offsetStorageSlots = exports.calcSlotKey = exports.isElementary = exports.calcStorageByteSize = exports.parseReferenceStorage = exports.convertClasses2Storages = exports.addStorageValues = exports.StorageType = void 0;
|
|
4
4
|
const umlClass_1 = require("./umlClass");
|
|
5
5
|
const associations_1 = require("./associations");
|
|
6
6
|
const slotValues_1 = require("./slotValues");
|
|
7
|
+
const utils_1 = require("ethers/lib/utils");
|
|
8
|
+
const ethers_1 = require("ethers");
|
|
7
9
|
var StorageType;
|
|
8
10
|
(function (StorageType) {
|
|
9
|
-
StorageType[
|
|
10
|
-
StorageType[
|
|
11
|
+
StorageType["Contract"] = "Contract";
|
|
12
|
+
StorageType["Struct"] = "Struct";
|
|
13
|
+
StorageType["Array"] = "Array";
|
|
11
14
|
})(StorageType = exports.StorageType || (exports.StorageType = {}));
|
|
12
15
|
let storageId = 1;
|
|
13
16
|
let variableId = 1;
|
|
@@ -18,10 +21,11 @@ let variableId = 1;
|
|
|
18
21
|
* @param storage is mutated with the storage values
|
|
19
22
|
*/
|
|
20
23
|
const addStorageValues = async (url, contractAddress, storage, blockTag) => {
|
|
21
|
-
const
|
|
24
|
+
const valueVariables = storage.variables.filter((s) => !s.noValue);
|
|
25
|
+
const slots = valueVariables.map((s) => s.fromSlot);
|
|
22
26
|
const values = await (0, slotValues_1.getStorageValues)(url, contractAddress, slots, blockTag);
|
|
23
|
-
|
|
24
|
-
storage.
|
|
27
|
+
valueVariables.forEach((storage, i) => {
|
|
28
|
+
storage.value = values[i];
|
|
25
29
|
});
|
|
26
30
|
};
|
|
27
31
|
exports.addStorageValues = addStorageValues;
|
|
@@ -62,8 +66,9 @@ const parseVariables = (umlClass, umlClasses, variables, storages, inheritedCont
|
|
|
62
66
|
// Recursively parse each new inherited contract
|
|
63
67
|
newInheritedContracts.forEach((parent) => {
|
|
64
68
|
const parentClass = (0, associations_1.findAssociatedClass)(parent, umlClass, umlClasses);
|
|
65
|
-
if (!parentClass)
|
|
66
|
-
throw Error(`Failed to find
|
|
69
|
+
if (!parentClass) {
|
|
70
|
+
throw Error(`Failed to find inherited contract "${parent.targetUmlClassName}" of "${umlClass.absolutePath}"`);
|
|
71
|
+
}
|
|
67
72
|
// recursively parse inherited contract
|
|
68
73
|
parseVariables(parentClass, umlClasses, variables, storages, inheritedContracts);
|
|
69
74
|
});
|
|
@@ -72,10 +77,11 @@ const parseVariables = (umlClass, umlClasses, variables, storages, inheritedCont
|
|
|
72
77
|
// Ignore any attributes that are constants or immutable
|
|
73
78
|
if (attribute.compiled)
|
|
74
79
|
return;
|
|
75
|
-
const byteSize = (0, exports.calcStorageByteSize)(attribute, umlClass, umlClasses);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
80
|
+
const { size: byteSize, dynamic } = (0, exports.calcStorageByteSize)(attribute, umlClass, umlClasses);
|
|
81
|
+
const noValue = attribute.attributeType === umlClass_1.AttributeType.Mapping ||
|
|
82
|
+
(attribute.attributeType === umlClass_1.AttributeType.Array && !dynamic);
|
|
83
|
+
// find any dependent storage locations
|
|
84
|
+
const referenceStorage = (0, exports.parseReferenceStorage)(attribute, umlClass, umlClasses, storages);
|
|
79
85
|
// Get the toSlot of the last storage item
|
|
80
86
|
let lastToSlot = 0;
|
|
81
87
|
let nextOffset = 0;
|
|
@@ -84,45 +90,121 @@ const parseVariables = (umlClass, umlClasses, variables, storages, inheritedCont
|
|
|
84
90
|
lastToSlot = lastStorage.toSlot;
|
|
85
91
|
nextOffset = lastStorage.byteOffset + lastStorage.byteSize;
|
|
86
92
|
}
|
|
93
|
+
let newVariable;
|
|
87
94
|
if (nextOffset + byteSize > 32) {
|
|
88
95
|
const nextFromSlot = variables.length > 0 ? lastToSlot + 1 : 0;
|
|
89
|
-
|
|
96
|
+
newVariable = {
|
|
90
97
|
id: variableId++,
|
|
91
98
|
fromSlot: nextFromSlot,
|
|
92
99
|
toSlot: nextFromSlot + Math.floor((byteSize - 1) / 32),
|
|
93
100
|
byteSize,
|
|
94
101
|
byteOffset: 0,
|
|
95
102
|
type: attribute.type,
|
|
103
|
+
dynamic,
|
|
104
|
+
noValue,
|
|
96
105
|
variable: attribute.name,
|
|
97
106
|
contractName: umlClass.name,
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
});
|
|
107
|
+
referenceStorageId: referenceStorage?.id,
|
|
108
|
+
};
|
|
101
109
|
}
|
|
102
110
|
else {
|
|
103
|
-
|
|
111
|
+
newVariable = {
|
|
104
112
|
id: variableId++,
|
|
105
113
|
fromSlot: lastToSlot,
|
|
106
114
|
toSlot: lastToSlot,
|
|
107
115
|
byteSize,
|
|
108
116
|
byteOffset: nextOffset,
|
|
109
117
|
type: attribute.type,
|
|
118
|
+
dynamic,
|
|
119
|
+
noValue,
|
|
110
120
|
variable: attribute.name,
|
|
111
121
|
contractName: umlClass.name,
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
122
|
+
referenceStorageId: referenceStorage?.id,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (referenceStorage) {
|
|
126
|
+
if (!newVariable.dynamic) {
|
|
127
|
+
(0, exports.offsetStorageSlots)(referenceStorage, newVariable.fromSlot, storages);
|
|
128
|
+
}
|
|
129
|
+
else if (attribute.attributeType === umlClass_1.AttributeType.Array) {
|
|
130
|
+
referenceStorage.slotKey = (0, exports.calcSlotKey)(newVariable);
|
|
131
|
+
}
|
|
115
132
|
}
|
|
133
|
+
variables.push(newVariable);
|
|
116
134
|
});
|
|
117
135
|
return variables;
|
|
118
136
|
};
|
|
119
|
-
const
|
|
120
|
-
if (attribute.attributeType === umlClass_1.AttributeType.
|
|
121
|
-
//
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
137
|
+
const parseReferenceStorage = (attribute, umlClass, otherClasses, storages) => {
|
|
138
|
+
if (attribute.attributeType === umlClass_1.AttributeType.Array) {
|
|
139
|
+
// storage is dynamic if the attribute type ends in []
|
|
140
|
+
const result = attribute.type.match(/\[(\w*)]$/);
|
|
141
|
+
const dynamic = result[1] === '';
|
|
142
|
+
const arrayLength = !dynamic
|
|
143
|
+
? (0, exports.findDimensionLength)(umlClass, result[1])
|
|
144
|
+
: undefined;
|
|
145
|
+
// get the type of the array items. eg
|
|
146
|
+
// address[][4][2] will have base type address[][4]
|
|
147
|
+
const baseType = attribute.type.substring(0, attribute.type.lastIndexOf('['));
|
|
148
|
+
let baseAttributeType;
|
|
149
|
+
if ((0, exports.isElementary)(baseType)) {
|
|
150
|
+
baseAttributeType = umlClass_1.AttributeType.Elementary;
|
|
125
151
|
}
|
|
152
|
+
else if (baseType[baseType.length - 1] === ']') {
|
|
153
|
+
baseAttributeType = umlClass_1.AttributeType.Array;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
baseAttributeType = umlClass_1.AttributeType.UserDefined;
|
|
157
|
+
}
|
|
158
|
+
const baseAttribute = {
|
|
159
|
+
visibility: attribute.visibility,
|
|
160
|
+
name: baseType,
|
|
161
|
+
type: baseType,
|
|
162
|
+
attributeType: baseAttributeType,
|
|
163
|
+
};
|
|
164
|
+
const { size: arrayItemSize } = (0, exports.calcStorageByteSize)(baseAttribute, umlClass, otherClasses);
|
|
165
|
+
const slotSize = arrayItemSize > 16 ? 32 : arrayItemSize;
|
|
166
|
+
const firstVariable = {
|
|
167
|
+
id: variableId++,
|
|
168
|
+
fromSlot: 0,
|
|
169
|
+
toSlot: Math.floor((slotSize - 1) / 32),
|
|
170
|
+
byteSize: arrayItemSize,
|
|
171
|
+
byteOffset: 0,
|
|
172
|
+
type: baseType,
|
|
173
|
+
dynamic,
|
|
174
|
+
noValue: false,
|
|
175
|
+
};
|
|
176
|
+
const variables = [firstVariable];
|
|
177
|
+
if (arrayLength > 1) {
|
|
178
|
+
for (let i = 1; i < arrayLength; i++) {
|
|
179
|
+
variables.push({
|
|
180
|
+
id: variableId++,
|
|
181
|
+
fromSlot: Math.floor((i * slotSize) / 32),
|
|
182
|
+
toSlot: Math.floor(((i + 1) * slotSize - 1) / 32),
|
|
183
|
+
byteSize: arrayItemSize,
|
|
184
|
+
byteOffset: (i * slotSize) % 32,
|
|
185
|
+
type: baseType,
|
|
186
|
+
dynamic,
|
|
187
|
+
noValue: false,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// recursively add storage
|
|
192
|
+
if (baseAttributeType !== umlClass_1.AttributeType.Elementary) {
|
|
193
|
+
const referenceStorage = (0, exports.parseReferenceStorage)(baseAttribute, umlClass, otherClasses, storages);
|
|
194
|
+
firstVariable.referenceStorageId = referenceStorage?.id;
|
|
195
|
+
}
|
|
196
|
+
const newStorage = {
|
|
197
|
+
id: storageId++,
|
|
198
|
+
name: `${attribute.type}: ${attribute.name}`,
|
|
199
|
+
type: StorageType.Array,
|
|
200
|
+
arrayDynamic: dynamic,
|
|
201
|
+
arrayLength,
|
|
202
|
+
variables,
|
|
203
|
+
};
|
|
204
|
+
storages.push(newStorage);
|
|
205
|
+
return newStorage;
|
|
206
|
+
}
|
|
207
|
+
if (attribute.attributeType === umlClass_1.AttributeType.UserDefined) {
|
|
126
208
|
// Is the user defined type linked to another Contract, Struct or Enum?
|
|
127
209
|
const dependentClass = otherClasses.find(({ name }) => {
|
|
128
210
|
return (name === attribute.type || name === attribute.type.split('.')[1]);
|
|
@@ -143,20 +225,13 @@ const parseStructStorage = (attribute, otherClasses, storages) => {
|
|
|
143
225
|
}
|
|
144
226
|
return undefined;
|
|
145
227
|
}
|
|
146
|
-
if (attribute.attributeType === umlClass_1.AttributeType.Mapping
|
|
147
|
-
|
|
148
|
-
// get the UserDefined type from the mapping or array
|
|
228
|
+
if (attribute.attributeType === umlClass_1.AttributeType.Mapping) {
|
|
229
|
+
// get the UserDefined type from the mapping
|
|
149
230
|
// note the mapping could be an array of Structs
|
|
150
231
|
// Could also be a mapping of a mapping
|
|
151
|
-
const result = attribute.
|
|
152
|
-
|
|
153
|
-
: attribute.type.match(/(\w+)\[/);
|
|
232
|
+
const result = attribute.type.match(/=\\>((?!mapping)\w*)[\\[]/);
|
|
233
|
+
// If mapping of user defined type
|
|
154
234
|
if (result !== null && result[1] && !(0, exports.isElementary)(result[1])) {
|
|
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;
|
|
159
|
-
}
|
|
160
235
|
// Find UserDefined type
|
|
161
236
|
const typeClass = otherClasses.find(({ name }) => name === result[1] || name === result[1].split('.')[1]);
|
|
162
237
|
if (!typeClass) {
|
|
@@ -178,66 +253,78 @@ const parseStructStorage = (attribute, otherClasses, storages) => {
|
|
|
178
253
|
}
|
|
179
254
|
return undefined;
|
|
180
255
|
};
|
|
181
|
-
exports.
|
|
256
|
+
exports.parseReferenceStorage = parseReferenceStorage;
|
|
182
257
|
// Calculates the storage size of an attribute in bytes
|
|
183
258
|
const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
|
|
184
259
|
if (attribute.attributeType === umlClass_1.AttributeType.Mapping ||
|
|
185
260
|
attribute.attributeType === umlClass_1.AttributeType.Function) {
|
|
186
|
-
return 32;
|
|
261
|
+
return { size: 32, dynamic: true };
|
|
187
262
|
}
|
|
188
263
|
if (attribute.attributeType === umlClass_1.AttributeType.Array) {
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
//
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
264
|
+
// Fixed sized arrays are read from right to left until there is a dynamic dimension
|
|
265
|
+
// eg address[][3][2] is a fixed size array that uses 6 slots.
|
|
266
|
+
// while address [2][] is a dynamic sized array.
|
|
267
|
+
const arrayDimensions = attribute.type.match(/\[\w*]/g);
|
|
268
|
+
// Remove first [ and last ] from each arrayDimensions
|
|
269
|
+
const dimensionsStr = arrayDimensions.map((a) => a.slice(1, -1));
|
|
270
|
+
// fixed-sized arrays are read from right to left so reverse the dimensions
|
|
271
|
+
const dimensionsStrReversed = dimensionsStr.reverse();
|
|
272
|
+
// read fixed-size dimensions until we get a dynamic array with no dimension
|
|
273
|
+
let dimension = dimensionsStrReversed.shift();
|
|
274
|
+
const fixedDimensions = [];
|
|
275
|
+
while (dimension && dimension !== '') {
|
|
276
|
+
const dimensionNum = (0, exports.findDimensionLength)(umlClass, dimension);
|
|
277
|
+
fixedDimensions.push(dimensionNum);
|
|
278
|
+
// read the next dimension for the next loop
|
|
279
|
+
dimension = dimensionsStrReversed.shift();
|
|
280
|
+
}
|
|
281
|
+
// If the first dimension is dynamic, ie []
|
|
282
|
+
if (fixedDimensions.length === 0) {
|
|
283
|
+
// dynamic arrays start at the keccak256 of the slot number
|
|
284
|
+
// the array length is stored in the 32 byte slot
|
|
285
|
+
return { size: 32, dynamic: true };
|
|
197
286
|
}
|
|
198
|
-
// All array dimensions are fixes so we now need to multiply all the dimensions
|
|
199
|
-
// to get a total number of array elements
|
|
200
|
-
const arrayDimensions = attribute.type.match(/\[\w+/g);
|
|
201
|
-
const dimensionsStr = arrayDimensions.map((d) => d.slice(1));
|
|
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
|
-
});
|
|
213
287
|
let elementSize;
|
|
288
|
+
const type = attribute.type.substring(0, attribute.type.indexOf('['));
|
|
214
289
|
// If a fixed sized array
|
|
215
|
-
if ((0, exports.isElementary)(
|
|
290
|
+
if ((0, exports.isElementary)(type)) {
|
|
216
291
|
const elementAttribute = {
|
|
217
292
|
attributeType: umlClass_1.AttributeType.Elementary,
|
|
218
|
-
type
|
|
293
|
+
type,
|
|
219
294
|
name: 'element',
|
|
220
295
|
};
|
|
221
|
-
elementSize = (0, exports.calcStorageByteSize)(elementAttribute, umlClass, otherClasses);
|
|
296
|
+
({ size: elementSize } = (0, exports.calcStorageByteSize)(elementAttribute, umlClass, otherClasses));
|
|
222
297
|
}
|
|
223
298
|
else {
|
|
224
299
|
const elementAttribute = {
|
|
225
300
|
attributeType: umlClass_1.AttributeType.UserDefined,
|
|
226
|
-
type
|
|
301
|
+
type,
|
|
227
302
|
name: 'userDefined',
|
|
228
303
|
};
|
|
229
|
-
elementSize = (0, exports.calcStorageByteSize)(elementAttribute, umlClass, otherClasses);
|
|
304
|
+
({ size: elementSize } = (0, exports.calcStorageByteSize)(elementAttribute, umlClass, otherClasses));
|
|
230
305
|
}
|
|
231
306
|
// Anything over 16 bytes, like an address, will take a whole 32 byte slot
|
|
232
307
|
if (elementSize > 16 && elementSize < 32) {
|
|
233
308
|
elementSize = 32;
|
|
234
309
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
310
|
+
// If multi dimension, then the first element is 32 bytes
|
|
311
|
+
if (fixedDimensions.length < arrayDimensions.length) {
|
|
312
|
+
const totalDimensions = fixedDimensions.reduce((total, dimension) => total * dimension, 1);
|
|
313
|
+
return {
|
|
314
|
+
size: 32 * totalDimensions,
|
|
315
|
+
dynamic: false,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
const lastItem = fixedDimensions.length - 1;
|
|
319
|
+
const lastDimensionBytes = elementSize * fixedDimensions[lastItem];
|
|
320
|
+
const lastDimensionSlotBytes = Math.ceil(lastDimensionBytes / 32) * 32;
|
|
321
|
+
const remainingDimensions = fixedDimensions
|
|
322
|
+
.slice(0, lastItem)
|
|
239
323
|
.reduce((total, dimension) => total * dimension, 1);
|
|
240
|
-
return
|
|
324
|
+
return {
|
|
325
|
+
size: lastDimensionSlotBytes * remainingDimensions,
|
|
326
|
+
dynamic: false,
|
|
327
|
+
};
|
|
241
328
|
}
|
|
242
329
|
// If a Struct or Enum
|
|
243
330
|
if (attribute.attributeType === umlClass_1.AttributeType.UserDefined) {
|
|
@@ -250,12 +337,12 @@ const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
|
|
|
250
337
|
}
|
|
251
338
|
switch (attributeClass.stereotype) {
|
|
252
339
|
case umlClass_1.ClassStereotype.Enum:
|
|
253
|
-
return 1;
|
|
340
|
+
return { size: 1, dynamic: false };
|
|
254
341
|
case umlClass_1.ClassStereotype.Contract:
|
|
255
342
|
case umlClass_1.ClassStereotype.Abstract:
|
|
256
343
|
case umlClass_1.ClassStereotype.Interface:
|
|
257
344
|
case umlClass_1.ClassStereotype.Library:
|
|
258
|
-
return 20;
|
|
345
|
+
return { size: 20, dynamic: false };
|
|
259
346
|
case umlClass_1.ClassStereotype.Struct:
|
|
260
347
|
let structByteSize = 0;
|
|
261
348
|
attributeClass.attributes.forEach((structAttribute) => {
|
|
@@ -280,7 +367,7 @@ const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
|
|
|
280
367
|
structByteSize = Math.ceil(structByteSize / 32) * 32;
|
|
281
368
|
}
|
|
282
369
|
}
|
|
283
|
-
const attributeSize = (0, exports.calcStorageByteSize)(structAttribute, umlClass, otherClasses);
|
|
370
|
+
const { size: attributeSize } = (0, exports.calcStorageByteSize)(structAttribute, umlClass, otherClasses);
|
|
284
371
|
// check if attribute will fit into the remaining slot
|
|
285
372
|
const endCurrentSlot = Math.ceil(structByteSize / 32) * 32;
|
|
286
373
|
const spaceLeftInSlot = endCurrentSlot - structByteSize;
|
|
@@ -292,24 +379,27 @@ const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
|
|
|
292
379
|
}
|
|
293
380
|
});
|
|
294
381
|
// structs take whole 32 byte slots so round up to the nearest 32 sized slots
|
|
295
|
-
return
|
|
382
|
+
return {
|
|
383
|
+
size: Math.ceil(structByteSize / 32) * 32,
|
|
384
|
+
dynamic: false,
|
|
385
|
+
};
|
|
296
386
|
default:
|
|
297
|
-
return 32;
|
|
387
|
+
return { size: 32, dynamic: false };
|
|
298
388
|
}
|
|
299
389
|
}
|
|
300
390
|
if (attribute.attributeType === umlClass_1.AttributeType.Elementary) {
|
|
301
391
|
switch (attribute.type) {
|
|
302
392
|
case 'bool':
|
|
303
|
-
return 1;
|
|
393
|
+
return { size: 1, dynamic: false };
|
|
304
394
|
case 'address':
|
|
305
|
-
return 20;
|
|
395
|
+
return { size: 20, dynamic: false };
|
|
306
396
|
case 'string':
|
|
307
397
|
case 'bytes':
|
|
308
398
|
case 'uint':
|
|
309
399
|
case 'int':
|
|
310
400
|
case 'ufixed':
|
|
311
401
|
case 'fixed':
|
|
312
|
-
return 32;
|
|
402
|
+
return { size: 32, dynamic: false };
|
|
313
403
|
default:
|
|
314
404
|
const result = attribute.type.match(/[u]*(int|fixed|bytes)([0-9]+)/);
|
|
315
405
|
if (result === null || !result[2]) {
|
|
@@ -317,12 +407,12 @@ const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
|
|
|
317
407
|
}
|
|
318
408
|
// If bytes
|
|
319
409
|
if (result[1] === 'bytes') {
|
|
320
|
-
return parseInt(result[2]);
|
|
410
|
+
return { size: parseInt(result[2]), dynamic: false };
|
|
321
411
|
}
|
|
322
412
|
// TODO need to handle fixed types when they are supported
|
|
323
413
|
// If an int
|
|
324
414
|
const bitSize = parseInt(result[2]);
|
|
325
|
-
return bitSize / 8;
|
|
415
|
+
return { size: bitSize / 8, dynamic: false };
|
|
326
416
|
}
|
|
327
417
|
}
|
|
328
418
|
throw new Error(`Failed to calc bytes size of attribute with name "${attribute.name}" and type ${attribute.type}`);
|
|
@@ -345,4 +435,44 @@ const isElementary = (type) => {
|
|
|
345
435
|
}
|
|
346
436
|
};
|
|
347
437
|
exports.isElementary = isElementary;
|
|
438
|
+
const calcSlotKey = (variable) => {
|
|
439
|
+
if (variable.dynamic) {
|
|
440
|
+
return (0, utils_1.keccak256)((0, utils_1.toUtf8Bytes)(ethers_1.BigNumber.from(variable.fromSlot).toHexString()));
|
|
441
|
+
}
|
|
442
|
+
return ethers_1.BigNumber.from(variable.fromSlot).toHexString();
|
|
443
|
+
};
|
|
444
|
+
exports.calcSlotKey = calcSlotKey;
|
|
445
|
+
// recursively offset the slots numbers of a storage item
|
|
446
|
+
const offsetStorageSlots = (storage, slots, storages) => {
|
|
447
|
+
storage.variables.forEach((variable) => {
|
|
448
|
+
variable.fromSlot += slots;
|
|
449
|
+
variable.toSlot += slots;
|
|
450
|
+
if (variable.referenceStorageId) {
|
|
451
|
+
// recursively offset the referenced storage
|
|
452
|
+
const referenceStorage = storages.find((s) => s.id === variable.referenceStorageId);
|
|
453
|
+
if (!referenceStorage.arrayDynamic) {
|
|
454
|
+
(0, exports.offsetStorageSlots)(referenceStorage, slots, storages);
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
referenceStorage.slotKey = (0, exports.calcSlotKey)(variable);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
};
|
|
462
|
+
exports.offsetStorageSlots = offsetStorageSlots;
|
|
463
|
+
const findDimensionLength = (umlClass, dimension) => {
|
|
464
|
+
const dimensionNum = parseInt(dimension);
|
|
465
|
+
if (Number.isInteger(dimensionNum)) {
|
|
466
|
+
return dimensionNum;
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
// Try and size array dimension from declared constants
|
|
470
|
+
const constant = umlClass.constants.find((constant) => constant.name === dimension);
|
|
471
|
+
if (!constant) {
|
|
472
|
+
throw Error(`Could not size fixed sized array with dimension "${dimension}"`);
|
|
473
|
+
}
|
|
474
|
+
return constant.value;
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
exports.findDimensionLength = findDimensionLength;
|
|
348
478
|
//# sourceMappingURL=converterClasses2Storage.js.map
|
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
import { Storage } from './converterClasses2Storage';
|
|
2
|
-
export declare const convertStorages2Dot: (storages: Storage[]
|
|
3
|
-
|
|
2
|
+
export declare const convertStorages2Dot: (storages: Storage[], options: {
|
|
3
|
+
data: boolean;
|
|
4
|
+
}) => string;
|
|
5
|
+
export declare function convertStorage2Dot(storage: Storage, dotString: string, options: {
|
|
6
|
+
data: boolean;
|
|
7
|
+
}): string;
|
|
@@ -3,22 +3,22 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.convertStorage2Dot = exports.convertStorages2Dot = void 0;
|
|
4
4
|
const converterClasses2Storage_1 = require("./converterClasses2Storage");
|
|
5
5
|
const debug = require('debug')('sol2uml');
|
|
6
|
-
const convertStorages2Dot = (storages) => {
|
|
6
|
+
const convertStorages2Dot = (storages, options) => {
|
|
7
7
|
let dotString = `
|
|
8
8
|
digraph StorageDiagram {
|
|
9
9
|
rankdir=LR
|
|
10
10
|
color=black
|
|
11
11
|
arrowhead=open
|
|
12
|
-
node [shape=record, style=filled, fillcolor=gray95]`;
|
|
12
|
+
node [shape=record, style=filled, fillcolor=gray95 fontname="Courier New"]`;
|
|
13
13
|
// process contract and the struct storages
|
|
14
14
|
storages.forEach((storage) => {
|
|
15
|
-
dotString = convertStorage2Dot(storage, dotString);
|
|
15
|
+
dotString = convertStorage2Dot(storage, dotString, options);
|
|
16
16
|
});
|
|
17
17
|
// link contract and structs to structs
|
|
18
18
|
storages.forEach((slot) => {
|
|
19
19
|
slot.variables.forEach((storage) => {
|
|
20
|
-
if (storage.
|
|
21
|
-
dotString += `\n ${slot.id}:${storage.id} -> ${storage.
|
|
20
|
+
if (storage.referenceStorageId) {
|
|
21
|
+
dotString += `\n ${slot.id}:${storage.id} -> ${storage.referenceStorageId}`;
|
|
22
22
|
}
|
|
23
23
|
});
|
|
24
24
|
});
|
|
@@ -28,10 +28,10 @@ node [shape=record, style=filled, fillcolor=gray95]`;
|
|
|
28
28
|
return dotString;
|
|
29
29
|
};
|
|
30
30
|
exports.convertStorages2Dot = convertStorages2Dot;
|
|
31
|
-
function convertStorage2Dot(storage, dotString) {
|
|
32
|
-
const steorotype = storage.type === converterClasses2Storage_1.StorageType.Struct ? 'Struct' : 'Contract';
|
|
31
|
+
function convertStorage2Dot(storage, dotString, options) {
|
|
33
32
|
// write storage header with name and optional address
|
|
34
|
-
dotString += `\n${storage.id} [label="${storage.name} \\<\\<${
|
|
33
|
+
dotString += `\n${storage.id} [label="${storage.name} \\<\\<${storage.type}\\>\\>\\n${storage.address || storage.slotKey || ''}`;
|
|
34
|
+
dotString += ' | {';
|
|
35
35
|
const startingVariables = storage.variables.filter((s) => s.byteOffset === 0);
|
|
36
36
|
// write slot numbers
|
|
37
37
|
dotString += '{ slot';
|
|
@@ -44,10 +44,10 @@ function convertStorage2Dot(storage, dotString) {
|
|
|
44
44
|
}
|
|
45
45
|
});
|
|
46
46
|
// write slot values if available
|
|
47
|
-
if (
|
|
47
|
+
if (options.data) {
|
|
48
48
|
dotString += '} | {value';
|
|
49
49
|
startingVariables.forEach((variable, i) => {
|
|
50
|
-
dotString += ` | ${variable.
|
|
50
|
+
dotString += ` | ${variable.value || ''}`;
|
|
51
51
|
});
|
|
52
52
|
}
|
|
53
53
|
const contractVariablePrefix = storage.type === converterClasses2Storage_1.StorageType.Contract ? '\\<inherited contract\\>.' : '';
|
|
@@ -58,6 +58,7 @@ function convertStorage2Dot(storage, dotString) {
|
|
|
58
58
|
const slotVariables = storage.variables.filter((s) => s.fromSlot === variable.fromSlot);
|
|
59
59
|
const usedBytes = slotVariables.reduce((acc, s) => acc + s.byteSize, 0);
|
|
60
60
|
if (usedBytes < 32) {
|
|
61
|
+
// Create an unallocated variable for display purposes
|
|
61
62
|
slotVariables.push({
|
|
62
63
|
id: 0,
|
|
63
64
|
fromSlot: variable.fromSlot,
|
|
@@ -65,9 +66,10 @@ function convertStorage2Dot(storage, dotString) {
|
|
|
65
66
|
byteSize: 32 - usedBytes,
|
|
66
67
|
byteOffset: usedBytes,
|
|
67
68
|
type: 'unallocated',
|
|
69
|
+
dynamic: false,
|
|
70
|
+
noValue: true,
|
|
68
71
|
contractName: variable.contractName,
|
|
69
72
|
variable: '',
|
|
70
|
-
values: [],
|
|
71
73
|
});
|
|
72
74
|
}
|
|
73
75
|
const slotVariablesReversed = slotVariables.reverse();
|
|
@@ -88,7 +90,7 @@ function convertStorage2Dot(storage, dotString) {
|
|
|
88
90
|
}
|
|
89
91
|
exports.convertStorage2Dot = convertStorage2Dot;
|
|
90
92
|
const dotVariable = (storage, contractName) => {
|
|
91
|
-
const port = storage.
|
|
93
|
+
const port = storage.referenceStorageId !== undefined ? `<${storage.id}>` : '';
|
|
92
94
|
const contractNamePrefix = storage.contractName !== contractName ? `${storage.contractName}.` : '';
|
|
93
95
|
const variable = storage.variable
|
|
94
96
|
? `: ${contractNamePrefix}${storage.variable}`
|
package/lib/parserEtherscan.js
CHANGED
|
@@ -71,7 +71,14 @@ class EtherscanParser {
|
|
|
71
71
|
const { files, contractName } = await this.getSourceCode(contractAddress);
|
|
72
72
|
let solidityCode = '';
|
|
73
73
|
files.forEach((file) => {
|
|
74
|
-
|
|
74
|
+
// comment out any import statements
|
|
75
|
+
// match whitespace before import
|
|
76
|
+
// and characters after import up to ;
|
|
77
|
+
// replace all in file and match across multiple lines
|
|
78
|
+
const removedImports = file.code.replace(/(\s)(import.*;)/gm, '$1/* $2 */');
|
|
79
|
+
// Rename SPDX-License-Identifier to SPDX--License-Identifier so the merged file will compile
|
|
80
|
+
const removedSPDX = removedImports.replace(/SPDX-/, 'SPDX--');
|
|
81
|
+
solidityCode += removedSPDX;
|
|
75
82
|
});
|
|
76
83
|
return {
|
|
77
84
|
solidityCode,
|
package/lib/slotValues.js
CHANGED
|
@@ -41,7 +41,7 @@ const getStorageValues = async (url, contractAddress, slots, blockTag = 'latest'
|
|
|
41
41
|
}
|
|
42
42
|
const responseData = response.data;
|
|
43
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);
|
|
44
|
+
return sortedResponses.map((data) => '0x' + data.result.toUpperCase().slice(2));
|
|
45
45
|
}
|
|
46
46
|
catch (err) {
|
|
47
47
|
throw new Error(`Failed to get ${slots.length} storage values for ${contractAddress} from ${url}`, { cause: err });
|
package/lib/sol2uml.js
CHANGED
|
@@ -39,8 +39,9 @@ The Solidity code can be pulled from verified source code on Blockchain explorer
|
|
|
39
39
|
'goerli',
|
|
40
40
|
'sepolia',
|
|
41
41
|
])
|
|
42
|
-
.default('mainnet')
|
|
43
|
-
.
|
|
42
|
+
.default('mainnet')
|
|
43
|
+
.env('ETH_NETWORK'))
|
|
44
|
+
.addOption(new commander_1.Option('-k, --apiKey <key>', 'Etherscan, Polygonscan, BscScan or Arbiscan API key').env('SCAN_API_KEY'))
|
|
44
45
|
.option('-v, --verbose', 'run with debugging statements', false);
|
|
45
46
|
program
|
|
46
47
|
.command('class', { isDefault: true })
|
|
@@ -75,18 +76,19 @@ If an Ethereum address with a 0x prefix is passed, the verified source code from
|
|
|
75
76
|
...command.parent._optionValues,
|
|
76
77
|
...options,
|
|
77
78
|
};
|
|
78
|
-
|
|
79
|
+
let { umlClasses, contractName } = await (0, parserGeneral_1.parserUmlClasses)(fileFolderAddress, combinedOptions);
|
|
79
80
|
let filteredUmlClasses = umlClasses;
|
|
80
81
|
if (options.baseContractNames) {
|
|
81
82
|
const baseContractNames = options.baseContractNames.split(',');
|
|
82
83
|
filteredUmlClasses = (0, filterClasses_1.classesConnectedToBaseContracts)(umlClasses, baseContractNames, options.depth);
|
|
84
|
+
contractName = baseContractNames[0];
|
|
83
85
|
}
|
|
84
86
|
const dotString = (0, converterClasses2Dot_1.convertUmlClasses2Dot)(filteredUmlClasses, combinedOptions.clusterFolders, combinedOptions);
|
|
85
|
-
await (0, writerFiles_1.writeOutputFiles)(dotString, fileFolderAddress, contractName, combinedOptions.outputFormat, combinedOptions.outputFileName);
|
|
87
|
+
await (0, writerFiles_1.writeOutputFiles)(dotString, fileFolderAddress, contractName || 'classDiagram', combinedOptions.outputFormat, combinedOptions.outputFileName);
|
|
86
88
|
debug(`Finished generating UML`);
|
|
87
89
|
}
|
|
88
90
|
catch (err) {
|
|
89
|
-
console.error(`Failed to generate UML diagram
|
|
91
|
+
console.error(`Failed to generate UML diagram\n${err.stack}`);
|
|
90
92
|
}
|
|
91
93
|
});
|
|
92
94
|
program
|
|
@@ -132,11 +134,11 @@ program
|
|
|
132
134
|
throw Error(`Could not find the "${contractName}" contract in list of parsed storages`);
|
|
133
135
|
await (0, converterClasses2Storage_1.addStorageValues)(combinedOptions.url, storageAddress, storage, combinedOptions.blockNumber);
|
|
134
136
|
}
|
|
135
|
-
const dotString = (0, converterStorage2Dot_1.convertStorages2Dot)(storages);
|
|
136
|
-
await (0, writerFiles_1.writeOutputFiles)(dotString, fileFolderAddress, contractName, combinedOptions.outputFormat, combinedOptions.outputFileName);
|
|
137
|
+
const dotString = (0, converterStorage2Dot_1.convertStorages2Dot)(storages, combinedOptions);
|
|
138
|
+
await (0, writerFiles_1.writeOutputFiles)(dotString, fileFolderAddress, contractName || 'storageDiagram', combinedOptions.outputFormat, combinedOptions.outputFileName);
|
|
137
139
|
}
|
|
138
140
|
catch (err) {
|
|
139
|
-
console.error(`Failed to generate storage diagram
|
|
141
|
+
console.error(`Failed to generate storage diagram.\n${err.stack}`);
|
|
140
142
|
}
|
|
141
143
|
});
|
|
142
144
|
program
|
|
@@ -144,16 +146,21 @@ program
|
|
|
144
146
|
.description('get all verified source code for a contract from the Blockchain explorer into one local file')
|
|
145
147
|
.argument('<contractAddress>', 'Contract address')
|
|
146
148
|
.action(async (contractAddress, options, command) => {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
149
|
+
try {
|
|
150
|
+
debug(`About to flatten ${contractAddress}`);
|
|
151
|
+
const combinedOptions = {
|
|
152
|
+
...command.parent._optionValues,
|
|
153
|
+
...options,
|
|
154
|
+
};
|
|
155
|
+
const etherscanParser = new parserEtherscan_1.EtherscanParser(combinedOptions.apiKey, combinedOptions.network);
|
|
156
|
+
const { solidityCode, contractName } = await etherscanParser.getSolidityCode(contractAddress);
|
|
157
|
+
// Write Solidity to the contract address
|
|
158
|
+
const outputFilename = combinedOptions.outputFileName || contractName;
|
|
159
|
+
await (0, writerFiles_1.writeSolidity)(solidityCode, outputFilename);
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
console.error(`Failed to flatten files.\n${err.stack}`);
|
|
163
|
+
}
|
|
157
164
|
});
|
|
158
165
|
program.on('option:verbose', () => {
|
|
159
166
|
debugControl.enable('sol2uml');
|
package/lib/umlClass.d.ts
CHANGED