Bug di aplikasi web bisa ditambal hari Jumat sore. Bug di smart contract yang sudah di-deploy bisa dilihat semua orang, permanen kalau dari awal tidak disiapkan untuk upgrade, dan sering berada tepat di sebelah tumpukan token yang bisa diambil siapa saja. Kerugian besar jarang datang dari kriptografi yang rumit. Kebanyakan datang dari sekumpulan kecil kesalahan yang sudah lama dikenal. Kemampuan mengenali kesalahan itu adalah bekal paling berharga bagi developer Solidity sebelum menulis kontrak yang menyimpan aset sungguhan.
Reentrancy
Ketika kontrak mengirim ETH atau memanggil kontrak lain, penerimanya mendapat kendali dan bisa memanggil balik kontrak asal sebelum pemanggilan pertama selesai. Kalau saldo baru diperbarui setelah transfer, penyerang bisa menarik dana berulang kali dari saldo yang sama. Bug inilah yang ada di balik peretasan The DAO tahun 2016, dan variasinya masih muncul sampai sekarang, termasuk read-only reentrancy ketika protokol lain membaca nilai yang belum diperbarui di tengah pemanggilan.
- Ikuti urutan checks, effects, interactions: validasi input, perbarui state, baru lakukan pemanggilan ke luar.
- Pasang ReentrancyGuard dari OpenZeppelin di fungsi yang memindahkan dana.
- Lebih baik pengguna menarik dananya sendiri daripada kontrak mengirim pembayaran ke banyak alamat dalam satu fungsi.
Access Control yang Hilang atau Rusak
Fungsi mint, pause, atau upgrade tanpa pengecekan onlyOwner atau role bisa dipanggil siapa saja. Kontrak upgradeable punya versi yang lebih samar dari bug yang sama: initializer yang tidak pernah dipanggil atau tidak dikunci, sehingga penyerang bisa menginisialisasi kontrak implementasi dan menjadikan dirinya owner. Pakai Ownable atau AccessControl dari OpenZeppelin, panggil _disableInitializers di constructor implementasi, dan tulis test untuk setiap fungsi khusus admin yang memanggilnya dari alamat acak dan mengharapkan revert.
Jangan pernah memakai tx.origin untuk otorisasi. Nilainya adalah alamat yang memulai transaksi, jadi kontrak jahat yang sedang dipakai owner bisa lolos dari pengecekan. Pakai msg.sender.
Manipulasi Harga Oracle
Protokol lending yang membaca harga aset dari cadangan satu pool DEX saat itu bisa dikelabui dalam satu transaksi. Penyerang meminjam dana besar lewat flash loan, menggeser harga di pool, meminjam dengan jaminan yang nilainya sudah digelembungkan, lalu melunasi flash loan, semuanya di blok yang sama. Pakai oracle terdesentralisasi seperti Chainlink, atau harga rata-rata tertimbang waktu dengan rentang yang masuk akal, lalu pastikan harganya masih baru dan berada dalam batas wajar.
Bug Lain yang Perlu Dikenali
| Celah | Apa yang terjadi | Pencegahan |
|---|---|---|
| Signature replay | Pesan yang sudah ditandatangani dipakai lagi di chain lain, kontrak lain, atau untuk kedua kalinya | Pakai typed data EIP-712 dengan nonce, chain ID, alamat kontrak, dan deadline |
| Low-level call tanpa pengecekan | call mengembalikan false saat gagal, dan kode tetap jalan seolah transfer berhasil | Periksa nilai kembaliannya, dan pakai SafeERC20 untuk token yang tidak mengembalikan boolean |
| Front-running | Pihak lain melihat transaksi di mempool lalu bertindak lebih dulu, misalnya saat swap | Batas slippage, deadline, dan skema commit-reveal kalau urutan transaksi penting |
| Loop tanpa batas | Loop di array yang terus bertambah lama-lama butuh gas melebihi batas blok, sehingga fungsinya terkunci | Bagi pekerjaan per halaman atau biarkan tiap pengguna memproses datanya sendiri |
| Storage collision saat upgrade | Implementasi baru mengubah urutan variabel state dan merusak data yang sudah ada | Hanya tambahkan variabel di bagian akhir, dan jalankan plugin upgrades OpenZeppelin untuk memvalidasi layout |
| Aritmetika di blok unchecked | Pengecekan overflow dilewati karena developer mengira angkanya tidak mungkin meluap | Pakai unchecked hanya kalau batasnya sudah terbukti, dan jaga presisi dengan mengalikan sebelum membagi |
Solidity 0.8 ke atas otomatis melakukan revert saat integer overflow, dan satu kelompok bug lama ikut hilang. Pembulatan masih jadi masalah, terutama di vault dan perhitungan share, karena pembulatan yang menguntungkan pengguna bisa dieksploitasi berkali-kali.
Tools untuk Menangkap Bug Lebih Awal
- Slither, static analyzer yang dalam hitungan detik menandai reentrancy, fungsi tanpa proteksi, variabel yang tertimpa, dan banyak pola lain. Jalankan di CI.
- Fuzz test di Foundry, yang memanggil fungsi dengan ribuan input acak dan menemukan kasus pinggir yang tidak terpikir untuk ditulis manual.
- Invariant test di Foundry atau Echidna, yang memeriksa sifat yang harus selalu berlaku, misalnya total deposit sama dengan jumlah semua saldo.
- Test dengan fork mainnet, yang menjalankan kontrak terhadap protokol dan harga asli yang sudah ada di jaringan.
- Library yang sudah teruji seperti OpenZeppelin Contracts untuk token, access control, dan proxy, daripada menulis sendiri.
Proses Rilis untuk Kontrak yang Menyimpan Aset
- 1Tulis spesifikasi singkat tentang apa yang boleh dan tidak boleh dilakukan setiap fungsi, beserta invariant sistemnya.
- 2Capai coverage test yang tinggi, termasuk fuzz dan invariant test, sebelum ada orang luar tim yang membaca kodenya.
- 3Jalankan Slither lalu perbaiki atau dokumentasikan setiap temuan.
- 4Bekukan kode dan minta audit eksternal. Dua audit independen sudah umum untuk kontrak yang menyimpan dana besar.
- 5Deploy ke testnet seperti Sepolia dan jalankan seluruh alur pengguna, termasuk aksi admin.
- 6Simpan kunci admin di dompet multisig dan pasang timelock untuk upgrade, supaya pengguna bisa melihat perubahan sebelum berlaku.
- 7Luncurkan bersama program bug bounty dan monitoring untuk transfer besar dan pemanggilan fungsi admin.
Audit adalah pemeriksaan kedua untuk kode yang seharusnya sudah teruji dengan baik. Audit tidak bisa menggantikan test.
Pelatihan smart contract kami membahas celah-celah ini dengan meminta peserta membobol kontrak yang sengaja dibuat lemah, lalu memperbaiki dan mengujinya dengan Foundry. Melihat serangan berhasil dengan mata sendiri membuat kebiasaan defensif jauh lebih melekat daripada membaca checklist.
Poin penting
- Perbarui state sebelum pemanggilan ke luar, dan pasang ReentrancyGuard di fungsi yang memindahkan dana.
- Lindungi setiap fungsi khusus admin dengan role, kunci initializer, dan jangan pakai tx.origin untuk otorisasi.
- Jangan ambil harga dari satu pool saja. Pakai Chainlink atau harga rata-rata tertimbang waktu dan periksa kebaruannya.
- Pakai signature EIP-712 dengan nonce, chain ID, dan deadline untuk mencegah replay.
- Gabungkan Slither, fuzz dan invariant test, audit eksternal, multisig, dan bug bounty sebelum menyimpan aset sungguhan.


