Langsung ke konten utama
BlockchainMar 28, 2026

Debugging Bytecode EVM — Trace, Stack Dump, dan cast run

OS
Open Soft Team

Engineering Team

Tantangan Debugging EVM

Debugging kontrak EVM — terutama yang ditulis di Huff atau Yul — berbeda fundamental dari debugging software tradisional. Tidak ada breakpoint, tidak ada variabel watch, tidak ada stack trace yang bermakna. Yang Anda miliki adalah:

  • Urutan opcode yang dieksekusi
  • State stack di setiap langkah
  • Perubahan memory dan storage
  • Gas yang dikonsumsi per opcode

Artikel ini mengajarkan cara menggunakan alat-alat ini secara efektif.

Foundry Trace dengan Verbosity Tinggi

Foundry menyediakan empat level verbosity:

forge test -v    # Level 1: pass/fail
forge test -vv   # Level 2: + log output
forge test -vvv  # Level 3: + execution trace
forge test -vvvv # Level 4: + opcode trace

Level 4 (-vvvv) menampilkan setiap opcode yang dieksekusi:

[CALL] 0x1234...::swap(uint256) [gas: 80000]
  ├─ [SLOAD] slot 0x00 → 0x...reserve0
  ├─ [SLOAD] slot 0x01 → 0x...reserve1
  ├─ [MUL] 997 * amountIn → amountWithFee
  ├─ [CALL] 0xtoken::transfer(pair, amount)
  │   ├─ [SLOAD] balances[sender] → balance
  │   ├─ [SUB] balance - amount → newBalance
  │   ├─ [SSTORE] balances[sender] = newBalance
  │   └─ [RETURN] true
  └─ [RETURN] success

cast run: Replay Transaksi

cast run memungkinkan Anda me-replay transaksi mainnet apa pun dengan trace lengkap:

# Replay transaksi dengan trace
cast run 0x1234...txhash --rpc-url $ETH_RPC -vvvv

# Replay pada block tertentu
cast run 0x1234...txhash --rpc-url $ETH_RPC --block 18000000

Ini sangat berguna untuk:

  • Menganalisis mengapa transaksi MEV gagal
  • Mempelajari strategi pencari lain
  • Memahami interaksi kontrak yang kompleks

Membaca Execution Trace

Execution trace menampilkan hierarki panggilan:

Transaction: 0xabc...
From: 0x123...
To: 0x456...
Value: 0
Gas: 150000
Gas Used: 85432

Trace:
[85432] 0x456::fallback()
  ├─ [2100] SLOAD(0x00) → operator_bitfield
  ├─ [3] CALLER → 0x123
  ├─ [3] AND(operator_bitfield, mask) → 1 (authorized)
  ├─ [3] CALLDATALOAD(0) → 0x01... (opcode)
  ├─ [45000] 0xWETH::transfer(pair, 1000000000000000000)
  │   ├─ [2100] SLOAD(balances[0x456]) → 5000000000000000000
  │   ├─ [100] SSTORE(balances[0x456]) = 4000000000000000000
  │   ├─ [20000] SSTORE(balances[pair]) += 1000000000000000000
  │   └─ [0] RETURN(true)
  ├─ [35000] 0xPair::swap(0, amountOut, recipient, "")
  │   └─ [0] RETURN()
  └─ [0] STOP

Mendekode Revert Reason

Ketika transaksi revert, data revert sering berisi informasi berguna:

# Dekode revert data
cast decode-error 0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000

# Output: Error(string): "Insufficient balance"

Untuk custom error di Solidity 0.8+:

cast decode-error 0x..."custom selector"

Untuk kontrak Huff yang revert tanpa data (revert(0, 0)), Anda hanya mendapatkan empty revert — inilah mengapa trace sangat penting.

Debugging Stack Error

Kesalahan stack paling umum di Huff:

1. Stack Underflow

Mengambil dari stack kosong. EVM revert tanpa pesan.

Diagnosa: Trace menunjukkan REVERT setelah opcode yang mengkonsumsi (ADD, SUB, dll) ketika stack tidak memiliki cukup item.

Fix: Tambahkan komentar stack di setiap baris dan verifikasi takes() annotation cocok.

2. Wrong Value on Stack

Nilai yang salah di posisi stack yang salah — hasilnya benar tetapi data yang salah.

Diagnosa: Gunakan forge debug untuk step-through:

forge debug --debug src/test/MyTest.t.sol::testSwap

Interactive debugger menampilkan stack di setiap langkah.

3. Off-by-one DUP/SWAP

dup3 ketika seharusnya dup4 karena push tambahan sebelumnya.

Diagnosa: Trace stack state di setiap opcode dan bandingkan dengan komentar.

Gas Profiling

# Gas report per fungsi
forge test --gas-report

# Output:
| Function      | Min  | Avg   | Median | Max   |
|---------------|------|-------|--------|-------|
| swap()        | 65000| 72000 | 71000  | 85000 |
| withdraw()    | 21000| 25000 | 24000  | 30000 |

Untuk profiling granular per opcode:

forge test -vvvv --match-test testSwap 2>&1 | grep "gas:"

Alat Debugging Tambahan

Tenderly

Platform web yang menyediakan visual debugger untuk transaksi mainnet. Menampilkan state changes, event, dan execution trace dalam format visual.

evm-trace (Python)

from evm_trace import TraceFrame
# Parse dan analisis trace dari node debug_traceTransaction

hevm

Symbolic execution engine yang bisa membuktikan properti tentang bytecode EVM.

Best Practice Debugging

  1. Tulis komentar stack — Satu-satunya dokumentasi yang diperlukan di Huff
  2. Test incrementally — Bangun dan test makro satu per satu
  3. Gunakan fork testing — Bug sering muncul hanya dengan state nyata
  4. Pin block number — Untuk reprodusibilitas sempurna
  5. Simpan trace gagal — Untuk analisis root cause nanti
  6. Bandingkan gas — Regresi gas sering mengindikasikan bug logika

Kesimpulan

Debugging bytecode EVM memerlukan mentalitas berbeda dari debugging tradisional. Execution trace menggantikan breakpoint, stack dump menggantikan variabel watch, dan gas profiling menggantikan performance profiler. Kuasai alat-alat ini dan debugging kontrak Huff menjadi sistematik alih-alih trial-and-error.