MEV: Maximal Extractable Value and Protection
Understanding MEV, its types, Flashbots, arbitrage mechanisms, and front-running prevention.
MEV: Maximal Extractable Value and Protection
MEV (Maximal Extractable Value) is profit extracted by reordering, including, or excluding transactions in blocks.
What is MEV?
MEV represents the maximum value that can be extracted from block production by manipulating transaction order.
Sources of MEV
- Arbitrage: Price differences across DEXs
- Liquidation: Liquidating undercollateralized positions
- Front-running: Seeing pending transactions and executing first
- Sandwich Attacks: Placing orders before and after target transaction
- Back-running: Following profitable transactions
Types of MEV
1. Arbitrage
RUSTstruct ArbitrageOpportunity { dex_a: String, dex_b: String, token: String, price_a: u64, price_b: u64, profit: u64, } struct ArbitrageBot { dexes: Vec<String>, min_profit: u64, } impl ArbitrageBot { fn find_opportunity(&self, token: &str) -> Option<ArbitrageOpportunity> { // Compare prices across DEXs // Find profitable arbitrage // Example: Buy on DEX A, sell on DEX B let price_a = self.get_price("dex_a", token); let price_b = self.get_price("dex_b", token); if price_b > price_a { let profit = price_b - price_a - self.calculate_fees(price_a); if profit > self.min_profit { return Some(ArbitrageOpportunity { dex_a: String::from("dex_a"), dex_b: String::from("dex_b"), token: token.to_string(), price_a, price_b, profit, }); } } None } fn execute_arbitrage(&self, opportunity: &ArbitrageOpportunity) -> Result<u64, String> { // Execute buy on DEX A // Execute sell on DEX B // Return profit Ok(opportunity.profit) } }
2. Front-Running
RUST// Front-running: See pending transaction, execute similar one first struct PendingTransaction { hash: String, from: String, to: String, data: Vec<u8>, gas_price: u64, } struct FrontRunner { mempool: Vec<PendingTransaction>, } impl FrontRunner { fn detect_opportunity(&self, tx: &PendingTransaction) -> bool { // Detect if transaction is profitable (e.g., large DEX swap) // If yes, front-run it with higher gas price // Check if it's a DEX swap if self.is_dex_swap(tx) { // Estimate price impact let impact = self.estimate_price_impact(tx); return impact > self.min_profit_threshold; } false } fn front_run(&self, target_tx: &PendingTransaction) -> PendingTransaction { // Create similar transaction with higher gas price PendingTransaction { hash: String::from("front_run_tx"), from: String::from("attacker"), to: target_tx.to.clone(), data: self.modify_data(&target_tx.data), gas_price: target_tx.gas_price + 1, // Higher gas to get included first } } }
3. Sandwich Attack
RUSTstruct SandwichAttack { front_tx: PendingTransaction, target_tx: PendingTransaction, back_tx: PendingTransaction, } impl SandwichAttack { fn create_sandwich(&self, target: &PendingTransaction) -> SandwichAttack { // Front-run: Buy before target let front_tx = self.create_buy_tx(target); // Target transaction (victim's swap) let target_tx = target.clone(); // Back-run: Sell after target (at better price) let back_tx = self.create_sell_tx(target); SandwichAttack { front_tx, target_tx, back_tx, } } fn execute(&self) -> Result<u64, String> { // Execute in order: front -> target -> back // Profit from price manipulation Ok(0) } }
MEV Protection
1. Private Mempools (Flashbots)
Flashbots provides a private mempool to prevent front-running:
RUSTstruct FlashbotsBundle { transactions: Vec<PendingTransaction>, block_number: u64, min_timestamp: Option<u64>, max_timestamp: Option<u64>, } struct FlashbotsProtection { bundles: Vec<FlashbotsBundle>, } impl FlashbotsProtection { fn create_bundle(&mut self, txs: Vec<PendingTransaction>) -> FlashbotsBundle { FlashbotsBundle { transactions: txs, block_number: 0, min_timestamp: None, max_timestamp: None, } } fn submit_bundle(&self, bundle: &FlashbotsBundle) -> Result<(), String> { // Submit to Flashbots relay // Transactions are private until included in block // Prevents front-running Ok(()) } }
2. Commit-Reveal Scheme
RUSTstruct CommitReveal { commitment: Vec<u8>, // hash(secret + transaction_data) reveal_block: u64, } impl CommitReveal { fn commit(&self, secret: &[u8], tx_data: &[u8]) -> Vec<u8> { // Hash secret + transaction data // Submit commitment first // Reveal later vec![] } fn reveal(&self, secret: &[u8], tx_data: &[u8], commitment: &[u8]) -> bool { // Verify commitment matches // Execute transaction if valid true } }
3. Time-Delayed Execution
RUSTstruct TimeDelayedTx { transaction: PendingTransaction, execute_after: u64, // Block number } impl TimeDelayedTx { fn create_delayed_tx(tx: PendingTransaction, delay: u64) -> Self { TimeDelayedTx { transaction: tx, execute_after: current_block() + delay, } } fn can_execute(&self, current_block: u64) -> bool { current_block >= self.execute_after } }
4. Slippage Protection
RUSTstruct SwapWithSlippage { amount_in: u64, min_amount_out: u64, // Minimum acceptable output max_slippage: u8, // Percentage (e.g., 1 = 1%) } impl SwapWithSlippage { fn execute_swap(&self, pool: &AMMPool) -> Result<u64, String> { let amount_out = pool.calculate_output(self.amount_in); // Check slippage let expected_out = self.amount_in; // Simplified let slippage = ((expected_out - amount_out) * 100) / expected_out; if slippage > self.max_slippage as u64 { return Err(String::from("Slippage too high")); } if amount_out < self.min_amount_out { return Err(String::from("Output below minimum")); } Ok(amount_out) } }
Real-World Examples
- Flashbots: Private mempool, MEV protection
- CowSwap: Batch auctions, MEV protection
- 1inch: DEX aggregator with MEV protection
- Uniswap V3: Concentrated liquidity, reduced MEV
Best Practices
- Use Private Mempools: Flashbots for sensitive transactions
- Set Slippage Limits: Protect against sandwich attacks
- Batch Transactions: Reduce MEV opportunities
- Time Delays: Delay execution to prevent front-running
- Commit-Reveal: Hide transaction details until execution
Code Examples
Arbitrage Detection
Simple arbitrage opportunity detection
struct DEXPrice {
dex: String,
price: u64,
}
struct ArbitrageBot {
min_profit: u64,
fee_percentage: u8,
}
impl ArbitrageBot {
fn new(min_profit: u64) -> Self {
ArbitrageBot {
min_profit,
fee_percentage: 3, // 0.3%
}
}
fn find_arbitrage(&self, prices: &[DEXPrice]) -> Option<(String, String, u64)> {
if prices.len() < 2 {
return None;
}
// Find best buy and sell prices
let mut best_buy: Option<&DEXPrice> = None;
let mut best_sell: Option<&DEXPrice> = None;
for price in prices {
if best_buy.is_none() || price.price < best_buy.unwrap().price {
best_buy = Some(price);
}
if best_sell.is_none() || price.price > best_sell.unwrap().price {
best_sell = Some(price);
}
}
if let (Some(buy), Some(sell)) = (best_buy, best_sell) {
if buy.dex == sell.dex {
return None; // Same DEX, no arbitrage
}
// Calculate profit after fees
let price_diff = sell.price - buy.price;
let fees = (buy.price * self.fee_percentage as u64) / 1000 +
(sell.price * self.fee_percentage as u64) / 1000;
let profit = price_diff.saturating_sub(fees);
if profit > self.min_profit {
return Some((buy.dex.clone(), sell.dex.clone(), profit));
}
}
None
}
}
fn main() {
let bot = ArbitrageBot::new(100); // Min profit: 100
let prices = vec![
DEXPrice { dex: String::from("Uniswap"), price: 2000 },
DEXPrice { dex: String::from("SushiSwap"), price: 2010 },
DEXPrice { dex: String::from("Curve"), price: 1995 },
];
if let Some((buy_dex, sell_dex, profit)) = bot.find_arbitrage(&prices) {
println!("Arbitrage opportunity found!");
println!("Buy on: {}, Sell on: {}", buy_dex, sell_dex);
println!("Expected profit: {}", profit);
} else {
println!("No arbitrage opportunity");
}
}Explanation:
Arbitrage bots find price differences across DEXs. They buy on the cheaper DEX and sell on the more expensive one, profiting from the difference. This is a legitimate form of MEV that helps keep prices consistent.
Slippage Protection
Protect against MEV with slippage limits
struct SwapRequest {
amount_in: u64,
min_amount_out: u64,
max_slippage_percent: u8,
}
struct AMMPool {
reserve_in: u64,
reserve_out: u64,
}
impl AMMPool {
fn calculate_output(&self, amount_in: u64) -> u64 {
// Constant product formula: x * y = k
let k = self.reserve_in * self.reserve_out;
let new_reserve_in = self.reserve_in + amount_in;
let new_reserve_out = k / new_reserve_in;
self.reserve_out - new_reserve_out
}
}
impl SwapRequest {
fn execute_swap(&self, pool: &AMMPool) -> Result<u64, String> {
let amount_out = pool.calculate_output(self.amount_in);
// Calculate expected output (simplified: assume 1:1 ratio)
let expected_out = self.amount_in;
// Calculate slippage percentage
if expected_out == 0 {
return Err(String::from("Invalid amount"));
}
let slippage = if amount_out < expected_out {
((expected_out - amount_out) * 100) / expected_out
} else {
0
};
// Check slippage limit
if slippage > self.max_slippage_percent as u64 {
return Err(format!("Slippage too high: {}% (max: {}%)",
slippage, self.max_slippage_percent));
}
// Check minimum output
if amount_out < self.min_amount_out {
return Err(format!("Output {} below minimum {}",
amount_out, self.min_amount_out));
}
Ok(amount_out)
}
}
fn main() {
let pool = AMMPool {
reserve_in: 1000000,
reserve_out: 2000000,
};
let swap = SwapRequest {
amount_in: 10000,
min_amount_out: 19000, // Minimum acceptable
max_slippage_percent: 5, // Max 5% slippage
};
match swap.execute_swap(&pool) {
Ok(amount_out) => {
println!("Swap successful! Output: {}", amount_out);
}
Err(e) => {
println!("Swap failed: {}", e);
println!("This protects against sandwich attacks!");
}
}
}Explanation:
Slippage protection prevents MEV attacks by rejecting transactions if the price moves too much. Users set a maximum acceptable slippage percentage and minimum output amount. This protects against sandwich attacks.
Exercises
Arbitrage Detection
Create a function that detects arbitrage opportunities!
Starter Code:
struct Price {
dex: String,
price: u64,
}
fn find_arbitrage(prices: &[Price]) -> Option<(String, String)> {
// Find best buy and sell prices
// Return (buy_dex, sell_dex) if profitable
}