Deep EVM #12: Advanced Huff — Adaptive Execution and On-Chain Computation
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.