Deep EVM #15: Simulasi MEV — Binary Search, State Fork, dan Deadline 12 Detik
Engineering Team
Mengapa Simulasi Diperlukan
Menemukan peluang arbitrase di graf pool tidak cukup — Anda harus memverifikasi bahwa transaksi benar-benar akan berhasil dan menguntungkan pada state blockchain saat ini. Simulasi adalah jembatan antara analisis off-chain dan eksekusi on-chain.
Tanpa simulasi, Anda akan:
- Mengirim transaksi yang revert (membuang gas)
- Salah menghitung profitabilitas (reserves sudah berubah)
- Gagal memperhitungkan efek samping (reentrancy guard, pause mechanism)
revm: Simulator EVM di Rust
revm adalah implementasi EVM di Rust yang cepat dan modular. Digunakan oleh Foundry, Reth, dan sebagian besar bot MEV di produksi.
use revm::{
db::CacheDB,
primitives::{ExecutionResult, Output, TransactTo, U256},
Evm,
};
fn simulate_swap(
db: &mut CacheDB<impl DatabaseRef>,
from: Address,
to: Address,
calldata: Bytes,
value: U256,
) -> Result<Bytes, SimError> {
let mut evm = Evm::builder()
.with_db(db)
.modify_tx_env(|tx| {
tx.caller = from;
tx.transact_to = TransactTo::Call(to);
tx.data = calldata;
tx.value = value;
tx.gas_limit = 500_000;
})
.build();
let result = evm.transact_commit()?;
match result {
ExecutionResult::Success { output, gas_used, .. } => {
match output {
Output::Call(data) => Ok(data),
_ => Err(SimError::UnexpectedOutput),
}
}
ExecutionResult::Revert { output, .. } => {
Err(SimError::Revert(output))
}
ExecutionResult::Halt { reason, .. } => {
Err(SimError::Halt(reason))
}
}
}
Fork State dari Node
Untuk simulasi akurat, Anda perlu state blockchain terbaru:
use alloy::providers::Provider;
use revm::db::AlloyDB;
async fn create_fork_db(provider: &impl Provider) -> CacheDB<AlloyDB> {
let block = provider.get_block_number().await.unwrap();
let alloy_db = AlloyDB::new(provider.clone(), block);
CacheDB::new(alloy_db)
}
CacheDB meng-cache hasil query sehingga pembacaan berulang dari slot storage yang sama tidak memerlukan RPC call tambahan.
Binary Search untuk Jumlah Optimal
Alih-alih menghitung secara analitis, gunakan simulasi aktual untuk menemukan jumlah input optimal:
async fn find_optimal_amount(
db: &CacheDB<impl DatabaseRef>,
cycle: &ArbitrageCycle,
) -> (U256, U256) {
let mut lo = U256::from(1_000_000_000_000_000u64); // 0.001 ETH
let mut hi = U256::from(50_000_000_000_000_000_000u128); // 50 ETH
for _ in 0..40 {
let mid = (lo + hi) / 2;
// Clone DB untuk simulasi non-destruktif
let mut sim_db = db.clone();
let profit = simulate_cycle(&mut sim_db, cycle, mid);
let profit_higher = simulate_cycle(&mut db.clone(), cycle, mid + mid / 100);
if profit_higher > profit {
lo = mid;
} else {
hi = mid;
}
}
let optimal = (lo + hi) / 2;
let profit = simulate_cycle(&mut db.clone(), cycle, optimal);
(optimal, profit)
}
Deadline 12 Detik
Ethereum menghasilkan block setiap 12 detik. Pencari MEV harus:
- Deteksi peluang — Memantau mempool dan event (1-2 detik)
- Simulasikan dan optimasi — Jalankan simulasi revm (1-3 detik)
- Bangun bundle — Buat dan tandatangani transaksi (< 1 detik)
- Kirim ke pembangun — Melalui Flashbots atau relay (1-2 detik)
- Margin keamanan — Waktu buffer (2-3 detik)
Total budget: ~12 detik. Jika simulasi memakan waktu terlalu lama, peluang sudah lewat.
Strategi Optimasi Waktu
use tokio::time::{timeout, Duration};
async fn search_with_deadline(
opportunities: Vec<ArbitrageCycle>,
db: &CacheDB<impl DatabaseRef>,
deadline: Duration,
) -> Option<(ArbitrageCycle, U256, U256)> {
let start = std::time::Instant::now();
let mut best: Option<(ArbitrageCycle, U256, U256)> = None;
for opp in opportunities {
// Periksa deadline
if start.elapsed() > deadline {
break;
}
let remaining = deadline - start.elapsed();
// Simulasi dengan timeout
match timeout(remaining / 2, find_optimal_amount(db, &opp)).await {
Ok((amount, profit)) => {
if let Some((_, _, best_profit)) = &best {
if profit > *best_profit {
best = Some((opp, amount, profit));
}
} else {
best = Some((opp, amount, profit));
}
}
Err(_) => break, // Timeout
}
}
best
}
Memperhitungkan Gas
Profitabilitas bersih = output - input - biaya gas:
fn net_profit(
gross_profit: U256,
gas_used: u64,
base_fee: U256,
priority_fee: U256,
) -> I256 {
let gas_cost = U256::from(gas_used) * (base_fee + priority_fee);
I256::from_raw(gross_profit) - I256::from_raw(gas_cost)
}
Bot MEV juga harus menawar sebagian profit ke pembangun block sebagai “tip” agar bundle mereka disertakan.
Simulasi Multi-Transaksi (Bundle)
Bundle MEV sering berisi beberapa transaksi:
fn simulate_bundle(
db: &mut CacheDB<impl DatabaseRef>,
transactions: &[Transaction],
) -> Result<BundleResult, SimError> {
let mut total_gas = 0u64;
let mut results = Vec::new();
for tx in transactions {
let result = simulate_swap(
db,
tx.from,
tx.to,
tx.calldata.clone(),
tx.value,
)?;
total_gas += result.gas_used;
results.push(result);
}
Ok(BundleResult {
total_gas,
results,
})
}
PENTING: Simulasi bundle harus dijalankan secara sekuensial pada DB yang sama karena setiap transaksi mengubah state yang memengaruhi transaksi berikutnya.
Kesimpulan
Simulasi adalah komponen kritis infrastruktur MEV. revm memberikan simulasi cepat di Rust, fork state memastikan akurasi, binary search mengoptimalkan jumlah input, dan manajemen deadline memastikan Anda tidak melewatkan peluang. Kemampuan mensimulasikan dengan cepat dan akurat adalah keunggulan kompetitif utama bagi pencari MEV.