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
|
// SPDX-License-Identifier: MIT
|
||||||
// This file was procedurally generated from scripts/generate/templates/StorageSlotMock.js.
|
// 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 {Multicall} from "../utils/Multicall.sol";
|
||||||
import {StorageSlot} from "../utils/StorageSlot.sol";
|
import {StorageSlot} from "../utils/StorageSlot.sol";
|
||||||
@ -84,54 +84,4 @@ contract StorageSlotMock is Multicall {
|
|||||||
function getBytesStorage(uint256 key) public view returns (bytes memory) {
|
function getBytesStorage(uint256 key) public view returns (bytes memory) {
|
||||||
return bytesMap[key].getBytesSlot().value;
|
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 {IERC7674} from "../../../interfaces/draft-IERC7674.sol";
|
||||||
import {Math} from "../../../utils/math/Math.sol";
|
import {Math} from "../../../utils/math/Math.sol";
|
||||||
import {SlotDerivation} from "../../../utils/SlotDerivation.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.
|
* @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 {
|
abstract contract ERC20TemporaryApproval is ERC20, IERC7674 {
|
||||||
using SlotDerivation for bytes32;
|
using SlotDerivation for bytes32;
|
||||||
using StorageSlot for bytes32;
|
using TransientSlot for bytes32;
|
||||||
using StorageSlot for StorageSlot.Uint256SlotType;
|
using TransientSlot for TransientSlot.Uint256Slot;
|
||||||
|
|
||||||
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC20_TEMPORARY_APPROVAL_STORAGE")) - 1)) & ~bytes32(uint256(0xff))
|
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC20_TEMPORARY_APPROVAL_STORAGE")) - 1)) & ~bytes32(uint256(0xff))
|
||||||
bytes32 private constant ERC20_TEMPORARY_APPROVAL_STORAGE =
|
bytes32 private constant ERC20_TEMPORARY_APPROVAL_STORAGE =
|
||||||
@ -113,10 +113,7 @@ abstract contract ERC20TemporaryApproval is ERC20, IERC7674 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _temporaryAllowanceSlot(
|
function _temporaryAllowanceSlot(address owner, address spender) private pure returns (TransientSlot.Uint256Slot) {
|
||||||
address owner,
|
|
||||||
address spender
|
|
||||||
) private pure returns (StorageSlot.Uint256SlotType) {
|
|
||||||
return ERC20_TEMPORARY_APPROVAL_STORAGE.deriveMapping(owner).deriveMapping(spender).asUint256();
|
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.
|
* {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.
|
* {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.
|
* {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.
|
* {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.
|
* {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
|
* {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}}
|
{{StorageSlot}}
|
||||||
|
|
||||||
|
{{TransientSlot}}
|
||||||
|
|
||||||
{{Multicall}}
|
{{Multicall}}
|
||||||
|
|
||||||
{{Context}}
|
{{Context}}
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
pragma solidity ^0.8.24;
|
pragma solidity ^0.8.24;
|
||||||
|
|
||||||
import {StorageSlot} from "./StorageSlot.sol";
|
import {TransientSlot} from "./TransientSlot.sol";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Variant of {ReentrancyGuard} that uses transient storage.
|
* @dev Variant of {ReentrancyGuard} that uses transient storage.
|
||||||
@ -13,7 +13,7 @@ import {StorageSlot} from "./StorageSlot.sol";
|
|||||||
* _Available since v5.1._
|
* _Available since v5.1._
|
||||||
*/
|
*/
|
||||||
abstract contract ReentrancyGuardTransient {
|
abstract contract ReentrancyGuardTransient {
|
||||||
using StorageSlot for *;
|
using TransientSlot for *;
|
||||||
|
|
||||||
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
|
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
|
||||||
bytes32 private constant REENTRANCY_GUARD_STORAGE =
|
bytes32 private constant REENTRANCY_GUARD_STORAGE =
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
// OpenZeppelin Contracts (last updated v5.1.0-rc.0) (utils/StorageSlot.sol)
|
// OpenZeppelin Contracts (last updated v5.1.0-rc.0) (utils/StorageSlot.sol)
|
||||||
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
|
// 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.
|
* @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}.
|
* TIP: Consider using this library along with {SlotDerivation}.
|
||||||
*/
|
*/
|
||||||
library StorageSlot {
|
library StorageSlot {
|
||||||
@ -158,154 +140,4 @@ library StorageSlot {
|
|||||||
r.slot := store.slot
|
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]
|
[source,solidity]
|
||||||
----
|
----
|
||||||
|
|||||||
@ -39,9 +39,11 @@ for (const [file, template] of Object.entries({
|
|||||||
'utils/structs/EnumerableMap.sol': './templates/EnumerableMap.js',
|
'utils/structs/EnumerableMap.sol': './templates/EnumerableMap.js',
|
||||||
'utils/SlotDerivation.sol': './templates/SlotDerivation.js',
|
'utils/SlotDerivation.sol': './templates/SlotDerivation.js',
|
||||||
'utils/StorageSlot.sol': './templates/StorageSlot.js',
|
'utils/StorageSlot.sol': './templates/StorageSlot.js',
|
||||||
|
'utils/TransientSlot.sol': './templates/TransientSlot.js',
|
||||||
'utils/Arrays.sol': './templates/Arrays.js',
|
'utils/Arrays.sol': './templates/Arrays.js',
|
||||||
'utils/Packing.sol': './templates/Packing.js',
|
'utils/Packing.sol': './templates/Packing.js',
|
||||||
'mocks/StorageSlotMock.sol': './templates/StorageSlotMock.js',
|
'mocks/StorageSlotMock.sol': './templates/StorageSlotMock.js',
|
||||||
|
'mocks/TransientSlotMock.sol': './templates/TransientSlotMock.js',
|
||||||
})) {
|
})) {
|
||||||
generateFromTemplate(file, template, './contracts/');
|
generateFromTemplate(file, template, './contracts/');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ const format = require('../format-lines');
|
|||||||
const { TYPES } = require('./Slot.opts');
|
const { TYPES } = require('./Slot.opts');
|
||||||
|
|
||||||
const header = `\
|
const header = `\
|
||||||
pragma solidity ^0.8.24;
|
pragma solidity ^0.8.20;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Library for reading and writing primitive types to specific storage slots.
|
* @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}.
|
* 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
|
// GENERATE
|
||||||
module.exports = format(
|
module.exports = format(
|
||||||
header.trimEnd(),
|
header.trimEnd(),
|
||||||
@ -123,8 +71,6 @@ module.exports = format(
|
|||||||
[].concat(
|
[].concat(
|
||||||
TYPES.map(type => struct(type)),
|
TYPES.map(type => struct(type)),
|
||||||
TYPES.flatMap(type => [get(type), !type.isValueType && getStorage(type)].filter(Boolean)),
|
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(),
|
).trimEnd(),
|
||||||
'}',
|
'}',
|
||||||
|
|||||||
@ -2,7 +2,7 @@ const format = require('../format-lines');
|
|||||||
const { TYPES } = require('./Slot.opts');
|
const { TYPES } = require('./Slot.opts');
|
||||||
|
|
||||||
const header = `\
|
const header = `\
|
||||||
pragma solidity ^0.8.24;
|
pragma solidity ^0.8.20;
|
||||||
|
|
||||||
import {Multicall} from "../utils/Multicall.sol";
|
import {Multicall} from "../utils/Multicall.sol";
|
||||||
import {StorageSlot} from "../utils/StorageSlot.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
|
// GENERATE
|
||||||
module.exports = format(
|
module.exports = format(
|
||||||
header,
|
header,
|
||||||
@ -63,7 +51,6 @@ module.exports = format(
|
|||||||
TYPES.filter(type => type.isValueType).map(type => storageSetValueType(type)),
|
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 => storageGetValueType(type)),
|
||||||
TYPES.filter(type => !type.isValueType).map(type => storageSetNonValueType(type)),
|
TYPES.filter(type => !type.isValueType).map(type => storageSetNonValueType(type)),
|
||||||
TYPES.filter(type => type.isValueType).map(type => transient(type)),
|
|
||||||
),
|
),
|
||||||
).trimEnd(),
|
).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