@openzeppelin/upgrades-core 1.28.0 → 1.30.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 (178) 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 +2 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +5 -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 +34 -0
  49. package/dist/storage/namespace.d.ts.map +1 -0
  50. package/dist/storage/namespace.js +191 -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/overrides.js +1 -1
  107. package/dist/validate/overrides.js.map +1 -1
  108. package/dist/validate/query.d.ts.map +1 -1
  109. package/dist/validate/query.js +7 -3
  110. package/dist/validate/query.js.map +1 -1
  111. package/dist/validate/report.js +2 -2
  112. package/dist/validate/report.js.map +1 -1
  113. package/dist/validate/run.d.ts +18 -2
  114. package/dist/validate/run.d.ts.map +1 -1
  115. package/dist/validate/run.js +104 -2
  116. package/dist/validate/run.js.map +1 -1
  117. package/dist/validate.test.js +3 -0
  118. package/dist/validate.test.js.map +1 -1
  119. package/package.json +3 -3
  120. package/src/call-optional-signature.ts +20 -0
  121. package/src/cli/cli.test.ts.md +4 -4
  122. package/src/cli/cli.test.ts.snap +0 -0
  123. package/src/cli/validate/build-info-file.test.ts +4 -1
  124. package/src/cli/validate/build-info-file.ts +12 -2
  125. package/src/cli/validate/project-report.test.ts.md +4 -4
  126. package/src/cli/validate/project-report.test.ts.snap +0 -0
  127. package/src/cli/validate/upgradeability-assessment.ts +1 -6
  128. package/src/cli/validate/validate-upgrade-safety.test.ts.md +2 -2
  129. package/src/cli/validate/validate-upgrade-safety.test.ts.snap +0 -0
  130. package/src/cli/validate/validations.test.ts.md +2 -2
  131. package/src/cli/validate/validations.test.ts.snap +0 -0
  132. package/src/cli/validate/validations.ts +6 -5
  133. package/src/impl-address.ts +9 -20
  134. package/src/index.ts +3 -0
  135. package/src/levenshtein.ts +7 -0
  136. package/src/solc-api.ts +14 -0
  137. package/src/standalone.test.ts +26 -0
  138. package/src/standalone.ts +6 -2
  139. package/src/storage/compare.ts +34 -2
  140. package/src/storage/extract.ts +68 -20
  141. package/src/storage/index.ts +22 -1
  142. package/src/storage/layout.ts +1 -0
  143. package/src/storage/namespace.ts +262 -0
  144. package/src/storage/report.ts +8 -0
  145. package/src/storage-0.8.test.ts.md +4 -0
  146. package/src/storage-0.8.test.ts.snap +0 -0
  147. package/src/storage-memory-0.5.test.ts.md +2 -0
  148. package/src/storage-memory-0.5.test.ts.snap +0 -0
  149. package/src/storage-namespaced-conflicts-layout.test.ts +96 -0
  150. package/src/storage-namespaced-conflicts-layout.test.ts.md +53 -0
  151. package/src/storage-namespaced-conflicts-layout.test.ts.snap +0 -0
  152. package/src/storage-namespaced-conflicts.test.ts +54 -0
  153. package/src/storage-namespaced-conflicts.test.ts.md +53 -0
  154. package/src/storage-namespaced-conflicts.test.ts.snap +0 -0
  155. package/src/storage-namespaced-layout.test.ts +77 -0
  156. package/src/storage-namespaced-layout.test.ts.md +67 -0
  157. package/src/storage-namespaced-layout.test.ts.snap +0 -0
  158. package/src/storage-namespaced-outside-contract.test.ts +24 -0
  159. package/src/storage-namespaced-solc-versions.test.ts +59 -0
  160. package/src/storage-namespaced-udvt.test.ts +160 -0
  161. package/src/storage-namespaced.test.ts +146 -0
  162. package/src/storage-namespaced.test.ts.md +89 -0
  163. package/src/storage-namespaced.test.ts.snap +0 -0
  164. package/src/storage.test.ts.md +2 -0
  165. package/src/storage.test.ts.snap +0 -0
  166. package/src/upgrade-interface-version.test.ts +37 -0
  167. package/src/upgrade-interface-version.ts +27 -0
  168. package/src/utils/annotations.ts +8 -0
  169. package/src/utils/make-namespaced.test.ts +74 -0
  170. package/src/utils/make-namespaced.test.ts.md +154 -0
  171. package/src/utils/make-namespaced.test.ts.snap +0 -0
  172. package/src/utils/make-namespaced.ts +154 -0
  173. package/src/utils/stabilize-layout.ts +5 -0
  174. package/src/validate/overrides.ts +1 -1
  175. package/src/validate/query.ts +9 -3
  176. package/src/validate/report.ts +3 -2
  177. package/src/validate/run.ts +109 -2
  178. package/src/validate.test.ts +3 -0
