Skip to content

Latest commit

 

History

History
433 lines (320 loc) · 11.5 KB

File metadata and controls

433 lines (320 loc) · 11.5 KB

Simple Arbitrage

A high-performance Rust library for detecting and simulating arbitrage opportunities across Uniswap V2 DEX pairs on Ethereum.

🌟 Overview

This library provides a modular, production-ready framework for:

  • 🔍 Discovering arbitrage opportunities across DEX pairs
  • ⚡ Parallel processing of blockchain blocks using Rayon
  • 🔄 Smart caching with thread-safe operations
  • 🧪 REVM-based execution simulation with accurate gas tracking
  • 📊 Mathematical optimization using ternary search algorithms

Originally a 1,347-line monolithic example, now refactored into a well-organized library with 6 specialized modules and comprehensive documentation.

📊 Architecture

graph TB
    subgraph "Core Library Modules"
        PARALLEL[parallel/<br/>Block Processing]
        CACHE[cache/<br/>Smart Caching]
        SIMULATION[simulation/<br/>REVM Execution]
        STRATEGY[strategy/<br/>Algorithms]
        TYPES[types/<br/>Data Structures]
        UTILS[utils/<br/>Helpers]
    end

    subgraph "External Dependencies"
        RPC[RPC Provider<br/>Ethereum Node]
        REVM_LIB[REVM<br/>EVM Simulator]
    end

    PARALLEL --> CACHE
    PARALLEL --> SIMULATION
    PARALLEL --> STRATEGY
    PARALLEL --> UTILS

    CACHE --> TYPES
    SIMULATION --> TYPES
    STRATEGY --> TYPES
    UTILS --> TYPES

    SIMULATION --> REVM_LIB
    CACHE --> RPC

    style PARALLEL fill:#ffd699,color:#000
    style CACHE fill:#d4a5ff,color:#000
    style SIMULATION fill:#a5ffb8,color:#000
    style STRATEGY fill:#ffb3b3,color:#000
    style TYPES fill:#d4d4d4,color:#000
    style UTILS fill:#fff066,color:#000
Loading

📖 Full Architecture Documentation - Detailed diagrams and design patterns

🚀 Quick Start

Prerequisites

You'll need access to an Ethereum archive node with full historical state. Options include:

  • Local node (Erigon, Reth, Geth archive mode)
  • Remote RPC provider (Alchemy, Infura, QuickNode with archive access)
  • Private network node (e.g., via Tailscale)

⚠️ Important: Update the RPC URL in examples to point to your node. The default URL in examples is a private Tailscale endpoint and won't work for you.

Installation

[dependencies]
simple-arbitrage = { path = "." }
tokio = { version = "1", features = ["full"] }
anyhow = "1.0"

Basic Usage

use simple_arbitrage::parallel::{
    process_block_range, ArbitrageStats, BlockProcessor,
};
use std::sync::Arc;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Setup RPC client
    let client = Arc::new(
        ProviderBuilder::new()
            .connect("http://localhost:8545")
            .await?
            .erased(),
    );

    // Create processor and stats
    let processor = BlockProcessor::new();
    let stats = ArbitrageStats::new();

    // Process blocks in parallel
    let results = process_block_range(
        22_200_000,     // start block
        22_210_000,     // end block
        client,
        processor.get_cache_clone(),
        processor.config.clone(),
        None,           // optional progress callback
        stats.clone(),
    );

    println!("Found {} profitable opportunities", stats.get_total());
    Ok(())
}

See examples/modular_arbitrage.rs for a complete example.

📦 Module Overview

🔄 parallel - Concurrent Block Processing

Orchestrates parallel arbitrage detection across multiple blocks using Rayon.

Key Features:

  • Rayon-based parallel iteration
  • Tokio async/sync bridging
  • Progress tracking with callbacks
  • Thread-safe statistics

Main Types:

  • BlockProcessor - Coordinates block processing
  • ArbitrageStats - Tracks profitable opportunities
  • process_block_range() - Main entry point

