From 5f30573f249ae3ae2fd2abda9c607f33fbc4716f Mon Sep 17 00:00:00 2001 From: ts0yu <120932697+ts0yu@users.noreply.github.com> Date: Wed, 21 Aug 2024 19:29:05 +0000 Subject: [PATCH] feat: async arbitrageur trait, improvements to DefaultArbitrageur --- Cargo.lock | 30 +++++++++++++++ Cargo.toml | 2 + src/arena.rs | 9 ++++- src/engine/arbitrageur.rs | 78 +++++++++++++++++++++++++++++++++++++-- src/lib.rs | 7 +++- 5 files changed, 120 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 549e5b7..2df529c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -632,9 +632,11 @@ dependencies = [ "alloy-contract", "alloy-sol-macro", "alloy-sol-types", + "async-trait", "plotly", "rand", "rand_distr", + "rug", "tokio", ] @@ -875,6 +877,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + [[package]] name = "backtrace" version = "0.3.73" @@ -1535,6 +1543,16 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "gmp-mpfr-sys" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0205cd82059bc63b63cf516d714352a30c44f2c74da9961dfda2617ae6b5918" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "group" version = "0.13.0" @@ -2537,6 +2555,18 @@ dependencies = [ "rustc-hex", ] +[[package]] +name = "rug" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d157703b9f96e9be75c739e7030d1d81be377d882d93046670309381517772" +dependencies = [ + "az", + "gmp-mpfr-sys", + "libc", + "libm", +] + [[package]] name = "ruint" version = "1.12.3" diff --git a/Cargo.toml b/Cargo.toml index a73f380..feed08a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,9 @@ alloy = { version = "0.2.1", features = ["full", "node-bindings", "json"] } alloy-contract = "0.2.1" alloy-sol-macro = "0.7.7" alloy-sol-types = "0.7.7" +async-trait = "0.1.81" plotly = "0.9.0" rand = "0.8.5" rand_distr = "0.4.3" +rug = "1.25.0" tokio = { version = "1.39.2", features = ["macros"] } diff --git a/src/arena.rs b/src/arena.rs index 5973e76..28ccea0 100644 --- a/src/arena.rs +++ b/src/arena.rs @@ -84,7 +84,8 @@ impl Arena { self.pool.clone(), U256::from(79228162514264337593543950336_u128), Bytes::default(), - ).nonce(5) + ) + .nonce(5) .send() .await .unwrap() @@ -105,6 +106,7 @@ impl Arena { self.providers[&(idx + 1)].clone(), Signal::new( *pool_manager.address(), + *fetcher.address(), self.pool.clone(), self.feed.current_value(), None, @@ -128,6 +130,7 @@ impl Arena { let signal = Signal::new( *pool_manager.address(), + *fetcher.address(), self.pool.clone(), self.feed.current_value(), Some(step), @@ -149,7 +152,9 @@ impl Arena { self.nonce += 1; - self.arbitrageur.arbitrage(&signal, admin_provider.clone()); + self.arbitrageur + .arbitrage(&signal, admin_provider.clone()) + .await; for (idx, strategy) in self.strategies.iter_mut().enumerate() { strategy.process( diff --git a/src/engine/arbitrageur.rs b/src/engine/arbitrageur.rs index 1f8c630..737a885 100644 --- a/src/engine/arbitrageur.rs +++ b/src/engine/arbitrageur.rs @@ -1,14 +1,86 @@ -use crate::{AnvilProvider, Signal}; +use async_trait::async_trait; +use rug::{ops::Pow, Float}; + +use crate::{types::Fetcher, AnvilProvider, Signal}; /// Generic trait allowing user defined arbitrage strategies. +#[async_trait] pub trait Arbitrageur { /// Perform an arbitrage based on a [`Signal`]. - fn arbitrage(&self, signal: &Signal, provider: AnvilProvider); + async fn arbitrage(&self, signal: &Signal, provider: AnvilProvider); +} + +/// Default implementation of an [`Arbitrageur`] that uses the closed-form optimal swap amount to determine the optimal arbitrage. +pub struct DefaultArbitrageur; + +#[async_trait] +impl Arbitrageur for DefaultArbitrageur { + async fn arbitrage(&self, signal: &Signal, provider: AnvilProvider) { + let base = Float::with_val(53, 1.0001); + let price = Float::with_val(53, signal.current_value); + + let target_tick = price.log10() / base.log10(); + let current_tick = Float::with_val(53, signal.tick); + + let (start, end) = if current_tick < target_tick { + (current_tick, target_tick) + } else { + (target_tick, current_tick) + }; + + let (a, b) = self + .get_tick_range_liquidity( + signal, + provider, + start.to_i32_saturating().unwrap(), + end.to_i32_saturating().unwrap(), + ) + .await; + } +} + +impl DefaultArbitrageur { + async fn get_tick_range_liquidity( + &self, + signal: &Signal, + provider: AnvilProvider, + start: i32, + end: i32, + ) -> (Float, Float) { + let fetcher = Fetcher::new(signal.fetcher, provider); + + let mut liquidity_a = Float::with_val(53, 0); + let mut liquidity_b = Float::with_val(53, 0); + + for tick in start..end { + let pool_id = fetcher + .toId(signal.pool.clone().into()) + .call() + .await + .unwrap() + .poolId; + + let tick_info = fetcher + .getTickInfo(signal.manager, pool_id, tick) + .call() + .await + .unwrap(); + let sqrt_price = Float::with_val(53, Float::with_val(53, 1.0001).pow(tick / 2)); + + let tick_liquidity = Float::with_val(53, tick_info.liquidityNet); + + liquidity_a += tick_liquidity.clone() / sqrt_price.clone(); + liquidity_b += tick_liquidity * sqrt_price; + } + + (liquidity_a, liquidity_b) + } } /// No-op implementation of an [`Arbitrageur`] for custom usecases. pub struct EmptyArbitrageur; +#[async_trait] impl Arbitrageur for EmptyArbitrageur { - fn arbitrage(&self, _signal: &Signal, _provider: AnvilProvider) {} + async fn arbitrage(&self, _signal: &Signal, _provider: AnvilProvider) {} } diff --git a/src/lib.rs b/src/lib.rs index 11adebf..ee835a1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -106,6 +106,9 @@ pub struct Signal { /// Address of the pool manager. pub manager: Address, + /// Address of the fetcher. + pub fetcher: Address, + /// Key of the pool. pub pool: PoolKey, @@ -126,6 +129,7 @@ impl Signal { /// Public constructor function for a new [`Signal`]. pub fn new( manager: Address, + fetcher: Address, pool: PoolKey, current_value: f64, step: Option, @@ -134,6 +138,7 @@ impl Signal { ) -> Self { Self { manager, + fetcher, pool, current_value, step, @@ -150,7 +155,7 @@ mod tests { arena::{Arena, ArenaBuilder}, config::Config, engine::{ - arbitrageur::{Arbitrageur, EmptyArbitrageur}, + arbitrageur::{Arbitrageur, DefaultArbitrageur, EmptyArbitrageur}, inspector::EmptyInspector, }, feed::OrnsteinUhlenbeck,