@openzeppelin/upgrades-core 1.15.0 → 1.17.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.
Files changed (88) hide show
  1. package/artifacts/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.dbg.json +1 -1
  2. package/artifacts/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.json +2 -2
  3. package/artifacts/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol/BeaconProxy.dbg.json +1 -1
  4. package/artifacts/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol/BeaconProxy.json +2 -2
  5. package/artifacts/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.dbg.json +1 -1
  6. package/artifacts/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.json +2 -2
  7. package/artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json +2 -2
  8. package/artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json +2 -2
  9. package/artifacts/AdminUpgradeabilityProxy.json +2 -2
  10. package/artifacts/ProxyAdmin.json +2 -2
  11. package/artifacts/build-info.json +5 -5
  12. package/artifacts/contracts/proxy/AdminUpgradeabilityProxy.sol/AdminUpgradeabilityProxy.json +2 -2
  13. package/artifacts/contracts/proxy/ProxyAdmin.sol/ProxyAdmin.json +2 -2
  14. package/contracts/Initializable.sol +4 -1
  15. package/dist/deployment.d.ts +1 -1
  16. package/dist/deployment.d.ts.map +1 -1
  17. package/dist/deployment.js +2 -3
  18. package/dist/deployment.js.map +1 -1
  19. package/dist/eip-1967.d.ts.map +1 -1
  20. package/dist/eip-1967.js +2 -7
  21. package/dist/eip-1967.js.map +1 -1
  22. package/dist/impl-address.d.ts.map +1 -1
  23. package/dist/impl-address.js +2 -1
  24. package/dist/impl-address.js.map +1 -1
  25. package/dist/impl-store.d.ts +1 -0
  26. package/dist/impl-store.d.ts.map +1 -1
  27. package/dist/impl-store.js +15 -13
  28. package/dist/impl-store.js.map +1 -1
  29. package/dist/index.d.ts +1 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +2 -1
  32. package/dist/index.js.map +1 -1
  33. package/dist/manifest-storage-layout.js +1 -1
  34. package/dist/manifest-storage-layout.js.map +1 -1
  35. package/dist/manifest.d.ts +5 -0
  36. package/dist/manifest.d.ts.map +1 -1
  37. package/dist/manifest.js +54 -10
  38. package/dist/manifest.js.map +1 -1
  39. package/dist/manifest.test.js +82 -0
  40. package/dist/manifest.test.js.map +1 -1
  41. package/dist/provider.d.ts.map +1 -1
  42. package/dist/provider.js +4 -6
  43. package/dist/provider.js.map +1 -1
  44. package/dist/provider.test.js +5 -5
  45. package/dist/provider.test.js.map +1 -1
  46. package/dist/proxy-kind.js +1 -2
  47. package/dist/proxy-kind.js.map +1 -1
  48. package/dist/scripts/migrate-oz-cli-project.js +1 -1
  49. package/dist/scripts/migrate-oz-cli-project.js.map +1 -1
  50. package/dist/scripts/migrate-oz-cli-project.test.js +1 -1
  51. package/dist/scripts/migrate-oz-cli-project.test.js.map +1 -1
  52. package/dist/src-decoder.js +1 -2
  53. package/dist/src-decoder.js.map +1 -1
  54. package/dist/storage/compare.js +3 -5
  55. package/dist/storage/compare.js.map +1 -1
  56. package/dist/storage/extract.js +6 -9
  57. package/dist/storage/extract.js.map +1 -1
  58. package/dist/storage/layout.js +3 -4
  59. package/dist/storage/layout.js.map +1 -1
  60. package/dist/storage.test.js +1 -1
  61. package/dist/storage.test.js.map +1 -1
  62. package/dist/stub-provider.d.ts +1 -1
  63. package/dist/stub-provider.d.ts.map +1 -1
  64. package/dist/stub-provider.js +5 -7
  65. package/dist/stub-provider.js.map +1 -1
  66. package/dist/utils/parse-type-id.js +2 -3
  67. package/dist/utils/parse-type-id.js.map +1 -1
  68. package/dist/validate/data.js +1 -2
  69. package/dist/validate/data.js.map +1 -1
  70. package/dist/validate/overrides.js +7 -7
  71. package/dist/validate/overrides.js.map +1 -1
  72. package/dist/validate/query.js +3 -5
  73. package/dist/validate/query.js.map +1 -1
  74. package/dist/validate/report.js +1 -2
  75. package/dist/validate/report.js.map +1 -1
  76. package/dist/validate/run.js +6 -10
  77. package/dist/validate/run.js.map +1 -1
  78. package/dist/version.js +1 -1
  79. package/dist/version.js.map +1 -1
  80. package/package.json +2 -4
  81. package/src/deployment.ts +3 -3
  82. package/src/eip-1967.ts +2 -4
  83. package/src/impl-address.ts +2 -1
  84. package/src/impl-store.ts +16 -6
  85. package/src/index.ts +1 -1
  86. package/src/manifest.test.ts +103 -1
  87. package/src/manifest.ts +62 -6
  88. package/src/provider.ts +3 -3
