From ff1eaecfe211abcf395ae7f8b5ab05bbba66ab55 Mon Sep 17 00:00:00 2001 From: kadenzipfel Date: Tue, 11 Aug 2026 14:17:52 -0700 Subject: [PATCH 1/7] Add MAPPING_SLOT macros --- src/data-structures/Hashmap.huff | 33 ++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/data-structures/Hashmap.huff b/src/data-structures/Hashmap.huff index 91d8ab28..02355983 100644 --- a/src/data-structures/Hashmap.huff +++ b/src/data-structures/Hashmap.huff @@ -103,6 +103,39 @@ sha3 // [slot3] } +/// @notice Computes the storage slot of `mapping[key]` +#define macro MAPPING_SLOT() = takes(2) returns(1) { + // Input stack: [base_slot, key] + 0x20 mstore // [key] + 0x00 mstore // [] + 0x40 0x00 sha3 // [slot] +} + +/// @notice Computes the storage slot of `mapping[key1][key2]` +#define macro MAPPING_SLOT_2D() = takes(3) returns(1) { + // Input stack: [base_slot, key1, key2] + 0x20 mstore // [key1, key2] + 0x00 mstore // [key2] + 0x40 0x00 sha3 // [slot_1, key2] + 0x20 mstore // [key2] + 0x00 mstore // [] + 0x40 0x00 sha3 // [slot] +} + +/// @notice Computes the storage slot of `mapping[key1][key2][key3]` +#define macro MAPPING_SLOT_3D() = takes(4) returns(1) { + // Input stack: [base_slot, key1, key2, key3] + 0x20 mstore // [key1, key2, key3] + 0x00 mstore // [key2, key3] + 0x40 0x00 sha3 // [slot_1, key2, key3] + 0x20 mstore // [key2, key3] + 0x00 mstore // [key3] + 0x40 0x00 sha3 // [slot_2, key3] + 0x20 mstore // [key3] + 0x00 mstore // [] + 0x40 0x00 sha3 // [slot] +} + /// @notice Load an element onto the stack from a key #define macro LOAD_ELEMENT(mem_ptr) = takes(1) returns(1) { // Input stack: [key] From 987f14975d96f1f08c9f60291c627537eb991693 Mon Sep 17 00:00:00 2001 From: kadenzipfel Date: Tue, 11 Aug 2026 14:18:28 -0700 Subject: [PATCH 2/7] Update ERC20 to use optimized slot usage --- src/tokens/ERC20.huff | 96 +++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 49 deletions(-) diff --git a/src/tokens/ERC20.huff b/src/tokens/ERC20.huff index a72e05d5..0a80a0e7 100644 --- a/src/tokens/ERC20.huff +++ b/src/tokens/ERC20.huff @@ -152,40 +152,36 @@ 0x04 calldataload // [from, to] caller // [msg.sender, from, to] dup2 // [from, msg.sender, from, to] - [APPROVAL_SLOT] // [slot, from, msg.sender, from, to] - // Check for max approval - LOAD_ELEMENT_FROM_KEYS_2D(0x00) // [approved, from, to] - dup1 // [approved, approved, from, to] - 0x44 calldataload // [value, approved, approved, from, to] + [APPROVAL_SLOT] MAPPING_SLOT_2D() // [slot, from, to] + dup1 sload // [approved, slot, from, to] + 0x44 calldataload // [value, approved, slot, from, to] // Check isOwner - dup4 // [from, value, approved, approved, from, to] - caller // [msg.sender, from, value, approved, approved, from, to] - eq // [msg.sender == from, value, approved, approved, from, to] - approved1 jumpi // [value, approved, approved, from, to] + dup4 // [from, value, approved, slot, from, to] + caller // [msg.sender, from, value, approved, slot, from, to] + eq // [msg.sender == from, value, approved, slot, from, to] + approved1 jumpi // [value, approved, slot, from, to] // Check max approval - dup2 // [approved, value, approved, approved, from, to] - [UINT_256_MAX] // [type(uint).max, approved, value, approved, approved, from, to] - eq // [type(uint).max == approved, value, approved, approved, from, to] - approved1 jumpi // [value, approved, approved, from, to] + dup2 // [approved, value, approved, slot, from, to] + [UINT_256_MAX] // [type(uint).max, approved, value, approved, slot, from, to] + eq // [type(uint).max == approved, value, approved, slot, from, to] + approved1 jumpi // [value, approved, slot, from, to] // Check has approval - gt // [value > approved, approved, from, to] - insufficientApproval jumpi // [approved, from, to] + dup2 dup2 // [value, approved, value, approved, slot, from, to] + gt // [value > approved, value, approved, slot, from, to] + insufficientApproval jumpi // [value, approved, slot, from, to] // Adjust approval - 0x44 calldataload // [value, approved, from, to] - swap1 // [approved, value, from, to] - sub // [approved - value => newApprovalValue, from, to] - caller // [msg.sender, newApprovalValue, from, to] - dup3 // [from, msg.sender, newApprovalValue, from, to] - [APPROVAL_SLOT] // [slot, from, msg.sender, newApprovalValue, from, to] - STORE_ELEMENT_FROM_KEYS_2D(0x00) // [from, to] + swap1 // [approved, value, slot, from, to] + sub // [approved - value => newApprovalValue, slot, from, to] + swap1 // [slot, newApprovalValue, from, to] + sstore // [from, to] approved2 jump // [from, to] - approved1: // [value, approved, approved, from, to] + approved1: // [value, approved, slot, from, to] pop pop pop // [from, to] approved2: // [from, to] @@ -212,34 +208,36 @@ /// @notice Transfers an amount of tokens from #define macro _TRANSFER_TAKE_FROM() = takes (3) returns (3) { // input stack: [value, from, to] - dup2 [BALANCE_SLOT] LOAD_ELEMENT_FROM_KEYS(0x00) // [balance, value, from, to] // [from, value, from, to] - dup1 // [balance, balance, value, from, to] - dup3 // [value, balance, balance, value, from, to] - gt // [value > balance, balance, value, from, to] - iszero // [value <= balance, balance, value, from, to] - valid jumpi // [balance, value, from, to] + dup2 // [from, value, from, to] + [BALANCE_SLOT] MAPPING_SLOT() // [slot, value, from, to] + dup1 sload // [balance, slot, value, from, to] + dup1 // [balance, balance, slot, value, from, to] + dup4 // [value, balance, balance, slot, value, from, to] + gt // [value > balance, balance, slot, value, from, to] + iszero // [value <= balance, balance, slot, value, from, to] + valid jumpi // [balance, slot, value, from, to] // Insufficient balance 0x00 0x00 revert // [] // Update the sender's balance. valid: - dup2 // [value, balance, value, from, to] - swap1 // [balance, value, value, from, to] - sub // [balance - value, value, from, to] - dup3 // [from, balance - value, value, from, to] - [BALANCE_SLOT] STORE_ELEMENT_FROM_KEYS(0x00) // [value, from, to] + dup3 // [value, balance, slot, value, from, to] + swap1 // [balance, value, slot, value, from, to] + sub // [balance - value, slot, value, from, to] + swap1 // [slot, balance - value, value, from, to] + sstore // [value, from, to] } /// @notice Transfers an amount of tokens from one address to another. #define macro _TRANSFER_GIVE_TO() = takes (3) returns (3) { // input stack: [value, from, to] - dup1 // [value, value, from, to] - dup4 // [to, value, value, from, to] - [BALANCE_SLOT] LOAD_ELEMENT_FROM_KEYS(0x00) // [balance, value, value, from, to] - add // [balance + value, value, from, to] - dup4 // [to, balance + value, value, from, to] - [BALANCE_SLOT] STORE_ELEMENT_FROM_KEYS(0x00) // [value, from, to] + dup3 // [to, value, from, to] + [BALANCE_SLOT] MAPPING_SLOT() // [slot, value, from, to] + dup1 sload // [balance, slot, value, from, to] + dup3 add // [balance + value, slot, value, from, to] + swap1 // [slot, balance + value, value, from, to] + sstore // [value, from, to] } /// @notice Domain Separator @@ -425,15 +423,15 @@ /// @notice Increments the nonce for next time, #define macro _NONCE_PLUS_PLUS() = takes (1) returns (1) { // input stack // [account] - dup1 // [account, account] - [NONCE_SLOT] LOAD_ELEMENT_FROM_KEYS(0x00) // [currentNonce, account] - dup1 // [currentNonce, currentNonce, account] - 0x01 // [1, currentNonce, currentNonce, account] - add // [nextNonce, currentNonce, account] - dup3 // [account, nextNonce, currentNonce, account] - [NONCE_SLOT] STORE_ELEMENT_FROM_KEYS(0x00) // [currentNonce, account] - swap1 // clean up stack // [account, currentNonce] - pop // clean up stack // [currentNonce] + [NONCE_SLOT] MAPPING_SLOT() // [slot] + dup1 // [slot, slot] + sload // [currentNonce, slot] + dup1 // [currentNonce, currentNonce, slot] + 0x01 // [1, currentNonce, currentNonce, slot] + add // [nextNonce, currentNonce, slot] + swap1 // [currentNonce, nextNonce, slot] + swap2 // [slot, nextNonce, currentNonce] + sstore // [currentNonce] } /// @notice Nonces From a6bf4a28f094707c169a3182287ac3e979e992e4 Mon Sep 17 00:00:00 2001 From: kadenzipfel Date: Tue, 11 Aug 2026 14:33:28 -0700 Subject: [PATCH 3/7] Update ERC721 to optimize slot usage --- src/tokens/ERC721.huff | 61 +++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/src/tokens/ERC721.huff b/src/tokens/ERC721.huff index 30084aeb..ae8cd778 100644 --- a/src/tokens/ERC721.huff +++ b/src/tokens/ERC721.huff @@ -228,30 +228,34 @@ // Input stack: // [tokenId] NON_PAYABLE() // [tokenId] + // ownerOf[tokenId] is read here and zeroed below, so hash it once and keep the slot dup1 // [tokenId, tokenId] - [OWNER_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [owner, tokenId] + [OWNER_LOCATION] MAPPING_SLOT() // [ownerSlot, tokenId] + dup1 sload // [owner, ownerSlot, tokenId] // Check that the recipient is valid - dup1 iszero // [owner == 0, owner, tokenId] - not_minted jumpi // [owner, tokenId] + dup1 iszero // [owner == 0, owner, ownerSlot, tokenId] + not_minted jumpi // [owner, ownerSlot, tokenId] // Create the burning params - 0x00 swap1 // [owner, to (0x00), tokenId] + 0x00 swap1 // [owner, to (0x00), ownerSlot, tokenId] - // Reduce the balance of owner by 1 - 0x01 dup2 // [owner, 1, owner, to, tokenId] - [BALANCE_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [balance, 1, owner, to, tokenId] - sub dup2 // [owner, balance-1, owner, to, tokenId] - [BALANCE_LOCATION] - STORE_ELEMENT_FROM_KEYS(0x00) // [owner, to, tokenId] - - // Set the owner of the token to 0x00 - 0x00 dup4 [OWNER_LOCATION] // [slot, owner, 0x00, owner, to, tokenId] - STORE_ELEMENT_FROM_KEYS(0x00) // [owner, to, tokenId] + // Reduce the balance of owner by 1, hashing balanceOf[owner] once for the read and write + dup1 // [owner, owner, to, ownerSlot, tokenId] + [BALANCE_LOCATION] MAPPING_SLOT() // [balSlot, owner, to, ownerSlot, tokenId] + dup1 sload // [balance, balSlot, owner, to, ownerSlot, tokenId] + 0x01 swap1 sub // [balance-1, balSlot, owner, to, ownerSlot, tokenId] + swap1 sstore // [owner, to, ownerSlot, tokenId] // Set the approval of the token to 0x00 for the owner - 0x00 dup4 [SINGLE_APPROVAL_LOCATION] // [slot, owner, 0x00, owner, to, tokenId] - STORE_ELEMENT_FROM_KEYS(0x00) // [owner, to, tokenId] + 0x00 dup5 [SINGLE_APPROVAL_LOCATION] // [slot, tokenId, 0x00, owner, to, ownerSlot, tokenId] + STORE_ELEMENT_FROM_KEYS(0x00) // [owner, to, ownerSlot, tokenId] + + // Set the owner of the token to 0x00, reusing the slot hashed above + swap2 // [ownerSlot, to, owner, tokenId] + 0x00 swap1 // [ownerSlot, 0x00, to, owner, tokenId] + sstore // [to, owner, tokenId] + swap1 // [owner, to, tokenId] // Emit the transfer event. __EVENT_HASH(Transfer) // [sig, owner, to (0x00), tokenId] @@ -529,27 +533,24 @@ is_authorized: - // Update balance of from - 0x01 dup2 // [from, 1, from, to, tokenId] - [BALANCE_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [balance, 1, from, to, tokenId] - sub dup2 // [from, balance-1, from, to, tokenId] - [BALANCE_LOCATION] - STORE_ELEMENT_FROM_KEYS(0x00) // [from, to, tokenId] + // Update balance of from. The entry is read and written, so hash it once and reuse the slot + dup1 // [from, from, to, tokenId] + [BALANCE_LOCATION] MAPPING_SLOT() // [slot, from, to, tokenId] + dup1 sload // [balance, slot, from, to, tokenId] + 0x01 swap1 sub // [balance-1, slot, from, to, tokenId] + swap1 sstore // [from, to, tokenId] } /// @notice Internal Macro to update Transfer to accounting #define macro TRANSFER_GIVE_TO() = takes (3) returns (3) { - // retrieve balance + // Bump the balance of to. The entry is read and written, so hash it once and reuse the slot // input stack: // [from, to, tokenId] dup2 // [to, from, to, tokenId] [BALANCE_LOCATION] // [balance_slot, to, from, to, tokenId] - LOAD_ELEMENT_FROM_KEYS(0x00) // [balance, from, to, tokenId] - 0x01 add // [balance+1, from, to, tokenId] - - // update balance - dup3 // [to, balance+1, from, to, tokenId] - [BALANCE_LOCATION] // [balance_slot, to, balance+1, from, to, tokenId] - STORE_ELEMENT_FROM_KEYS(0x00) // [from, to, tokenId] + MAPPING_SLOT() // [slot, from, to, tokenId] + dup1 sload // [balance, slot, from, to, tokenId] + 0x01 add // [balance+1, slot, from, to, tokenId] + swap1 sstore // [from, to, tokenId] // update ownerOf dup2 dup4 // [tokenId, to, from, to, tokenId] From c0477cff03732cc105c2e5e62341dcd7773e4750 Mon Sep 17 00:00:00 2001 From: kadenzipfel Date: Tue, 11 Aug 2026 14:40:55 -0700 Subject: [PATCH 4/7] Update RolesAuthority to optimize slot usage --- src/auth/RolesAuthority.huff | 84 +++++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 34 deletions(-) diff --git a/src/auth/RolesAuthority.huff b/src/auth/RolesAuthority.huff index 3974ae05..a3e251fc 100644 --- a/src/auth/RolesAuthority.huff +++ b/src/auth/RolesAuthority.huff @@ -26,10 +26,18 @@ #define constant IS_CAPABILITY_PUBLIC_LOCATION = FREE_STORAGE_POINTER() #define constant GET_ROLES_WITH_CAPABILITY_LOCATION = FREE_STORAGE_POINTER() +/// @notice Helper to get the storage slot holding a user's roles +/// @dev Callers that both read and write the entry should use this and keep the slot on the +/// stack, rather than hashing it once per access +#define macro GET_ROLES_FOR_ACCOUNT_SLOT() = takes (1) returns (1) { + [USER_ROLES_LOCATION] // [location, account] + MAPPING_SLOT() // [slot] +} + /// @notice Helper to get the roles for a user #define macro GET_ROLES_FOR_ACCOUNT() = takes (1) returns (1) { - [USER_ROLES_LOCATION] // [location, account] - LOAD_ELEMENT_FROM_KEYS(0x00) // [roles] + GET_ROLES_FOR_ACCOUNT_SLOT() // [slot] + sload // [roles] } /// @notice Returns if an account has a role @@ -45,9 +53,18 @@ 0x20 0x00 return // [] } +/// @notice Helper to get the storage slot holding the roles with a capability +/// @dev Callers that both read and write the entry should use this and keep the slot on the +/// stack, rather than hashing it once per access +#define macro GET_ROLE_FOR_CAPABILITY_SLOT() = takes (2) returns (1) { + // Input stack: [target, functionSig] + MAPPING_SLOT() // [slot] +} + /// @notice Helper to get the role for a capability #define macro GET_ROLE_FOR_CAPABILITY() = takes (2) returns (1) { - LOAD_ELEMENT_FROM_KEYS(0x00) // [role] + GET_ROLE_FOR_CAPABILITY_SLOT() // [slot] + sload // [role] } /// @notice Checks if a Role has a Capability @@ -67,7 +84,8 @@ /// @notice Helper to get if a capability is public #define macro IS_CAPABILITY_PUBLIC() = takes (2) returns (1) { - LOAD_ELEMENT_FROM_KEYS(0x00) // [public] + MAPPING_SLOT() // [slot] + sload // [public] } /// @notice Checks if an account can call a function on a given address @@ -145,39 +163,38 @@ // Disable the capability disable: - // Get the current roles with the capability + // Get the current roles with the capability. The entry is read and written, so hash it + // once and keep the slot on the stack 0x44 calldataload // [sig] 0x24 calldataload // [target, sig] - GET_ROLE_FOR_CAPABILITY() // [roles] + GET_ROLE_FOR_CAPABILITY_SLOT() // [slot] + dup1 sload // [roles, slot] // Shift 1 left the role - 0x01 0x04 calldataload shl // [role, roles] - not // [others, roles] - and // [updated] + 0x01 0x04 calldataload shl // [role, roles, slot] + not // [others, roles, slot] + and // [updated, slot] // Store the new capability - 0x44 calldataload // [sig, updated] - 0x24 calldataload // [target, sig, updated] - STORE_ELEMENT_FROM_KEYS(0x00) // [] + swap1 sstore // [] // Jump to the emit log label emit_log jump // Enable the capability enable: - // Get the current roles with the capability + // Get the current roles with the capability, hashing the entry once 0x44 calldataload // [sig] 0x24 calldataload // [target, sig] - GET_ROLE_FOR_CAPABILITY() // [roles] + GET_ROLE_FOR_CAPABILITY_SLOT() // [slot] + dup1 sload // [roles, slot] // Shift 1 left the role - 0x01 0x04 calldataload shl // [role, roles] - or // [capabilies] + 0x01 0x04 calldataload shl // [role, roles, slot] + or // [capabilies, slot] // Store the new capability - 0x44 calldataload // [sig, capabilies] - 0x24 calldataload // [target, sig, capabilies] - STORE_ELEMENT_FROM_KEYS(0x00) // [] + swap1 sstore // [] // Emit the capability updated event emit_log: @@ -208,37 +225,36 @@ // Disable the role disable: - // Get the account roles + // Get the account roles. The entry is read and written, so hash it once and keep the + // slot on the stack 0x04 calldataload // [account] - GET_ROLES_FOR_ACCOUNT() // [roles] + GET_ROLES_FOR_ACCOUNT_SLOT() // [slot] + dup1 sload // [roles, slot] // Shift 1 left the role - 0x01 0x24 calldataload shl // [new_role, roles] - not // [others, roles] - and // [updated] + 0x01 0x24 calldataload shl // [new_role, roles, slot] + not // [others, roles, slot] + and // [updated, slot] // Store the new roles - 0x04 calldataload // [account, updated] - [USER_ROLES_LOCATION] // [key, account, updated] - STORE_ELEMENT_FROM_KEYS(0x00) // [] + swap1 sstore // [] // Jump to the emit log label emit_log jump // Enable the role enable: - // Get the account roles + // Get the account roles, hashing the entry once 0x04 calldataload // [account] - GET_ROLES_FOR_ACCOUNT() // [roles] + GET_ROLES_FOR_ACCOUNT_SLOT() // [slot] + dup1 sload // [roles, slot] // Shift 1 left the role - 0x01 0x24 calldataload shl // [new_role, roles] - or // [updated] + 0x01 0x24 calldataload shl // [new_role, roles, slot] + or // [updated, slot] // Store the new roles - 0x04 calldataload // [account, updated] - [USER_ROLES_LOCATION] // [key, account, updated] - STORE_ELEMENT_FROM_KEYS(0x00) // [] + swap1 sstore // [] // Emit the user role updated event From fb25a3aa97583b6fdc1fb812cd4cc71deeeb1f73 Mon Sep 17 00:00:00 2001 From: kadenzipfel Date: Tue, 11 Aug 2026 14:42:14 -0700 Subject: [PATCH 5/7] Test MAPPING_SLOT macros --- test/data-structures/Hashmap.t.sol | 57 +++++++++++++++++++ .../mocks/HashmapWrappers.huff | 48 ++++++++++++++++ 2 files changed, 105 insertions(+) diff --git a/test/data-structures/Hashmap.t.sol b/test/data-structures/Hashmap.t.sol index e0b53377..54f65195 100644 --- a/test/data-structures/Hashmap.t.sol +++ b/test/data-structures/Hashmap.t.sol @@ -25,6 +25,10 @@ interface Hashmap { bytes32 key3, bytes32 value ) external; + function mappingSlot(bytes32 baseSlot, bytes32 key) external view returns (bytes32); + function mappingSlot2D(bytes32 baseSlot, bytes32 key1, bytes32 key2) external view returns (bytes32); + function mappingSlot3D(bytes32 baseSlot, bytes32 key1, bytes32 key2, bytes32 key3) + external view returns (bytes32); } contract HashmapTest is Test { @@ -89,4 +93,57 @@ contract HashmapTest is Test { hmap.storeElementFromKeys3D(slot, key_one, key_two, key_three, value); assertEq(hmap.loadElementFromKeys3D(slot, key_one, key_two, key_three), value); } + + /// >>>>>>>>>>>>>>>>>>> MAPPING_SLOT EQUIVALENCE <<<<<<<<<<<<<<<<<<<<< /// + + /// @notice MAPPING_SLOT must match the Solidity mapping slot convention. + function testMappingSlotMatchesConvention(bytes32 baseSlot, bytes32 key) public { + assertEq(hmap.mappingSlot(baseSlot, key), keccak256(abi.encode(key, baseSlot))); + } + + function testMappingSlot2DMatchesConvention(bytes32 baseSlot, bytes32 key1, bytes32 key2) public { + bytes32 inner = keccak256(abi.encode(key1, baseSlot)); + assertEq(hmap.mappingSlot2D(baseSlot, key1, key2), keccak256(abi.encode(key2, inner))); + } + + function testMappingSlot3DMatchesConvention( + bytes32 baseSlot, + bytes32 key1, + bytes32 key2, + bytes32 key3 + ) public { + bytes32 inner = keccak256(abi.encode(key2, keccak256(abi.encode(key1, baseSlot)))); + assertEq(hmap.mappingSlot3D(baseSlot, key1, key2, key3), keccak256(abi.encode(key3, inner))); + } + + /// @notice MAPPING_SLOT must resolve to the exact slot STORE_ELEMENT_FROM_KEYS writes to, + /// otherwise swapping one for the other silently relocates storage. + function testMappingSlotAgreesWithStoreElement(bytes32 key1, bytes32 key2, bytes32 value) public { + vm.assume(value != bytes32(0)); + hmap.storeElementFromKeys(key1, key2, value); + assertEq(vm.load(address(hmap), hmap.mappingSlot(key1, key2)), value); + } + + function testMappingSlot2DAgreesWithStoreElement( + bytes32 slot, + bytes32 key1, + bytes32 key2, + bytes32 value + ) public { + vm.assume(value != bytes32(0)); + hmap.storeElementFromKeys2D(slot, key1, key2, value); + assertEq(vm.load(address(hmap), hmap.mappingSlot2D(slot, key1, key2)), value); + } + + function testMappingSlot3DAgreesWithStoreElement( + bytes32 slot, + bytes32 key1, + bytes32 key2, + bytes32 key3, + bytes32 value + ) public { + vm.assume(value != bytes32(0)); + hmap.storeElementFromKeys3D(slot, key1, key2, key3, value); + assertEq(vm.load(address(hmap), hmap.mappingSlot3D(slot, key1, key2, key3)), value); + } } diff --git a/test/data-structures/mocks/HashmapWrappers.huff b/test/data-structures/mocks/HashmapWrappers.huff index c4b79630..0e7ca876 100644 --- a/test/data-structures/mocks/HashmapWrappers.huff +++ b/test/data-structures/mocks/HashmapWrappers.huff @@ -41,6 +41,15 @@ bytes32 value ) nonpayable returns () +// Returns the slot computed by MAPPING_SLOT for a base slot and one key. +#define function mappingSlot(bytes32 baseSlot, bytes32 key) view returns (bytes32) + +// Returns the slot computed by MAPPING_SLOT_2D for a base slot and two keys. +#define function mappingSlot2D(bytes32 baseSlot, bytes32 key1, bytes32 key2) view returns (bytes32) + +// Returns the slot computed by MAPPING_SLOT_3D for a base slot and three keys. +#define function mappingSlot3D(bytes32 baseSlot, bytes32 key1, bytes32 key2, bytes32 key3) view returns (bytes32) + #define constant LOCATION = FREE_STORAGE_POINTER() // Get the value for the given key. @@ -119,6 +128,36 @@ stop } +// Get the slot for a base slot and one key. +#define macro GET_MAPPING_SLOT() = takes(0) returns(0) { + 0x24 calldataload // [key] + 0x04 calldataload // [baseSlot, key] + MAPPING_SLOT() // [slot] + 0x00 mstore + 0x20 0x00 return +} + +// Get the slot for a base slot and two keys. +#define macro GET_MAPPING_SLOT_2D() = takes(0) returns(0) { + 0x44 calldataload // [key2] + 0x24 calldataload // [key1, key2] + 0x04 calldataload // [baseSlot, key1, key2] + MAPPING_SLOT_2D() // [slot] + 0x00 mstore + 0x20 0x00 return +} + +// Get the slot for a base slot and three keys. +#define macro GET_MAPPING_SLOT_3D() = takes(0) returns(0) { + 0x64 calldataload // [key3] + 0x44 calldataload // [key2, key3] + 0x24 calldataload // [key1, key2, key3] + 0x04 calldataload // [baseSlot, key1, key2, key3] + MAPPING_SLOT_3D() // [slot] + 0x00 mstore + 0x20 0x00 return +} + // Main Macro - The contract entrypoint #define macro MAIN() = takes(0) returns (0) { // Identify which function is being called using the 4 byte function signature @@ -132,6 +171,9 @@ dup1 __FUNC_SIG(storeElementFromKeys) eq store_element_from_keys jumpi dup1 __FUNC_SIG(storeElementFromKeys2D) eq store_element_from_keys_2d jumpi dup1 __FUNC_SIG(storeElementFromKeys3D) eq store_element_from_keys_3d jumpi + dup1 __FUNC_SIG(mappingSlot) eq mapping_slot jumpi + dup1 __FUNC_SIG(mappingSlot2D) eq mapping_slot_2d jumpi + dup1 __FUNC_SIG(mappingSlot3D) eq mapping_slot_3d jumpi // Revert if otherwise 0x00 dup1 revert @@ -152,4 +194,10 @@ STORE_FROM_KEYS_2D() store_element_from_keys_3d: STORE_FROM_KEYS_3D() + mapping_slot: + GET_MAPPING_SLOT() + mapping_slot_2d: + GET_MAPPING_SLOT_2D() + mapping_slot_3d: + GET_MAPPING_SLOT_3D() } From 357f592078f64d28d872da9327aff318020911b5 Mon Sep 17 00:00:00 2001 From: kadenzipfel Date: Tue, 11 Aug 2026 14:43:59 -0700 Subject: [PATCH 6/7] Use MAPPING_SLOT_2D instead of GET_SLOT_FROM_KEYS_2D --- src/tokens/ERC1155.huff | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tokens/ERC1155.huff b/src/tokens/ERC1155.huff index b22e391c..cc0366ef 100644 --- a/src/tokens/ERC1155.huff +++ b/src/tokens/ERC1155.huff @@ -677,7 +677,7 @@ #define macro DECREASE_BALANCE() = takes (3) returns (0) { // takes 3 [from, tokenId, amount] [BALANCE_LOCATION] // [&balance, from, tokenId, amount] - GET_SLOT_FROM_KEYS_2D(0x00) // [slot, amount] + MAPPING_SLOT_2D() // [slot, amount] dup1 // [slot, slot, amount] sload // [bal, slot, amount] swap1 // [slot, bal, amount] @@ -700,7 +700,7 @@ // input stack: [to, tokenId, amount] [BALANCE_LOCATION] // [&balance, to, tokenId, amount] - GET_SLOT_FROM_KEYS_2D(0x00) // [slot, amount] + MAPPING_SLOT_2D() // [slot, amount] dup1 // [slot, slot,amount] sload // [bal, slot, amount] swap1 // [slot, bal, amount] From e2e00a787a94370b0cd2546a3088a920101648e4 Mon Sep 17 00:00:00 2001 From: kadenzipfel Date: Tue, 11 Aug 2026 14:46:32 -0700 Subject: [PATCH 7/7] Update ERC4626 to optimize slot usage --- src/tokens/ERC4626.huff | 42 +++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/tokens/ERC4626.huff b/src/tokens/ERC4626.huff index 94e0d425..123d0e56 100644 --- a/src/tokens/ERC4626.huff +++ b/src/tokens/ERC4626.huff @@ -212,28 +212,29 @@ // Check if the caller is approved to redeem the shares - // Get the allowance[owner][msg.sender] + // Get the allowance[owner][msg.sender]. The slot is kept on the stack because the + // approval is read here and written again below caller dup4 // [owner, msg.sender, shares, receiver, owner] - [APPROVAL_SLOT] // [slot, owner, msg.sender, shares, receiver, owner] - LOAD_ELEMENT_FROM_KEYS_2D(0x00) // [allowance, shares, receiver, owner] + [APPROVAL_SLOT] MAPPING_SLOT_2D() // [slot, shares, receiver, owner] + dup1 sload // [allowance, slot, shares, receiver, owner] // If the allowed is no infinite approval, set to the allowance less shares - dup1 [TYPE_UINT_256_MAX] // [type(uint256).max, allowance, allowance, shares, receiver, owner] - eq infinite jumpi // [allowance, shares, receiver, owner] + dup1 [TYPE_UINT_256_MAX] // [type(uint256).max, allowance, allowance, slot, shares, receiver, owner] + eq infinite jumpi // [allowance, slot, shares, receiver, owner] // Revert unless the caller is approved for at least `shares` (allowance >= shares). - dup2 dup2 lt iszero redeem_approved jumpi // [allowance, shares, receiver, owner] + dup3 dup2 lt iszero redeem_approved jumpi // [allowance, slot, shares, receiver, owner] 0x00 dup1 revert redeem_approved: // Set the new allowance - dup2 dup2 sub // [new_allowance, allowance, shares, receiver, owner] - caller dup6 [APPROVAL_SLOT] // [slot, owner, msg.sender, new_allowance, allowance, shares, receiver, owner] - STORE_ELEMENT_FROM_KEYS_2D(0x00) // [allowance, shares, receiver, owner] + dup3 dup2 sub // [new_allowance, allowance, slot, shares, receiver, owner] + dup3 // [slot, new_allowance, allowance, slot, shares, receiver, owner] + sstore // [allowance, slot, shares, receiver, owner] // Jump dests for initial checks infinite: - pop // [shares, receiver, owner] + pop pop // [shares, receiver, owner] ahead: // Validate that the assets are non-zero @@ -391,27 +392,28 @@ // Skip ahead if msg.sender is the owner dup4 caller eq owner_jump jumpi // [shares, assets, receiver, owner] - // Get the allowance[owner][msg.sender] + // Get the allowance[owner][msg.sender]. The slot is kept on the stack because the + // approval is read here and written again below caller dup5 // [owner, msg.sender, shares, assets, receiver, owner] - [APPROVAL_SLOT] // [slot, owner, msg.sender, shares, assets, receiver, owner] - LOAD_ELEMENT_FROM_KEYS_2D(0x00) // [allowance, shares, assets, receiver, owner] + [APPROVAL_SLOT] MAPPING_SLOT_2D() // [slot, shares, assets, receiver, owner] + dup1 sload // [allowance, slot, shares, assets, receiver, owner] // If the allowed is no infinite approval, set to the allowance less shares - dup1 [TYPE_UINT_256_MAX] // [type(uint256).max, allowance, allowance, shares, assets, receiver, owner] - eq infinite jumpi // [allowance, shares, assets, receiver, owner] + dup1 [TYPE_UINT_256_MAX] // [type(uint256).max, allowance, allowance, slot, shares, assets, receiver, owner] + eq infinite jumpi // [allowance, slot, shares, assets, receiver, owner] // Revert unless the caller is approved for at least `shares` (allowance >= shares). - dup2 dup2 lt iszero withdraw_approved jumpi // [allowance, shares, assets, receiver, owner] + dup3 dup2 lt iszero withdraw_approved jumpi // [allowance, slot, shares, assets, receiver, owner] 0x00 dup1 revert withdraw_approved: // Set the new allowance - dup2 dup2 sub // [new_allowance, allowance, shares, assets, receiver, owner] - caller dup6 [APPROVAL_SLOT] // [slot, owner, msg.sender, new_allowance, allowance, shares, assets, receiver, owner] - STORE_ELEMENT_FROM_KEYS_2D(0x00) // [allowance, shares, assets, receiver, owner] + dup3 dup2 sub // [new_allowance, allowance, slot, shares, assets, receiver, owner] + dup3 // [slot, new_allowance, allowance, slot, shares, assets, receiver, owner] + sstore // [allowance, slot, shares, assets, receiver, owner] infinite: - pop // [shares, assets, receiver, owner] + pop pop // [shares, assets, receiver, owner] owner_jump: