@openzeppelin/upgrades-core 1.36.0 → 1.37.1
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 +9 -0
- package/dist/cli/cli.test.js +99 -1
- package/dist/cli/cli.test.js.map +1 -1
- package/dist/cli/validate/contract-report.d.ts +2 -1
- package/dist/cli/validate/contract-report.d.ts.map +1 -1
- package/dist/cli/validate/contract-report.js +43 -8
- 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/validate-upgrade-safety.d.ts +2 -1
- package/dist/cli/validate/validate-upgrade-safety.d.ts.map +1 -1
- package/dist/cli/validate/validate-upgrade-safety.js +3 -2
- package/dist/cli/validate/validate-upgrade-safety.js.map +1 -1
- package/dist/cli/validate.d.ts +1 -0
- package/dist/cli/validate.d.ts.map +1 -1
- package/dist/cli/validate.js +38 -16
- 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/dist/utils/make-namespaced.js +8 -2
- package/dist/utils/make-namespaced.js.map +1 -1
- package/package.json +4 -3
- package/src/cli/cli.test.ts +141 -1
- package/src/cli/cli.test.ts.md +136 -4
- package/src/cli/cli.test.ts.snap +0 -0
- package/src/cli/validate/contract-report.ts +59 -9
- package/src/cli/validate/default-exclude.ts +3 -0
- package/src/cli/validate/validate-upgrade-safety.ts +3 -1
- package/src/cli/validate.test.ts +22 -0
- package/src/cli/validate.ts +43 -16
- package/src/storage/extract.ts +2 -1
- package/src/utils/annotations.ts +1 -1
- package/src/utils/make-namespaced.ts +10 -2
package/src/cli/cli.test.ts.md
CHANGED
|
@@ -19,8 +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
|
-
--referenceBuildInfoDirs 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.␊
|
|
23
|
-
--
|
|
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␊
|
|
24
25
|
--unsafeAllowRenames Configure storage layout check to allow variable renaming.␊
|
|
25
26
|
--unsafeSkipStorageCheck Skips checking for storage layout compatibility errors. This is a dangerous option meant to be used as a last resort.␊
|
|
26
27
|
`
|
|
@@ -40,8 +41,9 @@ Generated by [AVA](https://avajs.dev).
|
|
|
40
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.␊
|
|
41
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.␊
|
|
42
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.␊
|
|
43
|
-
--referenceBuildInfoDirs 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.␊
|
|
44
|
-
--
|
|
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␊
|
|
45
47
|
--unsafeAllowRenames Configure storage layout check to allow variable renaming.␊
|
|
46
48
|
--unsafeSkipStorageCheck Skips checking for storage layout compatibility errors. This is a dangerous option meant to be used as a last resort.␊
|
|
47
49
|
`
|
|
@@ -383,6 +385,20 @@ Generated by [AVA](https://avajs.dev).
|
|
|
383
385
|
|
|
384
386
|
## validate - references multiple build info dirs by annotation
|
|
385
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
|
+
|
|
386
402
|
> Snapshot 1
|
|
387
403
|
|
|
388
404
|
`Stdout: ✘ contracts/test/cli/ValidateBuildInfoV2_Branch_Bad.sol:MyContract (upgrades from build-info-v2:contracts/test/cli/ValidateBuildInfoV2_Ok.sol:MyContract)␊
|
|
@@ -423,3 +439,119 @@ Generated by [AVA](https://avajs.dev).
|
|
|
423
439
|
FAILED␊
|
|
424
440
|
␊
|
|
425
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
|
|
@@ -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,
|
|
@@ -18,6 +17,9 @@ import { SourceContract } from './validations';
|
|
|
18
17
|
import { LayoutCompatibilityReport } from '../../storage/report';
|
|
19
18
|
import { indent } from '../../utils/indent';
|
|
20
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);
|
|
@@ -66,12 +77,14 @@ export class UpgradeableContractReport implements Report {
|
|
|
66
77
|
* @param buildInfoDictionary Dictionary of build info directories and the source contracts they contain.
|
|
67
78
|
* @param opts The validation options.
|
|
68
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.
|
|
69
81
|
* @returns The upgradeable contract reports.
|
|
70
82
|
*/
|
|
71
83
|
export function getContractReports(
|
|
72
84
|
buildInfoDictionary: BuildInfoDictionary,
|
|
73
85
|
opts: Required<ValidateUpgradeSafetyOptions>,
|
|
74
86
|
specifiedContracts?: SpecifiedContracts,
|
|
87
|
+
exclude?: string[],
|
|
75
88
|
) {
|
|
76
89
|
const upgradeableContractReports: UpgradeableContractReport[] = [];
|
|
77
90
|
|
|
@@ -91,20 +104,43 @@ export function getContractReports(
|
|
|
91
104
|
} else if (specifiedContracts !== undefined || upgradeabilityAssessment.upgradeable) {
|
|
92
105
|
const reference = upgradeabilityAssessment.referenceContract;
|
|
93
106
|
const kind = upgradeabilityAssessment.uups ? 'uups' : 'transparent';
|
|
94
|
-
const report = getUpgradeableContractReport(sourceContract, reference, { ...opts, kind: kind });
|
|
107
|
+
const report = getUpgradeableContractReport(sourceContract, reference, { ...opts, kind: kind }, exclude);
|
|
95
108
|
if (report !== undefined) {
|
|
96
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
|
+
);
|
|
97
120
|
}
|
|
98
121
|
}
|
|
99
122
|
}
|
|
123
|
+
|
|
100
124
|
return upgradeableContractReports;
|
|
101
125
|
}
|
|
102
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Gets a report for an upgradeable contract.
|
|
129
|
+
* Returns undefined if the contract is excluded or is abstract.
|
|
130
|
+
*/
|
|
103
131
|
function getUpgradeableContractReport(
|
|
104
132
|
contract: SourceContract,
|
|
105
133
|
referenceContract: SourceContract | undefined,
|
|
106
|
-
opts: ValidationOptions
|
|
134
|
+
opts: Required<ValidationOptions>,
|
|
135
|
+
exclude?: string[],
|
|
107
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
|
+
|
|
108
144
|
let version;
|
|
109
145
|
try {
|
|
110
146
|
version = getContractVersion(contract.validationData, contract.fullyQualifiedName);
|
|
@@ -119,7 +155,7 @@ function getUpgradeableContractReport(
|
|
|
119
155
|
}
|
|
120
156
|
|
|
121
157
|
debug('Checking: ' + contract.fullyQualifiedName);
|
|
122
|
-
const standaloneReport = getStandaloneReport(contract.validationData, version, opts);
|
|
158
|
+
const standaloneReport = getStandaloneReport(contract.validationData, version, opts, excludeWithDefaults);
|
|
123
159
|
|
|
124
160
|
let reference: string | undefined;
|
|
125
161
|
let storageLayoutReport: LayoutCompatibilityReport | undefined;
|
|
@@ -135,7 +171,7 @@ function getUpgradeableContractReport(
|
|
|
135
171
|
} else {
|
|
136
172
|
reference = referenceContract.fullyQualifiedName;
|
|
137
173
|
}
|
|
138
|
-
storageLayoutReport = getStorageUpgradeReport(referenceLayout, layout,
|
|
174
|
+
storageLayoutReport = getStorageUpgradeReport(referenceLayout, layout, opts);
|
|
139
175
|
}
|
|
140
176
|
|
|
141
177
|
return new UpgradeableContractReport(contract.fullyQualifiedName, reference, standaloneReport, storageLayoutReport);
|
|
@@ -144,8 +180,22 @@ function getUpgradeableContractReport(
|
|
|
144
180
|
function getStandaloneReport(
|
|
145
181
|
data: ValidationData,
|
|
146
182
|
version: Version,
|
|
147
|
-
opts: ValidationOptions
|
|
183
|
+
opts: Required<ValidationOptions>,
|
|
184
|
+
excludeWithDefaults: string[],
|
|
148
185
|
): UpgradeableContractErrorReport {
|
|
149
|
-
const
|
|
150
|
-
|
|
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];
|
|
151
201
|
}
|
|
@@ -28,6 +28,7 @@ export type SpecifiedContracts = {
|
|
|
28
28
|
* @param reference The name or fully qualified name of the reference contract to use for storage layout comparisons. Can only be used along with `contract`. If not specified, uses the `@custom:oz-upgrades-from` annotation in the contract that is being validated.
|
|
29
29
|
* @param opts Validation options, or undefined to use the default validation options.
|
|
30
30
|
* @param referenceBuildInfoDirs 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` param 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.
|
|
31
|
+
* @param exclude Exclude validations for contracts in source file paths that match any of the given glob patterns.
|
|
31
32
|
* @returns The project report.
|
|
32
33
|
*/
|
|
33
34
|
export async function validateUpgradeSafety(
|
|
@@ -36,6 +37,7 @@ export async function validateUpgradeSafety(
|
|
|
36
37
|
reference?: string,
|
|
37
38
|
opts: ValidateUpgradeSafetyOptions = {},
|
|
38
39
|
referenceBuildInfoDirs?: string[],
|
|
40
|
+
exclude?: string[],
|
|
39
41
|
): Promise<ProjectReport> {
|
|
40
42
|
const allOpts = withCliDefaults(opts);
|
|
41
43
|
|
|
@@ -63,7 +65,7 @@ export async function validateUpgradeSafety(
|
|
|
63
65
|
|
|
64
66
|
const specifiedContracts = findSpecifiedContracts(buildInfoDictionary, allOpts, contract, reference);
|
|
65
67
|
|
|
66
|
-
const contractReports = getContractReports(buildInfoDictionary, allOpts, specifiedContracts);
|
|
68
|
+
const contractReports = getContractReports(buildInfoDictionary, allOpts, specifiedContracts, exclude);
|
|
67
69
|
return getProjectReport(contractReports, specifiedContracts !== undefined);
|
|
68
70
|
}
|
|
69
71
|
|
package/src/cli/validate.test.ts
CHANGED
|
@@ -110,3 +110,25 @@ test('withDefaults - invalid unsafeAllow', t => {
|
|
|
110
110
|
)}`,
|
|
111
111
|
});
|
|
112
112
|
});
|
|
113
|
+
|
|
114
|
+
test('withDefaults - empty unsafeAllow', t => {
|
|
115
|
+
const parsedArgs = minimist(['validate', 'build-info.json', '--unsafeAllow']);
|
|
116
|
+
t.throws(() => withDefaults(parsedArgs), {
|
|
117
|
+
message: `Invalid option: --unsafeAllow cannot be empty`,
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('withDefaults - unsafeAllow multiple times', t => {
|
|
122
|
+
const parsedArgs = minimist([
|
|
123
|
+
'validate',
|
|
124
|
+
'build-info.json',
|
|
125
|
+
'--unsafeAllow',
|
|
126
|
+
'selfdestruct',
|
|
127
|
+
'--unsafeAllow',
|
|
128
|
+
'delegatecall',
|
|
129
|
+
'--unsafeAllow',
|
|
130
|
+
'constructor',
|
|
131
|
+
]);
|
|
132
|
+
const opts = withDefaults(parsedArgs);
|
|
133
|
+
t.deepEqual(opts.unsafeAllow, ['selfdestruct', 'delegatecall', 'constructor']);
|
|
134
|
+
});
|
package/src/cli/validate.ts
CHANGED
|
@@ -16,8 +16,9 @@ Options:
|
|
|
16
16
|
--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.
|
|
17
17
|
--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.
|
|
18
18
|
--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.
|
|
19
|
-
--referenceBuildInfoDirs 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.
|
|
20
|
-
--
|
|
19
|
+
--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.
|
|
20
|
+
--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.
|
|
21
|
+
--unsafeAllow "<VALIDATION_ERROR>[,<VALIDATION_ERROR>...]" Selectively disable one or more validation errors. Comma-separated list with one or more of the following: ${errorKinds.join(
|
|
21
22
|
', ',
|
|
22
23
|
)}
|
|
23
24
|
--unsafeAllowRenames Configure storage layout check to allow variable renaming.
|
|
@@ -34,6 +35,7 @@ export async function main(args: string[]): Promise<void> {
|
|
|
34
35
|
functionArgs.reference,
|
|
35
36
|
functionArgs.opts,
|
|
36
37
|
functionArgs.referenceBuildInfoDirs,
|
|
38
|
+
functionArgs.exclude,
|
|
37
39
|
);
|
|
38
40
|
console.log(result.explain());
|
|
39
41
|
process.exitCode = result.ok ? 0 : 1;
|
|
@@ -50,7 +52,7 @@ function parseArgs(args: string[]) {
|
|
|
50
52
|
'unsafeAllowLinkedLibraries',
|
|
51
53
|
'requireReference',
|
|
52
54
|
],
|
|
53
|
-
string: ['unsafeAllow', 'contract', 'reference', 'referenceBuildInfoDirs'],
|
|
55
|
+
string: ['unsafeAllow', 'contract', 'reference', 'referenceBuildInfoDirs', 'exclude'],
|
|
54
56
|
alias: { h: 'help' },
|
|
55
57
|
});
|
|
56
58
|
const extraArgs = parsedArgs._;
|
|
@@ -74,6 +76,7 @@ interface FunctionArgs {
|
|
|
74
76
|
reference?: string;
|
|
75
77
|
opts: Required<ValidateUpgradeSafetyOptions>;
|
|
76
78
|
referenceBuildInfoDirs?: string[];
|
|
79
|
+
exclude?: string[];
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
/**
|
|
@@ -93,7 +96,8 @@ export function getFunctionArgs(parsedArgs: minimist.ParsedArgs, extraArgs: stri
|
|
|
93
96
|
const contract = getAndValidateString(parsedArgs, 'contract');
|
|
94
97
|
const reference = getAndValidateString(parsedArgs, 'reference');
|
|
95
98
|
const opts = withDefaults(parsedArgs);
|
|
96
|
-
const referenceBuildInfoDirs =
|
|
99
|
+
const referenceBuildInfoDirs = getAndValidateStringArray(parsedArgs, 'referenceBuildInfoDirs', true);
|
|
100
|
+
const exclude = getAndValidateStringArray(parsedArgs, 'exclude', false);
|
|
97
101
|
|
|
98
102
|
if (contract === undefined) {
|
|
99
103
|
if (reference !== undefined) {
|
|
@@ -102,18 +106,45 @@ export function getFunctionArgs(parsedArgs: minimist.ParsedArgs, extraArgs: stri
|
|
|
102
106
|
throw new Error('The --requireReference option can only be used along with the --contract option.');
|
|
103
107
|
}
|
|
104
108
|
}
|
|
105
|
-
return { buildInfoDir, contract, reference, opts, referenceBuildInfoDirs };
|
|
109
|
+
return { buildInfoDir, contract, reference, opts, referenceBuildInfoDirs, exclude };
|
|
106
110
|
}
|
|
107
111
|
}
|
|
108
112
|
|
|
109
113
|
function getAndValidateString(parsedArgs: minimist.ParsedArgs, option: string): string | undefined {
|
|
110
114
|
const value = parsedArgs[option];
|
|
111
|
-
if (value !== undefined
|
|
112
|
-
|
|
115
|
+
if (value !== undefined) {
|
|
116
|
+
assertNonEmptyString(value, option);
|
|
117
|
+
}
|
|
118
|
+
return value;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function getAndValidateStringArray(
|
|
122
|
+
parsedArgs: minimist.ParsedArgs,
|
|
123
|
+
option: string,
|
|
124
|
+
useCommaDelimiter: boolean,
|
|
125
|
+
): string[] | undefined {
|
|
126
|
+
const value = parsedArgs[option];
|
|
127
|
+
if (value !== undefined) {
|
|
128
|
+
if (Array.isArray(value)) {
|
|
129
|
+
return value;
|
|
130
|
+
} else {
|
|
131
|
+
assertNonEmptyString(value, option);
|
|
132
|
+
if (useCommaDelimiter) {
|
|
133
|
+
return value.split(/[\s,]+/);
|
|
134
|
+
} else {
|
|
135
|
+
return [value];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
113
138
|
}
|
|
114
139
|
return value;
|
|
115
140
|
}
|
|
116
141
|
|
|
142
|
+
function assertNonEmptyString(value: unknown, option: string) {
|
|
143
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
144
|
+
throw new Error(`Invalid option: --${option} cannot be empty`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
117
148
|
function validateOptions(parsedArgs: minimist.ParsedArgs) {
|
|
118
149
|
const invalidArgs = Object.keys(parsedArgs).filter(
|
|
119
150
|
key =>
|
|
@@ -130,6 +161,7 @@ function validateOptions(parsedArgs: minimist.ParsedArgs) {
|
|
|
130
161
|
'reference',
|
|
131
162
|
'requireReference',
|
|
132
163
|
'referenceBuildInfoDirs',
|
|
164
|
+
'exclude',
|
|
133
165
|
].includes(key),
|
|
134
166
|
);
|
|
135
167
|
if (invalidArgs.length > 0) {
|
|
@@ -137,18 +169,13 @@ function validateOptions(parsedArgs: minimist.ParsedArgs) {
|
|
|
137
169
|
}
|
|
138
170
|
}
|
|
139
171
|
|
|
140
|
-
function getUnsafeAllowKinds(
|
|
172
|
+
function getUnsafeAllowKinds(parseArgs: minimist.ParsedArgs): ValidationError['kind'][] {
|
|
141
173
|
type errorKindsType = (typeof errorKinds)[number];
|
|
142
174
|
|
|
143
|
-
|
|
144
|
-
return [];
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const unsafeAllowTokens: string[] = unsafeAllow.split(/[\s,]+/);
|
|
175
|
+
const unsafeAllowTokens: string[] = getAndValidateStringArray(parseArgs, 'unsafeAllow', true) ?? [];
|
|
148
176
|
if (unsafeAllowTokens.some(token => !errorKinds.includes(token as errorKindsType))) {
|
|
149
|
-
// This includes empty strings
|
|
150
177
|
throw new Error(
|
|
151
|
-
`Invalid option: --unsafeAllow "${unsafeAllow}". Supported values for the --unsafeAllow option are: ${errorKinds.join(
|
|
178
|
+
`Invalid option: --unsafeAllow "${parseArgs['unsafeAllow']}". Supported values for the --unsafeAllow option are: ${errorKinds.join(
|
|
152
179
|
', ',
|
|
153
180
|
)}`,
|
|
154
181
|
);
|
|
@@ -164,7 +191,7 @@ export function withDefaults(parsedArgs: minimist.ParsedArgs): Required<Validate
|
|
|
164
191
|
unsafeSkipStorageCheck: parsedArgs['unsafeSkipStorageCheck'],
|
|
165
192
|
unsafeAllowCustomTypes: parsedArgs['unsafeAllowCustomTypes'],
|
|
166
193
|
unsafeAllowLinkedLibraries: parsedArgs['unsafeAllowLinkedLibraries'],
|
|
167
|
-
unsafeAllow: getUnsafeAllowKinds(parsedArgs
|
|
194
|
+
unsafeAllow: getUnsafeAllowKinds(parsedArgs),
|
|
168
195
|
requireReference: parsedArgs['requireReference'],
|
|
169
196
|
};
|
|
170
197
|
|
package/src/storage/extract.ts
CHANGED
|
@@ -203,7 +203,8 @@ export function loadLayoutType(typeName: TypeName | null | undefined, layout: St
|
|
|
203
203
|
function getRetypedRenamed(varDecl: VariableDeclaration) {
|
|
204
204
|
let retypedFrom, renamedFrom;
|
|
205
205
|
if ('documentation' in varDecl) {
|
|
206
|
-
const docs =
|
|
206
|
+
const docs =
|
|
207
|
+
typeof varDecl.documentation === 'string' ? varDecl.documentation : (varDecl.documentation?.text ?? '');
|
|
207
208
|
for (const { groups } of execall(
|
|
208
209
|
/^\s*(?:@(?<title>\w+)(?::(?<tag>[a-z][a-z-]*))? )?(?<args>(?:(?!^\s@\w+)[^])*)/m,
|
|
209
210
|
docs,
|
package/src/utils/annotations.ts
CHANGED
|
@@ -48,7 +48,7 @@ export function getAnnotationArgs(doc: string, tag: string, supportedArgs?: read
|
|
|
48
48
|
*/
|
|
49
49
|
export function getDocumentation(node: Node) {
|
|
50
50
|
if ('documentation' in node) {
|
|
51
|
-
return typeof node.documentation === 'string' ? node.documentation : node.documentation?.text ?? '';
|
|
51
|
+
return typeof node.documentation === 'string' ? node.documentation : (node.documentation?.text ?? '');
|
|
52
52
|
} else {
|
|
53
53
|
return '';
|
|
54
54
|
}
|
|
@@ -164,7 +164,7 @@ export function makeNamespacedInput(input: SolcInput, output: SolcOutput, solcVe
|
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
/**
|
|
167
|
-
* If Slang is supported for the current platform and
|
|
167
|
+
* If we have the compiler version available and Slang is supported for the current platform and compiler version,
|
|
168
168
|
* use Slang to parse and remove all NatSpec comments that do not precede a struct definition and return the modified content.
|
|
169
169
|
*
|
|
170
170
|
* Otherwise, return the original content.
|
|
@@ -172,7 +172,7 @@ export function makeNamespacedInput(input: SolcInput, output: SolcOutput, solcVe
|
|
|
172
172
|
function tryRemoveNonStructNatSpec(origContent: string, solcVersion: string | undefined): string {
|
|
173
173
|
const natSpecRemovals: Modification[] = [];
|
|
174
174
|
|
|
175
|
-
if (solcVersion !== undefined && tryRequire('@nomicfoundation/slang')) {
|
|
175
|
+
if (solcVersion !== undefined && tryRequire('@nomicfoundation/slang') && slangSupportsVersion(solcVersion)) {
|
|
176
176
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
177
177
|
const { Language } = require('@nomicfoundation/slang/language');
|
|
178
178
|
const { NonterminalKind, TerminalKind } = require('@nomicfoundation/slang/kinds');
|
|
@@ -217,6 +217,14 @@ function tryRequire(id: string) {
|
|
|
217
217
|
return false;
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
function slangSupportsVersion(solcVersion: string): boolean {
|
|
221
|
+
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
222
|
+
const { Language } = require('@nomicfoundation/slang/language');
|
|
223
|
+
/* eslint-enable @typescript-eslint/no-var-requires */
|
|
224
|
+
|
|
225
|
+
return Language.supportedVersions().includes(solcVersion);
|
|
226
|
+
}
|
|
227
|
+
|
|
220
228
|
interface Modification {
|
|
221
229
|
start: number;
|
|
222
230
|
end: number;
|