@openzeppelin/upgrades-core 1.26.1 → 1.27.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 (123) hide show
  1. package/artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/ITransparentUpgradeableProxy.json +126 -0
  2. package/dist/cli/cli.d.ts +3 -0
  3. package/dist/cli/cli.d.ts.map +1 -0
  4. package/dist/cli/cli.js +9 -0
  5. package/dist/cli/cli.js.map +1 -0
  6. package/dist/cli/cli.test.d.ts +2 -0
  7. package/dist/cli/cli.test.d.ts.map +1 -0
  8. package/dist/cli/cli.test.js +52 -0
  9. package/dist/cli/cli.test.js.map +1 -0
  10. package/dist/cli/index.d.ts +4 -0
  11. package/dist/cli/index.d.ts.map +1 -0
  12. package/dist/cli/index.js +10 -0
  13. package/dist/cli/index.js.map +1 -0
  14. package/dist/cli/validate/build-info-file.d.ts +22 -0
  15. package/dist/cli/validate/build-info-file.d.ts.map +1 -0
  16. package/dist/cli/validate/build-info-file.js +110 -0
  17. package/dist/cli/validate/build-info-file.js.map +1 -0
  18. package/dist/cli/validate/build-info-file.test.d.ts +2 -0
  19. package/dist/cli/validate/build-info-file.test.d.ts.map +1 -0
  20. package/dist/cli/validate/build-info-file.test.js +175 -0
  21. package/dist/cli/validate/build-info-file.test.js.map +1 -0
  22. package/dist/cli/validate/contract-report.d.ts +31 -0
  23. package/dist/cli/validate/contract-report.d.ts.map +1 -0
  24. package/dist/cli/validate/contract-report.js +107 -0
  25. package/dist/cli/validate/contract-report.js.map +1 -0
  26. package/dist/cli/validate/error.d.ts +4 -0
  27. package/dist/cli/validate/error.d.ts.map +1 -0
  28. package/dist/cli/validate/error.js +8 -0
  29. package/dist/cli/validate/error.js.map +1 -0
  30. package/dist/cli/validate/project-report.d.ts +18 -0
  31. package/dist/cli/validate/project-report.d.ts.map +1 -0
  32. package/dist/cli/validate/project-report.js +42 -0
  33. package/dist/cli/validate/project-report.js.map +1 -0
  34. package/dist/cli/validate/project-report.test.d.ts +2 -0
  35. package/dist/cli/validate/project-report.test.d.ts.map +1 -0
  36. package/dist/cli/validate/project-report.test.js +90 -0
  37. package/dist/cli/validate/project-report.test.js.map +1 -0
  38. package/dist/cli/validate/upgradeability-assessment.d.ts +19 -0
  39. package/dist/cli/validate/upgradeability-assessment.d.ts.map +1 -0
  40. package/dist/cli/validate/upgradeability-assessment.js +103 -0
  41. package/dist/cli/validate/upgradeability-assessment.js.map +1 -0
  42. package/dist/cli/validate/validate-upgrade-safety.d.ts +16 -0
  43. package/dist/cli/validate/validate-upgrade-safety.d.ts.map +1 -0
  44. package/dist/cli/validate/validate-upgrade-safety.js +23 -0
  45. package/dist/cli/validate/validate-upgrade-safety.js.map +1 -0
  46. package/dist/cli/validate/validate-upgrade-safety.test.d.ts +2 -0
  47. package/dist/cli/validate/validate-upgrade-safety.test.d.ts.map +1 -0
  48. package/dist/cli/validate/validate-upgrade-safety.test.js +74 -0
  49. package/dist/cli/validate/validate-upgrade-safety.test.js.map +1 -0
  50. package/dist/cli/validate/validations.d.ts +11 -0
  51. package/dist/cli/validate/validations.d.ts.map +1 -0
  52. package/dist/cli/validate/validations.js +40 -0
  53. package/dist/cli/validate/validations.js.map +1 -0
  54. package/dist/cli/validate/validations.test.d.ts +2 -0
  55. package/dist/cli/validate/validations.test.d.ts.map +1 -0
  56. package/dist/cli/validate/validations.test.js +64 -0
  57. package/dist/cli/validate/validations.test.js.map +1 -0
  58. package/dist/cli/validate.d.ts +16 -0
  59. package/dist/cli/validate.d.ts.map +1 -0
  60. package/dist/cli/validate.js +119 -0
  61. package/dist/cli/validate.js.map +1 -0
  62. package/dist/cli/validate.test.d.ts +2 -0
  63. package/dist/cli/validate.test.d.ts.map +1 -0
  64. package/dist/cli/validate.test.js +108 -0
  65. package/dist/cli/validate.test.js.map +1 -0
  66. package/dist/index.d.ts +1 -0
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +5 -1
  69. package/dist/index.js.map +1 -1
  70. package/dist/manifest.test.js +2 -1
  71. package/dist/manifest.test.js.map +1 -1
  72. package/dist/utils/annotations.d.ts +16 -0
  73. package/dist/utils/annotations.d.ts.map +1 -0
  74. package/dist/utils/annotations.js +46 -0
  75. package/dist/utils/annotations.js.map +1 -0
  76. package/dist/utils/contract-name.d.ts +2 -0
  77. package/dist/utils/contract-name.d.ts.map +1 -0
  78. package/dist/utils/contract-name.js +8 -0
  79. package/dist/utils/contract-name.js.map +1 -0
  80. package/dist/validate/index.d.ts +1 -1
  81. package/dist/validate/index.d.ts.map +1 -1
  82. package/dist/validate/index.js +2 -1
  83. package/dist/validate/index.js.map +1 -1
  84. package/dist/validate/query.d.ts +8 -1
  85. package/dist/validate/query.d.ts.map +1 -1
  86. package/dist/validate/query.js +16 -3
  87. package/dist/validate/query.js.map +1 -1
  88. package/dist/validate/run.d.ts +1 -4
  89. package/dist/validate/run.d.ts.map +1 -1
  90. package/dist/validate/run.js +8 -38
  91. package/dist/validate/run.js.map +1 -1
  92. package/package.json +9 -3
  93. package/src/cli/cli.test.ts +57 -0
  94. package/src/cli/cli.test.ts.md +138 -0
  95. package/src/cli/cli.test.ts.snap +0 -0
  96. package/src/cli/cli.ts +9 -0
  97. package/src/cli/index.ts +3 -0
  98. package/src/cli/validate/build-info-file.test.ts +211 -0
  99. package/src/cli/validate/build-info-file.ts +145 -0
  100. package/src/cli/validate/contract-report.ts +130 -0
  101. package/src/cli/validate/error.ts +3 -0
  102. package/src/cli/validate/project-report.test.ts +124 -0
  103. package/src/cli/validate/project-report.test.ts.md +40 -0
  104. package/src/cli/validate/project-report.test.ts.snap +0 -0
  105. package/src/cli/validate/project-report.ts +44 -0
  106. package/src/cli/validate/upgradeability-assessment.ts +137 -0
  107. package/src/cli/validate/validate-upgrade-safety.test.ts +102 -0
  108. package/src/cli/validate/validate-upgrade-safety.test.ts.md +98 -0
  109. package/src/cli/validate/validate-upgrade-safety.test.ts.snap +0 -0
  110. package/src/cli/validate/validate-upgrade-safety.ts +29 -0
  111. package/src/cli/validate/validations.test.ts +72 -0
  112. package/src/cli/validate/validations.test.ts.md +87 -0
  113. package/src/cli/validate/validations.test.ts.snap +0 -0
  114. package/src/cli/validate/validations.ts +53 -0
  115. package/src/cli/validate.test.ts +112 -0
  116. package/src/cli/validate.ts +134 -0
  117. package/src/index.ts +2 -0
  118. package/src/manifest.test.ts +2 -1
  119. package/src/utils/annotations.ts +47 -0
  120. package/src/utils/contract-name.ts +3 -0
  121. package/src/validate/index.ts +1 -0
  122. package/src/validate/query.ts +16 -3
  123. package/src/validate/run.ts +5 -41
@@ -0,0 +1,112 @@
1
+ import test from 'ava';
2
+
3
+ import minimist from 'minimist';
4
+ import { getFunctionArgs, withDefaults } from './validate';
5
+ import sinon from 'sinon';
6
+ import { errorKinds } from '../validate/run';
7
+
8
+ test.afterEach.always(() => {
9
+ sinon.restore();
10
+ });
11
+
12
+ test('getFunctionArgs - invalid command', t => {
13
+ const parsedArgs = minimist(['invalid']);
14
+ const extraArgs = parsedArgs._;
15
+ t.throws(() => getFunctionArgs(parsedArgs, extraArgs), {
16
+ message: `Invalid command: invalid. Supported commands are: validate`,
17
+ });
18
+ });
19
+
20
+ test('getFunctionArgs - invalid options', async t => {
21
+ const parsedArgs = minimist(['validate', 'build-info.json', '--foo', '--bar', 'xyz']);
22
+ const extraArgs = parsedArgs._;
23
+ t.throws(() => getFunctionArgs(parsedArgs, extraArgs), {
24
+ message: `Invalid options: foo, bar`,
25
+ });
26
+ });
27
+
28
+ test('getFunctionArgs - command only', t => {
29
+ const parsedArgs = minimist(['validate']);
30
+ const extraArgs = parsedArgs._;
31
+ const functionArgs = getFunctionArgs(parsedArgs, extraArgs);
32
+ if (functionArgs === undefined) {
33
+ t.fail();
34
+ } else {
35
+ t.is(functionArgs.buildInfoDir, undefined);
36
+ t.is(functionArgs.opts.unsafeAllowRenames, false);
37
+ t.is(functionArgs.opts.unsafeSkipStorageCheck, false);
38
+ t.is(functionArgs.opts.unsafeAllowCustomTypes, false);
39
+ t.is(functionArgs.opts.unsafeAllowLinkedLibraries, false);
40
+ t.deepEqual(functionArgs.opts.unsafeAllow, []);
41
+ }
42
+ });
43
+
44
+ test('getFunctionArgs - command with arg', t => {
45
+ const parsedArgs = minimist(['validate', 'build-info.json']);
46
+ const extraArgs = parsedArgs._;
47
+ const functionArgs = getFunctionArgs(parsedArgs, extraArgs);
48
+ if (functionArgs === undefined) {
49
+ t.fail();
50
+ } else {
51
+ t.is(functionArgs.buildInfoDir, 'build-info.json');
52
+ t.is(functionArgs.opts.unsafeAllowRenames, false);
53
+ t.is(functionArgs.opts.unsafeSkipStorageCheck, false);
54
+ t.is(functionArgs.opts.unsafeAllowCustomTypes, false);
55
+ t.is(functionArgs.opts.unsafeAllowLinkedLibraries, false);
56
+ t.deepEqual(functionArgs.opts.unsafeAllow, []);
57
+ }
58
+ });
59
+
60
+ test('withDefaults - empty', t => {
61
+ const parsedArgs = minimist(['validate', 'build-info.json']);
62
+ const opts = withDefaults(parsedArgs);
63
+ t.is(opts.unsafeAllowRenames, false);
64
+ t.is(opts.unsafeSkipStorageCheck, false);
65
+ t.is(opts.unsafeAllowCustomTypes, false);
66
+ t.is(opts.unsafeAllowLinkedLibraries, false);
67
+ t.deepEqual(opts.unsafeAllow, []);
68
+ });
69
+
70
+ test('withDefaults - some', t => {
71
+ const parsedArgs = minimist([
72
+ 'validate',
73
+ 'build-info.json',
74
+ '--unsafeAllowRenames',
75
+ '--unsafeAllow',
76
+ 'selfdestruct, delegatecall,constructor',
77
+ ]);
78
+ const opts = withDefaults(parsedArgs);
79
+ t.is(opts.unsafeAllowRenames, true);
80
+ t.is(opts.unsafeSkipStorageCheck, false);
81
+ t.is(opts.unsafeAllowCustomTypes, false);
82
+ t.is(opts.unsafeAllowLinkedLibraries, false);
83
+ t.deepEqual(opts.unsafeAllow, ['selfdestruct', 'delegatecall', 'constructor']);
84
+ });
85
+
86
+ test('withDefaults - all', t => {
87
+ const parsedArgs = minimist([
88
+ 'validate',
89
+ 'build-info.json',
90
+ '--unsafeAllowRenames',
91
+ '--unsafeSkipStorageCheck',
92
+ '--unsafeAllowCustomTypes',
93
+ '--unsafeAllowLinkedLibraries',
94
+ '--unsafeAllow',
95
+ ...errorKinds,
96
+ ]);
97
+ const opts = withDefaults(parsedArgs);
98
+ t.is(opts.unsafeAllowRenames, true);
99
+ t.is(opts.unsafeSkipStorageCheck, true);
100
+ t.is(opts.unsafeAllowCustomTypes, true);
101
+ t.is(opts.unsafeAllowLinkedLibraries, true);
102
+ t.true(opts.unsafeAllow.every((kind: string) => errorKinds.includes(kind as typeof errorKinds[number])));
103
+ });
104
+
105
+ test('withDefaults - invalid unsafeAllow', t => {
106
+ const parsedArgs = minimist(['validate', 'build-info.json', '--unsafeAllow', 'foo']);
107
+ t.throws(() => withDefaults(parsedArgs), {
108
+ message: `Invalid option: --unsafeAllow "foo". Supported values for the --unsafeAllow option are: ${errorKinds.join(
109
+ ', ',
110
+ )}`,
111
+ });
112
+ });
@@ -0,0 +1,134 @@
1
+ import minimist from 'minimist';
2
+
3
+ import { ValidateUpgradeSafetyOptions, validateUpgradeSafety } from '.';
4
+ import { withValidationDefaults } from '..';
5
+ import { ValidationError, errorKinds } from '../validate/run';
6
+ import debug from '../utils/debug';
7
+
8
+ const USAGE = 'Usage: npx @openzeppelin/upgrades-core validate [<BUILD_INFO_DIR>] [<OPTIONS>]';
9
+ const DETAILS = `
10
+ Detects upgradeable contracts from a build info directory and validates whether they are upgrade safe.
11
+
12
+ Arguments:
13
+ <BUILD_INFO_DIR> Optional path to the build info directory which contains JSON files with Solidity compiler input and output. Defaults to 'artifacts/build-info' for Hardhat projects or 'out/build-info' for Foundry projects. If your project uses a custom output directory, you must specify its build info directory here.
14
+
15
+ Options:
16
+ --unsafeAllow "<VALIDATION_ERRORS>" Selectively disable one or more validation errors. Comma-separated list with one or more of the following: ${errorKinds.join(
17
+ ', ',
18
+ )}
19
+ --unsafeAllowRenames Configure storage layout check to allow variable renaming.
20
+ --unsafeSkipStorageCheck Skips checking for storage layout compatibility errors. This is a dangerous option meant to be used as a last resort.`;
21
+
22
+ export async function main(args: string[]): Promise<void> {
23
+ const { parsedArgs, extraArgs } = parseArgs(args);
24
+
25
+ if (!help(parsedArgs, extraArgs)) {
26
+ const functionArgs = getFunctionArgs(parsedArgs, extraArgs);
27
+ const result = await validateUpgradeSafety(functionArgs.buildInfoDir, functionArgs.opts);
28
+ console.log(result.explain());
29
+ process.exitCode = result.ok ? 0 : 1;
30
+ }
31
+ }
32
+
33
+ function parseArgs(args: string[]) {
34
+ const parsedArgs = minimist(args, {
35
+ boolean: [
36
+ 'help',
37
+ 'unsafeAllowRenames',
38
+ 'unsafeSkipStorageCheck',
39
+ 'unsafeAllowCustomTypes',
40
+ 'unsafeAllowLinkedLibraries',
41
+ ],
42
+ string: ['unsafeAllow'],
43
+ alias: { h: 'help' },
44
+ });
45
+ const extraArgs = parsedArgs._;
46
+ debug('parsedArgs', parsedArgs);
47
+ return { parsedArgs, extraArgs };
48
+ }
49
+
50
+ function help(parsedArgs: minimist.ParsedArgs, extraArgs: string[]): boolean {
51
+ if (extraArgs.length === 0 || parsedArgs['help']) {
52
+ console.log(USAGE);
53
+ console.log(DETAILS);
54
+ return true;
55
+ } else {
56
+ return false;
57
+ }
58
+ }
59
+
60
+ interface FunctionArgs {
61
+ buildInfoDir?: string;
62
+ opts: Required<ValidateUpgradeSafetyOptions>;
63
+ }
64
+
65
+ /**
66
+ * Gets and validates function arguments and options.
67
+ * @returns Function arguments
68
+ * @throws Error if any arguments or options are invalid.
69
+ */
70
+ export function getFunctionArgs(parsedArgs: minimist.ParsedArgs, extraArgs: string[]): FunctionArgs {
71
+ if (extraArgs.length === 0) {
72
+ throw new Error('Missing command. Supported commands are: validate');
73
+ } else if (extraArgs[0] !== 'validate') {
74
+ throw new Error(`Invalid command: ${extraArgs[0]}. Supported commands are: validate`);
75
+ } else if (extraArgs.length > 2) {
76
+ throw new Error('The validate command takes only one argument: the build info directory.');
77
+ } else {
78
+ const buildInfoDir = extraArgs.length === 1 ? undefined : extraArgs[1];
79
+ const opts = withDefaults(parsedArgs);
80
+ return { buildInfoDir, opts };
81
+ }
82
+ }
83
+
84
+ function validateOptions(parsedArgs: minimist.ParsedArgs) {
85
+ const invalidArgs = Object.keys(parsedArgs).filter(
86
+ key =>
87
+ ![
88
+ 'help',
89
+ 'h',
90
+ '_',
91
+ 'unsafeAllowRenames',
92
+ 'unsafeSkipStorageCheck',
93
+ 'unsafeAllowCustomTypes',
94
+ 'unsafeAllowLinkedLibraries',
95
+ 'unsafeAllow',
96
+ ].includes(key),
97
+ );
98
+ if (invalidArgs.length > 0) {
99
+ throw new Error(`Invalid options: ${invalidArgs.join(', ')}`);
100
+ }
101
+ }
102
+
103
+ function getUnsafeAllowKinds(unsafeAllow: string | undefined): ValidationError['kind'][] {
104
+ type errorKindsType = typeof errorKinds[number];
105
+
106
+ if (unsafeAllow === undefined) {
107
+ return [];
108
+ }
109
+
110
+ const unsafeAllowTokens: string[] = unsafeAllow.split(/[\s,]+/);
111
+ if (unsafeAllowTokens.some(token => !errorKinds.includes(token as errorKindsType))) {
112
+ // This includes empty strings
113
+ throw new Error(
114
+ `Invalid option: --unsafeAllow "${unsafeAllow}". Supported values for the --unsafeAllow option are: ${errorKinds.join(
115
+ ', ',
116
+ )}`,
117
+ );
118
+ }
119
+ return unsafeAllowTokens as errorKindsType[];
120
+ }
121
+
122
+ export function withDefaults(parsedArgs: minimist.ParsedArgs): Required<ValidateUpgradeSafetyOptions> {
123
+ validateOptions(parsedArgs);
124
+
125
+ const allOpts: Required<ValidateUpgradeSafetyOptions> = {
126
+ unsafeAllowRenames: parsedArgs['unsafeAllowRenames'],
127
+ unsafeSkipStorageCheck: parsedArgs['unsafeSkipStorageCheck'],
128
+ unsafeAllowCustomTypes: parsedArgs['unsafeAllowCustomTypes'],
129
+ unsafeAllowLinkedLibraries: parsedArgs['unsafeAllowLinkedLibraries'],
130
+ unsafeAllow: getUnsafeAllowKinds(parsedArgs['unsafeAllow']),
131
+ };
132
+
133
+ return withValidationDefaults(allOpts);
134
+ }
package/src/index.ts CHANGED
@@ -55,3 +55,5 @@ export {
55
55
  PrepareUpgradeRequiresKindError,
56
56
  assertNotProxy,
57
57
  } from './usage-error';
