Belajar Solidity - Upgradeable Contracts & Proxies
Episode 14 of 23

Belajar Solidity - Upgradeable Contracts & Proxies

Mengatasi sifat immutable kontrak dengan pola proxy: mekanisme delegatecall, EIP-1967 untuk storage proxy, perbandingan pola Transparent dan UUPS, plugin OpenZeppelin Upgrades, serta governance dengan timelock dan multi-sig Safe

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Setelah di episode 13 kalian menghubungkan kontrak ke dunia luar dengan oracles, pada episode ini kita menghadapi salah satu keputusan arsitektur terbesar: bagaimana memperbaiki kontrak yang sudah di-deploy.

Seperti yang sering kita tekankan, kontrak yang di-deploy bersifat immutable. Namun hampir semua protokol produksi butuh perbaikan bug, penambahan fitur, atau upgrade parameter. Solusinya adalah pola proxy: kontrak tetap bisa "diganti logikanya" tanpa mengubah alamat yang dipakai pengguna.

Mengapa episode ini penting? Karena proxy adalah arsitektur di balik mayoritas protokol besar — USDC, Uniswap, dan hampir semua protokol upgradeable memakainya. Tetapi proxy juga menambah kompleksitas keamanan yang besar: satu kesalahan konfigurasi bisa membuat dana terkunci atau kontrak "disandera".

Mengapa Proxy Diperlukan

Tanpa proxy, setiap perbaikan berarti migrasi pengguna ke kontrak baru dengan alamat baru — mahal, membingungkan, dan berisiko. Dengan proxy:

  • User memegang alamat yang selalu sama.
  • Data (storage) tetap tinggal di proxy.
  • Logika (implementation) bisa ditukar oleh pemilik.

Cara Kerja: delegatecall

Jantung proxy adalah delegatecall: memanggil fungsi kontrak lain dengan storage milik sendiri. Implementasi dieksekusi, tetapi baca/tulis storage mengarah ke kontrak pemanggil.

100%

Semua state variable tinggal di proxy; implementation hanya berisi logika. Saat upgrade, proxy diarahkan ke implementation baru — data pengguna tidak berubah.

EIP-1967: Standar Slot Storage Proxy

Karena proxy harus kompatibel dengan storage layout, posisi alamat implementation tidak boleh bertabrakan dengan state variable kontrak. Solusinya: simpan di slot khusus yang tidak mungkin dipakai kontrak biasa.

solidity
bytes32 internal constant IMPLEMENTATION_SLOT =
    0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
 
function implementation() public view returns (address impl) {
    assembly {
        impl := sload(IMPLEMENTATION_SLOT)
    }
}

Slot tersebut dihitung dari rumus keccak256("eip1967.proxy.implementation") - 1. EIP-1967 menstandarkan slot untuk implementation, admin, dan beacon sehingga semua tooling (explorer, plugin) bisa mengenali proxy dengan benar.

Transparent vs UUPS

Dua pola utama upgradeable proxy:

AspekTransparent ProxyUUPS
Siapa yang meng-upgradeAdmin terpisah, semua fungsi dialihkanFungsi upgrade ada di implementation itu sendiri
Storage footprintLebih besar (admin storage)Lebih kecil
Gas tiap callSedikit lebih mahal (ada pengecekan admin)Lebih hemat
UpgradeabilityBisa dilepas (set impl ke 0)Dipegang kontrak sendiri
Umum dipakaiKontrak lama (OpenZeppelin 3.x)Standar modern

Pada UUPS, upgrade dikelola lewat fungsi upgradeToAndCall yang hidup di implementation. Karena logika upgrade ada di kode, kehilangan kendali terhadap implementation = kehilangan kemampuan upgrade. Pada Transparent, admin proxy memegang kendali, sehingga impl bisa diganti bahkan jika kode impl bermasalah — tetapi tiap transaksi user membayar biaya pengecekan admin.

Tip

Untuk proyek baru, UUPS adalah pilihan yang disarankan — lebih hemat gas dan lebih sederhana. Transparent tetap relevan jika kalian ingin memisahkan kontrol upgrade sepenuhnya dari logika kontrak.

Plugin OpenZeppelin Upgrades

Mengerjakan proxy manual rentan error. OpenZeppelin Upgrades Plugin mengotomatisasi deploy, validasi, dan verifikasi:

Install plugin
npm install --save-dev @openzeppelin/hardhat-upgrades
scripts/upgrade.js
const { ethers, upgrades } = require("hardhat");
 
async function main() {
  const VaultV1 = await ethers.getContractFactory("VaultV1");
  const proxy = await upgrades.deployProxy(VaultV1, [], { initializer: "initialize" });
  await proxy.waitForDeployment();
 
  const VaultV2 = await ethers.getContractFactory("VaultV2");
  const upgraded = await upgrades.upgradeProxy(await proxy.getAddress(), VaultV2);
  await upgraded.waitForDeployment();
}

Plugin memvalidasi storage layout — ia menolak upgrade yang menggeser slot variabel yang sudah ada. Ingat: kontrak upgradeable tidak memakai constructor, melainkan initialize (pola initializer) karena constructor berjalan di implementation yang storage-nya terpisah.

Governance: Timelock dan Multi-sig

Mengendalikan upgrade dari satu owner (EOA) adalah risiko tunggal. Standar produksi menggabungkan:

  • Timelock — setiap perubahan "dimasak" selama periode tertentu sebelum dieksekusi, memberi waktu komunitas bereaksi.
  • Multi-sig (Safe) — perubahan butuh persetujuan beberapa penandatangan (misal 3 dari 5), bukan satu kunci.

Alur umum: komunitas memilih → proposal dieksekusi via timelock → timelock dieksekusi oleh multi-sig → multi-sig memanggil upgradeToAndCall di proxy. Tidak ada satu entitas pun yang bisa meng-upgrade secara diam-diam.

Pitfall Umum Proxy

  • Constructor vs initializer — state yang di-set di constructor tidak tersimpan di proxy; wajib pakai initialize.
  • Storage layout berubah — menambahkan variabel di tengah struct atau memindahkan slot merusak data; plugin menangkap ini.
  • Fungsi fallback salah — proxy harus meneruskan semua call dengan fallback + delegatecall.
  • Tanpa timelock/multi-sig — satu kunci upgrade adalah single point of failure.

Penutup

Inti yang harus dibawa pulang:

  • Proxy memakai delegatecall — logika di implementation, storage di proxy.
  • EIP-1967 menstandarkan slot penyimpanan proxy agar tooling mengenalinya.
  • UUPS (modern) vs Transparent — beda di siapa yang memegang logika upgrade.
  • Gunakan Upgrades Plugin, pola initialize, dan kendalikan upgrade via timelock + multi-sig Safe.

Di episode 15 selanjutnya kita akan membahas security: reentrancy & common vulnerabilities — serangan reentrancy dan pola checks-effects-interactions, overflow bawaan 0.8.x, flash loans, oracle manipulation, front-running, serta pitfall tx.origin dan selfdestruct. Sampai jumpa di episode 15!

Belajar Solidity - Upgradeable Contracts & Proxies | Belajar Solidity