Skip to content

Commit 88e2c1b

Browse files
authored
Keep constants for namespaced compilation (#1046)
1 parent 3165e15 commit 88e2c1b

File tree

7 files changed

+121
-43
lines changed

7 files changed

+121
-43
lines changed

packages/core/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 1.34.2 (2024-07-18)
4+
5+
- Fix Hardhat compile error when constants have references to other constants. ([#1046](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/1046))
6+
37
## 1.34.1 (2024-06-18)
48

59
- Fix unexpected validation error when function parameter has internal function pointer. ([#1038](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/1038))

packages/core/contracts/test/NamespacedToModify.sol

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ contract Consumer {
116116
}
117117
}
118118

119+
contract HasConstantWithSelector {
120+
bytes4 constant CONTRACT_CONSTANT_USING_SELECTOR = HasFunction.foo.selector;
121+
}
122+
119123
function plusTwo(uint x) pure returns (uint) {
120124
return x + 2;
121125
}
@@ -176,17 +180,38 @@ enum FreeEnum { MyEnum }
176180
*/
177181
error CustomErrorOutsideContract(Example example);
178182

183+
int8 constant MAX_SIZE_C = 2;
184+
179185
contract StructArrayUsesConstant {
180186
uint16 private constant MAX_SIZE = 10;
181187

182188
struct NotNamespaced {
183189
uint16 a;
184190
uint256[MAX_SIZE] b;
191+
uint256[MAX_SIZE_C] c;
185192
}
186193

187194
/// @custom:storage-location erc7201:uses.constant
188195
struct MainStorage {
189196
uint256 x;
190197
uint256[MAX_SIZE] y;
198+
uint256[MAX_SIZE_C] c;
191199
}
200+
}
201+
202+
address constant MY_ADDRESS = address(0);
203+
uint constant CONVERTED_ADDRESS = uint160(MY_ADDRESS);
204+
205+
interface IDummy {
206+
}
207+
208+
contract UsesAddress {
209+
IDummy public constant MY_CONTRACT = IDummy(MY_ADDRESS);
210+
}
211+
212+
contract HasFunctionWithRequiredReturn {
213+
struct S { uint x; }
214+
function foo(S calldata s) internal pure returns (S calldata) {
215+
return s;
216+
}
192217
}

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@openzeppelin/upgrades-core",
3-
"version": "1.34.1",
3+
"version": "1.34.2",
44
"description": "",
55
"repository": "https://github.com/OpenZeppelin/openzeppelin-upgrades/tree/master/packages/core",
66
"license": "MIT",

packages/core/src/utils/make-namespaced.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ async function testMakeNamespaced(
3838

3939
// Run hardhat compile on the modified input and make sure it has no errors
4040
const modifiedOutput = await hardhatCompile(modifiedInput, solcVersion);
41-
t.is(modifiedOutput.errors, undefined);
41+
t.is(modifiedOutput.errors, undefined, JSON.stringify(modifiedInput.sources, null, 2));
4242

4343
normalizeIdentifiers(modifiedInput);
4444
t.snapshot(modifiedInput);

packages/core/src/utils/make-namespaced.test.ts.md

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -46,22 +46,22 @@ Generated by [AVA](https://avajs.dev).
4646
} SecondaryStorage $SecondaryStorage_random;␊
4747
4848
/// @notice some natspec␊
49-
enum $astId_id_random { dummy }␊
49+
function foo() public {}␊
5050
5151
/// @param a docs␊
52-
enum $astId_id_random { dummy }␊
52+
function foo1(uint a) public {}␊
5353
5454
/// @param a docs␊
55-
enum $astId_id_random { dummy }␊
55+
function foo2(uint a) internal {}␊
5656
struct MyStruct { uint b; }␊
5757
5858
// keccak256(abi.encode(uint256(keccak256("example.main")) - 1)) & ~bytes32(uint256(0xff));␊
5959
bytes32 private constant MAIN_STORAGE_LOCATION =␊
6060
0x183a6125c38840424c4a85fa12bab2ab606c4b6d0e7cc73c0c06ba5300eab500;␊
6161
62-
enum $astId_id_random { dummy }␊
62+
function _getMainStorage() private pure returns (bool) {}␊
6363
64-
enum $astId_id_random { dummy }␊
64+
function _getXTimesY() internal view returns (bool) {}␊
6565
6666
/// @notice standlone natspec␊
6767
@@ -77,14 +77,14 @@ Generated by [AVA](https://avajs.dev).
7777
/**␊
7878
* doc block without natspec␊
7979
*/␊
80-
enum $astId_id_random { dummy }␊
80+
function foo3() public {}␊
8181
8282
/**␊
8383
* doc block with natspec␊
8484
*␊
8585
* @notice some natspec␊
8686
*/␊
87-
enum $astId_id_random { dummy }␊
87+
function foo4() public {}␊
8888
8989
/**␊
9090
* @dev a custom error inside a contract␊
@@ -99,35 +99,39 @@ Generated by [AVA](https://avajs.dev).
9999
}␊
100100
101101
contract HasFunction {␊
102-
enum $astId_id_random { dummy }
103-
enum $astId_id_random { dummy }␊
102+
103+
function foo() pure public returns (bool) {}␊
104104
}␊
105105
106106
contract UsingFunction is HasFunction {␊
107107
enum $astId_id_random { dummy }␊
108108
}␊
109109
110-
enum FreeFunctionUsingSelector { dummy }␊
110+
function FreeFunctionUsingSelector() pure returns (bool) {}␊
111111
112-
enum CONSTANT_USING_SELECTOR { dummy }
112+
bytes4 constant CONSTANT_USING_SELECTOR = HasFunction.foo.selector;
113113
114114
library Lib {␊
115-
enum $astId_id_random { dummy }␊
115+
function usingSelector() pure public returns (bool) {}␊
116116
117-
enum $astId_id_random { dummy }␊
117+
function plusOne(uint x) pure public returns (bool) {}␊
118118
}␊
119119
120120
contract Consumer {␊
121121
enum $astId_id_random { dummy }␊
122122
123-
enum $astId_id_random { dummy }␊
123+
function usingFreeFunction() pure public returns (bool) {}␊
124124
125-
enum $astId_id_random { dummy }␊
125+
function usingConstant() pure public returns (bool) {}␊
126126
127-
enum $astId_id_random { dummy }␊
127+
function usingLibraryFunction() pure public returns (bool) {}␊
128128
}␊
129129
130-
enum plusTwo { dummy }␊
130+
contract HasConstantWithSelector {␊
131+
bytes4 constant CONTRACT_CONSTANT_USING_SELECTOR = HasFunction.foo.selector;␊
132+
}␊
133+
134+
function plusTwo(uint x) pure returns (bool) {}␊
131135
132136
/**␊
133137
* @notice originally orphaned natspec␊
@@ -137,7 +141,7 @@ Generated by [AVA](https://avajs.dev).
137141
* @dev plusThree␊
138142
* @param x x␊
139143
*/␊
140-
enum plusThree { dummy }␊
144+
function plusThree(uint x) pure returns (bool) {}␊
141145
142146
/// @notice originally orphaned natspec 2␊
143147
@@ -146,9 +150,9 @@ Generated by [AVA](https://avajs.dev).
146150
* @param x x␊
147151
* @param y y␊
148152
*/␊
149-
enum $astId_id_random { dummy }␊
153+
function plusThree(uint x, uint y) pure returns (bool) {}␊
150154
151-
enum originallyNoDocumentation { dummy }␊
155+
function originallyNoDocumentation() pure {}␊
152156
153157
/**␊
154158
* @param foo foo␊
@@ -158,7 +162,7 @@ Generated by [AVA](https://avajs.dev).
158162
contract UsingForDirectives {␊
159163
enum $astId_id_random { dummy }␊
160164
161-
enum $astId_id_random { dummy }␊
165+
function usingFor(uint x) pure public returns (bool) {}␊
162166
}␊
163167
164168
/**␊
@@ -179,19 +183,38 @@ Generated by [AVA](https://avajs.dev).
179183
*/␊
180184
enum CustomErrorOutsideContract { dummy }␊
181185
186+
int8 constant MAX_SIZE_C = 2;␊
187+
182188
contract StructArrayUsesConstant {␊
183189
uint16 private constant MAX_SIZE = 10;␊
184190
185191
struct NotNamespaced {␊
186192
uint16 a;␊
187193
uint256[MAX_SIZE] b;␊
194+
uint256[MAX_SIZE_C] c;␊
188195
}␊
189196
190197
/// @custom:storage-location erc7201:uses.constant␊
191198
struct MainStorage {␊
192199
uint256 x;␊
193200
uint256[MAX_SIZE] y;␊
201+
uint256[MAX_SIZE_C] c;␊
194202
} MainStorage $MainStorage_random;␊
203+
}␊
204+
205+
address constant MY_ADDRESS = address(0);␊
206+
uint constant CONVERTED_ADDRESS = uint160(MY_ADDRESS);␊
207+
208+
interface IDummy {␊
209+
}␊
210+
211+
contract UsesAddress {␊
212+
IDummy public constant MY_CONTRACT = IDummy(MY_ADDRESS);␊
213+
}␊
214+
215+
contract HasFunctionWithRequiredReturn {␊
216+
struct S { uint x; }␊
217+
function foo(S calldata s) internal pure returns (bool) {}␊
195218
}`,
196219
},
197220
'contracts/test/NamespacedToModifyImported.sol': {
@@ -234,8 +257,8 @@ Generated by [AVA](https://avajs.dev).
234257
pragma solidity 0.7.6;␊
235258
236259
contract HasFunction {␊
237-
enum $astId_id_random { dummy }
238-
enum $astId_id_random { dummy }␊
260+
261+
function foo() pure public returns (bool) {}␊
239262
}␊
240263
241264
contract UsingFunction is HasFunction {␊
328 Bytes
Binary file not shown.

packages/core/src/utils/make-namespaced.ts

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Node } from 'solidity-ast/node';
33
import { SolcInput, SolcOutput } from '../solc-api';
44
import { getStorageLocationAnnotation } from '../storage/namespace';
55
import { assert } from './assert';
6+
import { FunctionDefinition } from 'solidity-ast';
67

78
const OUTPUT_SELECTION = {
89
'*': {
@@ -40,7 +41,6 @@ export function makeNamespacedInput(input: SolcInput, output: SolcOutput): SolcI
4041

4142
const orig = Buffer.from(source.content, 'utf8');
4243

43-
const replacedIdentifiers = new Set<string>();
4444
const modifications: Modification[] = [];
4545

4646
for (const node of output.sources[sourcePath].ast.nodes) {
@@ -60,6 +60,10 @@ export function makeNamespacedInput(input: SolcInput, output: SolcOutput): SolcI
6060
const contractNodes = contractDef.nodes;
6161
for (const contractNode of contractNodes) {
6262
switch (contractNode.nodeType) {
63+
case 'FunctionDefinition': {
64+
replaceFunction(contractNode, orig, modifications);
65+
break;
66+
}
6367
case 'VariableDeclaration': {
6468
// If variable is a constant, keep it since it may be referenced in a struct
6569
if (contractNode.constant) {
@@ -69,7 +73,6 @@ export function makeNamespacedInput(input: SolcInput, output: SolcOutput): SolcI
6973
}
7074
case 'ErrorDefinition':
7175
case 'EventDefinition':
72-
case 'FunctionDefinition':
7376
case 'ModifierDefinition':
7477
case 'UsingForDirective': {
7578
// Replace with an enum based on astId (the original name is not needed, since nothing should reference it)
@@ -100,27 +103,30 @@ export function makeNamespacedInput(input: SolcInput, output: SolcOutput): SolcI
100103
break;
101104
}
102105

103-
// - UsingForDirective isn't needed, but it might have NatSpec documentation which is not included in the AST.
104-
// We convert it to a dummy enum to avoid orphaning any possible documentation.
105-
// - ErrorDefinition, FunctionDefinition, and VariableDeclaration might be imported by other files, so they cannot be deleted.
106+
case 'FunctionDefinition': {
107+
replaceFunction(node, orig, modifications);
108+
break;
109+
}
110+
111+
// - VariableDeclaration and ErrorDefinition might be imported by other files, so they cannot be deleted.
106112
// However, we need to remove their values to avoid referencing other deleted nodes.
107-
// We do this by converting them to dummy enums, but avoiding duplicate names.
108-
case 'UsingForDirective':
109-
case 'ErrorDefinition':
110-
case 'FunctionDefinition':
113+
// We do this by converting them to dummy enums with the same name.
111114
case 'VariableDeclaration': {
112-
// If an identifier with the same name was not previously written, replace with a dummy enum using its name.
113-
// Otherwise replace with an enum based on astId to avoid duplicate names, which can happen if there was overloading.
114-
// This does not need to check all identifiers from the original contract, since the original compilation
115-
// should have failed if there were conflicts in the first place.
116-
if ('name' in node && !replacedIdentifiers.has(node.name)) {
117-
modifications.push(makeReplace(node, orig, toDummyEnumWithName(node.name)));
118-
replacedIdentifiers.add(node.name);
119-
} else {
120-
modifications.push(makeReplace(node, orig, toDummyEnumWithAstId(node.id)));
115+
// If variable is a constant, keep it since it may be referenced in a struct
116+
if (node.constant) {
117+
break;
121118
}
119+
// Otherwise, fall through to convert to dummy enum
120+
}
121+
case 'ErrorDefinition': {
122+
modifications.push(makeReplace(node, orig, toDummyEnumWithName(node.name)));
122123
break;
123124
}
125+
// - UsingForDirective isn't needed, but it might have NatSpec documentation which is not included in the AST.
126+
// We convert it to a dummy enum to avoid orphaning any possible documentation.
127+
case 'UsingForDirective':
128+
modifications.push(makeReplace(node, orig, toDummyEnumWithAstId(node.id)));
129+
break;
124130
case 'EnumDefinition':
125131
case 'ImportDirective':
126132
case 'PragmaDirective':
@@ -158,6 +164,26 @@ function toDummyEnumWithAstId(astId: number) {
158164
return `enum $astId_${astId}_${(Math.random() * 1e6).toFixed(0)} { dummy }`;
159165
}
160166

167+
function replaceFunction(node: FunctionDefinition, orig: Buffer, modifications: Modification[]) {
168+
if (node.kind === 'constructor') {
169+
modifications.push(makeDelete(node, orig));
170+
} else {
171+
if (node.modifiers.length > 0) {
172+
for (const modifier of node.modifiers) {
173+
modifications.push(makeDelete(modifier, orig));
174+
}
175+
}
176+
177+
if (node.returnParameters.parameters.length > 0) {
178+
modifications.push(makeReplace(node.returnParameters, orig, '(bool)'));
179+
}
180+
181+
if (node.body) {
182+
modifications.push(makeReplace(node.body, orig, '{}'));
183+
}
184+
}
185+
}
186+
161187
function getPositions(node: Node) {
162188
const [start, length] = node.src.split(':').map(Number);
163189
const end = start + length;

0 commit comments

Comments
 (0)