[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-deep-evm-15-mev-simyuleisyeon-ijin-geomsaeg-sangtae-pokeu":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},"d5000000-0000-0000-0000-000000000115","a0000000-0000-0000-0000-000000000056","Deep EVM #15: MEV 시뮬레이션 — 이진 검색, 상태 포크, 12초 마감 시간","deep-evm-15-mev-simyuleisyeon-ijin-geomsaeg-sangtae-pokeu","MEV 시뮬레이션 파이프라인 구축: EVM 상태를 포크하고, 차익거래 실행을 시뮬레이션하고, 최적의 차입 금액을 이진 검색하고, 12초 블록 마감 시간을 관리합니다.","## 시뮬레이션 파이프라인\n\n사이클 찾기는 쉬운 부분입니다. 어려운 부분은 어떤 사이클이 실제로 수익성이 있는지, 얼마의 자본으로 수익이 나는지를 결정하는 것입니다. 시뮬레이션 파이프라인은 세 가지 질문에 답합니다:\n\n1. **이 사이클이 이익을 생산하는가?** (정적 수학으로는 수익성이 있어 보이지만 실제 컨트랙트 코드에 대해 시뮬레이션하면 실패하는 사이클도 있습니다.)\n2. **최적의 입력 금액은 얼마인가?** (자본이 너무 적으면 = 작은 이익. 너무 많으면 = 과도한 가격 영향이 이익을 잠식.)\n3. **블록 마감 시간 전에 실행할 수 있는가?** (슬롯의 11초에 발견된 수익성 있는 사이클은 무가치합니다.)\n\n```\n사이클 → 상태 포크 → 시뮬레이션 → 이진 검색 → 이익\u002F손실 → 번들 또는 폐기\n```\n\n## 상태 포크\n\n스왑을 시뮬레이션하려면 전체 EVM 상태가 필요합니다: 컨트랙트 바이트코드, 스토리지 슬롯, 잔액. 모든 시뮬레이션에 실제 노드를 호출할 수 없습니다 — 지연 시간이 치명적입니다. 대신 상태를 로컬로 포크합니다.\n\n### 접근법 1: Revm (Rust EVM)\n\nRevm은 포크된 상태 데이터베이스에 대해 실행할 수 있는 Rust의 EVM 구현입니다:\n\n```rust\nuse revm::{\n    db::{CacheDB, EthersDB},\n    primitives::{Address, ExecutionResult, Output, TransactTo, U256},\n    Evm,\n};\n\npub struct Simulator {\n    db: CacheDB\u003CEthersDB\u003CProvider\u003CHttp>>>,\n}\n\nimpl Simulator {\n    pub fn new(provider: Provider\u003CHttp>, block_number: u64) -> Self {\n        let ethers_db = EthersDB::new(provider, Some(block_number.into()));\n        let cache_db = CacheDB::new(ethers_db);\n        Self { db: cache_db }\n    }\n\n    pub fn simulate_swap(\n        &mut self,\n        from: Address,\n        to: Address,     \u002F\u002F 풀 또는 라우터\n        calldata: Vec\u003Cu8>,\n        value: U256,\n    ) -> Result\u003CSimResult, SimError> {\n        let mut evm = Evm::builder()\n            .with_db(&mut self.db)\n            .modify_tx_env(|tx| {\n                tx.caller = from;\n                tx.transact_to = TransactTo::Call(to);\n                tx.data = calldata.into();\n                tx.value = value;\n                tx.gas_limit = 500_000;\n            })\n            .build();\n\n        let result = evm.transact()?;\n\n        match result.result {\n            ExecutionResult::Success { output, gas_used, .. } => {\n                let return_data = match output {\n                    Output::Call(data) => data,\n                    Output::Create(data, _) => data,\n                };\n                Ok(SimResult {\n                    success: true,\n                    gas_used,\n                    return_data: return_data.to_vec(),\n                })\n            }\n            ExecutionResult::Revert { output, gas_used, .. } => {\n                Ok(SimResult {\n                    success: false,\n                    gas_used,\n                    return_data: output.to_vec(),\n                })\n            }\n            ExecutionResult::Halt { reason, gas_used, .. } => {\n                Err(SimError::Halt(reason, gas_used))\n            }\n        }\n    }\n}\n```\n\n`CacheDB` 레이어는 스토리지 읽기를 가로챕니다: 슬롯의 첫 읽기 시 원격 프로바이더에서 가져와 로컬에 캐시합니다. 이후 읽기는 즉시입니다. 사이클의 첫 시뮬레이션은 느리지만(cold 슬롯에 약 50-100ms) 다른 금액으로의 반복 시뮬레이션은 캐시를 재사용합니다(약 0.5-1ms).\n\n### 접근법 2: 사전 캐시 상태\n\n최대 속도를 위해, 각 블록 시작 시 모든 관련 스토리지 슬롯을 사전 로드합니다:\n\n```rust\npub async fn preload_pool_state(\n    provider: &Provider\u003CHttp>,\n    pools: &[Pool],\n    db: &mut CacheDB\u003CEthersDB\u003CProvider\u003CHttp>>>,\n) {\n    \u002F\u002F 필요한 모든 풀 슬롯에 대해 eth_getStorageAt 호출을 일괄 처리\n    let mut futures = Vec::new();\n\n    for pool in pools {\n        \u002F\u002F Uniswap V2: 슬롯 8 = reserve0+reserve1 (패킹)\n        \u002F\u002F Uniswap V3: 슬롯 0 = sqrtPriceX96+tick (패킹)\n        futures.push(provider.get_storage_at(\n            pool.address,\n            H256::from_low_u64_be(8),\n            None,\n        ));\n    }\n\n    let results = futures::future::join_all(futures).await;\n    \u002F\u002F cache_db에 삽입...\n}\n```\n\n사전 캐시를 사용하면 모든 시뮬레이션이 로컬 메모리에 대해 실행됩니다. 시뮬레이션 시간이 사이클당 0.1-0.5ms로 감소합니다.\n\n## 최적 입력을 위한 이진 검색\n\n차익거래 사이클의 이익 함수는 일반적으로 오목합니다: 입력 금액이 증가하면(더 많은 자본 = 더 많은 이익) 가격 영향이 차익거래 스프레드를 초과할 때까지 상승한 후 하락합니다. 최적 입력은 정점에 있습니다.\n\n```\n이익\n  ^\n  |      ****\n  |    **    **\n  |   *        **\n  |  *           ***\n  | *                ****\n  |*                     *****\n  +----------------------------> 입력 금액\n       ^-- 최적\n```\n\n이진 검색이 이 정점을 효율적으로 찾습니다:\n\n```rust\npub fn find_optimal_input(\n    simulator: &mut Simulator,\n    cycle: &Cycle,\n    min_input: U256,\n    max_input: U256,\n    gas_price: U256,\n) -> Option\u003C(U256, U256)> {  \u002F\u002F (optimal_input, profit)\n    let mut lo = min_input;\n    let mut hi = max_input;\n    let mut best_input = U256::ZERO;\n    let mut best_profit = U256::ZERO;\n\n    \u002F\u002F 이진 검색: 수렴에 약 20회 반복\n    for _ in 0..20 {\n        if hi \u003C= lo + U256::from(1000) {\n            break;\n        }\n\n        let mid = (lo + hi) \u002F 2;\n        let mid_left = (lo + mid) \u002F 2;\n        let mid_right = (mid + hi) \u002F 2;\n\n        let profit_left = simulate_cycle(simulator, cycle, mid_left);\n        let profit_right = simulate_cycle(simulator, cycle, mid_right);\n\n        match (profit_left, profit_right) {\n            (Some(pl), Some(pr)) => {\n                if pl > pr {\n                    hi = mid_right;\n                    if pl > best_profit {\n                        best_profit = pl;\n                        best_input = mid_left;\n                    }\n                } else {\n                    lo = mid_left;\n                    if pr > best_profit {\n                        best_profit = pr;\n                        best_input = mid_right;\n                    }\n                }\n            }\n            (Some(pl), None) => {\n                hi = mid;\n                if pl > best_profit {\n                    best_profit = pl;\n                    best_input = mid_left;\n                }\n            }\n            (None, Some(pr)) => {\n                lo = mid;\n                if pr > best_profit {\n                    best_profit = pr;\n                    best_input = mid_right;\n                }\n            }\n            (None, None) => {\n                lo = mid_left;\n                hi = mid_right;\n            }\n        }\n    }\n\n    \u002F\u002F 가스 비용 차감\n    let gas_cost = estimate_gas_cost(cycle) * gas_price;\n    if best_profit > gas_cost {\n        Some((best_input, best_profit - gas_cost))\n    } else {\n        None\n    }\n}\n```\n\n이 삼진 검색 변형(두 중간점 비교)은 단봉 함수에 대해 더 빠르게 수렴합니다. 20회 반복이면 검색 범위의 1\u002F2^20 이내의 정밀도를 제공합니다 — 충분합니다.\n\n## 12초 마감 시간\n\n이더리움은 12초마다 블록을 생산합니다. 새 블록이 도착하면:\n\n1. **t=0초:** 새 블록 헤더 수신. 상태 업데이트.\n2. **t=0-2초:** 이벤트 처리, 풀 준비금 업데이트, 변경된 사이클 식별.\n3. **t=2-6초:** 수익성 있는 사이클 시뮬레이션, 최적 금액 이진 검색.\n4. **t=6-10초:** 번들 구성, 충돌 해결, 빌더에 제출.\n5. **t=10-12초:** 빌더가 다음 블록 제안에 번들 포함.\n\n시뮬레이션이 8초 걸리면 윈도우를 놓칩니다. 속도가 모든 것입니다.\n\n### 마감 시간 인식 취소\n\n모든 시뮬레이션 루프는 시계를 확인해야 합니다:\n\n```rust\nuse std::time::{Duration, Instant};\n\npub struct DeadlineContext {\n    pub start: Instant,\n    pub deadline: Duration,\n}\n\nimpl DeadlineContext {\n    pub fn new(deadline_ms: u64) -> Self {\n        Self {\n            start: Instant::now(),\n            deadline: Duration::from_millis(deadline_ms),\n        }\n    }\n\n    pub fn remaining(&self) -> Duration {\n        self.deadline.saturating_sub(self.start.elapsed())\n    }\n\n    pub fn is_expired(&self) -> bool {\n        self.start.elapsed() >= self.deadline\n    }\n}\n```\n\n추정 수익성 순으로 사이클을 정렬하면, 마감 시간 컷오프가 가장 유망하지 않은 사이클을 먼저 폐기합니다.\n\n## 계층화 배칭 전략\n\n모든 사이클이 매 블록 시뮬레이션이 필요하지는 않습니다. 계층화 배칭을 사용합니다:\n\n### 계층 1: 이벤트 기반 (즉시)\n\n`Sync` 이벤트가 풀에서 발생하면, 해당 풀을 포함하는 모든 사이클을 즉시 재시뮬레이션합니다. 새로운 거래로 생성된 신선한 차익거래를 포착합니다.\n\n### 계층 2: 고가치 순환\n\n최근 블록에서 수익성이 있었던 사이클은 이벤트가 발생하지 않아도 매 블록 시뮬레이션됩니다. 풀 준비금이 다른 메커니즘(직접 전송, 리베이싱 토큰 등)을 통해 변경되었을 수 있습니다.\n\n### 계층 3: 백그라운드 스캔\n\n나머지 모든 사이클은 여러 블록에 걸쳐 라운드로빈 방식으로 시뮬레이션됩니다. 각 블록에서 다음 1,000개 사이클 배치를 시뮬레이션합니다.\n\n## 캐시 무효화\n\n새 블록이 도착하면 CacheDB를 무효화해야 합니다. 하지만 전체 캐시 삭제는 비용이 많이 듭니다 — 수천 개의 스토리지 슬롯을 다시 가져와야 합니다.\n\n더 스마트한 접근법: 변경된 슬롯만 무효화합니다. `eth_subscribe(\"logs\")`와 `eth_subscribe(\"newHeads\")`를 구독합니다. 풀에서 `Sync` 이벤트를 보면, 해당 풀의 스토리지 슬롯만 무효화합니다:\n\n```rust\npub fn invalidate_pool(\n    cache: &mut CacheDB\u003CEthersDB\u003CProvider\u003CHttp>>>,\n    pool: &Pool,\n) {\n    match pool.protocol {\n        Protocol::UniswapV2 => {\n            \u002F\u002F 슬롯 8에 패킹된 준비금 포함\n            cache.remove_storage(pool.address, U256::from(8));\n        }\n        Protocol::UniswapV3 => {\n            \u002F\u002F 슬롯 0에 sqrtPriceX96와 tick 포함\n            cache.remove_storage(pool.address, U256::ZERO);\n            \u002F\u002F 슬롯 4에 유동성\n            cache.remove_storage(pool.address, U256::from(4));\n        }\n        _ => {\n            \u002F\u002F 보수적: 이 주소의 모든 캐시 슬롯 삭제\n            cache.remove_account(pool.address);\n        }\n    }\n}\n```\n\n## 요약\n\n시뮬레이션은 사이클이 이익이 되는 곳입니다. Revm은 서브밀리초 실행을 위한 로컬 EVM을 제공합니다. 이진 검색은 약 20회 반복으로 최적 입력 금액을 찾습니다. 마감 시간 인식 스케줄링은 실행할 수 없는 사이클에 시간을 낭비하지 않게 합니다. 계층화 배칭은 가장 높은 확률의 기회에 컴퓨팅을 집중합니다. 다음 기사에서는 수익성 있는 사이클을 번들로 패킹합니다 — 충돌 해결, MEV-Share 요구사항 관리, 블록 빌더에 제출.","\u003Ch2 id=\"\">시뮬레이션 파이프라인\u003C\u002Fh2>\n\u003Cp>사이클 찾기는 쉬운 부분입니다. 어려운 부분은 어떤 사이클이 실제로 수익성이 있는지, 얼마의 자본으로 수익이 나는지를 결정하는 것입니다. 시뮬레이션 파이프라인은 세 가지 질문에 답합니다:\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>이 사이클이 이익을 생산하는가?\u003C\u002Fstrong> (정적 수학으로는 수익성이 있어 보이지만 실제 컨트랙트 코드에 대해 시뮬레이션하면 실패하는 사이클도 있습니다.)\u003C\u002Fli>\n\u003Cli>\u003Cstrong>최적의 입력 금액은 얼마인가?\u003C\u002Fstrong> (자본이 너무 적으면 = 작은 이익. 너무 많으면 = 과도한 가격 영향이 이익을 잠식.)\u003C\u002Fli>\n\u003Cli>\u003Cstrong>블록 마감 시간 전에 실행할 수 있는가?\u003C\u002Fstrong> (슬롯의 11초에 발견된 수익성 있는 사이클은 무가치합니다.)\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cpre>\u003Ccode>사이클 → 상태 포크 → 시뮬레이션 → 이진 검색 → 이익\u002F손실 → 번들 또는 폐기\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"\">상태 포크\u003C\u002Fh2>\n\u003Cp>스왑을 시뮬레이션하려면 전체 EVM 상태가 필요합니다: 컨트랙트 바이트코드, 스토리지 슬롯, 잔액. 모든 시뮬레이션에 실제 노드를 호출할 수 없습니다 — 지연 시간이 치명적입니다. 대신 상태를 로컬로 포크합니다.\u003C\u002Fp>\n\u003Ch3>접근법 1: Revm (Rust EVM)\u003C\u002Fh3>\n\u003Cp>Revm은 포크된 상태 데이터베이스에 대해 실행할 수 있는 Rust의 EVM 구현입니다:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">use revm::{\n    db::{CacheDB, EthersDB},\n    primitives::{Address, ExecutionResult, Output, TransactTo, U256},\n    Evm,\n};\n\npub struct Simulator {\n    db: CacheDB&lt;EthersDB&lt;Provider&lt;Http&gt;&gt;&gt;,\n}\n\nimpl Simulator {\n    pub fn new(provider: Provider&lt;Http&gt;, block_number: u64) -&gt; Self {\n        let ethers_db = EthersDB::new(provider, Some(block_number.into()));\n        let cache_db = CacheDB::new(ethers_db);\n        Self { db: cache_db }\n    }\n\n    pub fn simulate_swap(\n        &amp;mut self,\n        from: Address,\n        to: Address,     \u002F\u002F 풀 또는 라우터\n        calldata: Vec&lt;u8&gt;,\n        value: U256,\n    ) -&gt; Result&lt;SimResult, SimError&gt; {\n        let mut evm = Evm::builder()\n            .with_db(&amp;mut self.db)\n            .modify_tx_env(|tx| {\n                tx.caller = from;\n                tx.transact_to = TransactTo::Call(to);\n                tx.data = calldata.into();\n                tx.value = value;\n                tx.gas_limit = 500_000;\n            })\n            .build();\n\n        let result = evm.transact()?;\n\n        match result.result {\n            ExecutionResult::Success { output, gas_used, .. } =&gt; {\n                let return_data = match output {\n                    Output::Call(data) =&gt; data,\n                    Output::Create(data, _) =&gt; data,\n                };\n                Ok(SimResult {\n                    success: true,\n                    gas_used,\n                    return_data: return_data.to_vec(),\n                })\n            }\n            ExecutionResult::Revert { output, gas_used, .. } =&gt; {\n                Ok(SimResult {\n                    success: false,\n                    gas_used,\n                    return_data: output.to_vec(),\n                })\n            }\n            ExecutionResult::Halt { reason, gas_used, .. } =&gt; {\n                Err(SimError::Halt(reason, gas_used))\n            }\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>CacheDB\u003C\u002Fcode> 레이어는 스토리지 읽기를 가로챕니다: 슬롯의 첫 읽기 시 원격 프로바이더에서 가져와 로컬에 캐시합니다. 이후 읽기는 즉시입니다. 사이클의 첫 시뮬레이션은 느리지만(cold 슬롯에 약 50-100ms) 다른 금액으로의 반복 시뮬레이션은 캐시를 재사용합니다(약 0.5-1ms).\u003C\u002Fp>\n\u003Ch3>접근법 2: 사전 캐시 상태\u003C\u002Fh3>\n\u003Cp>최대 속도를 위해, 각 블록 시작 시 모든 관련 스토리지 슬롯을 사전 로드합니다:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">pub async fn preload_pool_state(\n    provider: &amp;Provider&lt;Http&gt;,\n    pools: &amp;[Pool],\n    db: &amp;mut CacheDB&lt;EthersDB&lt;Provider&lt;Http&gt;&gt;&gt;,\n) {\n    \u002F\u002F 필요한 모든 풀 슬롯에 대해 eth_getStorageAt 호출을 일괄 처리\n    let mut futures = Vec::new();\n\n    for pool in pools {\n        \u002F\u002F Uniswap V2: 슬롯 8 = reserve0+reserve1 (패킹)\n        \u002F\u002F Uniswap V3: 슬롯 0 = sqrtPriceX96+tick (패킹)\n        futures.push(provider.get_storage_at(\n            pool.address,\n            H256::from_low_u64_be(8),\n            None,\n        ));\n    }\n\n    let results = futures::future::join_all(futures).await;\n    \u002F\u002F cache_db에 삽입...\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>사전 캐시를 사용하면 모든 시뮬레이션이 로컬 메모리에 대해 실행됩니다. 시뮬레이션 시간이 사이클당 0.1-0.5ms로 감소합니다.\u003C\u002Fp>\n\u003Ch2 id=\"\">최적 입력을 위한 이진 검색\u003C\u002Fh2>\n\u003Cp>차익거래 사이클의 이익 함수는 일반적으로 오목합니다: 입력 금액이 증가하면(더 많은 자본 = 더 많은 이익) 가격 영향이 차익거래 스프레드를 초과할 때까지 상승한 후 하락합니다. 최적 입력은 정점에 있습니다.\u003C\u002Fp>\n\u003Cpre>\u003Ccode>이익\n  ^\n  |      ****\n  |    **    **\n  |   *        **\n  |  *           ***\n  | *                ****\n  |*                     *****\n  +----------------------------&gt; 입력 금액\n       ^-- 최적\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>이진 검색이 이 정점을 효율적으로 찾습니다:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">pub fn find_optimal_input(\n    simulator: &amp;mut Simulator,\n    cycle: &amp;Cycle,\n    min_input: U256,\n    max_input: U256,\n    gas_price: U256,\n) -&gt; Option&lt;(U256, U256)&gt; {  \u002F\u002F (optimal_input, profit)\n    let mut lo = min_input;\n    let mut hi = max_input;\n    let mut best_input = U256::ZERO;\n    let mut best_profit = U256::ZERO;\n\n    \u002F\u002F 이진 검색: 수렴에 약 20회 반복\n    for _ in 0..20 {\n        if hi &lt;= lo + U256::from(1000) {\n            break;\n        }\n\n        let mid = (lo + hi) \u002F 2;\n        let mid_left = (lo + mid) \u002F 2;\n        let mid_right = (mid + hi) \u002F 2;\n\n        let profit_left = simulate_cycle(simulator, cycle, mid_left);\n        let profit_right = simulate_cycle(simulator, cycle, mid_right);\n\n        match (profit_left, profit_right) {\n            (Some(pl), Some(pr)) =&gt; {\n                if pl &gt; pr {\n                    hi = mid_right;\n                    if pl &gt; best_profit {\n                        best_profit = pl;\n                        best_input = mid_left;\n                    }\n                } else {\n                    lo = mid_left;\n                    if pr &gt; best_profit {\n                        best_profit = pr;\n                        best_input = mid_right;\n                    }\n                }\n            }\n            (Some(pl), None) =&gt; {\n                hi = mid;\n                if pl &gt; best_profit {\n                    best_profit = pl;\n                    best_input = mid_left;\n                }\n            }\n            (None, Some(pr)) =&gt; {\n                lo = mid;\n                if pr &gt; best_profit {\n                    best_profit = pr;\n                    best_input = mid_right;\n                }\n            }\n            (None, None) =&gt; {\n                lo = mid_left;\n                hi = mid_right;\n            }\n        }\n    }\n\n    \u002F\u002F 가스 비용 차감\n    let gas_cost = estimate_gas_cost(cycle) * gas_price;\n    if best_profit &gt; gas_cost {\n        Some((best_input, best_profit - gas_cost))\n    } else {\n        None\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>이 삼진 검색 변형(두 중간점 비교)은 단봉 함수에 대해 더 빠르게 수렴합니다. 20회 반복이면 검색 범위의 1\u002F2^20 이내의 정밀도를 제공합니다 — 충분합니다.\u003C\u002Fp>\n\u003Ch2 id=\"12\">12초 마감 시간\u003C\u002Fh2>\n\u003Cp>이더리움은 12초마다 블록을 생산합니다. 새 블록이 도착하면:\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>t=0초:\u003C\u002Fstrong> 새 블록 헤더 수신. 상태 업데이트.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>t=0-2초:\u003C\u002Fstrong> 이벤트 처리, 풀 준비금 업데이트, 변경된 사이클 식별.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>t=2-6초:\u003C\u002Fstrong> 수익성 있는 사이클 시뮬레이션, 최적 금액 이진 검색.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>t=6-10초:\u003C\u002Fstrong> 번들 구성, 충돌 해결, 빌더에 제출.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>t=10-12초:\u003C\u002Fstrong> 빌더가 다음 블록 제안에 번들 포함.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>시뮬레이션이 8초 걸리면 윈도우를 놓칩니다. 속도가 모든 것입니다.\u003C\u002Fp>\n\u003Ch3>마감 시간 인식 취소\u003C\u002Fh3>\n\u003Cp>모든 시뮬레이션 루프는 시계를 확인해야 합니다:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">use std::time::{Duration, Instant};\n\npub struct DeadlineContext {\n    pub start: Instant,\n    pub deadline: Duration,\n}\n\nimpl DeadlineContext {\n    pub fn new(deadline_ms: u64) -&gt; Self {\n        Self {\n            start: Instant::now(),\n            deadline: Duration::from_millis(deadline_ms),\n        }\n    }\n\n    pub fn remaining(&amp;self) -&gt; Duration {\n        self.deadline.saturating_sub(self.start.elapsed())\n    }\n\n    pub fn is_expired(&amp;self) -&gt; bool {\n        self.start.elapsed() &gt;= self.deadline\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>추정 수익성 순으로 사이클을 정렬하면, 마감 시간 컷오프가 가장 유망하지 않은 사이클을 먼저 폐기합니다.\u003C\u002Fp>\n\u003Ch2 id=\"\">계층화 배칭 전략\u003C\u002Fh2>\n\u003Cp>모든 사이클이 매 블록 시뮬레이션이 필요하지는 않습니다. 계층화 배칭을 사용합니다:\u003C\u002Fp>\n\u003Ch3>계층 1: 이벤트 기반 (즉시)\u003C\u002Fh3>\n\u003Cp>\u003Ccode>Sync\u003C\u002Fcode> 이벤트가 풀에서 발생하면, 해당 풀을 포함하는 모든 사이클을 즉시 재시뮬레이션합니다. 새로운 거래로 생성된 신선한 차익거래를 포착합니다.\u003C\u002Fp>\n\u003Ch3>계층 2: 고가치 순환\u003C\u002Fh3>\n\u003Cp>최근 블록에서 수익성이 있었던 사이클은 이벤트가 발생하지 않아도 매 블록 시뮬레이션됩니다. 풀 준비금이 다른 메커니즘(직접 전송, 리베이싱 토큰 등)을 통해 변경되었을 수 있습니다.\u003C\u002Fp>\n\u003Ch3>계층 3: 백그라운드 스캔\u003C\u002Fh3>\n\u003Cp>나머지 모든 사이클은 여러 블록에 걸쳐 라운드로빈 방식으로 시뮬레이션됩니다. 각 블록에서 다음 1,000개 사이클 배치를 시뮬레이션합니다.\u003C\u002Fp>\n\u003Ch2 id=\"\">캐시 무효화\u003C\u002Fh2>\n\u003Cp>새 블록이 도착하면 CacheDB를 무효화해야 합니다. 하지만 전체 캐시 삭제는 비용이 많이 듭니다 — 수천 개의 스토리지 슬롯을 다시 가져와야 합니다.\u003C\u002Fp>\n\u003Cp>더 스마트한 접근법: 변경된 슬롯만 무효화합니다. \u003Ccode>eth_subscribe(\"logs\")\u003C\u002Fcode>와 \u003Ccode>eth_subscribe(\"newHeads\")\u003C\u002Fcode>를 구독합니다. 풀에서 \u003Ccode>Sync\u003C\u002Fcode> 이벤트를 보면, 해당 풀의 스토리지 슬롯만 무효화합니다:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">pub fn invalidate_pool(\n    cache: &amp;mut CacheDB&lt;EthersDB&lt;Provider&lt;Http&gt;&gt;&gt;,\n    pool: &amp;Pool,\n) {\n    match pool.protocol {\n        Protocol::UniswapV2 =&gt; {\n            \u002F\u002F 슬롯 8에 패킹된 준비금 포함\n            cache.remove_storage(pool.address, U256::from(8));\n        }\n        Protocol::UniswapV3 =&gt; {\n            \u002F\u002F 슬롯 0에 sqrtPriceX96와 tick 포함\n            cache.remove_storage(pool.address, U256::ZERO);\n            \u002F\u002F 슬롯 4에 유동성\n            cache.remove_storage(pool.address, U256::from(4));\n        }\n        _ =&gt; {\n            \u002F\u002F 보수적: 이 주소의 모든 캐시 슬롯 삭제\n            cache.remove_account(pool.address);\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"\">요약\u003C\u002Fh2>\n\u003Cp>시뮬레이션은 사이클이 이익이 되는 곳입니다. Revm은 서브밀리초 실행을 위한 로컬 EVM을 제공합니다. 이진 검색은 약 20회 반복으로 최적 입력 금액을 찾습니다. 마감 시간 인식 스케줄링은 실행할 수 없는 사이클에 시간을 낭비하지 않게 합니다. 계층화 배칭은 가장 높은 확률의 기회에 컴퓨팅을 집중합니다. 다음 기사에서는 수익성 있는 사이클을 번들로 패킹합니다 — 충돌 해결, MEV-Share 요구사항 관리, 블록 빌더에 제출.\u003C\u002Fp>\n","ko","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:28.055361Z","Deep EVM #15: MEV 시뮬레이션 — 이진 검색, 상태 포크, 마감 시간","MEV 시뮬레이션 파이프라인 구축: Revm 상태 포크, 최적 차익거래 입력 이진 검색, 마감 시간 인식 스케줄링, 계층화 배칭.","mev 시뮬레이션 이진 검색",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},"d0000000-0000-0000-0000-000000000674","2026년, Bali가 동남아시아의 임팩트 테크 허브가 되고 있는 이유","bali-2026-dongnamasia-impaekteu-tekeu-heobeu-iyu","Bali는 동남아시아 스타트업 생태계에서 16위를 차지하고 있습니다. Web3 빌더, AI 지속가능성 스타트업, 에코 여행 테크 기업이 집중되면서, 이 섬은 지역 임팩트 테크의 수도로 자리매김하고 있습니다.","2026-03-28T10:44:49.294484Z",{"id":48,"title":49,"slug":50,"excerpt":51,"locale":12,"category_name":39,"published_at":52},"d0000000-0000-0000-0000-000000000673","ASEAN 데이터 보호 패치워크: 개발자를 위한 컴플라이언스 체크리스트","asean-deiteo-boho-paechiwokeu-gaebaljaleul-wihan-keompeullaieonseuchekeuriseuteu","7개 ASEAN 국가가 포괄적인 데이터 보호법을 시행하고 있으며, 각각 다른 동의 모델, 현지화 요건, 벌칙 구조를 가지고 있습니다. 다중 국가 애플리케이션을 구축하는 개발자를 위한 실용적인 컴플라이언스 체크리스트입니다.","2026-03-28T10:44:49.286400Z",{"id":54,"title":55,"slug":56,"excerpt":57,"locale":12,"category_name":39,"published_at":58},"d0000000-0000-0000-0000-000000000672","Indonesia 290억 달러 디지털 전환: 소프트웨어 기업을 위한 기회","indonesia-290eok-dallleo-dijiteol-jeonhwan-sopeuteuweo-gieopui-gihoe","Indonesia IT 서비스 시장은 2026년 290.3억 달러에 달할 것으로 예상되며, 이는 2025년 243.7억 달러에서 증가한 수치입니다. 클라우드 인프라, AI, 전자상거래, 데이터센터가 동남아시아에서 가장 빠른 성장을 주도하고 있습니다.","2026-03-28T10:44:49.265609Z",{"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"]