Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add validations for namespaced storage layout #876

Merged
merged 135 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from 113 commits
Commits
Show all changes
135 commits
Select commit Hold shift + click to select a range
a7d81a6
Support namespaced storage
ericglau Sep 7, 2023
0cbe703
Add doc comments
ericglau Sep 7, 2023
2c264aa
Improve readability
ericglau Sep 7, 2023
b71ac7d
Readability
ericglau Sep 7, 2023
4847692
Add check for identical namespaces
ericglau Sep 7, 2023
3b43219
Test duplicate and multiple namespaces
ericglau Sep 7, 2023
8ff168d
improve test
ericglau Sep 7, 2023
043415c
Add conflicting namespace test
ericglau Sep 7, 2023
00b9342
Move duplicate namespace test into predeployment test - wip
ericglau Sep 7, 2023
3deeb28
Handle duplicate namespaces
ericglau Sep 7, 2023
ddd0a96
Add doc comments
ericglau Sep 7, 2023
94ce0f1
add todo
ericglau Sep 7, 2023
54d6c6c
Change namespace conflicts into an upgrade safety validation error
ericglau Sep 8, 2023
c55709d
Update message
ericglau Sep 8, 2023
d86020b
Add mixed use testcase
ericglau Sep 8, 2023
2351b76
Add test
ericglau Sep 8, 2023
17d578d
Remove conflict from namespace storagelayout info, add debug for conf…
ericglau Sep 8, 2023
6d6e925
test add and remove namespace
ericglau Sep 8, 2023
7034b0c
Convert delete namespace error into storage layout comparison error
ericglau Sep 8, 2023
f7e8b58
Test move namespace
ericglau Sep 8, 2023
cfb95b0
Test namespace conflicts in inherited contracts
ericglau Sep 8, 2023
082dce2
Add test
ericglau Sep 8, 2023
3594d74
Add expected scenario - to do
ericglau Sep 8, 2023
41e9e95
Include inherited namespaces in layout
ericglau Sep 8, 2023
7c6e011
Rename to getStorageLocationArg
ericglau Sep 8, 2023
375f2f4
Refactor namespaced context handling
ericglau Sep 8, 2023
cbbfc84
Refactor pushing inherited namespaces
ericglau Sep 8, 2023
ac499a9
Simplify
ericglau Sep 8, 2023
d7cf4eb
Refactor
ericglau Sep 8, 2023
187d1cb
Use 0.8.20 for namespace tests only
ericglau Sep 11, 2023
dfb473d
Update example contracts
ericglau Sep 11, 2023
a209299
Check duplicate namespaces earlier - wip
ericglau Sep 11, 2023
077fe1e
Remove unused validations since duplicate namespaces checked earlier
ericglau Sep 11, 2023
01c4c8e
Add tests, fix checking duplicates
ericglau Sep 11, 2023
ba0314f
Unit test with namespaced layout for conflicts
ericglau Sep 11, 2023
49e42d5
Move conflict tests from hardhat to core
ericglau Sep 11, 2023
a89817a
Simplify tests
ericglau Sep 11, 2023
813e048
Update snapshots
ericglau Sep 11, 2023
9eaa293
Lint
ericglau Sep 11, 2023
3dca62e
Add namespaced storage layout test in core
ericglau Sep 11, 2023
485bf92
Simplify load namespace code
ericglau Sep 12, 2023
0c2970a
Further simplify
ericglau Sep 12, 2023
d7ffbfe
Change order of params
ericglau Sep 12, 2023
b6deedf
Make more readable
ericglau Sep 12, 2023
b9e8f8c
Add comment
ericglau Sep 12, 2023
d72f0f6
Assume pre-flattened namespaces
ericglau Sep 13, 2023
6f2bb62
Check namespaces outside contract
ericglau Sep 13, 2023
60d3486
Fix namespace outside contract check to only check direct nodes
ericglau Sep 13, 2023
7cfb61c
Add namespace solc version check
ericglau Sep 13, 2023
09d7a73
Set modified input to have only storageLayout and ast
ericglau Sep 13, 2023
21e1f95
Add args to validate when called by CLI
ericglau Sep 13, 2023
ea37f66
Update debug
ericglau Sep 13, 2023
9d9052c
Add UDVT tests
ericglau Sep 14, 2023
2d105ff
Use proper layout compilation
ericglau Sep 14, 2023
8989050
Improve test
ericglau Sep 14, 2023
3b61d32
Rename test for consistency
ericglau Sep 14, 2023
74821ed
Fix lint
ericglau Sep 14, 2023
fa39736
Add doc section for namespaces
ericglau Sep 14, 2023
3eb31c9
Update changelogs
ericglau Sep 14, 2023
d6a580e
remove todo
ericglau Sep 14, 2023
aa75a68
Move getNamespacedStorageOperations to comparator.getStorageOperations
ericglau Sep 18, 2023
48a0805
Update docs/modules/ROOT/pages/writing-upgradeable.adoc
ericglau Sep 19, 2023
2992e2a
Update packages/core/src/validate/run.ts
ericglau Sep 19, 2023
1ff29fe
Update packages/core/src/storage/extract.ts
ericglau Sep 19, 2023
d9d7799
Use isStructMembers
ericglau Sep 19, 2023
5a3ef22
Improve error message
ericglau Sep 19, 2023
2824957
Update solc version note
ericglau Sep 19, 2023
2d3234c
Move makeNamespacedInputCopy to core
ericglau Sep 19, 2023
473b642
Add unit test for makeNamespacedInputCopy
ericglau Sep 19, 2023
73d3a7a
Update function doc to mention makeNamespacedInputCopy
ericglau Sep 19, 2023
09c9a12
Update packages/core/src/validate/run.ts
ericglau Sep 19, 2023
3a95bef
Remove docs from deleted nodes - WIP
ericglau Sep 19, 2023
baa92f6
Update packages/core/src/storage/namespace.ts
ericglau Sep 19, 2023
44154d5
Improve copying of modified source
ericglau Sep 20, 2023
28e05ef
Slight refactor
ericglau Sep 20, 2023
2769f38
Move makeNamespacedInputCopy to separate ts file
ericglau Sep 20, 2023
108c2b0
Merge branch 'master' into namespaced
ericglau Sep 20, 2023
3f415a7
Copy solc input by reference where possible
ericglau Sep 20, 2023
3c1f4fa
Test that we don't modify original input object
ericglau Sep 20, 2023
c01d6c8
Keep original settings except output selection
ericglau Sep 20, 2023
3443421
Change to use compareLayouts for namespaces
ericglau Sep 20, 2023
48e630f
Remove parent constructor invocations - WIP
ericglau Sep 20, 2023
f2b8962
Add compilation test for modified input
ericglau Sep 20, 2023
6779e59
Remove export
ericglau Sep 20, 2023
db69587
Improve error message if source not found in namespaced output
ericglau Sep 20, 2023
a5d0968
Give error if using namespaces without solcVersion param
ericglau Sep 20, 2023
db8fd49
Deprecate low-level upgrades-core api
ericglau Sep 20, 2023
1427e1f
Add deprecation notice for low-level API
ericglau Sep 20, 2023
4f3005f
Handle InheritanceSpecifier names from different solc versions
ericglau Sep 20, 2023
7ccf60f
Update packages/core/src/utils/make-namespaced.ts
ericglau Sep 20, 2023
d1e14a0
Rename to getInsertAfter
ericglau Sep 20, 2023
b44d7b0
Update packages/core/src/utils/make-namespaced.ts
ericglau Sep 20, 2023
c7f447b
Update packages/core/src/utils/make-namespaced.ts
ericglau Sep 20, 2023
072b12b
Update packages/core/src/utils/make-namespaced.ts
ericglau Sep 21, 2023
1ed8d95
import assert
ericglau Sep 21, 2023
64cf40f
Use assertions, rename functions
ericglau Sep 21, 2023
f42c643
Add random suffix to state vars
ericglau Sep 21, 2023
64a2fa7
Apply suggestions from code review
ericglau Sep 21, 2023
391cb2a
Update packages/plugin-hardhat/src/index.ts
ericglau Sep 21, 2023
f83dbdf
Remove debug logs, improve error message
ericglau Sep 21, 2023
99e63df
Improve error message
ericglau Sep 21, 2023
a1ac156
Update packages/core/src/storage/namespace.ts
ericglau Sep 21, 2023
1c761fe
Remove top level imports in hardhat
ericglau Sep 21, 2023
e65a914
Check for conflicts in combined types
ericglau Sep 21, 2023
fb19b12
Refactor getNamespacedStorageItems
ericglau Sep 21, 2023
07ff777
Combine getStorageLocation functions
ericglau Sep 21, 2023
ee88fbb
Handle free functions and constants
ericglau Sep 21, 2023
11542b0
Cleanup testcase, add library test
ericglau Sep 22, 2023
e347fc4
Rename testcase items, add using for scenario
ericglau Sep 22, 2023
7814d37
Delete using for directives
ericglau Sep 22, 2023
57a0e04
Add 0.7.6 test
ericglau Sep 22, 2023
bcdc8f5
Add comment
ericglau Sep 22, 2023
5f01015
Add low-level api deprecation to core changelog
ericglau Sep 22, 2023
e12f9f1
improve type accuracy of getTypeMembers
frangio Sep 26, 2023
4d5ec07
lint
frangio Sep 26, 2023
a2cff19
lint
frangio Sep 26, 2023
b3fced1
Update packages/core/CHANGELOG.md
ericglau Sep 26, 2023
cfc11b0
Update packages/core/src/utils/make-namespaced.ts
ericglau Sep 26, 2023
6918047
Update packages/core/src/storage/namespace.ts
ericglau Sep 26, 2023
b8848c1
Update packages/core/src/utils/make-namespaced.ts
ericglau Sep 26, 2023
646c334
Update packages/core/src/storage/extract.ts
ericglau Sep 26, 2023
d80c85e
Lint and update comment
ericglau Sep 26, 2023
f88c233
Update packages/core/src/storage/namespace.ts
ericglau Sep 26, 2023
bde851a
Assert isStructMembers
ericglau Sep 26, 2023
76ba26e
Use assert/strict
ericglau Sep 26, 2023
68bd53e
Update packages/core/src/utils/make-namespaced.ts
ericglau Sep 26, 2023
9e131ec
Add comment for loadLayoutType
ericglau Sep 26, 2023
3405182
Simplify check for duplicate namespaces
ericglau Sep 26, 2023
f461e5f
Rename var
ericglau Sep 26, 2023
bd3fa30
Remove variable
ericglau Sep 26, 2023
10df6e6
Remove variable
ericglau Sep 26, 2023
c521097
Simplify GotTypeMembers
ericglau Sep 26, 2023
14ef3df
Update packages/core/src/utils/make-namespaced.ts
ericglau Sep 26, 2023
e58d73c
Update packages/core/src/storage/namespace.ts
ericglau Sep 26, 2023
c712b4c
Lint
ericglau Sep 26, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/modules/ROOT/pages/api-core.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ If any errors are found, the command will exit with a non-zero exit code and pri
* `--unsafeAllowRenames` - Configure storage layout check to allow variable renaming.
* `--unsafeSkipStorageCheck` - Skips checking for storage layout compatibility errors. This is a dangerous option meant to be used as a last resort.

