[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-deep-evm-14-cycles-arbitrage-dfs-graphe-pools":3},{"article":4,"author":56},{"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},"d6000000-0000-0000-0000-000000000114","a0000000-0000-0000-0000-000000000062","Deep EVM #14 : Construire un algorithme de cycles d'arbitrage — DFS sur un graphe de pools","deep-evm-14-cycles-arbitrage-dfs-graphe-pools","Construisez un algorithme de recherche de cycles d'arbitrage en Rust utilisant le parcours en profondeur (DFS) sur un graphe de pools de liquidité. Modélisation, élagage et optimisation.","## Modéliser les DEX comme un graphe\n\nPour trouver des opportunités d'arbitrage, nous devons d'abord modéliser l'ensemble des pools de liquidité comme un graphe dirigé.\n\nChaque pool AMM (Uniswap V2, V3, Sushiswap, etc.) représente une arête bidirectionnelle entre deux tokens :\n\n```rust\nstruct Pool {\n    address: Address,\n    token0: Address,\n    token1: Address,\n    reserve0: U256,\n    reserve1: U256,\n    fee: u32,  \u002F\u002F En points de base (30 = 0.3%)\n}\n\nstruct Graph {\n    adjacency: HashMap\u003CAddress, Vec\u003C(Address, Pool)>>,  \u002F\u002F token -> [(token, pool)]\n}\n```\n\nSur Ethereum mainnet, il y a plus de 100 000 pools. Notre graphe a des milliers de nœuds (tokens) et des centaines de milliers d'arêtes (pools).\n\n## DFS avec détection de cycles\n\nUn cycle d'arbitrage est un chemin qui commence et se termine au même token, où l'exécution du chemin produit plus de tokens qu'au départ.\n\n```rust\nfn find_cycles(\n    graph: &Graph,\n    start_token: Address,\n    max_depth: usize,\n) -> Vec\u003CVec\u003CPool>> {\n    let mut result = Vec::new();\n    let mut path = Vec::new();\n    let mut visited = HashSet::new();\n\n    dfs(\n        graph,\n        start_token,\n        start_token,\n        &mut path,\n        &mut visited,\n        &mut result,\n        max_depth,\n    );\n    result\n}\n\nfn dfs(\n    graph: &Graph,\n    current: Address,\n    target: Address,\n    path: &mut Vec\u003CPool>,\n    visited: &mut HashSet\u003CAddress>,\n    result: &mut Vec\u003CVec\u003CPool>>,\n    max_depth: usize,\n) {\n    if path.len() > max_depth {\n        return;\n    }\n\n    if path.len() >= 2 && current == target {\n        result.push(path.clone());\n        return;\n    }\n\n    visited.insert(current);\n\n    if let Some(neighbors) = graph.adjacency.get(&current) {\n        for (next_token, pool) in neighbors {\n            if *next_token == target || !visited.contains(next_token) {\n                path.push(pool.clone());\n                dfs(graph, *next_token, target, path, visited, result, max_depth);\n                path.pop();\n            }\n        }\n    }\n\n    visited.remove(&current);\n}\n```\n\n## Calcul du profit d'un cycle\n\nPour chaque cycle trouvé, calculez le montant de sortie en simulant les swaps séquentiels :\n\n```rust\nfn calculate_output(\n    pools: &[Pool],\n    amount_in: U256,\n) -> U256 {\n    let mut current_amount = amount_in;\n\n    for pool in pools {\n        current_amount = get_amount_out(\n            current_amount,\n            pool.reserve_in,\n            pool.reserve_out,\n            pool.fee,\n        );\n    }\n\n    current_amount\n}\n\nfn get_amount_out(\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;\n    let amount_in_with_fee = amount_in * fee_multiplier;\n    let numerator = amount_in_with_fee * reserve_out;\n    let denominator = reserve_in * 10000 + amount_in_with_fee;\n    numerator \u002F denominator\n}\n```\n\nUn cycle est rentable si `calculate_output(cycle, amount) > amount`.\n\n## Élagage pour les performances\n\nAvec 100 000+ pools, le DFS naïf est trop lent. Techniques d'élagage :\n\n1. **Limiter la profondeur** — Les cycles de plus de 4-5 sauts sont rarement rentables\n2. **Filtrer les pools illiquides** — Ignorer les pools avec moins de 1000 $ de liquidité\n3. **Token de départ stratégique** — Commencer par WETH, USDC, USDT qui apparaissent dans la plupart des cycles rentables\n4. **Pré-filtrage des paires** — Indexer les pools par token pour un accès O(1)\n\n## Optimisation du montant d'entrée\n\nUne fois un cycle rentable identifié, trouvez le montant d'entrée optimal par recherche binaire :\n\n```rust\nfn optimal_input(\n    pools: &[Pool],\n    min_amount: U256,\n    max_amount: U256,\n) -> (U256, U256) {  \u002F\u002F (montant_optimal, profit)\n    let mut lo = min_amount;\n    let mut hi = max_amount;\n\n    for _ in 0..64 {  \u002F\u002F 64 itérations = précision de 1 wei\n        let mid = (lo + hi) \u002F 2;\n        let profit_mid = calculate_output(pools, mid) - mid;\n        let profit_mid_plus = calculate_output(pools, mid + 1) - (mid + 1);\n\n        if profit_mid_plus > profit_mid {\n            lo = mid;\n        } else {\n            hi = mid;\n        }\n    }\n\n    let optimal = (lo + hi) \u002F 2;\n    let profit = calculate_output(pools, optimal) - optimal;\n    (optimal, profit)\n}\n```\n\n## Conclusion\n\nLa recherche de cycles d'arbitrage est le cœur de tout bot MEV d'arbitrage. Le DFS sur un graphe de pools, combiné avec un élagage agressif et une optimisation du montant d'entrée, permet de scanner des milliers de chemins en quelques millisecondes.\n\nDans le prochain article, nous couvrirons la simulation MEV : fork d'état, recherche binaire de montant optimal, et la contrainte du délai de 12 secondes.","\u003Ch2 id=\"mod-liser-les-dex-comme-un-graphe\">Modéliser les DEX comme un graphe\u003C\u002Fh2>\n\u003Cp>Pour trouver des opportunités d’arbitrage, nous devons d’abord modéliser l’ensemble des pools de liquidité comme un graphe dirigé.\u003C\u002Fp>\n\u003Cp>Chaque pool AMM (Uniswap V2, V3, Sushiswap, etc.) représente une arête bidirectionnelle entre deux tokens :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">struct Pool {\n    address: Address,\n    token0: Address,\n    token1: Address,\n    reserve0: U256,\n    reserve1: U256,\n    fee: u32,  \u002F\u002F En points de base (30 = 0.3%)\n}\n\nstruct Graph {\n    adjacency: HashMap&lt;Address, Vec&lt;(Address, Pool)&gt;&gt;,  \u002F\u002F token -&gt; [(token, pool)]\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Sur Ethereum mainnet, il y a plus de 100 000 pools. Notre graphe a des milliers de nœuds (tokens) et des centaines de milliers d’arêtes (pools).\u003C\u002Fp>\n\u003Ch2 id=\"dfs-avec-d-tection-de-cycles\">DFS avec détection de cycles\u003C\u002Fh2>\n\u003Cp>Un cycle d’arbitrage est un chemin qui commence et se termine au même token, où l’exécution du chemin produit plus de tokens qu’au départ.\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">fn find_cycles(\n    graph: &amp;Graph,\n    start_token: Address,\n    max_depth: usize,\n) -&gt; Vec&lt;Vec&lt;Pool&gt;&gt; {\n    let mut result = Vec::new();\n    let mut path = Vec::new();\n    let mut visited = HashSet::new();\n\n    dfs(\n        graph,\n        start_token,\n        start_token,\n        &amp;mut path,\n        &amp;mut visited,\n        &amp;mut result,\n        max_depth,\n    );\n    result\n}\n\nfn dfs(\n    graph: &amp;Graph,\n    current: Address,\n    target: Address,\n    path: &amp;mut Vec&lt;Pool&gt;,\n    visited: &amp;mut HashSet&lt;Address&gt;,\n    result: &amp;mut Vec&lt;Vec&lt;Pool&gt;&gt;,\n    max_depth: usize,\n) {\n    if path.len() &gt; max_depth {\n        return;\n    }\n\n    if path.len() &gt;= 2 &amp;&amp; current == target {\n        result.push(path.clone());\n        return;\n    }\n\n    visited.insert(current);\n\n    if let Some(neighbors) = graph.adjacency.get(&amp;current) {\n        for (next_token, pool) in neighbors {\n            if *next_token == target || !visited.contains(next_token) {\n                path.push(pool.clone());\n                dfs(graph, *next_token, target, path, visited, result, max_depth);\n                path.pop();\n            }\n        }\n    }\n\n    visited.remove(&amp;current);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"calcul-du-profit-d-un-cycle\">Calcul du profit d’un cycle\u003C\u002Fh2>\n\u003Cp>Pour chaque cycle trouvé, calculez le montant de sortie en simulant les swaps séquentiels :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">fn calculate_output(\n    pools: &amp;[Pool],\n    amount_in: U256,\n) -&gt; U256 {\n    let mut current_amount = amount_in;\n\n    for pool in pools {\n        current_amount = get_amount_out(\n            current_amount,\n            pool.reserve_in,\n            pool.reserve_out,\n            pool.fee,\n        );\n    }\n\n    current_amount\n}\n\nfn get_amount_out(\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;\n    let amount_in_with_fee = amount_in * fee_multiplier;\n    let numerator = amount_in_with_fee * reserve_out;\n    let denominator = reserve_in * 10000 + amount_in_with_fee;\n    numerator \u002F denominator\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Un cycle est rentable si \u003Ccode>calculate_output(cycle, amount) &gt; amount\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch2 id=\"lagage-pour-les-performances\">Élagage pour les performances\u003C\u002Fh2>\n\u003Cp>Avec 100 000+ pools, le DFS naïf est trop lent. Techniques d’élagage :\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>Limiter la profondeur\u003C\u002Fstrong> — Les cycles de plus de 4-5 sauts sont rarement rentables\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Filtrer les pools illiquides\u003C\u002Fstrong> — Ignorer les pools avec moins de 1000 $ de liquidité\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Token de départ stratégique\u003C\u002Fstrong> — Commencer par WETH, USDC, USDT qui apparaissent dans la plupart des cycles rentables\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Pré-filtrage des paires\u003C\u002Fstrong> — Indexer les pools par token pour un accès O(1)\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"optimisation-du-montant-d-entr-e\">Optimisation du montant d’entrée\u003C\u002Fh2>\n\u003Cp>Une fois un cycle rentable identifié, trouvez le montant d’entrée optimal par recherche binaire :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">fn optimal_input(\n    pools: &amp;[Pool],\n    min_amount: U256,\n    max_amount: U256,\n) -&gt; (U256, U256) {  \u002F\u002F (montant_optimal, profit)\n    let mut lo = min_amount;\n    let mut hi = max_amount;\n\n    for _ in 0..64 {  \u002F\u002F 64 itérations = précision de 1 wei\n        let mid = (lo + hi) \u002F 2;\n        let profit_mid = calculate_output(pools, mid) - mid;\n        let profit_mid_plus = calculate_output(pools, mid + 1) - (mid + 1);\n\n        if profit_mid_plus &gt; profit_mid {\n            lo = mid;\n        } else {\n            hi = mid;\n        }\n    }\n\n    let optimal = (lo + hi) \u002F 2;\n    let profit = calculate_output(pools, optimal) - optimal;\n    (optimal, profit)\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"conclusion\">Conclusion\u003C\u002Fh2>\n\u003Cp>La recherche de cycles d’arbitrage est le cœur de tout bot MEV d’arbitrage. Le DFS sur un graphe de pools, combiné avec un élagage agressif et une optimisation du montant d’entrée, permet de scanner des milliers de chemins en quelques millisecondes.\u003C\u002Fp>\n\u003Cp>Dans le prochain article, nous couvrirons la simulation MEV : fork d’état, recherche binaire de montant optimal, et la contrainte du délai de 12 secondes.\u003C\u002Fp>\n","fr","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:29.118477Z","Algorithme de recherche de cycles d'arbitrage en Rust : DFS sur graphe de pools, calcul de profit, élagage et optimisation du montant.","arbitrage 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","Blockchain",[36,43,50],{"id":37,"title":38,"slug":39,"excerpt":40,"locale":12,"category_name":41,"published_at":42},"d0000000-0000-0000-0000-000000000654","WASI 0.3 et la mort des démarrages à froid : le Wasm côté serveur en production","wasi-0-3-mort-demarrages-froid-wasm-cote-serveur-production","WASI 0.3 est sorti en février 2026 avec l'async I\u002FO natif, les types stream et le support complet des sockets. Le WebAssembly côté serveur offre désormais des démarrages à froid en microsecondes, et chaque grand fournisseur cloud propose du Wasm serverless.","DevOps","2026-03-28T10:44:48.159283Z",{"id":44,"title":45,"slug":46,"excerpt":47,"locale":12,"category_name":48,"published_at":49},"d0000000-0000-0000-0000-000000000632","La stack backend moderne 2026 : Rust + PostgreSQL 18 + Wasm + eBPF","stack-backend-moderne-2026-rust-postgresql-wasm-ebpf","Quatre technologies convergent pour redefinir l'infrastructure backend en 2026 : Rust elimine l'overhead du garbage collection et reduit le nombre de conteneurs par 3, PostgreSQL 18 remplace les bases specialisees, WASI 0.3 offre des demarrages a froid en microsecondes pour les fonctions serverless, et eBPF permet l'observabilite sans instrumentation a une fraction du cout du monitoring traditionnel.","Engineering","2026-03-28T10:44:46.680187Z",{"id":51,"title":52,"slug":53,"excerpt":54,"locale":12,"category_name":34,"published_at":55},"d0000000-0000-0000-0000-000000000608","La couche d'interoperabilite Ethereum : comment 55+ L2 deviennent une seule chaine","couche-interoperabilite-ethereum-55-l2-deviennent-une-seule-chaine","Ethereum compte 55+ rollups Layer 2, fragmentant la liquidite et l'experience utilisateur. La couche d'interoperabilite Ethereum — combinant messagerie cross-rollup, sequenceurs partages et based rollups — vise a les unifier en un reseau composable unique.","2026-03-28T10:44:45.078068Z",{"id":13,"name":57,"slug":58,"bio":59,"photo_url":18,"linkedin":18,"role":60,"created_at":61,"updated_at":61},"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"]