sol2uml 2.4.3 → 2.5.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.
@@ -3,47 +3,33 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.findDimensionLength = exports.offsetStorageSlots = exports.calcSlotKey = exports.isElementary = exports.calcStorageByteSize = exports.parseReferenceStorage = exports.convertClasses2Storages = exports.addStorageValues = exports.StorageType = void 0;
6
+ exports.addDynamicVariables = exports.findDimensionLength = exports.calcSectionOffset = exports.isElementary = exports.calcStorageByteSize = exports.parseStorageSectionFromAttribute = exports.convertClasses2StorageSections = exports.StorageSectionType = void 0;
7
7
  const umlClass_1 = require("./umlClass");
8
8
  const associations_1 = require("./associations");
9
- const slotValues_1 = require("./slotValues");
10
9
  const utils_1 = require("ethers/lib/utils");
11
10
  const ethers_1 = require("ethers");
12
11
  const path_1 = __importDefault(require("path"));
12
+ const slotValues_1 = require("./slotValues");
13
13
  const debug = require('debug')('sol2uml');
14
- var StorageType;
15
- (function (StorageType) {
16
- StorageType["Contract"] = "Contract";
17
- StorageType["Struct"] = "Struct";
18
- StorageType["Array"] = "Array";
19
- })(StorageType = exports.StorageType || (exports.StorageType = {}));
14
+ var StorageSectionType;
15
+ (function (StorageSectionType) {
16
+ StorageSectionType["Contract"] = "Contract";
17
+ StorageSectionType["Struct"] = "Struct";
18
+ StorageSectionType["Array"] = "Array";
19
+ StorageSectionType["Bytes"] = "Bytes";
20
+ StorageSectionType["String"] = "String";
21
+ })(StorageSectionType = exports.StorageSectionType || (exports.StorageSectionType = {}));
20
22
  let storageId = 1;
21
23
  let variableId = 1;
22
- /**
23
- *
24
- * @param url of Ethereum JSON-RPC API provider. eg Infura or Alchemy
25
- * @param contractAddress Contract address to get the storage slot values from.
26
- * If proxied, use proxy and not the implementation contract.
27
- * @param storage is mutated with the storage values
28
- * @param blockTag block number or `latest`
29
- */
30
- const addStorageValues = async (url, contractAddress, storage, blockTag) => {
31
- const valueVariables = storage.variables.filter((s) => !s.noValue);
32
- const slots = valueVariables.map((s) => s.fromSlot);
33
- const values = await (0, slotValues_1.getStorageValues)(url, contractAddress, slots, blockTag);
34
- valueVariables.forEach((valueVariable, i) => {
35
- valueVariable.value = values[i];
36
- });
37
- };
38
- exports.addStorageValues = addStorageValues;
39
24
  /**
40
25
  *
41
26
  * @param contractName name of the contract to get storage layout.
42
27
  * @param umlClasses array of UML classes of type `UMLClass`
28
+ * @param arrayItems the number of items to display at the start and end of an array
43
29
  * @param contractFilename relative path of the contract in the file system
44
- * @return array of storage objects with consecutive slots
30
+ * @return storageSections array of storageSection objects
45
31
  */
