@openzeppelin/upgrades-core 1.29.0 → 1.30.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.
Files changed (161) hide show
  1. package/dist/call-optional-signature.d.ts +3 -0
  2. package/dist/call-optional-signature.d.ts.map +1 -0
  3. package/dist/call-optional-signature.js +24 -0
  4. package/dist/call-optional-signature.js.map +1 -0
  5. package/dist/cli/validate/build-info-file.d.ts +4 -0
  6. package/dist/cli/validate/build-info-file.d.ts.map +1 -1
  7. package/dist/cli/validate/build-info-file.js +5 -2
  8. package/dist/cli/validate/build-info-file.js.map +1 -1
  9. package/dist/cli/validate/build-info-file.test.js +4 -1
  10. package/dist/cli/validate/build-info-file.test.js.map +1 -1
  11. package/dist/cli/validate/upgradeability-assessment.js +2 -6
  12. package/dist/cli/validate/upgradeability-assessment.js.map +1 -1
  13. package/dist/cli/validate/validations.d.ts.map +1 -1
  14. package/dist/cli/validate/validations.js +5 -4
  15. package/dist/cli/validate/validations.js.map +1 -1
  16. package/dist/impl-address.d.ts.map +1 -1
  17. package/dist/impl-address.js +9 -16
  18. package/dist/impl-address.js.map +1 -1
  19. package/dist/index.d.ts +3 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +7 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/levenshtein.d.ts +6 -0
  24. package/dist/levenshtein.d.ts.map +1 -1
  25. package/dist/levenshtein.js.map +1 -1
  26. package/dist/solc-api.d.ts +14 -0
  27. package/dist/solc-api.d.ts.map +1 -1
  28. package/dist/standalone.d.ts +4 -1
  29. package/dist/standalone.d.ts.map +1 -1
  30. package/dist/standalone.js +6 -3
  31. package/dist/standalone.js.map +1 -1
  32. package/dist/standalone.test.js +17 -0
  33. package/dist/standalone.test.js.map +1 -1
  34. package/dist/storage/compare.d.ts +2 -1
  35. package/dist/storage/compare.d.ts.map +1 -1
  36. package/dist/storage/compare.js +25 -2
  37. package/dist/storage/compare.js.map +1 -1
  38. package/dist/storage/extract.d.ts +15 -3
  39. package/dist/storage/extract.d.ts.map +1 -1
  40. package/dist/storage/extract.js +48 -21
  41. package/dist/storage/extract.js.map +1 -1
  42. package/dist/storage/index.d.ts.map +1 -1
  43. package/dist/storage/index.js +15 -1
  44. package/dist/storage/index.js.map +1 -1
  45. package/dist/storage/layout.d.ts +1 -0
  46. package/dist/storage/layout.d.ts.map +1 -1
  47. package/dist/storage/layout.js.map +1 -1
  48. package/dist/storage/namespace.d.ts +35 -0
  49. package/dist/storage/namespace.d.ts.map +1 -0
  50. package/dist/storage/namespace.js +219 -0
  51. package/dist/storage/namespace.js.map +1 -0
  52. package/dist/storage/report.js +6 -0
  53. package/dist/storage/report.js.map +1 -1
  54. package/dist/storage-namespaced-conflicts-layout.test.d.ts +2 -0
  55. package/dist/storage-namespaced-conflicts-layout.test.d.ts.map +1 -0
  56. package/dist/storage-namespaced-conflicts-layout.test.js +69 -0
  57. package/dist/storage-namespaced-conflicts-layout.test.js.map +1 -0
  58. package/dist/storage-namespaced-conflicts.test.d.ts +2 -0
  59. package/dist/storage-namespaced-conflicts.test.d.ts.map +1 -0
  60. package/dist/storage-namespaced-conflicts.test.js +45 -0
  61. package/dist/storage-namespaced-conflicts.test.js.map +1 -0
  62. package/dist/storage-namespaced-layout.test.d.ts +2 -0
  63. package/dist/storage-namespaced-layout.test.d.ts.map +1 -0
  64. package/dist/storage-namespaced-layout.test.js +59 -0
  65. package/dist/storage-namespaced-layout.test.js.map +1 -0
  66. package/dist/storage-namespaced-outside-contract.test.d.ts +2 -0
  67. package/dist/storage-namespaced-outside-contract.test.d.ts.map +1 -0
  68. package/dist/storage-namespaced-outside-contract.test.js +22 -0
  69. package/dist/storage-namespaced-outside-contract.test.js.map +1 -0
  70. package/dist/storage-namespaced-solc-versions.test.d.ts +2 -0
  71. package/dist/storage-namespaced-solc-versions.test.d.ts.map +1 -0
  72. package/dist/storage-namespaced-solc-versions.test.js +42 -0
  73. package/dist/storage-namespaced-solc-versions.test.js.map +1 -0
  74. package/dist/storage-namespaced-udvt.test.d.ts +2 -0
  75. package/dist/storage-namespaced-udvt.test.d.ts.map +1 -0
  76. package/dist/storage-namespaced-udvt.test.js +130 -0
  77. package/dist/storage-namespaced-udvt.test.js.map +1 -0
  78. package/dist/storage-namespaced.test.d.ts +2 -0
  79. package/dist/storage-namespaced.test.d.ts.map +1 -0
  80. package/dist/storage-namespaced.test.js +132 -0
  81. package/dist/storage-namespaced.test.js.map +1 -0
  82. package/dist/upgrade-interface-version.d.ts +3 -0
  83. package/dist/upgrade-interface-version.d.ts.map +1 -0
  84. package/dist/upgrade-interface-version.js +25 -0
  85. package/dist/upgrade-interface-version.js.map +1 -0
  86. package/dist/upgrade-interface-version.test.d.ts +2 -0
  87. package/dist/upgrade-interface-version.test.d.ts.map +1 -0
  88. package/dist/upgrade-interface-version.test.js +32 -0
  89. package/dist/upgrade-interface-version.test.js.map +1 -0
  90. package/dist/utils/annotations.d.ts +4 -0
  91. package/dist/utils/annotations.d.ts.map +1 -1
  92. package/dist/utils/annotations.js +9 -1
  93. package/dist/utils/annotations.js.map +1 -1
  94. package/dist/utils/make-namespaced.d.ts +20 -0
  95. package/dist/utils/make-namespaced.d.ts.map +1 -0
  96. package/dist/utils/make-namespaced.js +131 -0
  97. package/dist/utils/make-namespaced.js.map +1 -0
  98. package/dist/utils/make-namespaced.test.d.ts +2 -0
  99. package/dist/utils/make-namespaced.test.d.ts.map +1 -0
  100. package/dist/utils/make-namespaced.test.js +59 -0
  101. package/dist/utils/make-namespaced.test.js.map +1 -0
  102. package/dist/utils/stabilize-layout.d.ts +11 -0
  103. package/dist/utils/stabilize-layout.d.ts.map +1 -1
  104. package/dist/utils/stabilize-layout.js +5 -0
  105. package/dist/utils/stabilize-layout.js.map +1 -1
  106. package/dist/validate/query.d.ts.map +1 -1
  107. package/dist/validate/query.js +4 -2
  108. package/dist/validate/query.js.map +1 -1
  109. package/dist/validate/run.d.ts +18 -2
  110. package/dist/validate/run.d.ts.map +1 -1
  111. package/dist/validate/run.js +80 -2
  112. package/dist/validate/run.js.map +1 -1
  113. package/package.json +3 -3
  114. package/src/call-optional-signature.ts +20 -0
  115. package/src/cli/validate/build-info-file.test.ts +4 -1
  116. package/src/cli/validate/build-info-file.ts +12 -2
  117. package/src/cli/validate/upgradeability-assessment.ts +1 -6
  118. package/src/cli/validate/validations.ts +6 -5
  119. package/src/impl-address.ts +9 -20
  120. package/src/index.ts +4 -0
  121. package/src/levenshtein.ts +7 -0
  122. package/src/solc-api.ts +14 -0
  123. package/src/standalone.test.ts +26 -0
  124. package/src/standalone.ts +6 -2
  125. package/src/storage/compare.ts +34 -2
  126. package/src/storage/extract.ts +68 -20
  127. package/src/storage/index.ts +22 -1
  128. package/src/storage/layout.ts +1 -0
  129. package/src/storage/namespace.ts +267 -0
  130. package/src/storage/report.ts +8 -0
  131. package/src/storage-0.8.test.ts.md +4 -0
  132. package/src/storage-0.8.test.ts.snap +0 -0
  133. package/src/storage-memory-0.5.test.ts.md +2 -0
  134. package/src/storage-memory-0.5.test.ts.snap +0 -0
  135. package/src/storage-namespaced-conflicts-layout.test.ts +96 -0
  136. package/src/storage-namespaced-conflicts-layout.test.ts.md +53 -0
  137. package/src/storage-namespaced-conflicts-layout.test.ts.snap +0 -0
  138. package/src/storage-namespaced-conflicts.test.ts +54 -0
  139. package/src/storage-namespaced-conflicts.test.ts.md +53 -0
  140. package/src/storage-namespaced-conflicts.test.ts.snap +0 -0
  141. package/src/storage-namespaced-layout.test.ts +77 -0
  142. package/src/storage-namespaced-layout.test.ts.md +67 -0
  143. package/src/storage-namespaced-layout.test.ts.snap +0 -0
  144. package/src/storage-namespaced-outside-contract.test.ts +24 -0
  145. package/src/storage-namespaced-solc-versions.test.ts +59 -0
  146. package/src/storage-namespaced-udvt.test.ts +160 -0
  147. package/src/storage-namespaced.test.ts +146 -0
  148. package/src/storage-namespaced.test.ts.md +89 -0
  149. package/src/storage-namespaced.test.ts.snap +0 -0
  150. package/src/storage.test.ts.md +2 -0
  151. package/src/storage.test.ts.snap +0 -0
  152. package/src/upgrade-interface-version.test.ts +37 -0
  153. package/src/upgrade-interface-version.ts +27 -0
  154. package/src/utils/annotations.ts +8 -0
  155. package/src/utils/make-namespaced.test.ts +74 -0
  156. package/src/utils/make-namespaced.test.ts.md +154 -0
  157. package/src/utils/make-namespaced.test.ts.snap +0 -0
  158. package/src/utils/make-namespaced.ts +154 -0
  159. package/src/utils/stabilize-layout.ts +5 -0
  160. package/src/validate/query.ts +4 -2
  161. package/src/validate/run.ts +108 -2
