Mendalami fungsi Solidity: visibility public, external, internal, dan private, mutability view, pure, dan payable, return multiple values, pola modifier onlyOwner, serta perbandingan require, assert, revert, dan custom error yang lebih hemat gas

Setelah di episode 4 kalian menguasai tipe data dan data location, pada episode ini kita mempelajari bahasa antarmuka kontrak: fungsi, modifier, dan visibility. Inilah yang menentukan siapa yang boleh memanggil apa, dan bagaimana kontrak memvalidasi input.
Mengapa episode ini penting? Karena hampir semua kerentanan akses kontrol berawal dari visibility atau modifier yang salah: fungsi yang seharusnya private ternyata public, atau guard onlyOwner yang lupa dipasang. Memahami keempat visibility dan cara kerja modifier sejak awal akan menyelamatkan kalian di episode 15 (security).
| Visibility | Dari kontrak lain | Turunan kontrak | Dalam kontrak |
|---|---|---|---|
public | Ya | Ya | Ya |
external | Ya | Tidak | Tidak (lewat this) |
internal | Tidak | Ya | Ya |
private | Tidak | Tidak | Ya |
Perbedaan penting public vs external:
external lebih hemat gas untuk data besar karena argumen dibaca dari calldata tanpa disalin ke memory.public membuat state variable otomatis punya getter — tetapi untuk fungsi, external lebih disarankan saat tidak dipanggil internal.function setData(bytes calldata data) external {
// data dibaca langsung dari calldata, hemat gas
}view — membaca state, tidak mengubahnya. Bisa dipanggil tanpa gas.pure — tidak membaca atau mengubah state; hanya komputasi murni.payable — bisa menerima ETH. Fungsi non-payable akan revert jika dikirimi ETH.function read() public view returns (uint256) {
return totalSupply;
}
function add(uint256 a, uint256 b) public pure returns (uint256) {
return a + b;
}
function deposit() public payable {
balances[msg.sender] += msg.value;
}Tip
Memakai view/pure dengan benar bukan hanya soal style — frontend bisa memanggil fungsi tanpa transaksi (gratis) jika mutability-nya view/pure. Label yang salah juga bikin kompiler mengeluh dan pembaca audior bingung.
Solidity mendukung multiple return:
function getUser() public view returns (address, uint256) {
return (owner, balance);
}Pemanggilnya bisa menerima tuple dengan destructuring (di frontend dengan ethers.js, atau lewat tuple). Untuk hasil yang lebih terstruktur, kembalikan struct:
function getInfo() public view returns (User memory) {
return user;
}Modifier adalah "wrapper" yang mengeksekusi logika sebelum dan/atau sesudah fungsi:
address public owner;
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
function setOwner(address _newOwner) public onlyOwner {
owner = _newOwner;
}Tanda _; adalah titik tempat tubuh fungsi asli dijalankan. Dengan onlyOwner, setiap fungsi yang memasangnya otomatis memeriksa msg.sender sebelum dieksekusi. Ini pola yang paling banyak dipakai di dunia nyata.
Tiga mekanisme pembatalan transaksi (semuanya membuat transaksi revert):
| Mekanisme | Penggunaan | Biaya gas |
|---|---|---|
require(kondisi, "pesan") | Validasi input & kondisi bisnis | Menengah (string disimpan) |
assert(kondisi) | Invariant yang harus selalu benar | Lebih mahal |
revert() | Pembatalan di tengah logika | Menengah |
error Nama() | Custom error | Paling murah |
error NotOwner();
error AmountTooSmall(uint256 amount);
function withdraw(uint256 amount) public onlyOwner {
if (amount < 1 ether) revert AmountTooSmall(amount);
// ...
}Custom error di deklarasi dengan error jauh lebih hemat gas daripada require dengan string — error string disimpan di calldata (gratis ketika revert, tapi 4-byte selector custom error lebih kecil) dan mendukung parameter. Ini rekomendasi modern untuk kontrak baru.
Warning
assert seharusnya dipakai untuk invariant yang "tidak mungkin gagal" — jika assert gagal, itu tanda bug serius (bisa memakai seluruh gas). Untuk validasi input biasa, pakai require atau custom error.
Inti yang harus dibawa pulang:
external hemat gas untuk argumen besar, internal/private untuk enkapsulasi.view/pure gratis dipanggil; payable satu-satunya yang menerima ETH.onlyOwner memusatkan validasi akses di satu tempat.error Nama()) dibanding require ber-string karena lebih hemat gas.Di episode 6 selanjutnya kita akan membahas storage, memory, dan gas optimization — layout storage berbasis slot 32-byte, cara EVM menempatkan uint128, address, dan bool dalam satu slot, kapan immutable/constant menghemat gas, serta strategi menghindari SSTORE yang mahal. Sampai jumpa di episode 6!