diff --git a/contracts/mocks/StorageSlotMock.sol b/contracts/mocks/StorageSlotMock.sol index e69bd36a2..ec176e21f 100644 --- a/contracts/mocks/StorageSlotMock.sol +++ b/contracts/mocks/StorageSlotMock.sol @@ -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); - } } diff --git a/contracts/mocks/TransientSlotMock.sol b/contracts/mocks/TransientSlotMock.sol new file mode 100644 index 000000000..6b18fa52f --- /dev/null +++ b/contracts/mocks/TransientSlotMock.sol @@ -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); + } +} diff --git a/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol b/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol index d75e3b428..8b8f10769 100644 --- a/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol +++ b/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol @@ -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(); } } diff --git a/contracts/utils/README.adoc b/contracts/utils/README.adoc index 4b40a967e..24b95b4e6 100644 --- a/contracts/utils/README.adoc +++ b/contracts/utils/README.adoc @@ -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}} diff --git a/contracts/utils/ReentrancyGuardTransient.sol b/contracts/utils/ReentrancyGuardTransient.sol index 5f901ad22..d10558027 100644 --- a/contracts/utils/ReentrancyGuardTransient.sol +++ b/contracts/utils/ReentrancyGuardTransient.sol @@ -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 = diff --git a/contracts/utils/StorageSlot.sol b/contracts/utils/StorageSlot.sol index 12d7201c7..f2dc0b9bc 100644 --- a/contracts/utils/StorageSlot.sol +++ b/contracts/utils/StorageSlot.sol @@ -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) - } - } } diff --git a/contracts/utils/TransientSlot.sol b/contracts/utils/TransientSlot.sol new file mode 100644 index 000000000..f1f67bdce --- /dev/null +++ b/contracts/utils/TransientSlot.sol @@ -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) + } + } +} diff --git a/docs/modules/ROOT/pages/utilities.adoc b/docs/modules/ROOT/pages/utilities.adoc index adee6477f..bb519907d 100644 --- a/docs/modules/ROOT/pages/utilities.adoc +++ b/docs/modules/ROOT/pages/utilities.adoc @@ -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] ---- diff --git a/scripts/generate/run.js b/scripts/generate/run.js index c28d1175b..e4947eb12 100755 --- a/scripts/generate/run.js +++ b/scripts/generate/run.js @@ -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/'); } diff --git a/scripts/generate/templates/StorageSlot.js b/scripts/generate/templates/StorageSlot.js index 7a00f5e22..53287b81f 100644 --- a/scripts/generate/templates/StorageSlot.js +++ b/scripts/generate/templates/StorageSlot.js @@ -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(), '}', diff --git a/scripts/generate/templates/StorageSlotMock.js b/scripts/generate/templates/StorageSlotMock.js index 623a67592..c6d326a5e 100644 --- a/scripts/generate/templates/StorageSlotMock.js +++ b/scripts/generate/templates/StorageSlotMock.js @@ -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(), '}', diff --git a/scripts/generate/templates/TransientSlot.js b/scripts/generate/templates/TransientSlot.js new file mode 100644 index 000000000..8e291bc13 --- /dev/null +++ b/scripts/generate/templates/TransientSlot.js @@ -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(), + '}', +); diff --git a/scripts/generate/templates/TransientSlotMock.js b/scripts/generate/templates/TransientSlotMock.js new file mode 100644 index 000000000..4807b0cc1 --- /dev/null +++ b/scripts/generate/templates/TransientSlotMock.js @@ -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(), + '}', +); diff --git a/test/utils/StorageSlot.test.js b/test/utils/StorageSlot.test.js index 35e83e29e..ddcf305d1 100644 --- a/test/utils/StorageSlot.test.js +++ b/test/utils/StorageSlot.test.js @@ -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); - }); - }); - } }); diff --git a/test/utils/TransientSlot.test.js b/test/utils/TransientSlot.test.js new file mode 100644 index 000000000..7b70be375 --- /dev/null +++ b/test/utils/TransientSlot.test.js @@ -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); + }); + }); + } +});