メインコンテンツへスキップ
BlockchainMar 28, 2026

Deep EVM #12: Advanced Huff — Adaptive Execution and On-Chain Computation

OS
Open Soft Team

Engineering Team

Beyond Hello World

The previous articles covered Huff fundamentals — macros, stack management, jump tables. Now we move to production patterns extracted from real MEV bot contracts. These patterns solve problems that Solidity cannot express efficiently: adaptive execution based on calldata, multi-operator authorization without storage reads, and memory layouts that minimize bytecode size.

Pattern 1: Adaptive Execution — The amount_in == 0 Fallback

In an MEV arbitrage contract, the bot pre-computes the optimal input amount off-chain and passes it via calldata. But sometimes the bot does not know the exact balance available — for example, when a previous swap in the same bundle deposits tokens into the contract, and the exact output amount depends on pool state at execution time.

The solution: if amount_in == 0 in calldata, the contract reads its own balance on-chain and uses that instead.

#define macro GET_AMOUNT_IN() = takes(0) returns(1) {
    // Read amount from calldata[1..33]
    0x01 calldataload           // [amount_in]
    dup1                        // [amount_in, amount_in]
    use_calldata_amount jumpi   // [amount_in]

    // amount_in == 0 → read on-chain balance
    pop                         // []

    // Build balanceOf(address(this)) call
    // selector: 0x70a08231
    0x70a08231                  // [selector]
    0xe0 shl                    // [selector << 224]
    0x00 mstore                 // [] — memory[0x00] = selector
    address                     // [this]
    0x04 mstore                 // [] — memory[0x04] = address(this)

    // staticcall to token
    0x20                        // [retSize]
    0x00                        // [retOffset]
    0x24                        // [argSize]
    0x00                        // [argOffset]
    [TOKEN]                     // [token, argOffset, argSize, retOffset, retSize]
    gas                         // [gas, token, ...]
    staticcall                  // [success]
    pop                         // []
    0x00 mload                  // [balance] — our actual token balance
    // falls through to use this as amount_in

    use_calldata_amount:
    // Stack: [amount_in] (either from calldata or balanceOf)
}

This pattern adds ~200 gas when the fallback triggers (the staticcall) but zero gas when amount_in is provided in calldata (just a DUP + JUMPI). The bot uses the calldata path 90% of the time and falls back to on-chain reads only when executing multi-step bundles where intermediate amounts are unpredictable.

Pattern 2: Multi-Operator Auth via Priority Fee Entropy

MEV bots often need multiple operators (hot wallets) that can call the contract. Storing authorized addresses in a mapping costs 2,100 gas per SLOAD (cold) or 100 gas (warm). For a contract called once per block, every call is cold.

An alternative: encode the operator’s authorization in the transaction’s tx.gasprice (or more precisely, the priority fee). The bot sets maxPriorityFeePerGas to a value that contains a secret nonce:

#define constant AUTH_MASK = 0xFFFF  // last 16 bits of priority fee
#define constant AUTH_SECRET = 0xBEEF

#define macro CHECK_AUTH() = takes(0) returns(0) {
    // Extract priority fee: gasprice - basefee
    gasprice            // [gasprice]
    basefee             // [basefee, gasprice]
    swap1 sub           // [priority_fee]

    // Check last 16 bits match secret
    [AUTH_MASK]         // [mask, priority_fee]
    and                 // [fee & mask]
    [AUTH_SECRET]       // [secret, fee & mask]
    eq                  // [authorized?]
    authorized jumpi
    0x00 0x00 revert

    authorized:
}

This costs only 14 gas (GASPRICE + BASEFEE + SUB + AND + EQ + JUMPI) compared to 2,100+ for a storage-based approach. The trade-off: the secret is visible in the mempool. But MEV bots use Flashbots or private mempools, so the transaction is never publicly visible before inclusion.

You can encode different secrets for different operators by partitioning the priority fee bits — e.g., bits 0-15 for the auth token, bits 16-31 for the operator ID.

Pattern 3: USDT Safe Approve (Reset to Zero)

USDT’s approve function reverts if the current allowance is non-zero and you try to set a new non-zero value. This is a notorious quirk that has broken countless DeFi integrations. In Huff, we handle it by always resetting to zero first:

#define macro SAFE_APPROVE() = takes(2) returns(0) {
    // takes: [spender, amount]

    // First: approve(spender, 0)
    0x095ea7b3 0xe0 shl     // [approve_selector]
    0x00 mstore             // memory[0x00] = selector
    dup2                    // [spender, spender, amount]
    0x04 mstore             // memory[0x04] = spender  [spender, amount]
    0x00 0x24 mstore        // memory[0x24] = 0        [spender, amount]

    0x00                    // [retSize]
    0x00                    // [retOffset]
    0x44                    // [argSize]
    0x00                    // [argOffset]
    0x00                    // [value (no ETH)]
    [USDT]                  // [token]
    gas call                // [success]
    pop                     // [] — ignore result, some tokens return nothing

    // Second: approve(spender, amount)
    // selector is still in memory[0x00]
    // spender is still in memory[0x04]
    swap1                   // [amount, spender]
    0x24 mstore             // memory[0x24] = amount  [spender]
    pop                     // []

    0x00 0x00 0x44 0x00 0x00
    [USDT] gas call
    pop
}

This pattern works for all ERC20 tokens, not just USDT — it is a safe universal approve. The extra call costs ~2,600 gas (warm CALL) but prevents silent failures.

Pattern 4: WETH Deposit and Withdraw

WETH (Wrapped Ether) conversion is a frequent operation in MEV bots. The ABI is simple:

#define constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2

