Skip to content
This repository has been archived by the owner on Sep 9, 2024. It is now read-only.

Commit

Permalink
Merge pull request #72 from pentagonxyz/dev
Browse files Browse the repository at this point in the history
staging: Huffmate V1.1
  • Loading branch information
Maddiaa0 authored Feb 21, 2023
2 parents e870794 + b57f42b commit 98d397f
Show file tree
Hide file tree
Showing 35 changed files with 1,813 additions and 423 deletions.
246 changes: 134 additions & 112 deletions .gas-snapshot

Large diffs are not rendered by default.

23 changes: 5 additions & 18 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,9 @@
name: ci

on:
# manual trigger
workflow_dispatch:

# on push
push:
paths:
- test/**
- src/**
on: [push]

jobs:
check:
strategy:
fail-fast: true
name: Foundry project
runs-on: ubuntu-latest
steps:
Expand All @@ -33,8 +23,6 @@ jobs:
id: build

tests:
strategy:
fail-fast: true
name: Foundry project
runs-on: ubuntu-latest
steps:
Expand Down Expand Up @@ -69,9 +57,8 @@ jobs:
with:
version: nightly
- name: Huff Tests
uses: abigger87/huff-tests@v2.0.0
uses: huff-language/huff-tests-action@v3
with:
with-forge-tests: false
test-location: "src"
test-extension: "*.huff"

with-location: "src"
with-extension: "*.huff"
with-format: "table"
19 changes: 14 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<img align="right" width="150" height="150" top="100" src="./assets/huff.jpg">

# huffmate • [![ci](https://github.com/pentagonxyz/huffmate/actions/workflows/test.yml/badge.svg)](https://github.com/pentagonxyz/huffmate/actions/workflows/test.yml) [![version](https://img.shields.io/badge/version-v1-ff69b4)](https://github.com/pentagonxyz/huffmate/releases/tag/v1) [![license](https://img.shields.io/badge/License-MIT-orange.svg?label=license)](https://opensource.org/licenses/MIT) ![Discord](https://img.shields.io/discord/980519274600882306?color=blue)
# huffmate • [![ci](https://github.com/pentagonxyz/huffmate/actions/workflows/test.yml/badge.svg)](https://github.com/pentagonxyz/huffmate/actions/workflows/test.yml) [![version](https://img.shields.io/badge/version-v1.1-ff69b4)](https://github.com/pentagonxyz/huffmate/releases/tag/v1.1) [![license](https://img.shields.io/badge/License-MIT-orange.svg?label=license)](https://opensource.org/licenses/MIT) ![Discord](https://img.shields.io/discord/980519274600882306?color=blue)

A set of **modern**, **opinionated**, and **secure** [Huff](https://github.com/huff-language) contracts.

Expand Down Expand Up @@ -37,6 +37,7 @@ auth
data-structures
├─ Arrays — "Memory translation handlers for arrays"
├─ Hashmap — "Simple mapping utilities for 32 byte words"
├─ Bytes — "Helpers for working with Bytes"
proxies
├─ Clones — "Clones library for deploying minimal proxy contracts"
├─ Proxy — "Minimal ERC1967-compliant + upgradeable proxy contract"
Expand All @@ -61,20 +62,28 @@ tokens
├─ ERC1155 — "Minimalist and gas efficient standard ERC1155 implementation"
├─ ERC4626 — "Minimal ERC4626 tokenized Vault implementation"
utils
├─ Calls — "Minimal wrappers for constructing calls to other contracts"
├─ Address — "Simple Utils for working with addresses"
├─ BitPackLib — "Efficient bit packing library"
├─ CustomErrors — "Wrappers for reverting with common error messages"
├─ Calls — "Minimal wrappers for constructing calls to other contracts"
├─ CommonErrors — "Wrappers for reverting with common error messages"
├─ CREATE3 — "Deploy to deterministic addresses without the initcode factor"
├─ ECDSA — "An optimised ECDSA wrapper"
├─ ERC1155Receiver — "A minimal interface for receiving ERC1155 tokens"
├─ ERC20Transfer — "Efficient ERC20 transfer wrappers"
├─ Errors — "Custom error utilities"
├─ Ethers — "Utilities for working with ether at a low level"
├─ JumpTableUtil — "Utility macros for retrieving jumpdest pcs from jump tables"
├─ LibBit — "A library ported from solady for bit twiddling operations"
├─ MerkleProofLib — "Gas optimized merkle proof verification library"
├─ Multicallable — "Enables a single call to call multiple methods within a contract"
├─ TSOwnable — "An Ownable Implementation using Two-Step Transfer Pattern"
├─ Pausable — "An implementation of the Pausable standard"
├─ ReentrancyGuard — "Gas optimized reentrancy protection for smart contracts"
├─ Refunded — "Efficient gas refunds distributed through a modifier"
├─ SafeTransferLib — "Safe ETH and ERC20 transfer library that gracefully handles missing return values"
├─ Shuffling — "Refactored algorithms for shuffling and other bitwise algorithms"
└─ SSTORE2 — "Faster & cheaper contract key-value storage for Ethereum Contracts"
└─ Ternary — "A collection of ternary operations with abstracted conditional logic"
└─ TSOwnable — "An Ownable Implementation using Two-Step Transfer Pattern"
```

### Acknowledgements
Expand All @@ -88,7 +97,7 @@ These contracts were inspired by or directly modified from many sources, primari
- [huff-rs](https://github.com/huff-language/huff-rs)
- [huff-clones](https://github.com/clabby/huff-clones)
- [huff-vrgda](https://github.com/cheethas/huff-vrgda)
- [huff-tests](https://github.com/abigger87/huff-tests)
- [huff-tests](https://github.com/huff-language/huff-tests-action)
- [erc721h](https://github.com/philogy/erc721h)
- [Gnosis](https://github.com/gnosis/gp-v2-contracts)
- [Uniswap](https://github.com/Uniswap/uniswap-lib)
Expand Down
6 changes: 5 additions & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ fs_permissions = [

{ access = "read", path = "./test/data-structures/mocks/ArrayWrappers.huff" },
{ access = "read", path = "./test/data-structures/mocks/HashmapWrappers.huff" },
{ access = "read", path = "./test/data-structures/mocks/BytesWrappers.huff" },

{ access = "read", path = "./test/math/mocks/FixedPointMathWrappers.huff" },
{ access = "read", path = "./test/math/mocks/SafeMathWrappers.huff" },
Expand All @@ -32,18 +33,21 @@ fs_permissions = [
{ access = "read", path = "./test/utils/mocks/BitPackLibWrappers.huff" },
{ access = "read", path = "./test/utils/mocks/CallWrappers.huff" },
{ access = "read", path = "./test/utils/mocks/ErrorWrappers.huff" },
{ access = "read", path = "./test/utils/mocks/EthersWrappers.huff" },
{ access = "read", path = "./test/utils/mocks/JumpTableUtilWrappers.huff" },
{ access = "read", path = "./test/utils/mocks/LibBitWrappers.huff" },
{ access = "read", path = "./test/utils/mocks/MerkleDistributorWrappers.huff" },
{ access = "read", path = "./test/utils/mocks/MerkleProofLibWrappers.huff" },
{ access = "read", path = "./test/utils/mocks/MulticallableWrappers.huff" },
{ access = "read", path = "./test/utils/mocks/Create3Wrappers.huff" },
{ access = "read", path = "./test/utils/mocks/PausableWrappers.huff" },
{ access = "read", path = "./test/utils/mocks/ReentrancyGuardWrappers.huff" },
{ access = "read", path = "./test/utils/mocks/RefundedWrappers.huff" },
{ access = "read", path = "./test/utils/mocks/SafeTransferLibWrappers.huff" },
{ access = "read", path = "./test/utils/mocks/ShufflingWrappers.huff" },
{ access = "read", path = "./test/utils/mocks/SSTORE2Wrappers.huff" },
{ access = "read", path = "./test/utils/mocks/TSOwnableWrappers.huff" }
{ access = "read", path = "./test/utils/mocks/TSOwnableWrappers.huff" },
{ access = "read", path = "./test/utils/mocks/ECDSAWrappers.huff" },
]
remappings = [
"forge-std/=lib/forge-std/src/",
Expand Down
111 changes: 111 additions & 0 deletions src/data-structures/Bytes.huff
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/// @title Bytes
/// @notice SPDX-License-Identifier: MIT
/// @author Franfran <https://github.com/iFrostizz>
/// @notice Low-level operations on bytes
/// @notice Adapted from BytesLib (https://github.com/GNSPS/solidity-bytes-utils/blob/master/contracts/BytesLib.sol)

/// @notice Concatenate two bytes arrays
/// @notice Takes in two pointers of the bytes to concatenate that must be sorted
/// @return Pointer of the new appended concatenated bytes array in the memory
/// @dev Warning! This assumes that the pointer in the memory of the second bytes chunk is after mem_ptr1 + 0x20
#define macro CONCAT_MEMORY() = takes(2) returns(1) {
// input stack // [mem_ptr1, mem_ptr2]

// setup stack and memory for the next iterations
dup2 mload swap1 // [mem_ptr1, len2, mem_ptr2]
msize swap1 // [mem_ptr1, free_loc_pos, len2, mem_ptr2]
dup1 mload dup4 // [len2, len1, mem_ptr1, free_loc_pos, len2, mem_ptr2]
dup2 add msize mstore // [len1, mem_ptr1, free_loc_pos, len2, mem_ptr2]

swap1 0x20 add // [index(i), len1, free_loc_pos, len2, mem_ptr2]
msize // [index(j), index(i), len1, free_loc_pos, len2, mem_ptr2]
swap2 0x00 // [is_sec_loop, len1, index(i), index(j), free_loc_pos, len2, mem_ptr2]

// i is the index where we get (mload) the array element and j is the index where we store (mstore) the array at j
loop: // [is_sec_loop, len_left, index(i), index(j), free_loc_pos, len2, mem_ptr2]
dup2 iszero empty_slot jumpi // [is_sec_loop, len_left, index(i), index(j), free_loc_pos, len2, mem_ptr2]

dup3 mload // [word, is_sec_loop, len_left, index(i), index(j), free_loc_pos, len2, mem_ptr2]
dup3 0x20 gt iszero // [is_full_slot, word, is_sec_loop, len_left, index(i), index(j), free_loc_pos, len2, mem_ptr2]
full_slot jumpi // [word, is_sec_loop, len_left, index(i), index(j), free_loc_pos, len2, mem_ptr2]

// else it's not a full slot, we're hitting an end. Then clean memory slot and update j with a partial length
dup3 0x20 sub // [pad_len, word, is_sec_loop, len_left, index(i), index(j), free_loc_pos, len2, mem_ptr2]
0x08 mul swap1 dup2 // [shift, word, shift, is_sec_loop, len_left, index(i), index(j), free_loc_pos, len2, mem_ptr2]
shr // [left_padded_word, shift, is_sec_loop, len_left, index(i), index(j), free_loc_pos, len2, mem_ptr2]
swap1 shl // [clean_word, is_sec_loop, len_left, index(i), index(j), free_loc_pos, len2, mem_ptr2]
dup5 mstore // [is_sec_loop, len_left, index(i), index(j), free_loc_pos, len2, mem_ptr2]
swap3 add swap2 // [is_sec_loop, index(i), index(j + 1), free_loc_pos, len2, mem_ptr2]

// here we check if current loop is for the 2nd array
swap1 pop // [is_sec_loop, index(j + 1), free_loc_pos, len2, mem_ptr2]
iszero bridge jumpi // [index(j + 1), free_loc_pos, len2, mem_ptr2]
pop break jump // [free_loc_pos, len2, mem_ptr2]

empty_slot: // [is_sec_loop, len_left, index(i), index(j), free_loc_pos, len2, mem_ptr2]
swap2 pop pop // [is_sec_loop, index(j), free_loc_pos, len2, mem_ptr2]
iszero bridge jumpi // [index(j), free_loc_pos, len2, mem_ptr2]
pop break jump // [free_loc_pos, len2, mem_ptr2]

bridge: // [index(j), free_loc_pos, len2, mem_ptr2]
dup4 0x20 add // [index(i), index(j), free_loc_pos, len2, mem_ptr2]
dup5 // [len2, index(i), index(j), free_loc_pos, len2, mem_ptr2]
0x01 // [is_sec_loop, len2, index(i), index(j), free_loc_pos, len2, mem_ptr2]
loop jump

full_slot: // [word, is_sec_loop, len_left, index(i), index(j), free_loc_pos, len2, mem_ptr2]
dup5 mstore // [is_sec_loop, len_left, index(i), index(j), free_loc_pos, len2, mem_ptr2]
swap1 0x20 swap1 sub // [len_left - 0x20, is_sec_loop, index(i), index(j), free_loc_pos, len2, mem_ptr2]
swap2 0x20 add // [index(i + 1), is_sec_loop, len_left - 0x20, index(j), free_loc_pos, len2, mem_ptr2]
swap3 0x20 add // [index(j + 1), is_sec_loop, len_left - 0x20, index(i + 1), free_loc_pos, len2, mem_ptr2]
swap3 swap2 swap1 // [is_sec_loop, len_left - 0x20, index(i + 1), index(j + 1), free_loc_pos, len2, mem_ptr2]
loop jump

break: // [free_loc_pos, len2, mem_ptr2]
swap2 pop pop // [free_loc_pos]
}

/// @param Pointer in memory of the start of the bytes array
/// @param Start position of the slice relative to the array
/// @param Length of the output slice
/// @return Pointer of the new appended concatenated bytes array in the memory
/// @dev Warning! This assumes that the length of the output slice is less or equal the length of the bytes array (bytes.len < slice.len)
/// @dev Warning! This assumes that the start of the bytes array is not out of bounds (start < len + mem_ptr)
#define macro SLICE_MEMORY() = takes(3) returns(1) {
// input stack // [mem_ptr, start, length]

msize dup4 msize mstore // [free_loc_pos, mem_ptr, start, length]
msize swap4 // [length, free_loc_pos, mem_ptr, start, index(j)]
// index(i) = mem_ptr + start + 0x20
swap1 swap3 // [start, length, mem_ptr, free_loc_pos, index(j)]
swap1 swap2 // [mem_ptr, start, length, free_loc_pos, index(j)]
0x20 add add // [index(i), length, free_loc_pos, index(j)]

// we load our slice chunk at i and store it in a free memory location at j
loop: // [index(i), length_left, free_loc_pos, index(j)]
dup1 mload // [slice_chunk, index(i), length_left, free_loc_pos, index(j)]

// if current is not full slot, then load the last bytes and break
0x20 dup4 lt // [is_not_full_slot, slice_chunk, index(i), length_left, free_loc_pos, index(j)]
break jumpi // [slice_chunk, index(i), length_left, free_loc_pos, index(j)]

dup5 mstore // [index(i), length_left, free_loc_pos, index(j)]

0x20 add swap3 // [free_loc, length_left, free_loc_pos, index(i+1)]
0x20 add swap3 // [index(i+1), length_left, free_loc_pos, free_loc + 1]
swap1 0x20 // [0x20, length_left, index(i+1), free_loc_pos, free_loc + 1]
swap1 sub // [length_left - 1, index(i+1), free_loc_pos, free_loc + 1]
swap1 // [index(i+1), length_left - 1, free_loc_pos, free_loc + 1]

loop jump

break: // [slice_chunk, index(i), length, free_loc_pos, index(j)]
// store the remaining length
dup3 0x20 sub // [zero_length, slice_chunk, index(i), length, free_loc_pos, index(j)]
0x08 mul swap1 dup2 // [shift, slice_chunk, shift, index(i), length, free_loc_pos, index(j)]
shr // [left_pad_slice, shift, index(i), length, free_loc_pos, index(j)]
swap1 shl // [slice_chunk, index(i), length, free_loc_pos, index(j)]
dup5 mstore // [index(i), length, free_loc_pos, index(j)]

pop pop swap1 pop // [free_loc_pos]
}
57 changes: 57 additions & 0 deletions src/data-structures/Hashmap.huff
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,49 @@
sha3 // [key]
}

/// @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> // [<mem_ptr>, slot, key1, key2, key3]
mstore // [key1, key2, key3]

// next byte
<mem_ptr> 0x20 add // [<mem_ptr> + 32, key1, key2, key3]
swap1 dup2 // [<mem_ptr> + 32, 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]
}

/// @notice Load an element onto the stack from a key
#define macro LOAD_ELEMENT(mem_ptr) = takes(1) returns(1) {
// Input stack: [key]
Expand All @@ -83,6 +126,13 @@
sload // [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]
}

/// @notice Store an element from a key
#define macro STORE_ELEMENT(mem_ptr) = takes(2) returns(0) {
// Input stack: [key, value]
Expand All @@ -103,3 +153,10 @@
GET_SLOT_FROM_KEYS_2D(<mem_ptr>) // [slot, value]
sstore // []
}

/// @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 // []
}
6 changes: 2 additions & 4 deletions src/math/SafeMath.huff
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,7 @@
/// @notice Divides two numbers and reverts on division by zero
#define macro SAFE_DIV() = takes (2) returns (1) {
// input stack // [num1, num2]
dup2 // [num2, num1, num2]
0x00 swap1 // [num2, 0, num1, num2]
0x00 dup3 // [num2, 0, num1, num2]
gt // [is_not_div_zero, num1, num2]
is_not_div_zero jumpi
[DIVIDE_BY_ZERO] PANIC()
Expand All @@ -77,8 +76,7 @@
/// @notice Divides two numbers and reverts on division by zero or modulo zero
#define macro SAFE_MOD() = takes (2) returns (1) {
// input stack // [num1, num2]
dup2 // [num2, num1, num2]
0x00 swap1 // [num2, 0, num1, num2]
0x00 dup3 // [num2, 0, num1, num2]
gt // [is_not_mod_zero, num1, num2]
is_not_mod_zero jumpi
[ARITHMETIC_OVERFLOW] PANIC()
Expand Down
4 changes: 2 additions & 2 deletions src/tokens/ERC20.huff
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,7 @@
dup1 __FUNC_SIG(approve) eq approveJump jumpi

// Bubble up to the parent macro
no_match jumpi
no_match jump

allowanceJump:
ALLOWANCE()
Expand Down Expand Up @@ -598,4 +598,4 @@
TRANSFER()

no_match:
}
}
Loading

0 comments on commit 98d397f

Please sign in to comment.