Skip to content

Commit

Permalink
Add new public 1559 executor
Browse files Browse the repository at this point in the history
  • Loading branch information
zhongeric committed Aug 14, 2024
1 parent 452c826 commit ece59a3
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 45 deletions.
22 changes: 14 additions & 8 deletions crates/uniswapx-rs/src/order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,14 @@ impl Order {
pub fn resolve(&self, timestamp: u64, priority_fee: Uint<256, 4>) -> OrderResolution {
match self {
Order::V2DutchOrder(order) => order.resolve(timestamp),
Order::PriorityOrder(order) => order.resolve(priority_fee),
Order::PriorityOrder(order) => order.resolve(timestamp, priority_fee),
}
}

pub fn encode(&self) -> Vec<u8> {
match self {
Order::V2DutchOrder(order) => order._encode(),
Order::PriorityOrder(order) => order._encode(),
Order::V2DutchOrder(order) => order.encode_inner(),
Order::PriorityOrder(order) => order.encode_inner(),
}
}
}
Expand Down Expand Up @@ -133,11 +133,11 @@ pub enum OrderResolution {
}

impl V2DutchOrder {
pub fn _decode(order_hex: &[u8], validate: bool) -> Result<Self, Box<dyn Error>> {
pub fn decode_inner(order_hex: &[u8], validate: bool) -> Result<Self, Box<dyn Error>> {
Ok(V2DutchOrder::decode_single(order_hex, validate)?)
}

pub fn _encode(&self) -> Vec<u8> {
pub fn encode_inner(&self) -> Vec<u8> {
V2DutchOrder::encode_single(self)
}

Expand Down Expand Up @@ -194,15 +194,21 @@ impl V2DutchOrder {
}

impl PriorityOrder {
pub fn _decode(order_hex: &[u8], validate: bool) -> Result<Self, Box<dyn Error>> {
pub fn decode_inner(order_hex: &[u8], validate: bool) -> Result<Self, Box<dyn Error>> {
Ok(PriorityOrder::decode_single(order_hex, validate)?)
}

pub fn _encode(&self) -> Vec<u8> {
pub fn encode_inner(&self) -> Vec<u8> {
PriorityOrder::encode_single(self)
}

pub fn resolve(&self, priority_fee: Uint<256, 4>) -> OrderResolution {
pub fn resolve(&self, timestamp: u64, priority_fee: Uint<256, 4>) -> OrderResolution {
let timestamp = Uint::from(timestamp);

if self.info.deadline.lt(&timestamp) {
return OrderResolution::Expired;
};

let input = self.input.scale(priority_fee);
let outputs = self
.outputs
Expand Down
2 changes: 1 addition & 1 deletion src/collectors/uniswapx_order_collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ mod tests {
};
let order_hex: Vec<u8> = hex::decode(encoded_order).unwrap();

let result = V2DutchOrder::_decode(&order_hex, false);
let result = V2DutchOrder::decode_inner(&order_hex, false);
match result {
Err(e) => panic!("Error decoding order: {:?}", e),
_ => (),
Expand Down
1 change: 1 addition & 0 deletions src/executors/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod protect_executor;
pub mod public_1559_executor;
6 changes: 4 additions & 2 deletions src/executors/protect_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use anyhow::{Context, Result};
use artemis_core::executors::mempool_executor::SubmitTxToMempool;
use artemis_core::types::Executor;
use async_trait::async_trait;
use ethers::providers::Middleware;
use ethers::{providers::Middleware, types::U256};

/// An executor that sends transactions to the mempool.
pub struct ProtectExecutor<M, N> {
Expand All @@ -35,7 +35,6 @@ where
{
/// Send a transaction to the mempool.
async fn execute(&self, mut action: SubmitTxToMempool) -> Result<()> {
info!("Executing tx {:?}", action.tx);
let gas_usage_result = self
.client
.estimate_gas(&action.tx, None)
Expand All @@ -60,6 +59,9 @@ where
.context("Error getting gas price: {}")?;
}
action.tx.set_gas_price(bid_gas_price);
// set max_priority_fee_per_gas to be 0
action.tx.as_eip1559_mut().unwrap().max_priority_fee_per_gas = Some(U256::from(50));
info!("Executing tx {:?}", action.tx);
self.sender_client.send_transaction(action.tx, None).await?;
Ok(())
}
Expand Down
64 changes: 64 additions & 0 deletions src/executors/public_1559_executor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use std::sync::Arc;
use tracing::info;

use anyhow::{Context, Result};
use artemis_core::types::Executor;
use async_trait::async_trait;
use ethers::{providers::Middleware, types::U256};

use crate::strategies::types::SubmitTxToMempoolWithAdvancedProfitCalculation;

/// An executor that sends transactions to the public mempool.
pub struct Public1559Executor<M, N> {
client: Arc<M>,
sender_client: Arc<N>,
}

impl<M: Middleware, N: Middleware> Public1559Executor<M, N> {
pub fn new(client: Arc<M>, sender_client: Arc<N>) -> Self {
Self {
client,
sender_client,
}
}
}

#[async_trait]
impl<M, N> Executor<SubmitTxToMempoolWithAdvancedProfitCalculation> for Public1559Executor<M, N>
where
M: Middleware,
M::Error: 'static,
N: Middleware,
N::Error: 'static,
{
/// Send a transaction to the mempool.
async fn execute(&self, mut action: SubmitTxToMempoolWithAdvancedProfitCalculation) -> Result<()> {
let gas_usage_result = self
.client
.estimate_gas(&action.execution.tx, None)
.await
.context("Error estimating gas usage: {}");
info!("Gas Usage {:?}", gas_usage_result);
// let gas_usage = gas_usage_result?;

let bid_priority_fee;
let base_fee: U256 = self
.client
.get_gas_price()
.await
.context("Error getting gas price: {}")?;

if let Some(gas_bid_info) = action.execution.gas_bid_info {
// priority fee at which we'd break even, meaning 100% of profit goes to user in the form of price improvement
// TODO: use gas estimate here
bid_priority_fee = action.profit_calculation.calculate_priority_fee(gas_bid_info.bid_percentage)
} else {
bid_priority_fee = Some(U256::from(50));
}
action.execution.tx.as_eip1559_mut().unwrap().max_fee_per_gas = Some(base_fee);
action.execution.tx.as_eip1559_mut().unwrap().max_priority_fee_per_gas = bid_priority_fee;
info!("Executing tx {:?}", action.execution.tx);
self.sender_client.send_transaction(action.execution.tx, None).await?;
Ok(())
}
}
5 changes: 3 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use ethers::{
signers::{LocalWallet, Signer},
};
use executors::protect_executor::ProtectExecutor;
use executors::public_1559_executor::Public1559Executor;
use std::sync::Arc;
use strategies::priority_strategy::UniswapXPriorityFill;
use strategies::{
Expand Down Expand Up @@ -158,7 +159,7 @@ async fn main() -> Result<()> {
mevblocker_provider.clone(),
));

let public_tx_executor = Box::new(ProtectExecutor::new(provider.clone(), provider.clone()));
let public_tx_executor = Box::new(Public1559Executor::new(provider.clone(), provider.clone()));

let protect_executor = ExecutorMap::new(protect_executor, |action| match action {
Action::SubmitTx(tx) => Some(tx),
Expand All @@ -167,7 +168,7 @@ async fn main() -> Result<()> {
});

let public_tx_executor = ExecutorMap::new(public_tx_executor, |action| match action {
Action::SubmitPublicTx(tx) => Some(tx),
Action::SubmitPublicTx(execution) => Some(execution),
// No op for protected transactions
_ => None,
});
Expand Down
92 changes: 63 additions & 29 deletions src/strategies/priority_strategy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ use super::{
shared::UniswapXStrategy,
types::{Config, OrderStatus},
};
use crate::collectors::{
use crate::{collectors::{
block_collector::NewBlock,
uniswapx_order_collector::UniswapXOrder,
uniswapx_route_collector::{OrderBatchData, OrderData, PriorityOrderData, RoutedOrder},
};
}, strategies::types::SubmitTxToMempoolWithAdvancedProfitCalculation};
use alloy_primitives::Uint;
use anyhow::Result;
use artemis_core::executors::mempool_executor::{GasBidInfo, SubmitTxToMempool};
Expand All @@ -28,11 +28,48 @@ use uniswapx_rs::order::{OrderResolution, PriorityOrder};

use super::types::{Action, Event};

const BLOCK_TIME: u64 = 2;
const DONE_EXPIRY: u64 = 300;
// Base addresses
const REACTOR_ADDRESS: &str = "0x000000001Ec5656dcdB24D90DFa42742738De729";
pub const WETH_ADDRESS: &str = "0x4200000000000000000000000000000000000006";

#[derive(Debug, Clone)]
pub struct ProfitCalculation {
// amount of quote token we can get
quote: U256,
// amount of quote token needed to fill the order
amount_out_required: U256,
}

impl ProfitCalculation {
pub fn new(quote: U256, amount_out_required: U256) -> Self {
Self {
quote,
amount_out_required,
}
}

pub fn calculate_priority_fee(&self, bid_percentage: u64) -> Option<U256> {
let mps = U256::from(10_000_000);

if self.quote.le(&self.amount_out_required) {
return None;
}

let profit_quote = self.quote.saturating_sub(self.amount_out_required);

let mps_of_improvement = profit_quote
.saturating_mul(mps)
.checked_div(self.amount_out_required)?;
info!("mps_of_improvement: {}", mps_of_improvement);
let priority_fee = mps_of_improvement
.checked_mul(U256::from(bid_percentage))?
.checked_div(U256::from(100))?;
return Some(priority_fee);
}
}

#[derive(Debug)]
#[allow(dead_code)]
pub struct UniswapXPriorityFill<M> {
Expand Down Expand Up @@ -106,7 +143,7 @@ impl<M: Middleware + 'static> UniswapXPriorityFill<M> {
};
let order_hex = hex::decode(encoded_order)?;

Ok(PriorityOrder::_decode(&order_hex, false)?)
Ok(PriorityOrder::decode_inner(&order_hex, false)?)
}

async fn process_order_event(&mut self, event: UniswapXOrder) -> Option<Action> {
Expand Down Expand Up @@ -140,25 +177,27 @@ impl<M: Middleware + 'static> UniswapXPriorityFill<M> {
..
} = &event.request;

if let Some(profit) = self.get_profit_eth(&event) {
if let Some(profit) = self.get_profit_calculation(&event) {
info!(
"Sending trade: num trades: {} routed quote: {}, batch needs: {}, profit: {} wei",
"Sending trade: num trades: {} routed quote: {}, batch needs: {}",
orders.len(),
event.route.quote,
amount_out_required,
profit
);

let signed_orders = self.get_signed_orders(orders.clone()).ok()?;
return Some(Action::SubmitPublicTx(SubmitTxToMempool {
tx: self
.build_fill(self.client.clone(), &self.executor_address, signed_orders, event)
.await
.ok()?,
gas_bid_info: Some(GasBidInfo {
bid_percentage: self.bid_percentage,
total_profit: profit,
}),
return Some(Action::SubmitPublicTx(SubmitTxToMempoolWithAdvancedProfitCalculation {
execution: SubmitTxToMempool {
tx: self
.build_fill(self.client.clone(), &self.executor_address, signed_orders, event)
.await
.ok()?,
gas_bid_info: Some(GasBidInfo {
bid_percentage: self.bid_percentage,
total_profit: U256::from(0),
}),
},
profit_calculation: profit,
}));
}

Expand Down Expand Up @@ -199,7 +238,7 @@ impl<M: Middleware + 'static> UniswapXPriorityFill<M> {
match batch {
OrderData::PriorityOrderData(order) => {
signed_orders.push(SignedOrder {
order: Bytes::from(order.order._encode()),
order: Bytes::from(order.order.encode_inner()),
sig: Bytes::from_str(&order.signature)?,
});
}
Expand Down Expand Up @@ -264,29 +303,24 @@ impl<M: Middleware + 'static> UniswapXPriorityFill<M> {
/// - we have to bid at least the base fee
/// - the priority fee set for the transaction is essentially total_profit_eth - base_fee
/// - at 100% bid_percentage, our priority fee is total_profit_eth and thus gives the maximum amount to the user
fn get_profit_eth(&self, RoutedOrder { request, route }: &RoutedOrder) -> Option<U256> {
fn get_profit_calculation(&self, RoutedOrder { request, route }: &RoutedOrder) -> Option<ProfitCalculation> {
let quote = U256::from_str_radix(&route.quote, 10).ok()?;
let amount_out_required =
U256::from_str_radix(&request.amount_out_required.to_string(), 10).ok()?;
if quote.le(&amount_out_required) {
return None;
}
let profit_quote = quote.saturating_sub(amount_out_required);

if request.token_out.to_lowercase() == WETH_ADDRESS.to_lowercase() {
return Some(profit_quote);
}

let gas_use_eth = U256::from_str_radix(&route.gas_use_estimate, 10)
.ok()?
.saturating_mul(U256::from_str_radix(&route.gas_price_wei, 10).ok()?);
profit_quote
.saturating_mul(gas_use_eth)
.checked_div(U256::from_str_radix(&route.gas_use_estimate_quote, 10).ok()?)
return Some({
ProfitCalculation {
quote,
amount_out_required,
}
})
}

fn update_order_state(&mut self, order: PriorityOrder, signature: String, order_hash: String) {
let resolved = order.resolve(Uint::from(0));
let resolved = order.resolve( self.last_block_timestamp + BLOCK_TIME, Uint::from(0));
let order_status: OrderStatus = match resolved {
OrderResolution::Expired => OrderStatus::Done,
OrderResolution::Invalid => OrderStatus::Done,
Expand Down
10 changes: 9 additions & 1 deletion src/strategies/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use crate::collectors::{
use artemis_core::executors::mempool_executor::SubmitTxToMempool;
use uniswapx_rs::order::ResolvedOrder;

use super::priority_strategy::ProfitCalculation;

/// Core Event enum for the current strategy.
#[derive(Debug, Clone)]
pub enum Event {
Expand All @@ -14,11 +16,17 @@ pub enum Event {
UniswapXRoute(Box<RoutedOrder>),
}

#[derive(Debug, Clone)]
pub struct SubmitTxToMempoolWithAdvancedProfitCalculation {
pub execution: SubmitTxToMempool,
pub profit_calculation: ProfitCalculation,
}

/// Core Action enum for the current strategy.
#[derive(Debug, Clone)]
pub enum Action {
SubmitTx(SubmitTxToMempool),
SubmitPublicTx(SubmitTxToMempool),
SubmitPublicTx(SubmitTxToMempoolWithAdvancedProfitCalculation),
}

/// Configuration for variables we need to pass to the strategy.
Expand Down
4 changes: 2 additions & 2 deletions src/strategies/uniswapx_strategy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ impl<M: Middleware + 'static> UniswapXUniswapFill<M> {
};
let order_hex: Vec<u8> = hex::decode(encoded_order)?;

Ok(V2DutchOrder::_decode(&order_hex, false)?)
Ok(V2DutchOrder::decode_inner(&order_hex, false)?)
}

// Process new orders as they come in.
Expand Down Expand Up @@ -198,7 +198,7 @@ impl<M: Middleware + 'static> UniswapXUniswapFill<M> {
match batch {
OrderData::V2DutchOrderData(order) => {
signed_orders.push(SignedOrder {
order: Bytes::from(order.order._encode()),
order: Bytes::from(order.order.encode_inner()),
sig: Bytes::from_str(&order.signature)?,
});
}
Expand Down

0 comments on commit ece59a3

Please sign in to comment.