@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.
Files changed (61) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/cli/cli.test.js +258 -0
  3. package/dist/cli/cli.test.js.map +1 -1
  4. package/dist/cli/validate/build-info-file.d.ts +4 -0
  5. package/dist/cli/validate/build-info-file.d.ts.map +1 -1
  6. package/dist/cli/validate/build-info-file.js +7 -2
  7. package/dist/cli/validate/build-info-file.js.map +1 -1
  8. package/dist/cli/validate/contract-report.d.ts +6 -5
  9. package/dist/cli/validate/contract-report.d.ts.map +1 -1
  10. package/dist/cli/validate/contract-report.js +54 -13
  11. package/dist/cli/validate/contract-report.js.map +1 -1
  12. package/dist/cli/validate/default-exclude.d.ts +2 -0
  13. package/dist/cli/validate/default-exclude.d.ts.map +1 -0
  14. package/dist/cli/validate/default-exclude.js +6 -0
  15. package/dist/cli/validate/default-exclude.js.map +1 -0
  16. package/dist/cli/validate/find-contract.d.ts +7 -2
  17. package/dist/cli/validate/find-contract.d.ts.map +1 -1
  18. package/dist/cli/validate/find-contract.js +32 -4
  19. package/dist/cli/validate/find-contract.js.map +1 -1
  20. package/dist/cli/validate/upgradeability-assessment.d.ts +2 -1
  21. package/dist/cli/validate/upgradeability-assessment.d.ts.map +1 -1
  22. package/dist/cli/validate/upgradeability-assessment.js +2 -2
  23. package/dist/cli/validate/upgradeability-assessment.js.map +1 -1
  24. package/dist/cli/validate/validate-upgrade-safety.d.ts +12 -3
  25. package/dist/cli/validate/validate-upgrade-safety.d.ts.map +1 -1
  26. package/dist/cli/validate/validate-upgrade-safety.js +28 -7
  27. package/dist/cli/validate/validate-upgrade-safety.js.map +1 -1
  28. package/dist/cli/validate/validate-upgrade-safety.test.js +1 -1
  29. package/dist/cli/validate/validations.d.ts +1 -0
  30. package/dist/cli/validate/validations.d.ts.map +1 -1
  31. package/dist/cli/validate/validations.js +1 -0
  32. package/dist/cli/validate/validations.js.map +1 -1
  33. package/dist/cli/validate/validations.test.js +8 -4
  34. package/dist/cli/validate/validations.test.js.map +1 -1
  35. package/dist/cli/validate.d.ts +2 -0
  36. package/dist/cli/validate.d.ts.map +1 -1
  37. package/dist/cli/validate.js +39 -14
  38. package/dist/cli/validate.js.map +1 -1
  39. package/dist/cli/validate.test.js +20 -0
  40. package/dist/cli/validate.test.js.map +1 -1
  41. package/dist/storage/extract.js +1 -1
  42. package/dist/storage/extract.js.map +1 -1
  43. package/dist/utils/annotations.js +1 -1
  44. package/dist/utils/annotations.js.map +1 -1
  45. package/package.json +4 -3
  46. package/src/cli/cli.test.ts +414 -0
  47. package/src/cli/cli.test.ts.md +253 -2
  48. package/src/cli/cli.test.ts.snap +0 -0
  49. package/src/cli/validate/build-info-file.ts +15 -4
  50. package/src/cli/validate/contract-report.ts +71 -16
  51. package/src/cli/validate/default-exclude.ts +3 -0
  52. package/src/cli/validate/find-contract.ts +50 -4
  53. package/src/cli/validate/upgradeability-assessment.ts +3 -2
  54. package/src/cli/validate/validate-upgrade-safety.test.ts +1 -1
  55. package/src/cli/validate/validate-upgrade-safety.ts +38 -6
  56. package/src/cli/validate/validations.test.ts +8 -4
  57. package/src/cli/validate/validations.ts +2 -0
  58. package/src/cli/validate.test.ts +22 -0
  59. package/src/cli/validate.ts +46 -14
  60. package/src/storage/extract.ts +2 -1
  61. package/src/utils/annotations.ts +1 -1
@@ -94,6 +94,21 @@ test('validate - single contract, reference', async t => {
94
94
  t.snapshot(expectation.join('\n'));
95
95
  });
96
96
 
