Cara Menghemat Gas Fee di Solidity: Kenali Dulu Sumber Biayanya

Panduan praktis menekan gas fee di smart contract Solidity: cara biaya storage dihitung, packing variabel, constant dan immutable, calldata, caching di loop, custom error, event, unchecked, mengukur dengan Foundry, dan kapan sebaiknya tidak dioptimasi.

Dipublikasikan 9 menit baca
Developer memeriksa kode Solidity di laptop di samping buku catatan

Bayangkan kontrak NFT yang biaya mint satu tokennya hampir dua kali lipat dibanding kompetitor. Logikanya sebenarnya benar. Masalahnya ada lima kali tulis ke storage padahal dua sudah cukup, ditambah satu counter yang dibaca dari storage di setiap putaran loop. Pemborosan gas kebanyakan memang seperti itu. Jarang sekali yang selesai dengan assembly canggih. Yang dibutuhkan adalah tahu operasi mana yang mahal, lalu menyentuhnya sesedikit mungkin.

Biaya Terbesar Ada di Storage

Operasi aritmetika di EVM cuma 3 sampai 5 gas. Storage beda kelas. Sejak EIP-2929 (upgrade Berlin), pembacaan pertama sebuah slot storage dalam satu transaksi dihitung "cold" dan biayanya 2.100 gas. Pembacaan berikutnya ke slot yang sama dihitung "warm", cukup 100 gas. Menulis slot dari nol ke nilai bukan nol biayanya 20.000 gas, ditambah 2.100 untuk akses cold kalau itu sentuhan pertama, jadi sekitar 22.100. Mengubah nilai yang sudah ada sekitar 5.000 kalau cold. Mengembalikan slot ke nol memberi refund 4.800, dengan batas seperlima dari gas yang terpakai sejak EIP-3529.

Kalau angka-angka itu dijejerkan, prioritasnya langsung kelihatan. Satu SSTORE yang bisa dihindari setara ribuan operasi penjumlahan. Jadi setiap kali mau optimasi, mulai dengan mendaftar semua SSTORE dan SLOAD di fungsi yang paling sering dipanggil pengguna.

OperasiPerkiraan gasArtinya di lapangan
ADD, SUB, perbandingan3Hampir gratis. Jangan mulai optimasi dari sini.
SLOAD warm100Murah, tapi bisa menumpuk di dalam loop
SLOAD cold2.100Pembacaan pertama tiap slot dalam satu transaksi
SSTORE nol ke bukan nol20.000 + 2.100 coldSaldo baru, entry mapping baru, item array baru
SSTORE bukan nol ke bukan nolsekitar 5.000 coldMengubah nilai yang sudah ada
LOG satu topic, data 32 bytesekitar 1.000Event jauh lebih murah daripada menyimpan riwayat
Byte calldata4 untuk nol / 16 bukan nolSangat berpengaruh di rollup, lihat bagian L2

Packing Variabel Storage

Satu slot storage berukuran 32 byte. Compiler akan menggabungkan variabel yang dideklarasikan berurutan ke satu slot kalau muat, jadi urutan deklarasi berpengaruh. Address memakai 20 byte, sisanya 12 byte cukup untuk uint96, timestamp uint64, atau beberapa bool. Struct dengan urutan uint256, address, uint256, bool memakai empat slot. Ubah urutannya jadi uint256, uint256, address, bool, hasilnya tiga slot. Kalau field-field itu selalu dibaca atau ditulis bersamaan, Anda hemat satu akses cold setiap kali.

Packing hanya menguntungkan kalau field yang digabung memang dipakai bersamaan. Menulis satu field kecil di slot bersama tetap kena biaya SSTORE penuh, plus operasi masking dari compiler. Pilih tipe data sesuai rentang nilai yang realistis. Timestamp uint64 cukup untuk miliaran tahun, dan uint96 bisa menampung total supply hampir semua token dengan 18 desimal.

Pakai constant dan immutable

