/// @title Merkle Distributor
/// @notice SPDX-License-Identifier: MIT
/// @author Ben Leimberger <https://github.com/benleim>
/// @author Magna <https://github.com/magna-eng>
/// @author asnared <https://github.com/abigger87>
/// @notice Minimal, gas efficient Merkle Distributor implementation
// Imports
#include "./CommonErrors.huff"
#include "./MerkleProofLib.huff"
#include "./Address.huff"
#include "./ERC20Transfer.huff"
// Interface
#define function claim(uint256,address,uint256,bytes32[]) nonpayable returns ()
#define function getMerkleRoot() view returns (bytes32)
#define function getTokenAddress() view returns (address)
#define function isClaimed(uint256) view returns (bool)
// Storage Slots
#define constant CLAIMED_BIT_MAP_SLOT = FREE_STORAGE_POINTER() // 0x00
#define constant TOKEN_ADDR_SLOT = FREE_STORAGE_POINTER() // 0x01
#define constant MERKLE_ROOT_SLOT = FREE_STORAGE_POINTER() // 0x02
// Errors
#define error TransferError(string)
#define error ClaimedError(string)
#define error ProofError(string)
/// @notice Get Merkle Root
/// @notice Entry point for: getMerkleRoot()
/// @dev Fetches merkle root from storage slot
/// @param {calldata} []
/// @return {return} [bytes32 root]
#define macro GET_MERKLE_ROOT() = takes (0) returns (0) {
// Load value from storage
[MERKLE_ROOT_SLOT] sload // [merkle_root]
// Store value in memory
0x00 mstore // []
// Return value
0x20 0x00 return // []
}
/// @notice Get Token Address
#define macro GET_TOKEN_ADDR() = takes (0) returns (0) {
// Load value from storage
[TOKEN_ADDR_SLOT] sload // [token_addr]
// Store value in memory
0x00 mstore // []
// Return value
0x20 0x00 return // []
}
/// Is Claimed
/// @notice Entry point for: isClaimed(uint256)
/// @dev Check if index is claimed
/// @param {calldata} [uint256 index]
/// @return {return} [bytes32 root]
#define macro IS_CLAIMED() = takes (0) returns (0) {
// Load first argument from calldata
0x04 calldataload // [index]
// Utility macro
__UTIL_IS_CLAIMED() // [isEqual]
// store result in memory
0x00 mstore // []
0x20 0x00 return
}
/// @dev stores result in 0x00 to 0x20 memory slot
#define macro __UTIL_IS_CLAIMED() = takes (1) returns (1) {
// Stack input: // [arg0]
// Stack output: // [isEqual]
// index / 256
0x100 dup2 div // [index]
__UTIL_GENERATE_MAPPING_KEY() // [key(claimed[index]), arg0]
// Load mapping key
sload // [claimed[index], arg0]
// index % 256 [claimed[index], arg0]
0x1 0x100 dup4 mod shl // [mask, claimed[index], arg0]
dup1 swap2 dup2 and // [masked, mask, claimed[index], arg0]
eq // [isEqual, claimed[index], arg0]
swap2 pop pop // [isEqual]
}
/// @notice Set as claimed
#define macro __UTIL_SET_CLAIMED() = takes (1) returns (0) {
// Stack input: // [arg0]
// index / 256
0x100 dup2 div // [index, arg0]
__UTIL_GENERATE_MAPPING_KEY() // [key(claimed[index]), arg0]
// Load mapping key
dup1 sload // [claimed[index], key(claimed[index]), arg0]
// index % 256 [claimed[index], key(claimed[index]), arg0]
0x1 0x100 dup5 mod shl // [mask, claimed[index], key(claimed[index]), arg0]
or // [masked, key(claimed[to]), arg0]
// Update
swap1 sstore pop // []
}
/// @notice Creates a mapping key from an index
#define macro __UTIL_GENERATE_MAPPING_KEY() = takes (1) returns (1) {
// Stack input: [index]
// Stack output: [key(claimed[index])]
0x00 mstore // []
[CLAIMED_BIT_MAP_SLOT] 0x20 mstore // []
// key(claimed[index]) = keccak256(index . claimedBitMapSlot)
0x40 0x00 sha3 // [key(claimed[index])]
}
/// @notice Claim
/// @notice Entry point for: claim(uint256,address,uint256,bytes[])
/// @dev Claim distribution with proof
/// @param {calldata} [uint256 index, address account, uint256 amount, bytes[] proof]
/// @return {return} []
#define macro CLAIM() = takes (0) returns (0) {
// Preload merkle root
[MERKLE_ROOT_SLOT] sload // [root]
// Load arguments from calldata
0x44 calldataload // [amount, root]
0x24 calldataload MASK_ADDRESS() // [account, amount, root]
0x04 calldataload // [index, account, amount, root]
// Check if an index is claimed
dup1 __UTIL_IS_CLAIMED() // [isClaimed, index, account, amount, root]
iszero cont jumpi // [index, account, amount, root]
ALREADY_CLAIMED(0x00)
cont:
// EncodePacked
// [ 32 bytes | 20 bytes | 32 bytes ] = 84 bytes
0x00 mstore // [account, amount, root]
0x60 shl // [account << 12, amount, root]
0x20 mstore // [amount, root]
0x34 mstore // [root]
0x54 0x00 sha3 // [leaf, root]
// Verify merkle proof
0x64 calldataload 0x4 add // [&proof_length, leaf, root]
// Required()
VERIFY_PROOF() // [isProven]
verified jumpi // []
INVALID_PROOF(0x00)
verified:
// Mark it claimed
0x04 calldataload // [arg0]
__UTIL_SET_CLAIMED() // []
// Load other calldata arguments
[TOKEN_ADDR_SLOT] sload // [getter_addr]
0x44 calldataload // [amount, getter_addr]
0x24 calldataload // [address_raw, amount, getter_addr]
MASK_ADDRESS() // [address, amount, getter_addr]
// Send the token
ERC20_TRANSFER()
// Finish Execution
stop
}
/// @notice Constructor
/// @param address The address of the token to distribute
/// @param merkleRoot The merkle root of the merkle tree
#define macro MERKLE_DISTRIBUTOR_CONSTRUCTOR() = takes (0) returns (0) {
// Copy the first argument into memory
0x20 // [size] - byte size to copy
0x40 codesize sub // [offset, size] - offset in the code to copy from
0x00 // [mem, offset, size] - offset in memory to copy to
codecopy // []
// Store the first argument in storage
0x00 mload // [arg1]
[TOKEN_ADDR_SLOT] // [TOKEN_ADDR, arg1]
sstore // []
// Copy the second argument into memory
0x20 // [size] - byte size to copy
0x20 codesize sub // [offset, size] - offset in the code to copy from
0x00 // [mem, offset, size] - offset in memory to copy to
codecopy // []
// Store the second argument in storage
0x00 mload // [arg2]
[MERKLE_ROOT_SLOT] // [CONSTRUCTOR_ARG_TWO, arg2]
sstore // []
}
/// @notice Function Dispatch
/// @notice Takes the first 4 bytes of calldata (aka the function selector) and dispatches to the appropriate function
/// @notice If non match, execution proceeds as the code is inlined
#define macro MERKLE_DISTRIBUTOR_MAIN() = takes (1) returns (1) {
// Input Stack: [function_selector]
dup1 __FUNC_SIG(getTokenAddress) eq getTokenAddress jumpi
dup1 __FUNC_SIG(getMerkleRoot) eq getMerkleRoot jumpi
dup1 __FUNC_SIG(isClaimed) eq isClaimed jumpi
dup1 __FUNC_SIG(claim) eq claim jumpi
// Jump to the end if non match
no_match jump
getMerkleRoot:
GET_MERKLE_ROOT()
getTokenAddress:
GET_TOKEN_ADDR()
isClaimed:
IS_CLAIMED()
claim:
CLAIM()
no_match:
}