💾 cache - Intelligent Pair Caching

Thread-safe caching layer with smart loading strategies.

Key Features:

  • Arc<Mutex> for thread safety
  • Full load vs reserve-only updates
  • Multicall batching (1000 pairs/call)
  • High-level management API

Main Types:

  • PairCache - Thread-safe cache wrapper
  • CacheManager - High-level cache operations
  • classify_pairs() - Smart loading strategy

🧪 simulation - REVM Execution Engine

EVM simulation infrastructure for accurate arbitrage execution testing.

Key Features:

  • REVM-based execution
  • Accurate gas tracking
  • Account and storage setup
  • Balance, transfer, and swap operations

Main Types:

  • simulate_arbitrage_execution() - Full simulation
  • GasMeter - Gas tracking
  • EVM call wrappers (balance_of, swap, transfer)

📐 strategy - Mathematical Algorithms

Core algorithms for arbitrage detection and optimization.

Key Features:

  • Mathematical inequality detection
  • Ternary search optimization (O(log₃ n))
  • U512 overflow prevention
  • Configurable convergence epsilon

Main Types:

  • find_arbitrage() - Detects opportunities
  • find_optimal_amount() - Ternary search
  • compute_profit() - Profit calculation

📋 types - Core Data Structures

Shared type definitions used across modules.

Key Features:

  • PairInfo with reserve updates
  • Solidity contract interfaces
  • Type aliases for complex nested types

Main Types:

  • PairInfo - Liquidity pool information
  • AlloyCacheDB - REVM state type alias
  • Contract interfaces (IUniswapV2Pair)

🛠️ utils - Helper Utilities

Common operations for pair grouping and combinations.

Key Features:

  • WETH pair grouping
  • Combination generation (C(n,2))
  • Comprehensive unit tests

Main Functions:

  • group_pairs_by_token() - Groups WETH pairs
  • get_pair_combinations() - Generates combinations

⚡ Performance Features

  • Parallel Processing: Rayon-based multi-threaded block processing
  • Smart Caching: Full load vs reserve-only update strategies
  • Multicall Batching: 1000 pairs per RPC call
  • Lock-Free Stats: Atomic counters for statistics
  • Overflow Prevention: U512 arithmetic in profit calculations
  • Pre-Allocation: Exact capacity for vectors (C(n,2) combinations)
  • Early Filtering: Minimum reserve checks (1 ETH threshold)

🔐 Thread Safety

The library is designed for safe concurrent execution:

graph LR
    subgraph "Shared State (Thread-Safe)"
        CACHE[Arc&lt;Mutex&lt;HashMap&gt;&gt;<br/>Pair Cache]
        STATS[Arc&lt;AtomicUsize&gt;<br/>Statistics]
        CONFIG[Arc&lt;Config&gt;<br/>Read-Only]
    end

    subgraph "Per-Thread (Isolated)"
        STATE1[REVM State 1]
        STATE2[REVM State 2]
        STATEN[REVM State N]
    end

    T1[Thread 1] --> CACHE
    T2[Thread 2] --> CACHE
    TN[Thread N] --> CACHE

    T1 --> STATS
    T2 --> STATS
    TN --> STATS

    T1 --> STATE1
    T2 --> STATE2
    TN --> STATEN
Loading

🧮 Algorithm Overview

Arbitrage Detection

Uses mathematical inequality to detect opportunities:

997² × reserve0_a × reserve1_b > 1000² × reserve1_a × reserve0_b

Where:

  • 997 = Uniswap fee factor (0.3% fee)
  • 1000 = Base factor
  • Reserves arranged as: reserve1 = WETH, reserve0 = Token

Optimal Amount Finding

Implements ternary search to find the optimal input amount:

  1. Initialize bounds: [1 wei, min(100×reserve, balance)]
  2. Divide range into thirds
  3. Evaluate profit at mid1 and mid2
  4. Discard third with lower profit
  5. Repeat until convergence (ε = 0.001 ETH)
  6. Return amount with maximum profit