Nilai yang tidak pernah berubah setelah deploy tidak perlu disimpan di storage. Constant ditetapkan saat kompilasi. Immutable diisi sekali di constructor. Keduanya ditanam langsung di bytecode, jadi membacanya hanya beberapa gas, jauh dari 2.100. Alamat penerima fee, alamat token, dan role owner yang tidak akan diganti cocok dijadikan immutable. Sejak Solidity 0.8.21 immutable boleh diisi secara kondisional di constructor, jadi alasan lama untuk tetap menaruhnya di storage hampir tidak ada lagi.

Kebiasaan Kecil yang Hasilnya Lumayan

  • Tandai parameter array dan string di fungsi external sebagai calldata, jangan memory. Calldata dibaca langsung di tempat, sedangkan memory harus disalin dulu.
  • Simpan nilai storage ke variabel lokal sebelum masuk loop. Membaca panjang array storage di setiap iterasi minimal 100 gas per putaran. Membaca salinan lokal cuma 3.
  • Ganti revert string dengan custom error, yang tersedia sejak Solidity 0.8.4. Bytecode jadi lebih kecil, data revert juga lebih pendek. Sejak 0.8.26 custom error bisa langsung dipakai di require.
  • Pakai event untuk riwayat yang tidak pernah dibaca oleh kontrak itu sendiri. Log transfer atau histori harga cukup dicatat lewat event, lalu diambil oleh indexer atau The Graph. Tidak perlu array storage yang terus membengkak.
  • Pakai blok unchecked hanya kalau Anda sudah membuktikan nilainya tidak mungkin overflow, misalnya mengurangi saldo tepat setelah mengecek saldonya cukup. Sejak 0.8.22 compiler sudah otomatis melewati pengecekan overflow untuk counter loop sederhana, jadi trik unchecked { ++i } yang dulu populer sekarang jarang diperlukan.
  • Pakai transient storage (EIP-1153, tersedia sejak upgrade Dencun) untuk nilai yang cukup hidup selama satu transaksi, contohnya lock reentrancy. TSTORE dan TLOAD masing-masing 100 gas, dan nilainya hilang begitu transaksi selesai.

Hindari Loop Tanpa Batas

Loop ke semua holder atau semua order terasa murah waktu datanya masih sepuluh. Begitu datanya sepuluh ribu, transaksinya gagal. Kalau sebuah fungsi butuh gas melebihi batas blok, fungsi itu tidak akan pernah bisa jalan lagi, dan dana yang bergantung padanya ikut terkunci. Rancang kontrak supaya setiap pengguna membayar pekerjaannya sendiri. Biarkan pengguna klaim reward masing-masing, jangan kontrak yang mengirim ke semua orang sekaligus. Kalau memang harus memproses daftar, proses per batch dengan index awal dan ukuran maksimum.

Ukur Sebelum dan Sesudah

Menebak-nebak soal gas cuma buang waktu. Optimizer compiler, via-IR, dan layout storage saling memengaruhi dengan cara yang sulit diprediksi. Ukur setiap perubahan.

  1. 1Tulis test yang memanggil fungsi-fungsi utama dengan input realistis, termasuk pengguna baru yang memicu penulisan dari nol ke bukan nol.
  2. 2Jalankan forge snapshot untuk mencatat gas per test ke file .gas-snapshot, lalu commit file itu.
  3. 3Jalankan forge test --gas-report untuk melihat biaya minimum, rata-rata, dan maksimum per fungsi. Di proyek Hardhat, hardhat-gas-reporter memberi tabel serupa.
  4. 4Ubah satu hal, lalu jalankan forge snapshot --diff untuk melihat test mana yang jadi lebih murah atau lebih mahal.
  5. 5Tambahkan forge snapshot --check di CI supaya pull request yang membuat fungsi utama lebih boros langsung ketahuan saat review.

Coba juga beberapa nilai optimizer runs. Nilai tinggi seperti 10.000 membuat pemanggilan fungsi lebih murah tapi deploy lebih mahal. Nilai rendah kebalikannya. Untuk kontrak yang akan dipanggil jutaan kali, runs tinggi biasanya pilihan yang tepat.