@@ -0,0 +1,154 @@
1
+ import { isNodeType } from 'solidity-ast/utils';
2
+ import { Node } from 'solidity-ast/node';
3
+ import { SolcInput, SolcOutput } from '../solc-api';
4
+ import { getStorageLocationAnnotation } from '../storage/namespace';
5
+ import { assert } from './assert';
6
+
7
+ const OUTPUT_SELECTION = {
8
+ '*': {
9
+ '*': ['storageLayout'],
10
+ '': ['ast'],
11
+ },
12
+ };
13
+
14
+ /**
15
+ * Makes a modified version of the solc input to add state variables in each contract for namespaced struct definitions,
16
+ * so that the compiler will generate their types in the storage layout.
17
+ *
18
+ * This makes the following modifications to the input:
19
+ * - Adds a state variable for each namespaced struct definition
20
+ * - Deletes all contracts' functions since they are not needed for storage layout
21
+ * - Deletes all contracts' modifiers, variables, and parent constructor invocations to avoid compilation errors due to deleted functions and constructors
22
+ * - Deletes all using for directives (at file level and in contracts) since they may reference deleted functions
23
+ * - Converts all free functions and constants (at file level) to dummy variables (do not delete them since they might be imported by other files)
24
+ *
25
+ * Also sets the outputSelection to only include storageLayout and ast, since the other outputs are not needed.
26
+ *
27
+ * @param input The original solc input.
28
+ * @param output The original solc output.
29
+ * @returns The modified solc input with storage layout that includes namespaced type information.
30
+ */
31
+ export function makeNamespacedInput(input: SolcInput, output: SolcOutput): SolcInput {
32
+ const modifiedSources: Record<string, { content?: string }> = {};
33
+
34
+ for (const [sourcePath] of Object.entries(input.sources)) {
35
+ const source = input.sources[sourcePath];
36
+
37
+ if (source.content === undefined) {
38
+ modifiedSources[sourcePath] = source;
39
+ continue;
40
+ }
41
+
42
+ const orig = Buffer.from(source.content, 'utf8');
43
+
44
+ const modifications: Modification[] = [];
45
+
46
+ for (const node of output.sources[sourcePath].ast.nodes) {
47
+ if (isNodeType('ContractDefinition', node)) {
48
+ const contractDef = node;
49
+
50
+ // Remove any calls to parent constructors from the inheritance list
51
+ const inherits = contractDef.baseContracts;
52
+ for (const inherit of inherits) {
53
+ if (Array.isArray(inherit.arguments)) {
54
+ assert(inherit.baseName.name !== undefined);
55
+ modifications.push(makeReplace(inherit, orig, inherit.baseName.name));
56
+ }
57
+ }
58
+
59
+ const contractNodes = contractDef.nodes;
60
+ for (const contractNode of contractNodes) {
61
+ if (
62
+ isNodeType('FunctionDefinition', contractNode) ||
63
+ isNodeType('ModifierDefinition', contractNode) ||
64
+ isNodeType('VariableDeclaration', contractNode)
65
+ ) {
66
+ if (contractNode.documentation) {
67
+ modifications.push(makeDelete(contractNode.documentation, orig));
68
+ }
69
+ modifications.push(makeDelete(contractNode, orig));
70
+ } else if (isNodeType('UsingForDirective', contractNode)) {
71
+ modifications.push(makeDelete(contractNode, orig));
72
+ } else if (isNodeType('StructDefinition', contractNode)) {
73
+ const storageLocation = getStorageLocationAnnotation(contractNode);
74
+ if (storageLocation !== undefined) {
75
+ const structName = contractNode.name;
76
+ const variableName = `$${structName}_${(Math.random() * 1e6).toFixed(0)}`;
77
+ const insertText = ` ${structName} ${variableName};`;
78
+
79
+ modifications.push(makeInsertAfter(contractNode, insertText));
80
+ }
81
+ }
82
+ }
83
+ } else if (isNodeType('FunctionDefinition', node) || isNodeType('VariableDeclaration', node)) {
84
+ if (node.documentation) {
85
+ modifications.push(makeDelete(node.documentation, orig));
86
+ }
87
+ // Replace with a dummy variable of arbitrary type
88
+ const name = node.name;
89
+ const insertText = `uint256 constant ${name} = 0;`;
90
+ modifications.push(makeReplace(node, orig, insertText));
91
+ } else if (isNodeType('UsingForDirective', node)) {
92
+ modifications.push(makeDelete(node, orig));
93
+ }
94
+ }
95
+
96
+ modifiedSources[sourcePath] = { ...source, content: getModifiedSource(orig, modifications) };
97
+ }
98
+
99
+ return { ...input, sources: modifiedSources, settings: { ...input.settings, outputSelection: OUTPUT_SELECTION } };
100
+ }
101
+
102
+ interface Modification {
103
+ start: number;
104
+ end: number;
105
+ text?: string;
106
+ }
107
+
108
+ function getPositions(node: Node) {
109
+ const [start, length] = node.src.split(':').map(Number);
110
+ const end = start + length;
111
+ return { start, end };
112
+ }
113
+
114
+ function makeReplace(node: Node, orig: Buffer, text: string): Modification {
115
+ // Replace is a delete and insert
116
+ const { start, end } = makeDelete(node, orig);
117
+ return { start, end, text };
118
+ }
119
+
120
+ function makeInsertAfter(node: Node, text: string): Modification {
121
+ const { end } = getPositions(node);
122
+ return { start: end, end, text };
123
+ }
124
+
125
+ function makeDelete(node: Node, orig: Buffer): Modification {
126
+ const positions = getPositions(node);
127
+ let end = positions.end;
128
+ // If the next character is a semicolon for variables, skip over it
129
+ if (isNodeType('VariableDeclaration', node) && end + 1 < orig.length && orig.toString('utf8', end, end + 1) === ';') {
130
+ end += 1;
131
+ }
132
+ return { start: positions.start, end };
133
+ }
134
+
135
+ function getModifiedSource(orig: Buffer, modifications: Modification[]): string {
136
+ let result = '';
137
+ let copyFromIndex = 0;
138
+
139
+ for (const modification of modifications) {
140
+ assert(modification.start >= copyFromIndex);
141
+ result += orig.toString('utf8', copyFromIndex, modification.start);
142
+
143
+ if (modification.text !== undefined) {
144
+ result += modification.text;
145
+ }
146
+
147
+ copyFromIndex = modification.end;
148
+ }
149
+
150
+ assert(copyFromIndex <= orig.length);
151
+ result += orig.toString('utf8', copyFromIndex);
152
+
153
+ return result;
154
+ }
@@ -13,5 +13,10 @@ export function stabilizeStorageLayout(layout: StorageLayout) {
13
13
  : item.members.map(m => ({ ...m, type: stabilizeTypeIdentifier(m.type) })));
