Belajar Solidity - DeFi Patterns: DEX, Vault & Staking
Episode 12 of 23

Belajar Solidity - DeFi Patterns: DEX, Vault & Staking

Menerapkan pola-pola inti DeFi di Solidity: AMM ala Uniswap V2 dengan liquidity pool dan slippage control, vault dengan deposit/withdraw dan shares accounting, serta model akrual bunga ala Compound

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

Pendahuluan

Setelah di episode 11 kalian membangun token standar, pada episode ini kita masuk ke ranah DeFi (Decentralized Finance) — tempat di mana kontrak pintar menggantikan peran bank, exchange, dan pasar modal.

Mengapa episode ini penting? Karena DeFi adalah kasus penggunaan Solidity terbesar di dunia nyata. Protokol dengan Total Value Locked (TVL) miliaran dolar — DEX seperti Uniswap, lending seperti Compound, vault seperti Yearn — semuanya dibangun dari tiga pola dasar yang akan kita bedah: AMM, vault dengan shares, dan model bunga. Menguasai pola ini berarti kalian bisa membaca bahkan membangun protokol DeFi sendiri.

DEX: AMM ala Uniswap V2

Automated Market Maker (AMM) menggantikan order book dengan formula harga otomatis. Uniswap V2 memakai formula constant product:

text
x * y = k

di mana x dan y adalah cadangan (reserve) dua token di pool. Produknya harus tetap konstan, sehingga saat kalian menambah satu token, kalian mendapat token lain sesuai harga pasar.

100%

Liquidity Pool

Pool diisi Liquidity Provider (LP) yang mendepositkan kedua token dalam rasio sama, dan mendapat LP token sebagai bukti kepemilikan bagian pool:

src/SimpleAMM.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.36;
 
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
 
contract SimpleAMM {
    IERC20 public immutable tokenA;
    IERC20 public immutable tokenB;
    uint256 public reserveA;
    uint256 public reserveB;
 
    constructor(address _tokenA, address _tokenB) {
        tokenA = IERC20(_tokenA);
        tokenB = IERC20(_tokenB);
    }
 
    function addLiquidity(uint256 amountA, uint256 amountB) external {
        tokenA.transferFrom(msg.sender, address(this), amountA);
        tokenB.transferFrom(msg.sender, address(this), amountB);
        reserveA += amountA;
        reserveB += amountB;
    }
 
    function swapAtoB(uint256 amountAIn) external returns (uint256 amountBOut) {
        uint256 amountInWithFee = amountAIn * 997;
        uint256 numerator = amountInWithFee * reserveB;
        uint256 denominator = reserveA * 1000 + amountInWithFee;
        amountBOut = numerator / denominator;
 
        tokenA.transferFrom(msg.sender, address(this), amountAIn);
        reserveA += amountAIn;
        reserveB -= amountBOut;
        tokenB.transfer(msg.sender, amountBOut);
    }
}

Perhatikan: fee 0.3% (faktor 997/1000) mengalir ke LP melalui rumus swap. Ini penyederhanaan besar dari Uniswap asli (yang menghitung ulang reserve sebelum swap dan punya manajemen LP token), tetapi inti matematikanya sama.

Warning

Contoh di atas sengaja disederhanakan untuk belajar — jangan deploy ke mainnet. Uniswap V2 asli punya perlindungan tak terhitung: minimum liquidity, slippage check, manipulasi reserve, dan manajemen LP token. Baca implementasinya dan pakai library ter-audit.

Slippage Control

Karena harga AMM berubah berdasarkan ukuran transaksi, trader wajib membatasi kerugian akibat pergeseran harga (slippage). Standarnya parameter amountOutMin — kalian menolak hasil jika lebih rendah dari minimum:

solidity
function swapWithLimit(uint256 amountIn, uint256 amountOutMin) external returns (uint256 out) {
    out = getAmountOut(amountIn, reserveA, reserveB);
    require(out >= amountOutMin, "slippage terlalu tinggi");
    // lakukan swap
}

Frontend menghitung amountOutMin dengan buffer (misal 1%) sehingga transaksi tidak dieksekusi jika harga bergerak buruk. Ini juga membatasi kerugian akibat sandwich attack / front-running yang kita bahas di episode 15.

