[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-property-based-testing-smart-contract-fuzzing-foundry":3},{"article":4,"author":58},{"id":5,"category_id":6,"title":7,"slug":8,"excerpt":9,"content_md":10,"content_html":11,"locale":12,"author_id":13,"published":14,"published_at":15,"meta_title":7,"meta_description":16,"focus_keyword":17,"og_image":18,"canonical_url":18,"robots_meta":19,"created_at":15,"updated_at":15,"tags":20,"category_name":38,"related_articles":39},"d2000000-0000-0000-0000-000000000119","a0000000-0000-0000-0000-000000000022","Property-Based Testing untuk Smart Contract — Fuzzing dengan Foundry","property-based-testing-smart-contract-fuzzing-foundry","Panduan property-based testing dan fuzzing untuk smart contract: mendefinisikan invariant, menulis fuzz test dengan Foundry, dan menemukan kerentanan yang tidak terdeteksi unit test.","## Mengapa Unit Test Tidak Cukup\n\nUnit test memverifikasi bahwa kode Anda bekerja untuk input spesifik yang Anda pilih. Tetapi smart contract menangani input dari pengguna mana pun — termasuk penyerang yang secara aktif mencari edge case. Property-based testing membalikkan pendekatan: alih-alih menentukan input, Anda mendefinisikan properti yang harus selalu benar, dan fuzzer mencari input yang melanggarnya.\n\n## Properti vs Unit Test\n\n```solidity\n\u002F\u002F Unit test: satu input spesifik\nfunction testTransfer() public {\n    token.transfer(alice, 100);\n    assertEq(token.balanceOf(alice), 100);\n}\n\n\u002F\u002F Property test: untuk SEMUA input yang valid\nfunction testFuzz_Transfer(address to, uint256 amount) public {\n    amount = bound(amount, 0, token.balanceOf(address(this)));\n    vm.assume(to != address(0));\n    \n    uint256 totalBefore = token.totalSupply();\n    token.transfer(to, amount);\n    uint256 totalAfter = token.totalSupply();\n    \n    \u002F\u002F PROPERTI: total supply tidak berubah\n    assertEq(totalBefore, totalAfter);\n}\n```\n\n## Mendefinisikan Invariant\n\nInvariant adalah kondisi yang harus SELALU benar, terlepas dari urutan operasi:\n\n### Invariant ERC20\n1. `totalSupply == sum(balances[semua_address])`\n2. `transfer` tidak mengubah totalSupply\n3. `balanceOf(x)` tidak pernah negatif (dijamin oleh uint256)\n4. `transfer(to, amount)` mengurangi pengirim dan menambah penerima persis `amount`\n\n### Invariant AMM (Uniswap V2)\n1. `reserve0 * reserve1 >= k` (setelah fee)\n2. `reserve0 > 0 && reserve1 > 0` (selalu memiliki likuiditas)\n3. Swap tidak pernah mengeluarkan lebih dari reserve yang ada\n4. `price_impact` meningkat secara monoton dengan ukuran swap\n\n### Invariant Lending Protocol\n1. `total_deposited >= total_borrowed`\n2. Posisi dengan `health_factor > 1` tidak bisa dilikuidasi\n3. Likuidasi selalu mengurangi bad debt\n\n## Fuzzing dengan Foundry\n\n### Dasar: Fuzz Test\n```solidity\nfunction testFuzz_DepositWithdraw(\n    uint256 depositAmount,\n    uint256 withdrawAmount\n) public {\n    depositAmount = bound(depositAmount, 1, 1000 ether);\n    withdrawAmount = bound(withdrawAmount, 1, depositAmount);\n    \n    deal(address(token), address(this), depositAmount);\n    token.approve(address(vault), depositAmount);\n    vault.deposit(depositAmount);\n    \n    uint256 sharesBefore = vault.balanceOf(address(this));\n    vault.withdraw(withdrawAmount);\n    \n    \u002F\u002F Properti: shares berkurang proporsional\n    assertLe(\n        vault.balanceOf(address(this)),\n        sharesBefore\n    );\n}\n```\n\n### Lanjutan: Invariant Testing\n```solidity\ncontract VaultInvariant is Test {\n    Vault vault;\n    Token token;\n    InvariantHandler handler;\n    \n    function setUp() public {\n        token = new Token();\n        vault = new Vault(address(token));\n        handler = new InvariantHandler(vault, token);\n        \n        targetContract(address(handler));\n    }\n    \n    \u002F\u002F Foundry memanggil fungsi handler secara acak\n    function invariant_totalAssetsMatchDeposits() public {\n        assertEq(\n            vault.totalAssets(),\n            handler.ghost_totalDeposited() - handler.ghost_totalWithdrawn()\n        );\n    }\n    \n    function invariant_solvency() public {\n        assertGe(\n            token.balanceOf(address(vault)),\n            vault.totalAssets()\n        );\n    }\n}\n\ncontract InvariantHandler is Test {\n    Vault vault;\n    Token token;\n    uint256 public ghost_totalDeposited;\n    uint256 public ghost_totalWithdrawn;\n    \n    function deposit(uint256 amount) external {\n        amount = bound(amount, 1, 100 ether);\n        deal(address(token), address(this), amount);\n        token.approve(address(vault), amount);\n        vault.deposit(amount);\n        ghost_totalDeposited += amount;\n    }\n    \n    function withdraw(uint256 amount) external {\n        uint256 max = vault.balanceOf(address(this));\n        if (max == 0) return;\n        amount = bound(amount, 1, max);\n        vault.withdraw(amount);\n        ghost_totalWithdrawn += amount;\n    }\n}\n```\n\n### Konfigurasi Fuzzing\n```toml\n# foundry.toml\n[fuzz]\nruns = 10000           # Iterasi per fuzz test\nmax_test_rejects = 1000 # Skip input yang tidak valid\nseed = 42              # Untuk reprodusibilitas\n\n[invariant]\nruns = 256             # Urutan operasi acak\ndepth = 100            # Operasi per urutan\nfail_on_revert = false # Jangan gagal pada revert handler\n```\n\n## Menemukan Kerentanan Nyata\n\nContoh kerentanan yang ditemukan oleh fuzzing:\n\n### 1. Rounding Error di Vault\n```solidity\n\u002F\u002F Bug: deposit 1 wei menghasilkan 0 shares (pembulatan ke bawah)\n\u002F\u002F tetapi token tetap ditransfer ke vault\nfunction testFuzz_MinDeposit(uint256 amount) public {\n    amount = bound(amount, 1, 100);\n    deal(address(token), address(this), amount);\n    token.approve(address(vault), amount);\n    \n    uint256 shares = vault.deposit(amount);\n    \n    \u002F\u002F Ditemukan: shares == 0 untuk amount == 1\n    \u002F\u002F Token hilang selamanya!\n    assertGt(shares, 0, \"Zero shares minted\");\n}\n```\n\n### 2. Overflow di Kalkulasi Harga\n```solidity\nfunction testFuzz_PriceOverflow(\n    uint256 reserve0,\n    uint256 reserve1,\n    uint256 amountIn\n) public {\n    reserve0 = bound(reserve0, 1e18, 1e30);\n    reserve1 = bound(reserve1, 1e18, 1e30);\n    amountIn = bound(amountIn, 1e18, 1e28);\n    \n    \u002F\u002F Ditemukan: mul(amountIn, 997) overflow untuk amountIn besar\n    uint256 output = getAmountOut(amountIn, reserve0, reserve1);\n    assertGt(output, 0);\n}\n```\n\n## Differential Testing\n\nBandingkan implementasi Huff dengan referensi Solidity:\n\n```solidity\nfunction testFuzz_Differential(\n    uint256 a,\n    uint256 b\n) public {\n    vm.assume(b > 0);  \u002F\u002F Hindari division by zero\n    \n    \u002F\u002F Referensi Solidity\n    uint256 expected = a \u002F b;\n    \n    \u002F\u002F Implementasi Huff\n    (bool ok, bytes memory data) = huffContract.call(\n        abi.encodeWithSignature(\"div(uint256,uint256)\", a, b)\n    );\n    assertTrue(ok);\n    uint256 actual = abi.decode(data, (uint256));\n    \n    assertEq(actual, expected, \"Huff div mismatch\");\n}\n```\n\n## Kesimpulan\n\nProperty-based testing dan fuzzing adalah senjata paling kuat untuk menemukan bug smart contract. Definisikan invariant yang jelas, tulis handler yang mencakup semua operasi, dan jalankan ribuan iterasi. Untuk kontrak Huff, tambahkan differential testing terhadap referensi Solidity untuk memverifikasi kebenaran di tingkat opcode.","\u003Ch2 id=\"mengapa-unit-test-tidak-cukup\">Mengapa Unit Test Tidak Cukup\u003C\u002Fh2>\n\u003Cp>Unit test memverifikasi bahwa kode Anda bekerja untuk input spesifik yang Anda pilih. Tetapi smart contract menangani input dari pengguna mana pun — termasuk penyerang yang secara aktif mencari edge case. Property-based testing membalikkan pendekatan: alih-alih menentukan input, Anda mendefinisikan properti yang harus selalu benar, dan fuzzer mencari input yang melanggarnya.\u003C\u002Fp>\n\u003Ch2 id=\"properti-vs-unit-test\">Properti vs Unit Test\u003C\u002Fh2>\n\u003Cpre>\u003Ccode class=\"language-solidity\">\u002F\u002F Unit test: satu input spesifik\nfunction testTransfer() public {\n    token.transfer(alice, 100);\n    assertEq(token.balanceOf(alice), 100);\n}\n\n\u002F\u002F Property test: untuk SEMUA input yang valid\nfunction testFuzz_Transfer(address to, uint256 amount) public {\n    amount = bound(amount, 0, token.balanceOf(address(this)));\n    vm.assume(to != address(0));\n    \n    uint256 totalBefore = token.totalSupply();\n    token.transfer(to, amount);\n    uint256 totalAfter = token.totalSupply();\n    \n    \u002F\u002F PROPERTI: total supply tidak berubah\n    assertEq(totalBefore, totalAfter);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"mendefinisikan-invariant\">Mendefinisikan Invariant\u003C\u002Fh2>\n\u003Cp>Invariant adalah kondisi yang harus SELALU benar, terlepas dari urutan operasi:\u003C\u002Fp>\n\u003Ch3>Invariant ERC20\u003C\u002Fh3>\n\u003Col>\n\u003Cli>\u003Ccode>totalSupply == sum(balances[semua_address])\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>\u003Ccode>transfer\u003C\u002Fcode> tidak mengubah totalSupply\u003C\u002Fli>\n\u003Cli>\u003Ccode>balanceOf(x)\u003C\u002Fcode> tidak pernah negatif (dijamin oleh uint256)\u003C\u002Fli>\n\u003Cli>\u003Ccode>transfer(to, amount)\u003C\u002Fcode> mengurangi pengirim dan menambah penerima persis \u003Ccode>amount\u003C\u002Fcode>\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3>Invariant AMM (Uniswap V2)\u003C\u002Fh3>\n\u003Col>\n\u003Cli>\u003Ccode>reserve0 * reserve1 &gt;= k\u003C\u002Fcode> (setelah fee)\u003C\u002Fli>\n\u003Cli>\u003Ccode>reserve0 &gt; 0 &amp;&amp; reserve1 &gt; 0\u003C\u002Fcode> (selalu memiliki likuiditas)\u003C\u002Fli>\n\u003Cli>Swap tidak pernah mengeluarkan lebih dari reserve yang ada\u003C\u002Fli>\n\u003Cli>\u003Ccode>price_impact\u003C\u002Fcode> meningkat secara monoton dengan ukuran swap\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3>Invariant Lending Protocol\u003C\u002Fh3>\n\u003Col>\n\u003Cli>\u003Ccode>total_deposited &gt;= total_borrowed\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>Posisi dengan \u003Ccode>health_factor &gt; 1\u003C\u002Fcode> tidak bisa dilikuidasi\u003C\u002Fli>\n\u003Cli>Likuidasi selalu mengurangi bad debt\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"fuzzing-dengan-foundry\">Fuzzing dengan Foundry\u003C\u002Fh2>\n\u003Ch3>Dasar: Fuzz Test\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-solidity\">function testFuzz_DepositWithdraw(\n    uint256 depositAmount,\n    uint256 withdrawAmount\n) public {\n    depositAmount = bound(depositAmount, 1, 1000 ether);\n    withdrawAmount = bound(withdrawAmount, 1, depositAmount);\n    \n    deal(address(token), address(this), depositAmount);\n    token.approve(address(vault), depositAmount);\n    vault.deposit(depositAmount);\n    \n    uint256 sharesBefore = vault.balanceOf(address(this));\n    vault.withdraw(withdrawAmount);\n    \n    \u002F\u002F Properti: shares berkurang proporsional\n    assertLe(\n        vault.balanceOf(address(this)),\n        sharesBefore\n    );\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Lanjutan: Invariant Testing\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-solidity\">contract VaultInvariant is Test {\n    Vault vault;\n    Token token;\n    InvariantHandler handler;\n    \n    function setUp() public {\n        token = new Token();\n        vault = new Vault(address(token));\n        handler = new InvariantHandler(vault, token);\n        \n        targetContract(address(handler));\n    }\n    \n    \u002F\u002F Foundry memanggil fungsi handler secara acak\n    function invariant_totalAssetsMatchDeposits() public {\n        assertEq(\n            vault.totalAssets(),\n            handler.ghost_totalDeposited() - handler.ghost_totalWithdrawn()\n        );\n    }\n    \n    function invariant_solvency() public {\n        assertGe(\n            token.balanceOf(address(vault)),\n            vault.totalAssets()\n        );\n    }\n}\n\ncontract InvariantHandler is Test {\n    Vault vault;\n    Token token;\n    uint256 public ghost_totalDeposited;\n    uint256 public ghost_totalWithdrawn;\n    \n    function deposit(uint256 amount) external {\n        amount = bound(amount, 1, 100 ether);\n        deal(address(token), address(this), amount);\n        token.approve(address(vault), amount);\n        vault.deposit(amount);\n        ghost_totalDeposited += amount;\n    }\n    \n    function withdraw(uint256 amount) external {\n        uint256 max = vault.balanceOf(address(this));\n        if (max == 0) return;\n        amount = bound(amount, 1, max);\n        vault.withdraw(amount);\n        ghost_totalWithdrawn += amount;\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Konfigurasi Fuzzing\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-toml\"># foundry.toml\n[fuzz]\nruns = 10000           # Iterasi per fuzz test\nmax_test_rejects = 1000 # Skip input yang tidak valid\nseed = 42              # Untuk reprodusibilitas\n\n[invariant]\nruns = 256             # Urutan operasi acak\ndepth = 100            # Operasi per urutan\nfail_on_revert = false # Jangan gagal pada revert handler\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"menemukan-kerentanan-nyata\">Menemukan Kerentanan Nyata\u003C\u002Fh2>\n\u003Cp>Contoh kerentanan yang ditemukan oleh fuzzing:\u003C\u002Fp>\n\u003Ch3>1. Rounding Error di Vault\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-solidity\">\u002F\u002F Bug: deposit 1 wei menghasilkan 0 shares (pembulatan ke bawah)\n\u002F\u002F tetapi token tetap ditransfer ke vault\nfunction testFuzz_MinDeposit(uint256 amount) public {\n    amount = bound(amount, 1, 100);\n    deal(address(token), address(this), amount);\n    token.approve(address(vault), amount);\n    \n    uint256 shares = vault.deposit(amount);\n    \n    \u002F\u002F Ditemukan: shares == 0 untuk amount == 1\n    \u002F\u002F Token hilang selamanya!\n    assertGt(shares, 0, \"Zero shares minted\");\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>2. Overflow di Kalkulasi Harga\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-solidity\">function testFuzz_PriceOverflow(\n    uint256 reserve0,\n    uint256 reserve1,\n    uint256 amountIn\n) public {\n    reserve0 = bound(reserve0, 1e18, 1e30);\n    reserve1 = bound(reserve1, 1e18, 1e30);\n    amountIn = bound(amountIn, 1e18, 1e28);\n    \n    \u002F\u002F Ditemukan: mul(amountIn, 997) overflow untuk amountIn besar\n    uint256 output = getAmountOut(amountIn, reserve0, reserve1);\n    assertGt(output, 0);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"differential-testing\">Differential Testing\u003C\u002Fh2>\n\u003Cp>Bandingkan implementasi Huff dengan referensi Solidity:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-solidity\">function testFuzz_Differential(\n    uint256 a,\n    uint256 b\n) public {\n    vm.assume(b &gt; 0);  \u002F\u002F Hindari division by zero\n    \n    \u002F\u002F Referensi Solidity\n    uint256 expected = a \u002F b;\n    \n    \u002F\u002F Implementasi Huff\n    (bool ok, bytes memory data) = huffContract.call(\n        abi.encodeWithSignature(\"div(uint256,uint256)\", a, b)\n    );\n    assertTrue(ok);\n    uint256 actual = abi.decode(data, (uint256));\n    \n    assertEq(actual, expected, \"Huff div mismatch\");\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"kesimpulan\">Kesimpulan\u003C\u002Fh2>\n\u003Cp>Property-based testing dan fuzzing adalah senjata paling kuat untuk menemukan bug smart contract. Definisikan invariant yang jelas, tulis handler yang mencakup semua operasi, dan jalankan ribuan iterasi. Untuk kontrak Huff, tambahkan differential testing terhadap referensi Solidity untuk memverifikasi kebenaran di tingkat opcode.\u003C\u002Fp>\n","id","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:25.115278Z","Property-based testing dan fuzzing smart contract: invariant, fuzz test Foundry, dan menemukan kerentanan tersembunyi.","fuzzing smart contract Foundry",null,"index, follow",[21,26,30,34],{"id":22,"name":23,"slug":24,"created_at":25},"c0000000-0000-0000-0000-000000000016","EVM","evm","2026-03-28T10:44:21.513630Z",{"id":27,"name":28,"slug":29,"created_at":25},"c0000000-0000-0000-0000-000000000021","Foundry","foundry",{"id":31,"name":32,"slug":33,"created_at":25},"c0000000-0000-0000-0000-000000000013","Security","security",{"id":35,"name":36,"slug":37,"created_at":25},"c0000000-0000-0000-0000-000000000018","Yul","yul","Blockchain",[40,46,52],{"id":41,"title":42,"slug":43,"excerpt":44,"locale":12,"category_name":38,"published_at":45},"d0000000-0000-0000-0000-000000000596","Lapisan Interoperabilitas Ethereum: Bagaimana 55+ L2 Menjadi Satu Chain","lapisan-interoperabilitas-ethereum-bagaimana-55-l2-menjadi-satu-chain","Ethereum memiliki 55+ rollup Layer 2, memecah likuiditas dan pengalaman pengguna. Lapisan Interoperabilitas Ethereum — menggabungkan pesan lintas-rollup, shared sequencer, dan based rollup — bertujuan menyatukan mereka menjadi satu jaringan yang dapat dikomposisi.","2026-03-28T10:44:44.364342Z",{"id":47,"title":48,"slug":49,"excerpt":50,"locale":12,"category_name":38,"published_at":51},"d0000000-0000-0000-0000-000000000595","ZK Proofs Melampaui Rollups: Inferensi AI Terverifikasi di Ethereum","zk-proofs-melampaui-rollups-inferensi-ai-terverifikasi-ethereum","Zero-knowledge proofs bukan lagi sekadar alat penskalaan. Pada 2026, zkML memungkinkan inferensi AI terverifikasi on-chain, ZK coprocessor memindahkan komputasi berat off-chain dengan verifikasi on-chain, dan sistem pembuktian baru seperti SP1 dan Jolt menjadikannya praktis.","2026-03-28T10:44:44.358370Z",{"id":53,"title":54,"slug":55,"excerpt":56,"locale":12,"category_name":38,"published_at":57},"d0000000-0000-0000-0000-000000000572","EIP-7702 dalam Praktik: Membangun Alur Akun Pintar Setelah Pectra","eip-7702-dalam-praktik-membangun-alur-akun-pintar-setelah-pectra","EIP-7702 memungkinkan EOA Ethereum mana pun untuk sementara bertindak sebagai kontrak pintar dalam satu transaksi. Berikut cara mengimplementasikan transaksi batch, sponsorship gas, dan social recovery menggunakan primitif account abstraction baru.","2026-03-28T10:44:42.816894Z",{"id":13,"name":59,"slug":60,"bio":61,"photo_url":18,"linkedin":18,"role":62,"created_at":63,"updated_at":63},"Open Soft Team","open-soft-team","The engineering team at Open Soft, building premium software solutions from Bali, Indonesia.","Engineering Team","2026-03-28T08:31:22.226811Z"]