sol2uml 2.5.23 → 2.5.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/SlotValueCache.d.ts +1 -1
- package/lib/SlotValueCache.js +3 -3
- package/lib/associations.d.ts +1 -1
- package/lib/associations.js +96 -94
- package/lib/converterClasses2Storage.d.ts +1 -1
- package/lib/converterClasses2Storage.js +4 -5
- package/lib/slotValues.d.ts +1 -1
- package/lib/slotValues.js +42 -40
- package/lib/utils/block.js +1 -1
- package/lib/utils/validators.js +3 -3
- package/lib/writerFiles.js +5 -2
- package/package.json +10 -9
package/lib/SlotValueCache.d.ts
CHANGED
package/lib/SlotValueCache.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SlotValueCache = void 0;
|
|
4
|
-
const
|
|
4
|
+
const ethers_1 = require("ethers");
|
|
5
5
|
const debug = require('debug')('sol2uml');
|
|
6
6
|
/**
|
|
7
7
|
* Singleton that caches a mapping of slot keys to values.
|
|
@@ -17,7 +17,7 @@ class SlotValueCache {
|
|
|
17
17
|
const cachedValues = [];
|
|
18
18
|
const missingKeys = [];
|
|
19
19
|
slotKeys.forEach((slotKey) => {
|
|
20
|
-
const key =
|
|
20
|
+
const key = (0, ethers_1.toBeHex)(BigInt(slotKey));
|
|
21
21
|
if (this.slotCache[key]) {
|
|
22
22
|
cachedValues.push(this.slotCache[key]);
|
|
23
23
|
}
|
|
@@ -46,7 +46,7 @@ class SlotValueCache {
|
|
|
46
46
|
}
|
|
47
47
|
});
|
|
48
48
|
return slotKeys.map((slotKey) => {
|
|
49
|
-
const key =
|
|
49
|
+
const key = (0, ethers_1.toBeHex)(BigInt(slotKey));
|
|
50
50
|
// it should find the slot value in the cache. if not it'll return undefined
|
|
51
51
|
return this.slotCache[key];
|
|
52
52
|
});
|
package/lib/associations.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { Association, UmlClass } from './umlClass';
|
|
2
|
-
export declare const findAssociatedClass: (association: Association, sourceUmlClass: UmlClass, umlClasses: readonly UmlClass[]
|
|
2
|
+
export declare const findAssociatedClass: (association: Association, sourceUmlClass: UmlClass, umlClasses: readonly UmlClass[]) => UmlClass | undefined;
|
package/lib/associations.js
CHANGED
|
@@ -2,34 +2,18 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.findAssociatedClass = void 0;
|
|
4
4
|
// Find the UML class linked to the association
|
|
5
|
-
const findAssociatedClass = (association, sourceUmlClass, umlClasses
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
// // add to already recursively processed files to avoid getting stuck in circular imports
|
|
18
|
-
// searchedAbsolutePaths.push(sourceUmlClass.absolutePath)
|
|
19
|
-
// const importedType = findChainedImport(
|
|
20
|
-
// association,
|
|
21
|
-
// sourceUmlClass,
|
|
22
|
-
// umlClasses,
|
|
23
|
-
// searchedAbsolutePaths,
|
|
24
|
-
// )
|
|
25
|
-
// if (importedType) return importedType
|
|
26
|
-
// // Still could not find association so now need to recursively look for inherited types
|
|
27
|
-
// const inheritedType = findInheritedType(
|
|
28
|
-
// association,
|
|
29
|
-
// sourceUmlClass,
|
|
30
|
-
// umlClasses,
|
|
31
|
-
// )
|
|
32
|
-
// if (inheritedType) return inheritedType
|
|
5
|
+
const findAssociatedClass = (association, sourceUmlClass, umlClasses) => {
|
|
6
|
+
// Phase 1: Iterative BFS through import chain, trying direct match at each level
|
|
7
|
+
const { result, visitedSources } = _findViaImportChain(association, sourceUmlClass, umlClasses);
|
|
8
|
+
if (result)
|
|
9
|
+
return result;
|
|
10
|
+
// Phase 2: Try inherited types for each source visited during import chain traversal
|
|
11
|
+
const visitedClassIds = new Set();
|
|
12
|
+
for (const source of visitedSources) {
|
|
13
|
+
const inherited = _findInheritedType(association, source, umlClasses, visitedClassIds);
|
|
14
|
+
if (inherited)
|
|
15
|
+
return inherited;
|
|
16
|
+
}
|
|
33
17
|
return undefined;
|
|
34
18
|
};
|
|
35
19
|
exports.findAssociatedClass = findAssociatedClass;
|
|
@@ -83,15 +67,91 @@ const isAssociated = (association, sourceUmlClass, targetUmlClass, targetParentU
|
|
|
83
67
|
importedClass.alias &&
|
|
84
68
|
importedClass.className === targetUmlClass.name))));
|
|
85
69
|
};
|
|
86
|
-
|
|
87
|
-
|
|
70
|
+
// Try to find a direct match for the association from the given source class
|
|
71
|
+
const _tryDirectMatch = (association, sourceUmlClass, umlClasses) => {
|
|
72
|
+
return umlClasses.find((targetUmlClass) => {
|
|
73
|
+
const targetParentClass = association.parentUmlClassName &&
|
|
74
|
+
targetUmlClass.parentId !== undefined
|
|
75
|
+
? umlClasses[targetUmlClass.parentId]
|
|
76
|
+
: undefined;
|
|
77
|
+
return isAssociated(association, sourceUmlClass, targetUmlClass, targetParentClass);
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
// Iterative BFS through import chain, trying direct match at each level.
|
|
81
|
+
// Returns the matched class (if found) and the list of source classes visited.
|
|
82
|
+
const _findViaImportChain = (association, sourceUmlClass, umlClasses) => {
|
|
83
|
+
const searched = new Set();
|
|
84
|
+
const visitedSources = [];
|
|
85
|
+
const visitedPaths = new Set();
|
|
86
|
+
const queue = [
|
|
87
|
+
{
|
|
88
|
+
source: sourceUmlClass,
|
|
89
|
+
targetName: association.targetUmlClassName,
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
while (queue.length > 0) {
|
|
93
|
+
const { source, targetName } = queue.shift();
|
|
94
|
+
const key = `${source.absolutePath}::${targetName}`;
|
|
95
|
+
if (searched.has(key))
|
|
96
|
+
continue;
|
|
97
|
+
searched.add(key);
|
|
98
|
+
// Track unique visited sources for phase 2 inherited type lookup
|
|
99
|
+
if (!visitedPaths.has(source.absolutePath)) {
|
|
100
|
+
visitedPaths.add(source.absolutePath);
|
|
101
|
+
visitedSources.push(source);
|
|
102
|
+
}
|
|
103
|
+
// Build association with potentially de-aliased target name
|
|
104
|
+
const currentAssoc = {
|
|
105
|
+
...association,
|
|
106
|
+
targetUmlClassName: targetName,
|
|
107
|
+
};
|
|
108
|
+
// Try direct match from this source
|
|
109
|
+
const match = _tryDirectMatch(currentAssoc, source, umlClasses);
|
|
110
|
+
if (match)
|
|
111
|
+
return { result: match, visitedSources };
|
|
112
|
+
// Get imports that could lead to the target
|
|
113
|
+
const imports = source.imports.filter((i) => i.classNames.length === 0 ||
|
|
114
|
+
i.classNames.some((cn) => (targetName === cn.className && !cn.alias) ||
|
|
115
|
+
targetName === cn.alias));
|
|
116
|
+
for (const importDetail of imports) {
|
|
117
|
+
const importedClass = umlClasses.find((c) => c.absolutePath === importDetail.absolutePath);
|
|
118
|
+
if (!importedClass)
|
|
119
|
+
continue;
|
|
120
|
+
// Queue with current target name to continue the chain
|
|
121
|
+
const origKey = `${importedClass.absolutePath}::${targetName}`;
|
|
122
|
+
if (!searched.has(origKey)) {
|
|
123
|
+
queue.push({ source: importedClass, targetName });
|
|
124
|
+
}
|
|
125
|
+
// Queue with de-aliased names for aliased imports
|
|
126
|
+
for (const cn of importDetail.classNames) {
|
|
127
|
+
if (cn.alias && targetName === cn.alias) {
|
|
128
|
+
const deAliasedKey = `${importedClass.absolutePath}::${cn.className}`;
|
|
129
|
+
if (!searched.has(deAliasedKey)) {
|
|
130
|
+
queue.push({
|
|
131
|
+
source: importedClass,
|
|
132
|
+
targetName: cn.className,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return { visitedSources };
|
|
140
|
+
};
|
|
141
|
+
// Walk the inheritance chain to find types (structs, enums) defined on parent contracts.
|
|
142
|
+
// Uses visitedClassIds to prevent re-processing in diamond inheritance hierarchies.
|
|
143
|
+
const _findInheritedType = (association, sourceUmlClass, umlClasses, visitedClassIds) => {
|
|
144
|
+
if (visitedClassIds.has(sourceUmlClass.id))
|
|
145
|
+
return undefined;
|
|
146
|
+
visitedClassIds.add(sourceUmlClass.id);
|
|
88
147
|
const parentAssociations = sourceUmlClass.getParentContracts();
|
|
89
|
-
// For each parent association
|
|
90
148
|
for (const parentAssociation of parentAssociations) {
|
|
91
|
-
|
|
149
|
+
// Resolve the parent class using import chain only (no inherited types)
|
|
150
|
+
// to avoid mutual recursion between findAssociatedClass and _findInheritedType
|
|
151
|
+
const { result: parent } = _findViaImportChain(parentAssociation, sourceUmlClass, umlClasses);
|
|
92
152
|
if (!parent)
|
|
93
153
|
continue;
|
|
94
|
-
//
|
|
154
|
+
// Check parent's structs for the target type
|
|
95
155
|
for (const structId of parent.structs) {
|
|
96
156
|
const structUmlClass = umlClasses.find((c) => c.id === structId);
|
|
97
157
|
if (!structUmlClass)
|
|
@@ -100,7 +160,7 @@ const _findInheritedType = (association, sourceUmlClass, umlClasses) => {
|
|
|
100
160
|
return structUmlClass;
|
|
101
161
|
}
|
|
102
162
|
}
|
|
103
|
-
//
|
|
163
|
+
// Check parent's enums for the target type
|
|
104
164
|
for (const enumId of parent.enums) {
|
|
105
165
|
const enumUmlClass = umlClasses.find((c) => c.id === enumId);
|
|
106
166
|
if (!enumUmlClass)
|
|
@@ -109,69 +169,11 @@ const _findInheritedType = (association, sourceUmlClass, umlClasses) => {
|
|
|
109
169
|
return enumUmlClass;
|
|
110
170
|
}
|
|
111
171
|
}
|
|
112
|
-
// Recursively
|
|
113
|
-
const targetClass = _findInheritedType(association, parent, umlClasses);
|
|
172
|
+
// Recursively check parent's parents
|
|
173
|
+
const targetClass = _findInheritedType(association, parent, umlClasses, visitedClassIds);
|
|
114
174
|
if (targetClass)
|
|
115
175
|
return targetClass;
|
|
116
176
|
}
|
|
117
177
|
return undefined;
|
|
118
178
|
};
|
|
119
|
-
// const findChainedImport = (
|
|
120
|
-
// association: Association,
|
|
121
|
-
// sourceUmlClass: UmlClass,
|
|
122
|
-
// umlClasses: readonly UmlClass[],
|
|
123
|
-
// searchedRelativePaths: string[],
|
|
124
|
-
// ): UmlClass | undefined => {
|
|
125
|
-
// // Get all valid imports. That is, imports that do not explicitly import contracts or interfaces
|
|
126
|
-
// // or explicitly import the source class
|
|
127
|
-
// const imports = sourceUmlClass.imports.filter(
|
|
128
|
-
// (i) =>
|
|
129
|
-
// i.classNames.length === 0 ||
|
|
130
|
-
// i.classNames.some(
|
|
131
|
-
// (cn) =>
|
|
132
|
-
// (association.targetUmlClassName === cn.className &&
|
|
133
|
-
// !cn.alias) ||
|
|
134
|
-
// association.targetUmlClassName === cn.alias,
|
|
135
|
-
// ),
|
|
136
|
-
// )
|
|
137
|
-
// // For each import
|
|
138
|
-
// for (const importDetail of imports) {
|
|
139
|
-
// // Find a class with the same absolute path as the import so we can get the new imports
|
|
140
|
-
// const newSourceUmlClass = umlClasses.find(
|
|
141
|
-
// (c) => c.absolutePath === importDetail.absolutePath,
|
|
142
|
-
// )
|
|
143
|
-
// if (!newSourceUmlClass) {
|
|
144
|
-
// // Could not find a class in the import file so just move onto the next loop
|
|
145
|
-
// continue
|
|
146
|
-
// }
|
|
147
|
-
// // Avoid circular imports
|
|
148
|
-
// if (searchedRelativePaths.includes(newSourceUmlClass.absolutePath)) {
|
|
149
|
-
// // Have already recursively looked for imports of imports in this file
|
|
150
|
-
// continue
|
|
151
|
-
// }
|
|
152
|
-
//
|
|
153
|
-
// // find class linked to the association without aliased imports
|
|
154
|
-
// const umlClass = findAssociatedClass(
|
|
155
|
-
// association,
|
|
156
|
-
// newSourceUmlClass,
|
|
157
|
-
// umlClasses,
|
|
158
|
-
// searchedRelativePaths,
|
|
159
|
-
// )
|
|
160
|
-
// if (umlClass) return umlClass
|
|
161
|
-
//
|
|
162
|
-
// // find all aliased imports
|
|
163
|
-
// const aliasedImports = importDetail.classNames.filter((cn) => cn.alias)
|
|
164
|
-
// // For each aliased import
|
|
165
|
-
// for (const aliasedImport of aliasedImports) {
|
|
166
|
-
// const umlClass = findAssociatedClass(
|
|
167
|
-
// { ...association, targetUmlClassName: aliasedImport.className },
|
|
168
|
-
// newSourceUmlClass,
|
|
169
|
-
// umlClasses,
|
|
170
|
-
// searchedRelativePaths,
|
|
171
|
-
// )
|
|
172
|
-
// if (umlClass) return umlClass
|
|
173
|
-
// }
|
|
174
|
-
// }
|
|
175
|
-
// return undefined
|
|
176
|
-
// }
|
|
177
179
|
//# sourceMappingURL=associations.js.map
|
|
@@ -6,7 +6,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.addDynamicVariables = exports.findDimensionLength = exports.calcSectionOffset = exports.isElementary = exports.calcStorageByteSize = exports.parseStorageSectionFromAttribute = exports.optionStorageVariables = exports.convertClasses2StorageSections = exports.StorageSectionType = void 0;
|
|
7
7
|
const umlClass_1 = require("./umlClass");
|
|
8
8
|
const associations_1 = require("./associations");
|
|
9
|
-
const utils_1 = require("ethers/lib/utils");
|
|
10
9
|
const ethers_1 = require("ethers");
|
|
11
10
|
const path_1 = __importDefault(require("path"));
|
|
12
11
|
const slotValues_1 = require("./slotValues");
|
|
@@ -630,10 +629,10 @@ const isElementary = (type) => {
|
|
|
630
629
|
exports.isElementary = isElementary;
|
|
631
630
|
const calcSectionOffset = (variable, sectionOffset = '0') => {
|
|
632
631
|
if (variable.dynamic) {
|
|
633
|
-
const hexStringOf32Bytes = (0,
|
|
634
|
-
return (0,
|
|
632
|
+
const hexStringOf32Bytes = (0, ethers_1.zeroPadValue)((0, ethers_1.toBeHex)(BigInt(variable.fromSlot) + BigInt(sectionOffset)), 32);
|
|
633
|
+
return (0, ethers_1.keccak256)(hexStringOf32Bytes);
|
|
635
634
|
}
|
|
636
|
-
return ethers_1.
|
|
635
|
+
return (0, ethers_1.toBeHex)(BigInt(variable.fromSlot) + BigInt(sectionOffset));
|
|
637
636
|
};
|
|
638
637
|
exports.calcSectionOffset = calcSectionOffset;
|
|
639
638
|
const findDimensionLength = (umlClass, dimension, otherClasses) => {
|
|
@@ -791,7 +790,7 @@ const addDynamicVariables = async (storageSection, storageSections, url, contrac
|
|
|
791
790
|
continue;
|
|
792
791
|
}
|
|
793
792
|
// Add missing dynamic array variables
|
|
794
|
-
const arrayLength =
|
|
793
|
+
const arrayLength = Number(BigInt(variable.slotValue));
|
|
795
794
|
if (arrayLength > 1) {
|
|
796
795
|
// Add missing array variables to the referenced dynamic array
|
|
797
796
|
addArrayVariables(arrayLength, arrayItems, referenceStorageSection.variables);
|
package/lib/slotValues.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { BigNumberish } from '@ethersproject/bignumber';
|
|
2
1
|
import { StorageSection, Variable } from './converterClasses2Storage';
|
|
2
|
+
import { BigNumberish } from 'ethers';
|
|
3
3
|
/**
|
|
4
4
|
* Adds the slot values to the variables in the storage section.
|
|
5
5
|
* This can be rerun for a section as it will only get if the slot value
|
package/lib/slotValues.js
CHANGED
|
@@ -4,13 +4,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.escapeString = exports.convert2String = exports.dynamicSlotSize = exports.getSlotValue = exports.getSlotValues = exports.parseValue = exports.addSlotValues = void 0;
|
|
7
|
-
const bignumber_1 = require("@ethersproject/bignumber");
|
|
8
7
|
const axios_1 = __importDefault(require("axios"));
|
|
9
8
|
const umlClass_1 = require("./umlClass");
|
|
10
|
-
const utils_1 = require("ethers/lib/utils");
|
|
11
|
-
const SlotValueCache_1 = require("./SlotValueCache");
|
|
12
9
|
const ethers_1 = require("ethers");
|
|
10
|
+
const SlotValueCache_1 = require("./SlotValueCache");
|
|
13
11
|
const debug = require('debug')('sol2uml');
|
|
12
|
+
const commify = (value) => {
|
|
13
|
+
const match = value.match(/^(-?)(\d+)(\.(.*))?$/);
|
|
14
|
+
if (!match)
|
|
15
|
+
return value;
|
|
16
|
+
const neg = match[1];
|
|
17
|
+
const whole = match[2];
|
|
18
|
+
const frac = match[4] ? '.' + match[4] : '';
|
|
19
|
+
return neg + whole.replace(/\B(?=(\d{3})+(?!\d))/g, ',') + frac;
|
|
20
|
+
};
|
|
14
21
|
/**
|
|
15
22
|
* Adds the slot values to the variables in the storage section.
|
|
16
23
|
* This can be rerun for a section as it will only get if the slot value
|
|
@@ -30,7 +37,7 @@ const addSlotValues = async (url, contractAddress, storageSection, arrayItems, b
|
|
|
30
37
|
const slots = [];
|
|
31
38
|
valueVariables.forEach((variable) => {
|
|
32
39
|
if (variable.offset) {
|
|
33
|
-
slots.push(
|
|
40
|
+
slots.push(BigInt(variable.offset));
|
|
34
41
|
}
|
|
35
42
|
else {
|
|
36
43
|
for (let i = 0; variable.fromSlot + i <= variable.toSlot; i++) {
|
|
@@ -45,12 +52,12 @@ const addSlotValues = async (url, contractAddress, storageSection, arrayItems, b
|
|
|
45
52
|
});
|
|
46
53
|
// remove duplicate slot numbers
|
|
47
54
|
const uniqueFromSlots = [...new Set(slots)];
|
|
48
|
-
// Convert slot numbers to
|
|
55
|
+
// Convert slot numbers to BigInts and offset dynamic arrays
|
|
49
56
|
const slotKeys = uniqueFromSlots.map((fromSlot) => {
|
|
50
57
|
if (storageSection.offset) {
|
|
51
|
-
return
|
|
58
|
+
return BigInt(storageSection.offset) + BigInt(fromSlot);
|
|
52
59
|
}
|
|
53
|
-
return
|
|
60
|
+
return BigInt(fromSlot);
|
|
54
61
|
});
|
|
55
62
|
// Get the contract slot values from the node provider
|
|
56
63
|
const values = await (0, exports.getSlotValues)(url, contractAddress, slotKeys, blockTag);
|
|
@@ -62,7 +69,7 @@ const addSlotValues = async (url, contractAddress, storageSection, arrayItems, b
|
|
|
62
69
|
for (const variable of storageSection.variables) {
|
|
63
70
|
if (variable.getValue &&
|
|
64
71
|
variable.offset &&
|
|
65
|
-
|
|
72
|
+
BigInt(variable.offset) === BigInt(fromSlot)) {
|
|
66
73
|
debug(`Set slot value ${value} for section "${storageSection.name}", var type ${variable.type}, slot ${variable.offset}`);
|
|
67
74
|
variable.slotValue = value;
|
|
68
75
|
// parse variable value from slot data
|
|
@@ -80,7 +87,7 @@ const addSlotValues = async (url, contractAddress, storageSection, arrayItems, b
|
|
|
80
87
|
}
|
|
81
88
|
// if variable is past the slot that has the value
|
|
82
89
|
else if (variable.toSlot &&
|
|
83
|
-
|
|
90
|
+
BigInt(variable.toSlot) > BigInt(fromSlot)) {
|
|
84
91
|
break;
|
|
85
92
|
}
|
|
86
93
|
}
|
|
@@ -103,7 +110,7 @@ const parseValue = (variable) => {
|
|
|
103
110
|
// dynamic arrays
|
|
104
111
|
if (variable.attributeType === umlClass_1.AttributeType.Array &&
|
|
105
112
|
variable.dynamic) {
|
|
106
|
-
return (0,
|
|
113
|
+
return (0, ethers_1.formatUnits)('0x' + variableValue, 0);
|
|
107
114
|
}
|
|
108
115
|
return undefined;
|
|
109
116
|
}
|
|
@@ -118,12 +125,12 @@ const parseUserDefinedValue = (variable, variableValue) => {
|
|
|
118
125
|
// https://blog.soliditylang.org/2021/09/27/user-defined-value-types/
|
|
119
126
|
// using byteSize is crude and will be incorrect for aliases types like int160 or uint160
|
|
120
127
|
if (variable.byteSize === 20) {
|
|
121
|
-
return (0,
|
|
128
|
+
return (0, ethers_1.getAddress)('0x' + variableValue);
|
|
122
129
|
}
|
|
123
130
|
// this will also be wrong if the alias is to a 1 byte type. eg bytes1, int8 or uint8
|
|
124
131
|
if (variable.byteSize === 1) {
|
|
125
132
|
// assume 1 byte is an enum so convert value to enum index number
|
|
126
|
-
const index =
|
|
133
|
+
const index = Number(BigInt('0x' + variableValue));
|
|
127
134
|
// lookup enum value if its available
|
|
128
135
|
return variable?.enumValues ? variable?.enumValues[index] : undefined;
|
|
129
136
|
}
|
|
@@ -142,17 +149,14 @@ const parseElementaryValue = (variable, variableValue) => {
|
|
|
142
149
|
if (variable.type === 'string' || variable.type === 'bytes') {
|
|
143
150
|
if (variable.dynamic) {
|
|
144
151
|
const lastByte = variable.slotValue.slice(-2);
|
|
145
|
-
const size =
|
|
152
|
+
const size = BigInt('0x' + lastByte);
|
|
146
153
|
// Check if the last bit is set by AND the size with 0x01
|
|
147
|
-
if (size
|
|
154
|
+
if ((size & 1n) === 1n) {
|
|
148
155
|
// Return the number of chars or bytes
|
|
149
|
-
return
|
|
150
|
-
.sub(1)
|
|
151
|
-
.div(2)
|
|
152
|
-
.toString();
|
|
156
|
+
return ((BigInt(variable.slotValue) - 1n) / 2n).toString();
|
|
153
157
|
}
|
|
154
158
|
// The last byte holds the length of the string or bytes in the slot
|
|
155
|
-
const valueHex = '0x' + variableValue.slice(0, size
|
|
159
|
+
const valueHex = '0x' + variableValue.slice(0, Number(size));
|
|
156
160
|
if (variable.type === 'bytes')
|
|
157
161
|
return valueHex;
|
|
158
162
|
return `\\"${(0, exports.convert2String)(valueHex)}\\"`;
|
|
@@ -162,34 +166,34 @@ const parseElementaryValue = (variable, variableValue) => {
|
|
|
162
166
|
return `\\"${(0, exports.convert2String)('0x' + variableValue)}\\"`;
|
|
163
167
|
}
|
|
164
168
|
if (variable.type === 'address') {
|
|
165
|
-
return (0,
|
|
169
|
+
return (0, ethers_1.getAddress)('0x' + variableValue);
|
|
166
170
|
}
|
|
167
171
|
if (variable.type.match(/^uint([0-9]*)$/)) {
|
|
168
|
-
const parsedValue = (0,
|
|
169
|
-
return
|
|
172
|
+
const parsedValue = (0, ethers_1.formatUnits)('0x' + variableValue, 0);
|
|
173
|
+
return commify(parsedValue);
|
|
170
174
|
}
|
|
171
175
|
if (variable.type.match(/^bytes([0-9]+)$/)) {
|
|
172
176
|
return '0x' + variableValue;
|
|
173
177
|
}
|
|
174
178
|
if (variable.type.match(/^int([0-9]*)/)) {
|
|
175
179
|
// parse variable value as an unsigned number
|
|
176
|
-
let rawValue =
|
|
180
|
+
let rawValue = BigInt('0x' + variableValue);
|
|
177
181
|
// parse the number of bits
|
|
178
182
|
const result = variable.type.match(/^int([0-9]*$)/);
|
|
179
|
-
const bitSize = result[1] ? result[1] : 256;
|
|
183
|
+
const bitSize = result[1] ? Number(result[1]) : 256;
|
|
180
184
|
// Convert the number of bits to the number of hex characters
|
|
181
|
-
const hexSize =
|
|
185
|
+
const hexSize = bitSize / 4;
|
|
182
186
|
// bit mask has a leading 1 and the rest 0. 0x8 = 1000 binary
|
|
183
|
-
const mask = '0x80' + '0'.repeat(hexSize - 2);
|
|
187
|
+
const mask = BigInt('0x80' + '0'.repeat(hexSize - 2));
|
|
184
188
|
// is the first bit a 1?
|
|
185
|
-
const negative = rawValue
|
|
186
|
-
if (negative
|
|
189
|
+
const negative = rawValue & mask;
|
|
190
|
+
if (negative > 0n) {
|
|
187
191
|
// Convert unsigned number to a signed negative
|
|
188
|
-
const negativeOne = '0xFF' + 'F'.repeat(hexSize - 2);
|
|
189
|
-
rawValue =
|
|
192
|
+
const negativeOne = BigInt('0xFF' + 'F'.repeat(hexSize - 2));
|
|
193
|
+
rawValue = (negativeOne - rawValue + 1n) * -1n;
|
|
190
194
|
}
|
|
191
|
-
const parsedValue = (0,
|
|
192
|
-
return
|
|
195
|
+
const parsedValue = (0, ethers_1.formatUnits)(rawValue, 0);
|
|
196
|
+
return commify(parsedValue);
|
|
193
197
|
}
|
|
194
198
|
// add fixed point numbers when they are supported by Solidity
|
|
195
199
|
return undefined;
|
|
@@ -209,9 +213,7 @@ const getSlotValues = async (url, contractAddress, slotKeys, blockTag = 'latest'
|
|
|
209
213
|
if (slotKeys.length === 0) {
|
|
210
214
|
return [];
|
|
211
215
|
}
|
|
212
|
-
const block = blockTag === 'latest'
|
|
213
|
-
? blockTag
|
|
214
|
-
: (0, utils_1.hexValue)(bignumber_1.BigNumber.from(blockTag));
|
|
216
|
+
const block = blockTag === 'latest' ? blockTag : (0, ethers_1.toQuantity)(BigInt(blockTag));
|
|
215
217
|
// get cached values and missing slot keys from the cache
|
|
216
218
|
const { cachedValues, missingKeys } = SlotValueCache_1.SlotValueCache.readSlotValues(slotKeys);
|
|
217
219
|
// If all values are in the cache then just return the cached values
|
|
@@ -219,7 +221,7 @@ const getSlotValues = async (url, contractAddress, slotKeys, blockTag = 'latest'
|
|
|
219
221
|
return cachedValues;
|
|
220
222
|
}
|
|
221
223
|
// Check we are pointing to the correct chain by checking the contract has code
|
|
222
|
-
const provider = new ethers_1.
|
|
224
|
+
const provider = new ethers_1.JsonRpcProvider(url);
|
|
223
225
|
const code = await provider.getCode(contractAddress, block);
|
|
224
226
|
if (!code || code === '0x') {
|
|
225
227
|
const msg = `Address ${contractAddress} has no code. Check your "-u, --url" option or "NODE_URL" environment variable is pointing to the correct node.\nurl: ${url}`;
|
|
@@ -242,7 +244,7 @@ const getSlotValues = async (url, contractAddress, slotKeys, blockTag = 'latest'
|
|
|
242
244
|
throw Error(`Requested ${missingKeys.length} storage slot values but only got ${response.data.length}`);
|
|
243
245
|
}
|
|
244
246
|
const responseData = response.data;
|
|
245
|
-
const sortedResponses = responseData.sort((a, b) =>
|
|
247
|
+
const sortedResponses = responseData.sort((a, b) => Number(a.id) - Number(b.id));
|
|
246
248
|
const missingValues = sortedResponses.map((data) => {
|
|
247
249
|
if (data.error) {
|
|
248
250
|
throw Error(`json rpc call with id ${data.id} failed to get storage values: ${data.error?.message}`);
|
|
@@ -284,11 +286,11 @@ const dynamicSlotSize = (variable) => {
|
|
|
284
286
|
if (!variable?.slotValue)
|
|
285
287
|
throw Error(`Missing slot value.`);
|
|
286
288
|
const last4bits = '0x' + variable.slotValue.slice(-1);
|
|
287
|
-
const last4bitsNum =
|
|
289
|
+
const last4bitsNum = Number(BigInt(last4bits));
|
|
288
290
|
// If the last 4 bits is an even number then it's not a dynamic slot
|
|
289
291
|
if (last4bitsNum % 2 === 0)
|
|
290
292
|
return 0;
|
|
291
|
-
const sizeRaw =
|
|
293
|
+
const sizeRaw = Number(BigInt(variable.slotValue));
|
|
292
294
|
// Adjust the size to bytes
|
|
293
295
|
return (sizeRaw - 1) / 2;
|
|
294
296
|
}
|
|
@@ -302,7 +304,7 @@ const convert2String = (bytes) => {
|
|
|
302
304
|
'0x0000000000000000000000000000000000000000000000000000000000000000') {
|
|
303
305
|
return '';
|
|
304
306
|
}
|
|
305
|
-
const rawString = (0,
|
|
307
|
+
const rawString = (0, ethers_1.toUtf8String)(bytes);
|
|
306
308
|
return (0, exports.escapeString)(rawString);
|
|
307
309
|
};
|
|
308
310
|
exports.convert2String = convert2String;
|
package/lib/utils/block.js
CHANGED
|
@@ -6,7 +6,7 @@ const debug = require('debug')('sol2uml');
|
|
|
6
6
|
const getBlock = async (options) => {
|
|
7
7
|
if (options.block === 'latest') {
|
|
8
8
|
try {
|
|
9
|
-
const provider = new ethers_1.
|
|
9
|
+
const provider = new ethers_1.JsonRpcProvider(options.url);
|
|
10
10
|
const block = await provider.getBlockNumber();
|
|
11
11
|
debug(`Latest block is ${block}. All storage slot values will be from this block.`);
|
|
12
12
|
return block;
|
package/lib/utils/validators.js
CHANGED
|
@@ -3,13 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.validateTypes = exports.validateSlotNames = exports.validateLineBuffer = exports.validateNames = exports.validateAddress = void 0;
|
|
4
4
|
const regEx_1 = require("./regEx");
|
|
5
5
|
const commander_1 = require("commander");
|
|
6
|
-
const
|
|
6
|
+
const ethers_1 = require("ethers");
|
|
7
7
|
const converterClasses2Storage_1 = require("../converterClasses2Storage");
|
|
8
8
|
const debug = require('debug')('sol2uml');
|
|
9
9
|
const validateAddress = (address) => {
|
|
10
10
|
try {
|
|
11
11
|
if (typeof address === 'string' && address?.match(regEx_1.ethereumAddress))
|
|
12
|
-
return (0,
|
|
12
|
+
return (0, ethers_1.getAddress)(address);
|
|
13
13
|
}
|
|
14
14
|
catch { /* validation failed */ }
|
|
15
15
|
throw new commander_1.InvalidArgumentError(`Address must be in hexadecimal format with a 0x prefix.`);
|
|
@@ -46,7 +46,7 @@ const validateSlotNames = (slotNames) => {
|
|
|
46
46
|
offset: slot,
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
|
-
const offset = (0,
|
|
49
|
+
const offset = (0, ethers_1.keccak256)((0, ethers_1.toUtf8Bytes)(slot));
|
|
50
50
|
debug(`Slot name "${slot}" has hash "${offset}"`);
|
|
51
51
|
return {
|
|
52
52
|
name: slot,
|
package/lib/writerFiles.js
CHANGED
|
@@ -13,6 +13,7 @@ const fs_1 = require("fs");
|
|
|
13
13
|
const path_1 = __importDefault(require("path"));
|
|
14
14
|
const sync_1 = __importDefault(require("@aduh95/viz.js/sync"));
|
|
15
15
|
const { convert } = require('convert-svg-to-png');
|
|
16
|
+
const puppeteer = require('puppeteer');
|
|
16
17
|
const debug = require('debug')('sol2uml');
|
|
17
18
|
/**
|
|
18
19
|
* Writes output files to the file system based on the provided input and options.
|
|
@@ -39,7 +40,9 @@ const writeOutputFiles = async (dot, contractName, outputFormat = 'svg', outputF
|
|
|
39
40
|
outputExt;
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
|
-
catch {
|
|
43
|
+
catch {
|
|
44
|
+
/* outputFilename does not exist yet */
|
|
45
|
+
}
|
|
43
46
|
}
|
|
44
47
|
if (outputFormat === 'dot' || outputFormat === 'all') {
|
|
45
48
|
writeDot(dot, outputFilename);
|
|
@@ -140,7 +143,7 @@ async function writePng(svg, filename) {
|
|
|
140
143
|
debug(`About to write png file ${pngFilename}`);
|
|
141
144
|
try {
|
|
142
145
|
const png = await convert(svg, {
|
|
143
|
-
|
|
146
|
+
launch: { executablePath: puppeteer.executablePath() },
|
|
144
147
|
});
|
|
145
148
|
return new Promise((resolve, reject) => {
|
|
146
149
|
(0, fs_1.writeFile)(pngFilename, png, (err) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sol2uml",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.24",
|
|
4
4
|
"description": "Solidity contract visualisation tool.",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"types": "./lib/index.d.ts",
|
|
@@ -29,26 +29,27 @@
|
|
|
29
29
|
"axios-debug-log": "^1.0.0",
|
|
30
30
|
"cli-color": "^2.0.4",
|
|
31
31
|
"commander": "^12.1.0",
|
|
32
|
-
"convert-svg-to-png": "^0.
|
|
32
|
+
"convert-svg-to-png": "^0.7.1",
|
|
33
33
|
"debug": "^4.4.1",
|
|
34
34
|
"diff-match-patch": "^1.0.5",
|
|
35
|
-
"ethers": "^
|
|
35
|
+
"ethers": "^6.16.0",
|
|
36
36
|
"js-graph-algorithms": "^1.0.18",
|
|
37
|
-
"klaw": "^4.1.0"
|
|
37
|
+
"klaw": "^4.1.0",
|
|
38
|
+
"puppeteer": "^24.37.3"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
41
|
"@eslint/js": "^9.39.2",
|
|
41
42
|
"@openzeppelin/contracts": "^4.9.3",
|
|
42
43
|
"@types/diff-match-patch": "^1.0.36",
|
|
43
|
-
"@types/jest": "^
|
|
44
|
+
"@types/jest": "^30.0.0",
|
|
44
45
|
"@types/klaw": "^3.0.7",
|
|
45
46
|
"eslint": "^9.39.2",
|
|
46
47
|
"eslint-config-prettier": "^10.1.8",
|
|
47
|
-
"jest": "^30.
|
|
48
|
-
"prettier": "^3.
|
|
49
|
-
"ts-jest": "^29.4.
|
|
48
|
+
"jest": "^30.2.0",
|
|
49
|
+
"prettier": "^3.8.1",
|
|
50
|
+
"ts-jest": "^29.4.6",
|
|
50
51
|
"ts-node": "^10.9.2",
|
|
51
|
-
"typescript": "^5.
|
|
52
|
+
"typescript": "^5.9.3",
|
|
52
53
|
"typescript-eslint": "^8.55.0"
|
|
53
54
|
},
|
|
54
55
|
"files": [
|