14
14
  return [stabilizeTypeIdentifier(type), { ...item, members }];
15
15
  }),
16
+ namespaces: layout.namespaces
17
+ ? Object.entries(layout.namespaces).map(([ns, items]) => {
18
+ return [ns, items.map(item => ({ ...item, type: stabilizeTypeIdentifier(item.type) }))];
19
+ })
20
+ : undefined,
16
21
  };
17
22
  }
@@ -93,9 +93,11 @@ export function unfoldStorageLayout(runData: ValidationRunData, fullContractName
93
93
  solcVersion,
94
94
  storage: c.layout.storage,
95
95
  types: c.layout.types,
96
+ namespaces: c.layout.namespaces,
96
97
  };
97
98
  } else {
98
- const layout: StorageLayout = { solcVersion, storage: [], types: {} };
99
+ // Namespaces are pre-flattened
100
+ const layout: StorageLayout = { solcVersion, storage: [], types: {}, namespaces: c.layout.namespaces };
99
101
  for (const name of [fullContractName].concat(c.inherit)) {
100
102
  layout.storage.unshift(...runData[name].layout.storage);
101
103
  Object.assign(layout.types, runData[name].layout.types);
@@ -192,7 +194,7 @@ export function isUpgradeSafe(data: ValidationData, version: Version): boolean {
192
194
 
193
195
  export function inferUUPS(runValidation: ValidationRunData, fullContractName: string): boolean {
194
196
  const methods = getAllMethods(runValidation, fullContractName);
195
- return methods.includes(upgradeToSignature);
197
+ return methods.includes(upgradeToSignature) || methods.includes(upgradeToAndCallSignature);
196
198
  }
197
199
 
198
200
  export function inferProxyKind(data: ValidationData, version: Version): ProxyDeployment['kind'] {
@@ -1,8 +1,9 @@
1
1
  import { Node } from 'solidity-ast/node';
2
2
  import { isNodeType, findAll, ASTDereferencer, astDereferencer } from 'solidity-ast/utils';
3
3
  import type { ContractDefinition, FunctionDefinition } from 'solidity-ast';
4
+ import debug from '../utils/debug';
4
5
 
5
- import { SolcOutput, SolcBytecode } from '../solc-api';
6
+ import { SolcOutput, SolcBytecode, SolcInput } from '../solc-api';
6
7
  import { SrcDecoder } from '../src-decoder';
7
8
  import { isNullish } from '../utils/is-nullish';
8
9
  import { getFunctionSignature } from '../utils/function';
@@ -12,6 +13,8 @@ import { extractStorageLayout } from '../storage/extract';
12
13
  import { StorageLayout } from '../storage/layout';
13
14
  import { getFullyQualifiedName } from '../utils/contract-name';
14
15
  import { getAnnotationArgs as getSupportedAnnotationArgs, getDocumentation } from '../utils/annotations';
16
+ import { getStorageLocationAnnotation, isNamespaceSupported } from '../storage/namespace';
17
+ import { UpgradesError } from '../error';
15
18
 
16
19
  export type ValidationRunData = Record<string, ContractValidation>;
17
20
 
@@ -116,7 +119,29 @@ function skipCheck(error: string, node: Node): boolean {
116
119
  return getAllowed(node, false).includes(error) || getAllowed(node, true).includes(error);
117
120
  }
118
121
 
119
- export function validate(solcOutput: SolcOutput, decodeSrc: SrcDecoder, solcVersion?: string): ValidationRunData {
122
+ /**
123
+ * Runs validations on the given solc output.
124
+ *
125
+ * If `namespacedOutput` is provided, it is used to extract storage layout information for namespaced types.
126
+ * It must be from a compilation with the same sources as `solcInput` and `solcOutput`, but with storage variables
127
+ * injected for each namespaced struct so that the types are available in the storage layout. This can be obtained by
128
+ * calling the `makeNamespacedInput` function from this package to create modified solc input, then compiling
129
+ * that modified solc input to get the namespaced output.
130
+ *
131
+ * @param solcOutput Solc output to validate
132
+ * @param decodeSrc Source decoder for the original source code
133
+ * @param solcVersion The version of solc used to compile the contracts
134
+ * @param solcInput Solc input that the compiler was invoked with
135
+ * @param namespacedOutput Namespaced solc output to extract storage layout information for namespaced types
136
+ * @returns A record of validation results for each fully qualified contract name
137
+ */
138
+ export function validate(
139
+ solcOutput: SolcOutput,
140
+ decodeSrc: SrcDecoder,
141
+ solcVersion?: string,
142
+ solcInput?: SolcInput,
143
+ namespacedOutput?: SolcOutput,
144
+ ): ValidationRunData {
120
145
  const validation: ValidationRunData = {};
121
146
  const fromId: Record<number, string> = {};
122
147
  const inheritIds: Record<string, number[]> = {};
@@ -128,6 +153,9 @@ export function validate(solcOutput: SolcOutput, decodeSrc: SrcDecoder, solcVers
128
153
  const selfDestructCache = initOpcodeCache();
129
154
 
130
155
  for (const source in solcOutput.contracts) {
156
+ checkNamespaceSolidityVersion(source, solcVersion, solcInput);
157
+ checkNamespacesOutsideContract(source, solcOutput, decodeSrc);
158
+
131
159
  for (const contractName in solcOutput.contracts[source]) {
132
160
  const bytecode = solcOutput.contracts[source][contractName].evm.bytecode;
133
161
  const version = bytecode.object === '' ? undefined : getVersion(bytecode.object);
@@ -176,7 +204,9 @@ export function validate(solcOutput: SolcOutput, decodeSrc: SrcDecoder, solcVers
176
204
  decodeSrc,
177
205
  deref,
178
206
  solcOutput.contracts[source][contractDef.name].storageLayout,
207
+ getNamespacedCompilationContext(source, contractDef, namespacedOutput),
179
208
  );
209
+
180
210
  validation[key].methods = [...findAll('FunctionDefinition', contractDef)]
181
211
  .filter(fnDef => ['external', 'public'].includes(fnDef.visibility))
182
212
  .map(fnDef => getFunctionSignature(fnDef, deref));
@@ -195,6 +225,82 @@ export function validate(solcOutput: SolcOutput, decodeSrc: SrcDecoder, solcVers
195
225
  return validation;
196
226
  }
197
227
 
228
+ function checkNamespaceSolidityVersion(source: string, solcVersion?: string, solcInput?: SolcInput) {
229
+ if (solcInput === undefined) {
230
+ // This should only be missing if using an old version of the Hardhat or Truffle plugin.
231
+ // Even without this param, namespace layout checks would still occur if compiled with solc version >= 0.8.20
232
+ debug('Cannot check Solidity version for namespaces because solcInput is undefined');
233
+ } else {
234
+ // Solc versions older than 0.8.20 do not have documentation for structs.
235
+ // Use a regex to check for strings that look like namespace annotations, and if found, check that the compiler version is >= 0.8.20
236
+ const content = solcInput.sources[source].content;
237
+ const hasMatch = content !== undefined && content.match(/@custom:storage-location/);
238
+ if (hasMatch) {
239
+ if (solcVersion === undefined) {
240
+ throw new UpgradesError(
241
+ `${source}: Namespace annotations require Solidity version >= 0.8.20, but no solcVersion parameter was provided`,
242
+ () =>
243
+ `Structs with the @custom:storage-location annotation can only be used with Solidity version 0.8.20 or higher. Pass the solcVersion parameter to the validate function, or remove the annotation if the struct is not used for namespaced storage.`,
244
+ );
245
+ } else if (!isNamespaceSupported(solcVersion)) {
246
+ throw new UpgradesError(
247
+ `${source}: Namespace annotations require Solidity version >= 0.8.20, but ${solcVersion} was used`,
248
+ () =>
249
+ `Structs with the @custom:storage-location annotation can only be used with Solidity version 0.8.20 or higher. Use a newer version of Solidity, or remove the annotation if the struct is not used for namespaced storage.`,
250
+ );
251
+ }
252
+ }
253
+ }
254
+ }
255
+
256
+ function checkNamespacesOutsideContract(source: string, solcOutput: SolcOutput, decodeSrc: SrcDecoder) {
257
+ for (const node of solcOutput.sources[source].ast.nodes) {
258
+ if (isNodeType('StructDefinition', node)) {
259
+ const storageLocation = getStorageLocationAnnotation(node);
260
+ if (storageLocation !== undefined) {
261
+ throw new UpgradesError(
262
+ `${decodeSrc(node)}: Namespace struct ${node.name} is defined outside of a contract`,
263
+ () =>
264
+ `Structs with the @custom:storage-location annotation must be defined within a contract. Move the struct definition into a contract, or remove the annotation if the struct is not used for namespaced storage.`,
265
+ );
266
+ }
267
+ }
268
+ }
269
+ }
270
+
271
+ function getNamespacedCompilationContext(
272
+ source: string,
273
+ contractDef: ContractDefinition,
274
+ namespacedOutput?: SolcOutput,
275
+ ) {
276
+ if (namespacedOutput === undefined || contractDef.canonicalName === undefined) {
277
+ return undefined;
278
+ }
279
+
280
+ if (namespacedOutput.sources[source] === undefined) {
281
+ throw new Error(`Source ${source} not found in namespaced solc output`);
282
+ }
283
+
284
+ const namespacedContractDef = namespacedOutput.sources[source].ast.nodes
285
+ .filter(isNodeType('ContractDefinition'))
286
+ .find(c => c.canonicalName === contractDef.canonicalName);
287
+
288
+ if (namespacedContractDef === undefined) {
289
+ throw new Error(`Contract definition with name ${contractDef.canonicalName} not found in namespaced solc output`);
290
+ }
291
+
292
+ const storageLayout = namespacedOutput.contracts[source][contractDef.name].storageLayout;
293
+ if (storageLayout === undefined) {
294
+ throw new Error(`Storage layout for contract ${contractDef.canonicalName} not found in namespaced solc output`);
295
+ }
296
+
297
+ return {
298
+ contractDef: namespacedContractDef,
299
+ deref: astDereferencer(namespacedOutput),
300
+ storageLayout: storageLayout,
301
+ };
302
+ }
303
+
198
304
  function* getConstructorErrors(contractDef: ContractDefinition, decodeSrc: SrcDecoder): Generator<ValidationError> {
199
305
  for (const fnDef of findAll('FunctionDefinition', contractDef, node => skipCheck('constructor', node))) {
200
306
  if (fnDef.kind === 'constructor' && ((fnDef.body?.statements?.length ?? 0) > 0 || fnDef.modifiers.length > 0)) {