@openzeppelin/upgrades-core 1.16.0 → 1.18.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/contracts/Initializable.sol +4 -1
- package/dist/deployment.js +2 -3
- package/dist/deployment.js.map +1 -1
- package/dist/eip-1967.d.ts.map +1 -1
- package/dist/eip-1967.js +2 -7
- package/dist/eip-1967.js.map +1 -1
- package/dist/impl-address.d.ts.map +1 -1
- package/dist/impl-address.js +2 -1
- package/dist/impl-address.js.map +1 -1
- package/dist/impl-store.js +6 -8
- package/dist/impl-store.js.map +1 -1
- 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/manifest-storage-layout.js +1 -1
- package/dist/manifest-storage-layout.js.map +1 -1
- package/dist/manifest.d.ts +5 -0
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +54 -10
- package/dist/manifest.js.map +1 -1
- package/dist/manifest.test.js +82 -0
- package/dist/manifest.test.js.map +1 -1
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +4 -6
- package/dist/provider.js.map +1 -1
- package/dist/provider.test.js +5 -5
- package/dist/provider.test.js.map +1 -1
- package/dist/proxy-kind.js +1 -2
- package/dist/proxy-kind.js.map +1 -1
- package/dist/scripts/migrate-oz-cli-project.js +1 -1
- package/dist/scripts/migrate-oz-cli-project.js.map +1 -1
- package/dist/scripts/migrate-oz-cli-project.test.js +1 -1
- package/dist/scripts/migrate-oz-cli-project.test.js.map +1 -1
- package/dist/src-decoder.js +1 -2
- package/dist/src-decoder.js.map +1 -1
- package/dist/storage/compare.js +3 -5
- package/dist/storage/compare.js.map +1 -1
- package/dist/storage/extract.js +6 -9
- package/dist/storage/extract.js.map +1 -1
- package/dist/storage/layout.js +3 -4
- package/dist/storage/layout.js.map +1 -1
- package/dist/storage.test.js +1 -1
- package/dist/storage.test.js.map +1 -1
- package/dist/stub-provider.js +5 -7
- package/dist/stub-provider.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/utils/parse-type-id.js +2 -3
- package/dist/utils/parse-type-id.js.map +1 -1
- package/dist/validate/data.js +1 -2
- 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 +7 -7
- package/dist/validate/overrides.js.map +1 -1
- package/dist/validate/query.js +3 -5
- package/dist/validate/query.js.map +1 -1
- package/dist/validate/report.js +1 -2
- package/dist/validate/report.js.map +1 -1
- package/dist/validate/run.js +6 -10
- package/dist/validate/run.js.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +2 -4
- package/src/eip-1967.ts +2 -4
- package/src/impl-address.ts +2 -1
- package/src/index.ts +2 -0
- package/src/manifest.test.ts +103 -1
- package/src/manifest.ts +62 -6
- package/src/provider.ts +3 -3
- package/src/usage-error.ts +22 -0
- package/src/validate/index.ts +1 -1
- package/src/validate/overrides.ts +39 -3
package/src/manifest.test.ts
CHANGED
|
@@ -1,5 +1,107 @@
|
|
|
1
|
-
import test from 'ava';
|
|
1
|
+
import test, { ExecutionContext } from 'ava';
|
|
2
2
|
import { Manifest, ManifestData, normalizeManifestData } from './manifest';
|
|
3
|
+
import { promises as fs } from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
async function writeTestManifest(file: string) {
|
|
7
|
+
const testManifest = {
|
|
8
|
+
manifestVersion: '3.2',
|
|
9
|
+
impls: {},
|
|
10
|
+
proxies: [
|
|
11
|
+
{
|
|
12
|
+
address: '0x123',
|
|
13
|
+
txHash: '0x0',
|
|
14
|
+
kind: 'uups',
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
19
|
+
await fs.writeFile(file, JSON.stringify(testManifest, null, 2) + '\n');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function deleteFile(t: ExecutionContext, file: string) {
|
|
23
|
+
try {
|
|
24
|
+
await fs.unlink(file);
|
|
25
|
+
} catch (e: any) {
|
|
26
|
+
if (!e.message.includes('ENOENT')) {
|
|
27
|
+
t.fail(e);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function assertOldName(t: ExecutionContext<unknown>, id: number) {
|
|
33
|
+
await fs.access(`.openzeppelin/unknown-${id}.json`);
|
|
34
|
+
t.throwsAsync(fs.access(`.openzeppelin/polygon-mumbai.json`));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function assertNewName(t: ExecutionContext<unknown>, id: number) {
|
|
38
|
+
t.throwsAsync(fs.access(`.openzeppelin/unknown-${id}.json`));
|
|
39
|
+
await fs.access(`.openzeppelin/polygon-mumbai.json`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function deleteManifests(t: ExecutionContext<unknown>, id: number) {
|
|
43
|
+
await deleteFile(t, `.openzeppelin/unknown-${id}.json`);
|
|
44
|
+
await deleteFile(t, '.openzeppelin/polygon-mumbai.json');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
test.serial('multiple manifests', async t => {
|
|
48
|
+
const id = 80001;
|
|
49
|
+
await deleteManifests(t, id);
|
|
50
|
+
|
|
51
|
+
await writeTestManifest(`.openzeppelin/unknown-${id}.json`);
|
|
52
|
+
await writeTestManifest(`.openzeppelin/polygon-mumbai.json`);
|
|
53
|
+
|
|
54
|
+
const manifest = new Manifest(id);
|
|
55
|
+
await manifest.lockedRun(async () => {
|
|
56
|
+
await t.throwsAsync(() => manifest.read(), {
|
|
57
|
+
message: new RegExp(
|
|
58
|
+
`Network files with different names .openzeppelin/unknown-${id}.json and .openzeppelin/polygon-mumbai.json were found for the same network.`,
|
|
59
|
+
),
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await deleteManifests(t, id);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test.serial('rename manifest', async t => {
|
|
67
|
+
const id = 80001;
|
|
68
|
+
await deleteManifests(t, id);
|
|
69
|
+
|
|
70
|
+
await writeTestManifest(`.openzeppelin/unknown-${id}.json`);
|
|
71
|
+
|
|
72
|
+
const manifest = new Manifest(id);
|
|
73
|
+
t.is(manifest.file, `.openzeppelin/polygon-mumbai.json`);
|
|
74
|
+
|
|
75
|
+
t.throwsAsync(fs.access(`.openzeppelin/chain-${id}.lock`));
|
|
76
|
+
|
|
77
|
+
await assertOldName(t, id);
|
|
78
|
+
await manifest.lockedRun(async () => {
|
|
79
|
+
await fs.access(`.openzeppelin/chain-${id}.lock`);
|
|
80
|
+
const data = await manifest.read();
|
|
81
|
+
data.proxies.push({
|
|
82
|
+
address: '0x456',
|
|
83
|
+
txHash: '0x0',
|
|
84
|
+
kind: 'uups',
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
await assertOldName(t, id);
|
|
88
|
+
await manifest.write(data);
|
|
89
|
+
await assertNewName(t, id);
|
|
90
|
+
});
|
|
91
|
+
await assertNewName(t, id);
|
|
92
|
+
|
|
93
|
+
t.throwsAsync(fs.access(`.openzeppelin/chain-${id}.lock`));
|
|
94
|
+
|
|
95
|
+
// check that the contents were persisted
|
|
96
|
+
const data = await new Manifest(id).read();
|
|
97
|
+
t.true(data.proxies[0].address === '0x123');
|
|
98
|
+
t.true(data.proxies[1].address === '0x456');
|
|
99
|
+
await assertNewName(t, id);
|
|
100
|
+
|
|
101
|
+
t.throwsAsync(fs.access(`.openzeppelin/chain-${id}.lock`));
|
|
102
|
+
|
|
103
|
+
await deleteManifests(t, id);
|
|
104
|
+
});
|
|
3
105
|
|
|
4
106
|
test('manifest name for a known network', t => {
|
|
5
107
|
const manifest = new Manifest(1);
|
package/src/manifest.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { Deployment } from './deployment';
|
|
|
8
8
|
import type { StorageLayout } from './storage';
|
|
9
9
|
import { pick } from './utils/pick';
|
|
10
10
|
import { mapValues } from './utils/map-values';
|
|
11
|
+
import { UpgradesError } from './error';
|
|
11
12
|
|
|
12
13
|
const currentManifestVersion = '3.2';
|
|
13
14
|
|
|
@@ -40,15 +41,23 @@ function defaultManifest(): ManifestData {
|
|
|
40
41
|
const manifestDir = '.openzeppelin';
|
|
41
42
|
|
|
42
43
|
export class Manifest {
|
|
44
|
+
readonly chainId: number;
|
|
43
45
|
readonly file: string;
|
|
46
|
+
private readonly fallbackFile: string;
|
|
47
|
+
|
|
44
48
|
private locked = false;
|
|
45
49
|
|
|
46
50
|
static async forNetwork(provider: EthereumProvider): Promise<Manifest> {
|
|
47
51
|
return new Manifest(await getChainId(provider));
|
|
48
52
|
}
|
|
49
53
|
|
|
50
|
-
constructor(
|
|
51
|
-
|
|
54
|
+
constructor(chainId: number) {
|
|
55
|
+
this.chainId = chainId;
|
|
56
|
+
|
|
57
|
+
const fallbackName = `unknown-${this.chainId}`;
|
|
58
|
+
this.fallbackFile = path.join(manifestDir, `${fallbackName}.json`);
|
|
59
|
+
|
|
60
|
+
const name = networkNames[this.chainId] ?? fallbackName;
|
|
52
61
|
this.file = path.join(manifestDir, `${name}.json`);
|
|
53
62
|
}
|
|
54
63
|
|
|
@@ -89,10 +98,55 @@ export class Manifest {
|
|
|
89
98
|
});
|
|
90
99
|
}
|
|
91
100
|
|
|
101
|
+
private async exists(file: string): Promise<boolean> {
|
|
102
|
+
try {
|
|
103
|
+
await fs.access(file);
|
|
104
|
+
return true;
|
|
105
|
+
} catch (e: any) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private async readFile(): Promise<string> {
|
|
111
|
+
if (this.file === this.fallbackFile) {
|
|
112
|
+
return await fs.readFile(this.file, 'utf8');
|
|
113
|
+
} else {
|
|
114
|
+
const fallbackExists = await this.exists(this.fallbackFile);
|
|
115
|
+
const fileExists = await this.exists(this.file);
|
|
116
|
+
|
|
117
|
+
if (fileExists && fallbackExists) {
|
|
118
|
+
throw new UpgradesError(
|
|
119
|
+
`Network files with different names ${this.fallbackFile} and ${this.file} were found for the same network.`,
|
|
120
|
+
() =>
|
|
121
|
+
`More than one network file was found for chain ID ${this.chainId}. Determine which file is the most up to date version, then take a backup of and delete the other file.`,
|
|
122
|
+
);
|
|
123
|
+
} else if (fallbackExists) {
|
|
124
|
+
return await fs.readFile(this.fallbackFile, 'utf8');
|
|
125
|
+
} else {
|
|
126
|
+
return await fs.readFile(this.file, 'utf8');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private async writeFile(content: string): Promise<void> {
|
|
132
|
+
await this.renameFileIfRequired();
|
|
133
|
+
await fs.writeFile(this.file, content);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private async renameFileIfRequired() {
|
|
137
|
+
if (this.file !== this.fallbackFile && (await this.exists(this.fallbackFile))) {
|
|
138
|
+
try {
|
|
139
|
+
await fs.rename(this.fallbackFile, this.file);
|
|
140
|
+
} catch (e: any) {
|
|
141
|
+
throw new Error(`Failed to rename network file from ${this.fallbackFile} to ${this.file}: ${e.message}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
92
146
|
async read(): Promise<ManifestData> {
|
|
93
147
|
const release = this.locked ? undefined : await this.lock();
|
|
94
148
|
try {
|
|
95
|
-
const data = JSON.parse(await
|
|
149
|
+
const data = JSON.parse(await this.readFile()) as ManifestData;
|
|
96
150
|
return validateOrUpdateManifestVersion(data);
|
|
97
151
|
} catch (e: any) {
|
|
98
152
|
if (e.code === 'ENOENT') {
|
|
@@ -110,7 +164,7 @@ export class Manifest {
|
|
|
110
164
|
throw new Error('Manifest must be locked');
|
|
111
165
|
}
|
|
112
166
|
const normalized = normalizeManifestData(data);
|
|
113
|
-
await
|
|
167
|
+
await this.writeFile(JSON.stringify(normalized, null, 2) + '\n');
|
|
114
168
|
}
|
|
115
169
|
|
|
116
170
|
async lockedRun<T>(cb: () => Promise<T>): Promise<T> {
|
|
@@ -126,8 +180,10 @@ export class Manifest {
|
|
|
126
180
|
}
|
|
127
181
|
|
|
128
182
|
private async lock() {
|
|
129
|
-
|
|
130
|
-
|
|
183
|
+
const lockfileName = path.join(manifestDir, `chain-${this.chainId}`);
|
|
184
|
+
|
|
185
|
+
await fs.mkdir(path.dirname(lockfileName), { recursive: true });
|
|
186
|
+
const release = await lockfile.lock(lockfileName, { retries: 3, realpath: false });
|
|
131
187
|
this.locked = true;
|
|
132
188
|
return async () => {
|
|
133
189
|
await release();
|
package/src/provider.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import BN from 'bn.js';
|
|
2
|
-
|
|
3
1
|
export interface EthereumProvider {
|
|
4
2
|
send(method: 'web3_clientVersion', params: []): Promise<string>;
|
|
5
3
|
send(method: 'net_version', params: []): Promise<string>;
|
|
@@ -33,7 +31,7 @@ export async function getNetworkId(provider: EthereumProvider): Promise<string>
|
|
|
33
31
|
|
|
34
32
|
export async function getChainId(provider: EthereumProvider): Promise<number> {
|
|
35
33
|
const id = await provider.send('eth_chainId', []);
|
|
36
|
-
return
|
|
34
|
+
return parseInt(id.replace(/^0x/, ''), 16);
|
|
37
35
|
}
|
|
38
36
|
|
|
39
37
|
export async function getClientVersion(provider: EthereumProvider): Promise<string> {
|
|
@@ -104,6 +102,8 @@ export const networkNames: { [chainId in number]?: string } = Object.freeze({
|
|
|
104
102
|
4: 'rinkeby',
|
|
105
103
|
5: 'goerli',
|
|
106
104
|
42: 'kovan',
|
|
105
|
+
137: 'polygon',
|
|
106
|
+
80001: 'polygon-mumbai',
|
|
107
107
|
});
|
|
108
108
|
|
|
109
109
|
export async function isDevelopmentNetwork(provider: EthereumProvider): Promise<boolean> {
|
package/src/usage-error.ts
CHANGED
|
@@ -51,6 +51,9 @@ export class PrepareUpgradeUnsupportedError extends UpgradesError {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* @deprecated No longer used since forceImport() supports importing any contract.
|
|
56
|
+
*/
|
|
54
57
|
export class ForceImportUnsupportedError extends UpgradesError {
|
|
55
58
|
constructor(proxyOrBeaconAddress: string) {
|
|
56
59
|
super(
|
|
@@ -60,6 +63,25 @@ export class ForceImportUnsupportedError extends UpgradesError {
|
|
|
60
63
|
}
|
|
61
64
|
}
|
|
62
65
|
|
|
66
|
+
export class NoContractImportError extends UpgradesError {
|
|
67
|
+
constructor(address: string) {
|
|
68
|
+
super(
|
|
69
|
+
`No contract at address ${address}`,
|
|
70
|
+
() => `The address could not be imported because no contract was found at the address.`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export class ValidateUpdateRequiresKindError extends UpgradesError {
|
|
76
|
+
constructor() {
|
|
77
|
+
super(
|
|
78
|
+
'The `kind` option must be provided',
|
|
79
|
+
() =>
|
|
80
|
+
'When validating an upgrade from an implementation address, pass in the `kind` option for the kind of proxy that you are using.',
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
63
85
|
export async function assertNotProxy(provider: EthereumProvider, address: string) {
|
|
64
86
|
if (await isTransparentOrUUPSProxy(provider, address)) {
|
|
65
87
|
throw new UpgradesError(
|
package/src/validate/index.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';
|
|
@@ -5,13 +5,49 @@ import { logWarning } from '../utils/log';
|
|
|
5
5
|
// Backwards compatibility
|
|
6
6
|
export { silenceWarnings } from '../utils/log';
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Option for the kind of proxy that will be used.
|
|
10
|
+
*/
|
|
11
|
+
export interface ProxyKindOption {
|
|
12
|
+
/**
|
|
13
|
+
* The kind of proxy to deploy, upgrade or import, or the kind of proxy that the implementation will be used with. Defaults to `"transparent"`.
|
|
14
|
+
*/
|
|
15
|
+
kind?: ProxyDeployment['kind'];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Validation options in a standalone context (without storage layout comparisons with a previous implementation).
|
|
20
|
+
*/
|
|
21
|
+
export interface StandaloneValidationOptions extends ProxyKindOption {
|
|
22
|
+
/**
|
|
23
|
+
* @deprecated Equivalent to including "struct-definition" and "enum-definition" in unsafeAllow. No longer necessary.
|
|
24
|
+
*/
|
|
9
25
|
unsafeAllowCustomTypes?: boolean;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @deprecated Equivalent to including `"external-library-linking"` in {@link unsafeAllow}.
|
|
29
|
+
*/
|
|
10
30
|
unsafeAllowLinkedLibraries?: boolean;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Selectively disable one or more validation errors.
|
|
34
|
+
*/
|
|
35
|
+
unsafeAllow?: ValidationError['kind'][];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Validation options in the context of an upgrade (with storage layout comparisions with a previous implementation).
|
|
40
|
+
*/
|
|
41
|
+
export interface ValidationOptions extends StandaloneValidationOptions {
|
|
42
|
+
/**
|
|
43
|
+
* Configure storage layout check to allow variable renaming.
|
|
44
|
+
*/
|
|
11
45
|
unsafeAllowRenames?: boolean;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 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.
|
|
49
|
+
*/
|
|
12
50
|
unsafeSkipStorageCheck?: boolean;
|
|
13
|
-
unsafeAllow?: ValidationError['kind'][];
|
|
14
|
-
kind?: ProxyDeployment['kind'];
|
|
15
51
|
}
|
|
16
52
|
|
|
17
53
|
export const ValidationErrorUnsafeMessages: Record<ValidationError['kind'], string[]> = {
|