Skip to content
This repository has been archived by the owner on Aug 10, 2023. It is now read-only.

Heavy cleanup #31

Merged
merged 48 commits into from
Jul 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
4b34637
cleanup submodules
mouseless0x Jun 26, 2023
df12a44
remove existing modules before readding
mouseless0x Jun 26, 2023
57e91bf
forge install: forge-std
mouseless0x Jun 26, 2023
e2984ae
forge install: foundry-huff
mouseless0x Jun 26, 2023
01c2b78
forge install: v3-core
mouseless0x Jun 26, 2023
7340c8b
forge install: v2-core
mouseless0x Jun 26, 2023
3b64474
forge install: v2-periphery
mouseless0x Jun 26, 2023
d5e5302
remove all redundant dependencies
mouseless0x Jun 27, 2023
b4093fc
cleanup braindance contract
mouseless0x Jun 27, 2023
ddd0541
missing comments
mouseless0x Jun 27, 2023
536bd2f
cleanup sando contract: clearer func names, fix comments
mouseless0x Jun 27, 2023
2495164
file rename
mouseless0x Jun 28, 2023
a8925b4
Merge branch 'master' into heavy-cleanup
mouseless0x Jun 28, 2023
549798f
cleanup variable names
mouseless0x Jun 28, 2023
72b4465
not needed for repo
mouseless0x Jun 28, 2023
2872e1a
specify support for push0
mouseless0x Jun 28, 2023
0f0c3ff
small correction regarding pool_key_hash
mouseless0x Jun 28, 2023
81de1f4
cleanup general helper
mouseless0x Jun 28, 2023
bd24f93
missing one stop to account for replacing push1 0x00 with push0
mouseless0x Jun 28, 2023
b0d6be7
split up v2 and v3 logic into seperate libraries
mouseless0x Jun 28, 2023
a5572dc
test v2 using fuzzing
mouseless0x Jun 28, 2023
1a0f775
apply forge fmt
mouseless0x Jun 28, 2023
326ed54
Update README.md
mouseless0x Jun 28, 2023
8a8e3e4
Update README.md
mouseless0x Jun 28, 2023
46b70c9
deploy doesnt exist:
mouseless0x Jun 28, 2023
ead617a
clarify
mouseless0x Jun 29, 2023
6c98232
clearer comments
mouseless0x Jun 29, 2023
fbf2cf1
add v3 fuzz test, simplify v3 interface
mouseless0x Jun 30, 2023
beefd8f
refactor codebase for artemis
mouseless0x Jul 2, 2023
eed47f4
add simulator module
mouseless0x Jul 2, 2023
1b43abd
reduce unnecessary function call
mouseless0x Jul 2, 2023
dad3e48
cleanup code by abstracting encoding process
mouseless0x Jul 15, 2023
8433709
rename BrainDance to LilRouter
mouseless0x Jul 15, 2023
ec86920
forge fmt contracts
mouseless0x Jul 16, 2023
5fcf35f
refactor bot logic into seperate dedicated modules
mouseless0x Jul 16, 2023
a5dcdaf
use type RawIngredients for all calculations
Jul 19, 2023
24765d5
include salmonella checker
mouseless0x Jul 23, 2023
71ee264
basic integration tests
mouseless0x Jul 23, 2023
4b38e17
rename braindance -> lil router
mouseless0x Jul 23, 2023
d927f05
cleanup logic
mouseless0x Jul 23, 2023
07a3ac7
return optimal recipe when sando opp is found
mouseless0x Jul 23, 2023
f0db6aa
cleanup
mouseless0x Jul 24, 2023
8249fff
Update README.md
mouseless0x Jul 24, 2023
148f215
Update README.md
mouseless0x Jul 24, 2023
94cb0cb
remove discord webhooks
mouseless0x Jul 24, 2023
10872bd
Update README.md
mouseless0x Jul 24, 2023
899e327
Update README.md
mouseless0x Jul 24, 2023
b8fecfa
Update README.md
mouseless0x Jul 24, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,3 @@ jobs:
forge build --sizes
id: build
continue-on-error: true

