@openzeppelin/upgrades-core 1.35.1 → 1.37.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/CHANGELOG.md +10 -0
- package/dist/cli/cli.test.js +258 -0
- package/dist/cli/cli.test.js.map +1 -1
- package/dist/cli/validate/build-info-file.d.ts +4 -0
- package/dist/cli/validate/build-info-file.d.ts.map +1 -1
- package/dist/cli/validate/build-info-file.js +7 -2
- package/dist/cli/validate/build-info-file.js.map +1 -1
- package/dist/cli/validate/contract-report.d.ts +6 -5
- package/dist/cli/validate/contract-report.d.ts.map +1 -1
- package/dist/cli/validate/contract-report.js +54 -13
- package/dist/cli/validate/contract-report.js.map +1 -1
- package/dist/cli/validate/default-exclude.d.ts +2 -0
- package/dist/cli/validate/default-exclude.d.ts.map +1 -0
- package/dist/cli/validate/default-exclude.js +6 -0
- package/dist/cli/validate/default-exclude.js.map +1 -0
- package/dist/cli/validate/find-contract.d.ts +7 -2
- package/dist/cli/validate/find-contract.d.ts.map +1 -1
- package/dist/cli/validate/find-contract.js +32 -4
- package/dist/cli/validate/find-contract.js.map +1 -1
- package/dist/cli/validate/upgradeability-assessment.d.ts +2 -1
- package/dist/cli/validate/upgradeability-assessment.d.ts.map +1 -1
- package/dist/cli/validate/upgradeability-assessment.js +2 -2
- package/dist/cli/validate/upgradeability-assessment.js.map +1 -1
- package/dist/cli/validate/validate-upgrade-safety.d.ts +12 -3
- package/dist/cli/validate/validate-upgrade-safety.d.ts.map +1 -1
- package/dist/cli/validate/validate-upgrade-safety.js +28 -7
- package/dist/cli/validate/validate-upgrade-safety.js.map +1 -1
- package/dist/cli/validate/validate-upgrade-safety.test.js +1 -1
- package/dist/cli/validate/validations.d.ts +1 -0
- package/dist/cli/validate/validations.d.ts.map +1 -1
- package/dist/cli/validate/validations.js +1 -0
- package/dist/cli/validate/validations.js.map +1 -1
- package/dist/cli/validate/validations.test.js +8 -4
- package/dist/cli/validate/validations.test.js.map +1 -1
- package/dist/cli/validate.d.ts +2 -0
- package/dist/cli/validate.d.ts.map +1 -1
- package/dist/cli/validate.js +39 -14
- package/dist/cli/validate.js.map +1 -1
- package/dist/cli/validate.test.js +20 -0
- package/dist/cli/validate.test.js.map +1 -1
- package/dist/storage/extract.js +1 -1
- package/dist/storage/extract.js.map +1 -1
- package/dist/utils/annotations.js +1 -1
- package/dist/utils/annotations.js.map +1 -1
- package/package.json +4 -3
- package/src/cli/cli.test.ts +414 -0
- package/src/cli/cli.test.ts.md +253 -2
- package/src/cli/cli.test.ts.snap +0 -0
- package/src/cli/validate/build-info-file.ts +15 -4
- package/src/cli/validate/contract-report.ts +71 -16
- package/src/cli/validate/default-exclude.ts +3 -0
- package/src/cli/validate/find-contract.ts +50 -4
- package/src/cli/validate/upgradeability-assessment.ts +3 -2
- package/src/cli/validate/validate-upgrade-safety.test.ts +1 -1
- package/src/cli/validate/validate-upgrade-safety.ts +38 -6
- package/src/cli/validate/validations.test.ts +8 -4
- package/src/cli/validate/validations.ts +2 -0
- package/src/cli/validate.test.ts +22 -0
- package/src/cli/validate.ts +46 -14
- package/src/storage/extract.ts +2 -1
- package/src/utils/annotations.ts +1 -1
package/src/cli/cli.test.ts.md
CHANGED
|
@@ -19,7 +19,9 @@ Generated by [AVA](https://avajs.dev).
|
|
|
19
19
|
--contract <CONTRACT> The name or fully qualified name of the contract to validate. If not specified, all upgradeable contracts in the build info directory will be validated.␊
|
|
20
20
|
--reference <REFERENCE_CONTRACT> Can only be used when the --contract option is also provided. The name or fully qualified name of the reference contract to use for storage layout comparisons. If not specified, uses the @custom:oz-upgrades-from annotation if it is defined in the contract that is being validated.␊
|
|
21
21
|
--requireReference Can only be used when the --contract option is also provided. Not compatible with --unsafeSkipStorageCheck. If specified, requires either the --reference option to be provided or the contract to have a @custom:oz-upgrades-from annotation.␊
|
|
22
|
-
--
|
|
22
|
+
--referenceBuildInfoDirs "<BUILD_INFO_DIR>[,<BUILD_INFO_DIR>...]" Optional paths of additional build info directories from previous versions of the project to use for storage layout comparisons. When using this option, refer to one of these directories using prefix '<dirName>:' before the contract name or fully qualified name in the --reference option or @custom:oz-upgrades-from annotation, where <dirName> is the directory short name. Each directory short name must be unique, including compared to the main build info directory. If passing in multiple directories, separate them with commas or call the option multiple times, once for each directory.␊
|
|
23
|
+
--exclude "<GLOB_PATTERN>" [--exclude "<GLOB_PATTERN>"...] Exclude validations for contracts in source file paths that match any of the given glob patterns. For example, --exclude "contracts/mocks/**/*.sol". Does not apply to reference contracts. If passing in multiple patterns, call the option multiple times, once for each pattern.␊
|
|
24
|
+
--unsafeAllow "<VALIDATION_ERROR>[,<VALIDATION_ERROR>...]" Selectively disable one or more validation errors. Comma-separated list with one or more of the following: state-variable-assignment, state-variable-immutable, external-library-linking, struct-definition, enum-definition, constructor, delegatecall, selfdestruct, missing-public-upgradeto, internal-function-storage␊
|
|
23
25
|
--unsafeAllowRenames Configure storage layout check to allow variable renaming.␊
|
|
24
26
|
--unsafeSkipStorageCheck Skips checking for storage layout compatibility errors. This is a dangerous option meant to be used as a last resort.␊
|
|
25
27
|
`
|
|
@@ -39,7 +41,9 @@ Generated by [AVA](https://avajs.dev).
|
|
|
39
41
|
--contract <CONTRACT> The name or fully qualified name of the contract to validate. If not specified, all upgradeable contracts in the build info directory will be validated.␊
|
|
40
42
|
--reference <REFERENCE_CONTRACT> Can only be used when the --contract option is also provided. The name or fully qualified name of the reference contract to use for storage layout comparisons. If not specified, uses the @custom:oz-upgrades-from annotation if it is defined in the contract that is being validated.␊
|
|
41
43
|
--requireReference Can only be used when the --contract option is also provided. Not compatible with --unsafeSkipStorageCheck. If specified, requires either the --reference option to be provided or the contract to have a @custom:oz-upgrades-from annotation.␊
|
|
42
|
-
--
|
|
44
|
+
--referenceBuildInfoDirs "<BUILD_INFO_DIR>[,<BUILD_INFO_DIR>...]" Optional paths of additional build info directories from previous versions of the project to use for storage layout comparisons. When using this option, refer to one of these directories using prefix '<dirName>:' before the contract name or fully qualified name in the --reference option or @custom:oz-upgrades-from annotation, where <dirName> is the directory short name. Each directory short name must be unique, including compared to the main build info directory. If passing in multiple directories, separate them with commas or call the option multiple times, once for each directory.␊
|
|
45
|
+
--exclude "<GLOB_PATTERN>" [--exclude "<GLOB_PATTERN>"...] Exclude validations for contracts in source file paths that match any of the given glob patterns. For example, --exclude "contracts/mocks/**/*.sol". Does not apply to reference contracts. If passing in multiple patterns, call the option multiple times, once for each pattern.␊
|
|
46
|
+
--unsafeAllow "<VALIDATION_ERROR>[,<VALIDATION_ERROR>...]" Selectively disable one or more validation errors. Comma-separated list with one or more of the following: state-variable-assignment, state-variable-immutable, external-library-linking, struct-definition, enum-definition, constructor, delegatecall, selfdestruct, missing-public-upgradeto, internal-function-storage␊
|
|
43
47
|
--unsafeAllowRenames Configure storage layout check to allow variable renaming.␊
|
|
44
48
|
--unsafeSkipStorageCheck Skips checking for storage layout compatibility errors. This is a dangerous option meant to be used as a last resort.␊
|
|
45
49
|
`
|
|
@@ -187,6 +191,22 @@ Generated by [AVA](https://avajs.dev).
|
|
|
187
191
|
|
|
188
192
|
## validate - single contract, reference
|
|
189
193
|
|
|
194
|
+
> Snapshot 1
|
|
195
|
+
|
|
196
|
+
`Stdout: ✘ contracts/test/cli/Validate.sol:BecomesBadLayout (upgrades from contracts/test/cli/Validate.sol:StorageV1)␊
|
|
197
|
+
␊
|
|
198
|
+
StorageV1: Deleted \`x\`␊
|
|
199
|
+
> Keep the variable even if unused␊
|
|
200
|
+
␊
|
|
201
|
+
StorageV1: Deleted \`__gap\`␊
|
|
202
|
+
> Keep the variable even if unused␊
|
|
203
|
+
␊
|
|
204
|
+
FAILED␊
|
|
205
|
+
␊
|
|
206
|
+
Stderr: `
|
|
207
|
+
|
|
208
|
+
## validate - single contract, reference with same build info dir
|
|
209
|
+
|
|
190
210
|
> Snapshot 1
|
|
191
211
|
|
|
192
212
|
`Stdout: ✘ contracts/test/cli/Validate.sol:BecomesBadLayout (upgrades from contracts/test/cli/Validate.sol:StorageV1)␊
|
|
@@ -304,3 +324,234 @@ Generated by [AVA](https://avajs.dev).
|
|
|
304
324
|
␊
|
|
305
325
|
SUCCESS␊
|
|
306
326
|
`
|
|
327
|
+
|
|
328
|
+
## validate - references other build info dir by command - ok
|
|
329
|
+
|
|
330
|
+
> Snapshot 1
|
|
331
|
+
|
|
332
|
+
{
|
|
333
|
+
stderr: '',
|
|
334
|
+
stdout: ` ✔ contracts/test/cli/ValidateBuildInfoV2_Ok.sol:MyContract (upgrades from build-info-v1:contracts/test/cli/ValidateBuildInfoV1.sol:MyContract)␊
|
|
335
|
+
␊
|
|
336
|
+
SUCCESS␊
|
|
337
|
+
`,
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
## validate - references other build info dir by command with fully qualified names - ok
|
|
341
|
+
|
|
342
|
+
> Snapshot 1
|
|
343
|
+
|
|
344
|
+
{
|
|
345
|
+
stderr: '',
|
|
346
|
+
stdout: ` ✔ contracts/test/cli/ValidateBuildInfoV2_Ok.sol:MyContract (upgrades from build-info-v1:contracts/test/cli/ValidateBuildInfoV1.sol:MyContract)␊
|
|
347
|
+
␊
|
|
348
|
+
SUCCESS␊
|
|
349
|
+
`,
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
## validate - references other build info dir by command - bad
|
|
353
|
+
|
|
354
|
+
> Snapshot 1
|
|
355
|
+
|
|
356
|
+
`Stdout: ✘ contracts/test/cli/ValidateBuildInfoV2_Bad.sol:MyContract (upgrades from build-info-v1:contracts/test/cli/ValidateBuildInfoV1.sol:MyContract)␊
|
|
357
|
+
␊
|
|
358
|
+
MyContract: Deleted \`y\`␊
|
|
359
|
+
> Keep the variable even if unused␊
|
|
360
|
+
␊
|
|
361
|
+
contracts/test/cli/ValidateBuildInfoV2_Bad.sol:6: Upgraded \`__gap\` to an incompatible type␊
|
|
362
|
+
- Bad storage gap resize from 48 to 49␊
|
|
363
|
+
Size cannot increase␊
|
|
364
|
+
␊
|
|
365
|
+
FAILED␊
|
|
366
|
+
␊
|
|
367
|
+
Stderr: `
|
|
368
|
+
|
|
369
|
+
## validate - references other build info dir by command with fully qualified names - bad
|
|
370
|
+
|
|
371
|
+
> Snapshot 1
|
|
372
|
+
|
|
373
|
+
`Stdout: ✘ contracts/test/cli/ValidateBuildInfoV2_Bad.sol:MyContract (upgrades from build-info-v1:contracts/test/cli/ValidateBuildInfoV1.sol:MyContract)␊
|
|
374
|
+
␊
|
|
375
|
+
MyContract: Deleted \`y\`␊
|
|
376
|
+
> Keep the variable even if unused␊
|
|
377
|
+
␊
|
|
378
|
+
contracts/test/cli/ValidateBuildInfoV2_Bad.sol:6: Upgraded \`__gap\` to an incompatible type␊
|
|
379
|
+
- Bad storage gap resize from 48 to 49␊
|
|
380
|
+
Size cannot increase␊
|
|
381
|
+
␊
|
|
382
|
+
FAILED␊
|
|
383
|
+
␊
|
|
384
|
+
Stderr: `
|
|
385
|
+
|
|
386
|
+
## validate - references multiple build info dirs by annotation
|
|
387
|
+
|
|
388
|
+
> Snapshot 1
|
|
389
|
+
|
|
390
|
+
`Stdout: ✘ contracts/test/cli/ValidateBuildInfoV2_Branch_Bad.sol:MyContract (upgrades from build-info-v2:contracts/test/cli/ValidateBuildInfoV2_Ok.sol:MyContract)␊
|
|
391
|
+
␊
|
|
392
|
+
contracts/test/cli/ValidateBuildInfoV2_Branch_Bad.sol:8: Renamed \`z\` to \`zBranch\`␊
|
|
393
|
+
␊
|
|
394
|
+
✔ contracts/test/cli/ValidateBuildInfoV2_Branch_Ok.sol:MyContract (upgrades from build-info-v1:contracts/test/cli/ValidateBuildInfoV1.sol:MyContract)␊
|
|
395
|
+
␊
|
|
396
|
+
FAILED (2 upgradeable contracts detected, 1 passed, 1 failed)␊
|
|
397
|
+
␊
|
|
398
|
+
Stderr: `
|
|
399
|
+
|
|
400
|
+
## validate - references multiple build info dirs by annotation - same arg multiple times
|
|
401
|
+
|
|
402
|
+
> Snapshot 1
|
|
403
|
+
|
|
404
|
+
`Stdout: ✘ contracts/test/cli/ValidateBuildInfoV2_Branch_Bad.sol:MyContract (upgrades from build-info-v2:contracts/test/cli/ValidateBuildInfoV2_Ok.sol:MyContract)␊
|
|
405
|
+
␊
|
|
406
|
+
contracts/test/cli/ValidateBuildInfoV2_Branch_Bad.sol:8: Renamed \`z\` to \`zBranch\`␊
|
|
407
|
+
␊
|
|
408
|
+
✔ contracts/test/cli/ValidateBuildInfoV2_Branch_Ok.sol:MyContract (upgrades from build-info-v1:contracts/test/cli/ValidateBuildInfoV1.sol:MyContract)␊
|
|
409
|
+
␊
|
|
410
|
+
FAILED (2 upgradeable contracts detected, 1 passed, 1 failed)␊
|
|
411
|
+
␊
|
|
412
|
+
Stderr: `
|
|
413
|
+
|
|
414
|
+
## validate - references other build info dir by annotation - ok
|
|
415
|
+
|
|
416
|
+
> Snapshot 1
|
|
417
|
+
|
|
418
|
+
{
|
|
419
|
+
stderr: '',
|
|
420
|
+
stdout: ` ✔ contracts/test/cli/ValidateBuildInfoV2_Annotation_Ok.sol:MyContract (upgrades from build-info-v1:contracts/test/cli/ValidateBuildInfoV1.sol:MyContract)␊
|
|
421
|
+
␊
|
|
422
|
+
SUCCESS␊
|
|
423
|
+
`,
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
## validate - references other build info dir by annotation - bad
|
|
427
|
+
|
|
428
|
+
> Snapshot 1
|
|
429
|
+
|
|
430
|
+
`Stdout: ✘ contracts/test/cli/ValidateBuildInfoV2_Annotation_Bad.sol:MyContract (upgrades from build-info-v1:contracts/test/cli/ValidateBuildInfoV1.sol:MyContract)␊
|
|
431
|
+
␊
|
|
432
|
+
MyContract: Deleted \`y\`␊
|
|
433
|
+
> Keep the variable even if unused␊
|
|
434
|
+
␊
|
|
435
|
+
contracts/test/cli/ValidateBuildInfoV2_Annotation_Bad.sol:7: Upgraded \`__gap\` to an incompatible type␊
|
|
436
|
+
- Bad storage gap resize from 48 to 49␊
|
|
437
|
+
Size cannot increase␊
|
|
438
|
+
␊
|
|
439
|
+
FAILED␊
|
|
440
|
+
␊
|
|
441
|
+
Stderr: `
|
|
442
|
+
|
|
443
|
+
## validate - excludes by pattern - no match
|
|
444
|
+
|
|
445
|
+
> Snapshot 1
|
|
446
|
+
|
|
447
|
+
`Stdout: ✘ contracts/test/cli/excludes/UsesAbstractUUPS.sol:UsesAbstractUUPS␊
|
|
448
|
+
␊
|
|
449
|
+
contracts/test/cli/excludes/UsesAbstractUUPS.sol:9: Contract \`UsesAbstractUUPS\` has a constructor␊
|
|
450
|
+
Define an initializer instead␊
|
|
451
|
+
https://zpl.in/upgrades/error-001␊
|
|
452
|
+
␊
|
|
453
|
+
contracts/test/cli/excludes/AbstractUUPS.sol:11: Contract \`AbstractUUPS\` has a constructor␊
|
|
454
|
+
Define an initializer instead␊
|
|
455
|
+
https://zpl.in/upgrades/error-001␊
|
|
456
|
+
␊
|
|
457
|
+
contracts/test/cli/excludes/AbstractUUPS.sol:9: Variable \`z\` is immutable and will be initialized on the implementation␊
|
|
458
|
+
If by design, annotate with '@custom:oz-upgrades-unsafe-allow state-variable-immutable'␊
|
|
459
|
+
Otherwise, consider a constant variable or use a mutable variable instead␊
|
|
460
|
+
https://zpl.in/upgrades/error-005␊
|
|
461
|
+
␊
|
|
462
|
+
contracts/test/cli/excludes/Abstract2.sol:7: Contract \`Abstract2\` has a constructor␊
|
|
463
|
+
Define an initializer instead␊
|
|
464
|
+
https://zpl.in/upgrades/error-001␊
|
|
465
|
+
␊
|
|
466
|
+
contracts/test/cli/excludes/Abstract2.sol:5: Variable \`y\` is immutable and will be initialized on the implementation␊
|
|
467
|
+
If by design, annotate with '@custom:oz-upgrades-unsafe-allow state-variable-immutable'␊
|
|
468
|
+
Otherwise, consider a constant variable or use a mutable variable instead␊
|
|
469
|
+
https://zpl.in/upgrades/error-005␊
|
|
470
|
+
␊
|
|
471
|
+
contracts/test/cli/excludes/Abstract1.sol:7: Contract \`Abstract1\` has a constructor␊
|
|
472
|
+
Define an initializer instead␊
|
|
473
|
+
https://zpl.in/upgrades/error-001␊
|
|
474
|
+
␊
|
|
475
|
+
contracts/test/cli/excludes/Abstract1.sol:5: Variable \`x\` is immutable and will be initialized on the implementation␊
|
|
476
|
+
If by design, annotate with '@custom:oz-upgrades-unsafe-allow state-variable-immutable'␊
|
|
477
|
+
Otherwise, consider a constant variable or use a mutable variable instead␊
|
|
478
|
+
https://zpl.in/upgrades/error-005␊
|
|
479
|
+
␊
|
|
480
|
+
FAILED (1 upgradeable contract detected, 0 passed, 1 failed)␊
|
|
481
|
+
␊
|
|
482
|
+
Stderr: `
|
|
483
|
+
|
|
484
|
+
## validate - excludes by pattern - some match
|
|
485
|
+
|
|
486
|
+
> Snapshot 1
|
|
487
|
+
|
|
488
|
+
`Stdout: ✘ contracts/test/cli/excludes/UsesAbstractUUPS.sol:UsesAbstractUUPS␊
|
|
489
|
+
␊
|
|
490
|
+
contracts/test/cli/excludes/UsesAbstractUUPS.sol:9: Contract \`UsesAbstractUUPS\` has a constructor␊
|
|
491
|
+
Define an initializer instead␊
|
|
492
|
+
https://zpl.in/upgrades/error-001␊
|
|
493
|
+
␊
|
|
494
|
+
FAILED (1 upgradeable contract detected, 0 passed, 1 failed)␊
|
|
495
|
+
␊
|
|
496
|
+
Stderr: `
|
|
497
|
+
|
|
498
|
+
## validate - excludes by pattern - all match
|
|
499
|
+
|
|
500
|
+
> Snapshot 1
|
|
501
|
+
|
|
502
|
+
{
|
|
503
|
+
stderr: '',
|
|
504
|
+
stdout: `No upgradeable contracts detected.␊
|
|
505
|
+
`,
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
## validate - excludes by pattern - all match using commas within glob
|
|
509
|
+
|
|
510
|
+
> Snapshot 1
|
|
511
|
+
|
|
512
|
+
{
|
|
513
|
+
stderr: '',
|
|
514
|
+
stdout: `No upgradeable contracts detected.␊
|
|
515
|
+
`,
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
## validate - exclude passed multiple times
|
|
519
|
+
|
|
520
|
+
> Snapshot 1
|
|
521
|
+
|
|
522
|
+
{
|
|
523
|
+
stderr: '',
|
|
524
|
+
stdout: `No upgradeable contracts detected.␊
|
|
525
|
+
`,
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
## validate - excludes UpgradeableBeacon and its parents by default
|
|
529
|
+
|
|
530
|
+
> Snapshot 1
|
|
531
|
+
|
|
532
|
+
`Stdout: ✘ contracts/test/cli/excludes/V2Bad1.sol:V2Bad1 (upgrades from contracts/test/cli/excludes/V1.sol:V1)␊
|
|
533
|
+
␊
|
|
534
|
+
V1: Deleted \`x\`␊
|
|
535
|
+
> Keep the variable even if unused␊
|
|
536
|
+
␊
|
|
537
|
+
✘ contracts/test/cli/excludes/V2Bad2.sol:V2Bad2 (upgrades from contracts/test/cli/excludes/V1.sol:V1)␊
|
|
538
|
+
␊
|
|
539
|
+
V1: Deleted \`x\`␊
|
|
540
|
+
> Keep the variable even if unused␊
|
|
541
|
+
␊
|
|
542
|
+
FAILED (2 upgradeable contracts detected, 0 passed, 2 failed)␊
|
|
543
|
+
␊
|
|
544
|
+
Stderr: `
|
|
545
|
+
|
|
546
|
+
## validate - excludes UpgradeableBeacon and its parents by default, and excludes one contract from layout comparisions
|
|
547
|
+
|
|
548
|
+
> Snapshot 1
|
|
549
|
+
|
|
550
|
+
`Stdout: ✘ contracts/test/cli/excludes/V2Bad2.sol:V2Bad2 (upgrades from contracts/test/cli/excludes/V1.sol:V1)␊
|
|
551
|
+
␊
|
|
552
|
+
V1: Deleted \`x\`␊
|
|
553
|
+
> Keep the variable even if unused␊
|
|
554
|
+
␊
|
|
555
|
+
FAILED (1 upgradeable contract detected, 0 passed, 1 failed)␊
|
|
556
|
+
␊
|
|
557
|
+
Stderr: `
|
package/src/cli/cli.test.ts.snap
CHANGED
|
Binary file
|
|
@@ -49,6 +49,11 @@ export interface BuildInfoFile {
|
|
|
49
49
|
* The Solidity compiler output JSON object.
|
|
50
50
|
*/
|
|
51
51
|
output: SolcOutput;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Short name of the build info dir containing this file.
|
|
55
|
+
*/
|
|
56
|
+
dirShortName: string;
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
/**
|
|
@@ -57,13 +62,18 @@ export interface BuildInfoFile {
|
|
|
57
62
|
* @param buildInfoDir Build info directory, or undefined to use the default Hardhat or Foundry build-info dir.
|
|
58
63
|
* @returns The build info files with Solidity compiler input and output.
|
|
59
64
|
*/
|
|
60
|
-
export async function getBuildInfoFiles(buildInfoDir?: string) {
|
|
65
|
+
export async function getBuildInfoFiles(buildInfoDir?: string): Promise<BuildInfoFile[]> {
|
|
61
66
|
const dir = await findDir(buildInfoDir);
|
|
67
|
+
const shortName = path.basename(dir);
|
|
62
68
|
const jsonFiles = await getJsonFiles(dir);
|
|
63
|
-
|
|
69
|
+
|
|
70
|
+
return await readBuildInfo(jsonFiles, shortName);
|
|
64
71
|
}
|
|
65
72
|
|
|
66
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Finds the build info dir if provided, otherwise finds the default Hardhat or Foundry build info dir. Throws an error if no build info files were found in the expected dir.
|
|
75
|
+
*/
|
|
76
|
+
async function findDir(buildInfoDir?: string): Promise<string> {
|
|
67
77
|
if (buildInfoDir !== undefined && !(await hasJsonFiles(buildInfoDir))) {
|
|
68
78
|
throw new ValidateCommandError(
|
|
69
79
|
`The directory '${buildInfoDir}' does not exist or does not contain any build info files.`,
|
|
@@ -123,7 +133,7 @@ async function getJsonFiles(dir: string): Promise<string[]> {
|
|
|
123
133
|
return jsonFiles.map(file => path.join(dir, file));
|
|
124
134
|
}
|
|
125
135
|
|
|
126
|
-
async function readBuildInfo(buildInfoFilePaths: string[]) {
|
|
136
|
+
async function readBuildInfo(buildInfoFilePaths: string[], dirShortName: string) {
|
|
127
137
|
const buildInfoFiles: BuildInfoFile[] = [];
|
|
128
138
|
|
|
129
139
|
for (const buildInfoFilePath of buildInfoFilePaths) {
|
|
@@ -143,6 +153,7 @@ async function readBuildInfo(buildInfoFilePaths: string[]) {
|
|
|
143
153
|
input: buildInfoJson.input,
|
|
144
154
|
output: buildInfoJson.output,
|
|
145
155
|
solcVersion: buildInfoJson.solcVersion,
|
|
156
|
+
dirShortName: dirShortName,
|
|
146
157
|
});
|
|
147
158
|
}
|
|
148
159
|
}
|
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
getContractVersion,
|
|
5
5
|
getStorageLayout,
|
|
6
6
|
ValidationOptions,
|
|
7
|
-
withValidationDefaults,
|
|
8
7
|
Version,
|
|
9
8
|
ValidationData,
|
|
10
9
|
ValidateUpgradeSafetyOptions,
|
|
@@ -17,7 +16,10 @@ import { getUpgradeabilityAssessment } from './upgradeability-assessment';
|
|
|
17
16
|
import { SourceContract } from './validations';
|
|
18
17
|
import { LayoutCompatibilityReport } from '../../storage/report';
|
|
19
18
|
import { indent } from '../../utils/indent';
|
|
20
|
-
import { SpecifiedContracts } from './validate-upgrade-safety';
|
|
19
|
+
import { BuildInfoDictionary, SpecifiedContracts } from './validate-upgrade-safety';
|
|
20
|
+
import { minimatch } from 'minimatch';
|
|
21
|
+
import { ValidateCommandError } from './error';
|
|
22
|
+
import { defaultExclude } from './default-exclude';
|
|
21
23
|
|
|
22
24
|
/**
|
|
23
25
|
* Report for an upgradeable contract.
|
|
@@ -29,7 +31,16 @@ export class UpgradeableContractReport implements Report {
|
|
|
29
31
|
readonly reference: string | undefined,
|
|
30
32
|
readonly standaloneReport: UpgradeableContractErrorReport,
|
|
31
33
|
readonly storageLayoutReport: LayoutCompatibilityReport | undefined,
|
|
32
|
-
) {
|
|
34
|
+
) {
|
|
35
|
+
if (reference === contract) {
|
|
36
|
+
throw new ValidateCommandError(
|
|
37
|
+
`The contract ${contract} must not use itself as a reference for storage layout comparisons.`,
|
|
38
|
+
() => `\
|
|
39
|
+
If this is the first version of your contract, do not specify a reference.
|
|
40
|
+
If this is a subsequent version, keep the previous version of the contract in another file and specify that as the reference, or specify a reference from another build info directory containing the previous version. If you do not have the previous version available, you can skip the storage layout check using the \`unsafeSkipStorageCheck\` option, which is a dangerous option meant to be used as a last resort.`,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
33
44
|
|
|
34
45
|
get ok(): boolean {
|
|
35
46
|
return this.standaloneReport.ok && (this.storageLayoutReport === undefined || this.storageLayoutReport.ok);
|
|
@@ -58,29 +69,32 @@ export class UpgradeableContractReport implements Report {
|
|
|
58
69
|
}
|
|
59
70
|
|
|
60
71
|
/**
|
|
61
|
-
* Gets upgradeble contract reports for the upgradeable contracts in the
|
|
72
|
+
* Gets upgradeble contract reports for the upgradeable contracts in the set of source contracts at dictionary key ''.
|
|
73
|
+
* Reference contracts can come from source contracts at the corresponding dictionary key.
|
|
62
74
|
* Only contracts that are detected as upgradeable will be included in the reports.
|
|
63
75
|
* Reports include upgradeable contracts regardless of whether they pass or fail upgrade safety checks.
|
|
64
76
|
*
|
|
65
|
-
* @param
|
|
77
|
+
* @param buildInfoDictionary Dictionary of build info directories and the source contracts they contain.
|
|
66
78
|
* @param opts The validation options.
|
|
67
79
|
* @param specifiedContracts If provided, only the specified contract (upgrading from its reference contract) will be reported.
|
|
80
|
+
* @param exclude Exclude validations for contracts in source file paths that match any of the given glob patterns.
|
|
68
81
|
* @returns The upgradeable contract reports.
|
|
69
82
|
*/
|
|
70
83
|
export function getContractReports(
|
|
71
|
-
|
|
84
|
+
buildInfoDictionary: BuildInfoDictionary,
|
|
72
85
|
opts: Required<ValidateUpgradeSafetyOptions>,
|
|
73
86
|
specifiedContracts?: SpecifiedContracts,
|
|
87
|
+
exclude?: string[],
|
|
74
88
|
) {
|
|
75
89
|
const upgradeableContractReports: UpgradeableContractReport[] = [];
|
|
76
90
|
|
|
77
91
|
const contractsToReport: SourceContract[] =
|
|
78
|
-
specifiedContracts !== undefined ? [specifiedContracts.contract] :
|
|
92
|
+
specifiedContracts !== undefined ? [specifiedContracts.contract] : buildInfoDictionary[''];
|
|
79
93
|
|
|
80
94
|
for (const sourceContract of contractsToReport) {
|
|
81
95
|
const upgradeabilityAssessment = getUpgradeabilityAssessment(
|
|
82
96
|
sourceContract,
|
|
83
|
-
|
|
97
|
+
buildInfoDictionary,
|
|
84
98
|
specifiedContracts?.reference,
|
|
85
99
|
);
|
|
86
100
|
if (opts.requireReference && upgradeabilityAssessment.referenceContract === undefined) {
|
|
@@ -90,20 +104,43 @@ export function getContractReports(
|
|
|
90
104
|
} else if (specifiedContracts !== undefined || upgradeabilityAssessment.upgradeable) {
|
|
91
105
|
const reference = upgradeabilityAssessment.referenceContract;
|
|
92
106
|
const kind = upgradeabilityAssessment.uups ? 'uups' : 'transparent';
|
|
93
|
-
const report = getUpgradeableContractReport(sourceContract, reference, { ...opts, kind: kind });
|
|
107
|
+
const report = getUpgradeableContractReport(sourceContract, reference, { ...opts, kind: kind }, exclude);
|
|
94
108
|
if (report !== undefined) {
|
|
95
109
|
upgradeableContractReports.push(report);
|
|
110
|
+
} else if (specifiedContracts !== undefined) {
|
|
111
|
+
// If there was no report for the specified contract, it was excluded or is abstract.
|
|
112
|
+
const userAction =
|
|
113
|
+
exclude !== undefined
|
|
114
|
+
? `Ensure the contract is not abstract and is not excluded by the exclude option.`
|
|
115
|
+
: `Ensure the contract is not abstract.`;
|
|
116
|
+
throw new ValidateCommandError(
|
|
117
|
+
`No validation report found for contract ${specifiedContracts.contract.fullyQualifiedName}`,
|
|
118
|
+
() => userAction,
|
|
119
|
+
);
|
|
96
120
|
}
|
|
97
121
|
}
|
|
98
122
|
}
|
|
123
|
+
|
|
99
124
|
return upgradeableContractReports;
|
|
100
125
|
}
|
|
101
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Gets a report for an upgradeable contract.
|
|
129
|
+
* Returns undefined if the contract is excluded or is abstract.
|
|
130
|
+
*/
|
|
102
131
|
function getUpgradeableContractReport(
|
|
103
132
|
contract: SourceContract,
|
|
104
133
|
referenceContract: SourceContract | undefined,
|
|
105
|
-
opts: ValidationOptions
|
|
134
|
+
opts: Required<ValidationOptions>,
|
|
135
|
+
exclude?: string[],
|
|
106
136
|
): UpgradeableContractReport | undefined {
|
|
137
|
+
const excludeWithDefaults = defaultExclude.concat(exclude ?? []);
|
|
138
|
+
|
|
139
|
+
if (excludeWithDefaults.some(glob => minimatch(getPath(contract.fullyQualifiedName), glob))) {
|
|
140
|
+
debug('Excluding contract: ' + contract.fullyQualifiedName);
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
|
|
107
144
|
let version;
|
|
108
145
|
try {
|
|
109
146
|
version = getContractVersion(contract.validationData, contract.fullyQualifiedName);
|
|
@@ -118,7 +155,7 @@ function getUpgradeableContractReport(
|
|
|
118
155
|
}
|
|
119
156
|
|
|
120
157
|
debug('Checking: ' + contract.fullyQualifiedName);
|
|
121
|
-
const standaloneReport = getStandaloneReport(contract.validationData, version, opts);
|
|
158
|
+
const standaloneReport = getStandaloneReport(contract.validationData, version, opts, excludeWithDefaults);
|
|
122
159
|
|
|
123
160
|
let reference: string | undefined;
|
|
124
161
|
let storageLayoutReport: LayoutCompatibilityReport | undefined;
|
|
@@ -129,8 +166,12 @@ function getUpgradeableContractReport(
|
|
|
129
166
|
const referenceVersion = getContractVersion(referenceContract.validationData, referenceContract.fullyQualifiedName);
|
|
130
167
|
const referenceLayout = getStorageLayout(referenceContract.validationData, referenceVersion);
|
|
131
168
|
|
|
132
|
-
|
|
133
|
-
|
|
169
|
+
if (referenceContract.buildInfoDirShortName !== contract.buildInfoDirShortName) {
|
|
170
|
+
reference = `${referenceContract.buildInfoDirShortName}:${referenceContract.fullyQualifiedName}`;
|
|
171
|
+
} else {
|
|
172
|
+
reference = referenceContract.fullyQualifiedName;
|
|
173
|
+
}
|
|
174
|
+
storageLayoutReport = getStorageUpgradeReport(referenceLayout, layout, opts);
|
|
134
175
|
}
|
|
135
176
|
|
|
136
177
|
return new UpgradeableContractReport(contract.fullyQualifiedName, reference, standaloneReport, storageLayoutReport);
|
|
@@ -139,8 +180,22 @@ function getUpgradeableContractReport(
|
|
|
139
180
|
function getStandaloneReport(
|
|
140
181
|
data: ValidationData,
|
|
141
182
|
version: Version,
|
|
142
|
-
opts: ValidationOptions
|
|
183
|
+
opts: Required<ValidationOptions>,
|
|
184
|
+
excludeWithDefaults: string[],
|
|
143
185
|
): UpgradeableContractErrorReport {
|
|
144
|
-
const
|
|
145
|
-
|
|
186
|
+
const allErrors = getErrors(data, version, opts);
|
|
187
|
+
|
|
188
|
+
const includeErrors = allErrors.filter(e => {
|
|
189
|
+
const shouldExclude = excludeWithDefaults.some(glob => minimatch(getPath(e.src), glob));
|
|
190
|
+
if (shouldExclude) {
|
|
191
|
+
debug('Excluding error: ' + e.src);
|
|
192
|
+
}
|
|
193
|
+
return !shouldExclude;
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
return new UpgradeableContractErrorReport(includeErrors);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function getPath(srcOrFullyQualifiedName: string): string {
|
|
200
|
+
return srcOrFullyQualifiedName.split(':')[0];
|
|
146
201
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { assert } from '../../utils/assert';
|
|
1
2
|
import { ValidateCommandError } from './error';
|
|
3
|
+
import { BuildInfoDictionary } from './validate-upgrade-safety';
|
|
2
4
|
import { SourceContract } from './validations';
|
|
3
5
|
|
|
4
6
|
export class ReferenceContractNotFound extends Error {
|
|
@@ -12,7 +14,12 @@ export class ReferenceContractNotFound extends Error {
|
|
|
12
14
|
*/
|
|
13
15
|
readonly origin?: string;
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Build info directories that were also searched.
|
|
19
|
+
*/
|
|
20
|
+
readonly buildInfoDirs?: string[];
|
|
21
|
+
|
|
22
|
+
constructor(reference: string, origin?: string, buildInfoDirs?: string[]) {
|
|
16
23
|
const msg =
|
|
17
24
|
origin !== undefined
|
|
18
25
|
? `Could not find contract ${reference} referenced in ${origin}.`
|
|
@@ -20,11 +27,30 @@ export class ReferenceContractNotFound extends Error {
|
|
|
20
27
|
super(msg);
|
|
21
28
|
this.reference = reference;
|
|
22
29
|
this.origin = origin;
|
|
30
|
+
this.buildInfoDirs = buildInfoDirs;
|
|
23
31
|
}
|
|
24
32
|
}
|
|
25
33
|
|
|
26
|
-
export function findContract(
|
|
27
|
-
|
|
34
|
+
export function findContract(
|
|
35
|
+
contractName: string,
|
|
36
|
+
origin: SourceContract | undefined,
|
|
37
|
+
buildInfoDictionary: BuildInfoDictionary,
|
|
38
|
+
onlyMainBuildInfoDir = false,
|
|
39
|
+
) {
|
|
40
|
+
const foundContracts: SourceContract[] = [];
|
|
41
|
+
if (onlyMainBuildInfoDir) {
|
|
42
|
+
if (hasBuildInfoDirWithContractName(contractName) || hasBuildInfoDirWithFullyQualifiedName(contractName)) {
|
|
43
|
+
throw new ValidateCommandError(
|
|
44
|
+
`Contract ${contractName} must be specified without a build info directory name`,
|
|
45
|
+
() => `Build info directory names can only be specified for reference contracts.`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
foundContracts.push(...buildInfoDictionary[''].filter(c => isMatchFound(contractName, c, '')));
|
|
49
|
+
} else {
|
|
50
|
+
for (const [dir, contracts] of Object.entries(buildInfoDictionary)) {
|
|
51
|
+
foundContracts.push(...contracts.filter(c => isMatchFound(contractName, c, dir)));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
28
54
|
|
|
29
55
|
if (foundContracts.length > 1) {
|
|
30
56
|
const msg =
|
|
@@ -39,6 +65,26 @@ export function findContract(contractName: string, origin: SourceContract | unde
|
|
|
39
65
|
} else if (foundContracts.length === 1) {
|
|
40
66
|
return foundContracts[0];
|
|
41
67
|
} else {
|
|
42
|
-
throw new ReferenceContractNotFound(contractName, origin?.fullyQualifiedName);
|
|
68
|
+
throw new ReferenceContractNotFound(contractName, origin?.fullyQualifiedName, Object.keys(buildInfoDictionary));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isMatchFound(contractName: string, foundContract: SourceContract, buildInfoDirShortName: string): boolean {
|
|
73
|
+
let prefix = '';
|
|
74
|
+
if (buildInfoDirShortName.length > 0) {
|
|
75
|
+
assert(foundContract.buildInfoDirShortName === buildInfoDirShortName);
|
|
76
|
+
prefix = `${buildInfoDirShortName}:`;
|
|
43
77
|
}
|
|
78
|
+
return (
|
|
79
|
+
`${prefix}${foundContract.fullyQualifiedName}` === contractName || `${prefix}${foundContract.name}` === contractName
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function hasBuildInfoDirWithContractName(contractName: string): boolean {
|
|
84
|
+
return contractName.split(':').length === 2 && !contractName.includes('.sol:');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function hasBuildInfoDirWithFullyQualifiedName(contractName: string): boolean {
|
|
88
|
+
const tokens = contractName.split(':');
|
|
89
|
+
return tokens.length === 3 && tokens[1].endsWith('.sol');
|
|
44
90
|
}
|
|
@@ -2,6 +2,7 @@ import { getAnnotationArgs, getDocumentation, hasAnnotationTag } from '../../uti
|
|
|
2
2
|
import { inferInitializable, inferUUPS } from '../../validate/query';
|
|
3
3
|
import { ValidateCommandError } from './error';
|
|
4
4
|
import { findContract } from './find-contract';
|
|
5
|
+
import { BuildInfoDictionary } from './validate-upgrade-safety';
|
|
5
6
|
import { SourceContract } from './validations';
|
|
6
7
|
|
|
7
8
|
interface AnnotationAssessment {
|
|
@@ -17,7 +18,7 @@ export interface UpgradeabilityAssessment {
|
|
|
17
18
|
|
|
18
19
|
export function getUpgradeabilityAssessment(
|
|
19
20
|
contract: SourceContract,
|
|
20
|
-
|
|
21
|
+
buildInfoDictionary: BuildInfoDictionary,
|
|
21
22
|
overrideReferenceContract?: SourceContract,
|
|
22
23
|
): UpgradeabilityAssessment {
|
|
23
24
|
const fullContractName = contract.fullyQualifiedName;
|
|
@@ -29,7 +30,7 @@ export function getUpgradeabilityAssessment(
|
|
|
29
30
|
|
|
30
31
|
let referenceContract = overrideReferenceContract;
|
|
31
32
|
if (referenceContract === undefined && annotationAssessment.referenceName !== undefined) {
|
|
32
|
-
referenceContract = findContract(annotationAssessment.referenceName, contract,
|
|
33
|
+
referenceContract = findContract(annotationAssessment.referenceName, contract, buildInfoDictionary);
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
let isReferenceUUPS = false;
|
|
@@ -100,7 +100,7 @@ test('invalid annotation args - upgrades', async t => {
|
|
|
100
100
|
|
|
101
101
|
test('findSpecifiedContracts - requireReference option without contract', async t => {
|
|
102
102
|
try {
|
|
103
|
-
findSpecifiedContracts(
|
|
103
|
+
findSpecifiedContracts({}, withCliDefaults({ requireReference: true }));
|
|
104
104
|
} catch (e: any) {
|
|
105
105
|
t.true(
|
|
106
106
|
e.message.includes(
|