-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit d77f515
Showing
643 changed files
with
131,357 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
name: ci | ||
|
||
on: [push] | ||
|
||
jobs: | ||
tests: | ||
name: Tests with Foundry | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- uses: actions/checkout@v3 | ||
with: | ||
submodules: recursive | ||
|
||
- name: Install Foundry | ||
uses: foundry-rs/foundry-toolchain@v1 | ||
with: | ||
version: nightly | ||
|
||
- name: Install Huff | ||
uses: huff-language/huff-toolchain@v2 | ||
with: | ||
version: nightly | ||
|
||
- name: Run Tests | ||
run: forge test -vvv | ||
|
||
scripts: | ||
strategy: | ||
matrix: { script: [build] } | ||
fail-fast: true | ||
name: Run Scripts | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
with: | ||
submodules: recursive | ||
|
||
- name: Install Foundry | ||
uses: foundry-rs/foundry-toolchain@v1 | ||
with: | ||
version: nightly | ||
|
||
- name: Install Huff | ||
uses: huff-language/huff-toolchain@v2 | ||
with: | ||
version: nightly | ||
|
||
- name: Run Forge build | ||
run: | | ||
forge --version | ||
forge build --sizes | ||
id: build | ||
continue-on-error: true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
bot/target | ||
bot/Cargo.lock | ||
contract/out | ||
contract/cache | ||
.env | ||
output.log | ||
__TEMP__* | ||
run-*.json | ||
*.cfmms-checkpoint.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
[submodule "contract/lib/forge-std"] | ||
path = contract/lib/forge-std | ||
url = https://github.com/foundry-rs/forge-std | ||
[submodule "contract/lib/foundry-huff"] | ||
path = contract/lib/foundry-huff | ||
url = https://github.com/huff-language/foundry-huff | ||
[submodule "contract/lib/v3-core"] | ||
path = contract/lib/v3-core | ||
url = https://github.com/Uniswap/v3-core | ||
[submodule "contract/lib/v2-core"] | ||
path = contract/lib/v2-core | ||
url = https://github.com/Uniswap/v2-core | ||
[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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2023 phureewat29 | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# Sandwich Maker ![license](https://img.shields.io/badge/License-MIT-green.svg?label=license) | ||
|
||
A sandwich making machine to perform V2/V3 and multi-meat sandwich attacks written using Rust and Huff. | ||
|
||
## Brief Explanation | ||
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/phureewat29/sandwich-maker/tree/master/bot) | ||
|
||
**Contract Logic Breakdown** can be found under [contract/README.md](https://github.com/phureewat29/sandwich-maker/tree/master/contract) | ||
|
||
## Features | ||
- **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 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!** | ||
|
||
> **Warning** | ||
> | ||
> **This software is highly experimental and should be used at your own risk.** Although tested, this bot is experimental software and is provided on an "as is" and "as available" basis under the MIT license. We cannot guarantee the stability or reliability of this codebase and are not responsible for any damage or loss caused by its use. We do not give out warranties. | ||
## Acknowledgments | ||
- [subway](https://github.com/libevm/subway) | ||
- [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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
WSS_RPC=ws://localhost:8545 | ||
SEARCHER_PRIVATE_KEY=0000000000000000000000000000000000000000000000000000000000000001 | ||
FLASHBOTS_AUTH_KEY=0000000000000000000000000000000000000000000000000000000000000002 | ||
SANDWICH_CONTRACT=0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa | ||
SANDWICH_INCEPTION_BLOCK=... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
[workspace] | ||
members = [ | ||
"crates/artemis-core", | ||
"crates/strategy", | ||
"sandwich-bin", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
# Sandwich Maker/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 [`LilRouter.sol`](https://github.com/phureewat29/sandwich-maker/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 addy. | ||
- Read and decode tx from mempool. | ||
- 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 the 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 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 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. | ||
|
||
```console | ||
cp .env.example .env | ||
``` | ||
|
||
``` | ||
WSS_RPC=ws://localhost:8545 | ||
SEARCHER_PRIVATE_KEY=0000000000000000000000000000000000000000000000000000000000000001 | ||
FLASHBOTS_AUTH_KEY=0000000000000000000000000000000000000000000000000000000000000002 | ||
SANDWICH_CONTRACT=0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa | ||
SANDWICH_INCEPTION_BLOCK=... | ||
``` | ||
|
||
5. Run the integration tests | ||
|
||
```console | ||
cargo test -p strategy --release --features debug | ||
``` | ||
|
||
6. Run the bot in `debug mode` | ||
Test bot's sandwich finding functionality without a deployed or funded contract (no bundles will be sent) | ||
|
||
``` | ||
cargo run --release --features debug | ||
``` | ||
|
||
1. Running the bot | ||
|
||
```console | ||
cargo run --release | ||
``` | ||
> **Warning** | ||
> | ||
> **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 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 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). | ||
- Longtail sandwiches to target TOKEN->(WETH or STABLE) swaps by pre buying and holding token. | ||
- 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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
use crate::types::{Collector, CollectorStream}; | ||
use anyhow::Result; | ||
use async_trait::async_trait; | ||
use ethers::{ | ||
prelude::Middleware, | ||
providers::PubsubClient, | ||
types::{U256, U64}, | ||
}; | ||
use std::sync::Arc; | ||
use tokio_stream::StreamExt; | ||
|
||
/// A collector that listens for new blocks, and generates a stream of | ||
/// [events](NewBlock) which contain the block number and hash. | ||
pub struct BlockCollector<M> { | ||
provider: Arc<M>, | ||
} | ||
|
||
/// A new block event, containing the block number and hash. | ||
#[derive(Debug, Clone)] | ||
pub struct NewBlock { | ||
pub number: U64, | ||
pub gas_used: U256, | ||
pub gas_limit: U256, | ||
pub base_fee_per_gas: U256, | ||
pub timestamp: U256, | ||
} | ||
|
||
impl<M> BlockCollector<M> { | ||
pub fn new(provider: Arc<M>) -> Self { | ||
Self { provider } | ||
} | ||
} | ||
|
||
/// Implementation of the [Collector](Collector) trait for the [BlockCollector](BlockCollector). | ||
/// This implementation uses the [PubsubClient](PubsubClient) to subscribe to new blocks. | ||
#[async_trait] | ||
impl<M> Collector<NewBlock> for BlockCollector<M> | ||
where | ||
M: Middleware, | ||
M::Provider: PubsubClient, | ||
M::Error: 'static, | ||
{ | ||
async fn get_event_stream(&self) -> Result<CollectorStream<'_, NewBlock>> { | ||
let stream = self.provider.subscribe_blocks().await?; | ||
let stream = stream.filter_map(|block| match block.number { | ||
Some(number) => Some(NewBlock { | ||
number, | ||
gas_limit: block.gas_limit, | ||
gas_used: block.gas_used, | ||
base_fee_per_gas: block.base_fee_per_gas.unwrap_or_default(), | ||
timestamp: block.timestamp, | ||
}), | ||
None => None, | ||
}); | ||
Ok(Box::pin(stream)) | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
bot/crates/artemis-core/src/collectors/mempool_collector.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
use async_trait::async_trait; | ||
|
||
use ethers::{prelude::Middleware, providers::PubsubClient, types::Transaction}; | ||
use std::sync::Arc; | ||
|
||
use crate::types::{Collector, CollectorStream}; | ||
use anyhow::Result; | ||
|
||
/// A collector that listens for new transactions in the mempool, and generates a stream of | ||
/// [events](Transaction) which contain the transaction. | ||
pub struct MempoolCollector<M> { | ||
provider: Arc<M>, | ||
} | ||
|
||
impl<M> MempoolCollector<M> { | ||
pub fn new(provider: Arc<M>) -> Self { | ||
Self { provider } | ||
} | ||
} | ||
|
||
/// Implementation of the [Collector](Collector) trait for the [MempoolCollector](MempoolCollector). | ||
/// This implementation uses the [PubsubClient](PubsubClient) to subscribe to new transactions. | ||
#[async_trait] | ||
impl<M> Collector<Transaction> for MempoolCollector<M> | ||
where | ||
M: Middleware, | ||
M::Provider: PubsubClient, | ||
M::Error: 'static, | ||
{ | ||
async fn get_event_stream(&self) -> Result<CollectorStream<'_, Transaction>> { | ||
let stream = self | ||
.provider | ||
.subscribe(["newPendingTransactionsWithBody"]) | ||
.await | ||
.map_err(|_| anyhow::anyhow!("Failed to create mempool stream"))?; | ||
Ok(Box::pin(stream)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
//! Collectors are responsible for collecting data from external sources and | ||
//! turning them into internal events. For example, a collector might listen to | ||
//! a stream of new blocks, and turn them into a stream of `NewBlock` events. | ||
|
||
/// This collector listens to a stream of new blocks. | ||
pub mod block_collector; | ||
|
||
/// This collector listens to a stream of new pending transactions. | ||
pub mod mempool_collector; |
Oops, something went wrong.