- name: Run scripts
run: |
ls -lsa
ls script/
for file in script/*; do
forge script $file -vvvv
done
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ contract/cache
output.log
__TEMP__*
run-*.json
*.cfmms-checkpoint.json
21 changes: 6 additions & 15 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,24 +1,9 @@
[submodule "contract/lib/forge-std"]
path = contract/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "SandwichV4/lib/v3-core"]
path = SandwichV4/lib/v3-core
url = https://github.com/Uniswap/v3-core
[submodule "lib/v3-core"]
branch = v1.0.0
[submodule "SandwichV4/lib/v2-core"]
path = SandwichV4/lib/v2-core
url = https://github.com/Uniswap/v2-core
[submodule "lib/v2-core"]
branch = v1.0.1
[submodule "SandwichV4/lib/v2-periphery"]
path = SandwichV4/lib/v2-periphery
url = https://github.com/Uniswap/v2-periphery
[submodule "contract/lib/foundry-huff"]
path = contract/lib/foundry-huff
url = https://github.com/huff-language/foundry-huff
[submodule "lib/forge-std"]
branch = v1.5.2
[submodule "contract/lib/v3-core"]
path = contract/lib/v3-core
url = https://github.com/Uniswap/v3-core
Expand All @@ -28,3 +13,9 @@
[submodule "contract/lib/v2-periphery"]
path = contract/lib/v2-periphery
url = https://github.com/Uniswap/v2-periphery
[submodule "contract/lib/solmate"]
path = contract/lib/solmate
url = https://github.com/transmissions11/solmate
[submodule "contract/lib/v3-periphery"]
path = contract/lib/v3-periphery
url = https://github.com/Uniswap/v3-periphery
25 changes: 14 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
# Rusty-Sando ![license](https://img.shields.io/badge/License-MIT-green.svg?label=license) ![twitter](https://img.shields.io/twitter/follow/0xMouseless?style=social)
# Rusty-Sando (re-write) ![license](https://img.shields.io/badge/License-MIT-green.svg?label=license) ![twitter](https://img.shields.io/twitter/follow/0xMouseless?style=social)
A practical example on how to perform V2/V3 and multi-meat sandwich attacks written using Rust and Huff.

The goal of this repo is to act as reference material for searchers looking to implement their strategies using Rust and Huff.
The goal of this repo is to act as reference material for aspiring searchers.

> **This codebase has been cleaned up and rewritten to make use of the [`Artemis`](https://github.com/paradigmxyz/artemis) framework.**

## Demo
https://user-images.githubusercontent.com/97399882/226269539-afedced0-e070-4d12-9853-dfbafbcefa49.mp4

## Brief Explanation
Anytime that a transaction interacts with a Uniswap V2/V3 pool and their forks, there is some slippage introduced (router swaps, aggregator swaps, other mev bots). Sandwich bots are a toxic form of MEV as they profit off this slippage by frontrunning the transaction pushing the price of the asset up to the slippage limit and then immediately selling the asset through a backrun transaction.
Anytime that a transaction interacts with a Uniswap V2/V3 pool and its forks, there is some slippage introduced (routers, aggregators, other MEV bots). Sandwich bots, like this one, are a toxic form of MEV as they profit off this slippage by frontrunning the transaction pushing the price of an asset up to the slippage limit, and then immediately selling the asset through a backrun transaction.

**Bot Logic Breakdown** can be found under [bot/README.md](https://github.com/mouseless-eth/rusty-sando/tree/master/bot)

**Contract Logic Breakdown** can be found under [contract/README.md](https://github.com/mouseless-eth/rusty-sando/tree/master/contract)

## Features
- **Fully Generalized**: Sandwich any tx that introduces slippage (routers, aggregators, mev bots, ...).
- **Fully Generalized**: Sandwich any tx that introduces slippage.
- **V2 and V3 Logic**: Logic to handle Uniswap V2/V3 pools.
- **Multi-Meat**: Build and send multi-meat sandwiches.
- **Gas Optimized**: Contract written in Huff using unconventional gas optimizations.
- **Local Simulations**: Fast concurrent EVM simulations to find sandwich opportunities.
- **Token Dust**: Stores dust at the end of every bundle for lower gas usage next time token is traded.
- **Salmonella Checks**: Detect if tx uses unusual opcodes that may produce different mainnet results.
- **Discord Logging**: Send notifications via discord webhooks.

**Bot Logic Breakdown** can be found under [bot/README.md](https://github.com/mouseless-eth/rusty-sando/tree/master/bot)

**Contract Logic Breakdown** can be found under [contract/README.md](https://github.com/mouseless-eth/rusty-sando/tree/master/contract)
- **Token Dust**: Stores dust at the end of every bundle for lower gas usage the next time the token is traded.
- **Salmonella Checks**: Detect if erc20's transfer function uses any unusual opcodes that may produce different mainnet results.

## Notice
If any bugs or optimizations are found, feel free to create a pull request. **All pull requests are welcome!**
Expand All @@ -35,8 +36,10 @@ If any bugs or optimizations are found, feel free to create a pull request. **Al
- [subway-rs](https://github.com/refcell/subway-rs)
- [cfmms-rs](https://github.com/0xKitsune/cfmms-rs)
- [revm](https://github.com/bluealloy/revm)
- [artemis](https://github.com/paradigmxyz/artemis)
- [huff-language](https://github.com/huff-language/huff-rs)
- [foundry](https://github.com/foundry-rs/foundry)
- [reth](https://github.com/paradigmxyz/reth)
- [ethers-rs](https://github.com/gakonst/ethers-rs)
- [ethers-flashbots](https://github.com/onbjerg/ethers-flashbots)
- [mev-template-rs](https://github.com/degatchi/mev-template-rs)
6 changes: 1 addition & 5 deletions bot/.env.example
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
RPC_URL_WSS=ws://localhost:8545
WSS_RPC=ws://localhost:8545
SEARCHER_PRIVATE_KEY=0000000000000000000000000000000000000000000000000000000000000001
FLASHBOTS_AUTH_KEY=0000000000000000000000000000000000000000000000000000000000000002
SANDWICH_CONTRACT=0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa
INTERVAL_BLOCK_NEW_POOL=50
V2_ALERT_DISCORD_WEBHOOK=...
V3_ALERT_DISCORD_WEBHOOK=...
POISON_ALERT_DISCORD_WEBHOOK=...
SANDWICH_INCEPTION_BLOCK=...
47 changes: 6 additions & 41 deletions bot/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,41 +1,6 @@
[package]
name = "rusty-sando"
version = "0.1.0"
edition = "2021"
license = "MIT"
description = "Optimized sandwich bot written using Rust and Huff"
readme = "README.md"
homepage = "https://github.com/mouseless-eth/rusty-sando"
repository = "https://github.com/mouseless-eth/rusty-sando"
keywords = ["Ethereum", "Mev", "Dex", "Sandwich"]
authors = ["0xmouseless <https://github.com/mouseless-eth>"]

[dependencies]
ethers-flashbots = { git = "https://github.com/onbjerg/ethers-flashbots" }
ethers = {version = "2.0.0", features = ["abigen", "ws"]}
revm = {version = "3.0.0", features = ["ethersdb", "serde", "std"]}
dotenv = "0.15.0"
hashbrown = "0.14.0"
tokio = { version = "1", features = ["full"] }
log = "0.4.17"
url = "2.3.1"
dashmap = "5.4.0"
async-recursion = "1.0.2"
hex = "0.4.3"
serde = "1.0.145"
eyre = "0.6.8"
reqwest = "0.11.12"
time = "*"
indoc = "2"
indicatif = "0.17.1"
thiserror = "1.0.37"
fern = {version = "0.6", features = ["colored"]}
chrono = "0.4.23"
futures = "0.3.5"
colored = "2.0.0"

[profile.release]
debug = true

[dev-dependencies]
tokio-test = "*"
[workspace]
members = [
"crates/artemis-core",
"crates/strategy",
"sando-bin"
]
96 changes: 24 additions & 72 deletions bot/README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,27 @@
# Rusty-Sando/Bot ![license](https://img.shields.io/badge/License-MIT-green.svg?label=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`](https://github.com/mouseless-eth/rusty-sando/blob/master/contract/src/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](https://github.com/Defi-Cartel/salmonella) check. If the sandwich is salmonella free, the bot then calculates gas bribes and sends bundle.
Bot logic relies heavily on REVM simulations to detect sandwichable transactions. The simulations are done by injecting a modified router contract called [`LilRouter.sol`](https://github.com/mouseless-eth/rusty-sando/blob/master/contract/src/LilRouter.sol) into a new EVM instance. Once injected, a concurrent binary search is performed to find an optimal input amount that results in the highest revenue. After sandwich calculations, the bot performs a [salmonella](https://github.com/Defi-Cartel/salmonella) check. If the sandwich is salmonella free, the bot then calculates gas bribes and sends the bundle to the fb relay.

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`](https://openethereum.github.io/JSONRPC-trace-module#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.
- Send tx to [`trace_call`](https://openethereum.github.io/JSONRPC-trace-module#trace_call) to obtain `stateDiff`.
- Check if `statediff` contains keys that equal to indexed pool addresses.
- 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.
- If profitable after gas calculations, send the sando bundle to relays.

## Usage

1. This repo requires you to run an [Erigon](https://github.com/ledgerwatch/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.
1. This repo requires you to run an [Erigon](https://github.com/ledgerwatch/erigon) archive node. The bot relies on the `newPendingTransactionsWithBody` subscription rpc endpoint which is a Erigon specific method. The node needs to be synced in archive mode to index all pools.

2. [Install Rust](https://www.rust-lang.org/tools/install) if you haven't already.

3. Fill in searcher address in Huff contract and deploy either straight onchain or via create2 using a [metamorphic](https://github.com/0age/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](https://medium.com/coinmonks/deploy-an-efficient-address-contract-a-walkthrough-cb4be4ffbc70).
3. Fill in the searcher address in Huff contract and deploy either straight onchain or via create2 using a [metamorphic](https://github.com/0age/metamorphic) like factory.
> If you are using create2, you can easily mine for an address containing 7 zero bytes, saving 84 gas of calldata every time the contract address is used as an argument. [read more](https://medium.com/coinmonks/deploy-an-efficient-address-contract-a-walkthrough-cb4be4ffbc70).

4. Copy `.env.example` into `.env` and fill out values.

Expand All @@ -32,89 +30,43 @@ cp .env.example .env
```

```
RPC_URL_WSS=ws://localhost:8545
WSS_RPC=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
SANDWICH_INCEPTION_BLOCK=...
```

5. Before running backtests get the runtime bytecode of the contract and set it to [`get_test_sandwich_code`](https://github.com/mouseless-eth/rusty-sando/blob/5ddeb4bbf703420de3cd5bc2b0d6885fce4cb0a4/bot/src/utils/constants.rs#L26) in constants.rs.
5. Run the integration tests

```console
huffc --bin-runtime contract/src/sandwich.huff
cargo test -p strategy --release --features debug
```

5. Run the tests
6. Run the bot in `debug mode`
Test bot's sandwich finding functionality without a deployed or funded Sando contract (no bundles will be sent)

```
cargo test --release -- --nocapture
cargo run --release --features debug
```

6. Create a binary executable
7. Running the bot

```console
cargo run --release
```
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.

> **By taking this codebase into production, you are doing so at your own risk under the MIT license.** I prefer this codebase to be used as a case study of what MEV could look like using Rust and Huff.

## 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:
This repo explores only basic and simple multi V2 and V3 sandwiches, however, sandwiches come in many flavors and require some modifications to the codebase to capture them:

- Stable coin pair sandwiches.
- Sandwiches involving pairs that have a transfer limit, an [example](https://eigenphi.io/mev/ethereum/tx/0xe7c1e7d96e63d31f937af48b61d534e32ed9cfdbef066f45d49b967caeea8eed). Transfer limit can be found using a method similiar to [Fej:Leuros's implementation](https://twitter.com/FejLeuros/status/1633379306750767106).
- Multi meat sandwiches that target more than one pool. example: [frontrun](https://etherscan.io/tx/0xa39d28624f6d18a3bd5f5289a70fdc2779782f9a2e2c36dddd95cf882a15da45), [meat1](https://etherscan.io/tx/0xd027b771da68544279262439fd3f1cdef6a438ab6219b510c73c033b4e377296), [meat2](https://etherscan.io/tx/0x288da393cb7c937b8fe29ce0013992063d252372da869e31c6aad689f8b1aaf3), [backrun](https://etherscan.io/tx/0xcf22f2a3c9c67d56282e77e60c09929e0451336a9ed38f037fd484ea29e3cd41).
- Sandwiches involving pairs that have a transfer limit, an [example](https://eigenphi.io/mev/ethereum/tx/0xe7c1e7d96e63d31f937af48b61d534e32ed9cfdbef066f45d49b967caeea8eed). Transfer limit can be found using a method similar to [Fej:Leuros's implementation](https://twitter.com/FejLeuros/status/1633379306750767106).
- Multi-meat sandwiches that target more than one pool. example: [frontrun](https://etherscan.io/tx/0xa39d28624f6d18a3bd5f5289a70fdc2779782f9a2e2c36dddd95cf882a15da45), [meat1](https://etherscan.io/tx/0xd027b771da68544279262439fd3f1cdef6a438ab6219b510c73c033b4e377296), [meat2](https://etherscan.io/tx/0x288da393cb7c937b8fe29ce0013992063d252372da869e31c6aad689f8b1aaf3), [backrun](https://etherscan.io/tx/0xcf22f2a3c9c67d56282e77e60c09929e0451336a9ed38f037fd484ea29e3cd41).
- 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](https://eigenphi.io/mev/ethereum/tx/0x502b66ce1a8b71098decc3585c651745c1af55de19e8f29ec6fff4ed2fcd1589).
- 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.
- Longtail sandwiches to target TOKEN->(WETH or STABLE) swaps.
- Sandwiches that include a user's token approval tx + swap tx in one bundle.
- Sandwiches that include a user's pending tx/s + swap tx in one bundle if swap tx nonce is higher than pending tx.
23 changes: 23 additions & 0 deletions bot/crates/artemis-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "artemis-core"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

## eth
ethers = { version = "2", features = ["ws", "rustls"]}
ethers-flashbots = { git = "https://github.com/onbjerg/ethers-flashbots", features = ["rustls"] }

## async
async-trait = "0.1.64"
reqwest = { version = "0.11.14", default-features = false, features = ["rustls-tls"] }
tokio = { version = "1.18", features = ["full"] }
tokio-stream = { version = "0.1", features = ['sync'] }

## misc
anyhow = "1.0.70"
thiserror = "1.0.40"
tracing = "0.1.37"
Loading