Langsung ke konten utama
BlockchainMar 28, 2026

Deep EVM #1: Cara EVM Mengeksekusi Kode Anda — Opcode, Stack, dan Gas

OS
Open Soft Team

Engineering Team

EVM Adalah Mesin Stack

Ethereum Virtual Machine tidak seperti prosesor x86 di laptop Anda. Ia tidak memiliki register. Sebaliknya, EVM adalah mesin stack — setiap komputasi mendorong ke atau mengambil dari stack 1024-elemen di mana setiap elemen adalah word 256-bit (32-byte).

Ketika Anda memanggil smart contract, EVM menerima bytecode kontrak — urutan datar opcode byte tunggal — dan mulai mengeksekusi dari byte 0. Tidak ada tabel fungsi, tidak ada header ELF, tidak ada langkah linking. Bytecode adalah programnya.

// Solidity:
// uint256 result = 2 + 3;

// Dikompilasi menjadi bytecode:
// PUSH1 0x02  PUSH1 0x03  ADD

// Jejak stack:
// []           -> PUSH1 0x02 -> [2]
// [2]          -> PUSH1 0x03 -> [2, 3]
// [2, 3]       -> ADD        -> [5]

Setiap opcode mengkonsumsi operand dari atas stack dan mendorong hasilnya kembali. Opcode ADD mengambil dua nilai, menjumlahkannya, dan mendorong hasilnya. Ini secara fundamental berbeda dari arsitektur berbasis register di mana Anda menentukan register sumber dan tujuan.

Kategori Opcode

EVM mendefinisikan sekitar 140 opcode, dikelompokkan ke dalam kategori fungsional:

Aritmatika dan Perbandingan

  • ADD, SUB, MUL, DIV, MOD — Aritmatika integer 256-bit dasar. Semua memerlukan 3 gas (tier G_verylow).
  • SDIV, SMOD — Pembagian dan modulo bertanda menggunakan two’s complement.
  • ADDMOD, MULMOD — Aritmatika modular: (a + b) % N dan (a * b) % N dalam satu opcode. Ini krusial untuk operasi kurva eliptik dan memerlukan 8 gas.
  • EXP — Eksponensial. Memerlukan 10 gas + 50 per byte dalam eksponen, menjadikannya salah satu opcode aritmatika termahal.
  • LT, GT, SLT, SGT, EQ, ISZERO — Opcode perbandingan yang mendorong 1 (benar) atau 0 (salah).

Operasi Bitwise

  • AND, OR, XOR, NOT — Logika bitwise, masing-masing 3 gas.
  • SHL, SHR, SAR — Shift left, logical shift right, arithmetic shift right (ditambahkan di Constantinople, EIP-145). Sebelum ini ada, shift memerlukan MUL/DIV dengan pangkat 2.
  • BYTE — Mengekstrak satu byte dari word 32-byte. BYTE(0, x) mengembalikan byte paling signifikan.

Manipulasi Stack

  • POP — Buang elemen teratas.
  • PUSH1 hingga PUSH32 — Dorong 1 hingga 32 byte data langsung ke stack. PUSH1 adalah opcode paling umum dalam bytecode yang di-deploy.
  • DUP1 hingga DUP16 — Duplikasi elemen stack ke-N ke atas.
  • SWAP1 hingga SWAP16 — Tukar elemen teratas dengan elemen ke-N di bawahnya.

Informasi Lingkungan dan Block

  • CALLER (msg.sender), CALLVALUE (msg.value), CALLDATALOAD, CALLDATASIZE, CALLDATACOPY — Akses konteks transaksi.
  • NUMBER, TIMESTAMP, BASEFEE, CHAINID — Informasi tingkat block.
  • BALANCE, EXTCODESIZE, EXTCODECOPY — Query akun lain.

Jadwal Gas

Setiap opcode memiliki biaya gas. Gas memiliki dua tujuan: mencegah loop tak terbatas (halting problem) dan menetapkan harga sumber daya komputasi secara adil.

Biaya gas jatuh ke dalam beberapa tier:

TierGasContoh
Zero0STOP, RETURN, REVERT
Base2ADDRESS, ORIGIN, CALLER
Very Low3ADD, SUB, LT, GT, AND, OR, POP
Low5MUL, DIV, MOD
Mid8ADDMOD, MULMOD, JUMP
High10JUMPI
SpecialbervariasiSLOAD, SSTORE, CALL, CREATE

Opcode yang mahal adalah yang menyentuh state:

// Biaya gas untuk akses state (pasca EIP-2929):
// SLOAD (cold):   2100 gas
// SLOAD (warm):    100 gas
// SSTORE (cold, 0->nonzero): 22100 gas
// SSTORE (warm):   100 gas (+ 20000 jika 0->nonzero)
// CALL (cold):    2600 gas
// CALL (warm):     100 gas
// BALANCE (cold): 2600 gas
// BALANCE (warm):  100 gas

Akses Cold vs Warm (EIP-2929)

