Skip to content

Example: simulation_uni_v2

To run this example:

  • Clone the examples repository: git clone git@github.com:alloy-rs/examples.git
  • Run: cargo run --example simulation_uni_v2
//! Simulates an arbitrage between Uniswap V2 and SushiSwap by forking anvil and using the
//! FlashBotsMultiCall contract.
use alloy::{
    hex,
    network::TransactionBuilder,
    node_bindings::Anvil,
    primitives::{utils::parse_units, Address, Bytes, B256, U256},
    providers::{ext::AnvilApi, Provider, ProviderBuilder},
    rpc::types::TransactionRequest,
    sol,
    sol_types::SolCall,
};
 
mod helpers;
use crate::helpers::{
    get_amount_in, get_amount_out, get_sushi_pair, get_uniswap_pair, set_hash_storage_slot,
    DAI_ADDR, WETH_ADDR,
};
use eyre::Result;
 
sol! {
    function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
}
 
sol!(
    #[sol(rpc)]
    contract IERC20 {
        function balanceOf(address target) returns (uint256);
    }
);
 
sol!(
    #[sol(rpc)]
    FlashBotsMultiCall,
    "examples/artifacts/FlashBotsMultiCall.json"
);
 
#[tokio::main]
async fn main() -> Result<()> {
    // Spawn `anvil` and fork mainnet
    // Make sure you have `anvil` in $PATH
    let anvil = Anvil::new().fork("https://reth-ethereum.ithaca.xyz/rpc").try_spawn()?;
 
    // Get the pool contract interfaces
    let uniswap_pair = get_uniswap_pair();
    let sushi_pair = get_sushi_pair();
 
    let wallet_address: Address = anvil.addresses()[0];
    let provider =
        ProviderBuilder::new().wallet(anvil.wallet().unwrap()).on_http(anvil.endpoint().parse()?);
 
    let executor = FlashBotsMultiCall::deploy(provider.clone(), wallet_address).await?;
    let iweth = IERC20::new(WETH_ADDR, provider.clone());
 
    // Mock WETH balance for executor contract
    set_hash_storage_slot(
        &provider,
        WETH_ADDR,
        U256::from(3),
        *executor.address(),
        parse_units("5.0", "ether")?.into(),
    )
    .await?;
 
    // Mock reserves for Uniswap pair
    provider
        .anvil_set_storage_at(
            uniswap_pair.address,
            U256::from(8), // getReserves slot
            B256::from_slice(&hex!(
                "665c6fcf00000000008ed55850d607f83a660000000526c08d812099d2577fbf"
            )),
        )
        .await?;
 
    // Mock WETH balance for Uniswap pair
    set_hash_storage_slot(
        &provider,
        WETH_ADDR,
        U256::from(3),
        uniswap_pair.address,
        uniswap_pair.reserve1,
    )
    .await?;
 
    // Mock DAI balance for Uniswap pair
    set_hash_storage_slot(
        &provider,
        DAI_ADDR,
        U256::from(2),
        uniswap_pair.address,
        uniswap_pair.reserve0,
    )
    .await?;
 
    // Mock reserves for Sushi pair
 
    provider
        .anvil_set_storage_at(
            sushi_pair.address,
            U256::from(8), // getReserves slot
            B256::from_slice(&hex!(
                "665c6fcf00000000006407e2ec8d4f09436700000003919bf56d886af022979d"
            )),
        )
        .await?;
 
    // Mock WETH balance for Sushi pair
    set_hash_storage_slot(
        &provider,
        WETH_ADDR,
        U256::from(3),
        sushi_pair.address,
        sushi_pair.reserve1,
    )
    .await?;
 
    // Mock DAI balance for Sushi pair
    set_hash_storage_slot(
        &provider,
        DAI_ADDR,
        U256::from(2),
        sushi_pair.address,
        sushi_pair.reserve0,
    )
    .await?;
 
    let balance_of = iweth.balanceOf(*executor.address()).call().await?;
    println!("Before - WETH balance of executor {:?}", balance_of);
 
    let weth_amount_in = get_amount_in(
        uniswap_pair.reserve0,
        uniswap_pair.reserve1,
        false,
        sushi_pair.reserve0,
        sushi_pair.reserve1,
    );
 
    let dai_amount_out =
        get_amount_out(uniswap_pair.reserve1, uniswap_pair.reserve0, weth_amount_in);
 
    let weth_amount_out = get_amount_out(sushi_pair.reserve0, sushi_pair.reserve1, dai_amount_out);
 
    let swap1 = swapCall {
        amount0Out: dai_amount_out,
        amount1Out: U256::ZERO,
        to: sushi_pair.address,
        data: Bytes::new(),
    }
    .abi_encode();
 
    let swap2 = swapCall {
        amount0Out: U256::ZERO,
        amount1Out: weth_amount_out,
        to: *executor.address(),
        data: Bytes::new(),
    }
    .abi_encode();
 
    let arb_calldata = FlashBotsMultiCall::uniswapWethCall {
        _wethAmountToFirstMarket: weth_amount_in,
        _ethAmountToCoinbase: U256::ZERO,
        _targets: vec![uniswap_pair.address, sushi_pair.address],
        _payloads: vec![Bytes::from(swap1), Bytes::from(swap2)],
    }
    .abi_encode();
 
    let arb_tx =
        TransactionRequest::default().with_to(*executor.address()).with_input(arb_calldata);
 
    let pending = provider.send_transaction(arb_tx).await?;
    pending.get_receipt().await?;
 
    let balance_of = iweth.balanceOf(*executor.address()).call().await?;
    println!("After - WETH balance of executor {:?}", balance_of);
 
    Ok(())
}

Find the source code on Github here.