@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.
- package/artifacts/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.dbg.json +1 -1
- package/artifacts/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol/BeaconProxy.dbg.json +1 -1
- package/artifacts/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.dbg.json +1 -1
- package/artifacts/build-info.json +39 -33
- package/dist/error.d.ts +1 -1
- package/dist/error.d.ts.map +1 -1
- package/dist/levenshtein.d.ts +4 -4
- package/dist/levenshtein.d.ts.map +1 -1
- package/dist/manifest.js +3 -3
- package/dist/manifest.js.map +1 -1
- package/dist/scripts/migrate-oz-cli-project.d.ts +1 -1
- package/dist/scripts/migrate-oz-cli-project.d.ts.map +1 -1
- package/dist/solc-api.d.ts +1 -1
- package/dist/solc-api.d.ts.map +1 -1
- package/dist/src-decoder.d.ts +1 -1
- package/dist/src-decoder.d.ts.map +1 -1
- package/dist/storage/compare.d.ts +9 -7
- package/dist/storage/compare.d.ts.map +1 -1
- package/dist/storage/compare.js +29 -4
- package/dist/storage/compare.js.map +1 -1
- package/dist/storage/layout.d.ts +4 -4
- package/dist/storage/layout.d.ts.map +1 -1
- package/dist/storage/report-retype-from-contract.test.d.ts +2 -0
- package/dist/storage/report-retype-from-contract.test.d.ts.map +1 -0
- package/dist/storage/report-retype-from-contract.test.js +148 -0
- package/dist/storage/report-retype-from-contract.test.js.map +1 -0
- package/dist/storage/report.js +8 -5
- package/dist/storage/report.js.map +1 -1
- package/dist/storage-memory-0.5.test.d.ts +2 -0
- package/dist/storage-memory-0.5.test.d.ts.map +1 -0
- package/dist/storage-memory-0.5.test.js +74 -0
- package/dist/storage-memory-0.5.test.js.map +1 -0
- package/dist/utils/deep-array.d.ts +1 -1
- package/dist/utils/deep-array.d.ts.map +1 -1
- package/dist/validate/compat.d.ts +4 -4
- package/dist/validate/compat.d.ts.map +1 -1
- package/dist/validate/data.d.ts +4 -4
- package/dist/validate/data.d.ts.map +1 -1
- package/dist/validate/run.d.ts +2 -2
- package/dist/validate/run.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/storage/compare.ts +35 -3
- package/src/storage/report-retype-from-contract.test.ts +169 -0
- package/src/storage/report.ts +9 -5
- package/src/storage-memory-0.5.test.ts +89 -0
- package/src/storage-memory-0.5.test.ts.md +175 -0
- package/src/storage-memory-0.5.test.ts.snap +0 -0
package/dist/validate/data.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { ValidationRunData } from './run';
|
|
2
|
-
|
|
3
|
-
|
|
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
|
|
9
|
-
export
|
|
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,
|
|
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"}
|
package/dist/validate/run.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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,
|
|
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.
|
|
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": "
|
|
61
|
+
"gitHead": "56dd6059cc916a95378d9e5893306fbb9020f071"
|
|
62
62
|
}
|
package/src/storage/compare.ts
CHANGED
|
@@ -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
|
|
172
|
-
|
|
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 (
|
|
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
|
+
});
|
package/src/storage/report.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
|
Binary file
|