@openzeppelin/upgrades-core 1.17.0 → 1.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/levenshtein.d.ts +4 -1
- package/dist/levenshtein.d.ts.map +1 -1
- package/dist/levenshtein.js +1 -1
- package/dist/levenshtein.js.map +1 -1
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +2 -0
- package/dist/provider.js.map +1 -1
- package/dist/storage/compare.d.ts +21 -3
- package/dist/storage/compare.d.ts.map +1 -1
- package/dist/storage/compare.js +80 -17
- package/dist/storage/compare.js.map +1 -1
- package/dist/storage/gap.d.ts +18 -0
- package/dist/storage/gap.d.ts.map +1 -0
- package/dist/storage/gap.js +29 -0
- package/dist/storage/gap.js.map +1 -0
- package/dist/storage/report-gap.test.d.ts +2 -0
- package/dist/storage/report-gap.test.d.ts.map +1 -0
- package/dist/storage/report-gap.test.js +106 -0
- package/dist/storage/report-gap.test.js.map +1 -0
- package/dist/storage/report-rename-retype.test.js +24 -0
- package/dist/storage/report-rename-retype.test.js.map +1 -1
- package/dist/storage/report.d.ts +1 -1
- package/dist/storage/report.d.ts.map +1 -1
- package/dist/storage/report.js +59 -10
- package/dist/storage/report.js.map +1 -1
- package/dist/storage.test.js +421 -2
- package/dist/storage.test.js.map +1 -1
- package/dist/usage-error.d.ts +9 -0
- package/dist/usage-error.d.ts.map +1 -1
- package/dist/usage-error.js +16 -1
- package/dist/usage-error.js.map +1 -1
- package/dist/validate/data.js +25 -5
- package/dist/validate/data.js.map +1 -1
- package/dist/validate/index.d.ts +1 -1
- package/dist/validate/index.d.ts.map +1 -1
- package/dist/validate/index.js.map +1 -1
- package/dist/validate/overrides.d.ts +34 -3
- package/dist/validate/overrides.d.ts.map +1 -1
- package/dist/validate/overrides.js.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +2 -0
- package/src/levenshtein.ts +4 -2
- package/src/provider.ts +2 -0
- package/src/storage/compare.ts +88 -18
- package/src/storage/gap.ts +26 -0
- package/src/storage/report-gap.test.ts +119 -0
- package/src/storage/report-gap.test.ts.md +59 -0
- package/src/storage/report-gap.test.ts.snap +0 -0
- package/src/storage/report-rename-retype.test.ts +27 -0
- package/src/storage/report-rename-retype.test.ts.md +7 -0
- package/src/storage/report-rename-retype.test.ts.snap +0 -0
- package/src/storage/report.test.ts.md +1 -4
- package/src/storage/report.test.ts.snap +0 -0
- package/src/storage/report.ts +83 -12
- package/src/storage.test.ts +466 -2
- package/src/storage.test.ts.snap +0 -0
- package/src/usage-error.ts +22 -0
- package/src/validate/data.ts +1 -1
- package/src/validate/index.ts +1 -1
- package/src/validate/overrides.ts +39 -3
package/dist/validate/data.js
CHANGED
|
@@ -1,10 +1,30 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
4
24
|
};
|
|
5
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
26
|
exports.concatRunData = exports.isCurrentValidationData = exports.normalizeValidationData = void 0;
|
|
7
|
-
const
|
|
27
|
+
const versions = __importStar(require("compare-versions"));
|
|
8
28
|
const currentMajor = '3';
|
|
9
29
|
const currentVersion = '3.2';
|
|
10
30
|
function normalizeValidationData(data) {
|
|
@@ -26,12 +46,12 @@ function isCurrentValidationData(data, exact = true) {
|
|
|
26
46
|
else if (!('version' in data)) {
|
|
27
47
|
return false;
|
|
28
48
|
}
|
|
29
|
-
else if (typeof data.version === 'string' &&
|
|
49
|
+
else if (typeof data.version === 'string' && versions.validate(data.version)) {
|
|
30
50
|
if (exact) {
|
|
31
51
|
return data.version === currentVersion;
|
|
32
52
|
}
|
|
33
53
|
else {
|
|
34
|
-
return
|
|
54
|
+
return versions.compare(data.version, `${currentMajor}.*`, '=');
|
|
35
55
|
}
|
|
36
56
|
}
|
|
37
57
|
else {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data.js","sourceRoot":"","sources":["../../src/validate/data.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"data.js","sourceRoot":"","sources":["../../src/validate/data.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2DAA6C;AAQ7C,MAAM,YAAY,GAAG,GAAG,CAAC;AACzB,MAAM,cAAc,GAAG,KAAK,CAAC;AAW7B,SAAgB,uBAAuB,CAAC,IAAoB;IAC1D,IAAI,uBAAuB,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE;QACxC,OAAO,IAAI,CAAC;KACb;SAAM,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;QAC9B,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;KAC/C;SAAM;QACL,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;KACjD;AACH,CAAC;AARD,0DAQC;AAED,SAAgB,uBAAuB,CAAC,IAAoB,EAAE,KAAK,GAAG,IAAI;IACxE,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;QACvB,OAAO,KAAK,CAAC;KACd;SAAM,IAAI,CAAC,CAAC,SAAS,IAAI,IAAI,CAAC,EAAE;QAC/B,OAAO,KAAK,CAAC;KACd;SAAM,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;QAC9E,IAAI,KAAK,EAAE;YACT,OAAO,IAAI,CAAC,OAAO,KAAK,cAAc,CAAC;SACxC;aAAM;YACL,OAAO,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,YAAY,IAAI,EAAE,GAAG,CAAC,CAAC;SACjE;KACF;SAAM;QACL,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;KACjE;AACH,CAAC;AAdD,0DAcC;AAED,SAAgB,aAAa,CAC3B,UAA6B,EAC7B,YAAoC;IAEpC,OAAO;QACL,OAAO,EAAE,cAAc;QACvB,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,YAAY,EAAE,GAAG,IAAI,EAAE,CAAC;KAClD,CAAC;AACJ,CAAC;AARD,sCAQC"}
|
package/dist/validate/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { validate, ValidationRunData, ContractValidation } from './run';
|
|
2
|
-
export { ValidationOptions, withValidationDefaults } from './overrides';
|
|
2
|
+
export { ProxyKindOption, StandaloneValidationOptions, ValidationOptions, withValidationDefaults } from './overrides';
|
|
3
3
|
export { ValidationErrors } from './error';
|
|
4
4
|
export { RunValidation, ValidationLog, Validation, ValidationResult } from './compat';
|
|
5
5
|
export { ValidationData, ValidationDataCurrent, isCurrentValidationData, concatRunData } from './data';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/validate/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,OAAO,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/validate/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,OAAO,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,2BAA2B,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACtH,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACtF,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvG,OAAO,EACL,kBAAkB,EAClB,+BAA+B,EAC/B,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,SAAS,EACT,aAAa,EACb,cAAc,GACf,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,8BAA8B,EAAE,MAAM,UAAU,CAAC;AAG1D,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/validate/index.ts"],"names":[],"mappings":";;;AAAA,6BAAwE;AAA/D,+FAAA,QAAQ,OAAA;AACjB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/validate/index.ts"],"names":[],"mappings":";;;AAAA,6BAAwE;AAA/D,+FAAA,QAAQ,OAAA;AACjB,yCAAsH;AAA5C,mHAAA,sBAAsB,OAAA;AAChG,iCAA2C;AAAlC,yGAAA,gBAAgB,OAAA;AAEzB,+BAAuG;AAAvD,+GAAA,uBAAuB,OAAA;AAAE,qGAAA,aAAa,OAAA;AACtF,iCASiB;AARf,2GAAA,kBAAkB,OAAA;AAClB,wHAAA,+BAA+B,OAAA;AAC/B,yGAAA,gBAAgB,OAAA;AAChB,0GAAA,iBAAiB,OAAA;AACjB,4GAAA,mBAAmB,OAAA;AACnB,kGAAA,SAAS,OAAA;AACT,sGAAA,aAAa,OAAA;AACb,uGAAA,cAAc,OAAA;AAEhB,mCAA0D;AAAjD,wHAAA,8BAA8B,OAAA;AAEvC,0BAA0B;AAC1B,oCAA+C;AAAtC,sGAAA,eAAe,OAAA"}
|
|
@@ -1,13 +1,44 @@
|
|
|
1
1
|
import { ValidationError } from './run';
|
|
2
2
|
import { ProxyDeployment } from '../manifest';
|
|
3
3
|
export { silenceWarnings } from '../utils/log';
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Option for the kind of proxy that will be used.
|
|
6
|
+
*/
|
|
7
|
+
export interface ProxyKindOption {
|
|
8
|
+
/**
|
|
9
|
+
* The kind of proxy to deploy, upgrade or import, or the kind of proxy that the implementation will be used with. Defaults to `"transparent"`.
|
|
10
|
+
*/
|
|
11
|
+
kind?: ProxyDeployment['kind'];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Validation options in a standalone context (without storage layout comparisons with a previous implementation).
|
|
15
|
+
*/
|
|
16
|
+
export interface StandaloneValidationOptions extends ProxyKindOption {
|
|
17
|
+
/**
|
|
18
|
+
* @deprecated Equivalent to including "struct-definition" and "enum-definition" in unsafeAllow. No longer necessary.
|
|
19
|
+
*/
|
|
5
20
|
unsafeAllowCustomTypes?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* @deprecated Equivalent to including `"external-library-linking"` in {@link unsafeAllow}.
|
|
23
|
+
*/
|
|
6
24
|
unsafeAllowLinkedLibraries?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Selectively disable one or more validation errors.
|
|
27
|
+
*/
|
|
28
|
+
unsafeAllow?: ValidationError['kind'][];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Validation options in the context of an upgrade (with storage layout comparisions with a previous implementation).
|
|
32
|
+
*/
|
|
33
|
+
export interface ValidationOptions extends StandaloneValidationOptions {
|
|
34
|
+
/**
|
|
35
|
+
* Configure storage layout check to allow variable renaming.
|
|
36
|
+
*/
|
|
7
37
|
unsafeAllowRenames?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Upgrades the proxy or beacon without first checking for storage layout compatibility errors. This is a dangerous option meant to be used as a last resort.
|
|
40
|
+
*/
|
|
8
41
|
unsafeSkipStorageCheck?: boolean;
|
|
9
|
-
unsafeAllow?: ValidationError['kind'][];
|
|
10
|
-
kind?: ProxyDeployment['kind'];
|
|
11
42
|
}
|
|
12
43
|
export declare const ValidationErrorUnsafeMessages: Record<ValidationError['kind'], string[]>;
|
|
13
44
|
export declare function withValidationDefaults(opts: ValidationOptions): Required<ValidationOptions>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overrides.d.ts","sourceRoot":"","sources":["../../src/validate/overrides.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAI9C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"overrides.d.ts","sourceRoot":"","sources":["../../src/validate/overrides.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAI9C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,IAAI,CAAC,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,2BAA4B,SAAQ,eAAe;IAClE;;OAEG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC;;OAEG;IACH,0BAA0B,CAAC,EAAE,OAAO,CAAC;IAErC;;OAEG;IACH,WAAW,CAAC,EAAE,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC;CACzC;AAED;;GAEG;AACH,MAAM,WAAW,iBAAkB,SAAQ,2BAA2B;IACpE;;OAEG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B;;OAEG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,eAAO,MAAM,6BAA6B,EAAE,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CA0BnF,CAAC;AAEF,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,iBAAiB,GAAG,QAAQ,CAAC,iBAAiB,CAAC,CA2B3F;AAED,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,eAAe,EAAE,EACzB,IAAI,EAAE,iBAAiB,GACtB,eAAe,EAAE,CAwBnB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overrides.js","sourceRoot":"","sources":["../../src/validate/overrides.ts"],"names":[],"mappings":";;;AAEA,sCAA0C;AAE1C,0BAA0B;AAC1B,oCAA+C;AAAtC,sGAAA,eAAe,OAAA;
|
|
1
|
+
{"version":3,"file":"overrides.js","sourceRoot":"","sources":["../../src/validate/overrides.ts"],"names":[],"mappings":";;;AAEA,sCAA0C;AAE1C,0BAA0B;AAC1B,oCAA+C;AAAtC,sGAAA,eAAe,OAAA;AA+CX,QAAA,6BAA6B,GAA8C;IACtF,2BAA2B,EAAE;QAC3B,mEAAmE;QACnE,mEAAmE;KACpE;IACD,0BAA0B,EAAE,CAAC,kEAAkE,CAAC;IAChG,0BAA0B,EAAE;QAC1B,gGAAgG;QAChG,iFAAiF;KAClF;IACD,mBAAmB,EAAE;QACnB,8FAA8F;QAC9F,+EAA+E;KAChF;IACD,iBAAiB,EAAE;QACjB,0FAA0F;QAC1F,+EAA+E;KAChF;IACD,WAAW,EAAE,CAAC,qDAAqD,CAAC;IACpE,YAAY,EAAE,CAAC,sDAAsD,CAAC;IACtE,YAAY,EAAE,CAAC,sDAAsD,CAAC;IACtE,0BAA0B,EAAE;QAC1B,kFAAkF;QAClF,yFAAyF;QACzF,gFAAgF;KACjF;CACF,CAAC;AAEF,SAAgB,sBAAsB,CAAC,IAAuB;IAC5D,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC;IAC3C,MAAM,sBAAsB,GAC1B,IAAI,CAAC,sBAAsB;QAC3B,CAAC,WAAW,CAAC,QAAQ,CAAC,mBAAmB,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC,CAAC;IACzF,MAAM,0BAA0B,GAC9B,IAAI,CAAC,0BAA0B,IAAI,WAAW,CAAC,QAAQ,CAAC,0BAA0B,CAAC,CAAC;IAEtF,IAAI,sBAAsB,EAAE;QAC1B,WAAW,CAAC,IAAI,CAAC,iBAAiB,EAAE,mBAAmB,CAAC,CAAC;KAC1D;IACD,IAAI,0BAA0B,EAAE;QAC9B,WAAW,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;KAC9C;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,aAAa,CAAC;IAExC,MAAM,kBAAkB,GAAG,IAAI,CAAC,kBAAkB,IAAI,KAAK,CAAC;IAC5D,MAAM,sBAAsB,GAAG,IAAI,CAAC,sBAAsB,IAAI,KAAK,CAAC;IAEpE,OAAO;QACL,sBAAsB;QACtB,0BAA0B;QAC1B,kBAAkB;QAClB,sBAAsB;QACtB,WAAW;QACX,IAAI;KACL,CAAC;AACJ,CAAC;AA3BD,wDA2BC;AAED,SAAgB,iBAAiB,CAC/B,YAAoB,EACpB,MAAyB,EACzB,IAAuB;IAEvB,MAAM,EAAE,WAAW,EAAE,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC;IAErD,IAAI,IAAI,CAAC,IAAI,KAAK,aAAa,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE;QACzD,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,0BAA0B,CAAC,CAAC;KAC5E;IAED,KAAK,MAAM,CAAC,SAAS,EAAE,gBAAgB,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,qCAA6B,CAAC,EAAE;QACzF,IAAI,WAAW,CAAC,QAAQ,CAAC,SAAoC,CAAC,EAAE;YAC9D,IAAI,eAAe,GAAG,KAAK,CAAC;YAE5B,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;gBAC7B,MAAM,WAAW,GAAG,SAAS,KAAK,KAAK,CAAC,IAAI,CAAC;gBAC7C,eAAe,GAAG,eAAe,IAAI,WAAW,CAAC;gBACjD,OAAO,CAAC,WAAW,CAAC;YACtB,CAAC,CAAC,CAAC;YAEH,IAAI,eAAe,IAAI,gBAAgB,EAAE;gBACvC,IAAA,gBAAU,EAAC,oCAAoC,YAAY,EAAE,EAAE,gBAAgB,CAAC,CAAC;aAClF;SACF;KACF;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AA5BD,8CA4BC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openzeppelin/upgrades-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": "https://github.com/OpenZeppelin/openzeppelin-upgrades/tree/master/packages/core",
|
|
6
6
|
"license": "MIT",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"/artifacts/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol",
|
|
17
17
|
"/artifacts/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol",
|
|
18
18
|
"/artifacts/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol",
|
|
19
|
-
"/artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json
|
|
19
|
+
"/artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json",
|
|
20
20
|
"/artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json",
|
|
21
21
|
"/artifacts/build-info.json"
|
|
22
22
|
],
|
|
@@ -52,11 +52,11 @@
|
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"cbor": "^8.0.0",
|
|
54
54
|
"chalk": "^4.1.0",
|
|
55
|
-
"compare-versions": "^
|
|
55
|
+
"compare-versions": "^5.0.0",
|
|
56
56
|
"debug": "^4.1.1",
|
|
57
57
|
"ethereumjs-util": "^7.0.3",
|
|
58
58
|
"proper-lockfile": "^4.1.1",
|
|
59
59
|
"solidity-ast": "^0.4.15"
|
|
60
60
|
},
|
|
61
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "4046edb571e48c46838a4d9b927fc21254757b3e"
|
|
62
62
|
}
|
package/src/index.ts
CHANGED
package/src/levenshtein.ts
CHANGED
|
@@ -10,7 +10,9 @@ export type BasicOperation<T> =
|
|
|
10
10
|
|
|
11
11
|
export type Operation<T, C> = C | BasicOperation<T>;
|
|
12
12
|
|
|
13
|
-
type
|
|
13
|
+
export type Cost = { cost?: number };
|
|
14
|
+
|
|
15
|
+
type GetChangeOp<T, C> = (a: T, b: T) => (C & Cost) | undefined;
|
|
14
16
|
|
|
15
17
|
export function levenshtein<T, C>(a: T[], b: T[], getChangeOp: GetChangeOp<T, C>): Operation<T, C>[] {
|
|
16
18
|
const matrix = buildMatrix(a, b, getChangeOp);
|
|
@@ -76,7 +78,7 @@ function buildMatrix<T, C>(a: T[], b: T[], getChangeOp: GetChangeOp<T, C>): Matr
|
|
|
76
78
|
const predCost = predecessor.totalCost;
|
|
77
79
|
const change = getChangeOp(original, updated);
|
|
78
80
|
if (change !== undefined) {
|
|
79
|
-
return { kind: 'change', totalCost: predCost + CHANGE_COST, predecessor, change };
|
|
81
|
+
return { kind: 'change', totalCost: predCost + (change.cost ?? CHANGE_COST), predecessor, change };
|
|
80
82
|
} else {
|
|
81
83
|
return { kind: 'nop', totalCost: predCost, predecessor };
|
|
82
84
|
}
|
package/src/provider.ts
CHANGED
|
@@ -104,6 +104,8 @@ export const networkNames: { [chainId in number]?: string } = Object.freeze({
|
|
|
104
104
|
42: 'kovan',
|
|
105
105
|
137: 'polygon',
|
|
106
106
|
80001: 'polygon-mumbai',
|
|
107
|
+
43113: 'avalanche-fuji',
|
|
108
|
+
43114: 'avalanche',
|
|
107
109
|
});
|
|
108
110
|
|
|
109
111
|
export async function isDevelopmentNetwork(provider: EthereumProvider): Promise<boolean> {
|
package/src/storage/compare.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { levenshtein, Operation } from '../levenshtein';
|
|
1
|
+
import { Cost, levenshtein, Operation } from '../levenshtein';
|
|
2
2
|
import { hasLayout, ParsedTypeDetailed, isEnumMembers, isStructMembers } from './layout';
|
|
3
3
|
import { UpgradesError } from '../error';
|
|
4
4
|
import { StorageItem as _StorageItem, StructMember as _StructMember, StorageField as _StorageField } from './layout';
|
|
5
5
|
import { LayoutCompatibilityReport } from './report';
|
|
6
6
|
import { assert } from '../utils/assert';
|
|
7
7
|
import { isValueType } from '../utils/is-value-type';
|
|
8
|
+
import { endMatchesGap, isGap } from './gap';
|
|
8
9
|
|
|
9
10
|
export type StorageItem = _StorageItem<ParsedTypeDetailed>;
|
|
10
11
|
type StructMember = _StructMember<ParsedTypeDetailed>;
|
|
@@ -15,13 +16,14 @@ export type StorageOperation<F extends StorageField> = Operation<F, StorageField
|
|
|
15
16
|
export type EnumOperation = Operation<string, { kind: 'replace'; original: string; updated: string }>;
|
|
16
17
|
|
|
17
18
|
type StorageFieldChange<F extends StorageField> = (
|
|
18
|
-
| { kind: 'replace' | 'rename' }
|
|
19
|
+
| { kind: 'replace' | 'rename' | 'finishgap' }
|
|
19
20
|
| { kind: 'typechange'; change: TypeChange }
|
|
20
21
|
| { kind: 'layoutchange'; change: LayoutChange }
|
|
22
|
+
| { kind: 'shrinkgap'; change: TypeChange }
|
|
21
23
|
) & {
|
|
22
24
|
original: F;
|
|
23
25
|
updated: F;
|
|
24
|
-
};
|
|
26
|
+
} & Cost;
|
|
25
27
|
|
|
26
28
|
export type TypeChange = (
|
|
27
29
|
| {
|
|
@@ -55,11 +57,45 @@ export type TypeChange = (
|
|
|
55
57
|
|
|
56
58
|
export interface LayoutChange {
|
|
57
59
|
uncertain?: boolean;
|
|
60
|
+
knownCompatible?: boolean;
|
|
58
61
|
slot?: Record<'from' | 'to', string>;
|
|
59
62
|
offset?: Record<'from' | 'to', number>;
|
|
60
63
|
bytes?: Record<'from' | 'to', string>;
|
|
61
64
|
}
|
|
62
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Gets the storage field's begin position as the number of bytes from 0.
|
|
68
|
+
*
|
|
69
|
+
* @param field the storage field
|
|
70
|
+
* @returns the begin position, or undefined if the slot or offset is undefined
|
|
71
|
+
*/
|
|
72
|
+
export function storageFieldBegin(field: StorageField): bigint | undefined {
|
|
73
|
+
if (field.slot === undefined || field.offset === undefined) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
return BigInt(field.slot) * 32n + BigInt(field.offset);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Gets the storage field's end position as the number of bytes from 0.
|
|
81
|
+
*
|
|
82
|
+
* @param field the storage field
|
|
83
|
+
* @returns the end position, or undefined if the slot or offset or number of bytes is undefined
|
|
84
|
+
*/
|
|
85
|
+
export function storageFieldEnd(field: StorageField): bigint | undefined {
|
|
86
|
+
const begin = storageFieldBegin(field);
|
|
87
|
+
const numberOfBytes = field.type.item.numberOfBytes;
|
|
88
|
+
if (begin === undefined || numberOfBytes === undefined) {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
return begin + BigInt(numberOfBytes);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const LAYOUTCHANGE_COST = 0;
|
|
95
|
+
const FINISHGAP_COST = 1;
|
|
96
|
+
const SHRINKGAP_COST = 0;
|
|
97
|
+
const TYPECHANGE_COST = 0;
|
|
98
|
+
|
|
63
99
|
export class StorageLayoutComparator {
|
|
64
100
|
hasAllowedUncheckedCustomTypes = false;
|
|
65
101
|
|
|
@@ -80,10 +116,39 @@ export class StorageLayoutComparator {
|
|
|
80
116
|
): StorageOperation<F>[] {
|
|
81
117
|
const ops = levenshtein(original, updated, (a, b) => this.getFieldChange(a, b));
|
|
82
118
|
|
|
83
|
-
if
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
119
|
+
// filter operations: the callback function returns true if operation is unsafe, false if safe
|
|
120
|
+
return ops.filter(o => {
|
|
121
|
+
if (o.kind === 'insert') {
|
|
122
|
+
const updatedStart = storageFieldBegin(o.updated);
|
|
123
|
+
const updatedEnd = storageFieldEnd(o.updated);
|
|
124
|
+
// If we cannot get the updated position, or if any entry in the original layout (that is not a gap) overlaps, then the insertion is unsafe.
|
|
125
|
+
return (
|
|
126
|
+
updatedStart === undefined ||
|
|
127
|
+
updatedEnd === undefined ||
|
|
128
|
+
original
|
|
129
|
+
.filter(entry => !isGap(entry))
|
|
130
|
+
.some(entry => {
|
|
131
|
+
const origStart = storageFieldBegin(entry);
|
|
132
|
+
const origEnd = storageFieldEnd(entry);
|
|
133
|
+
return (
|
|
134
|
+
origStart === undefined ||
|
|
135
|
+
origEnd === undefined ||
|
|
136
|
+
overlaps(origStart, origEnd, updatedStart, updatedEnd)
|
|
137
|
+
);
|
|
138
|
+
})
|
|
139
|
+
);
|
|
140
|
+
} else if ((o.kind === 'shrinkgap' || o.kind === 'finishgap') && endMatchesGap(o.original, o.updated)) {
|
|
141
|
+
// Gap shrink or gap replacement that finishes on the same slot is safe
|
|
142
|
+
return false;
|
|
143
|
+
} else if (o.kind === 'append' && allowAppend) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
return true;
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// https://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap
|
|
150
|
+
function overlaps(startA: bigint, endA: bigint, startB: bigint, endB: bigint) {
|
|
151
|
+
return startA < endB && startB < endA;
|
|
87
152
|
}
|
|
88
153
|
}
|
|
89
154
|
|
|
@@ -107,28 +172,29 @@ export class StorageLayoutComparator {
|
|
|
107
172
|
const typeChange = !retypedFromOriginal && this.getTypeChange(original.type, updated.type, { allowAppend: false });
|
|
108
173
|
const layoutChange = this.getLayoutChange(original, updated);
|
|
109
174
|
|
|
110
|
-
if (updated.retypedFrom && layoutChange) {
|
|
111
|
-
return { kind: 'layoutchange', original, updated, change: layoutChange };
|
|
175
|
+
if (updated.retypedFrom && layoutChange && (!layoutChange.uncertain || !layoutChange.knownCompatible)) {
|
|
176
|
+
return { kind: 'layoutchange', original, updated, change: layoutChange, cost: LAYOUTCHANGE_COST };
|
|
177
|
+
} else if (nameChange && endMatchesGap(original, updated)) {
|
|
178
|
+
// original is a gap that was consumed by a non-gap ending at the same slot, which is safe
|
|
179
|
+
return { kind: 'finishgap', original, updated, cost: FINISHGAP_COST };
|
|
180
|
+
} else if (typeChange && typeChange.kind === 'array shrink' && endMatchesGap(original, updated)) {
|
|
181
|
+
// original and updated are a gap that was shrunk and ends at the same slot, which is safe
|
|
182
|
+
return { kind: 'shrinkgap', change: typeChange, original, updated, cost: SHRINKGAP_COST };
|
|
112
183
|
} else if (typeChange && nameChange) {
|
|
113
184
|
return { kind: 'replace', original, updated };
|
|
114
185
|
} else if (nameChange) {
|
|
115
186
|
return { kind: 'rename', original, updated };
|
|
116
187
|
} else if (typeChange) {
|
|
117
|
-
return { kind: 'typechange', change: typeChange, original, updated };
|
|
188
|
+
return { kind: 'typechange', change: typeChange, original, updated, cost: TYPECHANGE_COST };
|
|
118
189
|
} else if (layoutChange && !layoutChange.uncertain) {
|
|
119
190
|
// Any layout change should be caught earlier as a type change, but we
|
|
120
191
|
// add this check as a safety fallback.
|
|
121
|
-
return { kind: 'layoutchange', original, updated, change: layoutChange };
|
|
192
|
+
return { kind: 'layoutchange', original, updated, change: layoutChange, cost: LAYOUTCHANGE_COST };
|
|
122
193
|
}
|
|
123
194
|
}
|
|
124
195
|
|
|
125
196
|
getLayoutChange(original: StorageField, updated: StorageField): LayoutChange | undefined {
|
|
126
|
-
|
|
127
|
-
const knownCompatibleTypes =
|
|
128
|
-
validPair.includes(original.type.item.label) && validPair.includes(updated.type.item.label);
|
|
129
|
-
if (original.type.item.label == updated.type.item.label || knownCompatibleTypes) {
|
|
130
|
-
return undefined;
|
|
131
|
-
} else if (hasLayout(original) && hasLayout(updated)) {
|
|
197
|
+
if (hasLayout(original) && hasLayout(updated)) {
|
|
132
198
|
const change = <T>(from: T, to: T) => (from === to ? undefined : { from, to });
|
|
133
199
|
const slot = change(original.slot, updated.slot);
|
|
134
200
|
const offset = change(original.offset, updated.offset);
|
|
@@ -137,7 +203,11 @@ export class StorageLayoutComparator {
|
|
|
137
203
|
return { slot, offset, bytes };
|
|
138
204
|
}
|
|
139
205
|
} else {
|
|
140
|
-
|
|
206
|
+
const validChanges = [['uint8', 'bool']];
|
|
207
|
+
const knownCompatible = validChanges.some(
|
|
208
|
+
group => group.includes(original.type.item.label) && group.includes(updated.type.item.label),
|
|
209
|
+
);
|
|
210
|
+
return { uncertain: true, knownCompatible };
|
|
141
211
|
}
|
|
142
212
|
}
|
|
143
213
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { StorageField, storageFieldEnd } from './compare';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns true if the field represents a storage gap.
|
|
5
|
+
*
|
|
6
|
+
* @param field the storage field
|
|
7
|
+
* @returns true if field is a gap, otherwise false
|
|
8
|
+
*/
|
|
9
|
+
export function isGap(field: StorageField): boolean {
|
|
10
|
+
return field.label === '__gap' && field.type.head === 't_array';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Returns true if original storage field is a gap and the updated storage field
|
|
15
|
+
* ends at the exact same position as the gap.
|
|
16
|
+
*
|
|
17
|
+
* @param original the original storage field
|
|
18
|
+
* @param updated the updated storage field
|
|
19
|
+
* @returns true if original is a gap and original and updated end at the same position, otherwise false
|
|
20
|
+
*/
|
|
21
|
+
export function endMatchesGap(original: StorageField, updated: StorageField) {
|
|
22
|
+
const originalEnd = storageFieldEnd(original);
|
|
23
|
+
const updatedEnd = storageFieldEnd(updated);
|
|
24
|
+
|
|
25
|
+
return isGap(original) && originalEnd !== undefined && updatedEnd !== undefined && originalEnd === updatedEnd;
|
|
26
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
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 } from './compare';
|
|
8
|
+
import { StorageLayout, getDetailedLayout } from './layout';
|
|
9
|
+
|
|
10
|
+
interface Context {
|
|
11
|
+
extractStorageLayout: (contract: string) => ReturnType<typeof extractStorageLayout>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const test = _test as TestFn<Context>;
|
|
15
|
+
|
|
16
|
+
const dummyDecodeSrc = () => 'file.sol:1';
|
|
17
|
+
const testContracts = [
|
|
18
|
+
'contracts/test/Storage.sol:StorageUpgrade_Gap_V1',
|
|
19
|
+
'contracts/test/Storage.sol:StorageUpgrade_Gap_V2_Ok',
|
|
20
|
+
'contracts/test/Storage.sol:StorageUpgrade_Gap_V2_Bad1',
|
|
21
|
+
'contracts/test/Storage.sol:StorageUpgrade_Gap_V2_Bad2',
|
|
22
|
+
'contracts/test/Storage.sol:StorageUpgrade_Gap_V2_Bad3',
|
|
23
|
+
'contracts/test/Storage.sol:StorageUpgrade_Gap_V2_Bad4',
|
|
24
|
+
'contracts/test/Storage.sol:StorageUpgrade_Gap_V2_Bad5',
|
|
25
|
+
'contracts/test/Storage.sol:StorageUpgrade_MultiConsumeGap_V1',
|
|
26
|
+
'contracts/test/Storage.sol:StorageUpgrade_MultiConsumeGap_V2_Ok',
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
test.before(async t => {
|
|
30
|
+
const contracts: Record<string, ContractDefinition> = {};
|
|
31
|
+
const deref: Record<string, ASTDereferencer> = {};
|
|
32
|
+
const storageLayout: Record<string, StorageLayout> = {};
|
|
33
|
+
for (const contract of testContracts) {
|
|
34
|
+
const buildInfo = await artifacts.getBuildInfo(contract);
|
|
35
|
+
if (buildInfo === undefined) {
|
|
36
|
+
throw new Error(`Build info not found for contract ${contract}`);
|
|
37
|
+
}
|
|
38
|
+
const solcOutput = buildInfo.output;
|
|
39
|
+
for (const def of findAll('ContractDefinition', solcOutput.sources['contracts/test/Storage.sol'].ast)) {
|
|
40
|
+
contracts[def.name] = def;
|
|
41
|
+
deref[def.name] = astDereferencer(solcOutput);
|
|
42
|
+
storageLayout[def.name] = (solcOutput.contracts['contracts/test/Storage.sol'][def.name] as any).storageLayout;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
t.context.extractStorageLayout = name =>
|
|
47
|
+
extractStorageLayout(contracts[name], dummyDecodeSrc, deref[name], storageLayout[name]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
function getReport(original: StorageLayout, updated: StorageLayout) {
|
|
51
|
+
const originalDetailed = getDetailedLayout(original);
|
|
52
|
+
const updatedDetailed = getDetailedLayout(updated);
|
|
53
|
+
const comparator = new StorageLayoutComparator();
|
|
54
|
+
return comparator.compareLayouts(originalDetailed, updatedDetailed);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
test('shrinkgap', t => {
|
|
58
|
+
const v1 = t.context.extractStorageLayout('StorageUpgrade_Gap_V1');
|
|
59
|
+
const v2 = t.context.extractStorageLayout('StorageUpgrade_Gap_V2_Ok');
|
|
60
|
+
const report = getReport(v1, v2);
|
|
61
|
+
t.true(report.ok);
|
|
62
|
+
t.is(report.explain(), '');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('finishgap', t => {
|
|
66
|
+
const v1 = t.context.extractStorageLayout('StorageUpgrade_MultiConsumeGap_V1');
|
|
67
|
+
const v2 = t.context.extractStorageLayout('StorageUpgrade_MultiConsumeGap_V2_Ok');
|
|
68
|
+
const report = getReport(v1, v2);
|
|
69
|
+
t.true(report.ok);
|
|
70
|
+
t.is(report.explain(), '');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('insert var without shrink gap', t => {
|
|
74
|
+
const v1 = t.context.extractStorageLayout('StorageUpgrade_Gap_V1');
|
|
75
|
+
const v2 = t.context.extractStorageLayout('StorageUpgrade_Gap_V2_Bad1');
|
|
76
|
+
const report = getReport(v1, v2);
|
|
77
|
+
t.false(report.ok);
|
|
78
|
+
t.snapshot(report.explain());
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('delete var and expand gap', t => {
|
|
82
|
+
const v1 = t.context.extractStorageLayout('StorageUpgrade_Gap_V1');
|
|
83
|
+
const v2 = t.context.extractStorageLayout('StorageUpgrade_Gap_V2_Bad2');
|
|
84
|
+
const report = getReport(v1, v2);
|
|
85
|
+
t.false(report.ok);
|
|
86
|
+
t.snapshot(report.explain());
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('shrink gap without adding var', t => {
|
|
90
|
+
const v1 = t.context.extractStorageLayout('StorageUpgrade_Gap_V1');
|
|
91
|
+
const v2 = t.context.extractStorageLayout('StorageUpgrade_Gap_V2_Bad3');
|
|
92
|
+
const report = getReport(v1, v2);
|
|
93
|
+
t.false(report.ok);
|
|
94
|
+
t.snapshot(report.explain());
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('insert var and shrink gap too much', t => {
|
|
98
|
+
const v1 = t.context.extractStorageLayout('StorageUpgrade_Gap_V1');
|
|
99
|
+
const v2 = t.context.extractStorageLayout('StorageUpgrade_Gap_V2_Bad4');
|
|
100
|
+
const report = getReport(v1, v2);
|
|
101
|
+
t.false(report.ok);
|
|
102
|
+
t.snapshot(report.explain());
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('insert vars and shrink gap not enough', t => {
|
|
106
|
+
const v1 = t.context.extractStorageLayout('StorageUpgrade_Gap_V1');
|
|
107
|
+
const v2 = t.context.extractStorageLayout('StorageUpgrade_Gap_V2_Bad5');
|
|
108
|
+
const report = getReport(v1, v2);
|
|
109
|
+
t.false(report.ok);
|
|
110
|
+
t.snapshot(report.explain());
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('insert vars without shrink gap (uint128)', t => {
|
|
114
|
+
const v1 = t.context.extractStorageLayout('StorageUpgrade_Uint128Gap_V1');
|
|
115
|
+
const v2 = t.context.extractStorageLayout('StorageUpgrade_Uint128Gap_V2_Bad');
|
|
116
|
+
const report = getReport(v1, v2);
|
|
117
|
+
t.false(report.ok);
|
|
118
|
+
t.snapshot(report.explain());
|
|
119
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Snapshot report for `src/storage/report-gap.test.ts`
|
|
2
|
+
|
|
3
|
+
The actual snapshot is saved in `report-gap.test.ts.snap`.
|
|
4
|
+
|
|
5
|
+
Generated by [AVA](https://avajs.dev).
|
|
6
|
+
|
|
7
|
+
## insert var without shrink gap
|
|
8
|
+
|
|
9
|
+
> Snapshot 1
|
|
10
|
+
|
|
11
|
+
`file.sol:1: Layout changed for \`__gap\` (uint256[48] -> uint256[48])␊
|
|
12
|
+
- Slot changed from 2 to 3␊
|
|
13
|
+
> Set __gap array to size 47`
|
|
14
|
+
|
|
15
|
+
## delete var and expand gap
|
|
16
|
+
|
|
17
|
+
> Snapshot 1
|
|
18
|
+
|
|
19
|
+
`StorageUpgrade_Gap_V1: Deleted \`b\`␊
|
|
20
|
+
> Keep the variable even if unused␊
|
|
21
|
+
␊
|
|
22
|
+
file.sol:1: Upgraded \`__gap\` to an incompatible type␊
|
|
23
|
+
- Bad storage gap resize from 48 to 49␊
|
|
24
|
+
Size cannot increase`
|
|
25
|
+
|
|
26
|
+
## shrink gap without adding var
|
|
27
|
+
|
|
28
|
+
> Snapshot 1
|
|
29
|
+
|
|
30
|
+
`file.sol:1: Upgraded \`__gap\` to an incompatible type␊
|
|
31
|
+
- Bad storage gap resize from 48 to 47␊
|
|
32
|
+
Size decrease must match with corresponding variable inserts␊
|
|
33
|
+
> Set __gap array to size 48`
|
|
34
|
+
|
|
35
|
+
## insert var and shrink gap too much
|
|
36
|
+
|
|
37
|
+
> Snapshot 1
|
|
38
|
+
|
|
39
|
+
`file.sol:1: Upgraded \`__gap\` to an incompatible type␊
|
|
40
|
+
- Bad storage gap resize from 48 to 46␊
|
|
41
|
+
Size decrease must match with corresponding variable inserts␊
|
|
42
|
+
> Set __gap array to size 47`
|
|
43
|
+
|
|
44
|
+
## insert vars and shrink gap not enough
|
|
45
|
+
|
|
46
|
+
> Snapshot 1
|
|
47
|
+
|
|
48
|
+
`file.sol:1: Upgraded \`__gap\` to an incompatible type␊
|
|
49
|
+
- Bad storage gap resize from 48 to 47␊
|
|
50
|
+
Size decrease must match with corresponding variable inserts␊
|
|
51
|
+
> Set __gap array to size 46`
|
|
52
|
+
|
|
53
|
+
## insert vars without shrink gap (uint128)
|
|
54
|
+
|
|
55
|
+
> Snapshot 1
|
|
56
|
+
|
|
57
|
+
`file.sol:1: Layout changed for \`__gap\` (uint128[49] -> uint128[49])␊
|
|
58
|
+
- Slot changed from 1 to 2␊
|
|
59
|
+
> Set __gap array to size 48`
|
|
Binary file
|
|
@@ -6,6 +6,7 @@ import { astDereferencer } from '../ast-dereferencer';
|
|
|
6
6
|
import { extractStorageLayout } from './extract';
|
|
7
7
|
import { StorageLayoutComparator } from './compare';
|
|
8
8
|
import { StorageLayout, getDetailedLayout } from './layout';
|
|
9
|
+
import { getStorageUpgradeErrors } from '.';
|
|
9
10
|
|
|
10
11
|
interface Context {
|
|
11
12
|
extractStorageLayout: (contract: string) => ReturnType<typeof extractStorageLayout>;
|
|
@@ -25,6 +26,8 @@ const testContracts = [
|
|
|
25
26
|
'contracts/test/RenamedRetyped.sol:ConfusingRetypeV2',
|
|
26
27
|
'contracts/test/RenamedRetyped.sol:NonHardcodedRetypeV1',
|
|
27
28
|
'contracts/test/RenamedRetyped.sol:NonHardcodedRetypeV2',
|
|
29
|
+
'contracts/test/RenamedRetyped.sol:LayoutChangeV1',
|
|
30
|
+
'contracts/test/RenamedRetyped.sol:LayoutChangeV2',
|
|
28
31
|
];
|
|
29
32
|
|
|
30
33
|
test.before(async t => {
|
|
@@ -104,3 +107,27 @@ test('non-hardcoded retype', t => {
|
|
|
104
107
|
t.true(report.ok);
|
|
105
108
|
t.snapshot(report.explain());
|
|
106
109
|
});
|
|
110
|
+
|
|
111
|
+
test('retype with layout change', t => {
|
|
112
|
+
const v1 = t.context.extractStorageLayout('LayoutChangeV1');
|
|
113
|
+
const v2 = t.context.extractStorageLayout('LayoutChangeV2');
|
|
114
|
+
|
|
115
|
+
// ensure both variables' layout changes appear in the internal errors
|
|
116
|
+
t.like(getStorageUpgradeErrors(v1, v2), {
|
|
117
|
+
length: 2,
|
|
118
|
+
0: {
|
|
119
|
+
kind: 'layoutchange',
|
|
120
|
+
original: { label: 'a' },
|
|
121
|
+
updated: { label: 'a' },
|
|
122
|
+
},
|
|
123
|
+
1: {
|
|
124
|
+
kind: 'layoutchange',
|
|
125
|
+
original: { label: 'b' },
|
|
126
|
+
updated: { label: 'b' },
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const report = getReport(v1, v2);
|
|
131
|
+
t.false(report.ok);
|
|
132
|
+
t.snapshot(report.explain());
|
|
133
|
+
});
|