Deep EVM #15: MEV-Simulation — Lokale EVM-Ausfuehrung mit revm fuer Gewinnberechnung
Engineering Team
Warum lokal simulieren?
In der MEV-Welt gilt: Wer zuerst simuliert, gewinnt. Bevor Sie einen Bundle an einen Block Builder senden, muessen Sie sicher sein, dass die Transaktion profitabel ist. Ein fehlgeschlagener Bundle kostet nichts (keine Gaskosten), aber verpasste Gelegenheiten kosten alles.
revm: Rust Ethereum Virtual Machine
revm ist eine in Rust geschriebene EVM-Implementierung, die fuer Simulation optimiert ist. Sie ist schneller als Geth und kann Transaktionen ohne ein vollstaendiges Ethereum-Node ausfuehren.
use revm::{Evm, db::CacheDB, primitives::*};
fn simulate_swap(
db: &mut CacheDB<EmptyDB>,
router: Address,
calldata: Bytes,
value: U256,
) -> Result<ExecutionResult, Box<dyn Error>> {
let mut evm = Evm::builder()
.with_db(db)
.modify_tx_env(|tx| {
tx.caller = MY_BOT_ADDRESS;
tx.transact_to = TransactTo::Call(router);
tx.data = calldata;
tx.value = value;
tx.gas_limit = 500_000;
})
.build();
let result = evm.transact()?;
Ok(result.result)
}
Zustandsverwaltung mit CacheDB
CacheDB haelt den Blockchain-Zustand im Speicher und ermoeglicht schnelle Simulation:
use revm::db::{CacheDB, EthersDB};
// Zustand von einem RPC-Node laden
let client = Provider::<Http>::try_from(rpc_url)?;
let ethers_db = EthersDB::new(client, Some(block_number));
let mut cache_db = CacheDB::new(ethers_db);
// Jetzt koennen Sie Transaktionen simulieren
let result = simulate_swap(&mut cache_db, router, calldata, value)?;
Gewinnberechnung
fn calculate_profit(
db: &mut CacheDB<impl Database>,
cycle: &ArbitrageCycle,
amount_in: U256,
) -> Result<ProfitResult, SimulationError> {
// Snapshot erstellen (fuer Rollback)
let snapshot = db.clone();
let mut current_amount = amount_in;
let mut total_gas = 0u64;
for hop in &cycle.hops {
let calldata = encode_swap(hop, current_amount);
let result = simulate_swap(db, hop.router, calldata, U256::ZERO)?;
match result {
ExecutionResult::Success { output, gas_used, .. } => {
current_amount = decode_amount_out(&output)?;
total_gas += gas_used;
}
_ => {
*db = snapshot; // Rollback
return Err(SimulationError::Revert);
}
}
}
let gas_cost = U256::from(total_gas) * gas_price;
let gross_profit = current_amount.saturating_sub(amount_in);
let net_profit = gross_profit.saturating_sub(gas_cost);
Ok(ProfitResult { net_profit, gas_used: total_gas })
}
Optimale Eingabemenge finden
Die optimale Eingabemenge maximiert den Gewinn. Bei AMMs (Automated Market Makers) gibt es ein analytisches Optimum:
fn find_optimal_amount(
db: &CacheDB<impl Database>,
cycle: &ArbitrageCycle,
) -> U256 {
// Binaere Suche ueber moegliche Eingabemengen
let mut low = U256::from(1_000_000); // 0.001 ETH
let mut high = U256::from(100_000_000_000_000_000u128); // 100 ETH
for _ in 0..64 { // 64 Iterationen fuer ausreichende Praezision
let mid = (low + high) / 2;
let profit_mid = calculate_profit(&mut db.clone(), cycle, mid);
let profit_high = calculate_profit(&mut db.clone(), cycle, mid + 1);
if profit_high > profit_mid {
low = mid;
} else {
high = mid;
}
}
low
}
Fazit
MEV-Simulation mit revm ist der kritische Schritt zwischen dem Finden von Arbitrage-Zyklen und dem Senden profitabler Bundles. Lokale Simulation ermoeglicht exakte Gewinnberechnung, optimale Eingabemengen und Gas-Schaetzung — alles in Mikrosekunden, ohne das Netzwerk zu beruehren.