[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-deep-evm-15-simulyatsiya-mev-binarnyj-poisk-forki-stejta":3},{"article":4,"author":59},{"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":16,"meta_description":17,"focus_keyword":18,"og_image":19,"canonical_url":19,"robots_meta":20,"created_at":15,"updated_at":15,"tags":21,"category_name":39,"related_articles":40},"d0000000-0000-0000-0000-000000000215","a0000000-0000-0000-0000-000000000016","Deep EVM #15: Симуляция MEV — бинарный поиск, форки стейта и 12-секундный дедлайн","deep-evm-15-simulyatsiya-mev-binarnyj-poisk-forki-stejta","Как серчеры симулируют MEV-транзакции: форк состояния блокчейна через revm, бинарный поиск оптимального размера свапа и укладывание в 12-секундный дедлайн слота.","## Зачем нужна симуляция\n\nНайти арбитражный цикл — полдела. Нужно определить оптимальный размер входа (сколько WETH вложить), подтвердить прибыль на актуальном стейте и сделать это за миллисекунды, пока возможность не устарела.\n\nСимуляция отвечает на три вопроса:\n1. **Прибылен ли маршрут?** — с учётом реальных резервов, комиссий, слиппеджа\n2. **Какой оптимальный размер входа?** — слишком мало = мало прибыли, слишком много = price impact съедает профит\n3. **Сколько газа потратится?** — для расчёта чистой прибыли\n\n## Форк стейта с revm\n\nrevm — библиотека на Rust, реализующая полный EVM. Она позволяет исполнять транзакции на локальной копии стейта без взаимодействия с реальной сетью:\n\n```rust\nuse revm::{\n    db::{CacheDB, EmptyDB, EthersDB},\n    primitives::{AccountInfo, Address, Bytecode, U256, TransactTo},\n    EVM,\n};\nuse ethers::providers::{Provider, Http};\n\npub struct StateForker {\n    db: CacheDB\u003CEthersDB\u003CProvider\u003CHttp>>>,\n}\n\nimpl StateForker {\n    pub async fn new(rpc_url: &str) -> Self {\n        let provider = Provider::\u003CHttp>::try_from(rpc_url).unwrap();\n        let ethers_db = EthersDB::new(provider, None).unwrap();\n        let cache_db = CacheDB::new(ethers_db);\n        Self { db: cache_db }\n    }\n    \n    pub fn simulate_call(\n        &mut self,\n        from: Address,\n        to: Address,\n        calldata: Vec\u003Cu8>,\n        value: U256,\n    ) -> SimulationResult {\n        let mut evm = EVM::new();\n        evm.database(&mut self.db);\n        \n        evm.env.tx.caller = from;\n        evm.env.tx.transact_to = TransactTo::Call(to);\n        evm.env.tx.data = calldata.into();\n        evm.env.tx.value = value;\n        evm.env.tx.gas_limit = 500_000;\n        \n        match evm.transact_commit() {\n            Ok(result) => SimulationResult {\n                success: result.is_success(),\n                gas_used: result.gas_used(),\n                output: result.output().unwrap_or_default().to_vec(),\n                logs: result.logs().to_vec(),\n            },\n            Err(e) => SimulationResult {\n                success: false,\n                gas_used: 0,\n                output: vec![],\n                logs: vec![],\n            },\n        }\n    }\n}\n\npub struct SimulationResult {\n    pub success: bool,\n    pub gas_used: u64,\n    pub output: Vec\u003Cu8>,\n    pub logs: Vec\u003Crevm::primitives::Log>,\n}\n```\n\n### Snapshot и Rollback\n\nКлючевая возможность — snapshot\u002Frollback. Вы сохраняете состояние, исполняете транзакцию, проверяете результат и откатываете если нужно:\n\n```rust\nimpl StateForker {\n    pub fn snapshot(&self) -> CacheDB\u003CEthersDB\u003CProvider\u003CHttp>>> {\n        self.db.clone()\n    }\n    \n    pub fn rollback(&mut self, snapshot: CacheDB\u003CEthersDB\u003CProvider\u003CHttp>>>) {\n        self.db = snapshot;\n    }\n}\n```\n\nЭто позволяет тестировать множество вариантов без повторной загрузки стейта.\n\n## Бинарный поиск оптимального размера\n\nФункция прибыли от размера входа имеет один максимум — сначала растёт (больше объём = больше абсолютная прибыль), потом падает (price impact поглощает profit). Это позволяет использовать тернарный или бинарный поиск:\n\n```rust\npub fn find_optimal_input(\n    forker: &mut StateForker,\n    route: &ArbitrageRoute,\n    min_input: U256,\n    max_input: U256,\n    iterations: u32,\n) -> (U256, U256) {\n    let mut lo = min_input;\n    let mut hi = max_input;\n    let mut best_input = min_input;\n    let mut best_profit = U256::ZERO;\n    \n    for _ in 0..iterations {\n        let mid1 = lo + (hi - lo) \u002F 3;\n        let mid2 = hi - (hi - lo) \u002F 3;\n        \n        let snapshot = forker.snapshot();\n        let profit1 = simulate_route(forker, route, mid1);\n        forker.rollback(snapshot.clone());\n        \n        let profit2 = simulate_route(forker, route, mid2);\n        forker.rollback(snapshot);\n        \n        if profit1 \u003C profit2 {\n            lo = mid1;\n            if profit2 > best_profit {\n                best_profit = profit2;\n                best_input = mid2;\n            }\n        } else {\n            hi = mid2;\n            if profit1 > best_profit {\n                best_profit = profit1;\n                best_input = mid1;\n            }\n        }\n    }\n    \n    (best_input, best_profit)\n}\n\nfn simulate_route(\n    forker: &mut StateForker,\n    route: &ArbitrageRoute,\n    input: U256,\n) -> U256 {\n    let calldata = encode_arbitrage_calldata(route, input);\n    let result = forker.simulate_call(\n        SEARCHER_ADDRESS,\n        CONTRACT_ADDRESS,\n        calldata,\n        U256::ZERO,\n    );\n    \n    if !result.success {\n        return U256::ZERO;\n    }\n    \n    \u002F\u002F Декодируем прибыль из output\n    decode_profit(&result.output)\n}\n```\n\n### Почему тернарный поиск\n\nФункция прибыли — унимодальная (один пик). Тернарный поиск гарантированно находит максимум за O(log n) итераций. На практике 30-40 итераций достаточно для точности до 1 wei при диапазоне 0-1000 ETH.\n\n## Учёт газа и приоритетной комиссии\n\nЧистая прибыль серчера:\n\n```rust\npub fn calculate_net_profit(\n    gross_profit: U256,\n    gas_used: u64,\n    base_fee: U256,\n    priority_fee: U256,\n) -> i128 {\n    let gas_cost = U256::from(gas_used) * (base_fee + priority_fee);\n    let builder_tip = gross_profit * 90 \u002F 100;  \u002F\u002F 90% отдаём билдеру\n    \n    let net = gross_profit.as_u128() as i128\n        - gas_cost.as_u128() as i128\n        - builder_tip.as_u128() as i128;\n    net\n}\n```\n\nНа практике серчеры отдают 90-99% прибыли билдеру в виде приоритетной комиссии. Это потому что конкурирующие серчеры поднимают ставки.\n\n## 12-секундный дедлайн\n\nEthereum производит блок каждые 12 секунд. Серчер должен:\n\n1. Получить новый блок\u002Fpending tx (0 мс)\n2. Обновить граф резервов (~5 мс)\n3. Найти арбитражные циклы (~50 мс)\n4. Симулировать топ-N маршрутов (~100-500 мс)\n5. Оптимизировать размер входа (~200 мс)\n6. Подписать и отправить бандл (~10 мс)\n\nИтого: ~500-800 мс. Остальное время — запас на сетевую задержку и непредвиденности.\n\n```rust\nuse tokio::time::{timeout, Duration};\n\npub async fn searcher_loop(mut forker: StateForker, graph: ArbitrageGraph) {\n    let deadline = Duration::from_millis(800);\n    \n    loop {\n        let new_block = wait_for_new_block().await;\n        \n        let result = timeout(deadline, async {\n            \u002F\u002F 1. Обновляем резервы\n            update_reserves(&mut forker, &new_block).await;\n            \n            \u002F\u002F 2. Ищем циклы\n            let routes = graph.find_cycles(WETH, 3);\n            \n            \u002F\u002F 3. Сортируем по потенциальной прибыли\n            let mut sorted = routes;\n            sorted.sort_by(|a, b| b.profit_ratio.partial_cmp(&a.profit_ratio).unwrap());\n            \n            \u002F\u002F 4. Симулируем топ-10\n            let mut best = None;\n            for route in sorted.iter().take(10) {\n                let snapshot = forker.snapshot();\n                let (input, profit) = find_optimal_input(\n                    &mut forker, route,\n                    U256::from(1_000_000_000_000_000_u64),  \u002F\u002F 0.001 ETH\n                    U256::from(100_000_000_000_000_000_000_u128),  \u002F\u002F 100 ETH\n                    30,\n                );\n                forker.rollback(snapshot);\n                \n                if profit > U256::ZERO {\n                    let net = calculate_net_profit(\n                        profit, 300_000, new_block.base_fee, priority_fee,\n                    );\n                    if net > 0 {\n                        best = Some((route.clone(), input, profit));\n                        break;\n                    }\n                }\n            }\n            \n            best\n        }).await;\n        \n        match result {\n            Ok(Some((route, input, profit))) => {\n                submit_bundle(&route, input).await;\n            }\n            Ok(None) => { \u002F* нет прибыльных маршрутов *\u002F }\n            Err(_) => { \u002F* таймаут — пропускаем блок *\u002F }\n        }\n    }\n}\n```\n\n## Оптимизация производительности симуляции\n\n### Предзагрузка стейта\n\nrevm с EthersDB загружает стейт лениво — при первом обращении к слоту хранилища делает RPC-запрос. Это медленно. Решение — предзагрузить стейт всех пулов при старте:\n\n```rust\npub async fn preload_pool_state(\n    forker: &mut StateForker,\n    pools: &[Address],\n) {\n    for pool in pools {\n        \u002F\u002F Предзагружаем storage слоты reserve0, reserve1\n        forker.db.storage(*pool, U256::from(8));  \u002F\u002F slot 8 = reserves\n    }\n}\n```\n\n### Параллельная симуляция\n\nКаждый маршрут можно симулировать в отдельном потоке с собственным клоном стейта:\n\n```rust\nuse rayon::prelude::*;\n\npub fn simulate_routes_parallel(\n    base_state: &CacheDB\u003CEthersDB\u003CProvider\u003CHttp>>>,\n    routes: &[ArbitrageRoute],\n) -> Vec\u003C(usize, U256, U256)> {\n    routes\n        .par_iter()\n        .enumerate()\n        .filter_map(|(i, route)| {\n            let mut local_db = base_state.clone();\n            let mut local_forker = StateForker { db: local_db };\n            let (input, profit) = find_optimal_input(\n                &mut local_forker, route,\n                MIN_INPUT, MAX_INPUT, 30,\n            );\n            if profit > U256::ZERO {\n                Some((i, input, profit))\n            } else {\n                None\n            }\n        })\n        .collect()\n}\n```\n\n## Итоги\n\nСимуляция — мост между обнаружением возможности и её реализацией. revm позволяет форкать стейт и исполнять транзакции локально за микросекунды. Тернарный поиск находит оптимальный размер входа за 30 итераций. Всё должно уложиться в 12-секундный дедлайн слота. В следующей статье — бандлинг: как упаковать множество прибыльных транзакций в один бандл и разрешить конфликты между ними.","\u003Ch2 id=\"\">Зачем нужна симуляция\u003C\u002Fh2>\n\u003Cp>Найти арбитражный цикл — полдела. Нужно определить оптимальный размер входа (сколько WETH вложить), подтвердить прибыль на актуальном стейте и сделать это за миллисекунды, пока возможность не устарела.\u003C\u002Fp>\n\u003Cp>Симуляция отвечает на три вопроса:\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>Прибылен ли маршрут?\u003C\u002Fstrong> — с учётом реальных резервов, комиссий, слиппеджа\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Какой оптимальный размер входа?\u003C\u002Fstrong> — слишком мало = мало прибыли, слишком много = price impact съедает профит\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Сколько газа потратится?\u003C\u002Fstrong> — для расчёта чистой прибыли\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"revm\">Форк стейта с revm\u003C\u002Fh2>\n\u003Cp>revm — библиотека на Rust, реализующая полный EVM. Она позволяет исполнять транзакции на локальной копии стейта без взаимодействия с реальной сетью:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">use revm::{\n    db::{CacheDB, EmptyDB, EthersDB},\n    primitives::{AccountInfo, Address, Bytecode, U256, TransactTo},\n    EVM,\n};\nuse ethers::providers::{Provider, Http};\n\npub struct StateForker {\n    db: CacheDB&lt;EthersDB&lt;Provider&lt;Http&gt;&gt;&gt;,\n}\n\nimpl StateForker {\n    pub async fn new(rpc_url: &amp;str) -&gt; Self {\n        let provider = Provider::&lt;Http&gt;::try_from(rpc_url).unwrap();\n        let ethers_db = EthersDB::new(provider, None).unwrap();\n        let cache_db = CacheDB::new(ethers_db);\n        Self { db: cache_db }\n    }\n    \n    pub fn simulate_call(\n        &amp;mut self,\n        from: Address,\n        to: Address,\n        calldata: Vec&lt;u8&gt;,\n        value: U256,\n    ) -&gt; SimulationResult {\n        let mut evm = EVM::new();\n        evm.database(&amp;mut self.db);\n        \n        evm.env.tx.caller = from;\n        evm.env.tx.transact_to = TransactTo::Call(to);\n        evm.env.tx.data = calldata.into();\n        evm.env.tx.value = value;\n        evm.env.tx.gas_limit = 500_000;\n        \n        match evm.transact_commit() {\n            Ok(result) =&gt; SimulationResult {\n                success: result.is_success(),\n                gas_used: result.gas_used(),\n                output: result.output().unwrap_or_default().to_vec(),\n                logs: result.logs().to_vec(),\n            },\n            Err(e) =&gt; SimulationResult {\n                success: false,\n                gas_used: 0,\n                output: vec![],\n                logs: vec![],\n            },\n        }\n    }\n}\n\npub struct SimulationResult {\n    pub success: bool,\n    pub gas_used: u64,\n    pub output: Vec&lt;u8&gt;,\n    pub logs: Vec&lt;revm::primitives::Log&gt;,\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Snapshot и Rollback\u003C\u002Fh3>\n\u003Cp>Ключевая возможность — snapshot\u002Frollback. Вы сохраняете состояние, исполняете транзакцию, проверяете результат и откатываете если нужно:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">impl StateForker {\n    pub fn snapshot(&amp;self) -&gt; CacheDB&lt;EthersDB&lt;Provider&lt;Http&gt;&gt;&gt; {\n        self.db.clone()\n    }\n    \n    pub fn rollback(&amp;mut self, snapshot: CacheDB&lt;EthersDB&lt;Provider&lt;Http&gt;&gt;&gt;) {\n        self.db = snapshot;\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Это позволяет тестировать множество вариантов без повторной загрузки стейта.\u003C\u002Fp>\n\u003Ch2 id=\"\">Бинарный поиск оптимального размера\u003C\u002Fh2>\n\u003Cp>Функция прибыли от размера входа имеет один максимум — сначала растёт (больше объём = больше абсолютная прибыль), потом падает (price impact поглощает profit). Это позволяет использовать тернарный или бинарный поиск:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">pub fn find_optimal_input(\n    forker: &amp;mut StateForker,\n    route: &amp;ArbitrageRoute,\n    min_input: U256,\n    max_input: U256,\n    iterations: u32,\n) -&gt; (U256, U256) {\n    let mut lo = min_input;\n    let mut hi = max_input;\n    let mut best_input = min_input;\n    let mut best_profit = U256::ZERO;\n    \n    for _ in 0..iterations {\n        let mid1 = lo + (hi - lo) \u002F 3;\n        let mid2 = hi - (hi - lo) \u002F 3;\n        \n        let snapshot = forker.snapshot();\n        let profit1 = simulate_route(forker, route, mid1);\n        forker.rollback(snapshot.clone());\n        \n        let profit2 = simulate_route(forker, route, mid2);\n        forker.rollback(snapshot);\n        \n        if profit1 &lt; profit2 {\n            lo = mid1;\n            if profit2 &gt; best_profit {\n                best_profit = profit2;\n                best_input = mid2;\n            }\n        } else {\n            hi = mid2;\n            if profit1 &gt; best_profit {\n                best_profit = profit1;\n                best_input = mid1;\n            }\n        }\n    }\n    \n    (best_input, best_profit)\n}\n\nfn simulate_route(\n    forker: &amp;mut StateForker,\n    route: &amp;ArbitrageRoute,\n    input: U256,\n) -&gt; U256 {\n    let calldata = encode_arbitrage_calldata(route, input);\n    let result = forker.simulate_call(\n        SEARCHER_ADDRESS,\n        CONTRACT_ADDRESS,\n        calldata,\n        U256::ZERO,\n    );\n    \n    if !result.success {\n        return U256::ZERO;\n    }\n    \n    \u002F\u002F Декодируем прибыль из output\n    decode_profit(&amp;result.output)\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Почему тернарный поиск\u003C\u002Fh3>\n\u003Cp>Функция прибыли — унимодальная (один пик). Тернарный поиск гарантированно находит максимум за O(log n) итераций. На практике 30-40 итераций достаточно для точности до 1 wei при диапазоне 0-1000 ETH.\u003C\u002Fp>\n\u003Ch2 id=\"\">Учёт газа и приоритетной комиссии\u003C\u002Fh2>\n\u003Cp>Чистая прибыль серчера:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">pub fn calculate_net_profit(\n    gross_profit: U256,\n    gas_used: u64,\n    base_fee: U256,\n    priority_fee: U256,\n) -&gt; i128 {\n    let gas_cost = U256::from(gas_used) * (base_fee + priority_fee);\n    let builder_tip = gross_profit * 90 \u002F 100;  \u002F\u002F 90% отдаём билдеру\n    \n    let net = gross_profit.as_u128() as i128\n        - gas_cost.as_u128() as i128\n        - builder_tip.as_u128() as i128;\n    net\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>На практике серчеры отдают 90-99% прибыли билдеру в виде приоритетной комиссии. Это потому что конкурирующие серчеры поднимают ставки.\u003C\u002Fp>\n\u003Ch2 id=\"12\">12-секундный дедлайн\u003C\u002Fh2>\n\u003Cp>Ethereum производит блок каждые 12 секунд. Серчер должен:\u003C\u002Fp>\n\u003Col>\n\u003Cli>Получить новый блок\u002Fpending tx (0 мс)\u003C\u002Fli>\n\u003Cli>Обновить граф резервов (~5 мс)\u003C\u002Fli>\n\u003Cli>Найти арбитражные циклы (~50 мс)\u003C\u002Fli>\n\u003Cli>Симулировать топ-N маршрутов (~100-500 мс)\u003C\u002Fli>\n\u003Cli>Оптимизировать размер входа (~200 мс)\u003C\u002Fli>\n\u003Cli>Подписать и отправить бандл (~10 мс)\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>Итого: ~500-800 мс. Остальное время — запас на сетевую задержку и непредвиденности.\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">use tokio::time::{timeout, Duration};\n\npub async fn searcher_loop(mut forker: StateForker, graph: ArbitrageGraph) {\n    let deadline = Duration::from_millis(800);\n    \n    loop {\n        let new_block = wait_for_new_block().await;\n        \n        let result = timeout(deadline, async {\n            \u002F\u002F 1. Обновляем резервы\n            update_reserves(&amp;mut forker, &amp;new_block).await;\n            \n            \u002F\u002F 2. Ищем циклы\n            let routes = graph.find_cycles(WETH, 3);\n            \n            \u002F\u002F 3. Сортируем по потенциальной прибыли\n            let mut sorted = routes;\n            sorted.sort_by(|a, b| b.profit_ratio.partial_cmp(&amp;a.profit_ratio).unwrap());\n            \n            \u002F\u002F 4. Симулируем топ-10\n            let mut best = None;\n            for route in sorted.iter().take(10) {\n                let snapshot = forker.snapshot();\n                let (input, profit) = find_optimal_input(\n                    &amp;mut forker, route,\n                    U256::from(1_000_000_000_000_000_u64),  \u002F\u002F 0.001 ETH\n                    U256::from(100_000_000_000_000_000_000_u128),  \u002F\u002F 100 ETH\n                    30,\n                );\n                forker.rollback(snapshot);\n                \n                if profit &gt; U256::ZERO {\n                    let net = calculate_net_profit(\n                        profit, 300_000, new_block.base_fee, priority_fee,\n                    );\n                    if net &gt; 0 {\n                        best = Some((route.clone(), input, profit));\n                        break;\n                    }\n                }\n            }\n            \n            best\n        }).await;\n        \n        match result {\n            Ok(Some((route, input, profit))) =&gt; {\n                submit_bundle(&amp;route, input).await;\n            }\n            Ok(None) =&gt; { \u002F* нет прибыльных маршрутов *\u002F }\n            Err(_) =&gt; { \u002F* таймаут — пропускаем блок *\u002F }\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"\">Оптимизация производительности симуляции\u003C\u002Fh2>\n\u003Ch3>Предзагрузка стейта\u003C\u002Fh3>\n\u003Cp>revm с EthersDB загружает стейт лениво — при первом обращении к слоту хранилища делает RPC-запрос. Это медленно. Решение — предзагрузить стейт всех пулов при старте:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">pub async fn preload_pool_state(\n    forker: &amp;mut StateForker,\n    pools: &amp;[Address],\n) {\n    for pool in pools {\n        \u002F\u002F Предзагружаем storage слоты reserve0, reserve1\n        forker.db.storage(*pool, U256::from(8));  \u002F\u002F slot 8 = reserves\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Параллельная симуляция\u003C\u002Fh3>\n\u003Cp>Каждый маршрут можно симулировать в отдельном потоке с собственным клоном стейта:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">use rayon::prelude::*;\n\npub fn simulate_routes_parallel(\n    base_state: &amp;CacheDB&lt;EthersDB&lt;Provider&lt;Http&gt;&gt;&gt;,\n    routes: &amp;[ArbitrageRoute],\n) -&gt; Vec&lt;(usize, U256, U256)&gt; {\n    routes\n        .par_iter()\n        .enumerate()\n        .filter_map(|(i, route)| {\n            let mut local_db = base_state.clone();\n            let mut local_forker = StateForker { db: local_db };\n            let (input, profit) = find_optimal_input(\n                &amp;mut local_forker, route,\n                MIN_INPUT, MAX_INPUT, 30,\n            );\n            if profit &gt; U256::ZERO {\n                Some((i, input, profit))\n            } else {\n                None\n            }\n        })\n        .collect()\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"\">Итоги\u003C\u002Fh2>\n\u003Cp>Симуляция — мост между обнаружением возможности и её реализацией. revm позволяет форкать стейт и исполнять транзакции локально за микросекунды. Тернарный поиск находит оптимальный размер входа за 30 итераций. Всё должно уложиться в 12-секундный дедлайн слота. В следующей статье — бандлинг: как упаковать множество прибыльных транзакций в один бандл и разрешить конфликты между ними.\u003C\u002Fp>\n","ru","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:23.676949Z","Deep EVM #15: Симуляция MEV — бинарный поиск, форки стейта и дедлайн","Симуляция MEV-транзакций: форк стейта через revm, бинарный поиск оптимального размера свапа, расчёт газа и 12-секундный дедлайн.","mev симуляция revm бинарный поиск",null,"index, follow",[22,27,31,35],{"id":23,"name":24,"slug":25,"created_at":26},"c0000000-0000-0000-0000-000000000016","EVM","evm","2026-03-28T10:44:21.513630Z",{"id":28,"name":29,"slug":30,"created_at":26},"c0000000-0000-0000-0000-000000000020","Gas Optimization","gas-optimization",{"id":32,"name":33,"slug":34,"created_at":26},"c0000000-0000-0000-0000-000000000019","MEV","mev",{"id":36,"name":37,"slug":38,"created_at":26},"c0000000-0000-0000-0000-000000000001","Rust","rust","Инженерия",[41,47,53],{"id":42,"title":43,"slug":44,"excerpt":45,"locale":12,"category_name":39,"published_at":46},"d0200000-0000-0000-0000-000000000013","Почему Бали становится хабом импакт-технологий Юго-Восточной Азии в 2026 году","pochemu-bali-stanovitsya-khabom-impakt-tekhnologiy-2026","Бали занимает 16-е место среди стартап-экосистем Юго-Восточной Азии. Растущая концентрация Web3-разработчиков, ИИ-стартапов в области устойчивого развития и компаний в сфере эко-тревел-технологий формирует нишу столицы импакт-технологий региона.","2026-03-28T10:44:37.953039Z",{"id":48,"title":49,"slug":50,"excerpt":51,"locale":12,"category_name":39,"published_at":52},"d0200000-0000-0000-0000-000000000012","Защита данных в ASEAN: чек-лист разработчика для мультистранового комплаенса","zashchita-dannykh-asean-chek-list-razrabotchika-komplaens","Семь стран ASEAN имеют собственные законы о защите данных с разными моделями согласия, требованиями к локализации и штрафами. Практический чек-лист для разработчиков мультистрановых приложений.","2026-03-28T10:44:37.944001Z",{"id":54,"title":55,"slug":56,"excerpt":57,"locale":12,"category_name":39,"published_at":58},"d0200000-0000-0000-0000-000000000011","Цифровая трансформация Индонезии на $29 миллиардов: возможности для софтверных компаний","tsifrovaya-transformatsiya-indonezii-29-milliardov-vozmozhnosti-dlya-kompaniy","Рынок IT-услуг Индонезии вырастет с $24,37 млрд в 2025 году до $29,03 млрд в 2026 году. Облачная инфраструктура, искусственный интеллект, электронная коммерция и дата-центры обеспечивают самый быстрый рост в Юго-Восточной Азии.","2026-03-28T10:44:37.917095Z",{"id":13,"name":60,"slug":61,"bio":62,"photo_url":19,"linkedin":19,"role":63,"created_at":64,"updated_at":64},"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"]