Skip to content

Commit cb2aaaa

Browse files
authored
Add slot derivation library (#4975)
1 parent 5e3ba29 commit cb2aaaa

File tree

18 files changed

+777
-63
lines changed

18 files changed

+777
-63
lines changed

.changeset/gorgeous-badgers-vanish.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': minor
3+
---
4+
5+
`SlotDerivation`: Add a library of methods for derivating common storage slots.

contracts/mocks/StorageSlotMock.sol

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ contract StorageSlotMock {
2323
slot.getUint256Slot().value = value;
2424
}
2525

26+
function setInt256Slot(bytes32 slot, int256 value) public {
27+
slot.getInt256Slot().value = value;
28+
}
29+
2630
function getBooleanSlot(bytes32 slot) public view returns (bool) {
2731
return slot.getBooleanSlot().value;
2832
}
@@ -39,6 +43,10 @@ contract StorageSlotMock {
3943
return slot.getUint256Slot().value;
4044
}
4145

46+
function getInt256Slot(bytes32 slot) public view returns (int256) {
47+
return slot.getInt256Slot().value;
48+
}
49+
4250
mapping(uint256 key => string) public stringMap;
4351

4452
function setStringSlot(bytes32 slot, string calldata value) public {

contracts/utils/Arrays.sol

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44

55
pragma solidity ^0.8.20;
66

7+
import {SlotDerivation} from "./SlotDerivation.sol";
78
import {StorageSlot} from "./StorageSlot.sol";
89
import {Math} from "./math/Math.sol";
910

1011
/**
1112
* @dev Collection of functions related to array types.
1213
*/
1314
library Arrays {
15+
using SlotDerivation for bytes32;
1416
using StorageSlot for bytes32;
1517

1618
/**
@@ -379,15 +381,11 @@ library Arrays {
379381
*/
380382
function unsafeAccess(address[] storage arr, uint256 pos) internal pure returns (StorageSlot.AddressSlot storage) {
381383
bytes32 slot;
382-
// We use assembly to calculate the storage slot of the element at index `pos` of the dynamic array `arr`
383-
// following https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_storage.html#mappings-and-dynamic-arrays.
384-
385384
/// @solidity memory-safe-assembly
386385
assembly {
387-
mstore(0, arr.slot)
388-
slot := add(keccak256(0, 0x20), pos)
386+
slot := arr.slot
389387
}
390-
return slot.getAddressSlot();
388+
return slot.deriveArray().offset(pos).getAddressSlot();
391389
}
392390

393391
/**
@@ -397,15 +395,11 @@ library Arrays {
397395
*/
398396
function unsafeAccess(bytes32[] storage arr, uint256 pos) internal pure returns (StorageSlot.Bytes32Slot storage) {
399397
bytes32 slot;
400-
// We use assembly to calculate the storage slot of the element at index `pos` of the dynamic array `arr`
401-
// following https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_storage.html#mappings-and-dynamic-arrays.
402-
403398
/// @solidity memory-safe-assembly
404399
assembly {
405-
mstore(0, arr.slot)
406-
slot := add(keccak256(0, 0x20), pos)
400+
slot := arr.slot
407401
}
408-
return slot.getBytes32Slot();
402+
return slot.deriveArray().offset(pos).getBytes32Slot();
409403
}
410404

411405
/**
@@ -415,15 +409,11 @@ library Arrays {
415409
*/
416410
function unsafeAccess(uint256[] storage arr, uint256 pos) internal pure returns (StorageSlot.Uint256Slot storage) {
417411
bytes32 slot;
418-
// We use assembly to calculate the storage slot of the element at index `pos` of the dynamic array `arr`
419-
// following https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_storage.html#mappings-and-dynamic-arrays.
420-
421412
/// @solidity memory-safe-assembly
422413
assembly {
423-
mstore(0, arr.slot)
424-
slot := add(keccak256(0, 0x20), pos)
414+
slot := arr.slot
425415
}
426-
return slot.getUint256Slot();
416+
return slot.deriveArray().offset(pos).getUint256Slot();
427417
}
428418

429419
/**

contracts/utils/README.adoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t
2828
* {Base64}: On-chain base64 and base64URL encoding according to https://datatracker.ietf.org/doc/html/rfc4648[RFC-4648].
2929
* {Strings}: Common operations for strings formatting.
3030
* {ShortString}: Library to encode (and decode) short strings into (or from) a single bytes32 slot for optimizing costs. Short strings are limited to 31 characters.
31+
* {SlotDerivation}: Methods for deriving storage slot from ERC-7201 namespaces as well as from constructions such as mapping and arrays.
3132
* {StorageSlot}: Methods for accessing specific storage slots formatted as common primitive types.
3233
* {Multicall}: Abstract contract with an utility to allow batching together multiple calls in a single transaction. Useful for allowing EOAs to perform multiple operations at once.
3334
* {Context}: An utility for abstracting the sender and calldata in the current execution context.
@@ -108,6 +109,8 @@ Ethereum contracts have no native concept of an interface, so applications must
108109

109110
{{ShortStrings}}
110111

112+
{{SlotDerivation}}
113+
111114
{{StorageSlot}}
112115

113116
{{Multicall}}

contracts/utils/SlotDerivation.sol

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// SPDX-License-Identifier: MIT
2+
// This file was procedurally generated from scripts/generate/templates/SlotDerivation.js.
3+
4+
pragma solidity ^0.8.20;
5+
6+
/**
7+
* @dev Library for computing storage (and transient storage) locations from namespaces and deriving slots
8+
* corresponding to standard patterns. The derivation method for array and mapping matches the storage layout used by
9+
* the solidity language / compiler.
10+
*
11+
* See https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_storage.html#mappings-and-dynamic-arrays[Solidity docs for mappings and dynamic arrays.].
12+
*
13+
* Example usage:
14+
* ```solidity
15+
* contract Example {
16+
* // Add the library methods
17+
* using StorageSlot for bytes32;
18+
* using SlotDerivation for bytes32;
19+
*
20+
* // Declare a namespace
21+
* string private constant _NAMESPACE = "<namespace>" // eg. OpenZeppelin.Slot
22+
*
23+
* function setValueInNamespace(uint256 key, address newValue) internal {
24+
* _NAMESPACE.erc7201Slot().deriveMapping(key).getAddressSlot().value = newValue;
25+
* }
26+
*
27+
* function getValueInNamespace(uint256 key) internal view returns (address) {
28+
* return _NAMESPACE.erc7201Slot().deriveMapping(key).getAddressSlot().value;
29+
* }
30+
* }
31+
* ```
32+
*
33+
* TIP: Consider using this library along with {StorageSlot}.
34+
*
35+
* NOTE: This library provides a way to manipulate storage locations in a non-standard way. Tooling for checking
36+
* upgrade safety will ignore the slots accessed through this library.
37+
*/
38+
library SlotDerivation {
39+
/**
40+
* @dev Derive an ERC-7201 slot from a string (namespace).
41+
*/
42+
function erc7201Slot(string memory namespace) internal pure returns (bytes32 slot) {
43+
/// @solidity memory-safe-assembly
44+
assembly {
45+
mstore(0x00, sub(keccak256(add(namespace, 0x20), mload(namespace)), 1))
46+
slot := and(keccak256(0x00, 0x20), not(0xff))
47+
}
48+
}
49+
50+
/**
51+
* @dev Add an offset to a slot to get the n-th element of a structure or an array.
52+
*/
53+
function offset(bytes32 slot, uint256 pos) internal pure returns (bytes32 result) {
54+
unchecked {
55+
return bytes32(uint256(slot) + pos);
56+
}
57+
}
58+
59+
/**
60+
* @dev Derive the location of the first element in an array from the slot where the length is stored.
61+
*/
62+
function deriveArray(bytes32 slot) internal pure returns (bytes32 result) {
63+
/// @solidity memory-safe-assembly
64+
assembly {
65+
mstore(0x00, slot)
66+
result := keccak256(0x00, 0x20)
67+
}
68+
}
69+
70+
/**
71+
* @dev Derive the location of a mapping element from the key.
72+
*/
73+
function deriveMapping(bytes32 slot, address key) internal pure returns (bytes32 result) {
74+
/// @solidity memory-safe-assembly
75+
assembly {
76+
mstore(0x00, key)
77+
mstore(0x20, slot)
78+
result := keccak256(0x00, 0x40)
79+
}
80+
}
81+
82+
/**
83+
* @dev Derive the location of a mapping element from the key.
84+
*/
85+
function deriveMapping(bytes32 slot, bool key) internal pure returns (bytes32 result) {
86+
/// @solidity memory-safe-assembly
87+
assembly {
88+
mstore(0x00, key)
89+
mstore(0x20, slot)
90+
result := keccak256(0x00, 0x40)
91+
}
92+
}
93+
94+
/**
95+
* @dev Derive the location of a mapping element from the key.
96+
*/
97+
function deriveMapping(bytes32 slot, bytes32 key) internal pure returns (bytes32 result) {
98+
/// @solidity memory-safe-assembly
99+
assembly {
100+
mstore(0x00, key)
101+
mstore(0x20, slot)
102+
result := keccak256(0x00, 0x40)
103+
}
104+
}
105+
106+
/**
107+
* @dev Derive the location of a mapping element from the key.
108+
*/
109+
function deriveMapping(bytes32 slot, uint256 key) internal pure returns (bytes32 result) {
110+
/// @solidity memory-safe-assembly
111+
assembly {
112+
mstore(0x00, key)
113+
mstore(0x20, slot)
114+
result := keccak256(0x00, 0x40)
115+
}
116+
}
117+
118+
/**
119+
* @dev Derive the location of a mapping element from the key.
120+
*/
121+
function deriveMapping(bytes32 slot, int256 key) internal pure returns (bytes32 result) {
122+
/// @solidity memory-safe-assembly
123+
assembly {
124+
mstore(0x00, key)
125+
mstore(0x20, slot)
126+
result := keccak256(0x00, 0x40)
127+
}
128+
}
129+
130+
/**
131+
* @dev Derive the location of a mapping element from the key.
132+
*/
133+
function deriveMapping(bytes32 slot, string memory key) internal pure returns (bytes32 result) {
134+
/// @solidity memory-safe-assembly
135+
assembly {
136+
let length := mload(key)
137+
let begin := add(key, 0x20)
138+
let end := add(begin, length)
139+
let cache := mload(end)
140+
mstore(end, slot)
141+
result := keccak256(begin, add(length, 0x20))
142+
mstore(end, cache)
143+
}
144+
}
145+
146+
/**
147+
* @dev Derive the location of a mapping element from the key.
148+
*/
149+
function deriveMapping(bytes32 slot, bytes memory key) internal pure returns (bytes32 result) {
150+
/// @solidity memory-safe-assembly
151+
assembly {
152+
let length := mload(key)
153+
let begin := add(key, 0x20)
154+
let end := add(begin, length)
155+
let cache := mload(end)
156+
mstore(end, slot)
157+
result := keccak256(begin, add(length, 0x20))
158+
mstore(end, cache)
159+
}
160+
}
161+
}

contracts/utils/StorageSlot.sol

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pragma solidity ^0.8.20;
1515
* Example usage to set ERC-1967 implementation slot:
1616
* ```solidity
1717
* contract ERC1967 {
18+
* // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.
1819
* bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
1920
*
2021
* function _getImplementation() internal view returns (address) {
@@ -27,6 +28,8 @@ pragma solidity ^0.8.20;
2728
* }
2829
* }
2930
* ```
31+
*
32+
* TIP: Consider using this library along with {SlotDerivation}.
3033
*/
3134
library StorageSlot {
3235
struct AddressSlot {
@@ -45,6 +48,10 @@ library StorageSlot {
4548
uint256 value;
4649
}
4750

51+
struct Int256Slot {
52+
int256 value;
53+
}
54+
4855
struct StringSlot {
4956
string value;
5057
}
@@ -93,6 +100,16 @@ library StorageSlot {
93100
}
94101
}
95102

103+
/**
104+
* @dev Returns an `Int256Slot` with member `value` located at `slot`.
105+
*/
106+
function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) {
107+
/// @solidity memory-safe-assembly
108+
assembly {
109+
r.slot := slot
110+
}
111+
}
112+
96113
/**
97114
* @dev Returns an `StringSlot` with member `value` located at `slot`.
98115
*/

0 commit comments

Comments
 (0)