Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor: optimize Hashmap lib with BALLs #139

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down
100 changes: 100 additions & 0 deletions src/data-structures/Hashmap.balls
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/// @title HashMap
/// @notice SPDX-License-Identifier: MIT
/// @author asnared <https://github.com/abigger87>

/// @notice A Module Encapsulating HashMap Methods
/// @notice Adapted from <https://github.com/huff-language/huff-examples/blob/main/erc20/contracts/utils/HashMap.huff>

/// @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<mem_ptr>(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<mem_ptr>(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<mem_ptr>(slot, key1, key2) -> (hashed) {
// Load the data into memory.
mstore(add(mem_ptr, 0x20), slot)
mstore(mem_ptr, key1)
sha3(mem_ptr, 0x40)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the result of this hash needs to be stored and set into memory. Also wonder if you could save gas by storing add(mem_ptr, 0x20) in a var and just referencing that again


mstore(add(mem_ptr, 0x20), 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<mem_ptr>(slot, key1, key2, key3) -> (hashed) {
// Load the data into memory
mstore(add(mem_ptr, 0x20), slot)
// concat the first two keys
mstore(mem_ptr, key1)
sha3(mem_ptr, 0x40)

// put key2 in memory, before slot1
mstore(add(mem_ptr, 0x20), key2)
sha3(mem_ptr, 0x40)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly here you forget to save and write the result of the hash to memory

Copy link
Contributor Author

@eugenioclrc eugenioclrc May 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that i mess up, gonna change current test to also predict the expected slot :D


// put key3 in memory, before slot2
mstore(add(mem_ptr, 0x20), 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<mem_ptr>(key) -> (value) {
value = sload(GET_SLOT_FROM_KEY<mem_ptr>(key))
}

/// @notice Load an element onto the stack from two keys
fn LOAD_ELEMENT_FROM_KEYS<mem_ptr>(key1, key2) -> (value) {
value = sload(GET_SLOT_FROM_KEYS<mem_ptr>(key1, key2))
}

/// @notice Load an element onto the stack from a slot and two keys
fn LOAD_ELEMENT_FROM_KEYS_2D<mem_ptr>(slot, key1, key2) -> (value) {
value = sload(GET_SLOT_FROM_KEYS_2D<mem_ptr>(slot, key1, key2))
}

/// @notice Load an element onto the stack from a slot and three keys
fn LOAD_ELEMENT_FROM_KEYS_3D<mem_ptr>(slot, key1, key2, key3) -> (value) {
value = sload(GET_SLOT_FROM_KEYS_3D<mem_ptr>(slot, key1, key2, key3))
}

/// @notice Store an element from a key
fn STORE_ELEMENT<mem_ptr>(key, value) {
sstore(GET_SLOT_FROM_KEY<mem_ptr>(key), value)
}

/// @notice Store an element from two keys
fn STORE_ELEMENT_FROM_KEYS<mem_ptr>(key1, key2, value) {
sstore(GET_SLOT_FROM_KEYS<mem_ptr>(key1, key2), value)
}

/// @notice Store an element from a slot and two keys
fn STORE_ELEMENT_FROM_KEYS_2D<mem_ptr>(slot, key1, key2, value) {
sstore(GET_SLOT_FROM_KEYS_2D<mem_ptr>(slot, key1, key2), value)
}

/// @notice Store an element from a slot and three keys
fn STORE_ELEMENT_FROM_KEYS_3D<mem_ptr>(slot, key1, key2, key3, value) {
sstore(GET_SLOT_FROM_KEYS_3D<mem_ptr>(slot, key1, key2, key3), value)
}
229 changes: 116 additions & 113 deletions src/data-structures/Hashmap.huff
Original file line number Diff line number Diff line change
@@ -1,160 +1,163 @@
/// @title HashMap
/// @notice SPDX-License-Identifier: MIT
/// @author asnared <https://github.com/abigger87>

/// @notice A Module Encapsulating HashMap Methods
/// @notice Adapted from <https://github.com/huff-language/huff-examples/blob/main/erc20/contracts/utils/HashMap.huff>

/// @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.
<mem_ptr> // [<mem_ptr>, key]
mstore // []
/// @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.

// Hash the data, generating a key.
0x20 // [32]
<mem_ptr> // [<mem_ptr>, 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]
<mem_ptr> // [key, mem_ptr]
mstore // []
0x20 // [0x20]
<mem_ptr> // [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.
<mem_ptr> 0x20 add // [<mem_ptr> + 32, slot, key]
mstore // [key]
<mem_ptr> // [<mem_ptr>, key]
mstore // []

// Hash the data, generating a slot.
0x40 // [64]
<mem_ptr> // [<mem_ptr>, 64]
sha3 // [slot]
#define macro GET_SLOT_FROM_KEYS(mem_ptr) = takes(2) returns(1) {
// takes: [key, slot]
<mem_ptr> // [key, slot, mem_ptr]
0x20 // [key, slot, mem_ptr, 0x20]
add // [key, slot, add(0x20, mem_ptr)]
mstore // [key]
<mem_ptr> // [key, mem_ptr]
mstore // []
0x40 // [0x40]
<mem_ptr> // [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
<mem_ptr> 0x20 add // [<mem_ptr> + 32, slot, key1, key2]
mstore // [key1, key2]

// next byte
<mem_ptr> // [<mem_ptr>, key1, key2]
mstore // [key2]

0x40 // [0x40, key2]
<mem_ptr> // [<mem_ptr>, 0x40, key2]
sha3 // [hash, key2]

// concat the two keys
<mem_ptr> 0x20 add // [<mem_ptr> + 32, hash, key2] put hash in memory
mstore // [key2]

// next byte
<mem_ptr> // [<mem_ptr>, key2]
mstore // []

// Hash the data, generating a slot.
0x40 // [0x40]
<mem_ptr> // [<mem_ptr>, 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]
<mem_ptr> // [key2, key1, slot, 0x20, mem_ptr]
add // [key2, key1, slot, add(mem_ptr, 0x20)]
mstore // [key2, key1]
<mem_ptr> // [key2, key1, mem_ptr]
mstore // [key2]
0x20 // [key2, 0x20]
<mem_ptr> // [key2, 0x20, mem_ptr]
0x40 // [key2, 0x20, mem_ptr, 0x40]
<mem_ptr> // [key2, 0x20, mem_ptr, 0x40, mem_ptr]
sha3 // [key2, 0x20, mem_ptr, sha3(mem_ptr, 0x40)]
pop // [key2, 0x20, mem_ptr]
add // [key2, add(mem_ptr, 0x20)]
mstore // []
0x40 // [0x40]
<mem_ptr> // [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
<mem_ptr> 0x20 add // [<mem_ptr> + 32, slot, key1, key2, key3]
swap1 dup2 // [<mem_ptr> + 32, slot, <mem_ptr> + 32, key1, key2, key3]
mstore // [<mem_ptr> + 32, key1, key2, key3]

swap1 <mem_ptr> // [<mem_ptr>, key1, <mem_ptr> + 32, key2, key3]
mstore // [<mem_ptr> + 32, key2, key3]

0x40 // [0x40, <mem_ptr> + 32, key2, key3]
<mem_ptr> // [<mem_ptr>, 0x40, <mem_ptr> + 32, key2, key3]
sha3 // [slot1, <mem_ptr> + 32, key2, key3]

// concat the first two keys
dup2 // [<mem_ptr> + 32, slot1, <mem_ptr> + 32, key2, key3] put slot1 in memory
mstore // [<mem_ptr> + 32, key2, key3]

// put key2 in memory, before slot1
swap1 // [key2, <mem_ptr> + 32, key3]
<mem_ptr> // [<mem_ptr>, key2, <mem_ptr> + 32, key3]
mstore // [key3]

0x40 // [0x40, <mem_ptr> + 32, key3]
<mem_ptr> // [<mem_ptr>, 0x40, <mem_ptr> + 32, key3]
sha3 // [slot2, <mem_ptr> + 32, key3]

// concat with the third key
swap1 // [<mem_ptr> + 32, slot2, key3] put slot2 in memory
mstore // [key3]

// put key3 in memory, before slot2
<mem_ptr> // [<mem_ptr>, key3]
mstore // []

// Hash the data, generating the final slot3
0x40 // [0x40]
<mem_ptr> // [<mem_ptr>, 0x40]
sha3 // [slot3]
#define macro GET_SLOT_FROM_KEYS_3D(mem_ptr) = takes(4) returns(1) {
// takes: [key3, key2, key1, slot]
0x20 // [key3, key2, key1, slot, 0x20]
<mem_ptr> // [key3, key2, key1, slot, 0x20, mem_ptr]
add // [key3, key2, key1, slot, add(mem_ptr, 0x20)]
mstore // [key3, key2, key1]
<mem_ptr> // [key3, key2, key1, mem_ptr]
mstore // [key3, key2]
0x20 // [key3, key2, 0x20]
0x40 // [key3, key2, 0x20, 0x40]
<mem_ptr> // [key3, key2, 0x20, 0x40, mem_ptr]
sha3 // [key3, key2, 0x20, sha3(mem_ptr, 0x40)]
pop // [key3, key2, 0x20]
<mem_ptr> // [key3, key2, 0x20, mem_ptr]
add // [key3, key2, add(mem_ptr, 0x20)]
mstore // [key3]
0x20 // [key3, 0x20]
<mem_ptr> // [key3, 0x20, mem_ptr]
add // [key3, add(mem_ptr, 0x20)]
0x40 // [key3, add(mem_ptr, 0x20), 0x40]
<mem_ptr> // [key3, add(mem_ptr, 0x20), 0x40, mem_ptr]
sha3 // [key3, add(mem_ptr, 0x20), sha3(mem_ptr, 0x40)]
pop // [key3, add(mem_ptr, 0x20)]
mstore // []
0x40 // [0x40]
<mem_ptr> // [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(<mem_ptr>) // [slot]
sload // [value]
// takes: [key]
GET_SLOT_FROM_KEY(<mem_ptr>)
// [GET_SLOT_FROM_KEY<mem_ptr>(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(<mem_ptr>) // [slot]
sload // [value]
// takes: [key2, key1]
GET_SLOT_FROM_KEYS(<mem_ptr>)
// [GET_SLOT_FROM_KEYS<mem_ptr>(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(<mem_ptr>) // [slot]
sload // [value]
// takes: [key2, key1, slot]
GET_SLOT_FROM_KEYS_2D(<mem_ptr>)
// [GET_SLOT_FROM_KEYS_2D<mem_ptr>(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(<mem_ptr>) // [slot]
sload // [value]
// takes: [key3, key2, key1, slot]
GET_SLOT_FROM_KEYS_3D(<mem_ptr>)
// [GET_SLOT_FROM_KEYS_3D<mem_ptr>(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(<mem_ptr>) // [slot, value]
sstore // []
// takes: [value, key]
GET_SLOT_FROM_KEY(<mem_ptr>)
// [value, GET_SLOT_FROM_KEY<mem_ptr>(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(<mem_ptr>) // [slot, value]
sstore // []
#define macro STORE_ELEMENT_FROM_KEYS(mem_ptr) = takes(3) returns(0) {
// takes: [value, key2, key1]
GET_SLOT_FROM_KEYS(<mem_ptr>)
// [value, GET_SLOT_FROM_KEYS<mem_ptr>(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(<mem_ptr>) // [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(<mem_ptr>)
// [value, GET_SLOT_FROM_KEYS_2D<mem_ptr>(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(<mem_ptr>) // [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(<mem_ptr>)
// [value, GET_SLOT_FROM_KEYS_3D<mem_ptr>(slot, key1, key2, key3)]
sstore // []
// returns: []
}