@openzeppelin/upgrades-core 1.20.4 → 1.20.6

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.
Files changed (47) hide show
  1. package/artifacts/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.dbg.json +1 -1
  2. package/artifacts/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol/BeaconProxy.dbg.json +1 -1
  3. package/artifacts/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.dbg.json +1 -1
  4. package/artifacts/build-info.json +39 -33
  5. package/dist/error.d.ts +1 -1
  6. package/dist/error.d.ts.map +1 -1
  7. package/dist/levenshtein.d.ts +4 -4
  8. package/dist/levenshtein.d.ts.map +1 -1
  9. package/dist/manifest.js +3 -3
  10. package/dist/manifest.js.map +1 -1
  11. package/dist/scripts/migrate-oz-cli-project.d.ts +1 -1
  12. package/dist/scripts/migrate-oz-cli-project.d.ts.map +1 -1
  13. package/dist/solc-api.d.ts +1 -1
  14. package/dist/solc-api.d.ts.map +1 -1
  15. package/dist/src-decoder.d.ts +1 -1
  16. package/dist/src-decoder.d.ts.map +1 -1
  17. package/dist/storage/compare.d.ts +9 -7
  18. package/dist/storage/compare.d.ts.map +1 -1
  19. package/dist/storage/compare.js +29 -4
  20. package/dist/storage/compare.js.map +1 -1
  21. package/dist/storage/layout.d.ts +4 -4
  22. package/dist/storage/layout.d.ts.map +1 -1
  23. package/dist/storage/report-retype-from-contract.test.d.ts +2 -0
  24. package/dist/storage/report-retype-from-contract.test.d.ts.map +1 -0
  25. package/dist/storage/report-retype-from-contract.test.js +148 -0
  26. package/dist/storage/report-retype-from-contract.test.js.map +1 -0
  27. package/dist/storage/report.js +8 -5
  28. package/dist/storage/report.js.map +1 -1
  29. package/dist/storage-memory-0.5.test.d.ts +2 -0
  30. package/dist/storage-memory-0.5.test.d.ts.map +1 -0
  31. package/dist/storage-memory-0.5.test.js +74 -0
  32. package/dist/storage-memory-0.5.test.js.map +1 -0
  33. package/dist/utils/deep-array.d.ts +1 -1
  34. package/dist/utils/deep-array.d.ts.map +1 -1
  35. package/dist/validate/compat.d.ts +4 -4
  36. package/dist/validate/compat.d.ts.map +1 -1
  37. package/dist/validate/data.d.ts +4 -4
  38. package/dist/validate/data.d.ts.map +1 -1
  39. package/dist/validate/run.d.ts +2 -2
  40. package/dist/validate/run.d.ts.map +1 -1
  41. package/package.json +2 -2
  42. package/src/storage/compare.ts +35 -3
  43. package/src/storage/report-retype-from-contract.test.ts +169 -0
  44. package/src/storage/report.ts +9 -5
  45. package/src/storage-memory-0.5.test.ts +89 -0
  46. package/src/storage-memory-0.5.test.ts.md +175 -0
  47. package/src/storage-memory-0.5.test.ts.snap +0 -0
@@ -1,12 +1,12 @@
1
1
  import { ValidationRunData } from './run';
2
- declare type ValidationDataV1 = ValidationRunData;
3
- declare type ValidationDataV2 = ValidationRunData[];
2
+ type ValidationDataV1 = ValidationRunData;
3
+ type ValidationDataV2 = ValidationRunData[];
4
4
  interface ValidationDataV3 {
5
5
  version: '3' | '3.1' | '3.2' | '3.3' | '3.4';
6
6
  log: ValidationRunData[];
7
7
  }
8
- export declare type ValidationDataCurrent = ValidationDataV3;
9
- export declare type ValidationData = ValidationDataV1 | ValidationDataV2 | ValidationDataV3;
8
+ export type ValidationDataCurrent = ValidationDataV3;
9
+ export type ValidationData = ValidationDataV1 | ValidationDataV2 | ValidationDataV3;
10
10
  export declare function normalizeValidationData(data: ValidationData): ValidationDataCurrent;
11
11
  export declare function isCurrentValidationData(data: ValidationData, exact?: boolean): data is ValidationDataCurrent;
12
12
  export declare function concatRunData(newRunData: ValidationRunData, previousData?: ValidationDataCurrent): ValidationDataCurrent;
@@ -1 +1 @@
1
- {"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../../src/validate/data.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAE1C,aAAK,gBAAgB,GAAG,iBAAiB,CAAC;AAE1C,aAAK,gBAAgB,GAAG,iBAAiB,EAAE,CAAC;AAK5C,UAAU,gBAAgB;IACxB,OAAO,EAAE,GAAG,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;IAC7C,GAAG,EAAE,iBAAiB,EAAE,CAAC;CAC1B;AAED,oBAAY,qBAAqB,GAAG,gBAAgB,CAAC;AAErD,oBAAY,cAAc,GAAG,gBAAgB,GAAG,gBAAgB,GAAG,gBAAgB,CAAC;AAEpF,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,cAAc,GAAG,qBAAqB,CAQnF;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,UAAO,GAAG,IAAI,IAAI,qBAAqB,CAczG;AAED,wBAAgB,aAAa,CAC3B,UAAU,EAAE,iBAAiB,EAC7B,YAAY,CAAC,EAAE,qBAAqB,GACnC,qBAAqB,CAKvB"}
1
+ {"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../../src/validate/data.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAE1C,KAAK,gBAAgB,GAAG,iBAAiB,CAAC;AAE1C,KAAK,gBAAgB,GAAG,iBAAiB,EAAE,CAAC;AAK5C,UAAU,gBAAgB;IACxB,OAAO,EAAE,GAAG,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;IAC7C,GAAG,EAAE,iBAAiB,EAAE,CAAC;CAC1B;AAED,MAAM,MAAM,qBAAqB,GAAG,gBAAgB,CAAC;AAErD,MAAM,MAAM,cAAc,GAAG,gBAAgB,GAAG,gBAAgB,GAAG,gBAAgB,CAAC;AAEpF,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,cAAc,GAAG,qBAAqB,CAQnF;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,UAAO,GAAG,IAAI,IAAI,qBAAqB,CAczG;AAED,wBAAgB,aAAa,CAC3B,UAAU,EAAE,iBAAiB,EAC7B,YAAY,CAAC,EAAE,qBAAqB,GACnC,qBAAqB,CAKvB"}
@@ -3,7 +3,7 @@ import { SrcDecoder } from '../src-decoder';
3
3
  import { Version } from '../version';
4
4
  import { LinkReference } from '../link-refs';
5
5
  import { StorageLayout } from '../storage/layout';