[[high-level-api]]
== High-Level API

The high-level API is a programmatic equivalent to the <<validate-command, validate command>>. Use this API if you want to validate all of your project's upgradeable contracts from a JavaScript or TypeScript environment.
Expand Down Expand Up @@ -193,6 +194,8 @@ An object that represents the result of upgrade safety checks and storage layout

== Low-Level API

NOTE: This low-level API is deprecated. Use the <<high-level-api>> instead.

The low-level API works with https://docs.soliditylang.org/en/latest/using-the-compiler.html#compiler-input-and-output-json-description[Solidity input and output JSON objects] and lets you perform upgrade safety checks and storage layout comparisons on individual contracts. Use this API if you want to validate specific contracts rather than a whole project.

=== Prerequisites
Expand Down Expand Up @@ -229,6 +232,7 @@ constructor UpgradeableContract(
unsafeSkipStorageCheck?: boolean,
kind?: 'uups' | 'transparent' | 'beacon',
},
solcVersion?: string,
): UpgradeableContract
----

Expand All @@ -244,6 +248,7 @@ Creates a new instance of `UpgradeableContract`.
** `unsafeAllow`
** `unsafeAllowRenames`
** `unsafeSkipStorageCheck`
* `solcVersion` - the Solidity version used to compile the implementation contract.

