Langsung ke konten utama
BlockchainMar 28, 2026

Deep EVM #2: Model Memory — Stack, Memory, Storage, dan Calldata

OS
Open Soft Team

Engineering Team

Empat Lokasi Data di EVM

EVM memiliki empat lokasi data yang berbeda, masing-masing dengan karakteristik biaya dan persistensi yang unik. Memilih lokasi yang tepat adalah faktor terbesar yang menentukan biaya gas kontrak Anda.

1. Stack

Stack adalah area kerja utama EVM. Menampung hingga 1024 elemen, masing-masing 32 byte. Biaya akses: 3 gas per operasi (PUSH, POP, DUP, SWAP). Stack bersifat sementara — hilang setelah eksekusi selesai.

Stack adalah satu-satunya tempat di mana operasi aritmatika dan logika dapat dilakukan. Anda tidak bisa menambahkan dua angka yang ada di memory — Anda harus memuatnya ke stack terlebih dahulu.

2. Memory

Memory adalah array byte yang dapat diperluas secara linear. Dimulai kosong dan tumbuh sesuai kebutuhan. Biaya tumbuh secara kuadratik — ini penting.

// Biaya memory:
// Biaya = 3 * ukuran_word + ukuran_word^2 / 512
// Di mana ukuran_word = ceil(ukuran_byte / 32)

Memory bersifat sementara — hanya ada selama panggilan saat ini. Sub-panggilan mendapatkan memory baru yang kosong. Ini berarti Anda tidak bisa meneruskan data besar ke sub-panggilan melalui memory — Anda harus menggunakan calldata.

Operasi memory utama:

  • MSTORE(offset, value) — Simpan word 32-byte di offset. Biaya: 3 gas + biaya ekspansi.
  • MLOAD(offset) — Muat word 32-byte dari offset. Biaya: 3 gas + biaya ekspansi.
  • MSTORE8(offset, value) — Simpan 1 byte di offset. Biaya: 3 gas + biaya ekspansi.
  • MCOPY(dst, src, size) — Salin byte dalam memory (EIP-5656, Cancun). Biaya: 3 gas + 3 per word + biaya ekspansi.

3. Storage

Storage adalah database key-value persisten kontrak. Setiap kontrak memiliki 2^256 slot, masing-masing 32 byte. Storage bertahan di antara transaksi — ini adalah state kontrak Anda.

// Setiap variabel state Solidity menempati slot storage:
uint256 public value;     // slot 0
uint256 public count;     // slot 1
mapping(address => uint) public balances;  // slot 2 (dasar)

Biaya storage sangat tinggi karena setiap node di jaringan harus menyimpan data ini selamanya:

  • SLOAD (cold): 2100 gas
  • SLOAD (warm): 100 gas
  • SSTORE (cold, 0 ke nonzero): 22100 gas
  • SSTORE (warm, nonzero ke nonzero): 2900 gas

4. Calldata

Calldata adalah data input read-only yang dikirim bersama transaksi atau panggilan. Tidak bisa dimodifikasi selama eksekusi.

// Biaya calldata per transaksi:
// 16 gas per byte non-zero
//  4 gas per byte zero

Calldata murah untuk dibaca (CALLDATALOAD = 3 gas) dan ukurannya diketahui sebelumnya (CALLDATASIZE). Ini adalah cara paling efisien untuk meneruskan data ke kontrak.

Free Memory Pointer

Solidity mempertahankan “free memory pointer” di memory offset 0x40. Pointer ini melacak di mana memory yang belum digunakan dimulai.

// Setiap fungsi Solidity dimulai dengan:
assembly {
    let fmp := mload(0x40)  // Baca free memory pointer
    // Alokasi 64 byte:
    mstore(0x40, add(fmp, 0x40))  // Update pointer
    // Gunakan fmp..fmp+64 sebagai scratch space
}

Di Yul atau Huff, Anda bisa mengabaikan free memory pointer sepenuhnya dan mengelola memory secara manual — menghemat gas yang digunakan Solidity untuk bookkeeping.

Tata Letak Memory Solidity

Solidity memesan beberapa offset memory:

OffsetTujuan
0x00-0x1fScratch space untuk hashing
0x20-0x3fScratch space untuk hashing
0x40-0x5fFree memory pointer
0x60-0x7fSlot zero (selalu 0x00)
0x80+Area alokasi dinamis

Ketika Anda menulis Yul inline, Anda bisa menggunakan 0x00-0x3f sebagai scratch space tanpa khawatir menimpa data — Solidity hanya menggunakan area ini untuk operasi hashing sementara.

Packing Storage Slot

EVM selalu membaca dan menulis 32 byte sekaligus dari storage. Solidity secara otomatis mengemas variabel yang lebih kecil dari 32 byte ke dalam slot yang sama:

contract Packed {
    uint128 a;  // slot 0, byte 0-15
    uint128 b;  // slot 0, byte 16-31
    uint256 c;  // slot 1, byte 0-31 (terlalu besar untuk berbagi)
}

Membaca a memerlukan: SLOAD slot 0 (2100/100 gas) + AND untuk mask (3 gas). Menulis a memerlukan: SLOAD + AND + OR + SSTORE. Packing menghemat slot tetapi menambah kompleksitas bitwise.

Perbandingan Biaya

OperasiBiaya GasCatatan
Stack push/pop3Paling murah
Memory baca/tulis3 + ekspansiMurah untuk ukuran kecil
Calldata baca3Murah, read-only
Storage baca (cold)2100Mahal
Storage baca (warm)100Masih mahal relatif
Storage tulis2900-22100Sangat mahal

Strategi Optimasi

  1. Cache pembacaan storage — Baca sekali ke variabel memory/stack, gunakan cache untuk pembacaan berikutnya.
  2. Pack variabel state — Kelompokkan variabel kecil ke dalam slot 32-byte.
  3. Gunakan calldata — Untuk parameter fungsi external, calldata lebih murah dari memory.
  4. Minimalkan ekspansi memory — Gunakan kembali area memory yang sama daripada terus mengalokasi.
  5. Gunakan immutable — Variabel immutable disematkan dalam bytecode, bukan storage.

Transient Storage (EIP-1153)

EIP-1153 (Cancun upgrade, 2024) memperkenalkan opcode baru: TSTORE dan TLOAD. Transient storage berperilaku seperti storage biasa tetapi dihapus di akhir transaksi.

Biaya: 100 gas untuk TSTORE dan TLOAD — jauh lebih murah dari SSTORE/SLOAD. Ini sempurna untuk guard reentrancy, kunci sementara, dan komunikasi antar sub-panggilan dalam satu transaksi.

// Sebelum EIP-1153 (guard reentrancy):
uint256 private _locked = 1;  // SLOAD 2100 gas + SSTORE 2900 gas

// Setelah EIP-1153:
assembly {
    if tload(0) { revert(0, 0) }  // TLOAD 100 gas
    tstore(0, 1)                   // TSTORE 100 gas
}

Kesimpulan

Memahami empat lokasi data EVM dan biaya relatifnya adalah kunci untuk menulis kontrak yang efisien gas. Stack untuk komputasi, memory untuk data sementara, storage untuk state persisten, calldata untuk input read-only. Setiap pilihan lokasi data berdampak langsung pada biaya gas pengguna Anda.