sol2uml 2.0.5 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2020 Nick Addison
3
+ Copyright (c) 2022 Nick Addison
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -115,12 +115,16 @@ Visually display a contract's storage slots.
115
115
  WARNING: sol2uml does not use the Solidity compiler so may differ with solc. A known example is fixed-sized arrays declared with an expression will fail to be sized.
116
116
 
117
117
  Arguments:
118
- fileFolderAddress file name, base folder or contract address
118
+ fileFolderAddress file name, base folder or contract address
119
119
 
120
120
  Options:
121
- -c, --contractName <value> Contract name in local Solidity files. Not needed when using an address as the first argument.
122
- -h, --help display help for command
123
-
121
+ -c, --contract <name> Contract name in local Solidity files. Not needed when using an address as the first argument as the contract name can be derived from Etherscan.
122
+ -d, --data Gets the values in the storage slots from an Ethereum node. (default: false)
123
+ -s, --storage <address> The address of the contract with the storage values. This will be different from the contract with the code if a proxy contract is used. This is not needed if `fileFolderAddress` is an address and
124
+ the contract is not proxied.
125
+ -u, --url <url> URL of the Ethereum node to get storage values if the `data` option is used. (default: "http://localhost:8545", env: NODE_URL)
126
+ -bn, --block <number> Block number to get the contract storage values from. (default: "latest")
127
+ -h, --help display help for command
124
128
  ```
125
129
 
126
130
  ### Flatten usage
@@ -98,7 +98,9 @@ function convertAST2UmlClasses(node, relativePath, filesystem = false) {
98
98
  }
99
99
  else {
100
100
  // this has come from Etherscan
101
- const importPath = path.join(codeFolder, childNode.path);
101
+ const importPath = childNode.path[0] === '@'
102
+ ? childNode.path
103
+ : path.join(codeFolder, childNode.path);
102
104
  imports.push({
103
105
  absolutePath: importPath,
104
106
  classNames: childNode.symbolAliases
@@ -3,7 +3,7 @@ export declare enum StorageType {
3
3
  Contract = 0,
4
4
  Struct = 1
5
5
  }
6
- export interface Storage {
6
+ export interface Variable {
7
7
  id: number;
8
8
  fromSlot: number;
9
9
  toSlot: number;
@@ -12,18 +12,25 @@ export interface Storage {
12
12
  type: string;
13
13
  variable: string;
14
14
  contractName?: string;
15
- value?: string;
16
- structObjectId?: number;
15
+ values: string[];
16
+ structStorageId?: number;
17
17
  enumId?: number;
18
18
  }
19
- export interface StorageObject {
19
+ export interface Storage {
20
20
  id: number;
21
21
  name: string;
22
22
  address?: string;
23
23
  type: StorageType;
24
- storages: Storage[];
24
+ variables: Variable[];
25
25
  }
26
- export declare const convertClasses2StorageObjects: (contractName: string, umlClasses: UmlClass[]) => StorageObject[];
27
- export declare const parseStructStorageObject: (attribute: Attribute, otherClasses: UmlClass[], storageObjects: StorageObject[]) => StorageObject | undefined;
26
+ /**
27
+ *
28
+ * @param url
29
+ * @param contractAddress Contract address to get the storage slot values from
30
+ * @param storage is mutated with the storage values
31
+ */
32
+ export declare const addStorageValues: (url: string, contractAddress: string, storage: Storage, blockTag: string) => Promise<void>;
33
+ export declare const convertClasses2Storages: (contractName: string, umlClasses: UmlClass[]) => Storage[];
34
+ export declare const parseStructStorage: (attribute: Attribute, otherClasses: UmlClass[], storages: Storage[]) => Storage | undefined;
28
35
  export declare const calcStorageByteSize: (attribute: Attribute, umlClass: UmlClass, otherClasses: UmlClass[]) => number;
29
36
  export declare const isElementary: (type: string) => boolean;
@@ -1,16 +1,31 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isElementary = exports.calcStorageByteSize = exports.parseStructStorageObject = exports.convertClasses2StorageObjects = exports.StorageType = void 0;
3
+ exports.isElementary = exports.calcStorageByteSize = exports.parseStructStorage = exports.convertClasses2Storages = exports.addStorageValues = exports.StorageType = void 0;
4
4
  const umlClass_1 = require("./umlClass");
5
5
  const associations_1 = require("./associations");
6
+ const slotValues_1 = require("./slotValues");
6
7
  var StorageType;
7
8
  (function (StorageType) {
8
9
  StorageType[StorageType["Contract"] = 0] = "Contract";
9
10
  StorageType[StorageType["Struct"] = 1] = "Struct";
10
11
  })(StorageType = exports.StorageType || (exports.StorageType = {}));
11
- let storageObjectId = 1;
12
12
  let storageId = 1;
13
- const convertClasses2StorageObjects = (contractName, umlClasses) => {
13
+ let variableId = 1;
14
+ /**
15
+ *
16
+ * @param url
17
+ * @param contractAddress Contract address to get the storage slot values from
18
+ * @param storage is mutated with the storage values
19
+ */
20
+ const addStorageValues = async (url, contractAddress, storage, blockTag) => {
21
+ const slots = storage.variables.map((s) => s.fromSlot);
22
+ const values = await (0, slotValues_1.getStorageValues)(url, contractAddress, slots, blockTag);
23
+ storage.variables.forEach((storage, i) => {
24
+ storage.values = [values[i]];
25
+ });
26
+ };
27
+ exports.addStorageValues = addStorageValues;
28
+ const convertClasses2Storages = (contractName, umlClasses) => {
14
29
  // Find the base UML Class from the base contract name
15
30
  const umlClass = umlClasses.find(({ name }) => {
16
31
  return name === contractName;
@@ -18,25 +33,25 @@ const convertClasses2StorageObjects = (contractName, umlClasses) => {
18
33
  if (!umlClass) {
19
34
  throw Error(`Failed to find contract with name "${contractName}"`);
20
35
  }
21
- const storageObjects = [];
22
- const storages = parseStorage(umlClass, umlClasses, [], storageObjects, []);
23
- storageObjects.unshift({
24
- id: storageObjectId++,
36
+ const storages = [];
37
+ const variables = parseVariables(umlClass, umlClasses, [], storages, []);
38
+ storages.unshift({
39
+ id: storageId++,
25
40
  name: contractName,
26
41
  type: StorageType.Contract,
27
- storages,
42
+ variables: variables,
28
43
  });
29
- return storageObjects;
44
+ return storages;
30
45
  };
31
- exports.convertClasses2StorageObjects = convertClasses2StorageObjects;
46
+ exports.convertClasses2Storages = convertClasses2Storages;
32
47
  /**
33
- * Recursively parses the storage for a given contract.
48
+ * Recursively parses the storage variables for a given contract.
34
49
  * @param umlClass contract or file level struct
35
50
  * @param umlClasses other contracts, structs and enums that may be a type of a storage variable.
36
- * @param storages mutable array of storage slots that is appended to
37
- * @param storageObjects mutable array of StorageObjects that is appended with structs
51
+ * @param variables mutable array of storage slots that is appended to
52
+ * @param storages mutable array of storages that is appended with structs
38
53
  */
39
- const parseStorage = (umlClass, umlClasses, storages, storageObjects, inheritedContracts) => {
54
+ const parseVariables = (umlClass, umlClasses, variables, storages, inheritedContracts) => {
40
55
  // Add storage slots from inherited contracts first.
41
56
  // Get immediate parent contracts that the class inherits from
42
57
  const parentContracts = umlClass.getParentContracts();
@@ -50,7 +65,7 @@ const parseStorage = (umlClass, umlClasses, storages, storageObjects, inheritedC
50
65
  if (!parentClass)
51
66
  throw Error(`Failed to find parent contract ${parent.targetUmlClassName} of ${umlClass.absolutePath}`);
52
67
  // recursively parse inherited contract
53
- parseStorage(parentClass, umlClasses, storages, storageObjects, inheritedContracts);
68
+ parseVariables(parentClass, umlClasses, variables, storages, inheritedContracts);
54
69
  });
55
70
  // Parse storage for each attribute
56
71
  umlClass.attributes.forEach((attribute) => {
@@ -59,20 +74,20 @@ const parseStorage = (umlClass, umlClasses, storages, storageObjects, inheritedC
59
74
  return;
60
75
  const byteSize = (0, exports.calcStorageByteSize)(attribute, umlClass, umlClasses);
61
76
  // find any dependent structs
62
- const linkedStruct = (0, exports.parseStructStorageObject)(attribute, umlClasses, storageObjects);
63
- const structObjectId = linkedStruct?.id;
77
+ const linkedStruct = (0, exports.parseStructStorage)(attribute, umlClasses, storages);
78
+ const structStorageId = linkedStruct?.id;
64
79
  // Get the toSlot of the last storage item
65
80
  let lastToSlot = 0;
66
81
  let nextOffset = 0;
67
- if (storages.length > 0) {
68
- const lastStorage = storages[storages.length - 1];
82
+ if (variables.length > 0) {
83
+ const lastStorage = variables[variables.length - 1];
69
84
  lastToSlot = lastStorage.toSlot;
70
85
  nextOffset = lastStorage.byteOffset + lastStorage.byteSize;
71
86
  }
72
87
  if (nextOffset + byteSize > 32) {
73
- const nextFromSlot = storages.length > 0 ? lastToSlot + 1 : 0;
74
- storages.push({
75
- id: storageId++,
88
+ const nextFromSlot = variables.length > 0 ? lastToSlot + 1 : 0;
89
+ variables.push({
90
+ id: variableId++,
76
91
  fromSlot: nextFromSlot,
77
92
  toSlot: nextFromSlot + Math.floor((byteSize - 1) / 32),
78
93
  byteSize,
@@ -80,12 +95,13 @@ const parseStorage = (umlClass, umlClasses, storages, storageObjects, inheritedC
80
95
  type: attribute.type,
81
96
  variable: attribute.name,
82
97
  contractName: umlClass.name,
83
- structObjectId,
98
+ structStorageId,
99
+ values: [],
84
100
  });
85
101
  }
86
102
  else {
87
- storages.push({
88
- id: storageId++,
103
+ variables.push({
104
+ id: variableId++,
89
105
  fromSlot: lastToSlot,
90
106
  toSlot: lastToSlot,
91
107
  byteSize,
@@ -93,18 +109,19 @@ const parseStorage = (umlClass, umlClasses, storages, storageObjects, inheritedC
93
109
  type: attribute.type,
94
110
  variable: attribute.name,
95
111
  contractName: umlClass.name,
96
- structObjectId,
112
+ structStorageId,
113
+ values: [],
97
114
  });
98
115
  }
99
116
  });
100
- return storages;
117
+ return variables;
101
118
  };
102
- const parseStructStorageObject = (attribute, otherClasses, storageObjects) => {
119
+ const parseStructStorage = (attribute, otherClasses, storages) => {
103
120
  if (attribute.attributeType === umlClass_1.AttributeType.UserDefined) {
104
- // Have we already created the storageObject?
105
- const existingStorageObject = storageObjects.find((dep) => dep.name === attribute.type);
106
- if (existingStorageObject) {
107
- return existingStorageObject;
121
+ // Have we already created the storage?
122
+ const existingStorage = storages.find((dep) => dep.name === attribute.type);
123
+ if (existingStorage) {
124
+ return existingStorage;
108
125
  }
109
126
  // Is the user defined type linked to another Contract, Struct or Enum?
110
127
  const dependentClass = otherClasses.find(({ name }) => {
@@ -114,15 +131,15 @@ const parseStructStorageObject = (attribute, otherClasses, storageObjects) => {
114
131
  throw Error(`Failed to find user defined type "${attribute.type}"`);
115
132
  }
116
133
  if (dependentClass.stereotype === umlClass_1.ClassStereotype.Struct) {
117
- const storages = parseStorage(dependentClass, otherClasses, [], storageObjects, []);
118
- const newStorageObject = {
119
- id: storageObjectId++,
134
+ const variables = parseVariables(dependentClass, otherClasses, [], storages, []);
135
+ const newStorage = {
136
+ id: storageId++,
120
137
  name: attribute.type,
121
138
  type: StorageType.Struct,
122
- storages,
139
+ variables,
123
140
  };
124
- storageObjects.push(newStorageObject);
125
- return newStorageObject;
141
+ storages.push(newStorage);
142
+ return newStorage;
126
143
  }
127
144
  return undefined;
128
145
  }
@@ -135,10 +152,10 @@ const parseStructStorageObject = (attribute, otherClasses, storageObjects) => {
135
152
  ? attribute.type.match(/=\\>((?!mapping)\w*)[\\[]/)
136
153
  : attribute.type.match(/(\w+)\[/);
137
154
  if (result !== null && result[1] && !(0, exports.isElementary)(result[1])) {
138
- // Have we already created the storageObject?
139
- const existingStorageObject = storageObjects.find(({ name }) => name === result[1] || name === result[1].split('.')[1]);
140
- if (existingStorageObject) {
141
- return existingStorageObject;
155
+ // Have we already created the storage?
156
+ const existingStorage = storages.find(({ name }) => name === result[1] || name === result[1].split('.')[1]);
157
+ if (existingStorage) {
158
+ return existingStorage;
142
159
  }
143
160
  // Find UserDefined type
144
161
  const typeClass = otherClasses.find(({ name }) => name === result[1] || name === result[1].split('.')[1]);
@@ -146,22 +163,22 @@ const parseStructStorageObject = (attribute, otherClasses, storageObjects) => {
146
163
  throw Error(`Failed to find user defined type "${result[1]}" in attribute type "${attribute.type}"`);
147
164
  }
148
165
  if (typeClass.stereotype === umlClass_1.ClassStereotype.Struct) {
149
- const storages = parseStorage(typeClass, otherClasses, [], storageObjects, []);
150
- const newStorageObject = {
151
- id: storageObjectId++,
166
+ const variables = parseVariables(typeClass, otherClasses, [], storages, []);
167
+ const newStorage = {
168
+ id: storageId++,
152
169
  name: typeClass.name,
153
170
  type: StorageType.Struct,
154
- storages,
171
+ variables,
155
172
  };
156
- storageObjects.push(newStorageObject);
157
- return newStorageObject;
173
+ storages.push(newStorage);
174
+ return newStorage;
158
175
  }
159
176
  }
160
177
  return undefined;
161
178
  }
162
179
  return undefined;
163
180
  };
164
- exports.parseStructStorageObject = parseStructStorageObject;
181
+ exports.parseStructStorage = parseStructStorage;
165
182
  // Calculates the storage size of an attribute in bytes
166
183
  const calcStorageByteSize = (attribute, umlClass, otherClasses) => {
167
184
  if (attribute.attributeType === umlClass_1.AttributeType.Mapping ||
@@ -1,3 +1,3 @@
1
- import { StorageObject } from './converterClasses2Storage';
2
- export declare const convertStorage2Dot: (storageObjects: StorageObject[]) => string;
3
- export declare function convertStorageObject2Dot(storageObject: StorageObject, dotString: string): string;
1
+ import { Storage } from './converterClasses2Storage';
2
+ export declare const convertStorages2Dot: (storages: Storage[]) => string;
3
+ export declare function convertStorage2Dot(storage: Storage, dotString: string): string;
@@ -1,24 +1,24 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.convertStorageObject2Dot = exports.convertStorage2Dot = void 0;
3
+ exports.convertStorage2Dot = exports.convertStorages2Dot = void 0;
4
4
  const converterClasses2Storage_1 = require("./converterClasses2Storage");
5
5
  const debug = require('debug')('sol2uml');
6
- const convertStorage2Dot = (storageObjects) => {
6
+ const convertStorages2Dot = (storages) => {
7
7
  let dotString = `
