Skip to content

Latest commit

 

History

History
 
 

bot

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 

Rusty-Sando/Bot license

Bot logic relies heavily on REVM simulations to detect sandwichable transactions. The simulations are done by injecting a modified router contract called BrainDance.sol into a new EVM instance. Once injected, a concurrent binary search is performed to find a optimal input amount that results in the highest revenue. After sandwich calculations, the bot performs a salmonella check. If the sandwich is salmonella free, the bot then calculates gas bribes and sends bundle.

Performing EVM simulations in this way allows the bot to detect sandwichable opportunities against any tx that introduces slippage.

Logic Breakdown

  • At startup, index all pools from a specific factory by parsing the PairCreated event. And fetch all token dust stored on sando addy.
  • Read and decode tx from mempool.
  • Send tx to trace_CallMany to obtain stateDiff. (could modify to use any other rpc that returns stateDiff)
  • Check if statediff contains keys that correspond to indexed pool addresses.
  • Construct a new EVM database instance from stateDiff, used for local simulations.
  • For each pool that tx touches:
    • Find the optimal amount in for a sandwich attack by performing a concurrent binary search.
    • Check for salmonella by checking if tx uses unconventional opcodes.
  • If profitable after gas calculations, send bundle to relays.
  • Store sandwich opportunity in backlog for multi meat sandwich calculations.

Usage

  1. This repo requires you to run an Erigon archive node. The bot relies on the newPendingTransactionsWithBody subscription endpoint and trace_callMany rpc which are Erigon specific methods. Node needs to be synced in archive mode to index all pools.

  2. Install Rust if you haven't already.

  3. Fill in searcher address in Huff contract and deploy either straight onchain or via create2 using a metamorphic like factory.

If you are using create2, you can easily mine for an address containing 7 zero bytes, saving 84 gas of calldata everytime the contract address is used as an argument. read more.

  1. Copy .env.example into .env and fill out values.
cp .env.example .env
RPC_URL_WSS=ws://localhost:8545
SEARCHER_PRIVATE_KEY=0000000000000000000000000000000000000000000000000000000000000001
FLASHBOTS_AUTH_KEY=0000000000000000000000000000000000000000000000000000000000000002
SANDWICH_CONTRACT=0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa
V2_ALERT_DISCORD_WEBHOOK=...
V3_ALERT_DISCORD_WEBHOOK=...
POISON_ALERT_DISCORD_WEBHOOK=...
SANDWICH_INCEPTION_BLOCK=... // block that sandwich contract was deployed in
  1. Before running backtests get the runtime bytecode of the contract and set it to get_test_sandwich_code in constants.rs.
huffc --bin-runtime contract/src/sandwich.huff
  1. Run the tests
cargo test --release -- --nocapture
  1. Create a binary executable
cargo run --bin rusty-sando --release

Note with the --release flag, the rust compiler will compile with optimizations. These optimizations are important because they speed up REVM simulations 10x.

Warning

By taking this codebase into production, you are doing so at your own risk under the MIT license. Although heavily tested, I cannot gurantee that it is bug free. I prefer this codebase to be used as a case study of what MEV could look like using Rust and Huff.

Blueprint

src
├── lib.rs
├── main.rs
├── abi - Holds contract abis 
│   └── ...
├── cfmm - Holds logic to index pools
│   └── ...
├── forked_db
│   ├── ...
│   ├── fork_db.rs - Local EVM instance for simulations
│   ├── fork_factory.rs - Creates `fork_db` instances and maintains connection with `global_backend`
│   └── global_backend.rs - Makes and caches rpc calls for missing state
├── runner
│   ├── mod.rs - Main runtime logic lives here
│   ├── bundle_sender.rs - Wrapper to submit bundles
│   ├── oracles.rs - Create execution environments for oracles
│   └── state.rs - Holds information about bot state
├── simulate
│   ...
│   ├── inspectors
│   │   ├── access_list.rs - Locally create access list for sandwich txs
│   │   └── is_sando_safu.rs - Salmonella checker
│   └── make_sandwich.rs - Optimal sandwich calculations and sanity checks
├── types - Common types used throughout codebase
└── utils
    ├── ...
    └── tx_builder - Logic to encode transactions
        └── ...

Oracles

There are three important oracles running on their own thread:

  • NextBlockOracle: Every new block, update latestBlock and nextBlock block number, timestamp, and basefee.
  • UpdatePoolOracle: Every 50 blocks, add any new pools created.
  • MegaSandwichOracle: Every 10.5 seconds after the latest block, search sandwich backlog to detect for multi meat sandwiches.

Improvements

This repo explores only basic and simple multi V2 and V3 sandwiches, however sandwiches come in many flavours and require some modifications to the codebase to capture them:

  • Stable coin pair sandwiches.
  • Sandwiches involving pairs that have a transfer limit, an example. Transfer limit can be found using a method similiar to Fej:Leuros's implementation.
  • Multi meat sandwiches that target more than one pool. example: frontrun, meat1, meat2, backrun.
  • Token -> Weth sandwiches by using a 'flashswap' between two pools. Normally we can only sandwich Weth -> Token swaps as the bot has Weth inventory, however you can use another pool's reserves as inventory to sandwich swaps in the other direction. example.
  • Flashloan sandwiches for larger value swaps.
  • Sandwiches that include a users token approval tx + swap tx in one bundle.
  • Sandwiches that include a users pending tx/s + swap tx in one bundle if swap tx nonce is higher than pending txs.