@@ -0,0 +1,154 @@
1
+ # Snapshot report for `src/utils/make-namespaced.test.ts`
2
+
3
+ The actual snapshot is saved in `make-namespaced.test.ts.snap`.
4
+
5
+ Generated by [AVA](https://avajs.dev).
6
+
7
+ ## make namespaced input
8
+
9
+ > Snapshot 1
10
+
11
+ {
12
+ language: 'Solidity',
13
+ settings: {
14
+ optimizer: {
15
+ enabled: true,
16
+ runs: 200,
17
+ },
18
+ outputSelection: {
19
+ '*': {
20
+ '': [
21
+ 'ast',
22
+ ],
23
+ '*': [
24
+ 'storageLayout',
25
+ ],
26
+ },
27
+ },
28
+ },
29
+ sources: {
30
+ 'contracts/test/NamespacedToModify.sol': {
31
+ content: `// SPDX-License-Identifier: MIT␊
32
+ pragma solidity ^0.8.20;␊
33
+ ␊
34
+ contract Example {␊
35
+ /// @custom:storage-location erc7201:example.main␊
36
+ struct MainStorage {␊
37
+ uint256 x;␊
38
+ uint256 y;␊
39
+ } MainStorage $MainStorage_random;␊
40
+ ␊
41
+ ␊
42
+ ␊
43
+ ␊
44
+ ␊
45
+ ␊
46
+ ␊
47
+ ␊
48
+ ␊
49
+ struct MyStruct { uint b; }␊
50
+ ␊
51
+ // keccak256(abi.encode(uint256(keccak256("example.main")) - 1)) & ~bytes32(uint256(0xff));␊
52
+ ␊
53
+ ␊
54
+ ␊
55
+ ␊
56
+ ␊
57
+ ␊
58
+ /// @notice standlone natspec␊
59
+ ␊
60
+ ␊
61
+ ␊
62
+ ␊
63
+ // standalone doc␊
64
+ ␊
65
+ /**␊
66
+ * standlone doc block␊
67
+ */␊
68
+ ␊
69
+ ␊
70
+ ␊
71
+ ␊
72
+ ␊
73
+ ␊
74
+ }␊
75
+ ␊
76
+ contract HasFunction {␊
77
+ ␊
78
+ ␊
79
+ }␊
80
+ ␊
81
+ contract UsingFunction is HasFunction {␊
82
+ ␊
83
+ }␊
84
+ ␊
85
+ uint256 constant FreeFunctionUsingSelector = 0;␊
86
+ ␊
87
+ uint256 constant CONSTANT_USING_SELECTOR = 0;␊
88
+ ␊
89
+ library Lib {␊
90
+ ␊
91
+ ␊
92
+ ␊
93
+ }␊
94
+ ␊
95
+ contract Consumer {␊
96
+ ␊
97
+ ␊
98
+ ␊
99
+ ␊
100
+ ␊
101
+ }␊
102
+ ␊
103
+ uint256 constant plusTwo = 0;␊
104
+ ␊
105
+ ␊
106
+ ␊
107
+ contract UsingForDirectives {␊
108
+ ␊
109
+ ␊
110
+ ␊
111
+ }`,
112
+ },
113
+ },
114
+ }
115
+
116
+ ## make namespaced input - solc 0.7
117
+
118
+ > Snapshot 1
119
+
120
+ {
121
+ language: 'Solidity',
122
+ settings: {
123
+ optimizer: {
124
+ enabled: true,
125
+ runs: 200,
126
+ },
127
+ outputSelection: {
128
+ '*': {
129
+ '': [
130
+ 'ast',
131
+ ],
132
+ '*': [
133
+ 'storageLayout',
134
+ ],
135
+ },
136
+ },
137
+ },
138
+ sources: {
139
+ 'contracts/test/NamespacedToModify07.sol': {
140
+ content: `// SPDX-License-Identifier: MIT␊
141
+ pragma solidity 0.7.6;␊
142
+ ␊
143
+ contract HasFunction {␊
144
+ ␊
145
+ ␊
146
+ }␊
147
+ ␊
148
+ contract UsingFunction is HasFunction {␊
149
+ ␊
150
+ }␊
151
+ `,
152
+ },
153
+ },
154
+ }
@@ -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
  }