Vault: Deposit/Withdraw dan Shares Accounting

Vault adalah kontrak yang mengelola dana deposan. Kunci desainnya adalah shares accounting: deposan menerima "saham" sebagai klaim atas total aset vault.

src/SimpleVault.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.36;
 
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
 
contract SimpleVault {
    IERC20 public immutable token;
    uint256 public totalShares;
 
    mapping(address => uint256) public shares;
 
    constructor(address _token) {
        token = IERC20(_token);
    }
 
    function deposit(uint256 amount) external returns (uint256 sharesMinted) {
        token.transferFrom(msg.sender, address(this), amount);
        uint256 assets = token.balanceOf(address(this));
        if (totalShares == 0) {
            sharesMinted = amount;
        } else {
            sharesMinted = (amount * totalShares) / assets;
        }
        shares[msg.sender] += sharesMinted;
        totalShares += sharesMinted;
    }
 
    function redeem(uint256 sharesAmount) external returns (uint256 assetsOut) {
        assetsOut = (sharesAmount * token.balanceOf(address(this))) / totalShares;
        shares[msg.sender] -= sharesAmount;
        totalShares -= sharesAmount;
        token.transfer(msg.sender, assetsOut);
    }
}

Logika di baliknya: nilai satu share = total aset / total share. Saat vault menghasilkan keuntungan (aset bertambah tanpa deposito baru), setiap share bernilai lebih — itulah cara deposan mendapat yield tanpa hitung bunga manual. Ini adalah pola inti ERC-4626 (tokenized vault) yang dipakai protokol modern.

Interest Model ala Compound

Compound menghitung bunga dengan cara yang jauh lebih hemat gas daripada mengupdate saldo semua user setiap blok: hanya index bunga global yang diupdate, lalu saldo user dihitung saat mereka bertransaksi.

solidity
uint256 public supplyIndex = 1e18;
mapping(address => uint256) public supplyIndexSnapshot;
mapping(address => uint256) public balanceOf;
 
function accrueInterest() internal {
    uint256 ratePerSecond = 3_170_979_198; // ~10% per tahun
    supplyIndex += (supplyIndex * ratePerSecond * (block.timestamp - lastTimestamp)) / 1e18;
    lastTimestamp = block.timestamp;
}
 
function claimYield(address user) public {
    accrueInterest();
    uint256 principal = balanceOf[user];
    uint256 yield = (principal * supplyIndex) / supplyIndexSnapshot[user] - principal;
    supplyIndexSnapshot[user] = supplyIndex;
    // kirim yield ke user
}

Prinsipnya: bunga diakrukan per unit (index), bukan per user. Semua orang berbagi index global; snapshot per user menentukan kapan "snapshot" terakhir mereka, dan selisih index-nya = bunga yang terakrual. Ini skala hingga jutaan user tanpa loop.

Pitfall Umum

  • Tanpa slippage limit — swap dapat dieksekusi dengan harga buruk saat volatilitas.
  • Donasi token ke vault — bisa mengubah rasio share/aset secara tidak adil; protokol modern (ERC-4626) punya mitigasi khusus.
  • Reentrancy di withdraw — transfer token sebelum update state membuka celah serangan (kita bahas di episode 15).
  • Integer rounding — pembagian integer bisa "mencuri" sebagian kecil nilai; arahkan pembulatan ke sisi yang aman (favor token untuk withdraw, favor protocol untuk deposit).

Penutup

Inti yang harus dibawa pulang:

  • AMM memakai formula constant product x * y = k; fee 0.3% mengalir ke LP.
  • Slippage dikendalikan lewat parameter amountOutMin di sisi kontrak.
  • Vault memakai shares accounting: nilai share = aset / total share.
  • Model bunga Compound memakai index global per unit, bukan per-user loop.

Di episode 13 selanjutnya kita akan membahas interaksi dengan data & oracles — harga off-chain lewat Chainlink Price Feeds, random number dengan Chainlink VRF, membaca event untuk off-chain data, indexing subgraph The Graph, dan Chainlink Functions. Sampai jumpa di episode 13!

Belajar Solidity - DeFi Patterns: DEX, Vault & Staking | Belajar Solidity