[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-deep-evm-14-pencari-siklus-arbitrase-dfs-graf-pool":3},{"article":4,"author":57},{"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":34,"related_articles":35},"d2000000-0000-0000-0000-000000000114","a0000000-0000-0000-0000-000000000026","Deep EVM #14: Membangun Pencari Siklus Arbitrase — DFS pada Graf Pool","deep-evm-14-pencari-siklus-arbitrase-dfs-graf-pool","Implementasi algoritma pencarian siklus arbitrase menggunakan DFS pada graf pool DEX. Bangun graf, temukan siklus menguntungkan, dan hitung profitabilitas.","## Masalah Pencarian Arbitrase\n\nArbitrase DEX pada dasarnya adalah masalah graf: setiap pool likuiditas adalah edge yang menghubungkan dua token (vertex). Siklus menguntungkan dalam graf ini merepresentasikan peluang arbitrase.\n\nMisalnya:\n- Pool A: WETH\u002FUSDC di Uniswap\n- Pool B: USDC\u002FDAI di SushiSwap\n- Pool C: DAI\u002FWETH di Curve\n\nJika harga relatif memungkinkan, siklus WETH -> USDC -> DAI -> WETH bisa menghasilkan lebih banyak WETH dari yang Anda mulai.\n\n## Membangun Graf Pool\n\nLangkah pertama adalah membangun graf dari semua pool yang tersedia:\n\n```rust\nuse std::collections::HashMap;\n\n#[derive(Clone)]\nstruct Pool {\n    address: Address,\n    token0: Address,\n    token1: Address,\n    reserve0: U256,\n    reserve1: U256,\n    fee: u32, \u002F\u002F basis points (30 = 0.3%)\n}\n\nstruct PoolGraph {\n    \u002F\u002F token -> [(pool_index, other_token)]\n    adjacency: HashMap\u003CAddress, Vec\u003C(usize, Address)>>,\n    pools: Vec\u003CPool>,\n}\n\nimpl PoolGraph {\n    fn new() -> Self {\n        Self {\n            adjacency: HashMap::new(),\n            pools: Vec::new(),\n        }\n    }\n    \n    fn add_pool(&mut self, pool: Pool) {\n        let idx = self.pools.len();\n        self.adjacency\n            .entry(pool.token0)\n            .or_default()\n            .push((idx, pool.token1));\n        self.adjacency\n            .entry(pool.token1)\n            .or_default()\n            .push((idx, pool.token0));\n        self.pools.push(pool);\n    }\n}\n```\n\n## Algoritma DFS untuk Pencarian Siklus\n\nKami menggunakan Depth-First Search (DFS) yang dimulai dari token tertentu dan mencari jalur yang kembali ke token awal:\n\n```rust\nimpl PoolGraph {\n    fn find_cycles(\n        &self,\n        start: Address,\n        max_depth: usize,\n    ) -> Vec\u003CVec\u003Cusize>> {\n        let mut cycles = Vec::new();\n        let mut path = Vec::new();\n        let mut visited_pools = vec![false; self.pools.len()];\n        \n        self.dfs(\n            start,\n            start,\n            &mut path,\n            &mut visited_pools,\n            &mut cycles,\n            max_depth,\n        );\n        \n        cycles\n    }\n    \n    fn dfs(\n        &self,\n        current: Address,\n        target: Address,\n        path: &mut Vec\u003Cusize>,\n        visited: &mut Vec\u003Cbool>,\n        cycles: &mut Vec\u003CVec\u003Cusize>>,\n        max_depth: usize,\n    ) {\n        if path.len() >= max_depth {\n            return;\n        }\n        \n        if let Some(neighbors) = self.adjacency.get(&current) {\n            for &(pool_idx, next_token) in neighbors {\n                if visited[pool_idx] {\n                    continue;\n                }\n                \n                \u002F\u002F Siklus ditemukan!\n                if next_token == target && path.len() >= 2 {\n                    let mut cycle = path.clone();\n                    cycle.push(pool_idx);\n                    cycles.push(cycle);\n                    continue;\n                }\n                \n                visited[pool_idx] = true;\n                path.push(pool_idx);\n                self.dfs(next_token, target, path, visited, cycles, max_depth);\n                path.pop();\n                visited[pool_idx] = false;\n            }\n        }\n    }\n}\n```\n\n## Menghitung Profitabilitas\n\nSetelah menemukan siklus, kita perlu menghitung apakah siklus tersebut menguntungkan:\n\n```rust\nfn calculate_output(\n    amount_in: U256,\n    reserve_in: U256,\n    reserve_out: U256,\n    fee_bps: u32,\n) -> U256 {\n    let fee_multiplier = 10000 - fee_bps; \u002F\u002F misal 9970 untuk 0.3%\n    let amount_with_fee = amount_in * U256::from(fee_multiplier);\n    let numerator = amount_with_fee * reserve_out;\n    let denominator = reserve_in * U256::from(10000) + amount_with_fee;\n    numerator \u002F denominator\n}\n\nfn is_profitable(\n    graph: &PoolGraph,\n    cycle: &[usize],\n    start_token: Address,\n    amount_in: U256,\n) -> Option\u003CU256> {\n    let mut current_amount = amount_in;\n    let mut current_token = start_token;\n    \n    for &pool_idx in cycle {\n        let pool = &graph.pools[pool_idx];\n        let (reserve_in, reserve_out) = if current_token == pool.token0 {\n            (pool.reserve0, pool.reserve1)\n        } else {\n            (pool.reserve1, pool.reserve0)\n        };\n        \n        current_amount = calculate_output(\n            current_amount,\n            reserve_in,\n            reserve_out,\n            pool.fee,\n        );\n        \n        current_token = if current_token == pool.token0 {\n            pool.token1\n        } else {\n            pool.token0\n        };\n    }\n    \n    if current_amount > amount_in {\n        Some(current_amount - amount_in)\n    } else {\n        None\n    }\n}\n```\n\n## Optimasi Input Optimal\n\nKeuntungan arbitrase adalah fungsi konkaf dari jumlah input. Ada jumlah optimal yang memaksimalkan keuntungan. Kita menggunakan binary search:\n\n```rust\nfn find_optimal_input(\n    graph: &PoolGraph,\n    cycle: &[usize],\n    start_token: Address,\n) -> (U256, U256) {\n    let mut lo = U256::from(1_000_000u64);  \u002F\u002F minimum 0.001 ETH\n    let mut hi = U256::from(100_000_000_000_000_000_000u128); \u002F\u002F 100 ETH\n    \n    for _ in 0..64 {\n        let mid1 = lo + (hi - lo) \u002F 3;\n        let mid2 = hi - (hi - lo) \u002F 3;\n        \n        let profit1 = is_profitable(graph, cycle, start_token, mid1)\n            .unwrap_or(U256::ZERO);\n        let profit2 = is_profitable(graph, cycle, start_token, mid2)\n            .unwrap_or(U256::ZERO);\n        \n        if profit1 \u003C profit2 {\n            lo = mid1;\n        } else {\n            hi = mid2;\n        }\n    }\n    \n    let optimal = (lo + hi) \u002F 2;\n    let profit = is_profitable(graph, cycle, start_token, optimal)\n        .unwrap_or(U256::ZERO);\n    (optimal, profit)\n}\n```\n\n## Pertimbangan Praktis\n\n1. **Latensi** — Pencarian harus selesai dalam milidetik. Batasi kedalaman DFS ke 3-4 hop.\n2. **Reserves stale** — Gunakan event subscription untuk memperbarui reserves secara real-time.\n3. **Gas cost** — Kurangi estimasi biaya gas dari profit untuk menentukan profitabilitas bersih.\n4. **Slippage** — Swap besar memindahkan harga; perhitungan dengan reserves statis melebih-lebihkan profit.\n5. **Kompetisi** — Pencari lain menemukan siklus yang sama; yang tercepat menang.\n\n## Kesimpulan\n\nPencarian siklus arbitrase adalah masalah graf klasik yang diterapkan ke DeFi. DFS dengan batasan kedalaman menemukan siklus, kalkulasi AMM menentukan profitabilitas, dan binary search mengoptimalkan jumlah input. Di artikel berikutnya, kita akan mensimulasikan siklus ini terhadap state fork untuk memverifikasi profitabilitas sebelum mengirim bundle.","\u003Ch2 id=\"masalah-pencarian-arbitrase\">Masalah Pencarian Arbitrase\u003C\u002Fh2>\n\u003Cp>Arbitrase DEX pada dasarnya adalah masalah graf: setiap pool likuiditas adalah edge yang menghubungkan dua token (vertex). Siklus menguntungkan dalam graf ini merepresentasikan peluang arbitrase.\u003C\u002Fp>\n\u003Cp>Misalnya:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>Pool A: WETH\u002FUSDC di Uniswap\u003C\u002Fli>\n\u003Cli>Pool B: USDC\u002FDAI di SushiSwap\u003C\u002Fli>\n\u003Cli>Pool C: DAI\u002FWETH di Curve\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Jika harga relatif memungkinkan, siklus WETH -&gt; USDC -&gt; DAI -&gt; WETH bisa menghasilkan lebih banyak WETH dari yang Anda mulai.\u003C\u002Fp>\n\u003Ch2 id=\"membangun-graf-pool\">Membangun Graf Pool\u003C\u002Fh2>\n\u003Cp>Langkah pertama adalah membangun graf dari semua pool yang tersedia:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">use std::collections::HashMap;\n\n#[derive(Clone)]\nstruct Pool {\n    address: Address,\n    token0: Address,\n    token1: Address,\n    reserve0: U256,\n    reserve1: U256,\n    fee: u32, \u002F\u002F basis points (30 = 0.3%)\n}\n\nstruct PoolGraph {\n    \u002F\u002F token -&gt; [(pool_index, other_token)]\n    adjacency: HashMap&lt;Address, Vec&lt;(usize, Address)&gt;&gt;,\n    pools: Vec&lt;Pool&gt;,\n}\n\nimpl PoolGraph {\n    fn new() -&gt; Self {\n        Self {\n            adjacency: HashMap::new(),\n            pools: Vec::new(),\n        }\n    }\n    \n    fn add_pool(&amp;mut self, pool: Pool) {\n        let idx = self.pools.len();\n        self.adjacency\n            .entry(pool.token0)\n            .or_default()\n            .push((idx, pool.token1));\n        self.adjacency\n            .entry(pool.token1)\n            .or_default()\n            .push((idx, pool.token0));\n        self.pools.push(pool);\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"algoritma-dfs-untuk-pencarian-siklus\">Algoritma DFS untuk Pencarian Siklus\u003C\u002Fh2>\n\u003Cp>Kami menggunakan Depth-First Search (DFS) yang dimulai dari token tertentu dan mencari jalur yang kembali ke token awal:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">impl PoolGraph {\n    fn find_cycles(\n        &amp;self,\n        start: Address,\n        max_depth: usize,\n    ) -&gt; Vec&lt;Vec&lt;usize&gt;&gt; {\n        let mut cycles = Vec::new();\n        let mut path = Vec::new();\n        let mut visited_pools = vec![false; self.pools.len()];\n        \n        self.dfs(\n            start,\n            start,\n            &amp;mut path,\n            &amp;mut visited_pools,\n            &amp;mut cycles,\n            max_depth,\n        );\n        \n        cycles\n    }\n    \n    fn dfs(\n        &amp;self,\n        current: Address,\n        target: Address,\n        path: &amp;mut Vec&lt;usize&gt;,\n        visited: &amp;mut Vec&lt;bool&gt;,\n        cycles: &amp;mut Vec&lt;Vec&lt;usize&gt;&gt;,\n        max_depth: usize,\n    ) {\n        if path.len() &gt;= max_depth {\n            return;\n        }\n        \n        if let Some(neighbors) = self.adjacency.get(&amp;current) {\n            for &amp;(pool_idx, next_token) in neighbors {\n                if visited[pool_idx] {\n                    continue;\n                }\n                \n                \u002F\u002F Siklus ditemukan!\n                if next_token == target &amp;&amp; path.len() &gt;= 2 {\n                    let mut cycle = path.clone();\n                    cycle.push(pool_idx);\n                    cycles.push(cycle);\n                    continue;\n                }\n                \n                visited[pool_idx] = true;\n                path.push(pool_idx);\n                self.dfs(next_token, target, path, visited, cycles, max_depth);\n                path.pop();\n                visited[pool_idx] = false;\n            }\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"menghitung-profitabilitas\">Menghitung Profitabilitas\u003C\u002Fh2>\n\u003Cp>Setelah menemukan siklus, kita perlu menghitung apakah siklus tersebut menguntungkan:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">fn calculate_output(\n    amount_in: U256,\n    reserve_in: U256,\n    reserve_out: U256,\n    fee_bps: u32,\n) -&gt; U256 {\n    let fee_multiplier = 10000 - fee_bps; \u002F\u002F misal 9970 untuk 0.3%\n    let amount_with_fee = amount_in * U256::from(fee_multiplier);\n    let numerator = amount_with_fee * reserve_out;\n    let denominator = reserve_in * U256::from(10000) + amount_with_fee;\n    numerator \u002F denominator\n}\n\nfn is_profitable(\n    graph: &amp;PoolGraph,\n    cycle: &amp;[usize],\n    start_token: Address,\n    amount_in: U256,\n) -&gt; Option&lt;U256&gt; {\n    let mut current_amount = amount_in;\n    let mut current_token = start_token;\n    \n    for &amp;pool_idx in cycle {\n        let pool = &amp;graph.pools[pool_idx];\n        let (reserve_in, reserve_out) = if current_token == pool.token0 {\n            (pool.reserve0, pool.reserve1)\n        } else {\n            (pool.reserve1, pool.reserve0)\n        };\n        \n        current_amount = calculate_output(\n            current_amount,\n            reserve_in,\n            reserve_out,\n            pool.fee,\n        );\n        \n        current_token = if current_token == pool.token0 {\n            pool.token1\n        } else {\n            pool.token0\n        };\n    }\n    \n    if current_amount &gt; amount_in {\n        Some(current_amount - amount_in)\n    } else {\n        None\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"optimasi-input-optimal\">Optimasi Input Optimal\u003C\u002Fh2>\n\u003Cp>Keuntungan arbitrase adalah fungsi konkaf dari jumlah input. Ada jumlah optimal yang memaksimalkan keuntungan. Kita menggunakan binary search:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">fn find_optimal_input(\n    graph: &amp;PoolGraph,\n    cycle: &amp;[usize],\n    start_token: Address,\n) -&gt; (U256, U256) {\n    let mut lo = U256::from(1_000_000u64);  \u002F\u002F minimum 0.001 ETH\n    let mut hi = U256::from(100_000_000_000_000_000_000u128); \u002F\u002F 100 ETH\n    \n    for _ in 0..64 {\n        let mid1 = lo + (hi - lo) \u002F 3;\n        let mid2 = hi - (hi - lo) \u002F 3;\n        \n        let profit1 = is_profitable(graph, cycle, start_token, mid1)\n            .unwrap_or(U256::ZERO);\n        let profit2 = is_profitable(graph, cycle, start_token, mid2)\n            .unwrap_or(U256::ZERO);\n        \n        if profit1 &lt; profit2 {\n            lo = mid1;\n        } else {\n            hi = mid2;\n        }\n    }\n    \n    let optimal = (lo + hi) \u002F 2;\n    let profit = is_profitable(graph, cycle, start_token, optimal)\n        .unwrap_or(U256::ZERO);\n    (optimal, profit)\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"pertimbangan-praktis\">Pertimbangan Praktis\u003C\u002Fh2>\n\u003Col>\n\u003Cli>\u003Cstrong>Latensi\u003C\u002Fstrong> — Pencarian harus selesai dalam milidetik. Batasi kedalaman DFS ke 3-4 hop.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Reserves stale\u003C\u002Fstrong> — Gunakan event subscription untuk memperbarui reserves secara real-time.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Gas cost\u003C\u002Fstrong> — Kurangi estimasi biaya gas dari profit untuk menentukan profitabilitas bersih.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Slippage\u003C\u002Fstrong> — Swap besar memindahkan harga; perhitungan dengan reserves statis melebih-lebihkan profit.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Kompetisi\u003C\u002Fstrong> — Pencari lain menemukan siklus yang sama; yang tercepat menang.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"kesimpulan\">Kesimpulan\u003C\u002Fh2>\n\u003Cp>Pencarian siklus arbitrase adalah masalah graf klasik yang diterapkan ke DeFi. DFS dengan batasan kedalaman menemukan siklus, kalkulasi AMM menentukan profitabilitas, dan binary search mengoptimalkan jumlah input. Di artikel berikutnya, kita akan mensimulasikan siklus ini terhadap state fork untuk memverifikasi profitabilitas sebelum mengirim bundle.\u003C\u002Fp>\n","id","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:24.898325Z","Implementasi pencari siklus arbitrase: bangun graf pool DEX, temukan siklus menguntungkan dengan DFS, dan hitung profitabilitas.","arbitrase MEV DFS",null,"index, follow",[21,26,30],{"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-000000000019","MEV","mev",{"id":31,"name":32,"slug":33,"created_at":25},"c0000000-0000-0000-0000-000000000001","Rust","rust","Rekayasa",[36,43,50],{"id":37,"title":38,"slug":39,"excerpt":40,"locale":12,"category_name":41,"published_at":42},"d0000000-0000-0000-0000-000000000642","WASI 0.3 dan Kematian Cold Start: Wasm Sisi Server di Produksi","wasi-0-3-kematian-cold-start-wasm-sisi-server-di-produksi","WASI 0.3 dirilis pada Februari 2026 dengan async I\u002FO native, tipe stream, dan dukungan socket penuh. WebAssembly sisi server kini menghadirkan cold start dalam hitungan mikrodetik, dan setiap penyedia cloud besar menawarkan Wasm serverless.","DevOps","2026-03-28T10:44:47.445780Z",{"id":44,"title":45,"slug":46,"excerpt":47,"locale":12,"category_name":48,"published_at":49},"d0000000-0000-0000-0000-000000000620","Stack Backend Modern 2026: Rust + PostgreSQL 18 + Wasm + eBPF","stack-backend-modern-2026-rust-postgresql-wasm-ebpf","Empat teknologi konvergen untuk mendefinisikan ulang infrastruktur backend di 2026: Rust menghilangkan overhead garbage collection dan mengurangi jumlah container hingga 3x, PostgreSQL 18 menggantikan database khusus, WASI 0.3 memberikan cold start mikrodetik untuk fungsi serverless, dan eBPF memungkinkan observabilitas tanpa instrumentasi dengan biaya yang jauh lebih rendah dari monitoring tradisional.","Engineering","2026-03-28T10:44:45.804120Z",{"id":51,"title":52,"slug":53,"excerpt":54,"locale":12,"category_name":55,"published_at":56},"d0000000-0000-0000-0000-000000000571","Peta Jalan Skalabilitas Ethereum 2026: Glamsterdam, PeerDAS, dan 10.000 TPS","peta-jalan-skalabilitas-ethereum-2026-glamsterdam-peerdas-10000-tps","Ethereum menargetkan 10.000 transaksi per detik di seluruh ekosistem L1 dan L2 pada 2026. Dengan upgrade Glamsterdam dan Hegota di depan mata, ditambah PeerDAS untuk ketersediaan data, berikut peta jalan teknis lengkapnya.","Blockchain","2026-03-28T10:44:42.809066Z",{"id":13,"name":58,"slug":59,"bio":60,"photo_url":18,"linkedin":18,"role":61,"created_at":62,"updated_at":62},"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"]