97
+ test('validate - single contract, reference with same build info dir', async t => {
98
+ const temp = await getTempDir(t);
99
+ const buildInfoDir = path.join(temp, 'build-info');
100
+ await fs.mkdir(buildInfoDir);
101
+
102
+ const buildInfo = await artifacts.getBuildInfo(`contracts/test/cli/Validate.sol:BecomesBadLayout`);
103
+ await fs.writeFile(path.join(buildInfoDir, 'validate.json'), JSON.stringify(buildInfo));
104
+
105
+ const error = await t.throwsAsync(
106
+ execAsync(`${CLI} validate ${buildInfoDir} --contract BecomesBadLayout --reference build-info:StorageV1`),
107
+ );
108
+ const expectation: string[] = [`Stdout: ${(error as any).stdout}`, `Stderr: ${(error as any).stderr}`];
109
+ t.snapshot(expectation.join('\n'));
110
+ });
111
+
97
112
  test('validate - single contract, reference, fully qualified names', async t => {
98
113
  const temp = await getTempDir(t);
99
114
  const buildInfo = await artifacts.getBuildInfo(`contracts/test/cli/Validate.sol:BecomesBadLayout`);
@@ -298,3 +313,402 @@ test('validate - references fully qualified version of ambiguous contract name',
298
313
  ).stdout;
299
314
  t.snapshot(output);
300
315
  });
