flashloan-rs 0.2.1

Minimal Multicall3 Flashloan Module
Documentation
/// @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:
}