58
+
59
+ export { ValidateUpgradeSafetyOptions, validateUpgradeSafety, ProjectReport, ReferenceContractNotFound } from './cli';
@@ -104,7 +104,8 @@ test.serial('rename manifest', async t => {
104
104
  await deleteManifests(t, id);
105
105
  });
106
106
 
107
- test.serial('rename hardhat from unknown to dev manifest', async t => {
107
+ // This test is not working on some Linux configurations ('EXDEV: cross-device link not permitted')
108
+ test.skip('rename hardhat from unknown to dev manifest', async t => {
108
109
  const id = 31337;
109
110
 
110
111
  await deleteFile(t, `.openzeppelin/unknown-${id}.json`);
@@ -0,0 +1,47 @@
1
+ import { execall } from './execall';
2
+ import { Node } from 'solidity-ast/node';
3
+
4
+ /**
5
+ * Get args from the doc string matching the given tag.
6
+ *
7
+ * @param doc The doc string to parse
8
+ * @param tag The tag to match
9
+ * @param supportedArgs The list of supported args, or undefined if all args are supported
10
+ */
11
+ export function getAnnotationArgs(doc: string, tag: string, supportedArgs?: readonly string[]) {
12
+ const result: string[] = [];
13
+ for (const { groups } of execall(
14
+ /^\s*(?:@(?<title>\w+)(?::(?<tag>[a-z][a-z-]*))? )?(?<args>(?:(?!^\s*@\w+)[^])*)/m,
15
+ doc,
16
+ )) {
17
+ if (groups && groups.title === 'custom' && groups.tag === tag) {
18
+ const trimmedArgs = groups.args.trim();
19
+ if (trimmedArgs.length > 0) {
20
+ result.push(...trimmedArgs.split(/\s+/));
21
+ }
22
+ }
23
+ }
24
+
25
+ if (supportedArgs !== undefined) {
26
+ result.forEach(arg => {
27
+ if (!supportedArgs.includes(arg)) {
28
+ throw new Error(`NatSpec: ${tag} argument not recognized: ${arg}`);
29
+ }
30
+ });
31
+ }
32
+
33
+ return result;
34
+ }
35
+
36
+ /**
37
+ * Get the documentation string for the given node.
38
+ * @param node The node
39
+ * @returns The documentation string, or an empty string if the node has no documentation
40
+ */
41
+ export function getDocumentation(node: Node) {
42
+ if ('documentation' in node) {
43
+ return typeof node.documentation === 'string' ? node.documentation : node.documentation?.text ?? '';
44
+ } else {
45
+ return '';
46
+ }
47
+ }
@@ -0,0 +1,3 @@
1
+ export function getFullyQualifiedName(source: string, contractName: string) {
2
+ return `${source}:${contractName}`;
3
+ }
@@ -12,6 +12,7 @@ export {
12
12
  getErrors,
13
13
  isUpgradeSafe,
14
14
  inferProxyKind,
15
+ inferInitializable,
15
16
  } from './query';
16
17
  export { UpgradeableContractErrorReport } from './report';
17
18
 
@@ -1,5 +1,5 @@
1
1
  import { Version, getVersion } from '../version';
2
- import { ValidationRunData, ValidationError, isOpcodeError } from './run';
2
+ import { ValidationRunData, ValidationError, isOpcodeError, ContractValidation } from './run';
3
3
  import { StorageLayout } from '../storage/layout';
4
4
  import { unlinkBytecode } from '../link-refs';
5
5
  import { ValidationOptions, processExceptions } from './overrides';
@@ -186,13 +186,26 @@ export function isUpgradeSafe(data: ValidationData, version: Version): boolean {
186
186
  return getErrors(dataV3, version).length == 0;
187
187
  }
188
188
 
189
+ export function inferUUPS(runValidation: ValidationRunData, fullContractName: string): boolean {
190
+ const methods = getAllMethods(runValidation, fullContractName);
191
+ return methods.includes(upgradeToSignature);
192
+ }
193
+
189
194
  export function inferProxyKind(data: ValidationData, version: Version): ProxyDeployment['kind'] {
190
195
  const dataV3 = normalizeValidationData(data);
191
196
  const [fullContractName, runValidation] = getContractNameAndRunValidation(dataV3, version);
192
- const methods = getAllMethods(runValidation, fullContractName);
193
- if (methods.includes(upgradeToSignature)) {
197
+ if (inferUUPS(runValidation, fullContractName)) {
194
198
  return 'uups';
195
199
  } else {
196
200
  return 'transparent';
197
201
  }
198
202
  }
203
+
204
+ /**
205
+ * Whether the contract inherits any contract named "Initializable"
206
+ * @param contractValidation The validation result for the contract
207
+ * @return true if the contract inheritss any contract whose fully qualified name ends with ":Initializable"
208
+ */
209
+ export function inferInitializable(contractValidation: ContractValidation) {
210
+ return contractValidation.inherit.some(c => c.endsWith(':Initializable'));
211
+ }
@@ -11,6 +11,8 @@ import { Version, getVersion } from '../version';
11
11
  import { extractLinkReferences, LinkReference } from '../link-refs';
12
12
  import { extractStorageLayout } from '../storage/extract';
13
13
  import { StorageLayout } from '../storage/layout';
14
+ import { getFullyQualifiedName } from '../utils/contract-name';
15
+ import { getAnnotationArgs as getSupportedAnnotationArgs, getDocumentation } from '../utils/annotations';
14
16
 
15
17
  export type ValidationRunData = Record<string, ContractValidation>;
16
18
 
@@ -26,7 +28,7 @@ export interface ContractValidation {
26
28
  solcVersion?: string;
27
29
  }
28
30
 
29
- const errorKinds = [
31
+ export const errorKinds = [
30
32
  'state-variable-assignment',
31
33
  'state-variable-immutable',
32
34
  'external-library-linking',
@@ -92,52 +94,18 @@ interface ValidationErrorUpgradeability extends ValidationErrorBase {
92
94
  kind: 'missing-public-upgradeto';
93
95
  }
94
96
 
95
- function* execall(re: RegExp, text: string) {
96
- re = new RegExp(re, re.flags + (re.sticky ? '' : 'y'));
97
- while (true) {
98
- const match = re.exec(text);
99
- if (match && match[0] !== '') {
100
- yield match;
101
- } else {
102
- break;
103
- }
104
- }
105
- }
106
-
107
97
  function getAllowed(node: Node, reachable: boolean): string[] {
108
98
  if ('documentation' in node) {
109
99
  const tag = `oz-upgrades-unsafe-allow${reachable ? '-reachable' : ''}`;
110
- const doc = typeof node.documentation === 'string' ? node.documentation : node.documentation?.text ?? '';
100
+ const doc = getDocumentation(node);
111
101
  return getAnnotationArgs(doc, tag);
112
102
  } else {
113
103
  return [];
114
104
  }
115
105
  }
116
106
 
117
- /**
118
- * Get args from the doc string matching the given tag
119
- */
120
107
  export function getAnnotationArgs(doc: string, tag: string) {
121
- const result: string[] = [];
122
- for (const { groups } of execall(
123
- /^\s*(?:@(?<title>\w+)(?::(?<tag>[a-z][a-z-]*))? )?(?<args>(?:(?!^\s*@\w+)[^])*)/m,
124
- doc,
125
- )) {
126
- if (groups && groups.title === 'custom' && groups.tag === tag) {
127
- const trimmedArgs = groups.args.trim();
128
- if (trimmedArgs.length > 0) {
129
- result.push(...trimmedArgs.split(/\s+/));
130
- }
131
- }
132
- }
133
-
134
- result.forEach(arg => {
135
- if (!(errorKinds as readonly string[]).includes(arg)) {
136
- throw new Error(`NatSpec: ${tag} argument not recognized: ${arg}`);
137
- }
138
- });
139
-
140
- return result;
108
+ return getSupportedAnnotationArgs(doc, tag, errorKinds);
141
109
  }
142
110
 
143
111
  function skipCheckReachable(error: string, node: Node): boolean {
@@ -149,10 +117,6 @@ function skipCheck(error: string, node: Node): boolean {
149
117
  return getAllowed(node, false).includes(error) || getAllowed(node, true).includes(error);
150
118
  }
151
119
 
152
- function getFullyQualifiedName(source: string, contractName: string) {
153
- return `${source}:${contractName}`;
154
- }
155
-
156
120
  export function validate(solcOutput: SolcOutput, decodeSrc: SrcDecoder, solcVersion?: string): ValidationRunData {
157
121
  const validation: ValidationRunData = {};
158
122
  const fromId: Record<number, string> = {};