316
+
317
+ test('validate - references other build info dir by command - ok', async t => {
318
+ const temp = await getTempDir(t);
319
+ const referenceDir = path.join(temp, 'build-info-v1');
320
+ await fs.mkdir(referenceDir);
321
+
322
+ const referenceBuildInfo = await artifacts.getBuildInfo(`contracts/test/cli/ValidateBuildInfoV1.sol:MyContract`);
323
+ await fs.writeFile(path.join(referenceDir, 'validate.json'), JSON.stringify(referenceBuildInfo));
324
+
325
+ const updatedDir = path.join(temp, 'build-info');
326
+ await fs.mkdir(updatedDir);
327
+
328
+ const updatedBuildInfo = await artifacts.getBuildInfo(`contracts/test/cli/ValidateBuildInfoV2_Ok.sol:MyContract`);
329
+ await fs.writeFile(path.join(updatedDir, 'validate.json'), JSON.stringify(updatedBuildInfo));
330
+
331
+ const output = await execAsync(
332
+ `${CLI} validate ${updatedDir} --referenceBuildInfoDirs ${referenceDir} --contract MyContract --reference build-info-v1:MyContract`,
333
+ );
334
+ t.snapshot(output);
335
+ });
336
+
337
+ test('validate - references other build info dir by command - reference not found', async t => {
338
+ const temp = await getTempDir(t);
339
+ const referenceDir = path.join(temp, 'build-info-v1');
340
+ await fs.mkdir(referenceDir);
341
+
342
+ const referenceBuildInfo = await artifacts.getBuildInfo(`contracts/test/cli/ValidateBuildInfoV1.sol:MyContract`);
343
+ await fs.writeFile(path.join(referenceDir, 'validate.json'), JSON.stringify(referenceBuildInfo));
344
+
345
+ const updatedDir = path.join(temp, 'build-info');
346
+ await fs.mkdir(updatedDir);
347
+
348
+ const updatedBuildInfo = await artifacts.getBuildInfo(`contracts/test/cli/ValidateBuildInfoV2_Ok.sol:MyContract`);
349
+ await fs.writeFile(path.join(updatedDir, 'validate.json'), JSON.stringify(updatedBuildInfo));
350
+
351
+ const error = await t.throwsAsync(
352
+ execAsync(
353
+ `${CLI} validate ${updatedDir} --referenceBuildInfoDirs ${referenceDir} --contract MyContract --reference build-info-not-found:MyContract`,
354
+ ),
355
+ );
356
+ t.true(error?.message.includes('Could not find contract build-info-not-found:MyContract.'), error?.message);
357
+ });
358
+
359
+ test('validate - references other build info dir by command - dir not found', async t => {
360
+ const temp = await getTempDir(t);
361
+
362
+ const updatedDir = path.join(temp, 'build-info');
363
+ await fs.mkdir(updatedDir);
364
+
365
+ const updatedBuildInfo = await artifacts.getBuildInfo(`contracts/test/cli/ValidateBuildInfoV2_Ok.sol:MyContract`);
366
+ await fs.writeFile(path.join(updatedDir, 'validate.json'), JSON.stringify(updatedBuildInfo));
367
+
368
+ const error = await t.throwsAsync(
369
+ execAsync(
370
+ `${CLI} validate ${updatedDir} --referenceBuildInfoDirs build-info-not-found --contract MyContract --reference build-info-not-found:MyContract`,
371
+ ),
372
+ );
373
+ t.true(
374
+ error?.message.includes(
375
+ "The directory 'build-info-not-found' does not exist or does not contain any build info files.",
376
+ ),
377
+ error?.message,
378
+ );
379
+ });
380
+
381
+ test('validate - references other build info dir by command with fully qualified names - ok', async t => {
382
+ const temp = await getTempDir(t);
383
+ const referenceDir = path.join(temp, 'build-info-v1');
384
+ await fs.mkdir(referenceDir);
385
+
386
+ const referenceBuildInfo = await artifacts.getBuildInfo(`contracts/test/cli/ValidateBuildInfoV1.sol:MyContract`);
387
+ await fs.writeFile(path.join(referenceDir, 'validate.json'), JSON.stringify(referenceBuildInfo));
388
+
389
+ const updatedDir = path.join(temp, 'build-info');
390
+ await fs.mkdir(updatedDir);
391
+
392
+ const updatedBuildInfo = await artifacts.getBuildInfo(`contracts/test/cli/ValidateBuildInfoV2_Ok.sol:MyContract`);
393
+ await fs.writeFile(path.join(updatedDir, 'validate.json'), JSON.stringify(updatedBuildInfo));
394
+
395
+ const output = await execAsync(
396
+ `${CLI} validate ${updatedDir} --referenceBuildInfoDirs ${referenceDir} --contract contracts/test/cli/ValidateBuildInfoV2_Ok.sol:MyContract --reference build-info-v1:contracts/test/cli/ValidateBuildInfoV1.sol:MyContract`,
397
+ );
398
+ t.snapshot(output);
399
+ });
400
+
401
+ test('validate - references other build info dir by command - bad', async t => {
402
+ const temp = await getTempDir(t);
403
+ const referenceDir = path.join(temp, 'build-info-v1');
404
+ await fs.mkdir(referenceDir);
405
+
406
+ const referenceBuildInfo = await artifacts.getBuildInfo(`contracts/test/cli/ValidateBuildInfoV1.sol:MyContract`);
407
+ await fs.writeFile(path.join(referenceDir, 'validate.json'), JSON.stringify(referenceBuildInfo));
408
+
409
+ const updatedDir = path.join(temp, 'build-info');
410
+ await fs.mkdir(updatedDir);
411
+
412
+ const updatedBuildInfo = await artifacts.getBuildInfo(`contracts/test/cli/ValidateBuildInfoV2_Bad.sol:MyContract`);
413
+ await fs.writeFile(path.join(updatedDir, 'validate.json'), JSON.stringify(updatedBuildInfo));
414
+
415
+ const error = await t.throwsAsync(
416
+ execAsync(
417
+ `${CLI} validate ${updatedDir} --referenceBuildInfoDirs ${referenceDir} --contract MyContract --reference build-info-v1:MyContract`,
418
+ ),
419
+ );
420
+ const expectation: string[] = [`Stdout: ${(error as any).stdout}`, `Stderr: ${(error as any).stderr}`];
421
+ t.snapshot(expectation.join('\n'));
422
+ });
423
+
424
+ test('validate - references other build info dir by command with fully qualified names - bad', async t => {
425
+ const temp = await getTempDir(t);
426
+ const referenceDir = path.join(temp, 'build-info-v1');
427
+ await fs.mkdir(referenceDir);
428
+
429
+ const referenceBuildInfo = await artifacts.getBuildInfo(`contracts/test/cli/ValidateBuildInfoV1.sol:MyContract`);
430
+ await fs.writeFile(path.join(referenceDir, 'validate.json'), JSON.stringify(referenceBuildInfo));
431
+
432
+ const updatedDir = path.join(temp, 'build-info');
433
+ await fs.mkdir(updatedDir);
434
+
435
+ const updatedBuildInfo = await artifacts.getBuildInfo(`contracts/test/cli/ValidateBuildInfoV2_Bad.sol:MyContract`);
436
+ await fs.writeFile(path.join(updatedDir, 'validate.json'), JSON.stringify(updatedBuildInfo));
437
+
438
+ const error = await t.throwsAsync(
439
+ execAsync(
440
+ `${CLI} validate ${updatedDir} --referenceBuildInfoDirs ${referenceDir} --contract contracts/test/cli/ValidateBuildInfoV2_Bad.sol:MyContract --reference build-info-v1:contracts/test/cli/ValidateBuildInfoV1.sol:MyContract`,
441
+ ),
442
+ );
443
+ const expectation: string[] = [`Stdout: ${(error as any).stdout}`, `Stderr: ${(error as any).stderr}`];
444
+ t.snapshot(expectation.join('\n'));
445
+ });
446
+
447
+ async function setupMultipleBuildInfoDirsTest(t: ExecutionContext<unknown>) {
448
+ const temp = await getTempDir(t);
449
+
450
+ const v1Dir = path.join(temp, 'build-info-v1');
451
+ await fs.mkdir(v1Dir);
452
+ const v1BuildInfo = await artifacts.getBuildInfo(`contracts/test/cli/ValidateBuildInfoV1.sol:MyContract`);
453
+ await fs.writeFile(path.join(v1Dir, 'validate.json'), JSON.stringify(v1BuildInfo));
454
+
455
+ const v2Dir = path.join(temp, 'build-info-v2');
456
+ await fs.mkdir(v2Dir);
457
+ const v2BuildInfo = await artifacts.getBuildInfo(`contracts/test/cli/ValidateBuildInfoV2_Ok.sol:MyContract`);
458
+ await fs.writeFile(path.join(v2Dir, 'validate.json'), JSON.stringify(v2BuildInfo));
459
+
460
+ const v2BranchDir = path.join(temp, 'build-info-v2-branch');
461
+ await fs.mkdir(v2BranchDir);
462
+ const v2BranchBuildInfoOk = await artifacts.getBuildInfo(
463
+ `contracts/test/cli/ValidateBuildInfoV2_Branch_Ok.sol:MyContract`,
464
+ );
465
+ const v2BranchBuildInfoBad = await artifacts.getBuildInfo(
466
+ `contracts/test/cli/ValidateBuildInfoV2_Branch_Bad.sol:MyContract`,
467
+ );
468
+ await fs.writeFile(path.join(v2BranchDir, 'ok.json'), JSON.stringify(v2BranchBuildInfoOk));
469
+ await fs.writeFile(path.join(v2BranchDir, 'bad.json'), JSON.stringify(v2BranchBuildInfoBad));
470
+ return { v2BranchDir, v1Dir, v2Dir };
471
+ }
472
+
473
+ test('validate - references multiple build info dirs by annotation', async t => {
474
+ const { v2BranchDir, v1Dir, v2Dir } = await setupMultipleBuildInfoDirsTest(t);
475
+
476
+ const error = await t.throwsAsync(
477
+ execAsync(`${CLI} validate ${v2BranchDir} --referenceBuildInfoDirs ${v1Dir},${v2Dir}`),
478
+ );
479
+ const expectation: string[] = [`Stdout: ${(error as any).stdout}`, `Stderr: ${(error as any).stderr}`];
480
+ t.snapshot(expectation.join('\n'));
481
+ });
482
+
483
+ test('validate - references multiple build info dirs by annotation - same arg multiple times', async t => {
484
+ const { v2BranchDir, v1Dir, v2Dir } = await setupMultipleBuildInfoDirsTest(t);
485
+
486
+ const error = await t.throwsAsync(
487
+ execAsync(`${CLI} validate ${v2BranchDir} --referenceBuildInfoDirs ${v1Dir} --referenceBuildInfoDirs ${v2Dir}`),
488
+ );
489
+ const expectation: string[] = [`Stdout: ${(error as any).stdout}`, `Stderr: ${(error as any).stderr}`];
490
+ t.snapshot(expectation.join('\n'));
491
+ });
492
+
493
+ test('validate - references other build info dir by annotation - ok', async t => {
494
+ const temp = await getTempDir(t);
495
+ const referenceDir = path.join(temp, 'build-info-v1');
496
+ await fs.mkdir(referenceDir);
497
+
498
+ const referenceBuildInfo = await artifacts.getBuildInfo(`contracts/test/cli/ValidateBuildInfoV1.sol:MyContract`);
499
+ await fs.writeFile(path.join(referenceDir, 'validate.json'), JSON.stringify(referenceBuildInfo));
500
+
501
+ const updatedDir = path.join(temp, 'build-info');
502
+ await fs.mkdir(updatedDir);
503
+
504
+ const updatedBuildInfo = await artifacts.getBuildInfo(
505
+ `contracts/test/cli/ValidateBuildInfoV2_Annotation_Ok.sol:MyContract`,
506
+ );
507
+ await fs.writeFile(path.join(updatedDir, 'validate.json'), JSON.stringify(updatedBuildInfo));
508
+
509
+ const output = await execAsync(
510
+ `${CLI} validate ${updatedDir} --referenceBuildInfoDirs ${referenceDir} --contract MyContract`,
511
+ );
512
+ t.snapshot(output);
513
+ });
514
+
515
+ test('validate - references other build info dir by annotation - bad', async t => {
516
+ const temp = await getTempDir(t);
517
+ const referenceDir = path.join(temp, 'build-info-v1');
518
+ await fs.mkdir(referenceDir);
519
+
520
+ const referenceBuildInfo = await artifacts.getBuildInfo(`contracts/test/cli/ValidateBuildInfoV1.sol:MyContract`);
521
+ await fs.writeFile(path.join(referenceDir, 'validate.json'), JSON.stringify(referenceBuildInfo));
522
+
523
+ const updatedDir = path.join(temp, 'build-info');
524
+ await fs.mkdir(updatedDir);
525
+
526
+ t.assert(updatedDir !== referenceDir);
527
+ const updatedBuildInfo = await artifacts.getBuildInfo(
528
+ `contracts/test/cli/ValidateBuildInfoV2_Annotation_Bad.sol:MyContract`,
529
+ );
530
+ await fs.writeFile(path.join(updatedDir, 'validate.json'), JSON.stringify(updatedBuildInfo));
531
+
532
+ const error = await t.throwsAsync(
533
+ execAsync(`${CLI} validate ${updatedDir} --referenceBuildInfoDirs ${referenceDir} --contract MyContract`),
534
+ );
535
+ const expectation: string[] = [`Stdout: ${(error as any).stdout}`, `Stderr: ${(error as any).stderr}`];
536
+ t.snapshot(expectation.join('\n'));
537
+ });
538
+
539
+ test('validate - contract must not have build info dir name', async t => {
540
+ const temp = await getTempDir(t);
541
+ const referenceDir = path.join(temp, 'build-info-v1');
542
+ await fs.mkdir(referenceDir);
543
+
544
+ const referenceBuildInfo = await artifacts.getBuildInfo(`contracts/test/cli/ValidateBuildInfoV1.sol:MyContract`);
545
+ await fs.writeFile(path.join(referenceDir, 'validate.json'), JSON.stringify(referenceBuildInfo));
546
+
547
+ const updatedDir = path.join(temp, 'build-info');
548
+ await fs.mkdir(updatedDir);
549
+
550
+ const updatedBuildInfo = await artifacts.getBuildInfo(`contracts/test/cli/ValidateBuildInfoV2_Ok.sol:MyContract`);
551
+ await fs.writeFile(path.join(updatedDir, 'validate.json'), JSON.stringify(updatedBuildInfo));
552
+
553
+ const error = await t.throwsAsync(
554
+ execAsync(
555
+ `${CLI} validate ${updatedDir} --referenceBuildInfoDirs ${referenceDir} --contract build-info:MyContract --reference build-info-v1:MyContract`,
556
+ ),
557
+ );
558
+ t.true(
559
+ error?.message.includes('Contract build-info:MyContract must be specified without a build info directory name'),
560
+ error?.message,
561
+ );
562
+ });
563
+
564
+ test('validate - contract must not have build info dir name - fully qualified', async t => {
565
+ const temp = await getTempDir(t);
566
+ const referenceDir = path.join(temp, 'build-info-v1');
567
+ await fs.mkdir(referenceDir);
568
+
569
+ const referenceBuildInfo = await artifacts.getBuildInfo(`contracts/test/cli/ValidateBuildInfoV1.sol:MyContract`);
570
+ await fs.writeFile(path.join(referenceDir, 'validate.json'), JSON.stringify(referenceBuildInfo));
571
+
572
+ const updatedDir = path.join(temp, 'build-info');
573
+ await fs.mkdir(updatedDir);
574
+
575
+ const updatedBuildInfo = await artifacts.getBuildInfo(`contracts/test/cli/ValidateBuildInfoV2_Ok.sol:MyContract`);
576
+ await fs.writeFile(path.join(updatedDir, 'validate.json'), JSON.stringify(updatedBuildInfo));
577
+
578
+ const error = await t.throwsAsync(
579
+ execAsync(
580
+ `${CLI} validate ${updatedDir} --referenceBuildInfoDirs ${referenceDir} --contract build-info:contracts/test/cli/ValidateBuildInfoV2_Ok.sol:MyContract --reference build-info-v1:MyContract`,
581
+ ),
582
+ );
583
+ t.true(
584
+ error?.message.includes(
585
+ 'Contract build-info:contracts/test/cli/ValidateBuildInfoV2_Ok.sol:MyContract must be specified without a build info directory name',
586
+ ),
587
+ error?.message,
588
+ );
589
+ });
590
+
591
+ test('validate - excludes by pattern - no match', async t => {
592
+ const temp = await getTempDir(t);
593
+ const buildInfo = await artifacts.getBuildInfo(`contracts/test/cli/excludes/UsesAbstractUUPS.sol:UsesAbstractUUPS`);
594
+ await fs.writeFile(path.join(temp, 'validate.json'), JSON.stringify(buildInfo));
595
+
596
+ const error = await t.throwsAsync(execAsync(`${CLI} validate ${temp} --exclude "**/NoMatch.sol"`));
597
+ const expectation: string[] = [`Stdout: ${(error as any).stdout}`, `Stderr: ${(error as any).stderr}`];
598
+ t.snapshot(expectation.join('\n'));
599
+ });
600
+
601
+ test('validate - excludes by pattern - some match', async t => {
602
+ const temp = await getTempDir(t);
603
+ const buildInfo = await artifacts.getBuildInfo(`contracts/test/cli/excludes/UsesAbstractUUPS.sol:UsesAbstractUUPS`);
604
+ await fs.writeFile(path.join(temp, 'validate.json'), JSON.stringify(buildInfo));
605
+
606
+ const error = await t.throwsAsync(execAsync(`${CLI} validate ${temp} --exclude "**/Abstract*.sol"`));
607
+ const expectation: string[] = [`Stdout: ${(error as any).stdout}`, `Stderr: ${(error as any).stderr}`];
608
+ t.snapshot(expectation.join('\n'));
609
+ });
610
+
611
+ test('validate - excludes by pattern - all match', async t => {
612
+ const temp = await getTempDir(t);
613
+ const buildInfo = await artifacts.getBuildInfo(`contracts/test/cli/excludes/UsesAbstractUUPS.sol:UsesAbstractUUPS`);
614
+ await fs.writeFile(path.join(temp, 'validate.json'), JSON.stringify(buildInfo));
615
+
616
+ const output = await execAsync(`${CLI} validate ${temp} --exclude "**/excludes/*.sol"`);
617
+ t.snapshot(output);
618
+ });
619
+
620
+ test('validate - excludes by pattern - all match using commas within glob', async t => {
621
+ const temp = await getTempDir(t);
622
+ const buildInfo = await artifacts.getBuildInfo(`contracts/test/cli/excludes/UsesAbstractUUPS.sol:UsesAbstractUUPS`);
623
+ await fs.writeFile(path.join(temp, 'validate.json'), JSON.stringify(buildInfo));
624
+
625
+ const output = await execAsync(`${CLI} validate ${temp} --exclude "**/excludes/{Abstract*,UsesAbstractUUPS}.sol"`);
626
+ t.snapshot(output);
627
+ });
628
+
629
+ test('validate - exclude passed multiple times', async t => {
630
+ const temp = await getTempDir(t);
631
+ const buildInfo = await artifacts.getBuildInfo(`contracts/test/cli/excludes/UsesAbstractUUPS.sol:UsesAbstractUUPS`);
632
+ await fs.writeFile(path.join(temp, 'validate.json'), JSON.stringify(buildInfo));
633
+
634
+ const output = await execAsync(
635
+ `${CLI} validate ${temp} --exclude "**/excludes/Abstract*.sol" --exclude "**/UsesAbstractUUPS.sol"`,
636
+ );
637
+ t.snapshot(output);
638
+ });
639
+
640
+ test('validate - excludes specified contract', async t => {
641
+ const temp = await getTempDir(t);
642
+ const buildInfo = await artifacts.getBuildInfo(`contracts/test/cli/Validate.sol:BecomesBadLayout`);
643
+ await fs.writeFile(path.join(temp, 'validate.json'), JSON.stringify(buildInfo));
644
+
645
+ const error = await t.throwsAsync(
646
+ execAsync(`${CLI} validate ${temp} --contract BecomesBadLayout --reference StorageV1 --exclude "**/Validate.sol"`),
647
+ );
648
+ t.true(
649
+ error?.message.includes('No validation report found for contract contracts/test/cli/Validate.sol:BecomesBadLayout'),
650
+ error?.message,
651
+ );
652
+ });
653
+
654
+ test('validate - excludes UpgradeableBeacon and its parents by default', async t => {
655
+ const temp = await getTempDir(t);
656
+ const buildInfo = await artifacts.getBuildInfo(`contracts/test/cli/excludes/ImportVersionsAndBeacon.sol:Dummy`);
657
+ await fs.writeFile(path.join(temp, 'validate.json'), JSON.stringify(buildInfo));
658
+
659
+ const error = await t.throwsAsync(execAsync(`${CLI} validate ${temp}`));
660
+ const expectation: string[] = [`Stdout: ${(error as any).stdout}`, `Stderr: ${(error as any).stderr}`];
661
+ t.snapshot(expectation.join('\n'));
662
+ });
663
+
664
+ test('validate - excludes UpgradeableBeacon and its parents by default, and excludes one contract from layout comparisions', async t => {
665
+ const temp = await getTempDir(t);
666
+ const buildInfo = await artifacts.getBuildInfo(`contracts/test/cli/excludes/ImportVersionsAndBeacon.sol:Dummy`);
667
+ await fs.writeFile(path.join(temp, 'validate.json'), JSON.stringify(buildInfo));
668
+
669
+ const error = await t.throwsAsync(execAsync(`${CLI} validate ${temp} --exclude "**/V2Bad1.sol"`));
670
+ const expectation: string[] = [`Stdout: ${(error as any).stdout}`, `Stderr: ${(error as any).stderr}`];
671
+ t.snapshot(expectation.join('\n'));
672
+ });
673
+
674
+ test('validate - self reference by annotation', async t => {
675
+ const temp = await getTempDir(t);
676
+ const buildInfo = await artifacts.getBuildInfo(`contracts/test/cli/SelfReferences.sol:SelfReference`);
677
+ await fs.writeFile(path.join(temp, 'validate.json'), JSON.stringify(buildInfo));
678
+
679
+ const error = await t.throwsAsync(execAsync(`${CLI} validate ${temp} --contract SelfReference`));
680
+ t.assert(error?.message.includes('must not use itself as a reference'), error?.message);
681
+ });
682
+
683
+ test('validate - self reference by fully qualified annotation', async t => {
684
+ const temp = await getTempDir(t);
685
+ const buildInfo = await artifacts.getBuildInfo(`contracts/test/cli/SelfReferences.sol:SelfReference`);
686
+ await fs.writeFile(path.join(temp, 'validate.json'), JSON.stringify(buildInfo));
687
+
688
+ const error = await t.throwsAsync(execAsync(`${CLI} validate ${temp} --contract SelfReferenceFullyQualified`));
689
+ t.assert(error?.message.includes('must not use itself as a reference'), error?.message);
690
+ });
691
+
692
+ test('validate - self reference by option', async t => {
693
+ const temp = await getTempDir(t);
694
+ const buildInfo = await artifacts.getBuildInfo(`contracts/test/cli/SelfReferences.sol:SelfReference`);
695
+ await fs.writeFile(path.join(temp, 'validate.json'), JSON.stringify(buildInfo));
696
+
697
+ const error = await t.throwsAsync(
698
+ execAsync(`${CLI} validate ${temp} --contract NoAnnotation --reference NoAnnotation`),
699
+ );
700
+ t.assert(error?.message.includes('must not use itself as a reference'), error?.message);
701
+ });
702
+
703
+ test('validate - self reference by fully qualified option', async t => {
704
+ const temp = await getTempDir(t);
705
+ const buildInfo = await artifacts.getBuildInfo(`contracts/test/cli/SelfReferences.sol:SelfReference`);
706
+ await fs.writeFile(path.join(temp, 'validate.json'), JSON.stringify(buildInfo));
707
+
708
+ const error = await t.throwsAsync(
709
+ execAsync(
710
+ `${CLI} validate ${temp} --contract NoAnnotation --reference contracts/test/cli/SelfReferences.sol:NoAnnotation`,
711
+ ),
712
+ );
713
+ t.assert(error?.message.includes('must not use itself as a reference'), error?.message);
714
+ });