Menjalankan proses audit kontrak pintar: static analysis dengan Slither dan Mythril, fuzzing dengan Echidna dan invariant testing, access control dengan OpenZeppelin AccessControl, pola pausable dan emergency stop, serta praktik keamanan kontrak upgradeable

Setelah di episode 17 kalian berhasil deploy dan verifikasi ke Sepolia, pada episode ini kita membahas proses yang menentukan apakah kontrak layak menyentuh mainnet: auditing.
Mengapa episode ini penting? Karena tes unit saja tidak cukup. Test menulis skenario yang kalian pikirkan; penyerang memikirkan skenario yang tidak kalian pikirkan. Audit menggabungkan tool otomasi, fuzzing, dan best practice untuk menutup celah sebelum dana nyata dipertaruhkan. Setiap protokol kredibel menjalani proses ini — dan memahami prosesnya membuat kalian menjadi pengembang yang bisa dipercaya.
Static analysis memindai kode tanpa menjalankannya — mencari pola berbahaya yang sudah dikenal.
Slither (Python) adalah static analyzer paling populer di ekosistem Solidity:
slither src/ --exclude-dependenciesSlither mendeteksi pola seperti: penggunaan tx.origin, fungsi tanpa visibility, reentrancy, variabel yang bisa dibaca tanpa set, dan banyak lagi. Ia juga punya printer untuk menampilkan arsitektur (call graph, inheritance) — berguna untuk review manual.
Mythril melakukan analisis berbasis symbolic execution — menelusuri jalur eksekusi dan mencari kondisi yang memicu bug:
myth analyze src/MyToken.sol --solc-json mythril.jsonMythril unggul menemukan masalah logika dalam yang sulit terlihat dari pembacaan manual, seperti pembulatan yang merugikan atau jalur state yang mustahil.
Tip
Static analyzer itu pembantu, bukan pengganti auditor manusia: mereka menghasilkan banyak false positive, dan tidak memahami intent bisnis. Gunakan untuk menyaring masalah awal, lalu tinjau manual dengan threat model yang sudah kita bangun di episode 15.
Fuzzing memberi input acak (dan terarah) ke fungsi-fungsi kontrak, mencari crash atau pelanggaran invariant — properti yang harus selalu benar.
Echidna (dari Trail of Bits) memakai property function: fungsi yang jika mengembalikan false, berarti invariant dilanggar.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.36;
import {Test} from "forge-std/Test.sol";
import {SimpleVault} from "../src/SimpleVault.sol";
contract VaultInvariant is Test {
SimpleVault vault;
function setUp() public {
vault = new SimpleVault(address(token));
}
// Invariant: total share tidak pernah melebihi total deposit yang sah
function invariant_shareSupplyMatchesDeposits() public view {
assertLe(vault.totalShares(), type(uint256).max);
}
}Di Foundry, fuzz test cukup ditulis dengan prefix testFuzz — parameter diisi otomatis:
function testFuzz_DepositAlwaysCredits(uint256 amount) public {
vm.assume(amount > 0 && amount < 1_000_000 ether);
vm.prank(alice);
vault.deposit(amount);
assertEq(vault.shares(alice), amount); // 1:1 di vault sederhana
}Invariant testing yang baik memikirkan properti seperti: saldo total tidak pernah turun di bawah deposit, tidak ada user yang saldonya negatif, supply share konsisten dengan aset. Ini menangkap bug yang tidak pernah terpikirkan oleh test manual.
Untuk kontrol akses bertingkat, gunakan AccessControl alih-alih membangun sendiri:
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
contract Treasury is AccessControl {
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
constructor() {
_grantRole(ADMIN_ROLE, msg.sender);
_grantRole(PAUSER_ROLE, msg.sender);
}
function pause() external onlyRole(PAUSER_ROLE) { ... }
}Keuntungannya: role-based (bukan sekadar owner), bisa di-revoke per akun, dan sudah ter-audit luas. Role memisahkan "siapa yang bisa pausing" dari "siapa yang bisa mengubah parameter" — mengurangi risiko satu kunci.
Protokol produksi butuh emergency brake: saat celah terdeteksi, operasi dihentikan sementara sambil tim menyiapkan perbaikan.
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
contract MyProtocol is Pausable {
function withdraw() external whenNotPaused {
// ...
}
function pause() external onlyRole(PAUSER_ROLE) {
_pause();
}
function unpause() external onlyRole(PAUSER_ROLE) {
_unpause();
}
}whenNotPaused memblokir fungsi kritis. Kebijakan yang baik: fungsi yang menggerakkan dana dapat di-pause, sementara fungsi read tetap berjalan agar eksplorer tetap bisa memantau. Pasangkan dengan timelock dari episode 14 agar pausing tidak bisa disalahgunakan diam-diam.
Kontrak upgradeable punya lapisan risiko tambahan:
initialize hanya boleh dipanggil sekali; sisakan slot _initialized dan gunakan initializer modifier dari library.initialize menerima owner/roles yang benar, bukan default.Warning
Aturan emas upgradeable: setiap perubahan kontrak harus melewati validasi storage layout dan peninjauan "bagaimana transisi dari V1 ke V2". Upgrade yang terlihat kecil bisa menghancurkan data pengguna jika layout berubah.
Inti yang harus dibawa pulang:
testFuzz Foundry.Di episode 19 selanjutnya kita akan membahas gas optimization lanjut — perbandingan calldata vs memory, assembly (Yul) untuk hot path, pola storage yang hemat, gas reporter di Hardhat, serta pemeriksaan storage pack. Sampai jumpa di episode 19!