diff --git a/foundry.toml b/foundry.toml index d672a2c2..85ad840f 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,6 +2,7 @@ out = 'out' libs = ['lib'] ffi = true +evm_version = "cancun" fs_permissions = [ { access = "read", path = "./test/auth/mocks/AuthWrappers.huff" }, { access = "read", path = "./test/auth/mocks/OwnedWrappers.huff" }, diff --git a/src/data-structures/Hashmap.balls b/src/data-structures/Hashmap.balls new file mode 100644 index 00000000..5346695a --- /dev/null +++ b/src/data-structures/Hashmap.balls @@ -0,0 +1,98 @@ +/// @title HashMap +/// @notice SPDX-License-Identifier: MIT +/// @author asnared + +/// @notice A Module Encapsulating HashMap Methods +/// @notice Adapted from + +/// @notice Given a piece of data (ie an address), hash it, generating the storage slot for a hashmap. +/// @notice Port to BALLs by eugenioclrc taken from https://github.com/huff-language/huffmate/blob/4e2c9bd3412ab8cc65f6ceadafc01a1ff1815796/src/data-structures/Hashmap.huff + +/// @notice Given a piece of data (ie an address), hash it, generating the storage slot for a hashmap. +fn GET_SLOT_FROM_KEY(key) -> (hashed) { + // Load the data into memory and hash it, while preserving the memory location. + mstore(mem_ptr, key) + // Hash the data, generating a key. + hashed = sha3(mem_ptr, 0x20) +} + +/// @notice Given two keys (ie a slot and a key), hash them together, generating a slot for a secondary hashmap. +fn GET_SLOT_FROM_KEYS(slot, key) -> (hashed) { + // Load the data into memory. + mstore(add(mem_ptr, 0x20), slot) + mstore(mem_ptr, key) + + // Hash the data, generating a slot. + hashed = sha3(mem_ptr, 0x40) +} + + +/// @notice Calculate the slot from two keys +fn GET_SLOT_FROM_KEYS_2D(slot, key1, key2) -> (hashed) { + mstore(mem_ptr, key1) + + _ptr_plus_32 = add(mem_ptr, 0x20) + mstore(_ptr_plus_32, slot) + mstore(_ptr_plus_32, sha3(mem_ptr, 0x40)) + + mstore(mem_ptr, key2) + // Hash the data, generating a slot. + hashed = sha3(mem_ptr, 0x40) +} + +/// @notice Calculate the slot from three keys +fn GET_SLOT_FROM_KEYS_3D(slot, key1, key2, key3) -> (hashed) { + mstore(mem_ptr, key1) + + _ptr_plus_32 = add(mem_ptr, 0x20) + mstore(_ptr_plus_32, slot) + mstore(_ptr_plus_32, sha3(mem_ptr, 0x40)) + + mstore(mem_ptr, key2) + mstore(_ptr_plus_32, sha3(mem_ptr, 0x40)) + + // put key3 in memory, before slot2 + mstore(mem_ptr, key3) + // Hash the data, generating the final slot3 + hashed = sha3(mem_ptr, 0x40) +} + +/// @notice Load an element onto the stack from a key +fn LOAD_ELEMENT(key) -> (value) { + value = sload(GET_SLOT_FROM_KEY(key)) +} + +/// @notice Load an element onto the stack from two keys +fn LOAD_ELEMENT_FROM_KEYS(key1, key2) -> (value) { + value = sload(GET_SLOT_FROM_KEYS(key1, key2)) +} + +/// @notice Load an element onto the stack from a slot and two keys +fn LOAD_ELEMENT_FROM_KEYS_2D(slot, key1, key2) -> (value) { + value = sload(GET_SLOT_FROM_KEYS_2D(slot, key1, key2)) +} + +/// @notice Load an element onto the stack from a slot and three keys +fn LOAD_ELEMENT_FROM_KEYS_3D(slot, key1, key2, key3) -> (value) { + value = sload(GET_SLOT_FROM_KEYS_3D(slot, key1, key2, key3)) +} + +/// @notice Store an element from a key +fn STORE_ELEMENT(key, value) { + sstore(GET_SLOT_FROM_KEY(key), value) +} + +/// @notice Store an element from two keys +fn STORE_ELEMENT_FROM_KEYS(key1, key2, value) { + sstore(GET_SLOT_FROM_KEYS(key1, key2), value) +} + +/// @notice Store an element from a slot and two keys +fn STORE_ELEMENT_FROM_KEYS_2D(slot, key1, key2, value) { + sstore(GET_SLOT_FROM_KEYS_2D(slot, key1, key2), value) +} + +/// @notice Store an element from a slot and three keys +fn STORE_ELEMENT_FROM_KEYS_3D(slot, key1, key2, key3, value) { + sstore(GET_SLOT_FROM_KEYS_3D(slot, key1, key2, key3), value) +} diff --git a/src/data-structures/Hashmap.huff b/src/data-structures/Hashmap.huff index 91d8ab28..1ac4fbe8 100644 --- a/src/data-structures/Hashmap.huff +++ b/src/data-structures/Hashmap.huff @@ -1,160 +1,163 @@ /// @title HashMap /// @notice SPDX-License-Identifier: MIT /// @author asnared + /// @notice A Module Encapsulating HashMap Methods /// @notice Adapted from /// @notice Given a piece of data (ie an address), hash it, generating the storage slot for a hashmap. -#define macro GET_SLOT_FROM_KEY(mem_ptr) = takes(1) returns (1) { - // Input stack: [key] - // Load the data into memory and hash it, while preserving the memory location. - // [, key] - mstore // [] +/// @notice Port to BALLs by eugenioclrc taken from https://github.com/huff-language/huffmate/blob/4e2c9bd3412ab8cc65f6ceadafc01a1ff1815796/src/data-structures/Hashmap.huff - // Hash the data, generating a key. - 0x20 // [32] - // [, 32] - sha3 // [slot] +/// @notice Given a piece of data (ie an address), hash it, generating the storage slot for a hashmap. +#define macro GET_SLOT_FROM_KEY(mem_ptr) = takes(1) returns(1) { + // takes: [key] + // [key, mem_ptr] + mstore // [] + 0x20 // [0x20] + // [0x20, mem_ptr] + sha3 // [hashed] + // returns: [hashed] } /// @notice Given two keys (ie a slot and a key), hash them together, generating a slot for a secondary hashmap. -#define macro GET_SLOT_FROM_KEYS(mem_ptr) = takes(2) returns (1) { - // Input stack: [slot, key] - // Load the data into memory. - 0x20 add // [ + 32, slot, key] - mstore // [key] - // [, key] - mstore // [] - - // Hash the data, generating a slot. - 0x40 // [64] - // [, 64] - sha3 // [slot] +#define macro GET_SLOT_FROM_KEYS(mem_ptr) = takes(2) returns(1) { + // takes: [key, slot] + // [key, slot, mem_ptr] + 0x20 // [key, slot, mem_ptr, 0x20] + add // [key, slot, add(0x20, mem_ptr)] + mstore // [key] + // [key, mem_ptr] + mstore // [] + 0x40 // [0x40] + // [0x40, mem_ptr] + sha3 // [hashed] + // returns: [hashed] } /// @notice Calculate the slot from two keys -#define macro GET_SLOT_FROM_KEYS_2D(mem_ptr) = takes(3) returns (1) { - // Input stack: [slot, key1, key2] - // Load the data into memory - 0x20 add // [ + 32, slot, key1, key2] - mstore // [key1, key2] - - // next byte - // [, key1, key2] - mstore // [key2] - - 0x40 // [0x40, key2] - // [, 0x40, key2] - sha3 // [hash, key2] - - // concat the two keys - 0x20 add // [ + 32, hash, key2] put hash in memory - mstore // [key2] - - // next byte - // [, key2] - mstore // [] - - // Hash the data, generating a slot. - 0x40 // [0x40] - // [, 0x40] - sha3 // [slot] +#define macro GET_SLOT_FROM_KEYS_2D(mem_ptr) = takes(3) returns(1) { + // takes: [key2, key1, slot] + 0x20 // [key2, key1, slot, 0x20] + // [key2, key1, slot, 0x20, mem_ptr] + add // [key2, key1, slot, _ptr_plus_32] + swap2 // [key2, _ptr_plus_32, slot, key1] + // [key2, _ptr_plus_32, slot, key1, mem_ptr] + mstore // [key2, _ptr_plus_32, slot] + dup2 // [key2, _ptr_plus_32, slot, _ptr_plus_32] + mstore // [key2, _ptr_plus_32] + 0x40 // [key2, _ptr_plus_32, 0x40] + // [key2, _ptr_plus_32, 0x40, mem_ptr] + sha3 // [key2, _ptr_plus_32, sha3(mem_ptr, 0x40)] + swap1 // [key2, sha3(mem_ptr, 0x40), _ptr_plus_32] + mstore // [key2] + // [key2, mem_ptr] + mstore // [] + 0x40 // [0x40] + // [0x40, mem_ptr] + sha3 // [hashed] + // returns: [hashed] } /// @notice Calculate the slot from three keys -#define macro GET_SLOT_FROM_KEYS_3D(mem_ptr) = takes(4) returns (1) { - // Input stack: [slot, key1, key2, key3] - // Load the data into memory - 0x20 add // [ + 32, slot, key1, key2, key3] - swap1 dup2 // [ + 32, slot, + 32, key1, key2, key3] - mstore // [ + 32, key1, key2, key3] - - swap1 // [, key1, + 32, key2, key3] - mstore // [ + 32, key2, key3] - - 0x40 // [0x40, + 32, key2, key3] - // [, 0x40, + 32, key2, key3] - sha3 // [slot1, + 32, key2, key3] - - // concat the first two keys - dup2 // [ + 32, slot1, + 32, key2, key3] put slot1 in memory - mstore // [ + 32, key2, key3] - - // put key2 in memory, before slot1 - swap1 // [key2, + 32, key3] - // [, key2, + 32, key3] - mstore // [key3] - - 0x40 // [0x40, + 32, key3] - // [, 0x40, + 32, key3] - sha3 // [slot2, + 32, key3] - - // concat with the third key - swap1 // [ + 32, slot2, key3] put slot2 in memory - mstore // [key3] - - // put key3 in memory, before slot2 - // [, key3] - mstore // [] - - // Hash the data, generating the final slot3 - 0x40 // [0x40] - // [, 0x40] - sha3 // [slot3] +#define macro GET_SLOT_FROM_KEYS_3D(mem_ptr) = takes(4) returns(1) { + // takes: [key3, key2, key1, slot] + // [key3, key2, key1, slot, mem_ptr] + 0x20 // [key3, key2, key1, slot, mem_ptr, 0x20] + add // [key3, key2, key1, slot, _ptr_plus_32] + swap2 // [key3, key2, _ptr_plus_32, slot, key1] + // [key3, key2, _ptr_plus_32, slot, key1, mem_ptr] + mstore // [key3, key2, _ptr_plus_32, slot] + dup2 // [key3, key2, _ptr_plus_32, slot, _ptr_plus_32] + mstore // [key3, key2, _ptr_plus_32] + // [key3, key2, _ptr_plus_32, mem_ptr] + swap2 // [key3, mem_ptr, _ptr_plus_32, key2] + // [key3, mem_ptr, _ptr_plus_32, key2, mem_ptr] + 0x40 // [key3, mem_ptr, _ptr_plus_32, key2, mem_ptr, 0x40] + // [key3, mem_ptr, _ptr_plus_32, key2, mem_ptr, 0x40, mem_ptr] + sha3 // [key3, mem_ptr, _ptr_plus_32, key2, mem_ptr, sha3(mem_ptr, 0x40)] + dup4 // [key3, mem_ptr, _ptr_plus_32, key2, mem_ptr, sha3(mem_ptr, 0x40), _ptr_plus_32] + mstore // [key3, mem_ptr, _ptr_plus_32, key2, mem_ptr] + mstore // [key3, mem_ptr, _ptr_plus_32] + 0x40 // [key3, mem_ptr, _ptr_plus_32, 0x40] + // [key3, mem_ptr, _ptr_plus_32, 0x40, mem_ptr] + sha3 // [key3, mem_ptr, _ptr_plus_32, sha3(mem_ptr, 0x40)] + swap1 // [key3, mem_ptr, sha3(mem_ptr, 0x40), _ptr_plus_32] + mstore // [key3, mem_ptr] + mstore // [] + 0x40 // [0x40] + // [0x40, mem_ptr] + sha3 // [hashed] + // returns: [hashed] } /// @notice Load an element onto the stack from a key #define macro LOAD_ELEMENT(mem_ptr) = takes(1) returns(1) { - // Input stack: [key] - GET_SLOT_FROM_KEY() // [slot] - sload // [value] + // takes: [key] + GET_SLOT_FROM_KEY() + // [GET_SLOT_FROM_KEY(key)] + sload // [value] + // returns: [value] } /// @notice Load an element onto the stack from two keys #define macro LOAD_ELEMENT_FROM_KEYS(mem_ptr) = takes(2) returns(1) { - // Input stack: [key1, key2] - GET_SLOT_FROM_KEYS() // [slot] - sload // [value] + // takes: [key2, key1] + GET_SLOT_FROM_KEYS() + // [GET_SLOT_FROM_KEYS(key1, key2)] + sload // [value] + // returns: [value] } /// @notice Load an element onto the stack from a slot and two keys #define macro LOAD_ELEMENT_FROM_KEYS_2D(mem_ptr) = takes(3) returns(1) { - // Input stack: [slot, key1, key2] - GET_SLOT_FROM_KEYS_2D() // [slot] - sload // [value] + // takes: [key2, key1, slot] + GET_SLOT_FROM_KEYS_2D() + // [GET_SLOT_FROM_KEYS_2D(slot, key1, key2)] + sload // [value] + // returns: [value] } /// @notice Load an element onto the stack from a slot and three keys #define macro LOAD_ELEMENT_FROM_KEYS_3D(mem_ptr) = takes(4) returns(1) { - // Input stack: [slot, key1, key2, key3] - GET_SLOT_FROM_KEYS_3D() // [slot] - sload // [value] + // takes: [key3, key2, key1, slot] + GET_SLOT_FROM_KEYS_3D() + // [GET_SLOT_FROM_KEYS_3D(slot, key1, key2, key3)] + sload // [value] + // returns: [value] } /// @notice Store an element from a key #define macro STORE_ELEMENT(mem_ptr) = takes(2) returns(0) { - // Input stack: [key, value] - GET_SLOT_FROM_KEY() // [slot, value] - sstore // [] + // takes: [value, key] + GET_SLOT_FROM_KEY() + // [value, GET_SLOT_FROM_KEY(key)] + sstore // [] + // returns: [] } /// @notice Store an element from two keys -#define macro STORE_ELEMENT_FROM_KEYS(mem_ptr) = takes(3) returns (0) { - // Input stack: [key1, key2, value] - GET_SLOT_FROM_KEYS() // [slot, value] - sstore // [] +#define macro STORE_ELEMENT_FROM_KEYS(mem_ptr) = takes(3) returns(0) { + // takes: [value, key2, key1] + GET_SLOT_FROM_KEYS() + // [value, GET_SLOT_FROM_KEYS(key1, key2)] + sstore // [] + // returns: [] } /// @notice Store an element from a slot and two keys -#define macro STORE_ELEMENT_FROM_KEYS_2D(mem_ptr) = takes(4) returns (0) { - // Input stack: [slot, key1, key2, value] - GET_SLOT_FROM_KEYS_2D() // [slot, value] - sstore // [] +#define macro STORE_ELEMENT_FROM_KEYS_2D(mem_ptr) = takes(4) returns(0) { + // takes: [value, key2, key1, slot] + GET_SLOT_FROM_KEYS_2D() + // [value, GET_SLOT_FROM_KEYS_2D(slot, key1, key2)] + sstore // [] + // returns: [] } /// @notice Store an element from a slot and three keys -#define macro STORE_ELEMENT_FROM_KEYS_3D(mem_ptr) = takes(5) returns (0) { - // Input stack: [slot, key1, key2, key3, value] - GET_SLOT_FROM_KEYS_3D() // [slot, value] - sstore // [] +#define macro STORE_ELEMENT_FROM_KEYS_3D(mem_ptr) = takes(5) returns(0) { + // takes: [value, key3, key2, key1, slot] + GET_SLOT_FROM_KEYS_3D() + // [value, GET_SLOT_FROM_KEYS_3D(slot, key1, key2, key3)] + sstore // [] + // returns: [] } \ No newline at end of file diff --git a/src/math/SafeMath.huff b/src/math/SafeMath.huff index e735e84c..0c0ad8c8 100644 --- a/src/math/SafeMath.huff +++ b/src/math/SafeMath.huff @@ -46,7 +46,7 @@ dup1 // [num1, num1, num2] is_not_zero jumpi // [num1, num2] mul // [result] - 0x01 is_not_overflow jumpi + is_not_overflow jump is_not_zero: // [num1, num2] dup2 // [num2, num1, num2] dup2 // [num1, num2, num1, num2] @@ -82,4 +82,4 @@ [ARITHMETIC_OVERFLOW] PANIC() is_not_mod_zero: mod // [result] -} \ No newline at end of file +} diff --git a/test/data-structures/Hashmap.t.sol b/test/data-structures/Hashmap.t.sol index e0b53377..915a4e6f 100644 --- a/test/data-structures/Hashmap.t.sol +++ b/test/data-structures/Hashmap.t.sol @@ -47,9 +47,14 @@ contract HashmapTest is Test { /// @notice Test set a key function testSetKey(bytes32 key, bytes32 value) public { - assertEq(hmap.loadElement(key), bytes32(0)); + bytes32 expectedSlot = keccak256(abi.encode(key)); + assertEq(hmap.loadElement(key), bytes32(0), "Expected key to be empty"); + assertEq(vm.load(address(hmap), expectedSlot), bytes32(0), "Expected slot to be empty"); + hmap.storeElement(key, value); - assertEq(hmap.loadElement(key), value); + + assertEq(hmap.loadElement(key), value, "Expected key to be filled with value"); + assertEq(vm.load(address(hmap), expectedSlot), value, "Expected slot to be filled with value"); } /// @notice Test get with keys @@ -60,9 +65,14 @@ contract HashmapTest is Test { /// @notice Test set with keys function testSetKeys(bytes32 key_one, bytes32 key_two, bytes32 value) public { - assertEq(hmap.loadElementFromKeys(key_one, key_two), bytes32(0)); + bytes32 expectedSlot = keccak256(abi.encode(key_two, key_one)); + assertEq(hmap.loadElementFromKeys(key_one, key_two), bytes32(0), "Expected key to be empty"); + assertEq(vm.load(address(hmap), expectedSlot), bytes32(0), "Expected slot to be empty"); + hmap.storeElementFromKeys(key_one, key_two, value); - assertEq(hmap.loadElementFromKeys(key_one, key_two), value); + + assertEq(hmap.loadElementFromKeys(key_one, key_two), value, "Expected slot to be filled with value"); + assertEq(vm.load(address(hmap), expectedSlot), value, "Expected slot to be filled with value"); } /// @notice Test set with slot and 2 keys @@ -72,9 +82,16 @@ contract HashmapTest is Test { bytes32 key_two, bytes32 value ) public { - assertEq(hmap.loadElementFromKeys2D(slot, key_one, key_two), bytes32(0)); + bytes32 expectedSlot = keccak256(abi.encode(key_one, slot )); + expectedSlot = keccak256(abi.encode(key_two, expectedSlot)); + + assertEq(hmap.loadElementFromKeys2D(slot, key_one, key_two), bytes32(0), "Expected key to be empty"); + assertEq(vm.load(address(hmap), expectedSlot), bytes32(0), "Expected slot to be empty"); + hmap.storeElementFromKeys2D(slot, key_one, key_two, value); + assertEq(hmap.loadElementFromKeys2D(slot, key_one, key_two), value); + assertEq(vm.load(address(hmap), expectedSlot), value, "Expected slot to be filled with value"); } /// @notice Test set with slot and 3 keys @@ -85,8 +102,16 @@ contract HashmapTest is Test { bytes32 key_three, bytes32 value ) public { - assertEq(hmap.loadElementFromKeys3D(slot, key_one, key_two, key_three), bytes32(0)); + bytes32 expectedSlot = keccak256(abi.encode(key_one, slot )); + expectedSlot = keccak256(abi.encode(key_two, expectedSlot)); + expectedSlot = keccak256(abi.encode(key_three, expectedSlot)); + + assertEq(hmap.loadElementFromKeys3D(slot, key_one, key_two, key_three), bytes32(0), "Expected key to be empty"); + assertEq(vm.load(address(hmap), expectedSlot), bytes32(0), "Expected slot to be empty"); + hmap.storeElementFromKeys3D(slot, key_one, key_two, key_three, value); + assertEq(hmap.loadElementFromKeys3D(slot, key_one, key_two, key_three), value); + assertEq(vm.load(address(hmap), expectedSlot), value, "Expected slot to be filled with value"); } }