Skip to content

Commit 2b1a10a

Browse files
authored
Validate Solidity custom storage layout, use OpenZeppelin Contracts 5.3.0 (#1144)
1 parent 5e0cc33 commit 2b1a10a

37 files changed

+1285
-20
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+
## Unreleased
4+
5+
- Validate Solidity custom storage layouts, use proxies from OpenZeppelin Contracts 5.3.0. ([#1144](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/1144))
6+
37
## 1.43.0 (2025-04-11)
48

59
- Add Sonic network to manifest file names. ([#1146](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/1146))
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.29;
3+
4+
abstract contract Base {
5+
uint256 public x;
6+
}
7+
8+
contract CustomLayout layout at 0x1 is Base {
9+
uint256 public y;
10+
}
11+
12+
contract CustomLayout_Same_Root_Ok layout at 0x1 is Base {
13+
uint256 public y;
14+
uint256 public z;
15+
}
16+
17+
contract CustomLayout_Same_Root_Decimal_Ok layout at 1 is Base {
18+
uint256 public y;
19+
uint256 public z;
20+
}
21+
22+
contract CustomLayout_Same_Root_ScientificNotation_Ok layout at 1000.0e-3 is Base {
23+
uint256 public y;
24+
uint256 public z;
25+
}
26+
27+
contract CustomLayout_Changed_Root_Bad layout at 0x2 is Base {
28+
uint256 public y;
29+
uint256 public z;
30+
}
31+
32+
contract CustomLayout_Changed_Root_Decimal_Bad layout at 2 is Base {
33+
uint256 public y;
34+
uint256 public z;
35+
}
36+
37+
contract CustomLayout_Changed_Root_ScientificNotation_Bad layout at 2.0e10 is Base {
38+
uint256 public y;
39+
uint256 public z;
40+
}
41+
42+
contract CustomLayout_Changed_To_Default_Bad is Base {
43+
uint256 public y;
44+
uint256 public z;
45+
}
46+
47+
contract CustomLayout_Changed_Root_Ok layout at 0x0 {
48+
uint256 public insertedBefore;
49+
uint256 public x;
50+
uint256 public y;
51+
uint256 public z;
52+
}
53+
54+
contract CustomLayout_Changed_To_Default_Ok {
55+
uint256 public insertedBefore;
56+
uint256 public x;
57+
uint256 public y;
58+
uint256 public z;
59+
}
60+
61+
contract Namespaced_Default_Layout {
62+
/// @custom:storage-location erc7201:example.main
63+
struct MainStorage {
64+
uint256 x;
65+
uint256 y;
66+
}
67+
68+
// keccak256(abi.encode(uint256(keccak256("example.main")) - 1)) & ~bytes32(uint256(0xff));
69+
bytes32 private constant MAIN_STORAGE_LOCATION =
70+
0x183a6125c38840424c4a85fa12bab2ab606c4b6d0e7cc73c0c06ba5300eab500;
71+
}
72+
73+
contract CustomLayout_Unsupported_Node_Type layout at 0x2 ** 0x3 {
74+
uint256 public x;
75+
}
76+
77+
contract Namespaced_Custom_Layout_Unaffected layout at 0x1 {
78+
/// @custom:storage-location erc7201:example.main
79+
struct MainStorage {
80+
uint256 x;
81+
uint256 y;
82+
}
83+
84+
// keccak256(abi.encode(uint256(keccak256("example.main")) - 1)) & ~bytes32(uint256(0xff));
85+
bytes32 private constant MAIN_STORAGE_LOCATION =
86+
0x183a6125c38840424c4a85fa12bab2ab606c4b6d0e7cc73c0c06ba5300eab500;
87+
}
88+
89+
contract Namespaced_Custom_Layout_Clash layout at 0x183a6125c38840424c4a85fa12bab2ab606c4b6d0e7cc73c0c06ba5300eab500 {
90+
/// @custom:storage-location erc7201:example.main
91+
struct MainStorage {
92+
uint256 x;
93+
uint256 y;
94+
}
95+
96+
uint256 clashesWithNamespace;
97+
98+
// keccak256(abi.encode(uint256(keccak256("example.main")) - 1)) & ~bytes32(uint256(0xff));
99+
bytes32 private constant MAIN_STORAGE_LOCATION =
100+
0x183a6125c38840424c4a85fa12bab2ab606c4b6d0e7cc73c0c06ba5300eab500;
101+
}
102+
103+
contract Namespaced_Custom_Layout_Clash_Decimal layout at 10958655983261152271848436692291137275443024275653522991983264966744321209600 {
104+
/// @custom:storage-location erc7201:example.main
105+
struct MainStorage {
106+
uint256 x;
107+
uint256 y;
108+
}
109+
110+
uint256 clashesWithNamespace;
111+
112+
// keccak256(abi.encode(uint256(keccak256("example.main")) - 1)) & ~bytes32(uint256(0xff));
113+
bytes32 private constant MAIN_STORAGE_LOCATION =
114+
0x183a6125c38840424c4a85fa12bab2ab606c4b6d0e7cc73c0c06ba5300eab500;
115+
}
116+
117+
contract Namespaced_Custom_Layout_Clash_Extra_Colon layout at 0xc7aaba8d22be97cadd7427e7a6841eaffa38e68fd57b8f5417d9acc1a1566e00 {
118+
/// @custom:storage-location erc7201:example:main
119+
struct MainStorage {
120+
uint256 x;
121+
uint256 y;
122+
}
123+
124+
uint256 clashesWithNamespace;
125+
126+
// keccak256(abi.encode(uint256(keccak256("example:main")) - 1)) & ~bytes32(uint256(0xff));
127+
bytes32 private constant MAIN_STORAGE_LOCATION =
128+
0xc7aaba8d22be97cadd7427e7a6841eaffa38e68fd57b8f5417d9acc1a1566e00;
129+
}
130+
131+
/// No formula id for the namespace (annotation does not comply with ERC-7201), so it is not checked for clash with custom layout
132+
contract CustomLayout_No_Namespace_Formula layout at 0x183a6125c38840424c4a85fa12bab2ab606c4b6d0e7cc73c0c06ba5300eab500 {
133+
/// @custom:storage-location example.main
134+
struct MainStorage {
135+
uint256 x;
136+
uint256 y;
137+
}
138+
139+
uint256 a;
140+
}
141+
142+
/// No formula id for the namespace (annotation does not comply with ERC-7201), so it is not checked for clash with custom layout
143+
contract CustomLayout_Unknown_Namespace_Formula layout at 0x183a6125c38840424c4a85fa12bab2ab606c4b6d0e7cc73c0c06ba5300eab500 {
144+
/// @custom:storage-location erc0000:example.main
145+
struct MainStorage {
146+
uint256 x;
147+
uint256 y;
148+
}
149+
150+
uint256 a;
151+
}
152+
153+
abstract contract NamespaceA {
154+
/// @custom:storage-location erc7201:example.a
155+
struct AStorage {
156+
uint256 x;
157+
}
158+
}
159+
160+
abstract contract NamespaceB {
161+
/// @custom:storage-location erc7201:example.b
162+
struct BStorage {
163+
uint256 x;
164+
}
165+
}
166+
167+
contract CustomLayout_Multiple_Namespaces layout at 0x1 is NamespaceA, NamespaceB {
168+
}
169+
170+
contract Gap {
171+
uint256[50] __gap;
172+
uint256 public x;
173+
}
174+
175+
contract Gap_Changed_Root_Ok layout at 25 {
176+
uint256[25] __gap;
177+
uint256 public x;
178+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.29;
3+
4+
abstract contract Base {
5+
}
6+
7+
contract ExampleCustomLayout layout at 0x123 is Base {
8+
/// @custom:storage-location erc7201:example.main
9+
struct MainStorage {
10+
uint256 x;
11+
uint256 z;
12+
}
13+
}
14+
15+
contract ExampleCustomLayout_DifferentSpecifierOrder is Base layout at 0x123 {
16+
/// @custom:storage-location erc7201:example.main
17+
struct MainStorage {
18+
uint256 x;
19+
uint256 z;
20+
}
21+
}

packages/core/hardhat.config.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,11 @@ function getNamespacedOverrides() {
3838
for (const c of namespacedContracts) {
3939
if (c === 'NamespacedToModify07.sol') {
4040
overrides[`contracts/test/${c}`] = { version: '0.7.6', settings };
41+
} else if (c === 'NamespacedToModifyCustomLayout.sol') {
42+
overrides[`contracts/test/${c}`] = { version: '0.8.29', settings: settingsWithParisEVM };
4143
} else {
42-
overrides[`contracts/test/${c}`] = { version: '0.8.20', settings: settingsWithParisEVM };
44+
// pin compiler version to the most recent Solidity version that Slang supports
45+
overrides[`contracts/test/${c}`] = { version: '0.8.28', settings: settingsWithParisEVM };
4346
}
4447
}
4548
return overrides;

packages/core/hardhat/separate-test-contracts.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@ task(TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOB_FOR_FILE, async (params, _, runSu
1111
// compiled separately (along with the other marked files).
1212
// Dependencies of proxy contracts would be automatically included in the proxy contracts compilation.
1313
if (!params.file.sourceName.startsWith('@openzeppelin/contracts/proxy/')) {
14-
// Mark each CLI Solidity file differently from regular tests, so that they can be compiled separately.
15-
// This is needed because CLI tests validate the entire build-info file, so each build-info should include only relevant contracts.
1614
if (params.file.sourceName.startsWith('contracts/test/cli/')) {
15+
// Mark each CLI Solidity file differently from regular tests, so that they can be compiled separately.
16+
// This is needed because CLI tests validate the entire build-info file, so each build-info should include only relevant contracts.
1717
mark(job, params.file.sourceName);
1818
} else if (params.file.sourceName.startsWith('contracts/test/Namespaced')) {
1919
mark(job, 'testNamespaced');
20+
} else if (params.file.sourceName.includes('CustomLayout')) {
21+
// Tests layout conflicts which causes errors if validated together with other tests.
22+
mark(job, 'testCustomLayout');
2023
} else {
2124
mark(job, 'test');
2225
}
@@ -36,6 +39,10 @@ function mark(job, group) {
3639

3740
const originalConfig = job.solidityConfig;
3841

42+
if (originalConfig === undefined) {
43+
throw Error(`Solidity config is missing for job: ${JSON.stringify(job, null, 2)}`);
44+
}
45+
3946
if (originalConfig[marker] && originalConfig[marker] !== group) {
4047
throw Error('Same job in different compilation groups');
4148
}

packages/core/package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@
3434
"compile:contracts": "hardhat compile",
3535
"copyfiles": "node scripts/copy-build-info-v5.js && bash scripts/copy-legacy-artifacts.sh",
3636
"prepare": "yarn clean && yarn compile && yarn copyfiles",
37-
"test": "tsc -b && hardhat compile --force && yarn copyfiles && ava",
37+
"pretest": "tsc -b && hardhat compile --force && yarn copyfiles",
38+
"test": "ava",
39+
"test:update-snapshots": "yarn test --update-snapshots",
3840
"test:watch": "hardhat compile --force && yarn copyfiles && fgbg 'ava --watch' 'tsc -b --watch' --",
3941
"version": "node ../../scripts/bump-changelog.js",
4042
"cli": "node ./dist/cli/cli.js"
@@ -45,7 +47,7 @@
4547
"devDependencies": {
4648
"@ava/typescript": "^5.0.0",
4749
"@nomicfoundation/hardhat-ethers": "^3.0.5",
48-
"@openzeppelin/contracts": "5.0.2",
50+
"@openzeppelin/contracts": "5.3.0",
4951
"@openzeppelin/upgrades-core-legacy": "npm:@openzeppelin/upgrades-core@1.31.3",
5052
"@types/debug": "^4.1.5",
5153
"@types/minimist": "^1.2.5",
@@ -65,11 +67,12 @@
6567
"cbor": "^10.0.0",
6668
"chalk": "^4.1.0",
6769
"compare-versions": "^6.0.0",
70+
"bignumber.js": "^9.1.2",
6871
"debug": "^4.1.1",
6972
"ethereumjs-util": "^7.0.3",
7073
"minimist": "^1.2.7",
7174
"proper-lockfile": "^4.1.1",
72-
"solidity-ast": "^0.4.51",
75+
"solidity-ast": "^0.4.60",
7376
"minimatch": "^9.0.5"
7477
}
7578
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
"0.8.20"
1+
"0.8.29"

packages/core/src/storage-0.8.test.ts.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Generated by [AVA](https://avajs.dev).
99
> Snapshot 1
1010
1111
{
12+
baseSlot: undefined,
1213
namespaces: [],
1314
storage: [
1415
{
@@ -47,6 +48,7 @@ Generated by [AVA](https://avajs.dev).
4748
> Snapshot 1
4849
4950
{
51+
baseSlot: undefined,
5052
namespaces: [],
5153
storage: [
5254
{
@@ -85,6 +87,7 @@ Generated by [AVA](https://avajs.dev).
8587
> Snapshot 1
8688
8789
{
90+
baseSlot: undefined,
8891
namespaces: [],
8992
storage: [
9093
{
@@ -120,6 +123,7 @@ Generated by [AVA](https://avajs.dev).
120123
> Snapshot 1
121124
122125
{
126+
baseSlot: undefined,
123127
namespaces: [],
124128
storage: [
125129
{
14 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)