@@ -73,7 +73,7 @@ export const ValidationErrorUnsafeMessages: Record<ValidationError['kind'], stri
73
73
  selfdestruct: [`You are using the \`unsafeAllow.selfdestruct\` flag.`],
74
74
  'missing-public-upgradeto': [
75
75
  `You are using the \`unsafeAllow.missing-public-upgradeto\` flag with uups proxy.`,
76
- `Not having a public upgradeTo function in your implementation can break upgradeability.`,
76
+ `Not having a public upgradeTo or upgradeToAndCall function in your implementation can break upgradeability.`,
77
77
  `Some implementation might check that onchain, and cause the upgrade to revert.`,
78
78
  ],
79
79
  };
@@ -8,6 +8,7 @@ import { ValidationData, normalizeValidationData } from './data';
8
8
  import { ProxyDeployment } from '../manifest';
9
9
 
10
10
  const upgradeToSignature = 'upgradeTo(address)';
11
+ const upgradeToAndCallSignature = 'upgradeToAndCall(address,bytes)';
11
12
 
12
13
  export function assertUpgradeSafe(data: ValidationData, version: Version, opts: ValidationOptions): void {
13
14
  const dataV3 = normalizeValidationData(data);
@@ -92,9 +93,11 @@ export function unfoldStorageLayout(runData: ValidationRunData, fullContractName
92
93
  solcVersion,
93
94
  storage: c.layout.storage,
94
95
  types: c.layout.types,
96
+ namespaces: c.layout.namespaces,
95
97
  };
96
98
  } else {
97
- const layout: StorageLayout = { solcVersion, storage: [], types: {} };
99
+ // Namespaces are pre-flattened
100
+ const layout: StorageLayout = { solcVersion, storage: [], types: {}, namespaces: c.layout.namespaces };
98
101
  for (const name of [fullContractName].concat(c.inherit)) {
99
102
  layout.storage.unshift(...runData[name].layout.storage);
100
103
  Object.assign(layout.types, runData[name].layout.types);
@@ -147,7 +150,10 @@ export function getErrors(data: ValidationData, version: Version, opts: Validati
147
150
 
148
151
  const selfAndInheritedMethods = getAllMethods(runValidation, fullContractName);
149
152
 
150
- if (!selfAndInheritedMethods.includes(upgradeToSignature)) {
153
+ if (
154
+ !selfAndInheritedMethods.includes(upgradeToSignature) &&
155
+ !selfAndInheritedMethods.includes(upgradeToAndCallSignature)
156
+ ) {
151
157
  errors.push({
152
158
  src: c.src,
153
159
  kind: 'missing-public-upgradeto',
@@ -188,7 +194,7 @@ export function isUpgradeSafe(data: ValidationData, version: Version): boolean {
188
194
 
189
195
  export function inferUUPS(runValidation: ValidationRunData, fullContractName: string): boolean {
190
196
  const methods = getAllMethods(runValidation, fullContractName);
191
- return methods.includes(upgradeToSignature);
197
+ return methods.includes(upgradeToSignature) || methods.includes(upgradeToAndCallSignature);
192
198
  }
193
199
 
194
200
  export function inferProxyKind(data: ValidationData, version: Version): ProxyDeployment['kind'] {
@@ -57,9 +57,10 @@ const errorInfo: ErrorDescriptions<ValidationError> = {
57
57
  hint: () => `Update your dependency and run again`,
58
58
  },
59
59
  'missing-public-upgradeto': {
60
- msg: () => `Implementation is missing a public \`upgradeTo(address)\` function`,
60
+ msg: () =>
61
+ `Implementation is missing a public \`upgradeTo(address)\` or \`upgradeToAndCall(address,bytes)\` function`,
61
62
  hint: () =>
62
- `Inherit UUPSUpgradeable to include this function in your contract\n` +
63
+ `Inherit UUPSUpgradeable to include one or both of these functions in your contract\n` +
63
64
  ` @openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol`,
64
65
  link: 'https://zpl.in/upgrades/error-008',
65
66
  },
@@ -1,8 +1,10 @@
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';
5
+ import * as versions from 'compare-versions';
4
6
 
5
- import { SolcOutput, SolcBytecode } from '../solc-api';
7
+ import { SolcOutput, SolcBytecode, SolcInput } from '../solc-api';
6
8
  import { SrcDecoder } from '../src-decoder';
7
9
  import { isNullish } from '../utils/is-nullish';
8
10
  import { getFunctionSignature } from '../utils/function';
@@ -12,6 +14,8 @@ import { extractStorageLayout } from '../storage/extract';
12
14
  import { StorageLayout } from '../storage/layout';
13
15
  import { getFullyQualifiedName } from '../utils/contract-name';
14
16
  import { getAnnotationArgs as getSupportedAnnotationArgs, getDocumentation } from '../utils/annotations';
17
+ import { getStorageLocationAnnotation } from '../storage/namespace';
18
+ import { UpgradesError } from '../error';
15
19
 
16
20
  export type ValidationRunData = Record<string, ContractValidation>;
17
21
 
@@ -116,7 +120,29 @@ function skipCheck(error: string, node: Node): boolean {
116
120
  return getAllowed(node, false).includes(error) || getAllowed(node, true).includes(error);
117
121
  }
118
122
 
119
- export function validate(solcOutput: SolcOutput, decodeSrc: SrcDecoder, solcVersion?: string): ValidationRunData {
123
+ /**
124
+ * Runs validations on the given solc output.
125
+ *
126
+ * If `namespacedOutput` is provided, it is used to extract storage layout information for namespaced types.
127
+ * It must be from a compilation with the same sources as `solcInput` and `solcOutput`, but with storage variables
128
+ * injected for each namespaced struct so that the types are available in the storage layout. This can be obtained by
129
+ * calling the `makeNamespacedInput` function from this package to create modified solc input, then compiling
130
+ * that modified solc input to get the namespaced output.
131
+ *
132
+ * @param solcOutput Solc output to validate
133
+ * @param decodeSrc Source decoder for the original source code
134
+ * @param solcVersion The version of solc used to compile the contracts
135
+ * @param solcInput Solc input that the compiler was invoked with
136
+ * @param namespacedOutput Namespaced solc output to extract storage layout information for namespaced types
137
+ * @returns A record of validation results for each fully qualified contract name
138
+ */
139
+ export function validate(
140
+ solcOutput: SolcOutput,
141
+ decodeSrc: SrcDecoder,
142
+ solcVersion?: string,
143
+ solcInput?: SolcInput,
144
+ namespacedOutput?: SolcOutput,
145
+ ): ValidationRunData {
120
146
  const validation: ValidationRunData = {};
121
147
  const fromId: Record<number, string> = {};
122
148
  const inheritIds: Record<string, number[]> = {};
@@ -128,6 +154,9 @@ export function validate(solcOutput: SolcOutput, decodeSrc: SrcDecoder, solcVers
128
154
  const selfDestructCache = initOpcodeCache();
129
155
 
130
156
  for (const source in solcOutput.contracts) {
157
+ checkNamespaceSolidityVersion(source, solcVersion, solcInput);
158
+ checkNamespacesOutsideContract(source, solcOutput, decodeSrc);
159
+
131
160
  for (const contractName in solcOutput.contracts[source]) {
132
161
  const bytecode = solcOutput.contracts[source][contractName].evm.bytecode;
133
162
  const version = bytecode.object === '' ? undefined : getVersion(bytecode.object);
@@ -176,7 +205,9 @@ export function validate(solcOutput: SolcOutput, decodeSrc: SrcDecoder, solcVers
176
205
  decodeSrc,
177
206
  deref,
178
207
  solcOutput.contracts[source][contractDef.name].storageLayout,
208
+ getNamespacedCompilationContext(source, contractDef, namespacedOutput),
179
209
  );
210
+
180
211
  validation[key].methods = [...findAll('FunctionDefinition', contractDef)]
181
212
  .filter(fnDef => ['external', 'public'].includes(fnDef.visibility))
182
213
  .map(fnDef => getFunctionSignature(fnDef, deref));
@@ -195,6 +226,82 @@ export function validate(solcOutput: SolcOutput, decodeSrc: SrcDecoder, solcVers
195
226
  return validation;
196
227
  }
197
228
 
229
+ function checkNamespaceSolidityVersion(source: string, solcVersion?: string, solcInput?: SolcInput) {
230
+ if (solcInput === undefined) {
231
+ // This should only be missing if using an old version of the Hardhat or Truffle plugin.
232
+ // Even without this param, namespace layout checks would still occur if compiled with solc version >= 0.8.20
233
+ debug('Cannot check Solidity version for namespaces because solcInput is undefined');
234
+ } else {
235
+ // Solc versions older than 0.8.20 do not have documentation for structs.
236
+ // Use a regex to check for strings that look like namespace annotations, and if found, check that the compiler version is >= 0.8.20
237
+ const content = solcInput.sources[source].content;
238
+ const hasMatch = content !== undefined && content.match(/@custom:storage-location/);
239
+ if (hasMatch) {
240
+ if (solcVersion === undefined) {
241
+ throw new UpgradesError(
242
+ `${source}: Namespace annotations require Solidity version >= 0.8.20, but no solcVersion parameter was provided`,
243
+ () =>
244
+ `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.`,
245
+ );
246
+ } else if (versions.compare(solcVersion, '0.8.20', '<')) {
247
+ throw new UpgradesError(
248
+ `${source}: Namespace annotations require Solidity version >= 0.8.20, but ${solcVersion} was used`,
249
+ () =>
250
+ `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.`,
251
+ );
252
+ }
253
+ }
254
+ }
255
+ }
256
+
257
+ function checkNamespacesOutsideContract(source: string, solcOutput: SolcOutput, decodeSrc: SrcDecoder) {
258
+ for (const node of solcOutput.sources[source].ast.nodes) {
259
+ if (isNodeType('StructDefinition', node)) {
260
+ const storageLocation = getStorageLocationAnnotation(node);
261
+ if (storageLocation !== undefined) {
262
+ throw new UpgradesError(
263
+ `${decodeSrc(node)}: Namespace struct ${node.name} is defined outside of a contract`,
264
+ () =>
265
+ `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.`,
266
+ );
267
+ }
268
+ }
269
+ }
270
+ }
271
+
272
+ function getNamespacedCompilationContext(
273
+ source: string,
274
+ contractDef: ContractDefinition,
275
+ namespacedOutput?: SolcOutput,
276
+ ) {
277
+ if (namespacedOutput === undefined || contractDef.canonicalName === undefined) {
278
+ return undefined;
279
+ }
280
+
281
+ if (namespacedOutput.sources[source] === undefined) {
282
+ throw new Error(`Source ${source} not found in namespaced solc output`);
283
+ }
284
+
285
+ const namespacedContractDef = namespacedOutput.sources[source].ast.nodes
286
+ .filter(isNodeType('ContractDefinition'))
287
+ .find(c => c.canonicalName === contractDef.canonicalName);
288
+
289
+ if (namespacedContractDef === undefined) {
290
+ throw new Error(`Contract definition with name ${contractDef.canonicalName} not found in namespaced solc output`);
291
+ }
292
+
293
+ const storageLayout = namespacedOutput.contracts[source][contractDef.name].storageLayout;
294
+ if (storageLayout === undefined) {
295
+ throw new Error(`Storage layout for contract ${contractDef.canonicalName} not found in namespaced solc output`);
296
+ }
297
+
298
+ return {
299
+ contractDef: namespacedContractDef,
300
+ deref: astDereferencer(namespacedOutput),
301
+ storageLayout: storageLayout,
302
+ };
303
+ }
304
+
198
305
  function* getConstructorErrors(contractDef: ContractDefinition, decodeSrc: SrcDecoder): Generator<ValidationError> {
199
306
  for (const fnDef of findAll('FunctionDefinition', contractDef, node => skipCheck('constructor', node))) {
200
307
  if (fnDef.kind === 'constructor' && ((fnDef.body?.statements?.length ?? 0) > 0 || fnDef.modifiers.length > 0)) {
@@ -99,7 +99,10 @@ testOverride('UsesExplicitSafeExternalLibrary', 'transparent', { unsafeAllow: ['
99
99
  testValid('HasEmptyConstructor', 'uups', false);
100
100
  testValid('HasInternalUpgradeToFunction', 'uups', false);
101
101
  testValid('HasUpgradeToFunction', 'uups', true);
102
+ testValid('HasInternalUpgradeToAndCallFunction', 'uups', false);
103
+ testValid('HasUpgradeToAndCallFunction', 'uups', true);
102
104
  testValid('ParentHasUpgradeToFunction', 'uups', true);
105
+ testValid('ParentHasUpgradeToAndCallFunction', 'uups', true);
103
106
  testValid('ChildOfProxiable', 'uups', true);
104
107
 
105
108
  testValid('HasNonEmptyConstructorNatspec1', 'transparent', true);