Skip to content

Commit

Permalink
Add transient storage slot support in StorageSlot.sol (#4980)
Browse files Browse the repository at this point in the history
Co-authored-by: ernestognw <[email protected]>
  • Loading branch information
Amxx and ernestognw authored Apr 3, 2024
1 parent 2d259ac commit d6ad9db
Show file tree
Hide file tree
Showing 17 changed files with 1,920 additions and 1,225 deletions.
5 changes: 5 additions & 0 deletions .changeset/kind-planets-cough.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`StorageSlot`: Add primitives for operating on the transient storage space using a typed-slot representation.
2 changes: 1 addition & 1 deletion .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ concurrency:
cancel-in-progress: true

env:
NODE_OPTIONS: --max_old_space_size=5120
NODE_OPTIONS: --max_old_space_size=8192

jobs:
lint:
Expand Down
72 changes: 62 additions & 10 deletions contracts/mocks/StorageSlotMock.sol
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
// SPDX-License-Identifier: MIT
// This file was procedurally generated from scripts/generate/templates/StorageSlotMock.js.

pragma solidity ^0.8.20;
pragma solidity ^0.8.24;

import {Multicall} from "../utils/Multicall.sol";
import {StorageSlot} from "../utils/StorageSlot.sol";

contract StorageSlotMock {
contract StorageSlotMock is Multicall {
using StorageSlot for *;

function setBooleanSlot(bytes32 slot, bool value) public {
slot.getBooleanSlot().value = value;
}

function setAddressSlot(bytes32 slot, address value) public {
slot.getAddressSlot().value = value;
}

function setBooleanSlot(bytes32 slot, bool value) public {
slot.getBooleanSlot().value = value;
}

function setBytes32Slot(bytes32 slot, bytes32 value) public {
slot.getBytes32Slot().value = value;
}
Expand All @@ -27,14 +29,14 @@ contract StorageSlotMock {
slot.getInt256Slot().value = value;
}

function getBooleanSlot(bytes32 slot) public view returns (bool) {
return slot.getBooleanSlot().value;
}

function getAddressSlot(bytes32 slot) public view returns (address) {
return slot.getAddressSlot().value;
}

function getBooleanSlot(bytes32 slot) public view returns (bool) {
return slot.getBooleanSlot().value;
}

function getBytes32Slot(bytes32 slot) public view returns (bytes32) {
return slot.getBytes32Slot().value;
}
Expand Down Expand Up @@ -82,4 +84,54 @@ contract StorageSlotMock {
function getBytesStorage(uint256 key) public view returns (bytes memory) {
return bytesMap[key].getBytesSlot().value;
}

event AddressValue(bytes32 slot, address value);

function tloadAddress(bytes32 slot) public {
emit AddressValue(slot, slot.asAddress().tload());
}

function tstore(bytes32 slot, address value) public {
slot.asAddress().tstore(value);
}

event BooleanValue(bytes32 slot, bool value);

function tloadBoolean(bytes32 slot) public {
emit BooleanValue(slot, slot.asBoolean().tload());
}

function tstore(bytes32 slot, bool value) public {
slot.asBoolean().tstore(value);
}

event Bytes32Value(bytes32 slot, bytes32 value);

function tloadBytes32(bytes32 slot) public {
emit Bytes32Value(slot, slot.asBytes32().tload());
}

function tstore(bytes32 slot, bytes32 value) public {
slot.asBytes32().tstore(value);
}

event Uint256Value(bytes32 slot, uint256 value);

function tloadUint256(bytes32 slot) public {
emit Uint256Value(slot, slot.asUint256().tload());
}

function tstore(bytes32 slot, uint256 value) public {
slot.asUint256().tstore(value);
}

event Int256Value(bytes32 slot, int256 value);

function tloadInt256(bytes32 slot) public {
emit Int256Value(slot, slot.asInt256().tload());
}

function tstore(bytes32 slot, int256 value) public {
slot.asInt256().tstore(value);
}
}
2 changes: 1 addition & 1 deletion contracts/utils/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t
* {Strings}: Common operations for strings formatting.
* {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.
* {SlotDerivation}: Methods for deriving storage slot from ERC-7201 namespaces as well as from constructions such as mapping and arrays.
* {StorageSlot}: Methods for accessing specific storage slots formatted as common primitive types.
* {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).
* {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.
* {Context}: An utility for abstracting the sender and calldata in the current execution context.
* {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].
Expand Down
180 changes: 179 additions & 1 deletion contracts/utils/StorageSlot.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// OpenZeppelin Contracts (last updated v5.0.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.

pragma solidity ^0.8.20;
pragma solidity ^0.8.24;

/**
* @dev Library for reading and writing primitive types to specific storage slots.
Expand All @@ -29,6 +29,24 @@ pragma solidity ^0.8.20;
* }
* ```
*
* Since version 5.1, this library also support writing and reading value types to and from transient storage.
*
* * Example using transient storage:
* ```solidity
* contract Lock {
* // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.
* bytes32 internal constant _LOCK_SLOT = 0xf4678858b2b588224636b8522b729e7722d32fc491da849ed75b3fdf3c84f542;
*
* modifier locked() {
* require(!_LOCK_SLOT.asBoolean().tload());
*
* _LOCK_SLOT.asBoolean().tstore(true);
* _;
* _LOCK_SLOT.asBoolean().tstore(false);
* }
* }
* ```
*
* TIP: Consider using this library along with {SlotDerivation}.
*/
library StorageSlot {
Expand Down Expand Up @@ -149,4 +167,164 @@ library StorageSlot {
r.slot := store.slot
}
}

/**
* @dev UDVT that represent a slot holding a address.
*/
type AddressSlotType is bytes32;

/**
* @dev Cast an arbitrary slot to a AddressSlotType.
*/
function asAddress(bytes32 slot) internal pure returns (AddressSlotType) {
return AddressSlotType.wrap(slot);
}

/**
* @dev UDVT that represent a slot holding a bool.
*/
type BooleanSlotType is bytes32;

/**
* @dev Cast an arbitrary slot to a BooleanSlotType.
*/
function asBoolean(bytes32 slot) internal pure returns (BooleanSlotType) {
return BooleanSlotType.wrap(slot);
}

/**
* @dev UDVT that represent a slot holding a bytes32.
*/
type Bytes32SlotType is bytes32;

/**
* @dev Cast an arbitrary slot to a Bytes32SlotType.
*/
function asBytes32(bytes32 slot) internal pure returns (Bytes32SlotType) {
return Bytes32SlotType.wrap(slot);
}

/**
* @dev UDVT that represent a slot holding a uint256.
*/
type Uint256SlotType is bytes32;

/**
* @dev Cast an arbitrary slot to a Uint256SlotType.
*/
function asUint256(bytes32 slot) internal pure returns (Uint256SlotType) {
return Uint256SlotType.wrap(slot);
}

/**
* @dev UDVT that represent a slot holding a int256.
*/
type Int256SlotType is bytes32;

/**
* @dev Cast an arbitrary slot to a Int256SlotType.
*/
function asInt256(bytes32 slot) internal pure returns (Int256SlotType) {
return Int256SlotType.wrap(slot);
}

/**
* @dev Load the value held at location `slot` in transient storage.
*/
function tload(AddressSlotType slot) internal view returns (address value) {
/// @solidity memory-safe-assembly
assembly {
value := tload(slot)
}
}

/**
* @dev Store `value` at location `slot` in transient storage.
*/
function tstore(AddressSlotType slot, address value) internal {
/// @solidity memory-safe-assembly
assembly {
tstore(slot, value)
}
}

/**
* @dev Load the value held at location `slot` in transient storage.
*/
function tload(BooleanSlotType slot) internal view returns (bool value) {
/// @solidity memory-safe-assembly
assembly {
value := tload(slot)
}
}

/**
* @dev Store `value` at location `slot` in transient storage.
*/
function tstore(BooleanSlotType slot, bool value) internal {
/// @solidity memory-safe-assembly
assembly {
tstore(slot, value)
}
}

/**
* @dev Load the value held at location `slot` in transient storage.
*/
function tload(Bytes32SlotType slot) internal view returns (bytes32 value) {
/// @solidity memory-safe-assembly
assembly {
value := tload(slot)
}
}

/**
* @dev Store `value` at location `slot` in transient storage.
*/
function tstore(Bytes32SlotType slot, bytes32 value) internal {
/// @solidity memory-safe-assembly
assembly {
tstore(slot, value)
}
}

/**
* @dev Load the value held at location `slot` in transient storage.
*/
function tload(Uint256SlotType slot) internal view returns (uint256 value) {
/// @solidity memory-safe-assembly
assembly {
value := tload(slot)
}
}

/**
* @dev Store `value` at location `slot` in transient storage.
*/
function tstore(Uint256SlotType slot, uint256 value) internal {
/// @solidity memory-safe-assembly
assembly {
tstore(slot, value)
}
}

/**
* @dev Load the value held at location `slot` in transient storage.
*/
function tload(Int256SlotType slot) internal view returns (int256 value) {
/// @solidity memory-safe-assembly
assembly {
value := tload(slot)
}
}

/**
* @dev Store `value` at location `slot` in transient storage.
*/
function tstore(Int256SlotType slot, int256 value) internal {
/// @solidity memory-safe-assembly
assembly {
tstore(slot, value)
}
}
}
15 changes: 15 additions & 0 deletions docs/modules/ROOT/pages/utilities.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,21 @@ function _setImplementation(address newImplementation) internal {
}
----

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.

[source,solidity]
----
bytes32 internal constant _LOCK_SLOT = 0xf4678858b2b588224636b8522b729e7722d32fc491da849ed75b3fdf3c84f542;
function _getTransientLock() internal view returns (bool) {
return _LOCK_SLOT.asBoolean().tload();
}
function _setTransientLock(bool lock) internal {
_LOCK_SLOT.asBoolean().tstore(lock);
}
----

WARNING: Manipulating storage slots directly is an advanced practice. Developers MUST make sure that the storage pointer is not colliding with other variables.

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.
Expand Down
2 changes: 2 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
[profile.default]
solc_version = '0.8.24'
evm_version = 'cancun'
src = 'contracts'
out = 'out'
libs = ['node_modules', 'lib']
Expand Down
10 changes: 9 additions & 1 deletion hardhat.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const { argv } = require('yargs/yargs')()
compiler: {
alias: 'compileVersion',
type: 'string',
default: '0.8.20',
default: '0.8.24',
},
src: {
alias: 'source',
Expand All @@ -36,6 +36,11 @@ const { argv } = require('yargs/yargs')()
type: 'boolean',
default: false,
},
evm: {
alias: 'evmVersion',
type: 'string',
default: 'cancun',
},
// Extra modules
coverage: {
type: 'boolean',
Expand Down Expand Up @@ -78,6 +83,7 @@ module.exports = {
enabled: withOptimizations,
runs: 200,
},
evmVersion: argv.evm,
viaIR: withOptimizations && argv.ir,
outputSelection: { '*': { '*': ['storageLayout'] } },
},
Expand All @@ -90,11 +96,13 @@ module.exports = {
'*': {
'code-size': withOptimizations,
'unused-param': !argv.coverage, // coverage causes unused-param warnings
'transient-storage': false,
default: 'error',
},
},
networks: {
hardhat: {
hardfork: argv.evm,
allowUnlimitedContractSize,
initialBaseFeePerGas: argv.coverage ? 0 : undefined,
},
Expand Down
Loading

0 comments on commit d6ad9db

Please sign in to comment.