Complexity: O(log₃(range)) iterations

🧪 Testing

Run the test suite:

# Run all tests
cargo test

# Run specific module tests
cargo test --lib cache
cargo test --lib utils

# Run with output
cargo test -- --nocapture

📖 Examples

Run the Modular Example

cargo run --example modular_arbitrage

Other Examples

The examples/ directory contains various demonstration scripts:

  • modular_arbitrage.rs - Clean example using refactored library
  • 110725_simulation3.rs - Original monolithic implementation
  • 110725_simulation2.rs - Intermediate version
  • 110725_simulation.rs - Earlier version

🔧 Configuration

Customize behavior via BlockProcessorConfig:

use simple_arbitrage::parallel::{BlockProcessor, BlockProcessorConfig};
use alloy::primitives::{address, U256};

let config = BlockProcessorConfig {
    weth_address: address!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"),
    account_address: address!("18B06aaF27d44B756FCF16Ca20C1f183EB49111f"),
    balance: U256::from(1_000_000_000_000_000_000_000_000u128), // 1M ETH
};

let processor = BlockProcessor::with_config(config);

📊 Statistics Tracking

Monitor arbitrage opportunities across parallel workers:

let stats = ArbitrageStats::new();

// ... process blocks ...

println!("Total profitable opportunities: {}", stats.get_total());

Thread-safe atomic counters ensure accurate statistics across parallel execution.

🐛 Debugging

Enable detailed logging:

// Add to Cargo.toml
tracing-subscriber = "0.3"

// In your code
tracing_subscriber::fmt::init();

Log levels:

  • debug - Skipped pairs, cache hits/misses
  • warn - Non-critical failures
  • error - Critical errors

📝 Documentation

🔍 How It Works

1. Pair Discovery

  • Reads recently active pair addresses from cached data
  • Filters for pairs with swaps in last ~10k blocks (~1 day)

2. Smart Caching

  • First encounter: Full load (addresses, symbols, decimals, reserves)
  • Already cached: Reserve-only update via multicall
  • Thread-safe shared cache across parallel workers

3. Arbitrage Detection

  • Groups pairs by non-WETH token
  • Generates all combinations C(n,2) for each token
  • Tests both directions (A→B and B→A)
  • Filters by minimum reserves (≥1 ETH)
  • Mathematical inequality check for arbitrage

4. Optimization

  • Ternary search finds optimal input amount
  • Evaluates profit function at midpoints
  • Converges to maximum profit amount

5. Simulation

  • REVM executes trades in simulated environment
  • Tracks gas for all operations
  • Calculates net profit after gas costs
  • Returns (profit, gas_used) tuple

6. Statistics

  • Atomic counters track profitable opportunities
  • Progress callbacks for monitoring
  • Per-block timing and results

🚧 Limitations

  • Uniswap V2 Only: Currently supports only Uniswap V2 pairs
  • Two-Pool Arbitrage: Limited to simple two-pool arbitrage loops
  • No Flash Loans: Assumes available balance for trades
  • Historical Data: Requires pre-cached pair addresses
  • Gas Price: Uses simple base_fee + priority_fee model

🔮 Future Enhancements

  • Multi-hop arbitrage (3+ pools)
  • Flash loan integration
  • Uniswap V3 support
  • Cross-DEX arbitrage (Sushiswap, Curve, etc.)
  • Real-time mempool monitoring
  • MEV protection strategies
  • Configurable gas price strategies
  • Web UI for monitoring

📄 License

This project is provided as-is for educational and research purposes.

🙏 Acknowledgments

Built with:

  • Alloy - Ethereum types and RPC
  • REVM - Rust EVM implementation
  • Rayon - Data parallelism
  • Tokio - Async runtime

⚠️ Disclaimer: This software is for educational purposes only. Using it for actual trading involves significant financial risk. Always test thoroughly and understand the code before deploying with real funds.