sol2uml 2.4.3 → 2.5.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.
@@ -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, umlClass, 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,110 @@ 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, umlClass, 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 umlClass the attribute is part of.
384
+ * @param otherClasses
385
+ */
386
+ const findTypeClass = (userType, attribute, umlClass, otherClasses) => {
387
+ // Find associated UserDefined type
388
+ const types = userType.split('.');
389
+ const association = {
390
+ referenceType: umlClass_1.ReferenceType.Memory,
391
+ targetUmlClassName: types.length === 1 ? types[0] : types[1],
392
+ };
393
+ const typeClass = (0, associations_1.findAssociatedClass)(association, umlClass, otherClasses);
394
+ if (!typeClass) {
395
+ throw Error(`Failed to find user defined type "${userType}" in attribute "${attribute.name}" of type "${attribute.attributeType}""`);
396
+ }
397
+ return typeClass;
398
+ };
284
399
  // Calculates the storage size of an attribute in bytes
285
400
  const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
286
401
  if (attribute.attributeType === umlClass_1.AttributeType.Mapping ||
@@ -291,7 +406,7 @@ const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
291
406
  // Fixed sized arrays are read from right to left until there is a dynamic dimension
292
407
  // eg address[][3][2] is a fixed size array that uses 6 slots.
293
408
  // while address [2][] is a dynamic sized array.
294
- const arrayDimensions = attribute.type.match(/\[\w*]/g);
409
+ const arrayDimensions = attribute.type.match(/\[[\w$.]*]/g);
295
410
  // Remove first [ and last ] from each arrayDimensions
296
411
  const dimensionsStr = arrayDimensions.map((a) => a.slice(1, -1));
297
412
  // fixed-sized arrays are read from right to left so reverse the dimensions
@@ -300,7 +415,7 @@ const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
300
415
  let dimension = dimensionsStrReversed.shift();
301
416
  const fixedDimensions = [];
302
417
  while (dimension && dimension !== '') {
303
- const dimensionNum = (0, exports.findDimensionLength)(umlClass, dimension);
418
+ const dimensionNum = (0, exports.findDimensionLength)(umlClass, dimension, otherClasses);
304
419
  fixedDimensions.push(dimensionNum);
305
420
  // read the next dimension for the next loop
306
421
  dimension = dimensionsStrReversed.shift();
@@ -311,9 +426,9 @@ const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
311
426
  // the array length is stored in the 32 byte slot
312
427
  return { size: 32, dynamic: true };
313
428
  }
429
+ // If a fixed sized array
314
430
  let elementSize;
315
431
  const type = attribute.type.substring(0, attribute.type.indexOf('['));
316
- // If a fixed sized array
317
432
  if ((0, exports.isElementary)(type)) {
318
433
  const elementAttribute = {
319
434
  attributeType: umlClass_1.AttributeType.Elementary,
@@ -343,7 +458,11 @@ const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
343
458
  };
344
459
  }
345
460
  const lastItem = fixedDimensions.length - 1;
346
- const lastDimensionBytes = elementSize * fixedDimensions[lastItem];
461
+ const lastArrayLength = fixedDimensions[lastItem];
462
+ const itemsPerSlot = Math.floor(32 / elementSize);
463
+ const lastDimensionBytes = itemsPerSlot > 0 // if one or more array items in a slot
464
+ ? Math.ceil(lastArrayLength / itemsPerSlot) * 32 // round up to include unallocated slot space
465
+ : elementSize * fixedDimensions[lastItem];
347
466
  const lastDimensionSlotBytes = Math.ceil(lastDimensionBytes / 32) * 32;
348
467
  const remainingDimensions = fixedDimensions
349
468
  .slice(0, lastItem)
@@ -353,16 +472,12 @@ const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
353
472
  dynamic: false,
354
473
  };
355
474
  }