#define macro WETH_DEPOSIT() = takes(1) returns(0) {
    // takes: [amount]
    // WETH.deposit{value: amount}()
    0x00 0x00 0x00 0x00     // [0, 0, 0, 0, amount] — retSize, retOff, argSize, argOff
    swap4                   // [amount, 0, 0, 0, 0]
    [WETH]                  // [weth, amount, 0, 0, 0, 0]
    gas call                // [success]
    pop
}

#define macro WETH_WITHDRAW() = takes(1) returns(0) {
    // takes: [amount]
    // WETH.withdraw(amount)
    0x2e1a7d4d 0xe0 shl     // [selector, amount]
    0x00 mstore             // memory[0] = selector     [amount]
    0x04 mstore             // memory[4] = amount       []

    0x00 0x00 0x24 0x00 0x00
    [WETH] gas call
    pop
}

The deposit function is particularly elegant — WETH’s deposit has no arguments, so we send zero argSize with the ETH value. Total bytecode for deposit: ~20 bytes.

Memory Layout Tricks

EVM memory is byte-addressable and free to expand (costs gas quadratically for expansion). MEV contracts use a fixed memory layout to avoid redundant MSTORE operations:

// Fixed memory layout — never changes between calls
// 0x00 - 0x03:  Current function selector (4 bytes)
// 0x04 - 0x23:  Argument 1 (32 bytes)
// 0x24 - 0x43:  Argument 2 (32 bytes)
// 0x44 - 0x63:  Argument 3 (32 bytes)
// 0x64 - 0x83:  Return data scratch (32 bytes)
// 0x80 - 0x9f:  Stack spill area (32 bytes)
// 0xa0 - 0xbf:  Second spill slot (32 bytes)

The key insight: if you are making multiple external calls with the same selector (e.g., multiple transfer calls), you only write the selector once. On subsequent calls, you only update the arguments that changed.

#define macro MULTI_TRANSFER() = takes(0) returns(0) {
    // Setup: write transfer selector once
    0xa9059cbb 0xe0 shl
    0x00 mstore                 // memory[0] = transfer(address,uint256)

    // Transfer 1: to=Alice, amount=100
    [ALICE] 0x04 mstore
    0x64 0x24 mstore            // 100
    0x00 0x00 0x44 0x00 0x00 [TOKEN] gas call pop

    // Transfer 2: to=Bob, amount=200
    // Selector still in memory[0] — no need to rewrite!
    [BOB] 0x04 mstore
    0xc8 0x24 mstore            // 200
    0x00 0x00 0x44 0x00 0x00 [TOKEN] gas call pop

    // Transfer 3: to=Bob, amount=300
    // Address still in memory[4] — only update amount!
    0x012c 0x24 mstore          // 300
    0x00 0x00 0x44 0x00 0x00 [TOKEN] gas call pop
}

Each avoided MSTORE saves 3 gas + the PUSH for the value. Across a multi-hop arbitrage with 5-6 swap calls, this saves 50-100 gas.

The Free Memory Pointer Myth

Solidity maintains a “free memory pointer” at 0x40 that tracks the next available memory offset. This is an abstraction for dynamic memory allocation (arrays, strings, abi.encode). In Huff, you do not need it.

MEV contracts have a fixed, known set of external calls. You can statically assign memory regions at compile time. No free memory pointer means:

  • No 3-gas MLOAD to read the pointer before every memory write.
  • No 3-gas MSTORE to update the pointer after every allocation.
  • No risk of memory collision from reentrancy (your layout is deterministic).

Delete the free memory pointer. Own your memory map.

Combining Patterns: A Production Swap Macro

Here is a production-grade Uniswap V2 swap macro that combines several patterns:

#define macro SWAP_V2() = takes(3) returns(0) {
    // takes: [amountOut, zeroForOne, pair]

    // Build swap(uint256,uint256,address,bytes) call
    0x022c0d9f 0xe0 shl
    0x00 mstore                     // selector

    // amount0Out = zeroForOne ? 0 : amountOut
    // amount1Out = zeroForOne ? amountOut : 0
    swap1                           // [zeroForOne, amountOut, pair]
    skip_zero jumpi                 // [amountOut, pair]

    // zeroForOne == 0: token1 → token0, amount0Out = amountOut
    dup1 0x04 mstore                // amount0Out = amountOut
    0x00 0x24 mstore                // amount1Out = 0
    done_amounts jump

    skip_zero:                      // [amountOut, pair]
    0x00 0x04 mstore                // amount0Out = 0
    dup1 0x24 mstore                // amount1Out = amountOut

    done_amounts:
    pop                             // [pair]
    address 0x44 mstore             // to = address(this)
    0x80 0x64 mstore                // bytes offset
    0x00 0x84 mstore                // bytes length = 0

    // call pair.swap
    0x00 0x00 0xa4 0x00 0x00
    swap5                           // [pair, ...]
    gas call

    // Check success
    iszero revert_swap jumpi
    stop

    revert_swap:
        returndatasize 0x00 0x00 returndatacopy
        returndatasize 0x00 revert
}

This is 120 bytes of bytecode. The equivalent Solidity with SafeERC20, interface types, and ABI encoding generates 400+ bytes.

Summary

Advanced Huff is about production patterns, not academic exercises. The amount_in == 0 fallback lets your bot adapt to unpredictable intermediate states. Priority fee auth eliminates cold SLOAD costs. USDT safe approve prevents silent reverts. Fixed memory layouts eliminate redundant writes. Every pattern saves 50-200 gas — and in MEV, those savings compound across thousands of daily executions into meaningful profit. In the next article, we shift focus from writing contracts to exploiting them: an introduction to MEV — extractable value, searchers, and block builders.