Skip to content

Commit

Permalink
refactor bot logic into seperate dedicated modules
Browse files Browse the repository at this point in the history
  • Loading branch information
mouseless-eth committed Jul 16, 2023
1 parent ec86920 commit 5fcf35f
Show file tree
Hide file tree
Showing 23 changed files with 474 additions and 108 deletions.
1 change: 1 addition & 0 deletions bot/crates/strategy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ ethers-flashbots = { git = "https://github.com/onbjerg/ethers-flashbots"}
ethers = {version = "2.0.7", features = ["abigen", "ws"]}
foundry-evm = { git = "https://github.com/mouseless-eth/foundry.git", branch = "ethers-version-change" }
anvil = { git = "https://github.com/mouseless-eth/foundry.git", branch = "ethers-version-change" }
eth-encode-packed = "0.1.0"

# Logging
colored = "2.0.0"
Expand Down
54 changes: 40 additions & 14 deletions bot/crates/strategy/src/bot/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@ use artemis_core::{collectors::block_collector::NewBlock, types::Strategy};
use async_trait::async_trait;
use colored::Colorize;
use ethers::{providers::Middleware, types::Transaction};
use foundry_evm::executor::fork::{BlockchainDb, BlockchainDbMeta, SharedBackend};
use log::{error, info};
use std::sync::Arc;
use std::{collections::BTreeSet, sync::Arc};

use crate::{
log_error, log_info_cyan, log_new_block_info, log_not_sandwichable, log_sandwichable,
managers::{
block_manager::BlockManager, pool_manager::PoolManager,
block_manager::{BlockInfo, BlockManager},
pool_manager::PoolManager,
sando_state_manager::SandoStateManager,
},
simulator::sandwich_finder::create_optimal_sandwich,
types::{Action, Event, StratConfig},
};

Expand Down Expand Up @@ -41,6 +42,41 @@ impl<M: Middleware + 'static> SandoBot<M> {
),
}
}

/// Main logic for the strategy
/// Checks if the RawIngredients are sandwichable
#[allow(unused_mut)]
pub async fn is_sandwichable(&self, target_block: BlockInfo) -> Result<()> {
// setup shared backend
let shared_backend = SharedBackend::spawn_backend_thread(
self.provider.clone(),
BlockchainDb::new(
BlockchainDbMeta {
cfg_env: Default::default(),
block_env: Default::default(),
hosts: BTreeSet::from(["".to_string()]),
},
None,
), /* default because not accounting for this atm */
Some((target_block.number - 1).into()),
);

#[cfg(feature = "debug")]
{
weth_inventory = (*crate::constants::WETH_FUND_AMT).into(); // Set a new value only when the debug feature is active
}

let optimal = find_optimal_input(
meats,
target_pool,
target_block,
weth_inventory,
shared_backend,
)
.await?;

Ok(())
}
}

#[async_trait]
Expand Down Expand Up @@ -113,17 +149,7 @@ impl<M: Middleware + 'static> SandoBot<M> {
let mut recipes = vec![];