6
- export declare type ValidationRunData = Record<string, ContractValidation>;
6
+ export type ValidationRunData = Record<string, ContractValidation>;
7
7
  export interface ContractValidation {
8
8
  version?: Version;
9
9
  src: string;
@@ -16,7 +16,7 @@ export interface ContractValidation {
16
16
  solcVersion?: string;
17
17
  }
18
18
  declare const errorKinds: readonly ["state-variable-assignment", "state-variable-immutable", "external-library-linking", "struct-definition", "enum-definition", "constructor", "delegatecall", "selfdestruct", "missing-public-upgradeto"];
19
- export declare type ValidationError = ValidationErrorConstructor | ValidationErrorOpcode | ValidationErrorWithName | ValidationErrorUpgradeability;
19
+ export type ValidationError = ValidationErrorConstructor | ValidationErrorOpcode | ValidationErrorWithName | ValidationErrorUpgradeability;
20
20
  interface ValidationErrorBase {
21
21
  src: string;
22
22
  kind: typeof errorKinds[number];
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/validate/run.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAgB,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAI5C,OAAO,EAAE,OAAO,EAAc,MAAM,YAAY,CAAC;AACjD,OAAO,EAAyB,aAAa,EAAE,MAAM,cAAc,CAAC;AAEpE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,oBAAY,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;AAEnE,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,MAAM,EAAE,aAAa,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,QAAA,MAAM,UAAU,mNAUN,CAAC;AAEX,oBAAY,eAAe,GACvB,0BAA0B,GAC1B,qBAAqB,GACrB,uBAAuB,GACvB,6BAA6B,CAAC;AAElC,UAAU,mBAAmB;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC;CACjC;AAED,UAAU,uBAAwB,SAAQ,mBAAmB;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EACA,2BAA2B,GAC3B,0BAA0B,GAC1B,0BAA0B,GAC1B,mBAAmB,GACnB,iBAAiB,CAAC;CACvB;AAED,UAAU,0BAA2B,SAAQ,mBAAmB;IAC9D,IAAI,EAAE,aAAa,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,qBAAsB,SAAQ,mBAAmB;IACzD,IAAI,EAAE,cAAc,GAAG,cAAc,CAAC;CACvC;AAED,UAAU,6BAA8B,SAAQ,mBAAmB;IACjE,IAAI,EAAE,0BAA0B,CAAC;CAClC;AAgDD,wBAAgB,QAAQ,CAAC,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,iBAAiB,CA0E/G"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/validate/run.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAgB,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAI5C,OAAO,EAAE,OAAO,EAAc,MAAM,YAAY,CAAC;AACjD,OAAO,EAAyB,aAAa,EAAE,MAAM,cAAc,CAAC;AAEpE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;AAEnE,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,MAAM,EAAE,aAAa,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,QAAA,MAAM,UAAU,mNAUN,CAAC;AAEX,MAAM,MAAM,eAAe,GACvB,0BAA0B,GAC1B,qBAAqB,GACrB,uBAAuB,GACvB,6BAA6B,CAAC;AAElC,UAAU,mBAAmB;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC;CACjC;AAED,UAAU,uBAAwB,SAAQ,mBAAmB;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EACA,2BAA2B,GAC3B,0BAA0B,GAC1B,0BAA0B,GAC1B,mBAAmB,GACnB,iBAAiB,CAAC;CACvB;AAED,UAAU,0BAA2B,SAAQ,mBAAmB;IAC9D,IAAI,EAAE,aAAa,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,qBAAsB,SAAQ,mBAAmB;IACzD,IAAI,EAAE,cAAc,GAAG,cAAc,CAAC;CACvC;AAED,UAAU,6BAA8B,SAAQ,mBAAmB;IACjE,IAAI,EAAE,0BAA0B,CAAC;CAClC;AAgDD,wBAAgB,QAAQ,CAAC,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,iBAAiB,CA0E/G"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openzeppelin/upgrades-core",
3
- "version": "1.20.4",
3
+ "version": "1.20.6",
4
4
  "description": "",
5
5
  "repository": "https://github.com/OpenZeppelin/openzeppelin-upgrades/tree/master/packages/core",
6
6
  "license": "MIT",
@@ -58,5 +58,5 @@
58
58
  "proper-lockfile": "^4.1.1",
59
59
  "solidity-ast": "^0.4.15"
60
60
  },
61
- "gitHead": "7f39ba7df93a3d5d9e7ecfe905cd09eb1af82b3f"
61
+ "gitHead": "56dd6059cc916a95378d9e5893306fbb9020f071"
62
62
  }
