@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.
@@ -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.true(data.proxies[0].address === '0x123');
98
- t.true(data.proxies[1].address === '0x456');
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 manifestDir = '.openzeppelin';
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
- private readonly fallbackFile: string;
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
- return new Manifest(await getChainId(provider));
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 fallbackName = `unknown-${this.chainId}`;
58
- this.fallbackFile = path.join(manifestDir, `${fallbackName}.json`);
114
+ const defaultFallbackName = `unknown-${chainId}`;
59
115
 
60
- const name = networkNames[this.chainId] ?? fallbackName;
61
- this.file = path.join(manifestDir, `${name}.json`);
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
- return defaultManifest();
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(manifestDir, `chain-${this.chainId}`);
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: 3, realpath: false });
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
 
@@ -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 = 0;
94
+ const LAYOUTCHANGE_COST = 1;
95
95
  const FINISHGAP_COST = 1;
96
96
  const SHRINKGAP_COST = 0;
97
- const TYPECHANGE_COST = 0;
97
+ const TYPECHANGE_COST = 1;
98
98
 
99
99
  export class StorageLayoutComparator {
100
100
  hasAllowedUncheckedCustomTypes = false;
@@ -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.label === '__gap' && field.type.head === 't_array';
10
+ return field.type.head === 't_array' && (field.label === '__gap' || field.label.startsWith('__gap_'));
11
11
  }
12
12
 
13
13
  /**