@openzeppelin/upgrades-core 1.9.2 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/artifacts/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.dbg.json +1 -1
- package/artifacts/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol/BeaconProxy.dbg.json +4 -0
- package/artifacts/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol/BeaconProxy.json +80 -0
- package/artifacts/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.dbg.json +4 -0
- package/artifacts/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.json +113 -0
- package/artifacts/AdminUpgradeabilityProxy.json +2 -2
- package/artifacts/contracts/proxy/AdminUpgradeabilityProxy.sol/AdminUpgradeabilityProxy.json +2 -2
- package/dist/beacon.d.ts +8 -0
- package/dist/beacon.d.ts.map +1 -0
- package/dist/beacon.js +25 -0
- package/dist/beacon.js.map +1 -0
- package/dist/deployment.d.ts +14 -3
- package/dist/deployment.d.ts.map +1 -1
- package/dist/deployment.js +20 -13
- package/dist/deployment.js.map +1 -1
- package/dist/deployment.test.js +23 -0
- package/dist/deployment.test.js.map +1 -1
- package/dist/eip-1967-type.d.ts +4 -0
- package/dist/eip-1967-type.d.ts.map +1 -0
- package/dist/eip-1967-type.js +37 -0
- package/dist/eip-1967-type.js.map +1 -0
- package/dist/eip-1967.d.ts +5 -0
- package/dist/eip-1967.d.ts.map +1 -1
- package/dist/eip-1967.js +19 -12
- package/dist/eip-1967.js.map +1 -1
- package/dist/impl-address.d.ts +19 -0
- package/dist/impl-address.d.ts.map +1 -0
- package/dist/impl-address.js +67 -0
- package/dist/impl-address.js.map +1 -0
- package/dist/impl-store.d.ts +3 -2
- package/dist/impl-store.d.ts.map +1 -1
- package/dist/impl-store.js +21 -10
- package/dist/impl-store.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -3
- package/dist/index.js.map +1 -1
- package/dist/manifest.d.ts +1 -1
- package/dist/manifest.d.ts.map +1 -1
- package/dist/provider.d.ts +2 -0
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +11 -1
- package/dist/provider.js.map +1 -1
- package/dist/proxy-kind.d.ts +12 -0
- package/dist/proxy-kind.d.ts.map +1 -0
- package/dist/proxy-kind.js +50 -0
- package/dist/proxy-kind.js.map +1 -0
- package/dist/standalone.d.ts +19 -0
- package/dist/standalone.d.ts.map +1 -0
- package/dist/standalone.js +24 -0
- package/dist/standalone.js.map +1 -0
- package/dist/standalone.test.d.ts +2 -0
- package/dist/standalone.test.d.ts.map +1 -0
- package/dist/standalone.test.js +33 -0
- package/dist/standalone.test.js.map +1 -0
- package/dist/storage/index.d.ts +7 -0
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +11 -5
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/report.d.ts +3 -2
- package/dist/storage/report.d.ts.map +1 -1
- package/dist/storage/report.js +9 -3
- package/dist/storage/report.js.map +1 -1
- package/dist/usage-error.d.ts +21 -0
- package/dist/usage-error.d.ts.map +1 -0
- package/dist/usage-error.js +51 -0
- package/dist/usage-error.js.map +1 -0
- package/dist/utils/address.d.ts +8 -0
- package/dist/utils/address.d.ts.map +1 -0
- package/dist/utils/address.js +20 -0
- package/dist/utils/address.js.map +1 -0
- package/dist/utils/make-non-enumerable.d.ts +1 -1
- package/dist/utils/make-non-enumerable.d.ts.map +1 -1
- package/dist/utils/make-non-enumerable.js.map +1 -1
- package/dist/validate/error.d.ts.map +1 -1
- package/dist/validate/error.js +2 -65
- package/dist/validate/error.js.map +1 -1
- package/dist/validate/index.d.ts +2 -1
- package/dist/validate/index.d.ts.map +1 -1
- package/dist/validate/index.js +4 -1
- package/dist/validate/index.js.map +1 -1
- package/dist/validate/overrides.js +1 -1
- package/dist/validate/overrides.js.map +1 -1
- package/dist/validate/query.d.ts +3 -1
- package/dist/validate/query.d.ts.map +1 -1
- package/dist/validate/query.js +23 -7
- package/dist/validate/query.js.map +1 -1
- package/dist/validate/report.d.ts +8 -0
- package/dist/validate/report.d.ts.map +1 -0
- package/dist/validate/report.js +79 -0
- package/dist/validate/report.js.map +1 -0
- package/dist/version.d.ts +3 -3
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +12 -6
- package/dist/version.js.map +1 -1
- package/package.json +8 -5
- package/src/beacon.ts +20 -0
- package/src/deployment.test.ts +25 -0
- package/src/deployment.ts +43 -13
- package/src/eip-1967-type.ts +35 -0
- package/src/eip-1967.ts +16 -12
- package/src/impl-address.ts +76 -0
- package/src/impl-store.ts +23 -7
- package/src/index.ts +18 -1
- package/src/manifest.ts +1 -1
- package/src/provider.ts +16 -0
- package/src/proxy-kind.ts +59 -0
- package/src/standalone.test.ts +40 -0
- package/src/standalone.ts +41 -0
- package/src/storage/index.ts +14 -4
- package/src/storage/report.ts +10 -5
- package/src/usage-error.ts +68 -0
- package/src/utils/address.ts +16 -0
- package/src/utils/make-non-enumerable.ts +1 -1
- package/src/validate/error.ts +5 -67
- package/src/validate/index.ts +2 -0
- package/src/validate/overrides.ts +1 -1
- package/src/validate/query.ts +24 -6
- package/src/validate/report.ts +78 -0
- package/src/version.ts +14 -6
- package/CHANGELOG.md +0 -114
- package/dist/set-proxy-kind.d.ts +0 -5
- package/dist/set-proxy-kind.d.ts.map +0 -1
- package/dist/set-proxy-kind.js +0 -25
- package/dist/set-proxy-kind.js.map +0 -1
- package/src/set-proxy-kind.ts +0 -27
package/src/impl-store.ts
CHANGED
|
@@ -4,9 +4,11 @@ import { EthereumProvider, isDevelopmentNetwork } from './provider';
|
|
|
4
4
|
import { Deployment, InvalidDeployment, resumeOrDeploy, waitAndValidateDeployment } from './deployment';
|
|
5
5
|
import type { Version } from './version';
|
|
6
6
|
import assert from 'assert';
|
|
7
|
+
import { DeployOpts } from '.';
|
|
7
8
|
|
|
8
9
|
interface ManifestLens<T> {
|
|
9
10
|
description: string;
|
|
11
|
+
type: string;
|
|
10
12
|
(data: ManifestData): ManifestField<T>;
|
|
11
13
|
}
|
|
12
14
|
|
|
@@ -15,10 +17,22 @@ interface ManifestField<T> {
|
|
|
15
17
|
set(value: T | undefined): void;
|
|
16
18
|
}
|
|
17
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Fetches the deployment from the manifest, or deploys it if not found.
|
|
22
|
+
*
|
|
23
|
+
* @param lens the manifest lens
|
|
24
|
+
* @param provider the Ethereum provider
|
|
25
|
+
* @param deploy the deploy function
|
|
26
|
+
* @param opts options containing the timeout and pollingInterval parameters. If undefined, assumes the timeout is not configurable and will not mention those parameters in the error message for TransactionMinedTimeout.
|
|
27
|
+
* @returns the deployment address
|
|
28
|
+
* @throws {InvalidDeployment} if the deployment is invalid
|
|
29
|
+
* @throws {TransactionMinedTimeout} if the transaction was not confirmed within the timeout period
|
|
30
|
+
*/
|
|
18
31
|
async function fetchOrDeployGeneric<T extends Deployment>(
|
|
19
32
|
lens: ManifestLens<T>,
|
|
20
33
|
provider: EthereumProvider,
|
|
21
34
|
deploy: () => Promise<T>,
|
|
35
|
+
opts?: DeployOpts,
|
|
22
36
|
): Promise<string> {
|
|
23
37
|
const manifest = await Manifest.forNetwork(provider);
|
|
24
38
|
|
|
@@ -40,7 +54,7 @@ async function fetchOrDeployGeneric<T extends Deployment>(
|
|
|
40
54
|
return updated;
|
|
41
55
|
});
|
|
42
56
|
|
|
43
|
-
await waitAndValidateDeployment(provider, deployment);
|
|
57
|
+
await waitAndValidateDeployment(provider, deployment, lens.type, opts);
|
|
44
58
|
|
|
45
59
|
return deployment.address;
|
|
46
60
|
} catch (e) {
|
|
@@ -67,12 +81,13 @@ export async function fetchOrDeploy(
|
|
|
67
81
|
version: Version,
|
|
68
82
|
provider: EthereumProvider,
|
|
69
83
|
deploy: () => Promise<ImplDeployment>,
|
|
84
|
+
opts?: DeployOpts,
|
|
70
85
|
): Promise<string> {
|
|
71
|
-
return fetchOrDeployGeneric(implLens(version.linkedWithoutMetadata), provider, deploy);
|
|
86
|
+
return fetchOrDeployGeneric(implLens(version.linkedWithoutMetadata), provider, deploy, opts);
|
|
72
87
|
}
|
|
73
88
|
|
|
74
89
|
const implLens = (versionWithoutMetadata: string) =>
|
|
75
|
-
lens(`implementation ${versionWithoutMetadata}`, data => ({
|
|
90
|
+
lens(`implementation ${versionWithoutMetadata}`, 'implementation', data => ({
|
|
76
91
|
get: () => data.impls[versionWithoutMetadata],
|
|
77
92
|
set: (value?: ImplDeployment) => (data.impls[versionWithoutMetadata] = value),
|
|
78
93
|
}));
|
|
@@ -80,17 +95,18 @@ const implLens = (versionWithoutMetadata: string) =>
|
|
|
80
95
|
export async function fetchOrDeployAdmin(
|
|
81
96
|
provider: EthereumProvider,
|
|
82
97
|
deploy: () => Promise<Deployment>,
|
|
98
|
+
opts?: DeployOpts,
|
|
83
99
|
): Promise<string> {
|
|
84
|
-
return fetchOrDeployGeneric(adminLens, provider, deploy);
|
|
100
|
+
return fetchOrDeployGeneric(adminLens, provider, deploy, opts);
|
|
85
101
|
}
|
|
86
102
|
|
|
87
|
-
const adminLens = lens('proxy admin', data => ({
|
|
103
|
+
const adminLens = lens('proxy admin', 'proxy admin', data => ({
|
|
88
104
|
get: () => data.admin,
|
|
89
105
|
set: (value?: Deployment) => (data.admin = value),
|
|
90
106
|
}));
|
|
91
107
|
|
|
92
|
-
function lens<T>(description: string, fn: (data: ManifestData) => ManifestField<T>): ManifestLens<T> {
|
|
93
|
-
return Object.assign(fn, { description });
|
|
108
|
+
function lens<T>(description: string, type: string, fn: (data: ManifestData) => ManifestField<T>): ManifestLens<T> {
|
|
109
|
+
return Object.assign(fn, { description, type });
|
|
94
110
|
}
|
|
95
111
|
|
|
96
112
|
async function checkForAddressClash(
|
package/src/index.ts
CHANGED
|
@@ -9,10 +9,27 @@ export * from './src-decoder';
|
|
|
9
9
|
export * from './solc-api';
|
|
10
10
|
export * from './deployment';
|
|
11
11
|
export * from './link-refs';
|
|
12
|
+
export * from './error';
|
|
12
13
|
|
|
13
14
|
export { getStorageLayoutForAddress } from './manifest-storage-layout';
|
|
14
15
|
|
|
15
16
|
export * from './scripts/migrate-oz-cli-project';
|
|
16
17
|
|
|
17
18
|
export { logWarning } from './utils/log';
|
|
18
|
-
export { setProxyKind } from './
|
|
19
|
+
export { setProxyKind, processProxyKind } from './proxy-kind';
|
|
20
|
+
|
|
21
|
+
export { UpgradeableContract } from './standalone';
|
|
22
|
+
|
|
23
|
+
export { isTransparentOrUUPSProxy, isBeaconProxy } from './eip-1967-type';
|
|
24
|
+
export { getImplementationAddressFromBeacon, getImplementationAddressFromProxy } from './impl-address';
|
|
25
|
+
export { isBeacon } from './beacon';
|
|
26
|
+
|
|
27
|
+
export {
|
|
28
|
+
BeaconProxyUnsupportedError,
|
|
29
|
+
LoadProxyUnsupportedError,
|
|
30
|
+
PrepareUpgradeUnsupportedError,
|
|
31
|
+
DeployBeaconProxyUnsupportedError,
|
|
32
|
+
DeployBeaconProxyImplUnknownError,
|
|
33
|
+
DeployBeaconProxyKindError,
|
|
34
|
+
assertNotProxy,
|
|
35
|
+
} from './usage-error';
|
package/src/manifest.ts
CHANGED
package/src/provider.ts
CHANGED
|
@@ -5,6 +5,7 @@ export interface EthereumProvider {
|
|
|
5
5
|
send(method: 'net_version', params: []): Promise<string>;
|
|
6
6
|
send(method: 'eth_chainId', params: []): Promise<string>;
|
|
7
7
|
send(method: 'eth_getCode', params: [string, string]): Promise<string>;
|
|
8
|
+
send(method: 'eth_call', params: unknown[]): Promise<string>;
|
|
8
9
|
send(method: 'eth_getStorageAt', params: [string, string, string]): Promise<string>;
|
|
9
10
|
send(method: 'eth_getTransactionByHash', params: [string]): Promise<null | EthereumTransaction>;
|
|
10
11
|
send(method: 'eth_getTransactionReceipt', params: [string]): Promise<null | EthereumTransactionReceipt>;
|
|
@@ -53,6 +54,21 @@ export async function getCode(provider: EthereumProvider, address: string, block
|
|
|
53
54
|
return provider.send('eth_getCode', [address, block]);
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
export async function call(
|
|
58
|
+
provider: EthereumProvider,
|
|
59
|
+
address: string,
|
|
60
|
+
data: string,
|
|
61
|
+
block = 'latest',
|
|
62
|
+
): Promise<string> {
|
|
63
|
+
return provider.send('eth_call', [
|
|
64
|
+
{
|
|
65
|
+
to: address,
|
|
66
|
+
data: data,
|
|
67
|
+
},
|
|
68
|
+
block,
|
|
69
|
+
]);
|
|
70
|
+
}
|
|
71
|
+
|
|
56
72
|
export async function hasCode(provider: EthereumProvider, address: string, block?: string): Promise<boolean> {
|
|
57
73
|
const code = await getCode(provider, address, block);
|
|
58
74
|
return code !== '0x';
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { EthereumProvider } from './provider';
|
|
2
|
+
import { inferProxyKind, ValidationData, ValidationOptions } from './validate';
|
|
3
|
+
import { Manifest, DeploymentNotFound, ProxyDeployment } from './manifest';
|
|
4
|
+
import { isBeaconProxy } from './eip-1967-type';
|
|
5
|
+
import { Version } from './version';
|
|
6
|
+
import { BeaconProxyUnsupportedError } from './usage-error';
|
|
7
|
+
|
|
8
|
+
export async function setProxyKind(
|
|
9
|
+
provider: EthereumProvider,
|
|
10
|
+
proxyAddress: string,
|
|
11
|
+
opts: ValidationOptions,
|
|
12
|
+
): Promise<ProxyDeployment['kind']> {
|
|
13
|
+
const manifest = await Manifest.forNetwork(provider);
|
|
14
|
+
|
|
15
|
+
const manifestDeployment = await manifest.getProxyFromAddress(proxyAddress).catch(e => {
|
|
16
|
+
if (e instanceof DeploymentNotFound) {
|
|
17
|
+
return undefined;
|
|
18
|
+
} else {
|
|
19
|
+
throw e;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (opts.kind === undefined) {
|
|
24
|
+
opts.kind = manifestDeployment?.kind ?? 'transparent';
|
|
25
|
+
} else if (manifestDeployment && opts.kind !== manifestDeployment.kind) {
|
|
26
|
+
throw new Error(`Requested an upgrade of kind ${opts.kind} but proxy is ${manifestDeployment.kind}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return opts.kind;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Processes opts.kind when deploying the implementation for a UUPS or Transparent proxy.
|
|
34
|
+
*
|
|
35
|
+
* @throws {BeaconProxyUnsupportedError} If this function is called for a Beacon proxy.
|
|
36
|
+
*/
|
|
37
|
+
export async function processProxyKind(
|
|
38
|
+
provider: EthereumProvider,
|
|
39
|
+
proxyAddress: string | undefined,
|
|
40
|
+
opts: ValidationOptions,
|
|
41
|
+
data: ValidationData,
|
|
42
|
+
version: Version,
|
|
43
|
+
) {
|
|
44
|
+
if (opts.kind === undefined) {
|
|
45
|
+
if (proxyAddress !== undefined && (await isBeaconProxy(provider, proxyAddress))) {
|
|
46
|
+
opts.kind = 'beacon';
|
|
47
|
+
} else {
|
|
48
|
+
opts.kind = inferProxyKind(data, version);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (proxyAddress !== undefined) {
|
|
53
|
+
await setProxyKind(provider, proxyAddress, opts);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (opts.kind === 'beacon') {
|
|
57
|
+
throw new BeaconProxyUnsupportedError();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import _test, { TestInterface } from 'ava';
|
|
2
|
+
import { artifacts } from 'hardhat';
|
|
3
|
+
|
|
4
|
+
import { SolcInput, SolcOutput } from './solc-api';
|
|
5
|
+
import { UpgradeableContract } from './standalone';
|
|
6
|
+
|
|
7
|
+
interface Context {
|
|
8
|
+
solcInput: SolcInput;
|
|
9
|
+
solcOutput: SolcOutput;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const test = _test as TestInterface<Context>;
|
|
13
|
+
|
|
14
|
+
test.before(async t => {
|
|
15
|
+
const buildInfo = await artifacts.getBuildInfo('contracts/test/Standalone.sol:StandaloneV1');
|
|
16
|
+
if (buildInfo === undefined) {
|
|
17
|
+
throw new Error('Build info not found');
|
|
18
|
+
}
|
|
19
|
+
t.context.solcInput = buildInfo.input;
|
|
20
|
+
t.context.solcOutput = buildInfo.output;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('reports unsafe operation', t => {
|
|
24
|
+
const impl = new UpgradeableContract('StandaloneV1', t.context.solcInput, t.context.solcOutput);
|
|
25
|
+
const report = impl.getErrorReport();
|
|
26
|
+
t.false(report.ok);
|
|
27
|
+
t.true(report.errors[0].kind === 'delegatecall');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('reports storage upgrade errors', t => {
|
|
31
|
+
const v1 = new UpgradeableContract('StandaloneV1', t.context.solcInput, t.context.solcOutput);
|
|
32
|
+
|
|
33
|
+
const v2Good = new UpgradeableContract('StandaloneV2Good', t.context.solcInput, t.context.solcOutput);
|
|
34
|
+
const goodReport = v1.getStorageUpgradeReport(v2Good);
|
|
35
|
+
t.true(goodReport.ok);
|
|
36
|
+
|
|
37
|
+
const v2Bad = new UpgradeableContract('StandaloneV2Bad', t.context.solcInput, t.context.solcOutput);
|
|
38
|
+
const badReport = v1.getStorageUpgradeReport(v2Bad);
|
|
39
|
+
t.false(badReport.ok);
|
|
40
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { SolcOutput, SolcInput } from './solc-api';
|
|
2
|
+
import { solcInputOutputDecoder } from './src-decoder';
|
|
3
|
+
import {
|
|
4
|
+
getContractVersion,
|
|
5
|
+
getErrors,
|
|
6
|
+
getStorageLayout,
|
|
7
|
+
UpgradeableContractErrorReport,
|
|
8
|
+
validate,
|
|
9
|
+
ValidationOptions,
|
|
10
|
+
withValidationDefaults,
|
|
11
|
+
} from './validate';
|
|
12
|
+
import { getStorageUpgradeReport, StorageLayout } from './storage';
|
|
13
|
+
import { Version } from './version';
|
|
14
|
+
import { ValidationError } from './validate/run';
|
|
15
|
+
|
|
16
|
+
export interface Report {
|
|
17
|
+
ok: boolean;
|
|
18
|
+
explain(color?: boolean): string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class UpgradeableContract {
|
|
22
|
+
readonly version: Version;
|
|
23
|
+
readonly errors: ValidationError[];
|
|
24
|
+
readonly layout: StorageLayout;
|
|
25
|
+
|
|
26
|
+
constructor(readonly name: string, solcInput: SolcInput, solcOutput: SolcOutput, opts: ValidationOptions = {}) {
|
|
27
|
+
const decodeSrc = solcInputOutputDecoder(solcInput, solcOutput);
|
|
28
|
+
const validation = validate(solcOutput, decodeSrc);
|
|
29
|
+
this.version = getContractVersion(validation, name);
|
|
30
|
+
this.errors = getErrors(validation, this.version, opts);
|
|
31
|
+
this.layout = getStorageLayout(validation, this.version);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getErrorReport() {
|
|
35
|
+
return new UpgradeableContractErrorReport(this.errors);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
getStorageUpgradeReport(newVersion: UpgradeableContract, opts: ValidationOptions = {}) {
|
|
39
|
+
return getStorageUpgradeReport(this.layout, newVersion.layout, withValidationDefaults(opts));
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/storage/index.ts
CHANGED
|
@@ -29,6 +29,18 @@ export function assertStorageUpgradeSafe(
|
|
|
29
29
|
opts = withValidationDefaults({ unsafeAllowCustomTypes });
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
const report = getStorageUpgradeReport(original, updated, opts);
|
|
33
|
+
|
|
34
|
+
if (!report.pass) {
|
|
35
|
+
throw new StorageUpgradeErrors(report);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function getStorageUpgradeReport(
|
|
40
|
+
original: StorageLayout,
|
|
41
|
+
updated: StorageLayout,
|
|
42
|
+
opts: Required<ValidationOptions>,
|
|
43
|
+
): LayoutCompatibilityReport {
|
|
32
44
|
const originalDetailed = getDetailedLayout(original);
|
|
33
45
|
const updatedDetailed = getDetailedLayout(updated);
|
|
34
46
|
const comparator = new StorageLayoutComparator(opts.unsafeAllowCustomTypes, opts.unsafeAllowRenames);
|
|
@@ -43,12 +55,10 @@ export function assertStorageUpgradeSafe(
|
|
|
43
55
|
logNote(`\`unsafeAllowCustomTypes\` is no longer necessary. Structs are enums are automatically checked.\n`);
|
|
44
56
|
}
|
|
45
57
|
|
|
46
|
-
|
|
47
|
-
throw new StorageUpgradeErrors(report);
|
|
48
|
-
}
|
|
58
|
+
return report;
|
|
49
59
|
}
|
|
50
60
|
|
|
51
|
-
class StorageUpgradeErrors extends UpgradesError {
|
|
61
|
+
export class StorageUpgradeErrors extends UpgradesError {
|
|
52
62
|
constructor(readonly report: LayoutCompatibilityReport) {
|
|
53
63
|
super(`New storage layout is incompatible`, () => report.explain());
|
|
54
64
|
}
|
package/src/storage/report.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import _chalk from 'chalk';
|
|
2
2
|
|
|
3
3
|
import type { BasicOperation } from '../levenshtein';
|
|
4
4
|
import type { ParsedTypeDetailed } from './layout';
|
|
@@ -8,13 +8,18 @@ import { indent } from '../utils/indent';
|
|
|
8
8
|
import { assert } from '../utils/assert';
|
|
9
9
|
|
|
10
10
|
export class LayoutCompatibilityReport {
|
|
11
|
-
readonly
|
|
11
|
+
constructor(readonly ops: StorageOperation<StorageItem>[]) {}
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
this.pass
|
|
13
|
+
get ok(): boolean {
|
|
14
|
+
return this.pass;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
get pass(): boolean {
|
|
18
|
+
return this.ops.length === 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
explain(color = true): string {
|
|
22
|
+
const chalk = new _chalk.Instance({ level: color && _chalk.supportsColor ? _chalk.supportsColor.level : 0 });
|
|
18
23
|
const res = [];
|
|
19
24
|
|
|
20
25
|
for (const op of this.ops) {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { EthereumProvider, getBeaconAddress, isBeaconProxy, isTransparentOrUUPSProxy, UpgradesError } from '.';
|
|
2
|
+
|
|
3
|
+
export class BeaconProxyUnsupportedError extends UpgradesError {
|
|
4
|
+
constructor() {
|
|
5
|
+
super(
|
|
6
|
+
'Beacon proxies are not supported with the current function.',
|
|
7
|
+
() => 'Use deployBeacon(), deployBeaconProxy(), or upgradeBeacon() instead.',
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class DeployBeaconProxyKindError extends UpgradesError {
|
|
13
|
+
constructor(kind: string) {
|
|
14
|
+
super(
|
|
15
|
+
`Unsupported proxy kind '${kind}'`,
|
|
16
|
+
() => `deployBeaconProxy() is only supported with proxy kind undefined or 'beacon'`,
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class DeployBeaconProxyUnsupportedError extends UpgradesError {
|
|
22
|
+
constructor(beaconAddress: string) {
|
|
23
|
+
super(`Contract at ${beaconAddress} doesn't look like a beacon`, () => 'Deploy a beacon using deployBeacon().');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class DeployBeaconProxyImplUnknownError extends UpgradesError {
|
|
28
|
+
constructor(implAddress: string) {
|
|
29
|
+
super(
|
|
30
|
+
`Beacon's current implementation at ${implAddress} is unknown`,
|
|
31
|
+
() => `Call deployBeaconProxy() with the implementation option providing the beacon's current implementation.`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class LoadProxyUnsupportedError extends UpgradesError {
|
|
37
|
+
constructor(proxyAddress: string) {
|
|
38
|
+
super(
|
|
39
|
+
`Contract at ${proxyAddress} doesn't look like a supported proxy`,
|
|
40
|
+
() => 'Only transparent, UUPS, or beacon proxies can be loaded with the loadProxy() function.',
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export class PrepareUpgradeUnsupportedError extends UpgradesError {
|
|
46
|
+
constructor(proxyOrBeaconAddress: string) {
|
|
47
|
+
super(
|
|
48
|
+
`Contract at address ${proxyOrBeaconAddress} doesn't look like a supported proxy or beacon`,
|
|
49
|
+
() => `Only transparent, UUPS, or beacon proxies or beacons can be used with the prepareUpgrade() function.`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function assertNotProxy(provider: EthereumProvider, address: string) {
|
|
55
|
+
if (await isTransparentOrUUPSProxy(provider, address)) {
|
|
56
|
+
throw new UpgradesError(
|
|
57
|
+
'Address is a transparent or UUPS proxy which cannot be upgraded using upgradeBeacon().',
|
|
58
|
+
() => 'Use upgradeProxy() instead.',
|
|
59
|
+
);
|
|
60
|
+
} else if (await isBeaconProxy(provider, address)) {
|
|
61
|
+
const beaconAddress = await getBeaconAddress(provider, address);
|
|
62
|
+
throw new UpgradesError(
|
|
63
|
+
'Address is a beacon proxy which cannot be upgraded directly.',
|
|
64
|
+
() =>
|
|
65
|
+
`upgradeBeacon() must be called with a beacon address, not a beacon proxy address. Call upgradeBeacon() on the beacon address ${beaconAddress} instead.`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { toChecksumAddress } from 'ethereumjs-util';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parses an address from a hex string which may come from storage or a returned address via eth_call.
|
|
5
|
+
*
|
|
6
|
+
* @param addressString The address hex string.
|
|
7
|
+
* @returns The parsed checksum address, or undefined if the input string is not an address.
|
|
8
|
+
*/
|
|
9
|
+
export function parseAddress(addressString: string): string | undefined {
|
|
10
|
+
const buf = Buffer.from(addressString.replace(/^0x/, ''), 'hex');
|
|
11
|
+
if (!buf.slice(0, 12).equals(Buffer.alloc(12, 0))) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
const address = '0x' + buf.toString('hex', 12, 32); // grab the last 20 bytes
|
|
15
|
+
return toChecksumAddress(address);
|
|
16
|
+
}
|
package/src/validate/error.ts
CHANGED
|
@@ -1,73 +1,11 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
|
|
3
1
|
import { ValidationError } from './run';
|
|
4
|
-
import { UpgradesError
|
|
2
|
+
import { UpgradesError } from '../error';
|
|
3
|
+
import { UpgradeableContractErrorReport } from './report';
|
|
5
4
|
|
|
6
5
|
export class ValidationErrors extends UpgradesError {
|
|
7
6
|
constructor(contractName: string, readonly errors: ValidationError[]) {
|
|
8
|
-
super(`Contract \`${contractName}\` is not upgrade safe`, () =>
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const errorInfo: ErrorDescriptions<ValidationError> = {
|
|
15
|
-
constructor: {
|
|
16
|
-
msg: e => `Contract \`${e.contract}\` has a constructor`,
|
|
17
|
-
hint: () => 'Define an initializer instead',
|
|
18
|
-
link: 'https://zpl.in/upgrades/error-001',
|
|
19
|
-
},
|
|
20
|
-
delegatecall: {
|
|
21
|
-
msg: () => `Use of delegatecall is not allowed`,
|
|
22
|
-
link: 'https://zpl.in/upgrades/error-002',
|
|
23
|
-
},
|
|
24
|
-
selfdestruct: {
|
|
25
|
-
msg: () => `Use of selfdestruct is not allowed`,
|
|
26
|
-
link: 'https://zpl.in/upgrades/error-003',
|
|
27
|
-
},
|
|
28
|
-
'state-variable-assignment': {
|
|
29
|
-
msg: e => `Variable \`${e.name}\` is assigned an initial value`,
|
|
30
|
-
hint: () => 'Move the assignment to the initializer',
|
|
31
|
-
link: 'https://zpl.in/upgrades/error-004',
|
|
32
|
-
},
|
|
33
|
-
'state-variable-immutable': {
|
|
34
|
-
msg: e => `Variable \`${e.name}\` is immutable`,
|
|
35
|
-
hint: () => `Use a constant or mutable variable instead`,
|
|
36
|
-
link: 'https://zpl.in/upgrades/error-005',
|
|
37
|
-
},
|
|
38
|
-
'external-library-linking': {
|
|
39
|
-
msg: e => `Linking external libraries like \`${e.name}\` is not yet supported`,
|
|
40
|
-
hint: () =>
|
|
41
|
-
`Use libraries with internal functions only, or skip this check with the \`unsafeAllowLinkedLibraries\` flag \n` +
|
|
42
|
-
` if you have manually checked that the libraries are upgrade safe`,
|
|
43
|
-
link: 'https://zpl.in/upgrades/error-006',
|
|
44
|
-
},
|
|
45
|
-
'struct-definition': {
|
|
46
|
-
msg: e => `Structs like \`${e.name}\` are supported in the latest version of the plugin`,
|
|
47
|
-
hint: () => `Update your dependency and run again`,
|
|
48
|
-
},
|
|
49
|
-
'enum-definition': {
|
|
50
|
-
msg: e => `Enums like \`${e.name}\` are supported in the latest version of the plugin`,
|
|
51
|
-
hint: () => `Update your dependency and run again`,
|
|
52
|
-
},
|
|
53
|
-
'missing-public-upgradeto': {
|
|
54
|
-
msg: () => `Implementation is missing a public \`upgradeTo(address)\` function`,
|
|
55
|
-
hint: () =>
|
|
56
|
-
`Inherit UUPSUpgradeable to include this function in your contract\n` +
|
|
57
|
-
` @openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol`,
|
|
58
|
-
link: 'https://zpl.in/upgrades/error-008',
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
function describeError(e: ValidationError): string {
|
|
63
|
-
const info = errorInfo[e.kind];
|
|
64
|
-
const log = [chalk.bold(e.src) + ': ' + info.msg(e as any)];
|
|
65
|
-
const hint = info.hint?.(e as any);
|
|
66
|
-
if (hint) {
|
|
67
|
-
log.push(hint);
|
|
68
|
-
}
|
|
69
|
-
if (info.link) {
|
|
70
|
-
log.push(chalk.dim(info.link));
|
|
7
|
+
super(`Contract \`${contractName}\` is not upgrade safe`, () =>
|
|
8
|
+
new UpgradeableContractErrorReport(errors).explain(),
|
|
9
|
+
);
|
|
71
10
|
}
|
|
72
|
-
return log.join('\n ');
|
|
73
11
|
}
|
package/src/validate/index.ts
CHANGED
|
@@ -69,7 +69,7 @@ export function processExceptions(
|
|
|
69
69
|
): ValidationError[] {
|
|
70
70
|
const { unsafeAllow } = withValidationDefaults(opts);
|
|
71
71
|
|
|
72
|
-
if (opts.kind === 'transparent') {
|
|
72
|
+
if (opts.kind === 'transparent' || opts.kind === 'beacon') {
|
|
73
73
|
errors = errors.filter(error => error.kind !== 'missing-public-upgradeto');
|
|
74
74
|
}
|
|
75
75
|
|
package/src/validate/query.ts
CHANGED
|
@@ -5,13 +5,15 @@ import { unlinkBytecode } from '../link-refs';
|
|
|
5
5
|
import { ValidationOptions, processExceptions } from './overrides';
|
|
6
6
|
import { ValidationErrors } from './error';
|
|
7
7
|
import { ValidationData, normalizeValidationData } from './data';
|
|
8
|
+
import { ProxyDeployment } from '../manifest';
|
|
9
|
+
|
|
10
|
+
const upgradeToSignature = 'upgradeTo(address)';
|
|
8
11
|
|
|
9
12
|
export function assertUpgradeSafe(data: ValidationData, version: Version, opts: ValidationOptions): void {
|
|
10
13
|
const dataV3 = normalizeValidationData(data);
|
|
11
14
|
const [contractName] = getContractNameAndRunValidation(dataV3, version);
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
errors = processExceptions(contractName, errors, opts);
|
|
16
|
+
const errors = getErrors(dataV3, version, opts);
|
|
15
17
|
|
|
16
18
|
if (errors.length > 0) {
|
|
17
19
|
throw new ValidationErrors(contractName, errors);
|
|
@@ -100,23 +102,28 @@ export function getUnlinkedBytecode(data: ValidationData, bytecode: string): str
|
|
|
100
102
|
return bytecode;
|
|
101
103
|
}
|
|
102
104
|
|
|
103
|
-
export function getErrors(data: ValidationData, version: Version): ValidationError[] {
|
|
105
|
+
export function getErrors(data: ValidationData, version: Version, opts: ValidationOptions = {}): ValidationError[] {
|
|
104
106
|
const dataV3 = normalizeValidationData(data);
|
|
105
107
|
const [contractName, runValidation] = getContractNameAndRunValidation(dataV3, version);
|
|
106
108
|
const c = runValidation[contractName];
|
|
107
109
|
|
|
108
110
|
const errors = getUsedContractsAndLibraries(contractName, runValidation).flatMap(name => runValidation[name].errors);
|
|
109
111
|
|
|
110
|
-
const selfAndInheritedMethods =
|
|
112
|
+
const selfAndInheritedMethods = getAllMethods(runValidation, contractName);
|
|
111
113
|
|
|
112
|
-
if (!selfAndInheritedMethods.includes(
|
|
114
|
+
if (!selfAndInheritedMethods.includes(upgradeToSignature)) {
|
|
113
115
|
errors.push({
|
|
114
116
|
src: c.src,
|
|
115
117
|
kind: 'missing-public-upgradeto',
|
|
116
118
|
});
|
|
117
119
|
}
|
|
118
120
|
|
|
119
|
-
return errors;
|
|
121
|
+
return processExceptions(contractName, errors, opts);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function getAllMethods(runValidation: ValidationRunData, contractName: string): string[] {
|
|
125
|
+
const c = runValidation[contractName];
|
|
126
|
+
return c.methods.concat(...c.inherit.map(name => runValidation[name].methods));
|
|
120
127
|
}
|
|
121
128
|
|
|
122
129
|
function getUsedContractsAndLibraries(contractName: string, runValidation: ValidationRunData) {
|
|
@@ -136,3 +143,14 @@ export function isUpgradeSafe(data: ValidationData, version: Version): boolean {
|
|
|
136
143
|
const dataV3 = normalizeValidationData(data);
|
|
137
144
|
return getErrors(dataV3, version).length == 0;
|
|
138
145
|
}
|
|
146
|
+
|
|
147
|
+
export function inferProxyKind(data: ValidationData, version: Version): ProxyDeployment['kind'] {
|
|
148
|
+
const dataV3 = normalizeValidationData(data);
|
|
149
|
+
const [contractName, runValidation] = getContractNameAndRunValidation(dataV3, version);
|
|
150
|
+
const methods = getAllMethods(runValidation, contractName);
|
|
151
|
+
if (methods.includes(upgradeToSignature)) {
|
|
152
|
+
return 'uups';
|
|
153
|
+
} else {
|
|
154
|
+
return 'transparent';
|
|
155
|
+
}
|
|
156
|
+
}
|