@@ -168,8 +168,9 @@ export class StorageLayoutComparator {
168
168
  original.label !== updated.renamedFrom &&
169
169
  (updated.label !== original.label ||
170
170
  (updated.renamedFrom !== undefined && updated.renamedFrom !== original.renamedFrom));
171
- const retypedFromOriginal = original.type.item.label === updated.retypedFrom?.trim();
172
- const typeChange = !retypedFromOriginal && this.getTypeChange(original.type, updated.type, { allowAppend: false });
171
+ const typeChange =
172
+ !this.isRetypedFromOriginal(original, updated) &&
173
+ this.getTypeChange(original.type, updated.type, { allowAppend: false });
173
174
  const layoutChange = this.getLayoutChange(original, updated);
174
175
 
175
176
  if (updated.retypedFrom && layoutChange && (!layoutChange.uncertain || !layoutChange.knownCompatible)) {
@@ -193,6 +194,13 @@ export class StorageLayoutComparator {
193
194
  }
194
195
  }
195
196
 
197
+ private isRetypedFromOriginal(original: StorageField, updated: StorageField) {
198
+ const originalLabel = stripContractSubstrings(original.type.item.label);
199
+ const updatedLabel = stripContractSubstrings(updated.retypedFrom?.trim());
200
+
201
+ return originalLabel === updatedLabel;
202
+ }
203
+
196
204
  getLayoutChange(original: StorageField, updated: StorageField): LayoutChange | undefined {
197
205
  if (hasLayout(original) && hasLayout(updated)) {
198
206
  const change = <T>(from: T, to: T) => (from === to ? undefined : { from, to });
@@ -244,7 +252,16 @@ export class StorageLayoutComparator {
244
252
  return this.getVisibilityChange(original, updated);
245
253
  }
246
254
 
247
- if (original.head !== updated.head) {
255
+ if (
256
+ (original.head === 't_contract' && updated.head === 't_address') ||
257
+ (original.head === 't_address' && updated.head === 't_contract')
258
+ ) {
259
+ // changing contract to address or address to contract
260
+ // equivalent to just addresses
261
+ return undefined;
262
+ }
263
+
264
+ if (normalizeMemoryPointer(original.head) !== normalizeMemoryPointer(updated.head)) {
248
265
  return { kind: 'obvious mismatch', original, updated };
249
266
  }
250
267
 
@@ -256,6 +273,7 @@ export class StorageLayoutComparator {
256
273
 
257
274
  switch (original.head) {
258
275
  case 't_contract':
276
+ // changing contract to contract
259
277
  // no storage layout errors can be introduced here since it is just an address
260
278
  return undefined;
261
279
 
@@ -389,3 +407,17 @@ export class StorageLayoutComparator {
389
407
  function enumSize(memberCount: number): number {
390
408
  return Math.ceil(Math.log2(Math.max(2, memberCount)) / 8);
391
409
  }
410
+
411
+ export function stripContractSubstrings(label?: string) {
412
+ if (label !== undefined) {
413
+ return label.replace(/\b(contract|struct|enum) /g, '');
414
+ }
415
+ }
416
+
417
+ /**
418
+ * Some versions of Solidity use type ids with _memory_ptr suffix while other versions use _memory suffix.
419
+ * Normalize these for type comparison purposes only.
420
+ */
421
+ function normalizeMemoryPointer(typeIdentifier: string): string {
422
+ return typeIdentifier.replace(/_memory_ptr\b/g, '_memory');
423
+ }
@@ -0,0 +1,169 @@
1
+ import _test, { TestFn } from 'ava';
2
+ import { ContractDefinition } from 'solidity-ast';
3
+ import { ASTDereferencer, findAll } from 'solidity-ast/utils';
4
+ import { artifacts } from 'hardhat';
5
+ import { astDereferencer } from '../ast-dereferencer';
6
+ import { extractStorageLayout } from './extract';
7
+ import { StorageLayoutComparator, stripContractSubstrings } from './compare';
8
+ import { StorageLayout, getDetailedLayout } from './layout';
9
+ import { getStorageUpgradeErrors } from '.';
10
+
11
+ interface Context {
12
+ extractStorageLayout: (contract: string) => ReturnType<typeof extractStorageLayout>;
13
+ }
14
+
15
+ const test = _test as TestFn<Context>;
16
+
17
+ const dummyDecodeSrc = () => 'file.sol:1';
18
+ const testContracts = [
19
+ 'contracts/test/RetypeFromContract.sol:RetypeContractToUint160V1',
20
+ 'contracts/test/RetypeFromContract.sol:RetypeContractToUint160V2',
21
+ 'contracts/test/RetypeFromContract.sol:RetypeUint160ToContractV1',
22
+ 'contracts/test/RetypeFromContract.sol:RetypeUint160ToContractV2',
23
+ 'contracts/test/RetypeFromContract.sol:RetypeContractToUint160MappingV1',
24
+ 'contracts/test/RetypeFromContract.sol:RetypeContractToUint160MappingV2',
25
+ 'contracts/test/RetypeFromContract.sol:RetypeUint160ToContractMappingV1',
26
+ 'contracts/test/RetypeFromContract.sol:RetypeUint160ToContractMappingV2',
27
+ 'contracts/test/RetypeFromContract.sol:ImplicitRetypeV1',
28
+ 'contracts/test/RetypeFromContract.sol:ImplicitRetypeV2',
29
+ 'contracts/test/RetypeFromContract.sol:ImplicitRetypeMappingV1',
30
+ 'contracts/test/RetypeFromContract.sol:ImplicitRetypeMappingV2',
31
+ 'contracts/test/RetypeFromContract.sol:RetypeStructV1',
32
+ 'contracts/test/RetypeFromContract.sol:RetypeStructV2',
33
+ 'contracts/test/RetypeFromContract.sol:RetypeStructV2Bad',
34
+ 'contracts/test/RetypeFromContract.sol:RetypeEnumV1',
35
+ 'contracts/test/RetypeFromContract.sol:RetypeEnumV2',
36
+ 'contracts/test/RetypeFromContract.sol:RetypeEnumV2Bad',
37
+ ];
38
+
39
+ test.before(async t => {
40
+ const contracts: Record<string, ContractDefinition> = {};
41
+ const deref: Record<string, ASTDereferencer> = {};
42
+ const storageLayout: Record<string, StorageLayout> = {};
43
+ for (const contract of testContracts) {
44
+ const buildInfo = await artifacts.getBuildInfo(contract);
45
+ if (buildInfo === undefined) {
46
+ throw new Error(`Build info not found for contract ${contract}`);
47
+ }
48
+ const solcOutput = buildInfo.output;
49
+ for (const def of findAll('ContractDefinition', solcOutput.sources['contracts/test/RetypeFromContract.sol'].ast)) {
50
+ contracts[def.name] = def;
51
+ deref[def.name] = astDereferencer(solcOutput);
52
+ storageLayout[def.name] = (
53
+ solcOutput.contracts['contracts/test/RetypeFromContract.sol'][def.name] as any
54
+ ).storageLayout;
55
+ }
56
+ }
57
+
58
+ t.context.extractStorageLayout = name =>
59
+ extractStorageLayout(contracts[name], dummyDecodeSrc, deref[name], storageLayout[name]);
60
+ });
61
+
62
+ function getReport(original: StorageLayout, updated: StorageLayout) {
63
+ const originalDetailed = getDetailedLayout(original);
64
+ const updatedDetailed = getDetailedLayout(updated);
65
+ const comparator = new StorageLayoutComparator();
66
+ return comparator.compareLayouts(originalDetailed, updatedDetailed);
67
+ }
68
+
69
+ test('retype contract to uint160', t => {
70
+ const v1 = t.context.extractStorageLayout('RetypeContractToUint160V1');
71
+ const v2 = t.context.extractStorageLayout('RetypeContractToUint160V2');
72
+ const report = getReport(v1, v2);
73
+ t.true(report.ok, report.explain());
74
+ });
75
+
76
+ test('retype uint160 to contract', t => {
77
+ const v1 = t.context.extractStorageLayout('RetypeUint160ToContractV1');
78
+ const v2 = t.context.extractStorageLayout('RetypeUint160ToContractV2');
79
+ const report = getReport(v1, v2);
80
+ t.true(report.ok, report.explain());
81
+ });
82
+
83
+ test('retype contract to uint160 mapping', t => {
84
+ const v1 = t.context.extractStorageLayout('RetypeContractToUint160MappingV1');
85
+ const v2 = t.context.extractStorageLayout('RetypeContractToUint160MappingV2');
86
+ const report = getReport(v1, v2);
87
+ t.true(report.ok, report.explain());
88
+ });
89
+
90
+ test('retype uint160 to contract mapping', t => {
91
+ const v1 = t.context.extractStorageLayout('RetypeUint160ToContractMappingV1');
92
+ const v2 = t.context.extractStorageLayout('RetypeUint160ToContractMappingV2');
93
+ const report = getReport(v1, v2);
94
+ t.true(report.ok, report.explain());
95
+ });
96
+
97
+ test('implicit retype', t => {
98
+ const v1 = t.context.extractStorageLayout('ImplicitRetypeV1');
99
+ const v2 = t.context.extractStorageLayout('ImplicitRetypeV2');
100
+ const report = getReport(v1, v2);
101
+ t.true(report.ok, report.explain());
102
+ });
103
+
104
+ test('implicit retype mapping', t => {
105
+ const v1 = t.context.extractStorageLayout('ImplicitRetypeMappingV1');
106
+ const v2 = t.context.extractStorageLayout('ImplicitRetypeMappingV2');
107
+ const report = getReport(v1, v2);
108
+ t.true(report.ok, report.explain());
109
+ });
110
+
111
+ test('strip contract substrings', t => {
112
+ t.is(stripContractSubstrings(undefined), undefined);
113
+ t.is(stripContractSubstrings('address'), 'address');
114
+ t.is(stripContractSubstrings('CustomContract'), 'CustomContract');
115
+ t.is(stripContractSubstrings('contract CustomContract'), 'CustomContract');
116
+ t.is(stripContractSubstrings('mapping(uint8 => CustomContract)'), 'mapping(uint8 => CustomContract)');
117
+ t.is(stripContractSubstrings('mapping(uint8 => contract CustomContract)'), 'mapping(uint8 => CustomContract)');
118
+ t.is(stripContractSubstrings('mapping(contract CustomContract => uint8)'), 'mapping(CustomContract => uint8)');
119
+ t.is(stripContractSubstrings('mapping(contract A => contract B)'), 'mapping(A => B)');
120
+ t.is(stripContractSubstrings('mapping(Substringcontract => address)'), 'mapping(Substringcontract => address)');
121
+ t.is(
122
+ stripContractSubstrings('mapping(contract Substringcontract => address)'),
123
+ 'mapping(Substringcontract => address)',
124
+ );
125
+ t.is(stripContractSubstrings('Mystruct'), 'Mystruct');
126
+ t.is(stripContractSubstrings('struct Mystruct'), 'Mystruct');
127
+ t.is(stripContractSubstrings('Myenum'), 'Myenum');
128
+ t.is(stripContractSubstrings('enum Myenum'), 'Myenum');
129
+ });
130
+
131
+ test('retype from struct', t => {
132
+ const v1 = t.context.extractStorageLayout('RetypeStructV1');
133
+ const v2 = t.context.extractStorageLayout('RetypeStructV2');
134
+ const report = getReport(v1, v2);
135
+ t.true(report.ok, report.explain());
136
+ });
137
+
138
+ test('bad retype from struct', t => {
139
+ const v1 = t.context.extractStorageLayout('RetypeStructV1');
140
+ const v2 = t.context.extractStorageLayout('RetypeStructV2Bad');
141
+ t.like(getStorageUpgradeErrors(v1, v2), {
142
+ length: 1,
143
+ 0: {
144
+ kind: 'layoutchange',
145
+ original: { label: 'x' },
146
+ updated: { label: 'x' },
147
+ },
148
+ });
149
+ });
150
+
151
+ test('retype from enum', t => {
152
+ const v1 = t.context.extractStorageLayout('RetypeEnumV1');
153
+ const v2 = t.context.extractStorageLayout('RetypeEnumV2');
154
+ const report = getReport(v1, v2);
155
+ t.true(report.ok, report.explain());
156
+ });
157
+
158
+ test('bad retype from enum', t => {
159
+ const v1 = t.context.extractStorageLayout('RetypeEnumV1');
160
+ const v2 = t.context.extractStorageLayout('RetypeEnumV2Bad');
161
+ t.like(getStorageUpgradeErrors(v1, v2), {
162
+ length: 1,
163
+ 0: {
164
+ kind: 'layoutchange',
165
+ original: { label: 'x' },
166
+ updated: { label: 'x' },
167
+ },
168
+ });
169
+ });
@@ -73,12 +73,10 @@ function getExpectedGapSize(original: StorageField, updated: StorageField) {
73
73
  return expectedSizeBytes / bytesPerItem;
74
74
  }
75
75
 
76
- function suggestGapSize(original: StorageField, updated: StorageField): string {
76
+ function suggestGapSize(original: StorageField, updated: StorageField): string | undefined {
77
77
  const expectedSize = getExpectedGapSize(original, updated);
78
78
  if (expectedSize !== undefined) {
79
79
  return `Set __gap array to size ${expectedSize}`;
80
- } else {
81
- return '';
82
80
  }
83
81
  }
84
82
 
@@ -97,7 +95,10 @@ function explainStorageOperation(op: StorageOperation<StorageField>, ctx: Storag
97
95
  : [];
98
96
  const hints = [];
99
97
  if (isGap(op.original) && op.change.kind === 'array shrink') {
100
- hints.push(suggestGapSize(op.original, op.updated));
98
+ const suggestion = suggestGapSize(op.original, op.updated);
99
+ if (suggestion) {
100
+ hints.push(suggestion);
101
+ }
101
102
  }
102
103
  return printWithHints({
103
104
  title: `Upgraded ${label(op.updated)} to an incompatible type\n` + itemize(basic, ...details),
@@ -122,7 +123,10 @@ function explainStorageOperation(op: StorageOperation<StorageField>, ctx: Storag
122
123
  const hints = [];
123
124
 
124
125
  if (isGap(op.original)) {
125
- hints.push(suggestGapSize(op.original, op.updated));
126
+ const suggestion = suggestGapSize(op.original, op.updated);
127
+ if (suggestion) {
128
+ hints.push(suggestion);
129
+ }
126
130
  }
127
131
 
128
132
  return printWithHints({ title, hints });
@@ -0,0 +1,89 @@
1
+ import _test, { TestFn } from 'ava';
2
+ import { findAll } from 'solidity-ast/utils';
3
+ import { artifacts } from 'hardhat';
4
+
5
+ import { SolcOutput } from './solc-api';
6
+ import { astDereferencer } from './ast-dereferencer';
7
+ import { getStorageUpgradeErrors } from './storage';
8
+ import { extractStorageLayout } from './storage/extract';
9
+ import { BuildInfo } from 'hardhat/types';
10
+ import { stabilizeStorageLayout } from './utils/stabilize-layout';
11
+
12
+ interface Context {
13
+ extractStorageLayout: (
14
+ contract: string,
15
+ contractName?: string,
16
+ withLayout?: boolean,
17
+ ) => Promise<ReturnType<typeof extractStorageLayout>>;
18
+ }
19
+
20
+ const test = _test as TestFn<Context>;
21
+
22
+ test.before(async t => {
23
+ const buildInfoCache: Record<string, BuildInfo | undefined> = {};
24
+ t.context.extractStorageLayout = async (contractFile, contractName, withLayout = true) => {
25
+ const expectedContract = contractName ?? contractFile;
26
+
27
+ const [file] = contractFile.split('_');
28
+ const source = `contracts/test/${file}.sol`;
29
+ const buildInfo = (buildInfoCache[source] ??= await artifacts.getBuildInfo(`${source}:${expectedContract}`));
30
+ if (buildInfo === undefined) {
31
+ throw new Error(`Build info for ${source} not found`);
32
+ }
33
+ const solcOutput: SolcOutput = buildInfo.output;
34
+ for (const def of findAll('ContractDefinition', solcOutput.sources[source].ast)) {
35
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
36
+ const layout = solcOutput.contracts[source][def.name].storageLayout!;
37
+ if (def.name === expectedContract) {
38
+ return extractStorageLayout(def, dummyDecodeSrc, astDereferencer(solcOutput), withLayout ? layout : undefined);
39
+ }
40
+ }
41
+ throw new Error(`Contract ${expectedContract} not found in ${source}`);
42
+ };
43
+ });
44
+
45
+ const dummyDecodeSrc = () => 'file.sol:1';
46
+
47
+ test('memory 0.5.16', async t => {
48
+ const layout = await t.context.extractStorageLayout('Memory05');
49
+ t.snapshot(stabilizeStorageLayout(layout));
50
+ });
51
+
52
+ test('memory 0.8.9', async t => {
53
+ const layout = await t.context.extractStorageLayout('Memory08');
54
+ t.snapshot(stabilizeStorageLayout(layout));
55
+ });
56
+
57
+ test('memory - upgrade from 0.5.16 to 0.8.9', async t => {
58
+ const v1 = await t.context.extractStorageLayout('Memory05');
59
+ const v2 = await t.context.extractStorageLayout('Memory08');
60
+
61
+ const comparison = getStorageUpgradeErrors(v1, v2);
62
+ t.deepEqual(comparison, []);
63
+ });
64
+
65
+ test('memory - bad upgrade from 0.5.16 to 0.8.9', async t => {
66
+ const v1 = await t.context.extractStorageLayout('Memory05');
67
+ const v2 = await t.context.extractStorageLayout('Memory08', 'Memory08Bad');
68
+
69
+ const comparison = getStorageUpgradeErrors(v1, v2);
70
+ t.like(comparison, {
71
+ length: 2,
72
+ 0: {
73
+ kind: 'typechange',
74
+ change: {
75
+ kind: 'mapping key',
76
+ },
77
+ original: { label: 'a' },
78
+ updated: { label: 'a' },
79
+ },
80
+ 1: {
81
+ kind: 'typechange',
82
+ change: {
83
+ kind: 'mapping key',
84
+ },
85
+ original: { label: 'b' },
86
+ updated: { label: 'b' },
87
+ },
88
+ });
89
+ });
@@ -0,0 +1,175 @@
1
+ # Snapshot report for `src/storage-memory-0.5.test.ts`
2
+
3
+ The actual snapshot is saved in `storage-memory-0.5.test.ts.snap`.
4
+
5
+ Generated by [AVA](https://avajs.dev).
6
+
7
+ ## memory 0.5.16
8
+
9
+ > Snapshot 1
10
+
11
+ {
12
+ storage: [
13
+ {
14
+ contract: 'Memory05',
15
+ label: 'a',
16
+ offset: 0,
17
+ renamedFrom: undefined,
18
+ retypedFrom: undefined,
19
+ slot: '0',
20
+ src: 'file.sol:1',
21
+ type: 't_mapping(t_string_memory,t_address)',
22
+ },
23
+ {
24
+ contract: 'Memory05',
25
+ label: 'b',
26
+ offset: 0,
27
+ renamedFrom: undefined,
28
+ retypedFrom: undefined,
29
+ slot: '1',
30
+ src: 'file.sol:1',
31
+ type: 't_mapping(t_bytes_memory,t_address)',
32
+ },
33
+ ],
34
+ types: [
35
+ [
36
+ 't_address',
37
+ {
38
+ label: 'address',
39
+ members: undefined,
40
+ numberOfBytes: '20',
41
+ },
42
+ ],
43
+ [
44
+ 't_bytes_memory',
45
+ {
46
+ label: 'bytes',
47
+ members: undefined,
48
+ numberOfBytes: '32',
49
+ },
50
+ ],
51
+ [
52
+ 't_mapping(t_bytes_memory,t_address)',
53
+ {
54
+ label: 'mapping(bytes => address)',
55
+ members: undefined,
56
+ numberOfBytes: '32',
57
+ },
58
+ ],
59
+ [
60
+ 't_mapping(t_string_memory,t_address)',
61
+ {
62
+ label: 'mapping(string => address)',
63
+ members: undefined,
64
+ numberOfBytes: '32',
65
+ },
66
+ ],
67
+ [
68
+ 't_string_memory',
69
+ {
70
+ label: 'string',
71
+ members: undefined,
72
+ numberOfBytes: '32',
73
+ },
74
+ ],
75
+ [
76
+ 't_string_storage',
77
+ {
78
+ label: 'string',
79
+ members: undefined,
80
+ },
81
+ ],
82
+ [
83
+ 't_bytes_storage',
84
+ {
85
+ label: 'bytes',
86
+ members: undefined,
87
+ },
88
+ ],
89
+ ],
90
+ }
91
+
92
+ ## memory 0.8.9
93
+
94
+ > Snapshot 1
95
+
96
+ {
97
+ storage: [
98
+ {
99
+ contract: 'Memory08',
100
+ label: 'a',
101
+ offset: 0,
102
+ renamedFrom: undefined,
103
+ retypedFrom: undefined,
104
+ slot: '0',
105
+ src: 'file.sol:1',
106
+ type: 't_mapping(t_string_memory_ptr,t_address)',
107
+ },
108
+ {
109
+ contract: 'Memory08',
110
+ label: 'b',
111
+ offset: 0,
112
+ renamedFrom: undefined,
113
+ retypedFrom: undefined,
114
+ slot: '1',
115
+ src: 'file.sol:1',
116
+ type: 't_mapping(t_bytes_memory_ptr,t_address)',
117
+ },
118
+ ],
119
+ types: [
120
+ [
121
+ 't_address',
122
+ {
123
+ label: 'address',
124
+ members: undefined,
125
+ numberOfBytes: '20',
126
+ },
127
+ ],
128
+ [
129
+ 't_bytes_memory_ptr',
130
+ {
131
+ label: 'bytes',
132
+ members: undefined,
133
+ numberOfBytes: '32',
134
+ },
135
+ ],
136
+ [
137
+ 't_mapping(t_bytes_memory_ptr,t_address)',
138
+ {
139
+ label: 'mapping(bytes => address)',
140
+ members: undefined,
141
+ numberOfBytes: '32',
142
+ },
143
+ ],
144
+ [
145
+ 't_mapping(t_string_memory_ptr,t_address)',
146
+ {
147
+ label: 'mapping(string => address)',
148
+ members: undefined,
149
+ numberOfBytes: '32',
150
+ },
151
+ ],
152
+ [
153
+ 't_string_memory_ptr',
154
+ {
155
+ label: 'string',
156
+ members: undefined,
157
+ numberOfBytes: '32',
158
+ },
159
+ ],
160
+ [
161
+ 't_string_storage',
162
+ {
163
+ label: 'string',
164
+ members: undefined,
165
+ },
166
+ ],
167
+ [
168
+ 't_bytes_storage',
169
+ {
170
+ label: 'bytes',
171
+ members: undefined,
172
+ },
173
+ ],
174
+ ],
175
+ }