for pool in touched_pools {
let optimal_sandwich = match create_optimal_sandwich(
vec![tx.clone()],
pool,
next_block.clone(),
weth_inventory,
self.sando_state_manager.get_searcher(),
self.sando_state_manager.get_sando_address(),
self.provider.clone(),
)
.await
{
let optimal_sandwich = match self.is_sandwichable(next_block.clone()).await {
Ok(s) => {
log_sandwichable!("{:?} {:?}", tx.hash, s);
recipes.push(s)
Expand Down
6 changes: 4 additions & 2 deletions bot/crates/strategy/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use ethers::{
use foundry_evm::revm::primitives::{B160 as rAddress, U256 as rU256};

pub static ONE_ETHER_IN_WEI: Lazy<rU256> = Lazy::new(|| rU256::from(1000000000000000000_u128));
pub static WETH_FUND_AMT: Lazy<rU256> = Lazy::new(|| rU256::from(69) * *ONE_ETHER_IN_WEI);

// could generate random address to use at runtime
pub static BRAINDANCE_CONTROLLER: Lazy<rAddress> = Lazy::new(|| {
Expand All @@ -20,14 +21,15 @@ pub static BRAINDANCE_ADDRESS: Lazy<rAddress> = Lazy::new(|| {
.unwrap()
});

// could compile from `../contract` at runtime
// could compile from `../contract` at runtime instead of parsing from string
pub static BRAINDANCE_CODE: Lazy<Bytes> = Lazy::new(|| {
"0x608060405234801561001057600080fd5b50600436106100415760003560e01c80634b588d401461004657806381eeb93c14610072578063fa461e3314610085575b600080fd5b610059610054366004610743565b61009a565b6040805192835260208301919091520160405180910390f35b610059610080366004610743565b61021f565b610098610093366004610796565b6104e3565b005b600080846001600160a01b038085169086161082816100cd5773fffd8963efd1fc6a506488495d951d5263988d256100d4565b6401000276ad5b9050600082886040516020016100ff92919091151582526001600160a01b0316602082015260400190565b6040516020818303038152906040529050600080856001600160a01b031663128acb0830878f88886040518663ffffffff1660e01b8152600401610147959493929190610863565b60408051808303816000875af1158015610165573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610189919061089e565b9150915084610198578161019a565b805b6101a3906108d8565b6040516370a0823160e01b81523060048201529098506001600160a01b038a16906370a0823190602401602060405180830381865afa1580156101ea573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061020e91906108f4565b965050505050505094509492505050565b60405163a9059cbb60e01b81526001600160a01b03848116600483015260248201869052600091829185169063a9059cbb906044016020604051808303816000875af1158015610273573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610297919061091b565b50600080600080886001600160a01b0316630902f1ac6040518163ffffffff1660e01b8152600401606060405180830381865afa1580156102dc573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610300919061095b565b506001600160701b031691506001600160701b03169150866001600160a01b0316886001600160a01b0316101561033c57819350809250610343565b8093508192505b50506040516370a0823160e01b81526001600160a01b03888116600483015260009184918916906370a0823190602401602060405180830381865afa158015610390573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103b491906108f4565b6103be91906109ab565b90506103cb818484610606565b9450600080876001600160a01b0316896001600160a01b0316106103f1578660006103f5565b6000875b6040805160008152602081019182905263022c0d9f60e01b90915291935091506001600160a01b038b169063022c0d9f9061043990859085903090602481016109c2565b600060405180830381600087803b15801561045357600080fd5b505af1158015610467573d6000803e3d6000fd5b50506040516370a0823160e01b81523060048201526001600160a01b038b1692506370a082319150602401602060405180830381865afa1580156104af573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104d391906108f4565b9550505050505094509492505050565b60008413806104f25750600083135b6104fb57600080fd5b60008061050a838501856109f9565b91509150811561058b5760405163a9059cbb60e01b8152336004820152602481018790526001600160a01b0382169063a9059cbb906044016020604051808303816000875af1158015610561573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610585919061091b565b506105fe565b60405163a9059cbb60e01b8152336004820152602481018690526001600160a01b0382169063a9059cbb906044016020604051808303816000875af11580156105d8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105fc919061091b565b505b505050505050565b60008084116106705760405162461bcd60e51b815260206004820152602b60248201527f556e697377617056324c6962726172793a20494e53554646494349454e545f4960448201526a1394155517d05353d5539560aa1b60648201526084015b60405180910390fd5b6000831180156106805750600082115b6106dd5760405162461bcd60e51b815260206004820152602860248201527f556e697377617056324c6962726172793a20494e53554646494349454e545f4c604482015267495155494449545960c01b6064820152608401610667565b60006106eb856103e5610a32565b905060006106f98483610a32565b905060008261070a876103e8610a32565b6107149190610a51565b90506107208183610a69565b979650505050505050565b6001600160a01b038116811461074057600080fd5b50565b6000806000806080858703121561075957600080fd5b84359350602085013561076b8161072b565b9250604085013561077b8161072b565b9150606085013561078b8161072b565b939692955090935050565b600080600080606085870312156107ac57600080fd5b8435935060208501359250604085013567ffffffffffffffff808211156107d257600080fd5b818701915087601f8301126107e657600080fd5b8135818111156107f557600080fd5b88602082850101111561080757600080fd5b95989497505060200194505050565b6000815180845260005b8181101561083c57602081850181015186830182015201610820565b8181111561084e576000602083870101525b50601f01601f19169290920160200192915050565b6001600160a01b0386811682528515156020830152604082018590528316606082015260a06080820181905260009061072090830184610816565b600080604083850312156108b157600080fd5b505080516020909101519092909150565b634e487b7160e01b600052601160045260246000fd5b6000600160ff1b82016108ed576108ed6108c2565b5060000390565b60006020828403121561090657600080fd5b5051919050565b801515811461074057600080fd5b60006020828403121561092d57600080fd5b81516109388161090d565b9392505050565b80516001600160701b038116811461095657600080fd5b919050565b60008060006060848603121561097057600080fd5b6109798461093f565b92506109876020850161093f565b9150604084015163ffffffff811681146109a057600080fd5b809150509250925092565b6000828210156109bd576109bd6108c2565b500390565b84815283602082015260018060a01b03831660408201526080606082015260006109ef6080830184610816565b9695505050505050565b60008060408385031215610a0c57600080fd5b8235610a178161090d565b91506020830135610a278161072b565b809150509250929050565b6000816000190483118215151615610a4c57610a4c6108c2565b500290565b60008219821115610a6457610a646108c2565b500190565b600082610a8657634e487b7160e01b600052601260045260246000fd5b50049056fea2646970667358221220a3830fddb415d84a0f9225a1e9bbeef724e1b5a2dc0efc456635debefba7af2c64736f6c634300080f0033"
.parse()
.unwrap()
});

pub static WETH_FUND_AMT: Lazy<rU256> = Lazy::new(|| rU256::from(69) * *ONE_ETHER_IN_WEI);
// funciton signature for getting reserves
pub static GET_RESERVES_SIG: Lazy<Bytes> = Lazy::new(|| "0x0902f1ac".parse().unwrap());

pub static ERC20_TRANSFER_EVENT_SIG: Lazy<H256> = Lazy::new(|| {
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
Expand Down
3 changes: 3 additions & 0 deletions bot/crates/strategy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ mod simulator;
/// Module contains logic to manage info on onchain pools
mod managers;

/// Module contains logic related to transaction building
pub(crate) mod tx_utils;

/// Module contains core strategy implementation
pub mod bot;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ use anvil::eth::util::get_precompiles_for;
use anyhow::{anyhow, Result};
use cfmms::pool::Pool;
use cfmms::pool::Pool::{UniswapV2, UniswapV3};
use ethers::abi::Address;
use ethers::signers::LocalWallet;
use ethers::types::{Transaction, U256};
use ethers::abi::{self, Address, ParamType};
use ethers::types::{Bytes, Transaction, U256};
use foundry_evm::executor::inspector::AccessListTracer;
use foundry_evm::executor::{ExecutionResult, TransactTo};
use foundry_evm::executor::{ExecutionResult, Output, TransactTo};
use foundry_evm::{
executor::fork::SharedBackend,
revm::{
Expand All @@ -16,6 +15,8 @@ use foundry_evm::{
},
};

use crate::constants::{GET_RESERVES_SIG, WETH_ADDRESS};
use crate::tx_utils::huff_sando_interface::common::weth_encoder::WethEncoder;
use crate::{managers::block_manager::BlockInfo, simulator::setup_block_state};

/// finds if sandwich is profitable + salmonella free
Expand All @@ -40,22 +41,16 @@ fn create_recipe(
// *.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
//
// encode frontrun_in before passing to sandwich contract
let frontrun_in = match target_pool {
UniswapV2(p) => tx_builder::v2::encode_weth(optimal_in),
UniswapV3(p) => tx_builder::v3::encode_weth(optimal_in),
};
let frontrun_in = WethEncoder::encode(optimal_in);

// caluclate frontrun_out using encoded frontrun_in
let frontrun_out = match target_pool {
UniswapV2(_) => {
let target_pool = target_pool.address();
let token_in = ingredients.startend_token;
let token_out = ingredients.intermediary_token;
evm.env.tx.gas_price = next_block.base_fee_per_gas.into();
evm.env.tx.gas_limit = 700000;
evm.env.tx.value = rU256::ZERO;
let amount_out =
get_amount_out_evm(frontrun_in, target_pool, token_in, token_out, &mut evm)?;
let amount_out = get_amount_out_evm(frontrun_in, target_pool, &mut evm)?;
tx_builder::v2::decode_intermediary(amount_out, true, token_out)
}
UniswapV3(_) => U256::zero(),
Expand All @@ -69,11 +64,11 @@ fn create_recipe(
ingredients.intermediary_token,
ingredients.target_pool,
),
UniswapV3(_) => sandwich_maker.v3.create_payload_weth_is_input(
UniswapV3(_) => v3_create_frontrun_payload(
target_pool.address(),
frontrun_in.as_u128().into(),
ingredients.startend_token,
ingredients.intermediary_token,
ingredients.target_pool,
),
};

Expand Down Expand Up @@ -117,7 +112,10 @@ fn create_recipe(
match salmonella_inspector.is_sando_safu() {
IsSandoSafu::Safu => { /* continue operation */ }
IsSandoSafu::NotSafu(not_safu_opcodes) => {
return Err(SimulationError::FrontrunNotSafu(not_safu_opcodes))
return Err(anyhow!(
"[huffsando: FrontrunNotSafu] {:?}",
not_safu_opcodes
))
}
}

Expand Down Expand Up @@ -156,7 +154,7 @@ fn create_recipe(
// remove reverted meats because mempool tx/s gas costs are accounted for by fb
let res = match evm.transact_commit() {
Ok(result) => result,
Err(e) => return Err(SimulationError::EvmError(e)),
Err(e) => return Err(anyhow!("[huffsando: EVM ERROR] {:?}", e)),
};
match res.is_success() {
true => is_meat_good.push(true),
Expand Down Expand Up @@ -224,7 +222,7 @@ fn create_recipe(
get_precompiles_for(evm.env.cfg.spec_id),
);
evm.inspect_ref(&mut access_list_inspector)
.map_err(|e| anyhow!("[EVM ERROR] sando frontrun: {:?}", e))
.map_err(|e| anyhow!("[huffsando: EVM ERROR] sando frontrun: {:?}", e))
.unwrap();
let backrun_access_list = access_list_inspector.access_list();
evm.env.tx.access_list = backrun_access_list;
Expand All @@ -234,15 +232,15 @@ fn create_recipe(
let mut salmonella_inspector = SalmonellaInspectoooor::new();
let backrun_result = match evm.inspect_commit(&mut salmonella_inspector) {
Ok(result) => result,
Err(e) => return Err(anyhow!("[EVM ERROR] sando backrun: {:?}", e)),
Err(e) => return Err(anyhow!("[huffsando: EVM ERROR] sando backrun: {:?}", e)),
};
match backrun_result {
ExecutionResult::Success { .. } => { /* continue */ }
ExecutionResult::Revert { output, .. } => {
return Err(anyhow!("[REVERT] sando backrun: {:?}", output));
return Err(anyhow!("[huffsando: REVERT] sando backrun: {:?}", output));
}
ExecutionResult::Halt { reason, .. } => {
return Err(anyhow!("[HALT] sando backrun: {:?}", reason))
return Err(anyhow!("[huffsando: HALT] sando backrun: {:?}", reason))
}
};
match salmonella_inspector.is_sando_safu() {
Expand Down Expand Up @@ -293,3 +291,94 @@ fn create_recipe(
//))
Ok(())
}

// Find amount out from an amount in using the k=xy formula
// note: assuming fee is set to 3% for all pools (not case irl)
//
// Arguments:
// * `amount_in`: amount of token in
// * `target_pool`: address of pool
// * `token_in`: address of token in
// * `token_out`: address of token out
// * `evm`: mutable reference to evm used for query
//
// Returns:
// Ok(U256): amount out
// Err(SimulationError): if error during caluclation
pub fn get_amount_out_evm(
amount_in: U256,
target_pool: Pool,
is_frontrun: bool,
evm: &mut EVM<CacheDB<SharedBackend>>,
) -> Result<U256> {
// get reserves
evm.env.tx.transact_to = TransactTo::Call(target_pool.address().0.into());
evm.env.tx.caller = (*WETH_ADDRESS).0.into(); // spoof weth address for its ether
evm.env.tx.value = rU256::ZERO;
evm.env.tx.data = (*GET_RESERVES_SIG).0; // getReserves()
let result = match evm.transact_ref() {
Ok(result) => result.result,
Err(e) => {
return Err(anyhow!(
"[huffsando: EVM ERROR, get_amount_out_evm] {:?}",
e
))
}
};
let output: Bytes = match result {
ExecutionResult::Success { output, .. } => match output {
Output::Call(o) => o.into(),
Output::Create(o, _) => o.into(),
},
ExecutionResult::Revert { output, .. } => {
return Err(anyhow!(
"[huffsando: EVM REVERTED, get_amount_out_evm] {:?}",
output
))
}
ExecutionResult::Halt { reason, .. } => {
return Err(anyhow!(
"[huffsando: EVM HALT, get_amount_out_evm] {:?}",
reason
))
}
};

let tokens = abi::decode(
&vec![
ParamType::Uint(128),
ParamType::Uint(128),
ParamType::Uint(32),
],
&output,
)
.unwrap();

let reserves_0 = tokens[0].clone().into_uint().unwrap();
let reserves_1 = tokens[1].clone().into_uint().unwrap();

let other_token = [target_pool.token_a, target_pool.token_b]
.into_iter()
.find(|&t| t != *WETH_ADDRESS)
.unwrap();

let (input_token, output_token) = if is_frontrun {
// if frontrun we trade WETH -> TOKEN
(*WETH_ADDRESS, other_token)
} else {
// if backrun we trade TOKEN -> WETH
(other_token, *WETH_ADDRESS)
};

let (reserve_in, reserve_out) = match token_in < token_out {
true => (reserves_0, reserves_1),
false => (reserves_1, reserves_0),
};

let a_in_with_fee: U256 = amount_in * 997;
let numerator: U256 = a_in_with_fee * reserve_out;
let denominator: U256 = reserve_in * 1000 + a_in_with_fee;
let amount_out: U256 = numerator.checked_div(denominator).unwrap_or(U256::zero());

Ok(amount_out)
}
2 changes: 0 additions & 2 deletions bot/crates/strategy/src/simulator/huffsando/mod.rs

This file was deleted.

This file was deleted.

Empty file.
Empty file.
Loading

0 comments on commit 5fcf35f

Please sign in to comment.