356
- // If a Struct or Enum
475
+ // If a Struct, Enum or Contract reference
476
+ // TODO need to handle User Defined Value Types when they are added to Solidity
357
477
  if (attribute.attributeType === umlClass_1.AttributeType.UserDefined) {
358
478
  // 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) {
479
+ const attributeTypeClass = findTypeClass(attribute.type, attribute, umlClass, otherClasses);
480
+ switch (attributeTypeClass.stereotype) {
366
481
  case umlClass_1.ClassStereotype.Enum:
367
482
  return { size: 1, dynamic: false };
368
483
  case umlClass_1.ClassStereotype.Contract:
@@ -372,7 +487,7 @@ const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
372
487
  return { size: 20, dynamic: false };
373
488
  case umlClass_1.ClassStereotype.Struct:
374
489
  let structByteSize = 0;
375
- attributeClass.attributes.forEach((structAttribute) => {
490
+ attributeTypeClass.attributes.forEach((structAttribute) => {
376
491
  // If next attribute is an array, then we need to start in a new slot
377
492
  if (structAttribute.attributeType === umlClass_1.AttributeType.Array) {
378
493
  structByteSize = Math.ceil(structByteSize / 32) * 32;
@@ -381,13 +496,7 @@ const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
381
496
  else if (structAttribute.attributeType ===
382
497
  umlClass_1.AttributeType.UserDefined) {
383
498
  // 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
- }
499
+ const userDefinedClass = findTypeClass(structAttribute.type, structAttribute, umlClass, otherClasses);
391
500
  // If a struct
392
501
  if (userDefinedClass.stereotype ===
393
502
  umlClass_1.ClassStereotype.Struct) {
@@ -422,6 +531,7 @@ const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
422
531
  return { size: 20, dynamic: false };
423
532
  case 'string':
424
533
  case 'bytes':
534
+ return { size: 32, dynamic: true };
425
535
  case 'uint':
426
536
  case 'int':
427
537
  case 'ufixed':
@@ -457,49 +567,206 @@ const isElementary = (type) => {
457
567
  case 'fixed':
458
568
  return true;
459
569
  default:
460
- const result = type.match(/[u]*(int|fixed|bytes)([0-9]+)/);
570
+ const result = type.match(/^[u]?(int|fixed|bytes)([0-9]+)$/);
461
571
  return result !== null;
462
572
  }
463
573
  };
464
574
  exports.isElementary = isElementary;
465
- const calcSlotKey = (variable) => {
575
+ const calcSectionOffset = (variable, sectionOffset = '0') => {
466
576
  if (variable.dynamic) {
467
- return (0, utils_1.keccak256)((0, utils_1.toUtf8Bytes)(ethers_1.BigNumber.from(variable.fromSlot).toHexString()));
577
+ const hexStringOf32Bytes = (0, utils_1.hexZeroPad)(ethers_1.BigNumber.from(variable.fromSlot).add(sectionOffset).toHexString(), 32);
578
+ return (0, utils_1.keccak256)(hexStringOf32Bytes);
468
579
  }
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
- });
580
+ return ethers_1.BigNumber.from(variable.fromSlot).add(sectionOffset).toHexString();
488
581
  };
489
- exports.offsetStorageSlots = offsetStorageSlots;
490
- const findDimensionLength = (umlClass, dimension) => {
582
+ exports.calcSectionOffset = calcSectionOffset;
583
+ const findDimensionLength = (umlClass, dimension, otherClasses) => {
491
584
  const dimensionNum = parseInt(dimension);
492
585
  if (Number.isInteger(dimensionNum)) {
493
586
  return dimensionNum;
494
587
  }
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
- }
588
+ // Try and size array dimension from declared constants
589
+ const constant = umlClass.constants.find((constant) => constant.name === dimension);
590
+ if (constant) {
501
591
  return constant.value;
502
592
  }
593
+ // Try and size array dimension from file constants
594
+ const fileConstant = otherClasses.find((umlClass) => umlClass.name === dimension &&
595
+ umlClass.stereotype === umlClass_1.ClassStereotype.Constant);
596
+ if (fileConstant?.constants[0]?.value) {
597
+ return fileConstant.constants[0].value;
598
+ }
599
+ throw Error(`Could not size fixed sized array with dimension "${dimension}"`);
503
600
  };
504
601
  exports.findDimensionLength = findDimensionLength;
602
+ /**
603
+ * Calculate if the storage slot value for the attribute should be displayed in the storage section.
604
+ *
605
+ * Storage sections with true mapping should return false.
606
+ * Mapping types should return false.
607
+ * Elementary types should return true.
608
+ * Dynamic Array types should return true.
609
+ * Static Array types should return false.
610
+ * UserDefined types that are Structs should return false.
611
+ * UserDefined types that are Enums or alias to Elementary type or contract should return true.
612
+ *
613
+ * @param attributeType
614
+ * @param dynamic flags if the variable is of dynamic size
615
+ * @param mapping flags if the storage section is referenced by a mapping
616
+ * @param storageSectionType
617
+ * @return displayValue true if the slot value should be displayed.
618
+ */
619
+ const calcDisplayValue = (attributeType, dynamic, mapping, storageSectionType) => mapping === false &&
620
+ (attributeType === umlClass_1.AttributeType.Elementary ||
621
+ (attributeType === umlClass_1.AttributeType.UserDefined &&
622
+ storageSectionType !== StorageSectionType.Struct) ||
623
+ (attributeType === umlClass_1.AttributeType.Array && dynamic));
624
+ /**
625
+ * Calculate if the storage slot value for the attribute should be retrieved from the chain.
626
+ *
627
+ * Storage sections with true mapping should return false.
628
+ * Mapping types should return false.
629
+ * Elementary types should return true.
630
+ * Array types should return true.
631
+ * UserDefined should return true.
632
+ *
633
+ * @param attributeType the type of attribute the storage variable is for.
634
+ * @param mapping flags if the storage section is referenced by a mapping
635
+ * @return getValue true if the slot value should be retrieved.
636
+ */
637
+ const calcGetValue = (attributeType, mapping) => mapping === false && attributeType !== umlClass_1.AttributeType.Mapping;
638
+ /**
639
+ * Recursively adds variables for dynamic string, bytes or arrays
640
+ * @param storageSection
641
+ * @param storageSections
642
+ * @param url of Ethereum JSON-RPC API provider. eg Infura or Alchemy
643
+ * @param contractAddress Contract address to get the storage slot values from.
644
+ * @param arrayItems the number of items to display at the start and end of an array
645
+ * @param blockTag block number or `latest`
646
+ */
647
+ const addDynamicVariables = async (storageSection, storageSections, url, contractAddress, arrayItems, blockTag) => {
648
+ for (const variable of storageSection.variables) {
649
+ try {
650
+ if (!variable.dynamic)
651
+ continue;
652
+ // STEP 1 - add slots for dynamic string and bytes
653
+ if (variable.type === 'string' || variable.type === 'bytes') {
654
+ if (!variable.slotValue) {
655
+ debug(`WARNING: Variable "${variable.name}" of type "${variable.type}" has no slot value`);
656
+ continue;
657
+ }
658
+ const size = (0, slotValues_1.dynamicSlotSize)(variable);
659
+ if (size > 31) {
660
+ const maxSlotNumber = Math.floor((size - 1) / 32);
661
+ const variables = [];
662
+ // For each dynamic slot
663
+ for (let i = 0; i <= maxSlotNumber; i++) {
664
+ // If the last slot then get the remaining bytes
665
+ const byteSize = i === maxSlotNumber ? size - 32 * maxSlotNumber : 32;
666
+ // Add variable for the slot
667
+ variables.push({
668
+ id: variableId++,
669
+ fromSlot: i,
670
+ toSlot: i,
671
+ byteSize,
672
+ byteOffset: 0,
673
+ type: variable.type,
674
+ contractName: variable.contractName,
675
+ attributeType: umlClass_1.AttributeType.Elementary,
676
+ dynamic: false,
677
+ getValue: true,
678
+ displayValue: true,
679
+ });
680
+ }
681
+ // add unallocated variable
682
+ const unusedBytes = 32 - (size - 32 * maxSlotNumber);
683
+ if (unusedBytes > 0) {
684
+ const lastVariable = variables[variables.length - 1];
685
+ variables.push({
686
+ ...lastVariable,
687
+ byteOffset: unusedBytes,
688
+ });
689
+ variables[maxSlotNumber] = {
690
+ id: variableId++,
691
+ fromSlot: maxSlotNumber,
692
+ toSlot: maxSlotNumber,
693
+ byteSize: unusedBytes,
694
+ byteOffset: 0,
695
+ type: 'unallocated',
696
+ attributeType: umlClass_1.AttributeType.UserDefined,
697
+ contractName: variable.contractName,
698
+ name: '',
699
+ dynamic: false,
700
+ getValue: true,
701
+ displayValue: false,
702
+ };
703
+ }
704
+ const newStorageSection = {
705
+ id: storageId++,
706
+ name: `${variable.type}: ${variable.name}`,
707
+ offset: (0, exports.calcSectionOffset)(variable, storageSection.offset),
708
+ type: variable.type === 'string'
709
+ ? StorageSectionType.String
710
+ : StorageSectionType.Bytes,
711
+ arrayDynamic: true,
712
+ arrayLength: size,
713
+ variables,
714
+ mapping: false,
715
+ };
716
+ variable.referenceSectionId = newStorageSection.id;
717
+ // get slot values for new referenced dynamic string or bytes
718
+ await (0, slotValues_1.addSlotValues)(url, contractAddress, newStorageSection, arrayItems, blockTag);
719
+ storageSections.push(newStorageSection);
720
+ }
721
+ continue;
722
+ }
723
+ if (variable.attributeType !== umlClass_1.AttributeType.Array)
724
+ continue;
725
+ // STEP 2 - add slots for dynamic arrays
726
+ // find storage section that the variable is referencing
727
+ const referenceStorageSection = storageSections.find((ss) => ss.id === variable.referenceSectionId);
728
+ if (!referenceStorageSection)
729
+ continue;
730
+ // recursively add dynamic variables to referenced array.
731
+ // this could be a fixed-size or dynamic array
732
+ await (0, exports.addDynamicVariables)(referenceStorageSection, storageSections, url, contractAddress, arrayItems, blockTag);
733
+ if (!variable.slotValue) {
734
+ debug(`WARNING: Dynamic array variable "${variable.name}" of type "${variable.type}" has no slot value`);
735
+ continue;
736
+ }
737
+ // Add missing dynamic array variables
738
+ const arrayLength = ethers_1.BigNumber.from(variable.slotValue).toNumber();
739
+ if (arrayLength > 1) {
740
+ // Add missing array variables to the referenced dynamic array
741
+ addArrayVariables(arrayLength, arrayItems, referenceStorageSection.variables);
742
+ // // For the newly added variables
743
+ // referenceStorageSection.variables.forEach((variable, i) => {
744
+ // if (
745
+ // referenceStorageSection.variables[0].attributeType !==
746
+ // AttributeType.Elementary &&
747
+ // i > 0
748
+ // ) {
749
+ // // recursively add storage section for Array and UserDefined types
750
+ // const references = parseStorageSectionFromAttribute(
751
+ // baseAttribute,
752
+ // umlClass,
753
+ // otherClasses,
754
+ // storageSections,
755
+ // mapping,
756
+ // arrayItems
757
+ // )
758
+ // variable.referenceSectionId = references.storageSection?.id
759
+ // variable.enumValues = references?.enumValues
760
+ // }
761
+ // })
762
+ }
763
+ // Get missing slot values to the referenced dynamic array
764
+ await (0, slotValues_1.addSlotValues)(url, contractAddress, referenceStorageSection, arrayItems, blockTag);
765
+ }
766
+ catch (err) {
767
+ 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 });
768
+ }
769
+ }
770
+ };
771
+ exports.addDynamicVariables = addDynamicVariables;
505
772
  //# sourceMappingURL=converterClasses2Storage.js.map