Langsung ke konten utama
BlockchainMar 28, 2026

Deep EVM #11: Jump Table Huff — Dispatch Fungsi O(1) Tanpa Overhead Solidity

OS
Open Soft Team

Engineering Team

Masalah dengan Dispatcher Solidity

Ketika Anda memanggil kontrak Solidity, hal pertama yang dieksekusi EVM adalah function dispatcher. Solidity menghasilkan rantai if-else linear yang membandingkan 4 byte pertama calldata (function selector) dengan setiap selector yang diketahui:

CALLDATASIZE -> CALLDATALOAD -> SHR 224 -> DUP1 -> PUSH4 selector
-> EQ -> PUSH2 dest -> JUMPI -> ...

Untuk kontrak dengan N fungsi, ini O(N) — kasus terburuk memeriksa semua N selector sebelum menemukan kecocokan. Setiap perbandingan memerlukan sekitar 22 gas (DUP1 + PUSH4 + EQ + PUSH2 + JUMPI). Kontrak dengan 20 fungsi membuang hingga 440 gas hanya untuk dispatch.

Untuk kontrak bot MEV yang dipanggil jutaan kali, 400+ unit gas per panggilan bertambah menjadi ETH nyata.

Pendekatan Jump Table: O(1)

Jump table memetakan function selector langsung ke offset kode menggunakan aritmatika, bukan perbandingan. Idenya dipinjam dari arsitektur CPU — computed GOTO telah digunakan dalam pemrograman sistem sejak 1960-an.

Konsepnya:

  1. Ekstrak function selector dari calldata (4 byte).
  2. Gunakan aritmatika untuk menghitung tujuan jump dari selector.
  3. JUMP langsung ke tujuan tersebut.

Tanpa perbandingan, tanpa branching, waktu konstan terlepas dari berapa banyak fungsi yang ada.

Pendekatan 1: Encoding Selector Minimal

Jika kontrak Anda memiliki sejumlah kecil fungsi (1-8), Anda bisa menetapkan selector secara manual dengan mining vanity selector di mana byte pertama atau dua mengkodekan integer kecil yang unik:

#define macro DISPATCHER() = takes(0) returns(0) {
    0x00 calldataload       // [calldata_word]
    0xe0 shr                // [selector]

    // Ekstrak routing byte — byte pertama selector
    0x18 shr                // [first_byte]

    // Setiap entri di tabel kita adalah 2 byte (PUSH2 offset)
    0x02 mul                // [offset_dalam_tabel]
    __tablestart(JumpTable) // [table_start, offset_dalam_tabel]
    add                     // [entry_address]
    
    0x00 codecopy
    0x00 mload
    0xf0 shr
    jump
}

#define jumptable JumpTable {
    swap_exact
    add_liq
    remove_liq
    flash_loan
}

Pendekatan 2: Tabel Kode yang Dipadatkan

Untuk kepadatan maksimum, Anda bisa memadatkan jump table langsung ke dalam bytecode menggunakan __tablestart dan __tablesize. Huff secara native mendukung jump table sebagai konstruk kelas satu:

#define jumptable__packed SELECTOR_TABLE {
    fn_swap
    fn_transfer
    fn_approve
    fn_balance
}

#define macro MAIN() = takes(0) returns(0) {
    0x00 calldataload 0xe0 shr
    0x18 shr
    dup1 0x04 lt
    valid jumpi
    0x00 0x00 revert

    valid:
    __tablestart(SELECTOR_TABLE)
    swap1 0x02 mul add
    0x1e mload
    jump

    fn_swap:
        SWAP_IMPL()
    fn_transfer:
        TRANSFER_IMPL()
    fn_approve:
        APPROVE_IMPL()
    fn_balance:
        BALANCE_IMPL()
}

Perbandingan Gas

FungsiSolidity (if-else)Solidity (binary)Huff Jump Table
222-44 gas22-44 gas15 gas
422-88 gas22-66 gas15 gas
822-176 gas22-88 gas15 gas
1622-352 gas22-110 gas15 gas
3222-704 gas22-132 gas15 gas

Biaya jump table konstan: CALLDATALOAD (3) + SHR (3) + aritmatika (3-6) + JUMP (8) = ~15-18 gas. Tidak pernah berubah terlepas dari jumlah fungsi.

Mining Vanity Selector

Agar pendekatan jump table berfungsi, Anda memerlukan function selector dengan routing byte yang bisa diprediksi. Anda bisa mining ini:

import hashlib
import itertools

target_byte = 0x00
base_name = "swap"

for suffix in itertools.count():
    name = f"{base_name}{suffix}(uint256,address)"
    selector = hashlib.sha3_256(name.encode()).digest()[:4]
    if selector[0] == target_byte:
        print(f"Ditemukan: {name} -> 0x{selector.hex()}")
        break

Dalam praktik, kami menggunakan cast sig dari Foundry atau tool Rust yang brute-force nama fungsi untuk menemukan selector dengan prefix yang diinginkan.

Dampak Ukuran Bytecode

PendekatanRuntime BytecodeGas Deploy
Solidity (8 funcs)~800 byte160.000 gas
Huff jump table (8 funcs)~200 byte40.000 gas
Huff minimal (2 funcs)~61 byte12.200 gas

Untuk bot MEV yang sering men-redeploy kontrak, bytecode lebih kecil langsung diterjemahkan menjadi biaya operasional yang lebih rendah.

Batasan

  1. ABI non-standar — Tool eksternal (Etherscan, wallet) tidak bisa mendecode calldata Anda tanpa definisi ABI kustom.
  2. Mining selector — Memerlukan pekerjaan di muka dan membatasi penamaan fungsi.
  3. Biaya pemeliharaan — Huff lebih sulit diaudit dan dimodifikasi dari Solidity.
  4. Tabel yang dipadatkan — Builtin __tablestart dan __tablesize memiliki edge case dengan compiler Huff; uji secara menyeluruh.

Ringkasan

Jump table menggantikan rantai dispatch O(N) Solidity dengan computed jump O(1). Penghematan gas bertambah melintasi jutaan panggilan — keunggulan signifikan untuk kontrak frekuensi tinggi seperti bot MEV dan router DEX.