46
- const convertClasses2Storages = (contractName, umlClasses, contractFilename) => {
32
+ const convertClasses2StorageSections = (contractName, umlClasses, arrayItems, contractFilename) => {
47
33
  // Find the base UML Class from the base contract name
48
34
  const umlClass = umlClasses.find(({ name, relativePath }) => {
49
35
  if (!contractFilename) {
@@ -61,25 +47,32 @@ const convertClasses2Storages = (contractName, umlClasses, contractFilename) =>
61
47
  throw Error(`Failed to find contract with name "${contractName}"${contractFilenameError}.\nIs the \`-c --contract <name>\` option correct?`);
62
48
  }
63
49
  debug(`Found contract "${contractName}" in ${umlClass.absolutePath}`);
64
- const storages = [];
65
- const variables = parseVariables(umlClass, umlClasses, [], storages, []);
66
- storages.unshift({
50
+ const storageSections = [];
51
+ const variables = parseVariables(umlClass, umlClasses, [], storageSections, [], false, arrayItems);
52
+ // Add new storage section to the beginning of the array
53
+ storageSections.unshift({
67
54
  id: storageId++,
68
55
  name: contractName,
69
- type: StorageType.Contract,
56
+ type: StorageSectionType.Contract,
70
57
  variables: variables,
58
+ mapping: false,
71
59
  });
72
- return storages;
60
+ adjustSlots(storageSections[0], 0, storageSections);
61
+ return storageSections;
73
62
  };
74
- exports.convertClasses2Storages = convertClasses2Storages;
63
+ exports.convertClasses2StorageSections = convertClasses2StorageSections;
75
64
  /**
76
- * Recursively parses the storage variables for a given contract.
65
+ * Recursively parse the storage variables for a given contract or struct.
77
66
  * @param umlClass contract or file level struct
78
67
  * @param umlClasses other contracts, structs and enums that may be a type of a storage variable.
79
- * @param variables mutable array of storage slots that is appended to
80
- * @param storages mutable array of storages that is appended with structs
68
+ * @param variables mutable array of storage variables that are appended to
69
+ * @param storageSections mutable array of storageSection objects
70
+ * @param inheritedContracts mutable array of contracts that have been inherited already
71
+ * @param mapping flags that the storage section is under a mapping
72
+ * @param arrayItems the number of items to display at the start and end of an array
73
+ * @return variables array of storage variables in the `umlClass`
81
74
  */
82
- const parseVariables = (umlClass, umlClasses, variables, storages, inheritedContracts) => {
75
+ const parseVariables = (umlClass, umlClasses, variables, storageSections, inheritedContracts, mapping, arrayItems) => {
83
76
  // Add storage slots from inherited contracts first.
84
77
  // Get immediate parent contracts that the class inherits from
85
78
  const parentContracts = umlClass.getParentContracts();
@@ -94,7 +87,7 @@ const parseVariables = (umlClass, umlClasses, variables, storages, inheritedCont
94
87
  throw Error(`Failed to find inherited contract "${parent.targetUmlClassName}" of "${umlClass.absolutePath}"`);
95
88
  }
96
89
  // recursively parse inherited contract
97
- parseVariables(parentClass, umlClasses, variables, storages, inheritedContracts);
90
+ parseVariables(parentClass, umlClasses, variables, storageSections, inheritedContracts, mapping, arrayItems);
98
91
  });
99
92
  // Parse storage for each attribute
100
93
  umlClass.attributes.forEach((attribute) => {
@@ -102,69 +95,97 @@ const parseVariables = (umlClass, umlClasses, variables, storages, inheritedCont
102
95
  if (attribute.compiled)
103
96
  return;
104
97
  const { size: byteSize, dynamic } = (0, exports.calcStorageByteSize)(attribute, umlClass, umlClasses);
105
- const noValue = attribute.attributeType === umlClass_1.AttributeType.Mapping ||
106
- (attribute.attributeType === umlClass_1.AttributeType.Array && !dynamic);
107
- // find any dependent storage locations
108
- const referenceStorage = (0, exports.parseReferenceStorage)(attribute, umlClass, umlClasses, storages);
98
+ // parse any dependent storage sections or enums
99
+ const references = (0, exports.parseStorageSectionFromAttribute)(attribute, umlClass, umlClasses, storageSections, mapping || attribute.attributeType === umlClass_1.AttributeType.Mapping, arrayItems);
100
+ // should this new variable get the slot value
101
+ const displayValue = calcDisplayValue(attribute.attributeType, dynamic, mapping, references?.storageSection?.type);
102
+ const getValue = calcGetValue(attribute.attributeType, mapping);
109
103
  // Get the toSlot of the last storage item
110
- let lastToSlot = 0;
111
- let nextOffset = 0;
112
- if (variables.length > 0) {
113
- const lastStorage = variables[variables.length - 1];
114
- lastToSlot = lastStorage.toSlot;
115
- nextOffset = lastStorage.byteOffset + lastStorage.byteSize;
116
- }
117
- let newVariable;
104
+ const lastVariable = variables[variables.length - 1];
105
+ let lastToSlot = lastVariable ? lastVariable.toSlot : 0;
106
+ let nextOffset = lastVariable
107
+ ? lastVariable.byteOffset + lastVariable.byteSize
108
+ : 0;
109
+ let fromSlot;
110
+ let toSlot;
111
+ let byteOffset;
118
112
  if (nextOffset + byteSize > 32) {
119
113
  const nextFromSlot = variables.length > 0 ? lastToSlot + 1 : 0;
120
- newVariable = {
121
- id: variableId++,
122
- fromSlot: nextFromSlot,
123
- toSlot: nextFromSlot + Math.floor((byteSize - 1) / 32),
124
- byteSize,
125
- byteOffset: 0,
126
- type: attribute.type,
127
- dynamic,
128
- noValue,
129
- variable: attribute.name,
130
- contractName: umlClass.name,
131
- referenceStorageId: referenceStorage?.id,
132
- };
114
+ fromSlot = nextFromSlot;
115
+ toSlot = nextFromSlot + Math.floor((byteSize - 1) / 32);
116
+ byteOffset = 0;
133
117
  }
134
118
  else {
135
- newVariable = {
136
- id: variableId++,
137
- fromSlot: lastToSlot,
138
- toSlot: lastToSlot,
139
- byteSize,
140
- byteOffset: nextOffset,
141
- type: attribute.type,
142
- dynamic,
143
- noValue,
144
- variable: attribute.name,
145
- contractName: umlClass.name,
146
- referenceStorageId: referenceStorage?.id,
147
- };
119
+ fromSlot = lastToSlot;
120
+ toSlot = lastToSlot;
121
+ byteOffset = nextOffset;
148
122
  }
149
- if (referenceStorage) {
150
- if (!newVariable.dynamic) {
151
- (0, exports.offsetStorageSlots)(referenceStorage, newVariable.fromSlot, storages);
123
+ variables.push({
124
+ id: variableId++,
125
+ fromSlot,
126
+ toSlot,
127
+ byteSize,
128
+ byteOffset,
129
+ type: attribute.type,
130
+ attributeType: attribute.attributeType,
131
+ dynamic,
132
+ getValue,
133
+ displayValue,
134
+ name: attribute.name,
135
+ contractName: umlClass.name,
136
+ referenceSectionId: references?.storageSection?.id,
137
+ enumValues: references?.enumValues,
138
+ });
139
+ });
140
+ return variables;
141
+ };
142
+ /**
143
+ * Recursively adjusts the fromSlot and toSlot properties of any storage variables
144
+ * that are referenced by a static array or struct.
145
+ * Also sets the storage slot offset for dynamic arrays, strings and bytes.
146
+ * @param storageSection
147
+ * @param slotOffset
148
+ * @param storageSections
149
+ */
150
+ const adjustSlots = (storageSection, slotOffset, storageSections) => {
151
+ storageSection.variables.forEach((variable) => {
152
+ // offset storage slots
153
+ variable.fromSlot += slotOffset;
154
+ variable.toSlot += slotOffset;
155
+ // find storage section that the variable is referencing
156
+ const referenceStorageSection = storageSections.find((ss) => ss.id === variable.referenceSectionId);
157
+ if (referenceStorageSection) {
158
+ referenceStorageSection.offset = storageSection.offset;
159
+ if (!variable.dynamic) {
160
+ adjustSlots(referenceStorageSection, variable.fromSlot, storageSections);
152
161
  }
153
- else if (attribute.attributeType === umlClass_1.AttributeType.Array) {
154
- referenceStorage.slotKey = (0, exports.calcSlotKey)(newVariable);
162
+ else if (variable.attributeType === umlClass_1.AttributeType.Array) {
163
+ // attribute is a dynamic array
164
+ referenceStorageSection.offset = (0, exports.calcSectionOffset)(variable, storageSection.offset);
165
+ adjustSlots(referenceStorageSection, 0, storageSections);
155
166
  }
156
167
  }
157
- variables.push(newVariable);
158
168
  });
159
- return variables;
160
169
  };
161
- const parseReferenceStorage = (attribute, umlClass, otherClasses, storages) => {
170
+ /**
171
+ * Recursively adds new storage sections under a class attribute.
172
+ * also returns the allowed enum values
173
+ * @param attribute the attribute that is referencing a storage section
174
+ * @param umlClass contract or file level struct
175
+ * @param otherClasses array of all the UML Classes
176
+ * @param storageSections mutable array of storageSection objects
177
+ * @param mapping flags that the storage section is under a mapping
178
+ * @param arrayItems the number of items to display at the start and end of an array
179
+ * @return storageSection new storage section that was added or undefined if none was added.
180
+ * @return enumValues array of allowed enum values. undefined if attribute is not an enum
181
+ */
182
+ const parseStorageSectionFromAttribute = (attribute, umlClass, otherClasses, storageSections, mapping, arrayItems) => {
162
183
  if (attribute.attributeType === umlClass_1.AttributeType.Array) {
163
184
  // storage is dynamic if the attribute type ends in []
164
- const result = attribute.type.match(/\[(\w*)]$/);
185
+ const result = attribute.type.match(/\[([\w$.]*)]$/);
165
186
  const dynamic = result[1] === '';
166
187
  const arrayLength = !dynamic
167
- ? (0, exports.findDimensionLength)(umlClass, result[1])
188
+ ? (0, exports.findDimensionLength)(umlClass, result[1], otherClasses)
168
189
  : undefined;
169
190
  // get the type of the array items. eg
170
191
  // address[][4][2] will have base type address[][4]
@@ -181,14 +202,24 @@ const parseReferenceStorage = (attribute, umlClass, otherClasses, storages) => {
181
202
  }
182
203
  const baseAttribute = {
183
204
  visibility: attribute.visibility,
184
- name: baseType,
205
+ name: attribute.name,
185
206
  type: baseType,
186
207
  attributeType: baseAttributeType,
187
208
  };
188
- const { size: arrayItemSize } = (0, exports.calcStorageByteSize)(baseAttribute, umlClass, otherClasses);
209
+ const { size: arrayItemSize, dynamic: dynamicBase } = (0, exports.calcStorageByteSize)(baseAttribute, umlClass, otherClasses);
210
+ // If more than 16 bytes, then round up in 32 bytes increments
189
211
  const arraySlotSize = arrayItemSize > 16
190
212
  ? 32 * Math.ceil(arrayItemSize / 32)
191
213
  : arrayItemSize;
214
+ // If base type is not an Elementary type
215
+ // This can only be Array and UserDefined for base types of arrays.
216
+ let references;
217
+ if (baseAttributeType !== umlClass_1.AttributeType.Elementary) {
218
+ // recursively add storage section for Array and UserDefined types
219
+ references = (0, exports.parseStorageSectionFromAttribute)(baseAttribute, umlClass, otherClasses, storageSections, mapping, arrayItems);
220
+ }
221
+ const displayValue = calcDisplayValue(baseAttribute.attributeType, dynamicBase, mapping, references?.storageSection?.type);
222
+ const getValue = calcGetValue(attribute.attributeType, mapping);
192
223
  const variables = [];
193
224
  variables[0] = {
194
225
  id: variableId++,
@@ -197,58 +228,63 @@ const parseReferenceStorage = (attribute, umlClass, otherClasses, storages) => {
197
228
  byteSize: arrayItemSize,
198
229
  byteOffset: 0,
199
230
  type: baseType,
200
- dynamic,
201
- noValue: false,
231
+ attributeType: baseAttributeType,
232
+ dynamic: dynamicBase,
233
+ getValue,
234
+ displayValue,
235
+ referenceSectionId: references?.storageSection?.id,
236
+ enumValues: references?.enumValues,
202
237
  };
238
+ // If a fixed size array.
239
+ // Note dynamic arrays will have undefined arrayLength
203
240
  if (arrayLength > 1) {
204
- // For fixed length arrays. Dynamic arrays will have undefined arrayLength
205
- for (let i = 1; i < arrayLength; i++) {
206
- variables.push({
207
- id: variableId++,
208
- fromSlot: Math.floor((i * arraySlotSize) / 32),
209
- toSlot: Math.floor(((i + 1) * arraySlotSize - 1) / 32),
210
- byteSize: arrayItemSize,
211
- byteOffset: (i * arraySlotSize) % 32,
212
- type: baseType,
213
- dynamic,
214
- noValue: false,
215
- });
216
- }
217
- }
218
- // recursively add storage
219
- if (baseAttributeType !== umlClass_1.AttributeType.Elementary) {
220
- const referenceStorage = (0, exports.parseReferenceStorage)(baseAttribute, umlClass, otherClasses, storages);
221
- variables[0].referenceStorageId = referenceStorage?.id;
241
+ // Add missing fixed array variables from index 1
242
+ addArrayVariables(arrayLength, arrayItems, variables);
243
+ // For the newly added variables
244
+ variables.forEach((variable, i) => {
245
+ if (i > 0 &&
246
+ baseAttributeType !== umlClass_1.AttributeType.Elementary &&
247
+ variable.type !== '----' // ignore any filler variables
248
+ ) {
249
+ // recursively add storage section for Array and UserDefined types
250
+ references = (0, exports.parseStorageSectionFromAttribute)(baseAttribute, umlClass, otherClasses, storageSections, mapping, arrayItems);
251
+ variable.referenceSectionId = references?.storageSection?.id;
252
+ variable.enumValues = references?.enumValues;
253
+ }
254
+ });
222
255
  }
223
- const newStorage = {
256
+ const storageSection = {
224
257
  id: storageId++,
225
258
  name: `${attribute.type}: ${attribute.name}`,
226
- type: StorageType.Array,
259
+ type: StorageSectionType.Array,
227
260
  arrayDynamic: dynamic,
228
261
  arrayLength,
229
262
  variables,
263
+ mapping,
230
264
  };
231
- storages.push(newStorage);
232
- return newStorage;
265
+ storageSections.push(storageSection);
266
+ return { storageSection };
233
267
  }
234
268
  if (attribute.attributeType === umlClass_1.AttributeType.UserDefined) {
235
269
  // Is the user defined type linked to another Contract, Struct or Enum?
236
- const dependentClass = otherClasses.find(({ name }) => {
237
- return (name === attribute.type || name === attribute.type.split('.')[1]);
238
- });
239
- if (!dependentClass) {
240
- throw Error(`Failed to find user defined type "${attribute.type}"`);
241
- }
242
- if (dependentClass.stereotype === umlClass_1.ClassStereotype.Struct) {
243
- const variables = parseVariables(dependentClass, otherClasses, [], storages, []);
244
- const newStorage = {
270
+ const typeClass = findTypeClass(attribute.type, attribute, otherClasses);
271
+ if (typeClass.stereotype === umlClass_1.ClassStereotype.Struct) {
272
+ const variables = parseVariables(typeClass, otherClasses, [], storageSections, [], mapping, arrayItems);
273
+ const storageSection = {
245
274
  id: storageId++,
246
275
  name: attribute.type,
247
- type: StorageType.Struct,
276
+ type: StorageSectionType.Struct,
248
277
  variables,
278
+ mapping,
279
+ };
280
+ storageSections.push(storageSection);
281
+ return { storageSection };
282
+ }
283
+ else if (typeClass.stereotype === umlClass_1.ClassStereotype.Enum) {
284
+ return {
285
+ storageSection: undefined,
286
+ enumValues: typeClass.attributes.map((a) => a.name),
249
287
  };
250
- storages.push(newStorage);
251
- return newStorage;
252
288
  }
253
289
  return undefined;
254
290
  }
@@ -256,31 +292,105 @@ const parseReferenceStorage = (attribute, umlClass, otherClasses, storages) => {
256
292
  // get the UserDefined type from the mapping
257
293
  // note the mapping could be an array of Structs
258
294
  // Could also be a mapping of a mapping
259
- const result = attribute.type.match(/=\\>((?!mapping)\w*)[\\[]/);
295
+ const result = attribute.type.match(/=\\>((?!mapping)[\w$.]*)[\\[]/);
260
296
  // If mapping of user defined type
261
297
  if (result !== null && result[1] && !(0, exports.isElementary)(result[1])) {
262
- // Find UserDefined type
263
- const typeClass = otherClasses.find(({ name }) => name === result[1] || name === result[1].split('.')[1]);
264
- if (!typeClass) {
265
- throw Error(`Failed to find user defined type "${result[1]}" in attribute type "${attribute.type}"`);
266
- }
298
+ // Find UserDefined type can be a contract, struct or enum
299
+ const typeClass = findTypeClass(result[1], attribute, otherClasses);
267
300
  if (typeClass.stereotype === umlClass_1.ClassStereotype.Struct) {
268
- const variables = parseVariables(typeClass, otherClasses, [], storages, []);
269
- const newStorage = {
301
+ let variables = parseVariables(typeClass, otherClasses, [], storageSections, [], true, arrayItems);
302
+ const storageSection = {
270
303
  id: storageId++,
271
304
  name: typeClass.name,
272
- type: StorageType.Struct,
305
+ type: StorageSectionType.Struct,
306
+ mapping: true,
273
307
  variables,
274
308
  };
275
- storages.push(newStorage);
276
- return newStorage;
309
+ storageSections.push(storageSection);
310
+ return { storageSection };
277
311
  }
278
312
  }
279
313
  return undefined;
280
314
  }
281
315
  return undefined;
282
316
  };
283
- exports.parseReferenceStorage = parseReferenceStorage;
317
+ exports.parseStorageSectionFromAttribute = parseStorageSectionFromAttribute;
318
+ /**
319
+ * Adds missing storage variables to a fixed-size or dynamic array by cloning them from the first variable.
320
+ * @param arrayLength the length of the array
321
+ * @param arrayItems the number of items to display at the start and end of an array
322
+ * @param variables mutable array of storage variables that are appended to
323
+ */
324
+ const addArrayVariables = (arrayLength, arrayItems, variables) => {
325
+ const arraySlotSize = variables[0].byteSize;
326
+ const itemsPerSlot = Math.floor(32 / arraySlotSize);
327
+ const slotsPerItem = Math.ceil(arraySlotSize / 32);
328
+ const firstFillerItem = itemsPerSlot > 0 ? arrayItems * itemsPerSlot : arrayItems;
329
+ const lastFillerItem = itemsPerSlot > 0
330
+ ? arrayLength -
331
+ (arrayItems - 1) * itemsPerSlot - // the number of items in all but the last row
332
+ (arrayLength % itemsPerSlot || itemsPerSlot) - // the remaining items in the last row or all the items in a slot
333
+ 1 // need the items before the last three rows
334
+ : arrayLength - arrayItems - 1;
335
+ // Add variable from index 1 for each item in the array
336
+ for (let i = 1; i < arrayLength; i++) {
337
+ const fromSlot = itemsPerSlot > 0 ? Math.floor(i / itemsPerSlot) : i * slotsPerItem;
338
+ const toSlot = itemsPerSlot > 0 ? fromSlot : fromSlot + slotsPerItem;
339
+ // add filler variable before adding the first of the last items of the array
340
+ if (i === lastFillerItem && firstFillerItem < lastFillerItem) {
341
+ const fillerFromSlot = itemsPerSlot > 0
342
+ ? Math.floor(firstFillerItem / itemsPerSlot)
343
+ : firstFillerItem * slotsPerItem;
344
+ variables.push({
345
+ id: variableId++,
346
+ attributeType: umlClass_1.AttributeType.UserDefined,
347
+ type: '----',
348
+ fromSlot: fillerFromSlot,
349
+ toSlot: toSlot,
350
+ byteOffset: 0,
351
+ byteSize: (toSlot - fillerFromSlot + 1) * 32,
352
+ getValue: false,
353
+ displayValue: false,
354
+ dynamic: false,
355
+ });
356
+ }
357
+ // Add variables for the first arrayItems and last arrayItems
358
+ if (i < firstFillerItem || i > lastFillerItem) {
359
+ const byteOffset = itemsPerSlot > 0 ? (i % itemsPerSlot) * arraySlotSize : 0;
360
+ const slotValue = fromSlot === 0 ? variables[0].slotValue : undefined;
361
+ // add array variable
362
+ const newVariable = {
363
+ ...variables[0],
364
+ id: variableId++,
365
+ fromSlot,
366
+ toSlot,
367
+ byteOffset,
368
+ slotValue,
369
+ // These will be added in a separate step
370
+ parsedValue: undefined,
371
+ referenceSectionId: undefined,
372
+ enumValues: undefined,
373
+ };
374
+ newVariable.parsedValue = (0, slotValues_1.parseValue)(newVariable);
375
+ variables.push(newVariable);
376
+ }
377
+ }
378
+ };
379
+ /**
380
+ * Finds an attribute's user defined type that can be a Contract, Struct or Enum
381
+ * @param userType User defined type that is being looked for. This can be the base type of an attribute.
382
+ * @param attribute the attribute in the class that is user defined. This is just used for logging purposes
383
+ * @param otherClasses
384
+ */
385
+ const findTypeClass = (userType, attribute, otherClasses) => {
386
+ // Find associated UserDefined type
387
+ // TODO this just matches on name and doesn't take into account imports
388
+ const typeClass = otherClasses.find(({ name }) => name === userType || name === userType.split('.')[1]);
389
+ if (!typeClass) {
390
+ throw Error(`Failed to find user defined type "${userType}" in attribute "${attribute.name}" of type "${attribute.attributeType}""`);
391
+ }
392
+ return typeClass;
393
+ };
284
394
  // Calculates the storage size of an attribute in bytes
285
395
  const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
286
396
  if (attribute.attributeType === umlClass_1.AttributeType.Mapping ||
@@ -291,7 +401,7 @@ const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
291
401
  // Fixed sized arrays are read from right to left until there is a dynamic dimension
292
402
  // eg address[][3][2] is a fixed size array that uses 6 slots.
293
403
  // while address [2][] is a dynamic sized array.
294
- const arrayDimensions = attribute.type.match(/\[\w*]/g);
404
+ const arrayDimensions = attribute.type.match(/\[[\w$.]*]/g);
295
405
  // Remove first [ and last ] from each arrayDimensions
296
406
  const dimensionsStr = arrayDimensions.map((a) => a.slice(1, -1));
297
407
  // fixed-sized arrays are read from right to left so reverse the dimensions
@@ -300,7 +410,7 @@ const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
300
410
  let dimension = dimensionsStrReversed.shift();
301
411
  const fixedDimensions = [];
302
412
  while (dimension && dimension !== '') {
303
- const dimensionNum = (0, exports.findDimensionLength)(umlClass, dimension);
413
+ const dimensionNum = (0, exports.findDimensionLength)(umlClass, dimension, otherClasses);
304
414
  fixedDimensions.push(dimensionNum);
305
415
  // read the next dimension for the next loop
306
416
  dimension = dimensionsStrReversed.shift();
@@ -311,9 +421,9 @@ const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
311
421
  // the array length is stored in the 32 byte slot
312
422
  return { size: 32, dynamic: true };
313
423
  }
424
+ // If a fixed sized array
314
425
  let elementSize;
315
426
  const type = attribute.type.substring(0, attribute.type.indexOf('['));
316
- // If a fixed sized array
317
427
  if ((0, exports.isElementary)(type)) {
318
428
  const elementAttribute = {
319
429
  attributeType: umlClass_1.AttributeType.Elementary,
@@ -343,7 +453,11 @@ const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
343
453
  };
344
454
  }
345
455
  const lastItem = fixedDimensions.length - 1;
346
- const lastDimensionBytes = elementSize * fixedDimensions[lastItem];
456
+ const lastArrayLength = fixedDimensions[lastItem];
457
+ const itemsPerSlot = Math.floor(32 / elementSize);
458
+ const lastDimensionBytes = itemsPerSlot > 0 // if one or more array items in a slot
459
+ ? Math.ceil(lastArrayLength / itemsPerSlot) * 32 // round up to include unallocated slot space
460
+ : elementSize * fixedDimensions[lastItem];
347
461
  const lastDimensionSlotBytes = Math.ceil(lastDimensionBytes / 32) * 32;
348
462
  const remainingDimensions = fixedDimensions
349
463
  .slice(0, lastItem)
@@ -353,16 +467,12 @@ const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
353
467
  dynamic: false,
354
468
  };
355
469
  }
356
- // If a Struct or Enum
470
+ // If a Struct, Enum or Contract reference
471
+ // TODO need to handle User Defined Value Types when they are added to Solidity
357
472
  if (attribute.attributeType === umlClass_1.AttributeType.UserDefined) {
358
473
  // Is the user defined type linked to another Contract, Struct or Enum?
359
- const attributeClass = otherClasses.find(({ name }) => {
360
- return (name === attribute.type || name === attribute.type.split('.')[1]);
361
- });
362
- if (!attributeClass) {
363
- throw Error(`Failed to find user defined struct or enum "${attribute.type}"`);
364
- }
365
- switch (attributeClass.stereotype) {
474
+ const attributeTypeClass = findTypeClass(attribute.type, attribute, otherClasses);
475
+ switch (attributeTypeClass.stereotype) {
366
476
  case umlClass_1.ClassStereotype.Enum:
367
477
  return { size: 1, dynamic: false };
368
478
  case umlClass_1.ClassStereotype.Contract:
@@ -372,7 +482,7 @@ const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
372
482
  return { size: 20, dynamic: false };
373
483
  case umlClass_1.ClassStereotype.Struct:
374
484
  let structByteSize = 0;
375
- attributeClass.attributes.forEach((structAttribute) => {
485
+ attributeTypeClass.attributes.forEach((structAttribute) => {
376
486
  // If next attribute is an array, then we need to start in a new slot
377
487
  if (structAttribute.attributeType === umlClass_1.AttributeType.Array) {
378
488
  structByteSize = Math.ceil(structByteSize / 32) * 32;
@@ -381,13 +491,7 @@ const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
381
491
  else if (structAttribute.attributeType ===
382
492
  umlClass_1.AttributeType.UserDefined) {
383
493
  // UserDefined types can be a struct or enum, so we need to check if it's a struct
384
- const userDefinedClass = otherClasses.find(({ name }) => {
385
- return (name === structAttribute.type ||
386
- name === structAttribute.type.split('.')[1]);
387
- });
388
- if (!userDefinedClass) {
389
- throw Error(`Failed to find user defined type "${structAttribute.type}" in struct ${attributeClass.name}`);
390
- }
494
+ const userDefinedClass = findTypeClass(structAttribute.type, structAttribute, otherClasses);
391
495
  // If a struct
392
496
  if (userDefinedClass.stereotype ===
393
497
  umlClass_1.ClassStereotype.Struct) {
@@ -422,6 +526,7 @@ const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
422
526
  return { size: 20, dynamic: false };
423
527
  case 'string':
424
528
  case 'bytes':
529
+ return { size: 32, dynamic: true };
425
530
  case 'uint':
426
531
  case 'int':
427
532
  case 'ufixed':
@@ -457,49 +562,206 @@ const isElementary = (type) => {
457
562
  case 'fixed':
458
563
  return true;
459
564
  default:
460
- const result = type.match(/[u]*(int|fixed|bytes)([0-9]+)/);
565
+ const result = type.match(/^[u]?(int|fixed|bytes)([0-9]+)$/);
461
566
  return result !== null;
462
567
  }
463
568
  };
464
569
  exports.isElementary = isElementary;
465
- const calcSlotKey = (variable) => {
570
+ const calcSectionOffset = (variable, sectionOffset = '0') => {
466
571
  if (variable.dynamic) {
467
- return (0, utils_1.keccak256)((0, utils_1.toUtf8Bytes)(ethers_1.BigNumber.from(variable.fromSlot).toHexString()));
572
+ const hexStringOf32Bytes = (0, utils_1.hexZeroPad)(ethers_1.BigNumber.from(variable.fromSlot).add(sectionOffset).toHexString(), 32);
573
+ return (0, utils_1.keccak256)(hexStringOf32Bytes);
468
574
  }
469
- return ethers_1.BigNumber.from(variable.fromSlot).toHexString();
470
- };
471
- exports.calcSlotKey = calcSlotKey;
472
- // recursively offset the slots numbers of a storage item
473
- const offsetStorageSlots = (storage, slots, storages) => {
474
- storage.variables.forEach((variable) => {
475
- variable.fromSlot += slots;
476
- variable.toSlot += slots;
477
- if (variable.referenceStorageId) {
478
- // recursively offset the referenced storage
479
- const referenceStorage = storages.find((s) => s.id === variable.referenceStorageId);
480
- if (!referenceStorage.arrayDynamic) {
481
- (0, exports.offsetStorageSlots)(referenceStorage, slots, storages);
482
- }
483
- else {
484
- referenceStorage.slotKey = (0, exports.calcSlotKey)(variable);
485
- }
486
- }
487
- });
575
+ return ethers_1.BigNumber.from(variable.fromSlot).add(sectionOffset).toHexString();
488
576
  };
489
- exports.offsetStorageSlots = offsetStorageSlots;
490
- const findDimensionLength = (umlClass, dimension) => {
577
+ exports.calcSectionOffset = calcSectionOffset;
578
+ const findDimensionLength = (umlClass, dimension, otherClasses) => {
491
579
  const dimensionNum = parseInt(dimension);
492
580
  if (Number.isInteger(dimensionNum)) {
493
581
  return dimensionNum;
494
582
  }
495
- else {
496
- // Try and size array dimension from declared constants
497
- const constant = umlClass.constants.find((constant) => constant.name === dimension);
498
- if (!constant) {
499
- throw Error(`Could not size fixed sized array with dimension "${dimension}"`);
500
- }
583
+ // Try and size array dimension from declared constants
584
+ const constant = umlClass.constants.find((constant) => constant.name === dimension);
585
+ if (constant) {
501
586
  return constant.value;
502
587
  }
588
+ // Try and size array dimension from file constants
589
+ const fileConstant = otherClasses.find((umlClass) => umlClass.name === dimension &&
590
+ umlClass.stereotype === umlClass_1.ClassStereotype.Constant);
591
+ if (fileConstant?.constants[0]?.value) {
592
+ return fileConstant.constants[0].value;
593
+ }
594
+ throw Error(`Could not size fixed sized array with dimension "${dimension}"`);
503
595
  };
504
596
  exports.findDimensionLength = findDimensionLength;
597
+ /**
598
+ * Calculate if the storage slot value for the attribute should be displayed in the storage section.
599
+ *
600
+ * Storage sections with true mapping should return false.
601
+ * Mapping types should return false.
602
+ * Elementary types should return true.
603
+ * Dynamic Array types should return true.
604
+ * Static Array types should return false.
605
+ * UserDefined types that are Structs should return false.
606
+ * UserDefined types that are Enums or alias to Elementary type or contract should return true.
607
+ *
608
+ * @param attributeType
609
+ * @param dynamic flags if the variable is of dynamic size
610
+ * @param mapping flags if the storage section is referenced by a mapping
611
+ * @param storageSectionType
612
+ * @return displayValue true if the slot value should be displayed.
613
+ */
614
+ const calcDisplayValue = (attributeType, dynamic, mapping, storageSectionType) => mapping === false &&
615
+ (attributeType === umlClass_1.AttributeType.Elementary ||
616
+ (attributeType === umlClass_1.AttributeType.UserDefined &&
617
+ storageSectionType !== StorageSectionType.Struct) ||
618
+ (attributeType === umlClass_1.AttributeType.Array && dynamic));
619
+ /**
620
+ * Calculate if the storage slot value for the attribute should be retrieved from the chain.
621
+ *
622
+ * Storage sections with true mapping should return false.
623
+ * Mapping types should return false.
624
+ * Elementary types should return true.
625
+ * Array types should return true.
626
+ * UserDefined should return true.
627
+ *
628
+ * @param attributeType the type of attribute the storage variable is for.
629
+ * @param mapping flags if the storage section is referenced by a mapping
630
+ * @return getValue true if the slot value should be retrieved.
631
+ */
632
+ const calcGetValue = (attributeType, mapping) => mapping === false && attributeType !== umlClass_1.AttributeType.Mapping;
633
+ /**
634
+ * Recursively adds variables for dynamic string, bytes or arrays
635
+ * @param storageSection
636
+ * @param storageSections
637
+ * @param url of Ethereum JSON-RPC API provider. eg Infura or Alchemy
638
+ * @param contractAddress Contract address to get the storage slot values from.
639
+ * @param arrayItems the number of items to display at the start and end of an array
640
+ * @param blockTag block number or `latest`
641
+ */
642
+ const addDynamicVariables = async (storageSection, storageSections, url, contractAddress, arrayItems, blockTag) => {
643
+ for (const variable of storageSection.variables) {
644
+ try {
645
+ if (!variable.dynamic)
646
+ continue;
647
+ // STEP 1 - add slots for dynamic string and bytes
648
+ if (variable.type === 'string' || variable.type === 'bytes') {
649
+ if (!variable.slotValue) {
650
+ debug(`WARNING: Variable "${variable.name}" of type "${variable.type}" has no slot value`);
651
+ continue;
652
+ }
653
+ const size = (0, slotValues_1.dynamicSlotSize)(variable);
654
+ if (size > 31) {
655
+ const maxSlotNumber = Math.floor((size - 1) / 32);
656
+ const variables = [];
657
+ // For each dynamic slot
658
+ for (let i = 0; i <= maxSlotNumber; i++) {
659
+ // If the last slot then get the remaining bytes
660
+ const byteSize = i === maxSlotNumber ? size - 32 * maxSlotNumber : 32;
661
+ // Add variable for the slot
662
+ variables.push({
663
+ id: variableId++,
664
+ fromSlot: i,
665
+ toSlot: i,
666
+ byteSize,
667
+ byteOffset: 0,
668
+ type: variable.type,
669
+ contractName: variable.contractName,
670
+ attributeType: umlClass_1.AttributeType.Elementary,
671
+ dynamic: false,
672
+ getValue: true,
673
+ displayValue: true,
674
+ });
675
+ }
676
+ // add unallocated variable
677
+ const unusedBytes = 32 - (size - 32 * maxSlotNumber);
678
+ if (unusedBytes > 0) {
679
+ const lastVariable = variables[variables.length - 1];
680
+ variables.push({
681
+ ...lastVariable,
682
+ byteOffset: unusedBytes,
683
+ });
684
+ variables[maxSlotNumber] = {
685
+ id: variableId++,
686
+ fromSlot: maxSlotNumber,
687
+ toSlot: maxSlotNumber,
688
+ byteSize: unusedBytes,
689
+ byteOffset: 0,
690
+ type: 'unallocated',
691
+ attributeType: umlClass_1.AttributeType.UserDefined,
692
+ contractName: variable.contractName,
693
+ name: '',
694
+ dynamic: false,
695
+ getValue: true,
696
+ displayValue: false,
697
+ };
698
+ }
699
+ const newStorageSection = {
700
+ id: storageId++,
701
+ name: `${variable.type}: ${variable.name}`,
702
+ offset: (0, exports.calcSectionOffset)(variable, storageSection.offset),
703
+ type: variable.type === 'string'
704
+ ? StorageSectionType.String
705
+ : StorageSectionType.Bytes,
706
+ arrayDynamic: true,
707
+ arrayLength: size,
708
+ variables,
709
+ mapping: false,
710
+ };
711
+ variable.referenceSectionId = newStorageSection.id;
712
+ // get slot values for new referenced dynamic string or bytes
713
+ await (0, slotValues_1.addSlotValues)(url, contractAddress, newStorageSection, arrayItems, blockTag);
714
+ storageSections.push(newStorageSection);
715
+ }
716
+ continue;
717
+ }
718
+ if (variable.attributeType !== umlClass_1.AttributeType.Array)
719
+ continue;
720
+ // STEP 2 - add slots for dynamic arrays
721
+ // find storage section that the variable is referencing
722
+ const referenceStorageSection = storageSections.find((ss) => ss.id === variable.referenceSectionId);
723
+ if (!referenceStorageSection)
724
+ continue;
725
+ // recursively add dynamic variables to referenced array.
726
+ // this could be a fixed-size or dynamic array
727
+ await (0, exports.addDynamicVariables)(referenceStorageSection, storageSections, url, contractAddress, arrayItems, blockTag);
728
+ if (!variable.slotValue) {
729
+ debug(`WARNING: Dynamic array variable "${variable.name}" of type "${variable.type}" has no slot value`);
730
+ continue;
731
+ }
732
+ // Add missing dynamic array variables
733
+ const arrayLength = ethers_1.BigNumber.from(variable.slotValue).toNumber();
734
+ if (arrayLength > 1) {
735
+ // Add missing array variables to the referenced dynamic array
736
+ addArrayVariables(arrayLength, arrayItems, referenceStorageSection.variables);
737
+ // // For the newly added variables
738
+ // referenceStorageSection.variables.forEach((variable, i) => {
739
+ // if (
740
+ // referenceStorageSection.variables[0].attributeType !==
741
+ // AttributeType.Elementary &&
742
+ // i > 0
743
+ // ) {
744
+ // // recursively add storage section for Array and UserDefined types
745
+ // const references = parseStorageSectionFromAttribute(
746
+ // baseAttribute,
747
+ // umlClass,
748
+ // otherClasses,
749
+ // storageSections,
750
+ // mapping,
751
+ // arrayItems
752
+ // )
753
+ // variable.referenceSectionId = references.storageSection?.id
754
+ // variable.enumValues = references?.enumValues
755
+ // }
756
+ // })
757
+ }
758
+ // Get missing slot values to the referenced dynamic array
759
+ await (0, slotValues_1.addSlotValues)(url, contractAddress, referenceStorageSection, arrayItems, blockTag);
760
+ }
761
+ catch (err) {
762
+ throw Error(`Failed to add dynamic vars for section "${storageSection.name}", var type "${variable.type}" with value "${variable.slotValue}" from slot ${variable.fromSlot} and section offset ${storageSection.offset}`, { cause: err });
763
+ }
764
+ }
765
+ };
766
+ exports.addDynamicVariables = addDynamicVariables;
505
767
  //# sourceMappingURL=converterClasses2Storage.js.map