Split StorageSlot into TransientSlot (#5239)
Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com> Signed-off-by: Hadrien Croubois <hadrien.croubois@gmail.com>
This commit is contained in:
committed by
Hadrien Croubois
parent
f8432e82d6
commit
1bcd1c65db
@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// This file was procedurally generated from scripts/generate/templates/StorageSlotMock.js.
|
||||
|
||||
pragma solidity ^0.8.24;
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Multicall} from "../utils/Multicall.sol";
|
||||
import {StorageSlot} from "../utils/StorageSlot.sol";
|
||||
@ -84,54 +84,4 @@ contract StorageSlotMock is Multicall {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
61
contracts/mocks/TransientSlotMock.sol
Normal file
61
contracts/mocks/TransientSlotMock.sol
Normal file
@ -0,0 +1,61 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// This file was procedurally generated from scripts/generate/templates/TransientSlotMock.js.
|
||||
|
||||
pragma solidity ^0.8.24;
|
||||
|
||||
import {Multicall} from "../utils/Multicall.sol";
|
||||
import {TransientSlot} from "../utils/TransientSlot.sol";
|
||||
|
||||
contract TransientSlotMock is Multicall {
|
||||
using TransientSlot for *;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -7,7 +7,7 @@ import {IERC20, ERC20} from "../ERC20.sol";
|
||||
import {IERC7674} from "../../../interfaces/draft-IERC7674.sol";
|
||||
import {Math} from "../../../utils/math/Math.sol";
|
||||
import {SlotDerivation} from "../../../utils/SlotDerivation.sol";
|
||||
import {StorageSlot} from "../../../utils/StorageSlot.sol";
|
||||
import {TransientSlot} from "../../../utils/TransientSlot.sol";
|
||||
|
||||
/**
|
||||
* @dev Extension of {ERC20} that adds support for temporary allowances following ERC-7674.
|
||||
@ -18,8 +18,8 @@ import {StorageSlot} from "../../../utils/StorageSlot.sol";
|
||||
*/
|
||||
abstract contract ERC20TemporaryApproval is ERC20, IERC7674 {
|
||||
using SlotDerivation for bytes32;
|
||||
using StorageSlot for bytes32;
|
||||
using StorageSlot for StorageSlot.Uint256SlotType;
|
||||
using TransientSlot for bytes32;
|
||||
using TransientSlot for TransientSlot.Uint256Slot;
|
||||
|
||||
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC20_TEMPORARY_APPROVAL_STORAGE")) - 1)) & ~bytes32(uint256(0xff))
|
||||
bytes32 private constant ERC20_TEMPORARY_APPROVAL_STORAGE =
|
||||
@ -113,10 +113,7 @@ abstract contract ERC20TemporaryApproval is ERC20, IERC7674 {
|
||||
}
|
||||
}
|
||||
|
||||
function _temporaryAllowanceSlot(
|
||||
address owner,
|
||||
address spender
|
||||
) private pure returns (StorageSlot.Uint256SlotType) {
|
||||
function _temporaryAllowanceSlot(address owner, address spender) private pure returns (TransientSlot.Uint256Slot) {
|
||||
return ERC20_TEMPORARY_APPROVAL_STORAGE.deriveMapping(owner).deriveMapping(spender).asUint256();
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,7 +34,8 @@ 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. Also include primitives for reading from and writing to transient storage (only value types are currently supported).
|
||||
* {StorageSlot}: Methods for accessing specific storage slots formatted as common primitive types.
|
||||
* {TransientSlot}: Primitives for reading from and writing to transient storage (only value types are currently supported).
|
||||
* {Multicall}: Abstract contract with a utility to allow batching together multiple calls in a single transaction. Useful for allowing EOAs to perform multiple operations at once.
|
||||
* {Context}: A utility for abstracting the sender and calldata in the current execution context.
|
||||
* {Packing}: A library for packing and unpacking multiple values into bytes32
|
||||
@ -130,6 +131,8 @@ Ethereum contracts have no native concept of an interface, so applications must
|
||||
|
||||
{{StorageSlot}}
|
||||
|
||||
{{TransientSlot}}
|
||||
|
||||
{{Multicall}}
|
||||
|
||||
{{Context}}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
pragma solidity ^0.8.24;
|
||||
|
||||
import {StorageSlot} from "./StorageSlot.sol";
|
||||
import {TransientSlot} from "./TransientSlot.sol";
|
||||
|
||||
/**
|
||||
* @dev Variant of {ReentrancyGuard} that uses transient storage.
|
||||
@ -13,7 +13,7 @@ import {StorageSlot} from "./StorageSlot.sol";
|
||||
* _Available since v5.1._
|
||||
*/
|
||||
abstract contract ReentrancyGuardTransient {
|
||||
using StorageSlot for *;
|
||||
using TransientSlot for *;
|
||||
|
||||
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
|
||||
bytes32 private constant REENTRANCY_GUARD_STORAGE =
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// OpenZeppelin Contracts (last updated v5.1.0-rc.0) (utils/StorageSlot.sol)
|
||||
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
|
||||
|
||||
pragma solidity ^0.8.24;
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
/**
|
||||
* @dev Library for reading and writing primitive types to specific storage slots.
|
||||
@ -29,24 +29,6 @@ pragma solidity ^0.8.24;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* 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 {
|
||||
@ -158,154 +140,4 @@ 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) {
|
||||
assembly ("memory-safe") {
|
||||
value := tload(slot)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Store `value` at location `slot` in transient storage.
|
||||
*/
|
||||
function tstore(AddressSlotType slot, address value) internal {
|
||||
assembly ("memory-safe") {
|
||||
tstore(slot, value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Load the value held at location `slot` in transient storage.
|
||||
*/
|
||||
function tload(BooleanSlotType slot) internal view returns (bool value) {
|
||||
assembly ("memory-safe") {
|
||||
value := tload(slot)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Store `value` at location `slot` in transient storage.
|
||||
*/
|
||||
function tstore(BooleanSlotType slot, bool value) internal {
|
||||
assembly ("memory-safe") {
|
||||
tstore(slot, value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Load the value held at location `slot` in transient storage.
|
||||
*/
|
||||
function tload(Bytes32SlotType slot) internal view returns (bytes32 value) {
|
||||
assembly ("memory-safe") {
|
||||
value := tload(slot)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Store `value` at location `slot` in transient storage.
|
||||
*/
|
||||
function tstore(Bytes32SlotType slot, bytes32 value) internal {
|
||||
assembly ("memory-safe") {
|
||||
tstore(slot, value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Load the value held at location `slot` in transient storage.
|
||||
*/
|
||||
function tload(Uint256SlotType slot) internal view returns (uint256 value) {
|
||||
assembly ("memory-safe") {
|
||||
value := tload(slot)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Store `value` at location `slot` in transient storage.
|
||||
*/
|
||||
function tstore(Uint256SlotType slot, uint256 value) internal {
|
||||
assembly ("memory-safe") {
|
||||
tstore(slot, value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Load the value held at location `slot` in transient storage.
|
||||
*/
|
||||
function tload(Int256SlotType slot) internal view returns (int256 value) {
|
||||
assembly ("memory-safe") {
|
||||
value := tload(slot)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Store `value` at location `slot` in transient storage.
|
||||
*/
|
||||
function tstore(Int256SlotType slot, int256 value) internal {
|
||||
assembly ("memory-safe") {
|
||||
tstore(slot, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
182
contracts/utils/TransientSlot.sol
Normal file
182
contracts/utils/TransientSlot.sol
Normal file
@ -0,0 +1,182 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// This file was procedurally generated from scripts/generate/templates/TransientSlot.js.
|
||||
|
||||
pragma solidity ^0.8.24;
|
||||
|
||||
/**
|
||||
* @dev Library for reading and writing value-types to specific transient storage slots.
|
||||
*
|
||||
* Transient slots are often used to store temporary values that are removed after the current transaction.
|
||||
* This library helps with reading and writing to such slots without the need for inline assembly.
|
||||
*
|
||||
* * Example reading and writing values using transient storage:
|
||||
* ```solidity
|
||||
* contract Lock {
|
||||
* using TransientSlot for *;
|
||||
*
|
||||
* // 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 TransientSlot {
|
||||
/**
|
||||
* @dev UDVT that represent a slot holding a address.
|
||||
*/
|
||||
type AddressSlot is bytes32;
|
||||
|
||||
/**
|
||||
* @dev Cast an arbitrary slot to a AddressSlot.
|
||||
*/
|
||||
function asAddress(bytes32 slot) internal pure returns (AddressSlot) {
|
||||
return AddressSlot.wrap(slot);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev UDVT that represent a slot holding a bool.
|
||||
*/
|
||||
type BooleanSlot is bytes32;
|
||||
|
||||
/**
|
||||
* @dev Cast an arbitrary slot to a BooleanSlot.
|
||||
*/
|
||||
function asBoolean(bytes32 slot) internal pure returns (BooleanSlot) {
|
||||
return BooleanSlot.wrap(slot);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev UDVT that represent a slot holding a bytes32.
|
||||
*/
|
||||
type Bytes32Slot is bytes32;
|
||||
|
||||
/**
|
||||
* @dev Cast an arbitrary slot to a Bytes32Slot.
|
||||
*/
|
||||
function asBytes32(bytes32 slot) internal pure returns (Bytes32Slot) {
|
||||
return Bytes32Slot.wrap(slot);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev UDVT that represent a slot holding a uint256.
|
||||
*/
|
||||
type Uint256Slot is bytes32;
|
||||
|
||||
/**
|
||||
* @dev Cast an arbitrary slot to a Uint256Slot.
|
||||
*/
|
||||
function asUint256(bytes32 slot) internal pure returns (Uint256Slot) {
|
||||
return Uint256Slot.wrap(slot);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev UDVT that represent a slot holding a int256.
|
||||
*/
|
||||
type Int256Slot is bytes32;
|
||||
|
||||
/**
|
||||
* @dev Cast an arbitrary slot to a Int256Slot.
|
||||
*/
|
||||
function asInt256(bytes32 slot) internal pure returns (Int256Slot) {
|
||||
return Int256Slot.wrap(slot);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Load the value held at location `slot` in transient storage.
|
||||
*/
|
||||
function tload(AddressSlot slot) internal view returns (address value) {
|
||||
assembly ("memory-safe") {
|
||||
value := tload(slot)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Store `value` at location `slot` in transient storage.
|
||||
*/
|
||||
function tstore(AddressSlot slot, address value) internal {
|
||||
assembly ("memory-safe") {
|
||||
tstore(slot, value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Load the value held at location `slot` in transient storage.
|
||||
*/
|
||||
function tload(BooleanSlot slot) internal view returns (bool value) {
|
||||
assembly ("memory-safe") {
|
||||
value := tload(slot)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Store `value` at location `slot` in transient storage.
|
||||
*/
|
||||
function tstore(BooleanSlot slot, bool value) internal {
|
||||
assembly ("memory-safe") {
|
||||
tstore(slot, value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Load the value held at location `slot` in transient storage.
|
||||
*/
|
||||
function tload(Bytes32Slot slot) internal view returns (bytes32 value) {
|
||||
assembly ("memory-safe") {
|
||||
value := tload(slot)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Store `value` at location `slot` in transient storage.
|
||||
*/
|
||||
function tstore(Bytes32Slot slot, bytes32 value) internal {
|
||||
assembly ("memory-safe") {
|
||||
tstore(slot, value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Load the value held at location `slot` in transient storage.
|
||||
*/
|
||||
function tload(Uint256Slot slot) internal view returns (uint256 value) {
|
||||
assembly ("memory-safe") {
|
||||
value := tload(slot)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Store `value` at location `slot` in transient storage.
|
||||
*/
|
||||
function tstore(Uint256Slot slot, uint256 value) internal {
|
||||
assembly ("memory-safe") {
|
||||
tstore(slot, value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Load the value held at location `slot` in transient storage.
|
||||
*/
|
||||
function tload(Int256Slot slot) internal view returns (int256 value) {
|
||||
assembly ("memory-safe") {
|
||||
value := tload(slot)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Store `value` at location `slot` in transient storage.
|
||||
*/
|
||||
function tstore(Int256Slot slot, int256 value) internal {
|
||||
assembly ("memory-safe") {
|
||||
tstore(slot, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -319,7 +319,7 @@ function _setImplementation(address newImplementation) internal {
|
||||
}
|
||||
----
|
||||
|
||||
The xref:api:utils.adoc#StorageSlot[`StorageSlot`] library also supports transient storage through user defined value types (https://docs.soliditylang.org/en/latest/types.html#user-defined-value-types[UDVTs]), which enables the same value types as in Solidity.
|
||||
The xref:api:utils.adoc#TransientSlot[`TransientSlot`] library supports transient storage through user defined value types (https://docs.soliditylang.org/en/latest/types.html#user-defined-value-types[UDVTs]), which enables the same value types as in Solidity.
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
|
||||
@ -39,9 +39,11 @@ for (const [file, template] of Object.entries({
|
||||
'utils/structs/EnumerableMap.sol': './templates/EnumerableMap.js',
|
||||
'utils/SlotDerivation.sol': './templates/SlotDerivation.js',
|
||||
'utils/StorageSlot.sol': './templates/StorageSlot.js',
|
||||
'utils/TransientSlot.sol': './templates/TransientSlot.js',
|
||||
'utils/Arrays.sol': './templates/Arrays.js',
|
||||
'utils/Packing.sol': './templates/Packing.js',
|
||||
'mocks/StorageSlotMock.sol': './templates/StorageSlotMock.js',
|
||||
'mocks/TransientSlotMock.sol': './templates/TransientSlotMock.js',
|
||||
})) {
|
||||
generateFromTemplate(file, template, './contracts/');
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ const format = require('../format-lines');
|
||||
const { TYPES } = require('./Slot.opts');
|
||||
|
||||
const header = `\
|
||||
pragma solidity ^0.8.24;
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
/**
|
||||
* @dev Library for reading and writing primitive types to specific storage slots.
|
||||
@ -29,24 +29,6 @@ pragma solidity ^0.8.24;
|
||||
* }
|
||||
* \`\`\`
|
||||
*
|
||||
* 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}.
|
||||
*/
|
||||
`;
|
||||
@ -81,40 +63,6 @@ function get${name}Slot(${type} storage store) internal pure returns (${name}Slo
|
||||
}
|
||||
`;
|
||||
|
||||
const udvt = ({ type, name }) => `\
|
||||
/**
|
||||
* @dev UDVT that represent a slot holding a ${type}.
|
||||
*/
|
||||
type ${name}SlotType is bytes32;
|
||||
|
||||
/**
|
||||
* @dev Cast an arbitrary slot to a ${name}SlotType.
|
||||
*/
|
||||
function as${name}(bytes32 slot) internal pure returns (${name}SlotType) {
|
||||
return ${name}SlotType.wrap(slot);
|
||||
}
|
||||
`;
|
||||
|
||||
const transient = ({ type, name }) => `\
|
||||
/**
|
||||
* @dev Load the value held at location \`slot\` in transient storage.
|
||||
*/
|
||||
function tload(${name}SlotType slot) internal view returns (${type} value) {
|
||||
assembly ("memory-safe") {
|
||||
value := tload(slot)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Store \`value\` at location \`slot\` in transient storage.
|
||||
*/
|
||||
function tstore(${name}SlotType slot, ${type} value) internal {
|
||||
assembly ("memory-safe") {
|
||||
tstore(slot, value)
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// GENERATE
|
||||
module.exports = format(
|
||||
header.trimEnd(),
|
||||
@ -123,8 +71,6 @@ module.exports = format(
|
||||
[].concat(
|
||||
TYPES.map(type => struct(type)),
|
||||
TYPES.flatMap(type => [get(type), !type.isValueType && getStorage(type)].filter(Boolean)),
|
||||
TYPES.filter(type => type.isValueType).map(type => udvt(type)),
|
||||
TYPES.filter(type => type.isValueType).map(type => transient(type)),
|
||||
),
|
||||
).trimEnd(),
|
||||
'}',
|
||||
|
||||
@ -2,7 +2,7 @@ const format = require('../format-lines');
|
||||
const { TYPES } = require('./Slot.opts');
|
||||
|
||||
const header = `\
|
||||
pragma solidity ^0.8.24;
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Multicall} from "../utils/Multicall.sol";
|
||||
import {StorageSlot} from "../utils/StorageSlot.sol";
|
||||
@ -40,18 +40,6 @@ function get${name}Storage(uint256 key) public view returns (${type} memory) {
|
||||
}
|
||||
`;
|
||||
|
||||
const transient = ({ type, name }) => `\
|
||||
event ${name}Value(bytes32 slot, ${type} value);
|
||||
|
||||
function tload${name}(bytes32 slot) public {
|
||||
emit ${name}Value(slot, slot.as${name}().tload());
|
||||
}
|
||||
|
||||
function tstore(bytes32 slot, ${type} value) public {
|
||||
slot.as${name}().tstore(value);
|
||||
}
|
||||
`;
|
||||
|
||||
// GENERATE
|
||||
module.exports = format(
|
||||
header,
|
||||
@ -63,7 +51,6 @@ module.exports = format(
|
||||
TYPES.filter(type => type.isValueType).map(type => storageSetValueType(type)),
|
||||
TYPES.filter(type => type.isValueType).map(type => storageGetValueType(type)),
|
||||
TYPES.filter(type => !type.isValueType).map(type => storageSetNonValueType(type)),
|
||||
TYPES.filter(type => type.isValueType).map(type => transient(type)),
|
||||
),
|
||||
).trimEnd(),
|
||||
'}',
|
||||
|
||||
80
scripts/generate/templates/TransientSlot.js
Normal file
80
scripts/generate/templates/TransientSlot.js
Normal file
@ -0,0 +1,80 @@
|
||||
const format = require('../format-lines');
|
||||
const { TYPES } = require('./Slot.opts');
|
||||
|
||||
const header = `\
|
||||
pragma solidity ^0.8.24;
|
||||
|
||||
/**
|
||||
* @dev Library for reading and writing value-types to specific transient storage slots.
|
||||
*
|
||||
* Transient slots are often used to store temporary values that are removed after the current transaction.
|
||||
* This library helps with reading and writing to such slots without the need for inline assembly.
|
||||
*
|
||||
* * Example reading and writing values using transient storage:
|
||||
* \`\`\`solidity
|
||||
* contract Lock {
|
||||
* using TransientSlot for *;
|
||||
*
|
||||
* // 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}.
|
||||
*/
|
||||
`;
|
||||
|
||||
const udvt = ({ type, name }) => `\
|
||||
/**
|
||||
* @dev UDVT that represent a slot holding a ${type}.
|
||||
*/
|
||||
type ${name}Slot is bytes32;
|
||||
|
||||
/**
|
||||
* @dev Cast an arbitrary slot to a ${name}Slot.
|
||||
*/
|
||||
function as${name}(bytes32 slot) internal pure returns (${name}Slot) {
|
||||
return ${name}Slot.wrap(slot);
|
||||
}
|
||||
`;
|
||||
|
||||
const transient = ({ type, name }) => `\
|
||||
/**
|
||||
* @dev Load the value held at location \`slot\` in transient storage.
|
||||
*/
|
||||
function tload(${name}Slot slot) internal view returns (${type} value) {
|
||||
assembly ("memory-safe") {
|
||||
value := tload(slot)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Store \`value\` at location \`slot\` in transient storage.
|
||||
*/
|
||||
function tstore(${name}Slot slot, ${type} value) internal {
|
||||
assembly ("memory-safe") {
|
||||
tstore(slot, value)
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// GENERATE
|
||||
module.exports = format(
|
||||
header.trimEnd(),
|
||||
'library TransientSlot {',
|
||||
format(
|
||||
[].concat(
|
||||
TYPES.filter(type => type.isValueType).map(type => udvt(type)),
|
||||
TYPES.filter(type => type.isValueType).map(type => transient(type)),
|
||||
),
|
||||
).trimEnd(),
|
||||
'}',
|
||||
);
|
||||
35
scripts/generate/templates/TransientSlotMock.js
Normal file
35
scripts/generate/templates/TransientSlotMock.js
Normal file
@ -0,0 +1,35 @@
|
||||
const format = require('../format-lines');
|
||||
const { TYPES } = require('./Slot.opts');
|
||||
|
||||
const header = `\
|
||||
pragma solidity ^0.8.24;
|
||||
|
||||
import {Multicall} from "../utils/Multicall.sol";
|
||||
import {TransientSlot} from "../utils/TransientSlot.sol";
|
||||
`;
|
||||
|
||||
const transient = ({ type, name }) => `\
|
||||
event ${name}Value(bytes32 slot, ${type} value);
|
||||
|
||||
function tload${name}(bytes32 slot) public {
|
||||
emit ${name}Value(slot, slot.as${name}().tload());
|
||||
}
|
||||
|
||||
function tstore(bytes32 slot, ${type} value) public {
|
||||
slot.as${name}().tstore(value);
|
||||
}
|
||||
`;
|
||||
|
||||
// GENERATE
|
||||
module.exports = format(
|
||||
header,
|
||||
'contract TransientSlotMock is Multicall {',
|
||||
format(
|
||||
[].concat(
|
||||
'using TransientSlot for *;',
|
||||
'',
|
||||
TYPES.filter(type => type.isValueType).map(type => transient(type)),
|
||||
),
|
||||
).trimEnd(),
|
||||
'}',
|
||||
);
|
||||
@ -70,37 +70,4 @@ describe('StorageSlot', function () {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
for (const { name, type, value, zero } of TYPES.filter(type => type.isValueType)) {
|
||||
describe(`${type} transient slot`, function () {
|
||||
const load = `tload${name}(bytes32)`;
|
||||
const store = `tstore(bytes32,${type})`;
|
||||
const event = `${name}Value`;
|
||||
|
||||
it('load', async function () {
|
||||
await expect(this.mock[load](slot)).to.emit(this.mock, event).withArgs(slot, zero);
|
||||
});
|
||||
|
||||
it('store and load (2 txs)', async function () {
|
||||
await this.mock[store](slot, value);
|
||||
await expect(this.mock[load](slot)).to.emit(this.mock, event).withArgs(slot, zero);
|
||||
});
|
||||
|
||||
it('store and load (batched)', async function () {
|
||||
await expect(
|
||||
this.mock.multicall([
|
||||
this.mock.interface.encodeFunctionData(store, [slot, value]),
|
||||
this.mock.interface.encodeFunctionData(load, [slot]),
|
||||
this.mock.interface.encodeFunctionData(load, [otherSlot]),
|
||||
]),
|
||||
)
|
||||
.to.emit(this.mock, event)
|
||||
.withArgs(slot, value)
|
||||
.to.emit(this.mock, event)
|
||||
.withArgs(otherSlot, zero);
|
||||
|
||||
await expect(this.mock[load](slot)).to.emit(this.mock, event).withArgs(slot, zero);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
59
test/utils/TransientSlot.test.js
Normal file
59
test/utils/TransientSlot.test.js
Normal file
@ -0,0 +1,59 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
const { generators } = require('../helpers/random');
|
||||
|
||||
const slot = ethers.id('some.storage.slot');
|
||||
const otherSlot = ethers.id('some.other.storage.slot');
|
||||
|
||||
// Non-value types are not supported by the `TransientSlot` library.
|
||||
const TYPES = [
|
||||
{ name: 'Boolean', type: 'bool', value: true, zero: false },
|
||||
{ name: 'Address', type: 'address', value: generators.address(), zero: generators.address.zero },
|
||||
{ name: 'Bytes32', type: 'bytes32', value: generators.bytes32(), zero: generators.bytes32.zero },
|
||||
{ name: 'Uint256', type: 'uint256', value: generators.uint256(), zero: generators.uint256.zero },
|
||||
{ name: 'Int256', type: 'int256', value: generators.int256(), zero: generators.int256.zero },
|
||||
];
|
||||
|
||||
async function fixture() {
|
||||
return { mock: await ethers.deployContract('TransientSlotMock') };
|
||||
}
|
||||
|
||||
describe('TransientSlot', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
for (const { name, type, value, zero } of TYPES) {
|
||||
describe(`${type} transient slot`, function () {
|
||||
const load = `tload${name}(bytes32)`;
|
||||
const store = `tstore(bytes32,${type})`;
|
||||
const event = `${name}Value`;
|
||||
|
||||
it('load', async function () {
|
||||
await expect(this.mock[load](slot)).to.emit(this.mock, event).withArgs(slot, zero);
|
||||
});
|
||||
|
||||
it('store and load (2 txs)', async function () {
|
||||
await this.mock[store](slot, value);
|
||||
await expect(this.mock[load](slot)).to.emit(this.mock, event).withArgs(slot, zero);
|
||||
});
|
||||
|
||||
it('store and load (batched)', async function () {
|
||||
await expect(
|
||||
this.mock.multicall([
|
||||
this.mock.interface.encodeFunctionData(store, [slot, value]),
|
||||
this.mock.interface.encodeFunctionData(load, [slot]),
|
||||
this.mock.interface.encodeFunctionData(load, [otherSlot]),
|
||||
]),
|
||||
)
|
||||
.to.emit(this.mock, event)
|
||||
.withArgs(slot, value)
|
||||
.to.emit(this.mock, event)
|
||||
.withArgs(otherSlot, zero);
|
||||
|
||||
await expect(this.mock[load](slot)).to.emit(this.mock, event).withArgs(slot, zero);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user