Skip to content

Commit d6ad9db

Browse files
Amxxernestognw
andauthored
Add transient storage slot support in StorageSlot.sol (#4980)
Co-authored-by: ernestognw <[email protected]>
1 parent 2d259ac commit d6ad9db

File tree

17 files changed

+1920
-1225
lines changed

17 files changed

+1920
-1225
lines changed

.changeset/kind-planets-cough.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+
`StorageSlot`: Add primitives for operating on the transient storage space using a typed-slot representation.

.github/workflows/checks.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ concurrency:
1414
cancel-in-progress: true
1515

1616
env:
17-
NODE_OPTIONS: --max_old_space_size=5120
17+
NODE_OPTIONS: --max_old_space_size=8192
1818

1919
jobs:
2020
lint:

contracts/mocks/StorageSlotMock.sol

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
// SPDX-License-Identifier: MIT
2+
// This file was procedurally generated from scripts/generate/templates/StorageSlotMock.js.
23

3-
pragma solidity ^0.8.20;
4+
pragma solidity ^0.8.24;
45

6+
import {Multicall} from "../utils/Multicall.sol";
57
import {StorageSlot} from "../utils/StorageSlot.sol";
68

7-
contract StorageSlotMock {
9+
contract StorageSlotMock is Multicall {
810
using StorageSlot for *;
911

10-
function setBooleanSlot(bytes32 slot, bool value) public {
11-
slot.getBooleanSlot().value = value;
12-
}
13-
1412
function setAddressSlot(bytes32 slot, address value) public {
1513
slot.getAddressSlot().value = value;
1614
}
1715

16+
function setBooleanSlot(bytes32 slot, bool value) public {
17+
slot.getBooleanSlot().value = value;
18+
}
19+
1820
function setBytes32Slot(bytes32 slot, bytes32 value) public {
1921
slot.getBytes32Slot().value = value;
2022
}
@@ -27,14 +29,14 @@ contract StorageSlotMock {
2729
slot.getInt256Slot().value = value;
2830
}
2931

30-
function getBooleanSlot(bytes32 slot) public view returns (bool) {
31-
return slot.getBooleanSlot().value;
32-
}
33-
3432
function getAddressSlot(bytes32 slot) public view returns (address) {
3533
return slot.getAddressSlot().value;
3634
}
3735

36+
function getBooleanSlot(bytes32 slot) public view returns (bool) {
37+
return slot.getBooleanSlot().value;
38+
}
39+
3840
function getBytes32Slot(bytes32 slot) public view returns (bytes32) {
3941
return slot.getBytes32Slot().value;
4042
}
@@ -82,4 +84,54 @@ contract StorageSlotMock {
8284
function getBytesStorage(uint256 key) public view returns (bytes memory) {
8385
return bytesMap[key].getBytesSlot().value;
8486
}
87+
88+
event AddressValue(bytes32 slot, address value);
89+
90+
function tloadAddress(bytes32 slot) public {
91+
emit AddressValue(slot, slot.asAddress().tload());
92+
}
93+
94+
function tstore(bytes32 slot, address value) public {
95+
slot.asAddress().tstore(value);
96+
}
97+
98+
event BooleanValue(bytes32 slot, bool value);
99+
100+
function tloadBoolean(bytes32 slot) public {
101+
emit BooleanValue(slot, slot.asBoolean().tload());
102+
}
103+
104+
function tstore(bytes32 slot, bool value) public {
105+
slot.asBoolean().tstore(value);
106+
}
107+
108+
event Bytes32Value(bytes32 slot, bytes32 value);
109+
110+
function tloadBytes32(bytes32 slot) public {
111+
emit Bytes32Value(slot, slot.asBytes32().tload());
112+
}
113+
114+
function tstore(bytes32 slot, bytes32 value) public {
115+
slot.asBytes32().tstore(value);
116+
}
117+
118+
event Uint256Value(bytes32 slot, uint256 value);
119+
120+
function tloadUint256(bytes32 slot) public {
121+
emit Uint256Value(slot, slot.asUint256().tload());
122+
}
123+
124+
function tstore(bytes32 slot, uint256 value) public {
125+
slot.asUint256().tstore(value);
126+
}
127+
128+
event Int256Value(bytes32 slot, int256 value);
129+
130+
function tloadInt256(bytes32 slot) public {
131+
emit Int256Value(slot, slot.asInt256().tload());
132+
}
133+
134+
function tstore(bytes32 slot, int256 value) public {
135+
slot.asInt256().tstore(value);
136+
}
85137
}

contracts/utils/README.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t
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.
3131
* {SlotDerivation}: Methods for deriving storage slot from ERC-7201 namespaces as well as from constructions such as mapping and arrays.
32-
* {StorageSlot}: Methods for accessing specific storage slots formatted as common primitive types.
32+
* {StorageSlot}: Methods for accessing specific storage slots formatted as common primitive types. Also include primitives for reading from and writing to transient storage (only value types are currently supported).
3333
* {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.
3434
* {Context}: An utility for abstracting the sender and calldata in the current execution context.
3535
* {Panic}: A library to revert with https://docs.soliditylang.org/en/v0.8.20/control-structures.html#panic-via-assert-and-error-via-require[Solidity panic codes].

contracts/utils/StorageSlot.sol

Lines changed: 179 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// OpenZeppelin Contracts (last updated v5.0.0) (utils/StorageSlot.sol)
33
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
44

5-
pragma solidity ^0.8.20;
5+
pragma solidity ^0.8.24;
66

77
/**
88
* @dev Library for reading and writing primitive types to specific storage slots.
@@ -29,6 +29,24 @@ pragma solidity ^0.8.20;
2929
* }
3030
* ```
3131
*
32+
* Since version 5.1, this library also support writing and reading value types to and from transient storage.
33+
*
34+
* * Example using transient storage:
35+
* ```solidity
36+
* contract Lock {
37+
* // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.
38+
* bytes32 internal constant _LOCK_SLOT = 0xf4678858b2b588224636b8522b729e7722d32fc491da849ed75b3fdf3c84f542;
39+
*
40+
* modifier locked() {
41+
* require(!_LOCK_SLOT.asBoolean().tload());
42+
*
43+
* _LOCK_SLOT.asBoolean().tstore(true);
44+
* _;
45+
* _LOCK_SLOT.asBoolean().tstore(false);
46+
* }
47+
* }
48+
* ```
49+
*
3250
* TIP: Consider using this library along with {SlotDerivation}.
3351
*/
3452
library StorageSlot {
@@ -149,4 +167,164 @@ library StorageSlot {
149167
r.slot := store.slot
150168
}
151169
}
170+
171+
/**
172+
* @dev UDVT that represent a slot holding a address.
173+
*/
174+
type AddressSlotType is bytes32;
175+
176+
/**
177+
* @dev Cast an arbitrary slot to a AddressSlotType.
178+
*/
179+
function asAddress(bytes32 slot) internal pure returns (AddressSlotType) {
180+
return AddressSlotType.wrap(slot);
181+
}
182+
183+
/**
184+
* @dev UDVT that represent a slot holding a bool.
185+
*/
186+
type BooleanSlotType is bytes32;
187+
188+
/**
189+
* @dev Cast an arbitrary slot to a BooleanSlotType.
190+
*/
191+
function asBoolean(bytes32 slot) internal pure returns (BooleanSlotType) {
192+
return BooleanSlotType.wrap(slot);
193+
}
194+
195+
/**
196+
* @dev UDVT that represent a slot holding a bytes32.
197+
*/
198+
type Bytes32SlotType is bytes32;
199+
200+
/**
201+
* @dev Cast an arbitrary slot to a Bytes32SlotType.
202+
*/
203+
function asBytes32(bytes32 slot) internal pure returns (Bytes32SlotType) {
204+
return Bytes32SlotType.wrap(slot);
205+
}
206+
207+
/**
208+
* @dev UDVT that represent a slot holding a uint256.
209+
*/
210+
type Uint256SlotType is bytes32;
211+
212+
/**
213+
* @dev Cast an arbitrary slot to a Uint256SlotType.
214+
*/
215+
function asUint256(bytes32 slot) internal pure returns (Uint256SlotType) {
216+
return Uint256SlotType.wrap(slot);
217+
}
218+
219+
/**
220+
* @dev UDVT that represent a slot holding a int256.
221+
*/
222+
type Int256SlotType is bytes32;
223+
224+
/**
225+
* @dev Cast an arbitrary slot to a Int256SlotType.
226+
*/
227+
function asInt256(bytes32 slot) internal pure returns (Int256SlotType) {
228+
return Int256SlotType.wrap(slot);
229+
}
230+
231+
/**
232+
* @dev Load the value held at location `slot` in transient storage.
233+
*/
234+
function tload(AddressSlotType slot) internal view returns (address value) {
235+
/// @solidity memory-safe-assembly
236+
assembly {
237+
value := tload(slot)
238+
}
239+
}
240+
241+
/**
242+
* @dev Store `value` at location `slot` in transient storage.
243+
*/
244+
function tstore(AddressSlotType slot, address value) internal {
245+
/// @solidity memory-safe-assembly
246+
assembly {
247+
tstore(slot, value)
248+
}
249+
}
250+
251+
/**
252+
* @dev Load the value held at location `slot` in transient storage.
253+
*/
254+
function tload(BooleanSlotType slot) internal view returns (bool value) {
255+
/// @solidity memory-safe-assembly
256+
assembly {
257+
value := tload(slot)
258+
}
259+
}
260+
261+
/**
262+
* @dev Store `value` at location `slot` in transient storage.
263+
*/
264+
function tstore(BooleanSlotType slot, bool value) internal {
265+
/// @solidity memory-safe-assembly
266+
assembly {
267+
tstore(slot, value)
268+
}
269+
}
270+
271+
/**
272+
* @dev Load the value held at location `slot` in transient storage.
273+
*/
274+
function tload(Bytes32SlotType slot) internal view returns (bytes32 value) {
275+
/// @solidity memory-safe-assembly
276+
assembly {
277+
value := tload(slot)
278+
}
279+
}
280+
281+
/**
282+
* @dev Store `value` at location `slot` in transient storage.
283+
*/
284+
function tstore(Bytes32SlotType slot, bytes32 value) internal {
285+
/// @solidity memory-safe-assembly
286+
assembly {
287+
tstore(slot, value)
288+
}
289+
}
290+
291+
/**
292+
* @dev Load the value held at location `slot` in transient storage.
293+
*/
294+
function tload(Uint256SlotType slot) internal view returns (uint256 value) {
295+
/// @solidity memory-safe-assembly
296+
assembly {
297+
value := tload(slot)
298+
}
299+
}
300+
301+
/**
302+
* @dev Store `value` at location `slot` in transient storage.
303+
*/
304+
function tstore(Uint256SlotType slot, uint256 value) internal {
305+
/// @solidity memory-safe-assembly
306+
assembly {
307+
tstore(slot, value)
308+
}
309+
}
310+
311+
/**
312+
* @dev Load the value held at location `slot` in transient storage.
313+
*/
314+
function tload(Int256SlotType slot) internal view returns (int256 value) {
315+
/// @solidity memory-safe-assembly
316+
assembly {
317+
value := tload(slot)
318+
}
319+
}
320+
321+
/**
322+
* @dev Store `value` at location `slot` in transient storage.
323+
*/
324+
function tstore(Int256SlotType slot, int256 value) internal {
325+
/// @solidity memory-safe-assembly
326+
assembly {
327+
tstore(slot, value)
328+
}
329+
}
152330
}

docs/modules/ROOT/pages/utilities.adoc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,21 @@ function _setImplementation(address newImplementation) internal {
166166
}
167167
----
168168

169+
The xref:api:utils.adoc#StorageSlot[`StorageSlot`] library also supports transient storage through user defined value types (UDVTs[https://docs.soliditylang.org/en/latest/types.html#user-defined-value-types]), which enables the same value types as in Solidity.
170+
171+
[source,solidity]
172+
----
173+
bytes32 internal constant _LOCK_SLOT = 0xf4678858b2b588224636b8522b729e7722d32fc491da849ed75b3fdf3c84f542;
174+
175+
function _getTransientLock() internal view returns (bool) {
176+
return _LOCK_SLOT.asBoolean().tload();
177+
}
178+
179+
function _setTransientLock(bool lock) internal {
180+
_LOCK_SLOT.asBoolean().tstore(lock);
181+
}
182+
----
183+
169184
WARNING: Manipulating storage slots directly is an advanced practice. Developers MUST make sure that the storage pointer is not colliding with other variables.
170185

171186
One of the most common use cases for writing directly to storage slots is ERC-7201 for namespaced storage, which is guaranteed to not collide with other storage slots derived by Solidity.

foundry.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
[profile.default]
2+
solc_version = '0.8.24'
3+
evm_version = 'cancun'
24
src = 'contracts'
35
out = 'out'
46
libs = ['node_modules', 'lib']

hardhat.config.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const { argv } = require('yargs/yargs')()
1818
compiler: {
1919
alias: 'compileVersion',
2020
type: 'string',
21-
default: '0.8.20',
21+
default: '0.8.24',
2222
},
2323
src: {
2424
alias: 'source',
@@ -36,6 +36,11 @@ const { argv } = require('yargs/yargs')()
3636
type: 'boolean',
3737
default: false,
3838
},
39+
evm: {
40+
alias: 'evmVersion',
41+
type: 'string',
42+
default: 'cancun',
43+
},
3944
// Extra modules
4045
coverage: {
4146
type: 'boolean',
@@ -78,6 +83,7 @@ module.exports = {
7883
enabled: withOptimizations,
7984
runs: 200,
8085
},
86+
evmVersion: argv.evm,
8187
viaIR: withOptimizations && argv.ir,
8288
outputSelection: { '*': { '*': ['storageLayout'] } },
8389
},
@@ -90,11 +96,13 @@ module.exports = {
9096
'*': {
9197
'code-size': withOptimizations,
9298
'unused-param': !argv.coverage, // coverage causes unused-param warnings
99+
'transient-storage': false,
93100
default: 'error',
94101
},
95102
},
96103
networks: {
97104
hardhat: {
105+
hardfork: argv.evm,
98106
allowUnlimitedContractSize,
99107
initialBaseFeePerGas: argv.coverage ? 0 : undefined,
100108
},

0 commit comments

Comments
 (0)