8
8
  digraph StorageDiagram {
9
9
  rankdir=LR
10
10
  color=black
11
11
  arrowhead=open
12
12
  node [shape=record, style=filled, fillcolor=gray95]`;
13
- // process contract and the struct objects
14
- storageObjects.forEach((storageObject) => {
15
- dotString = convertStorageObject2Dot(storageObject, dotString);
13
+ // process contract and the struct storages
14
+ storages.forEach((storage) => {
15
+ dotString = convertStorage2Dot(storage, dotString);
16
16
  });
17
17
  // link contract and structs to structs
18
- storageObjects.forEach((slot) => {
19
- slot.storages.forEach((storage) => {
20
- if (storage.structObjectId) {
21
- dotString += `\n ${slot.id}:${storage.id} -> ${storage.structObjectId}`;
18
+ storages.forEach((slot) => {
19
+ slot.variables.forEach((storage) => {
20
+ if (storage.structStorageId) {
21
+ dotString += `\n ${slot.id}:${storage.id} -> ${storage.structStorageId}`;
22
22
  }
23
23
  });
24
24
  });
@@ -27,63 +27,72 @@ node [shape=record, style=filled, fillcolor=gray95]`;
27
27
  debug(dotString);
28
28
  return dotString;
29
29
  };
30
- exports.convertStorage2Dot = convertStorage2Dot;
31
- function convertStorageObject2Dot(storageObject, dotString) {
32
- const steorotype = storageObject.type === converterClasses2Storage_1.StorageType.Struct ? 'Struct' : 'Contract';
33
- // write object header with name and optional address
34
- dotString += `\n${storageObject.id} [label="{\\<\\<${steorotype}\\>\\>\\n${storageObject.name}\\n${storageObject.address || ''} | `;
30
+ exports.convertStorages2Dot = convertStorages2Dot;
31
+ function convertStorage2Dot(storage, dotString) {
32
+ const steorotype = storage.type === converterClasses2Storage_1.StorageType.Struct ? 'Struct' : 'Contract';
33
+ // write storage header with name and optional address
34
+ dotString += `\n${storage.id} [label="${storage.name} \\<\\<${steorotype}\\>\\>\\n${storage.address || ''} | {`;
35
+ const startingVariables = storage.variables.filter((s) => s.byteOffset === 0);
35
36
  // write slot numbers
36
- storageObject.storages.forEach((storage, i) => {
37
- if (i === 0) {
38
- dotString += `{slot | 0`;
37
+ dotString += '{ slot';
38
+ startingVariables.forEach((variable, i) => {
39
+ if (variable.fromSlot === variable.toSlot) {
40
+ dotString += `| ${variable.fromSlot} `;
39
41
  }
40
- else if (storage.byteOffset === 0) {
41
- if (storage.fromSlot === storage.toSlot) {
42
- dotString += `| ${storage.fromSlot}`;
43
- }
44
- else {
45
- dotString += `| ${storage.fromSlot}-${storage.toSlot}`;
46
- }
42
+ else {
43
+ dotString += `| ${variable.fromSlot}-${variable.toSlot} `;
47
44
  }
48
45
  });
49
- // write storage types
50
- storageObject.storages.forEach((storage, i) => {
51
- const lastStorage = i > 0 ? storageObject.storages[i - 1] : undefined;
52
- const nextStorage = i + 1 < storageObject.storages.length
53
- ? storageObject.storages[i + 1]
54
- : undefined;
55
- if (i === 0) {
56
- const contractVaraiblePrefix = storageObject.type === converterClasses2Storage_1.StorageType.Contract
57
- ? '\\<inherited contract\\>.'
58
- : '';
59
- dotString += `} | {type: ${contractVaraiblePrefix}variable (bytes) `;
60
- }
61
- // if next storage is in the same slot
62
- // and storage is the first in the slot
63
- if (nextStorage?.fromSlot === storage.fromSlot &&
64
- storage.byteOffset === 0) {
65
- dotString += `| { ${dotVariable(storage, storageObject.name)} `;
66
- return;
67
- }
68
- // if last storage was on the same slot
69
- // and the next storage is on a different slot
70
- if (lastStorage?.fromSlot === storage.fromSlot &&
71
- (nextStorage?.fromSlot > storage.fromSlot ||
72
- nextStorage === undefined)) {
73
- dotString += `| ${dotVariable(storage, storageObject.name)} } `;
74
- return;
46
+ // write slot values if available
47
+ if (startingVariables[0]?.values[0]) {
48
+ dotString += '} | {value';
49
+ startingVariables.forEach((variable, i) => {
50
+ dotString += ` | ${variable.values[0]}`;
51
+ });
52
+ }
53
+ const contractVariablePrefix = storage.type === converterClasses2Storage_1.StorageType.Contract ? '\\<inherited contract\\>.' : '';
54
+ dotString += `} | { type: ${contractVariablePrefix}variable (bytes)`;
55
+ // For each slot
56
+ startingVariables.forEach((variable) => {
57
+ // Get all the storage variables in this slot
58
+ const slotVariables = storage.variables.filter((s) => s.fromSlot === variable.fromSlot);
59
+ const usedBytes = slotVariables.reduce((acc, s) => acc + s.byteSize, 0);
60
+ if (usedBytes < 32) {
61
+ slotVariables.push({
62
+ id: 0,
63
+ fromSlot: variable.fromSlot,
64
+ toSlot: variable.fromSlot,
65
+ byteSize: 32 - usedBytes,
66
+ byteOffset: usedBytes,
67
+ type: 'unallocated',
68
+ contractName: variable.contractName,
69
+ variable: '',
70
+ values: [],
71
+ });
75
72
  }
76
- // If storage covers a whole slot or is not at the start or end of a slot
77
- dotString += `| ${dotVariable(storage, storageObject.name)} `;
73
+ const slotVariablesReversed = slotVariables.reverse();
74
+ // For each variable in the slot
75
+ slotVariablesReversed.forEach((variable, i) => {
76
+ if (i === 0) {
77
+ dotString += ` | { ${dotVariable(variable, storage.name)} `;
78
+ }
79
+ else {
80
+ dotString += ` | ${dotVariable(variable, storage.name)} `;
81
+ }
82
+ });
83
+ dotString += '}';
78
84
  });
79
85
  // Need to close off the last label
80
86
  dotString += '}}"]\n';
81
87
  return dotString;
82
88
  }
83
- exports.convertStorageObject2Dot = convertStorageObject2Dot;
89
+ exports.convertStorage2Dot = convertStorage2Dot;
84
90
  const dotVariable = (storage, contractName) => {
85
- const port = storage.structObjectId !== undefined ? `<${storage.id}>` : '';
91
+ const port = storage.structStorageId !== undefined ? `<${storage.id}>` : '';
86
92
  const contractNamePrefix = storage.contractName !== contractName ? `${storage.contractName}.` : '';
87
- return `${port} ${storage.type}: ${contractNamePrefix}${storage.variable} (${storage.byteSize})`;
93
+ const variable = storage.variable
94
+ ? `: ${contractNamePrefix}${storage.variable}`
95
+ : '';
96
+ return `${port} ${storage.type}${variable} (${storage.byteSize})`;
88
97
  };
89
98
  //# sourceMappingURL=converterStorage2Dot.js.map
@@ -0,0 +1,3 @@
1
+ import { BigNumberish } from '@ethersproject/bignumber';
2
+ export declare const getStorageValue: (url: string, contractAddress: string, slot: BigNumberish, blockTag?: BigNumberish | 'latest') => Promise<string>;
3
+ export declare const getStorageValues: (url: string, contractAddress: string, slots: BigNumberish[], blockTag?: BigNumberish | 'latest') => Promise<string[]>;
package/lib/slotValues.js CHANGED
@@ -1,21 +1,51 @@
1
- // import { providers } from 'ethers'
2
- // import { BigNumberish } from '@ethersproject/bignumber'
3
- // import { BlockTag } from '@ethersproject/abstract-provider'
4
- //
5
- // export const getStorageValue = async (
6
- // contractAddress: string,
7
- // slot: BigNumberish,
8
- // blockTag: BlockTag = 'latest'
9
- // ) => {
10
- // const provider = new providers.JsonRpcProvider(
11
- // 'https://eth-mainnet.alchemyapi.io/v2/iRsTnBFfuK96dHeFnEJ0wg56wdzioYPg',
12
- // 'mainnet'
13
- // )
14
- // const slotValue = await provider.getStorageAt(
15
- // contractAddress,
16
- // slot,
17
- // blockTag
18
- // )
19
- // console.log(`Slot ${slot}: ${slotValue}`)
20
- // }
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getStorageValues = exports.getStorageValue = void 0;
7
+ const bignumber_1 = require("@ethersproject/bignumber");
8
+ const axios_1 = __importDefault(require("axios"));
9
+ const debug = require('debug')('sol2uml');
10
+ const getStorageValue = async (url, contractAddress, slot, blockTag = 'latest') => {
11
+ debug(`About to get storage slot ${slot} value for ${contractAddress}`);
12
+ const values = await (0, exports.getStorageValues)(url, contractAddress, [slot], blockTag);
13
+ debug(`Got slot ${slot} value: ${values[0]}`);
14
+ return values[0];
15
+ };
16
+ exports.getStorageValue = getStorageValue;
17
+ let jsonRpcId = 0;
18
+ const getStorageValues = async (url, contractAddress, slots, blockTag = 'latest') => {
19
+ try {
20
+ debug(`About to get ${slots.length} storage values for ${contractAddress} at block ${blockTag}`);
21
+ const block = blockTag === 'latest'
22
+ ? blockTag
23
+ : bignumber_1.BigNumber.from(blockTag).toHexString();
24
+ const payload = slots.map((slot) => ({
25
+ id: (jsonRpcId++).toString(),
26
+ jsonrpc: '2.0',
27
+ method: 'eth_getStorageAt',
28
+ params: [
29
+ contractAddress,
30
+ bignumber_1.BigNumber.from(slot).toHexString(),
31
+ block,
32
+ ],
33
+ }));
34
+ const response = await axios_1.default.post(url, payload);
35
+ console.log(response.data);
36
+ if (response.data?.error?.message) {
37
+ throw new Error(response.data.error.message);
38
+ }
39
+ if (response.data.length !== slots.length) {
40
+ throw new Error(`Requested ${slots.length} storage slot values but only got ${response.data.length}`);
41
+ }
42
+ const responseData = response.data;
43
+ const sortedResponses = responseData.sort((a, b) => bignumber_1.BigNumber.from(a.id).gt(b.id) ? 1 : -1);
44
+ return sortedResponses.map((data) => data.result);
45
+ }
46
+ catch (err) {
47
+ throw new Error(`Failed to get ${slots.length} storage values for ${contractAddress} from ${url}`, { cause: err });
48
+ }
49
+ };
50
+ exports.getStorageValues = getStorageValues;
21
51
  //# sourceMappingURL=slotValues.js.map
package/lib/sol2uml.js CHANGED
@@ -75,14 +75,14 @@ If an Ethereum address with a 0x prefix is passed, the verified source code from
75
75
  ...command.parent._optionValues,
76
76
  ...options,
77
77
  };
78
- const { umlClasses } = await (0, parserGeneral_1.parserUmlClasses)(fileFolderAddress, combinedOptions);
78
+ const { umlClasses, contractName } = await (0, parserGeneral_1.parserUmlClasses)(fileFolderAddress, combinedOptions);
79
79
  let filteredUmlClasses = umlClasses;
80
80
  if (options.baseContractNames) {
81
81
  const baseContractNames = options.baseContractNames.split(',');
82
82
  filteredUmlClasses = (0, filterClasses_1.classesConnectedToBaseContracts)(umlClasses, baseContractNames, options.depth);
83
83
  }
84
84
  const dotString = (0, converterClasses2Dot_1.convertUmlClasses2Dot)(filteredUmlClasses, combinedOptions.clusterFolders, combinedOptions);
85
- await (0, writerFiles_1.writeOutputFiles)(dotString, fileFolderAddress, combinedOptions.outputFormat, combinedOptions.outputFileName);
85
+ await (0, writerFiles_1.writeOutputFiles)(dotString, fileFolderAddress, contractName, combinedOptions.outputFormat, combinedOptions.outputFileName);
86
86
  debug(`Finished generating UML`);
87
87
  }
88
88
  catch (err) {
@@ -93,8 +93,13 @@ program
93
93
  .command('storage')
94
94
  .description("Visually display a contract's storage slots.\n\nWARNING: sol2uml does not use the Solidity compiler so may differ with solc. A known example is fixed-sized arrays declared with an expression will fail to be sized.")
95
95
  .argument('<fileFolderAddress>', 'file name, base folder or contract address')
96
- .option('-c, --contractName <value>', 'Contract name in local Solidity files. Not needed when using an address as the first argument.')
97
- // .option('-d, --data', 'gets the data in the storage slots')
96
+ .option('-c, --contract <name>', 'Contract name in local Solidity files. Not needed when using an address as the first argument as the contract name can be derived from Etherscan.')
97
+ .option('-d, --data', 'Gets the values in the storage slots from an Ethereum node.', false)
98
+ .option('-s, --storage <address>', 'The address of the contract with the storage values. This will be different from the contract with the code if a proxy contract is used. This is not needed if `fileFolderAddress` is an address and the contract is not proxied.')
99
+ .addOption(new commander_1.Option('-u, --url <url>', 'URL of the Ethereum node to get storage values if the `data` option is used.')
100
+ .env('NODE_URL')
101
+ .default('http://localhost:8545'))
102
+ .option('-bn, --block <number>', 'Block number to get the contract storage values from.', 'latest')
98
103
  .action(async (fileFolderAddress, options, command) => {
99
104
  try {
100
105
  const combinedOptions = {
@@ -102,14 +107,32 @@ program
102
107
  ...options,
103
108
  };
104
109
  let { umlClasses, contractName } = await (0, parserGeneral_1.parserUmlClasses)(fileFolderAddress, combinedOptions);
105
- contractName = combinedOptions.contractName || contractName;
106
- const storageObjects = (0, converterClasses2Storage_1.convertClasses2StorageObjects)(contractName, umlClasses);
110
+ contractName = combinedOptions.contract || contractName;
111
+ const storages = (0, converterClasses2Storage_1.convertClasses2Storages)(contractName, umlClasses);
107
112
  if ((0, regEx_1.isAddress)(fileFolderAddress)) {
108
- // The first object is the contract
109
- storageObjects[0].address = fileFolderAddress;
113
+ // The first storage is the contract
114
+ storages[0].address = fileFolderAddress;
115
+ }
116
+ debug(storages);
117
+ if (combinedOptions.data) {
118
+ let storageAddress = combinedOptions.storage;
119
+ if (storageAddress) {
120
+ if (!(0, regEx_1.isAddress)(storageAddress)) {
121
+ throw Error(`Invalid address to get storage data from "${storageAddress}"`);
122
+ }
123
+ }
124
+ else {
125
+ if (!(0, regEx_1.isAddress)(fileFolderAddress)) {
126
+ throw Error(`Can not get storage slot values if first param is not an address and the \`address\` option is not used.`);
127
+ }
128
+ storageAddress = fileFolderAddress;
129
+ }
130
+ const storage = storages.find((so) => so.name === contractName);
131
+ if (!storageAddress)
132
+ throw Error(`Could not find the "${contractName}" contract in list of parsed storages`);
133
+ await (0, converterClasses2Storage_1.addStorageValues)(combinedOptions.url, storageAddress, storage, combinedOptions.blockNumber);
110
134
  }
111
- debug(storageObjects);
112
- const dotString = (0, converterStorage2Dot_1.convertStorage2Dot)(storageObjects);
135
+ const dotString = (0, converterStorage2Dot_1.convertStorages2Dot)(storages);
113
136
  await (0, writerFiles_1.writeOutputFiles)(dotString, fileFolderAddress, contractName, combinedOptions.outputFormat, combinedOptions.outputFileName);
114
137
  }
115
138
  catch (err) {
@@ -2,6 +2,6 @@ export declare type OutputFormats = 'svg' | 'png' | 'dot' | 'all';
2
2
  export declare const writeOutputFiles: (dot: string, fileFolderAddress: string, contractName: string, outputFormat?: OutputFormats, outputFilename?: string) => Promise<void>;
3
3
  export declare function convertDot2Svg(dot: string): any;
4
4
  export declare function writeSolidity(code: string, filename?: string): void;
5
- export declare function writeDot(dot: string, filename?: string): void;
5
+ export declare function writeDot(dot: string, filename: string): void;
6
6
  export declare function writeSVG(svg: any, svgFilename?: string, outputFormats?: OutputFormats): Promise<void>;
7
7
  export declare function writePng(svg: any, filename: string): Promise<void>;
@@ -10,13 +10,6 @@ const sync_1 = __importDefault(require("@aduh95/viz.js/sync"));
10
10
  const { convert } = require('convert-svg-to-png');
11
11
  const debug = require('debug')('sol2uml');
12
12
  const writeOutputFiles = async (dot, fileFolderAddress, contractName, outputFormat = 'svg', outputFilename) => {
13
- if (outputFormat === 'dot' || outputFormat === 'all') {
14
- writeDot(dot, outputFilename);
15
- // No need to continue if only generating a dot file
16
- if (outputFormat === 'dot') {
17
- return;
18
- }
19
- }
20
13
  // If all output then extension is svg
21
14
  const outputExt = outputFormat === 'all' ? 'svg' : outputFormat;
22
15
  if (!outputFilename) {
@@ -36,6 +29,13 @@ const writeOutputFiles = async (dot, fileFolderAddress, contractName, outputForm
36
29
  }
37
30
  catch (err) { } // we can ignore errors as it just means outputFilename does not exist yet
38
31
  }
32
+ if (outputFormat === 'dot' || outputFormat === 'all') {
33
+ writeDot(dot, outputFilename);
34
+ // No need to continue if only generating a dot file
35
+ if (outputFormat === 'dot') {
36
+ return;
37
+ }
38
+ }
39
39
  const svg = convertDot2Svg(dot);
40
40
  if (outputFormat === 'svg' || outputFormat === 'all') {
41
41
  await writeSVG(svg, outputFilename, outputFormat);
@@ -73,18 +73,16 @@ function writeSolidity(code, filename = 'solidity') {
73
73
  });
74
74
  }
75
75
  exports.writeSolidity = writeSolidity;
76
- function writeDot(dot, filename = 'classDiagram.dot') {
77
- const extension = path_1.default.extname(filename);
78
- const outputFile = extension === '.dot' ? filename : filename + '.dot';
79
- debug(`About to write Dot file to ${outputFile}`);
80
- (0, fs_1.writeFile)(outputFile, dot, (err) => {
76
+ function writeDot(dot, filename) {
77
+ debug(`About to write Dot file to ${filename}`);
78
+ (0, fs_1.writeFile)(filename, dot, (err) => {
81
79
  if (err) {
82
- throw new Error(`Failed to write Dot file to ${outputFile}`, {
80
+ throw new Error(`Failed to write Dot file to ${filename}`, {
83
81
  cause: err,
84
82
  });
85
83
  }
86
84
  else {
87
- console.log(`Dot file written to ${outputFile}`);
85
+ console.log(`Dot file written to ${filename}`);
88
86
  }
89
87
  });
90
88
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sol2uml",
3
- "version": "2.0.5",
3
+ "version": "2.1.0",
4
4
  "description": "Unified Modeling Language (UML) class diagram generator for Solidity contracts",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./lib/index.d.ts",
@@ -25,6 +25,7 @@
25
25
  "commander": "^9.4.0",
26
26
  "convert-svg-to-png": "^0.6.4",
27
27
  "debug": "^4.3.4",
28
+ "ethers": "^5.6.9",
28
29
  "js-graph-algorithms": "^1.0.18",
29
30
  "klaw": "^4.0.1"
30
31
  },