Kapan Sebaiknya Tidak Dioptimasi

Kami pernah mereview kontrak yang menghemat 200 gas per panggilan dengan inline assembly, tapi malah memunculkan bug yang bisa menguras pool. Hemat 200 gas di 1 gwei itu nilainya cuma belasan rupiah per transaksi. Kalau kena exploit, yang hilang seluruh treasury. Kode yang mudah dibaca lebih mudah diaudit, dan biaya audit naik setiap ada baris yang membingungkan. Pertahankan urutan checks-effects-interactions, buat access control tetap eksplisit, dan pakai assembly hanya kalau hasil pengukuran memang menunjukkan fungsi itu perlu dan auditor setuju.

Di Layer 2 hitungannya juga lain. Di rollup seperti Arbitrum, Optimism, atau Base, gas eksekusi murah, dan sebagian besar fee datang dari biaya mengirim data transaksi ke Ethereum. Sejak EIP-4844 data itu masuk ke blob sehingga fee turun drastis, tapi ukuran calldata tetap lebih berpengaruh di sana dibanding satu SLOAD tambahan. Argumen fungsi yang lebih pendek dan parameter yang lebih sedikit bisa lebih hemat daripada trik storage. Pastikan dulu kontrak Anda akan jalan di jaringan mana sebelum memutuskan apa yang dioptimasi.

Optimasi fungsi yang dipanggil pengguna setiap hari, ukur setiap perubahan, dan berhenti begitu kodenya mulai susah diaudit.

Optimasi gas termasuk materi yang kami bahas di pelatihan smart contract dan Ethereum untuk developer, lengkap dengan praktik langsung soal layout storage, gas report di Foundry, dan mereview kontrak sungguhan untuk mencari pemborosan.

Poin penting

  • Biaya terbesar ada di penulisan storage. SSTORE dari nol ke bukan nol sekitar 22.100 gas dengan akses cold, sedangkan aritmetika cuma 3.
  • Gabungkan variabel yang dipakai bersamaan dalam satu slot, dan pindahkan nilai tetap ke constant atau immutable.
  • Pakai calldata, cache variabel sebelum loop, custom error, dan event untuk riwayat yang tidak dibaca kontrak.
  • Hindari loop atas daftar yang bisa terus bertambah, dan biarkan pengguna membayar pekerjaannya sendiri.
  • Ukur setiap perubahan dengan forge snapshot atau gas reporter, dan jangan korbankan keterbacaan kode demi beberapa ratus gas.
Bagikan artikel ini

Artikel terkait

Artikel lain tentang software development, AI, cloud, dan infrastruktur.

Smartphone dengan wireframe aplikasi di samping buku catatan dan kalkulator di atas mejaPengembangan Software

10 menit baca

Berapa Biaya Pembuatan Aplikasi Mobile di Indonesia?

Kisaran harga indikatif dalam Rupiah untuk aplikasi mobile sederhana, menengah, dan kompleks, faktor yang benar-benar menentukan harga, Flutter versus dua aplikasi native, tim dan durasi, biaya tahunan setelah rilis, serta cara membandingkan penawaran vendor.

Orang memegang HP Android yang sedang membuka website dengan koneksi selulerPengembangan Web

9 menit baca

Cara Mempercepat Loading Website Bisnis: Panduan Core Web Vitals

Panduan praktis mempercepat website: kenapa kecepatan penting di HP Android kelas menengah dan jaringan seluler Indonesia, arti LCP, INP, dan CLS, beda data lab dan data lapangan, penyebab website lambat, cara memperbaikinya, dan langkah kerjanya.

Mari berdiskusi

Sedang mencari partner untuk pengembangan software?

Ceritakan proyek yang sedang Anda bangun, kebutuhan yang ingin diselesaikan, dan tantangan yang dihadapi. Kami dapat membantu membahas pendekatan teknis, scope, timeline, dan estimasi biaya.

Mulai diskusi