TIP: In Hardhat, `solcInput` and `solcOutput` can be obtained from the Build Info file, which itself can be retrieved with `hre.artifacts.getBuildInfo`.

Expand Down
11 changes: 11 additions & 0 deletions docs/modules/ROOT/pages/writing-upgradeable.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -454,3 +454,14 @@ contract Base {
----

To help determine the proper storage gap size in the new version of your contract, you can simply attempt an upgrade using `upgradeProxy` or just run the validations with `validateUpgrade` (see docs for xref:api-hardhat-upgrades.adoc[Hardhat] or xref:api-truffle-upgrades.adoc[Truffle]). If a storage gap is not being reduced properly, you will see an error message indicating the expected size of the storage gap.

[[namespaced-storage-layout]]
=== Namespaced Storage Layout

https://eips.ethereum.org/EIPS/eip-7201[ERC-7201: Namespaced Storage Layout] is another convention that can be used to avoid storage layout errors when modifying base contracts or when changing the inheritance order of contracts. This convention is used in the upgradeable variant of OpenZeppelin Contracts starting with version 5.0.

This convention involves placing all storage variables of a contract into one or more structs and annotating those structs with `@custom:storage-location erc7201:<NAMESPACE_ID>`. A namespace id is a string that uniquely identifies each namespace in a contract, so the same id must not be defined more than once in a contract or any of its base contracts.

When using namespaced storage layouts, the OpenZeppelin Upgrades plugins will automatically detect the namespace ids and validate that each change within a namespace during an upgrade is safe according to the same rules as described in <<modifying-your-contracts>>.

NOTE: Solidity version 0.8.20 or higher is required in order to use the Upgrades plugins with namespaced storage layouts. The plugins will give an error if they detect `@custom:storage-location` annotations with an older version of Solidity, because older versions of the compiler do not produce sufficient information for validation of namespaced storage layouts.
5 changes: 5 additions & 0 deletions packages/core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## Unreleased

- Add validations for namespaced storage layout. ([#876](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/876))
- Deprecate low-level API. Use [CLI or high-level API](https://docs.openzeppelin.com/upgrades-plugins/1.x/api-core) instead.
ericglau marked this conversation as resolved.
Show resolved Hide resolved

## 1.29.0 (2023-09-19)

- Support implementations with upgradeTo or upgradeToAndCall. ([#880](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/880))
Expand Down
181 changes: 181 additions & 0 deletions packages/core/contracts/test/Namespaced.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract Example {
/// @custom:storage-location erc7201:example.main
struct MainStorage {
uint256 x;
uint256 y;
}

// keccak256(abi.encode(uint256(keccak256("example.main")) - 1)) & ~bytes32(uint256(0xff));
bytes32 private constant MAIN_STORAGE_LOCATION =
0x183a6125c38840424c4a85fa12bab2ab606c4b6d0e7cc73c0c06ba5300eab500;

function _getMainStorage() private pure returns (MainStorage storage $) {
assembly {
$.slot := MAIN_STORAGE_LOCATION
}
}

function _getXTimesY() internal view returns (uint256) {
MainStorage storage $ = _getMainStorage();
return $.x * $.y;
}
}

contract MultipleNamespaces {
/// @custom:storage-location erc7201:one
struct S1 {
uint256 a;
}

/// @custom:storage-location erc7201:two
struct S2 {
uint128 a;
}
}

contract ExampleV2_Ok {
/// @custom:storage-location erc7201:example.main
struct MainStorage {
uint256 x;
uint256 y;
uint256 z;
}

// keccak256(abi.encode(uint256(keccak256("example.main")) - 1)) & ~bytes32(uint256(0xff));
bytes32 private constant MAIN_STORAGE_LOCATION =
0x183a6125c38840424c4a85fa12bab2ab606c4b6d0e7cc73c0c06ba5300eab500;

function _getMainStorage() private pure returns (MainStorage storage $) {
assembly {
$.slot := MAIN_STORAGE_LOCATION
}
}

function _getXTimesYPlusZ() internal view returns (uint256) {
MainStorage storage $ = _getMainStorage();
return $.x * $.y + $.z;
}
}

contract ExampleV2_Bad {
/// @custom:storage-location erc7201:example.main
struct MainStorage {
uint256 y;
}

// keccak256(abi.encode(uint256(keccak256("example.main")) - 1)) & ~bytes32(uint256(0xff));
bytes32 private constant MAIN_STORAGE_LOCATION =
0x183a6125c38840424c4a85fa12bab2ab606c4b6d0e7cc73c0c06ba5300eab500;

function _getMainStorage() private pure returns (MainStorage storage $) {
assembly {
$.slot := MAIN_STORAGE_LOCATION
}
}

function _getYSquared() internal view returns (uint256) {
MainStorage storage $ = _getMainStorage();
return $.y * $.y;
}
}

contract RecursiveStruct {
struct MyStruct {
uint128 a;
uint256 b;
}

/// @custom:storage-location erc7201:example.main
struct MainStorage {
MyStruct s;
uint256 y;
}
}

contract RecursiveStructV2_Outer_Ok {
struct MyStruct {
uint128 a;
uint256 b;
}

/// @custom:storage-location erc7201:example.main
struct MainStorage {
MyStruct s;
uint256 y;
uint256 z;
}
}

contract RecursiveStructV2_Bad {
struct MyStruct {
uint128 a;
uint256 b;
uint256 c;
}

/// @custom:storage-location erc7201:example.main
struct MainStorage {
MyStruct s;
uint256 y;
}
}

contract MultipleNamespacesAndRegularVariables {
/// @custom:storage-location erc7201:one
struct S1 {
uint128 a;
uint256 b;
}

/// @custom:storage-location erc7201:two
struct S2 {
uint128 a;
uint256 b;
}

uint128 public a;
uint256 public b;
}

contract MultipleNamespacesAndRegularVariablesV2_Ok {
/// @custom:storage-location erc7201:one
struct S1 {
uint128 a;
uint256 b;
uint256 c;
}

/// @custom:storage-location erc7201:two
struct S2 {
uint128 a;
uint256 b;
uint256 c;
}

uint128 public a;
uint256 public b;
uint256 public c;
}

contract MultipleNamespacesAndRegularVariablesV2_Bad {
/// @custom:storage-location erc7201:one
struct S1 {
uint256 c;
uint128 a;
uint256 b;
}

/// @custom:storage-location erc7201:two
struct S2 {
uint256 c;
uint128 a;
uint256 b;
}

uint256 public c;
uint128 public a;
uint256 public b;
}
59 changes: 59 additions & 0 deletions packages/core/contracts/test/NamespacedConflicts.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract DuplicateNamespace {
function foo() public pure returns (uint256) {
return 0;
}

/// @custom:storage-location erc7201:conflicting
struct Conflicting1 {
uint256 b;
}

/// @custom:storage-location erc7201:conflicting
struct Conflicting2 {
uint256 c;
}

function foo2() public pure returns (uint256) {
return 0;
}
}

contract Parent {
function foo5() public pure returns (uint256) {
return 0;
}

/// @custom:storage-location erc7201:conflicting
struct Conflicting0 {
uint256 a;
}

function foo6() public pure returns (uint256) {
return 0;
}
}

contract ConflictsWithParent is Parent {
function foo3() public pure returns (uint256) {
return 0;
}

/// @custom:storage-location erc7201:conflicting
struct Conflicting {
uint256 a;
}

function foo4() public pure returns (uint256) {
return 0;
}
}

contract ConflictsInBothParents is DuplicateNamespace, ConflictsWithParent {
uint256 a;
}

contract InheritsDuplicate is DuplicateNamespace {
}
35 changes: 35 additions & 0 deletions packages/core/contracts/test/NamespacedConflictsLayout.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract DuplicateNamespace {
/// @custom:storage-location erc7201:conflicting
struct Conflicting1 {
uint256 b;
} Conflicting1 $Conflicting1;

/// @custom:storage-location erc7201:conflicting
struct Conflicting2 {
uint256 c;
} Conflicting2 $Conflicting2;
}

contract Parent {
/// @custom:storage-location erc7201:conflicting
struct Conflicting0 {
uint256 a;
} Conflicting0 $Conflicting0;
}

contract ConflictsWithParent is Parent {
/// @custom:storage-location erc7201:conflicting
struct Conflicting {
uint256 a;
} Conflicting $Conflicting;
}

contract ConflictsInBothParents is DuplicateNamespace, ConflictsWithParent {
uint256 a;
}

contract InheritsDuplicate is DuplicateNamespace {
}
16 changes: 16 additions & 0 deletions packages/core/contracts/test/NamespacedLayout.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract Example {
MainStorage $MainStorage;

/// @custom:storage-location erc7201:example.main
struct MainStorage {
uint256 x;
uint256 y;
}

// keccak256(abi.encode(uint256(keccak256("example.main")) - 1)) & ~bytes32(uint256(0xff));
bytes32 private constant MAIN_STORAGE_LOCATION =
0x183a6125c38840424c4a85fa12bab2ab606c4b6d0e7cc73c0c06ba5300eab500;
}
27 changes: 27 additions & 0 deletions packages/core/contracts/test/NamespacedOutsideContract.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

// This is not valid according to ERC-7201 because the namespaced struct is outside of a contract.

/// @custom:storage-location erc7201:example.main
struct MainStorage {
uint256 x;
uint256 y;
}

contract Example {
// keccak256(abi.encode(uint256(keccak256("example.main")) - 1)) & ~bytes32(uint256(0xff));
bytes32 private constant MAIN_STORAGE_LOCATION =
0x183a6125c38840424c4a85fa12bab2ab606c4b6d0e7cc73c0c06ba5300eab500;

function _getMainStorage() private pure returns (MainStorage storage $) {
assembly {
$.slot := MAIN_STORAGE_LOCATION
}
}

function _getXTimesY() internal view returns (uint256) {
MainStorage storage $ = _getMainStorage();
return $.x * $.y;
}
}
Loading