package/src/impl-store.ts CHANGED
@@ -26,17 +26,17 @@ export interface ManifestField<T> {
26
26
  * @param deploy the deploy function
27
27
  * @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.
28
28
  * @param merge if true, adds a deployment to existing deployment by merging their addresses. Defaults to false.
29
- * @returns the deployment address
29
+ * @returns the deployment
30
30
  * @throws {InvalidDeployment} if the deployment is invalid
31
31
  * @throws {TransactionMinedTimeout} if the transaction was not confirmed within the timeout period
32
32
  */
33
- async function fetchOrDeployGeneric<T extends Deployment>(
33
+ async function fetchOrDeployGeneric<T extends Deployment, U extends T = T>(
34
34
  lens: ManifestLens<T>,
35
35
  provider: EthereumProvider,
36
- deploy: () => Promise<T>,
36
+ deploy: () => Promise<U>,
37
37
  opts?: DeployOpts,
38
38
  merge?: boolean,
39
- ): Promise<string> {
39
+ ): Promise<U | Deployment> {
40
40
  const manifest = await Manifest.forNetwork(provider);
41
41
 
42
42
  try {
@@ -69,7 +69,7 @@ async function fetchOrDeployGeneric<T extends Deployment>(
69
69
 
70
70
  await waitAndValidateDeployment(provider, deployment, lens.type, opts);
71
71
 
72
- return deployment.address;
72
+ return deployment;
73
73
  } catch (e) {
74
74
  // If we run into a deployment error, we remove it from the manifest.
75
75
  if (e instanceof InvalidDeployment) {
@@ -105,6 +105,16 @@ export async function fetchOrDeploy(
105
105
  opts?: DeployOpts,
106
106
  merge?: boolean,
107
107
  ): Promise<string> {
108
+ return (await fetchOrDeployGeneric(implLens(version.linkedWithoutMetadata), provider, deploy, opts, merge)).address;
109
+ }
110
+
111
+ export async function fetchOrDeployGetDeployment<T extends ImplDeployment>(
112
+ version: Version,
113
+ provider: EthereumProvider,
114
+ deploy: () => Promise<T>,
115
+ opts?: DeployOpts,
116
+ merge?: boolean,
117
+ ): Promise<T | Deployment> {
108
118
  return fetchOrDeployGeneric(implLens(version.linkedWithoutMetadata), provider, deploy, opts, merge);
109
119
  }
110
120
 
@@ -146,7 +156,7 @@ export async function fetchOrDeployAdmin(
146
156
  deploy: () => Promise<Deployment>,
147
157
  opts?: DeployOpts,
148
158
  ): Promise<string> {
149
- return fetchOrDeployGeneric(adminLens, provider, deploy, opts);
159
+ return (await fetchOrDeployGeneric(adminLens, provider, deploy, opts)).address;
150
160
  }
151
161
 
152
162
  const adminLens = lens('proxy admin', 'proxy admin', data => ({
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from './validate';
2
- export { fetchOrDeploy, fetchOrDeployAdmin } from './impl-store';
2
+ export { fetchOrDeploy, fetchOrDeployAdmin, fetchOrDeployGetDeployment } from './impl-store';
3
3
  export * from './version';
4
4
  export * from './storage';
5
5
  export {
@@ -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(readonly chainId: number) {
51
- const name = networkNames[chainId] ?? `unknown-${chainId}`;
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 fs.readFile(this.file, 'utf8')) as ManifestData;
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 fs.writeFile(this.file, JSON.stringify(normalized, null, 2) + '\n');
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
- await fs.mkdir(path.dirname(this.file), { recursive: true });
130
- const release = await lockfile.lock(this.file, { retries: 3, realpath: false });
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 new BN(id.replace(/^0x/, ''), 'hex').toNumber();
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> {