EIP-2929 (upgrade Berlin, April 2021) memperkenalkan konsep daftar akses — kumpulan per-transaksi dari alamat dan slot penyimpanan yang telah disentuh.

Pertama kali Anda mengakses slot penyimpanan atau alamat eksternal dalam suatu transaksi, ia “cold” dan memerlukan gas ekstra. Akses berikutnya “warm” dan murah. Inilah mengapa urutan Anda membaca slot penyimpanan berpengaruh untuk optimasi gas.

// Di Solidity, pola ini mahal:
function buruk() external view returns (uint256) {
    uint256 a = myStorage; // Pembacaan pertama: 2100 gas (cold)
    // ... logika ...
    uint256 b = myStorage; // Pembacaan kedua: 100 gas (warm)
    return a + b;
}

// Cache di memory:
function baik() external view returns (uint256) {
    uint256 cached = myStorage; // 2100 gas (cold), hanya sekali
    return cached + cached;     // 6 gas (ADD + DUP)
}

Alur Eksekusi: Apa yang Terjadi dalam Transaksi

Ketika Anda mengirim transaksi yang memanggil kontrak, berikut urutan eksekusi lengkapnya:

  1. Validasi transaksi — Pemeriksaan nonce, saldo >= value + gas * gasPrice, verifikasi tanda tangan.
  2. Pengurangan gas intrinsik — 21000 gas untuk transaksi itu sendiri, ditambah 16 gas per byte calldata non-zero dan 4 per byte zero.
  3. Pengaturan konteks — EVM membuat konteks eksekusi: kode, calldata, pemanggil, value, gas tersisa.
  4. Program counter mulai dari 0 — EVM membaca opcode di posisi 0 dan mengeksekusinya.
  5. Eksekusi sekuensial — Setiap opcode dieksekusi, gas dikurangi. JUMP dan JUMPI memungkinkan alur kontrol non-linear, tetapi hanya ke posisi yang ditandai dengan JUMPDEST.
  6. Terminasi — Eksekusi berakhir dengan STOP (sukses, tanpa data kembali), RETURN (sukses, dengan data kembali), REVERT (gagal, state dikembalikan), atau kehabisan gas.
  7. Commit atau rollback state — Pada sukses, semua perubahan state di-commit. Pada revert, semua perubahan dalam konteks panggilan ini di-rollback.

Program Counter dan JUMP

Program counter (PC) adalah register implisit yang melacak posisi saat ini dalam bytecode. Sebagian besar opcode memajukan PC sebanyak 1 (atau 1 + N untuk opcode PUSH). Dua opcode memodifikasi PC secara langsung:

  • JUMP — Mengambil tujuan dari stack, mengatur PC ke nilai tersebut. Tujuan harus berisi opcode JUMPDEST atau transaksi di-revert.
  • JUMPI — Jump bersyarat. Mengambil tujuan dan kondisi. Jika kondisi bukan nol, jump; jika tidak, lanjut sekuensial.

Ini adalah cara EVM mengimplementasikan if/else, loop, dan dispatch fungsi. Compiler Solidity menghasilkan selektor fungsi yang memuat 4 byte pertama calldata, membandingkan dengan tanda tangan fungsi yang diketahui, dan JUMPI ke blok kode yang cocok.

Sub-panggilan: CALL, STATICCALL, DELEGATECALL

Kontrak dapat memanggil kontrak lain menggunakan tiga opcode panggilan:

  • CALL — Panggilan standar. Membuat konteks eksekusi baru dengan stack dan memory sendiri.
  • STATICCALL — Panggilan hanya-baca (EIP-214). Opcode yang memodifikasi state dalam callee menyebabkan revert langsung.
  • DELEGATECALL — Mengeksekusi kode callee tetapi dalam konteks storage pemanggil. msg.sender dan msg.value dipertahankan dari panggilan asli. Ini adalah cara kerja pola proxy dan library.

Implikasi Praktis untuk MEV

Jika Anda membangun bot MEV, memahami EVM di tingkat opcode bukan opsional — ini adalah kebutuhan kompetitif. Setiap unit gas yang dihemat dalam eksekusi bot Anda adalah margin keuntungan.

  • Simulasikan sebelum mengirim — Gunakan eth_call atau EVM lokal (revm, EVMONE) untuk menelusuri eksekusi.
  • Minimalkan akses cold — Panaskan slot penyimpanan melalui daftar akses (EIP-2930).
  • Gunakan STATICCALL untuk pembacaan — Sedikit lebih murah dan menjamin tidak ada mutasi state.
  • Ketahui biaya opcode Anda — Satu SLOAD yang salah tempat bisa memerlukan 2100 gas.

Kesimpulan

EVM elegan dalam kesederhanaannya: mesin stack dengan word 256-bit, format bytecode datar, dan sistem pengukuran gas yang menetapkan harga setiap operasi. Memahami fondasi ini — opcode, stack, dan gas — adalah prasyarat untuk segala sesuatu yang mengikuti dalam seri ini: tata letak memory, optimasi storage, primitif keamanan, dan akhirnya menulis Yul dan Huff mentah.