@openzeppelin/upgrades-core 1.22.0 → 1.24.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/artifacts/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.dbg.json +1 -1
- package/artifacts/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol/BeaconProxy.dbg.json +1 -1
- package/artifacts/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.dbg.json +1 -1
- package/artifacts/build-info.json +2 -2
- package/dist/manifest.d.ts +14 -3
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +79 -13
- package/dist/manifest.js.map +1 -1
- package/dist/manifest.test.js +249 -4
- package/dist/manifest.test.js.map +1 -1
- package/dist/provider.d.ts +14 -0
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +12 -2
- package/dist/provider.js.map +1 -1
- package/dist/storage/compare.js +2 -2
- package/dist/storage/gap.js +1 -1
- package/dist/storage/gap.js.map +1 -1
- package/dist/storage/report-gap.test.js +75 -0
- package/dist/storage/report-gap.test.js.map +1 -1
- package/dist/storage/report.js +1 -1
- package/dist/storage/report.js.map +1 -1
- package/package.json +2 -2
- package/src/manifest.test.ts +320 -4
- package/src/manifest.ts +104 -15
- package/src/provider.ts +22 -1
- package/src/storage/compare.ts +2 -2
- package/src/storage/gap.ts +1 -1
- package/src/storage/report-gap.test.ts +87 -0
- package/src/storage/report-gap.test.ts.md +30 -0
- package/src/storage/report-gap.test.ts.snap +0 -0
- package/src/storage/report.ts +1 -1
package/src/manifest.test.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
+
import os from 'os';
|
|
1
2
|
import test, { ExecutionContext } from 'ava';
|
|
2
3
|
import { Manifest, ManifestData, normalizeManifestData } from './manifest';
|
|
3
4
|
import { promises as fs } from 'fs';
|
|
4
5
|
import path from 'path';
|
|
5
6
|
|
|
6
|
-
async function writeTestManifest(file: string) {
|
|
7
|
+
async function writeTestManifest(file: string, address?: string) {
|
|
7
8
|
const testManifest = {
|
|
8
9
|
manifestVersion: '3.2',
|
|
9
10
|
impls: {},
|
|
10
11
|
proxies: [
|
|
11
12
|
{
|
|
12
|
-
address: '0x123',
|
|
13
|
+
address: address ?? '0x123',
|
|
13
14
|
txHash: '0x0',
|
|
14
15
|
kind: 'uups',
|
|
15
16
|
},
|
|
@@ -94,8 +95,8 @@ test.serial('rename manifest', async t => {
|
|
|
94
95
|
|
|
95
96
|
// check that the contents were persisted
|
|
96
97
|
const data = await new Manifest(id).read();
|
|
97
|
-
t.
|
|
98
|
-
t.
|
|
98
|
+
t.is(data.proxies[0].address, '0x123');
|
|
99
|
+
t.is(data.proxies[1].address, '0x456');
|
|
99
100
|
await assertNewName(t, id);
|
|
100
101
|
|
|
101
102
|
t.throwsAsync(fs.access(`.openzeppelin/chain-${id}.lock`));
|
|
@@ -103,17 +104,332 @@ test.serial('rename manifest', async t => {
|
|
|
103
104
|
await deleteManifests(t, id);
|
|
104
105
|
});
|
|
105
106
|
|
|
107
|
+
test.serial('rename hardhat from unknown to dev manifest', async t => {
|
|
108
|
+
const id = 31337;
|
|
109
|
+
|
|
110
|
+
await deleteFile(t, `.openzeppelin/unknown-${id}.json`);
|
|
111
|
+
await writeTestManifest(`.openzeppelin/unknown-${id}.json`);
|
|
112
|
+
|
|
113
|
+
const instanceId = '0xaa0';
|
|
114
|
+
const devInstanceMetadata = { networkName: 'hardhat', instanceId: instanceId };
|
|
115
|
+
|
|
116
|
+
const manifest = new Manifest(id, devInstanceMetadata, os.tmpdir());
|
|
117
|
+
|
|
118
|
+
await fs.access(`.openzeppelin/unknown-${id}.json`);
|
|
119
|
+
await manifest.lockedRun(async () => {
|
|
120
|
+
await fs.access(`${os.tmpdir()}/openzeppelin-upgrades/chain-${id}-${instanceId}.lock`);
|
|
121
|
+
const data = await manifest.read();
|
|
122
|
+
data.proxies.push({
|
|
123
|
+
address: '0x456',
|
|
124
|
+
txHash: '0x0',
|
|
125
|
+
kind: 'uups',
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
await fs.access(`.openzeppelin/unknown-${id}.json`);
|
|
129
|
+
await manifest.write(data);
|
|
130
|
+
});
|
|
131
|
+
t.throwsAsync(fs.access(`.openzeppelin/unknown-${id}.json`));
|
|
132
|
+
await fs.access(`${os.tmpdir()}/openzeppelin-upgrades/hardhat-${id}-${instanceId}.json`);
|
|
133
|
+
|
|
134
|
+
const dev = await new Manifest(id, devInstanceMetadata, os.tmpdir()).read();
|
|
135
|
+
t.is(dev.proxies.length, 2);
|
|
136
|
+
t.is(dev.proxies[0].address, '0x123');
|
|
137
|
+
t.is(dev.proxies[1].address, '0x456');
|
|
138
|
+
|
|
139
|
+
await deleteFile(t, `.openzeppelin/unknown-${id}.json`);
|
|
140
|
+
await deleteFile(t, `${os.tmpdir()}/openzeppelin-upgrades/hardhat-${id}-${instanceId}.json`);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test.serial('forked chain from known network with fallback name', async t => {
|
|
144
|
+
const forkedId = 80001;
|
|
145
|
+
const devId = 55555;
|
|
146
|
+
|
|
147
|
+
await deleteManifests(t, forkedId);
|
|
148
|
+
|
|
149
|
+
await writeTestManifest(`.openzeppelin/unknown-${forkedId}.json`);
|
|
150
|
+
|
|
151
|
+
const instanceId = '0xaaa';
|
|
152
|
+
const devInstanceMetadata = { networkName: 'hardhat', instanceId: instanceId, forkedNetwork: { chainId: forkedId } };
|
|
153
|
+
|
|
154
|
+
const manifest = new Manifest(devId, devInstanceMetadata, os.tmpdir());
|
|
155
|
+
|
|
156
|
+
await assertOldName(t, forkedId);
|
|
157
|
+
await manifest.lockedRun(async () => {
|
|
158
|
+
await fs.access(`${os.tmpdir()}/openzeppelin-upgrades/chain-${devId}-${instanceId}.lock`);
|
|
159
|
+
const data = await manifest.read();
|
|
160
|
+
data.proxies.push({
|
|
161
|
+
address: '0x456',
|
|
162
|
+
txHash: '0x0',
|
|
163
|
+
kind: 'uups',
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
await assertOldName(t, forkedId);
|
|
167
|
+
await manifest.write(data);
|
|
168
|
+
await assertOldName(t, forkedId); // original network file should not be changed
|
|
169
|
+
});
|
|
170
|
+
t.throwsAsync(fs.access(`.openzeppelin/chain-${forkedId}.lock`));
|
|
171
|
+
t.throwsAsync(fs.access(`${os.tmpdir()}/openzeppelin-upgrades/chain-${devId}-${instanceId}.lock`));
|
|
172
|
+
await fs.access(`${os.tmpdir()}/openzeppelin-upgrades/hardhat-${devId}-${instanceId}.json`);
|
|
173
|
+
|
|
174
|
+
// check that the contents were NOT persisted to original manifest
|
|
175
|
+
const orig = await new Manifest(forkedId).read();
|
|
176
|
+
t.is(orig.proxies.length, 1);
|
|
177
|
+
t.is(orig.proxies[0].address, '0x123');
|
|
178
|
+
|
|
179
|
+
// check that the contents were persisted to dev copy of manifest
|
|
180
|
+
const dev = await new Manifest(devId, devInstanceMetadata, os.tmpdir()).read();
|
|
181
|
+
t.is(dev.proxies.length, 2);
|
|
182
|
+
t.is(dev.proxies[0].address, '0x123');
|
|
183
|
+
t.is(dev.proxies[1].address, '0x456');
|
|
184
|
+
|
|
185
|
+
await deleteManifests(t, forkedId);
|
|
186
|
+
await deleteFile(t, `${os.tmpdir()}/openzeppelin-upgrades/hardhat-${devId}-${instanceId}.json`);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test.serial('forked chain from known network with actual name', async t => {
|
|
190
|
+
const forkedId = 80001;
|
|
191
|
+
const devId = 55555;
|
|
192
|
+
|
|
193
|
+
await deleteManifests(t, forkedId);
|
|
194
|
+
|
|
195
|
+
await writeTestManifest(`.openzeppelin/polygon-mumbai.json`);
|
|
196
|
+
|
|
197
|
+
const instanceId = '0xbbb';
|
|
198
|
+
const devInstanceMetadata = { networkName: 'hardhat', instanceId: instanceId, forkedNetwork: { chainId: forkedId } };
|
|
199
|
+
|
|
200
|
+
const manifest = new Manifest(devId, devInstanceMetadata, os.tmpdir());
|
|
201
|
+
|
|
202
|
+
await assertNewName(t, forkedId);
|
|
203
|
+
await manifest.lockedRun(async () => {
|
|
204
|
+
await fs.access(`${os.tmpdir()}/openzeppelin-upgrades/chain-${devId}-${instanceId}.lock`);
|
|
205
|
+
const data = await manifest.read();
|
|
206
|
+
data.proxies.push({
|
|
207
|
+
address: '0x456',
|
|
208
|
+
txHash: '0x0',
|
|
209
|
+
kind: 'uups',
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
await assertNewName(t, forkedId);
|
|
213
|
+
await manifest.write(data);
|
|
214
|
+
await assertNewName(t, forkedId); // original network file should not be changed
|
|
215
|
+
});
|
|
216
|
+
t.throwsAsync(fs.access(`.openzeppelin/chain-${forkedId}.lock`));
|
|
217
|
+
t.throwsAsync(fs.access(`${os.tmpdir()}/openzeppelin-upgrades/chain-${devId}-${instanceId}.lock`));
|
|
218
|
+
await fs.access(`${os.tmpdir()}/openzeppelin-upgrades/hardhat-${devId}-${instanceId}.json`);
|
|
219
|
+
|
|
220
|
+
// check that the contents were NOT persisted to original manifest
|
|
221
|
+
const orig = await new Manifest(forkedId).read();
|
|
222
|
+
t.is(orig.proxies.length, 1);
|
|
223
|
+
t.is(orig.proxies[0].address, '0x123');
|
|
224
|
+
|
|
225
|
+
// check that the contents were persisted to dev copy of manifest
|
|
226
|
+
const dev = await new Manifest(devId, devInstanceMetadata, os.tmpdir()).read();
|
|
227
|
+
t.is(dev.proxies.length, 2);
|
|
228
|
+
t.is(dev.proxies[0].address, '0x123');
|
|
229
|
+
t.is(dev.proxies[1].address, '0x456');
|
|
230
|
+
|
|
231
|
+
await deleteManifests(t, forkedId);
|
|
232
|
+
await deleteFile(t, `${os.tmpdir()}/openzeppelin-upgrades/hardhat-${devId}-${instanceId}.json`);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test.serial('forked chain, real manifest already locked', async t => {
|
|
236
|
+
const forkedId = 80001;
|
|
237
|
+
const devId = 55555;
|
|
238
|
+
|
|
239
|
+
const instanceId = '0xccc';
|
|
240
|
+
const devInstanceMetadata = { networkName: 'hardhat', instanceId: instanceId, forkedNetwork: { chainId: forkedId } };
|
|
241
|
+
|
|
242
|
+
const realManifest = new Manifest(forkedId);
|
|
243
|
+
const devManifest = new Manifest(devId, devInstanceMetadata, os.tmpdir());
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
await realManifest.lockedRun(async () => {
|
|
247
|
+
await devManifest.read(0);
|
|
248
|
+
});
|
|
249
|
+
t.fail();
|
|
250
|
+
} catch (e: any) {
|
|
251
|
+
t.is(e.code, 'ELOCKED');
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test.serial('forked chain, real manifest already locked but dev manifest exists', async t => {
|
|
256
|
+
const forkedId = 80001;
|
|
257
|
+
const devId = 55555;
|
|
258
|
+
|
|
259
|
+
const instanceId = '0xddd';
|
|
260
|
+
const devInstanceMetadata = { networkName: 'hardhat', instanceId: instanceId, forkedNetwork: { chainId: forkedId } };
|
|
261
|
+
|
|
262
|
+
const realManifest = new Manifest(forkedId);
|
|
263
|
+
const devManifest = new Manifest(devId, devInstanceMetadata, os.tmpdir());
|
|
264
|
+
|
|
265
|
+
// write dev manifest
|
|
266
|
+
await writeTestManifest(devManifest.file, '0x999');
|
|
267
|
+
|
|
268
|
+
// then write real manifest
|
|
269
|
+
await deleteManifests(t, forkedId);
|
|
270
|
+
await writeTestManifest(`.openzeppelin/polygon-mumbai.json`);
|
|
271
|
+
|
|
272
|
+
let devManifestContents;
|
|
273
|
+
await realManifest.lockedRun(async () => {
|
|
274
|
+
// should ignore real manifest and real lock file
|
|
275
|
+
devManifestContents = await devManifest.read();
|
|
276
|
+
t.is(devManifestContents.proxies.length, 1);
|
|
277
|
+
t.is(devManifestContents.proxies[0].address, '0x999');
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
await deleteManifests(t, forkedId);
|
|
281
|
+
await deleteFile(t, `${os.tmpdir()}/openzeppelin-upgrades/hardhat-${devId}-${instanceId}.json`);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test.serial('forked chain without existing manifest', async t => {
|
|
285
|
+
const forkedId = 80001;
|
|
286
|
+
const devId = 55555;
|
|
287
|
+
|
|
288
|
+
await deleteManifests(t, forkedId);
|
|
289
|
+
|
|
290
|
+
const instanceId = '0xeee';
|
|
291
|
+
const devInstanceMetadata = { networkName: 'hardhat', instanceId: instanceId, forkedNetwork: { chainId: forkedId } };
|
|
292
|
+
|
|
293
|
+
const manifest = new Manifest(devId, devInstanceMetadata, os.tmpdir());
|
|
294
|
+
|
|
295
|
+
await manifest.lockedRun(async () => {
|
|
296
|
+
await fs.access(`${os.tmpdir()}/openzeppelin-upgrades/chain-${devId}-${instanceId}.lock`);
|
|
297
|
+
const data = await manifest.read();
|
|
298
|
+
data.proxies.push({
|
|
299
|
+
address: '0x456',
|
|
300
|
+
txHash: '0x0',
|
|
301
|
+
kind: 'uups',
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
await manifest.write(data);
|
|
305
|
+
});
|
|
306
|
+
t.throwsAsync(fs.access(`${os.tmpdir()}/openzeppelin-upgrades/chain-${devId}-${instanceId}.lock`));
|
|
307
|
+
await fs.access(`${os.tmpdir()}/openzeppelin-upgrades/hardhat-${devId}-${instanceId}.json`);
|
|
308
|
+
|
|
309
|
+
// check that the contents were NOT persisted to regular manifest
|
|
310
|
+
t.throwsAsync(fs.access(`.openzeppelin/unknown-${forkedId}.json`));
|
|
311
|
+
t.throwsAsync(fs.access(`.openzeppelin/polygon-mumbai.json`));
|
|
312
|
+
|
|
313
|
+
// check that the contents were persisted to dev copy of manifest
|
|
314
|
+
const dev = await new Manifest(devId, devInstanceMetadata, os.tmpdir()).read();
|
|
315
|
+
t.is(dev.proxies.length, 1);
|
|
316
|
+
t.is(dev.proxies[0].address, '0x456');
|
|
317
|
+
|
|
318
|
+
await deleteManifests(t, forkedId);
|
|
319
|
+
await deleteFile(t, `${os.tmpdir()}/openzeppelin-upgrades/hardhat-${devId}-${instanceId}.json`);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test.serial('dev instance with known network id', async t => {
|
|
323
|
+
const devId = 80001;
|
|
324
|
+
|
|
325
|
+
await deleteManifests(t, devId);
|
|
326
|
+
|
|
327
|
+
// dev instance without forking, so this real manifest should not be actually used
|
|
328
|
+
await writeTestManifest(`.openzeppelin/polygon-mumbai.json`);
|
|
329
|
+
|
|
330
|
+
const instanceId = '0xfff';
|
|
331
|
+
const devInstanceMetadata = { networkName: 'hardhat', instanceId: instanceId };
|
|
332
|
+
|
|
333
|
+
const manifest = new Manifest(devId, devInstanceMetadata, os.tmpdir());
|
|
334
|
+
|
|
335
|
+
await manifest.lockedRun(async () => {
|
|
336
|
+
await fs.access(`${os.tmpdir()}/openzeppelin-upgrades/chain-${devId}-${instanceId}.lock`);
|
|
337
|
+
const data = await manifest.read();
|
|
338
|
+
data.proxies.push({
|
|
339
|
+
address: '0x456',
|
|
340
|
+
txHash: '0x0',
|
|
341
|
+
kind: 'uups',
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
await manifest.write(data);
|
|
345
|
+
});
|
|
346
|
+
t.throwsAsync(fs.access(`${os.tmpdir()}/openzeppelin-upgrades/chain-${devId}-${instanceId}.lock`));
|
|
347
|
+
await fs.access(`${os.tmpdir()}/openzeppelin-upgrades/hardhat-${devId}-${instanceId}.json`);
|
|
348
|
+
|
|
349
|
+
// check that the contents were NOT persisted to original manifest
|
|
350
|
+
const orig = await new Manifest(devId).read();
|
|
351
|
+
t.is(orig.proxies.length, 1);
|
|
352
|
+
t.is(orig.proxies[0].address, '0x123');
|
|
353
|
+
|
|
354
|
+
// check that the contents were persisted to dev manifest without original contents
|
|
355
|
+
const dev = await new Manifest(devId, devInstanceMetadata, os.tmpdir()).read();
|
|
356
|
+
t.is(dev.proxies.length, 1);
|
|
357
|
+
t.is(dev.proxies[0].address, '0x456');
|
|
358
|
+
|
|
359
|
+
await deleteManifests(t, devId);
|
|
360
|
+
await deleteFile(t, `${os.tmpdir()}/openzeppelin-upgrades/hardhat-${devId}-${instanceId}.json`);
|
|
361
|
+
});
|
|
362
|
+
|
|
106
363
|
test('manifest name for a known network', t => {
|
|
107
364
|
const manifest = new Manifest(1);
|
|
108
365
|
t.is(manifest.file, '.openzeppelin/mainnet.json');
|
|
109
366
|
});
|
|
110
367
|
|
|
368
|
+
test('manifest name for a known network, development instance', t => {
|
|
369
|
+
const chainId = 1;
|
|
370
|
+
const instanceId = '0x11111';
|
|
371
|
+
const devInstanceMetadata = { networkName: 'hardhat', instanceId: instanceId };
|
|
372
|
+
|
|
373
|
+
const manifest = new Manifest(chainId, devInstanceMetadata, '/tmp');
|
|
374
|
+
|
|
375
|
+
const expectedPath = `/tmp/openzeppelin-upgrades/hardhat-${chainId}-${instanceId}.json`;
|
|
376
|
+
t.is(manifest.file, expectedPath);
|
|
377
|
+
t.is(manifest.fallbackFile, expectedPath);
|
|
378
|
+
});
|
|
379
|
+
|
|
111
380
|
test('manifest name for an unknown network', t => {
|
|
112
381
|
const id = 55555;
|
|
113
382
|
const manifest = new Manifest(id);
|
|
114
383
|
t.is(manifest.file, `.openzeppelin/unknown-${id}.json`);
|
|
115
384
|
});
|
|
116
385
|
|
|
386
|
+
test('manifest name for an unknown network, development instance, non hardhat', t => {
|
|
387
|
+
const chainId = 55555;
|
|
388
|
+
const instanceId = '0x22222';
|
|
389
|
+
const devInstanceMetadata = { networkName: 'dev', instanceId: instanceId };
|
|
390
|
+
|
|
391
|
+
const manifest = new Manifest(chainId, devInstanceMetadata, '/tmp');
|
|
392
|
+
|
|
393
|
+
const expectedPath = `/tmp/openzeppelin-upgrades/dev-${chainId}-${instanceId}.json`;
|
|
394
|
+
t.is(manifest.file, expectedPath);
|
|
395
|
+
t.is(manifest.fallbackFile, expectedPath);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
test('manifest name for an unknown network, development instance, hardhat', t => {
|
|
399
|
+
const chainId = 31337;
|
|
400
|
+
const instanceId = '0x22223';
|
|
401
|
+
const devInstanceMetadata = { networkName: 'dev', instanceId: instanceId };
|
|
402
|
+
|
|
403
|
+
const manifest = new Manifest(chainId, devInstanceMetadata, '/tmp');
|
|
404
|
+
|
|
405
|
+
const expectedPath = `/tmp/openzeppelin-upgrades/dev-${chainId}-${instanceId}.json`;
|
|
406
|
+
t.is(manifest.file, expectedPath);
|
|
407
|
+
t.is(manifest.fallbackFile, `.openzeppelin/unknown-${chainId}.json`);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
test('manifest dev instance without tmp dir param', t => {
|
|
411
|
+
const chainId = 1;
|
|
412
|
+
const instanceId = '0x33333';
|
|
413
|
+
const devInstanceMetadata = { networkName: 'hardhat', instanceId: instanceId };
|
|
414
|
+
|
|
415
|
+
t.throws(() => new Manifest(chainId, devInstanceMetadata));
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
test('manifest lock file name for a development network instance', async t => {
|
|
419
|
+
const chainId = 55555;
|
|
420
|
+
const instanceId = '0x55555';
|
|
421
|
+
const devInstanceMetadata = { networkName: 'hardhat', instanceId: instanceId };
|
|
422
|
+
|
|
423
|
+
const manifest = new Manifest(chainId, devInstanceMetadata, '/tmp');
|
|
424
|
+
|
|
425
|
+
await manifest.lockedRun(async () => {
|
|
426
|
+
t.throwsAsync(fs.access(`.openzeppelin/chain-${chainId}.lock`));
|
|
427
|
+
t.throwsAsync(fs.access(`.openzeppelin/chain-${chainId}-${instanceId}.lock`));
|
|
428
|
+
t.throwsAsync(fs.access(`/tmp/openzeppelin-upgrades/chain-${chainId}.lock`));
|
|
429
|
+
await fs.access(`/tmp/openzeppelin-upgrades/chain-${chainId}-${instanceId}.lock`);
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
|
|
117
433
|
test('normalize manifest', t => {
|
|
118
434
|
const deployment = {
|
|
119
435
|
address: '0x1234',
|
package/src/manifest.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import os from 'os';
|
|
1
2
|
import path from 'path';
|
|
2
3
|
import { promises as fs } from 'fs';
|
|
3
|
-
import { EthereumProvider, getChainId, networkNames } from './provider';
|
|
4
|
+
import { EthereumProvider, getChainId, getHardhatMetadata, networkNames } from './provider';
|
|
4
5
|
import lockfile from 'proper-lockfile';
|
|
5
6
|
import { compare as compareVersions } from 'compare-versions';
|
|
6
7
|
|
|
@@ -9,6 +10,8 @@ import type { StorageLayout } from './storage';
|
|
|
9
10
|
import { pick } from './utils/pick';
|
|
10
11
|
import { mapValues } from './utils/map-values';
|
|
11
12
|
import { UpgradesError } from './error';
|
|
13
|
+
import debug from './utils/debug';
|
|
14
|
+
import { assert } from './utils/assert';
|
|
12
15
|
|
|
13
16
|
const currentManifestVersion = '3.2';
|
|
14
17
|
|
|
@@ -38,27 +41,109 @@ function defaultManifest(): ManifestData {
|
|
|
38
41
|
};
|
|
39
42
|
}
|
|
40
43
|
|
|
41
|
-
const
|
|
44
|
+
const MANIFEST_DEFAULT_DIR = '.openzeppelin';
|
|
45
|
+
const MANIFEST_TEMP_DIR = 'openzeppelin-upgrades';
|
|
46
|
+
|
|
47
|
+
async function getDevInstanceMetadata(
|
|
48
|
+
provider: EthereumProvider,
|
|
49
|
+
chainId: number,
|
|
50
|
+
): Promise<DevInstanceMetadata | undefined> {
|
|
51
|
+
let hardhatMetadata;
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
hardhatMetadata = await getHardhatMetadata(provider);
|
|
55
|
+
} catch (e: unknown) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (hardhatMetadata.chainId !== chainId) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`Broken invariant: Hardhat metadata's chainId ${hardhatMetadata.chainId} does not match eth_chainId ${chainId}`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
networkName: 'hardhat',
|
|
67
|
+
instanceId: hardhatMetadata.instanceId,
|
|
68
|
+
forkedNetwork: hardhatMetadata.forkedNetwork,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getSuffix(chainId: number, devInstanceMetadata?: DevInstanceMetadata) {
|
|
73
|
+
if (devInstanceMetadata !== undefined) {
|
|
74
|
+
return `${chainId}-${devInstanceMetadata.instanceId}`;
|
|
75
|
+
} else {
|
|
76
|
+
return `${chainId}`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface DevInstanceMetadata {
|
|
81
|
+
networkName: string;
|
|
82
|
+
instanceId: string;
|
|
83
|
+
forkedNetwork?: {
|
|
84
|
+
// The chainId of the network that is being forked
|
|
85
|
+
chainId: number;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
42
88
|
|
|
43
89
|
export class Manifest {
|
|
44
90
|
readonly chainId: number;
|
|
45
91
|
readonly file: string;
|
|
46
|
-
|
|
92
|
+
readonly fallbackFile: string;
|
|
93
|
+
private readonly dir: string;
|
|
94
|
+
|
|
95
|
+
private readonly chainIdSuffix: string;
|
|
96
|
+
private readonly parent?: Manifest;
|
|
47
97
|
|
|
48
98
|
private locked = false;
|
|
49
99
|
|
|
50
100
|
static async forNetwork(provider: EthereumProvider): Promise<Manifest> {
|
|
51
|
-
|
|
101
|
+
const chainId = await getChainId(provider);
|
|
102
|
+
const devInstanceMetadata = await getDevInstanceMetadata(provider, chainId);
|
|
103
|
+
if (devInstanceMetadata !== undefined) {
|
|
104
|
+
return new Manifest(chainId, devInstanceMetadata, os.tmpdir());
|
|
105
|
+
} else {
|
|
106
|
+
return new Manifest(chainId);
|
|
107
|
+
}
|
|
52
108
|
}
|
|
53
109
|
|
|
54
|
-
constructor(chainId: number) {
|
|
110
|
+
constructor(chainId: number, devInstanceMetadata?: DevInstanceMetadata, osTmpDir?: string) {
|
|
55
111
|
this.chainId = chainId;
|
|
112
|
+
this.chainIdSuffix = getSuffix(chainId, devInstanceMetadata);
|
|
56
113
|
|
|
57
|
-
const
|
|
58
|
-
this.fallbackFile = path.join(manifestDir, `${fallbackName}.json`);
|
|
114
|
+
const defaultFallbackName = `unknown-${chainId}`;
|
|
59
115
|
|
|
60
|
-
|
|
61
|
-
|
|
116
|
+
if (devInstanceMetadata !== undefined) {
|
|
117
|
+
assert(osTmpDir !== undefined);
|
|
118
|
+
this.dir = path.join(osTmpDir, MANIFEST_TEMP_DIR);
|
|
119
|
+
debug('development manifest directory:', this.dir);
|
|
120
|
+
|
|
121
|
+
const devName = `${devInstanceMetadata.networkName}-${this.chainIdSuffix}`;
|
|
122
|
+
const devFile = path.join(this.dir, `${devName}.json`);
|
|
123
|
+
|
|
124
|
+
this.file = devFile;
|
|
125
|
+
if (chainId === 31337) {
|
|
126
|
+
this.fallbackFile = path.join(MANIFEST_DEFAULT_DIR, `${defaultFallbackName}.json`);
|
|
127
|
+
} else {
|
|
128
|
+
this.fallbackFile = devFile;
|
|
129
|
+
}
|
|
130
|
+
debug('development manifest file:', this.file, 'fallback file:', this.fallbackFile);
|
|
131
|
+
|
|
132
|
+
if (devInstanceMetadata.forkedNetwork !== undefined) {
|
|
133
|
+
const forkedChainId = devInstanceMetadata.forkedNetwork.chainId;
|
|
134
|
+
debug('forked network chain id:', forkedChainId);
|
|
135
|
+
|
|
136
|
+
this.parent = new Manifest(forkedChainId);
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
this.dir = MANIFEST_DEFAULT_DIR;
|
|
140
|
+
|
|
141
|
+
const networkName = networkNames[chainId];
|
|
142
|
+
this.file = path.join(MANIFEST_DEFAULT_DIR, `${networkName ?? defaultFallbackName}.json`);
|
|
143
|
+
this.fallbackFile = path.join(MANIFEST_DEFAULT_DIR, `${defaultFallbackName}.json`);
|
|
144
|
+
|
|
145
|
+
debug('manifest file:', this.file, 'fallback file:', this.fallbackFile);
|
|
146
|
+
}
|
|
62
147
|
}
|
|
63
148
|
|
|
64
149
|
async getAdmin(): Promise<Deployment | undefined> {
|
|
@@ -143,14 +228,18 @@ export class Manifest {
|
|
|
143
228
|
}
|
|
144
229
|
}
|
|
145
230
|
|
|
146
|
-
async read(): Promise<ManifestData> {
|
|
147
|
-
const release = this.locked ? undefined : await this.lock();
|
|
231
|
+
async read(retries?: number): Promise<ManifestData> {
|
|
232
|
+
const release = this.locked ? undefined : await this.lock(retries);
|
|
148
233
|
try {
|
|
149
234
|
const data = JSON.parse(await this.readFile()) as ManifestData;
|
|
150
235
|
return validateOrUpdateManifestVersion(data);
|
|
151
236
|
} catch (e: any) {
|
|
152
237
|
if (e.code === 'ENOENT') {
|
|
153
|
-
|
|
238
|
+
if (this.parent !== undefined) {
|
|
239
|
+
return await this.parent.read(retries);
|
|
240
|
+
} else {
|
|
241
|
+
return defaultManifest();
|
|
242
|
+
}
|
|
154
243
|
} else {
|
|
155
244
|
throw e;
|
|
156
245
|
}
|
|
@@ -179,11 +268,11 @@ export class Manifest {
|
|
|
179
268
|
}
|
|
180
269
|
}
|
|
181
270
|
|
|
182
|
-
private async lock() {
|
|
183
|
-
const lockfileName = path.join(
|
|
271
|
+
private async lock(retries = 3) {
|
|
272
|
+
const lockfileName = path.join(this.dir, `chain-${this.chainIdSuffix}`);
|
|
184
273
|
|
|
185
274
|
await fs.mkdir(path.dirname(lockfileName), { recursive: true });
|
|
186
|
-
const release = await lockfile.lock(lockfileName, { retries
|
|
275
|
+
const release = await lockfile.lock(lockfileName, { retries, realpath: false });
|
|
187
276
|
this.locked = true;
|
|
188
277
|
return async () => {
|
|
189
278
|
await release();
|
package/src/provider.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export interface EthereumProvider {
|
|
2
|
+
send(method: 'hardhat_metadata', params: []): Promise<HardhatMetadata>;
|
|
2
3
|
send(method: 'web3_clientVersion', params: []): Promise<string>;
|
|
3
4
|
send(method: 'net_version', params: []): Promise<string>;
|
|
4
5
|
send(method: 'eth_chainId', params: []): Promise<string>;
|
|
6
|
+
send(method: 'eth_instanceId', params: []): Promise<string>;
|
|
5
7
|
send(method: 'eth_getCode', params: [string, string]): Promise<string>;
|
|
6
8
|
send(method: 'eth_call', params: unknown[]): Promise<string>;
|
|
7
9
|
send(method: 'eth_getStorageAt', params: [string, string, string]): Promise<string>;
|
|
@@ -10,6 +12,15 @@ export interface EthereumProvider {
|
|
|
10
12
|
send(method: string, params: unknown[]): Promise<unknown>;
|
|
11
13
|
}
|
|
12
14
|
|
|
15
|
+
interface HardhatMetadata {
|
|
16
|
+
chainId: number;
|
|
17
|
+
instanceId: string;
|
|
18
|
+
forkedNetwork?: {
|
|
19
|
+
// The chainId of the network that is being forked
|
|
20
|
+
chainId: number;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
13
24
|
interface EthereumTransaction {
|
|
14
25
|
blockHash: string | null;
|
|
15
26
|
input: string;
|
|
@@ -38,6 +49,14 @@ export async function getClientVersion(provider: EthereumProvider): Promise<stri
|
|
|
38
49
|
return provider.send('web3_clientVersion', []);
|
|
39
50
|
}
|
|
40
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Gets Hardhat metadata when used with Hardhat 2.12.3 or later.
|
|
54
|
+
* The underlying provider will throw an error if this RPC method is not available.
|
|
55
|
+
*/
|
|
56
|
+
export async function getHardhatMetadata(provider: EthereumProvider): Promise<HardhatMetadata> {
|
|
57
|
+
return provider.send('hardhat_metadata', []);
|
|
58
|
+
}
|
|
59
|
+
|
|
41
60
|
export async function getStorageAt(
|
|
42
61
|
provider: EthereumProvider,
|
|
43
62
|
address: string,
|
|
@@ -101,10 +120,12 @@ export const networkNames: { [chainId in number]?: string } = Object.freeze({
|
|
|
101
120
|
3: 'ropsten',
|
|
102
121
|
4: 'rinkeby',
|
|
103
122
|
5: 'goerli',
|
|
123
|
+
10: 'optimism',
|
|
104
124
|
42: 'kovan',
|
|
105
125
|
56: 'bsc',
|
|
106
126
|
97: 'bsc-testnet',
|
|
107
127
|
137: 'polygon',
|
|
128
|
+
420: 'optimism-goerli',
|
|
108
129
|
80001: 'polygon-mumbai',
|
|
109
130
|
43113: 'avalanche-fuji',
|
|
110
131
|
43114: 'avalanche',
|
|
@@ -121,7 +142,7 @@ export async function isDevelopmentNetwork(provider: EthereumProvider): Promise<
|
|
|
121
142
|
} else {
|
|
122
143
|
const clientVersion = await getClientVersion(provider);
|
|
123
144
|
const [name] = clientVersion.split('/', 1);
|
|
124
|
-
return name === 'HardhatNetwork' || name === 'EthereumJS TestRPC';
|
|
145
|
+
return name === 'HardhatNetwork' || name === 'EthereumJS TestRPC' || name === 'anvil';
|
|
125
146
|
}
|
|
126
147
|
}
|
|
127
148
|
|
package/src/storage/compare.ts
CHANGED
|
@@ -91,10 +91,10 @@ export function storageFieldEnd(field: StorageField): bigint | undefined {
|
|
|
91
91
|
return begin + BigInt(numberOfBytes);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
const LAYOUTCHANGE_COST =
|
|
94
|
+
const LAYOUTCHANGE_COST = 1;
|
|
95
95
|
const FINISHGAP_COST = 1;
|
|
96
96
|
const SHRINKGAP_COST = 0;
|
|
97
|
-
const TYPECHANGE_COST =
|
|
97
|
+
const TYPECHANGE_COST = 1;
|
|
98
98
|
|
|
99
99
|
export class StorageLayoutComparator {
|
|
100
100
|
hasAllowedUncheckedCustomTypes = false;
|
package/src/storage/gap.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { StorageField, storageFieldEnd } from './compare';
|
|
|
7
7
|
* @returns true if field is a gap, otherwise false
|
|
8
8
|
*/
|
|
9
9
|
export function isGap(field: StorageField): boolean {
|
|
10
|
-
return field.
|
|
10
|
+
return field.type.head === 't_array' && (field.label === '__gap' || field.label.startsWith('__gap_'));
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
/**
|