Skip to content

Commit

Permalink
feat: add more signal data
Browse files Browse the repository at this point in the history
  • Loading branch information
ts0yu authored Sep 14, 2024
1 parent 8aa0987 commit 84d05ee
Show file tree
Hide file tree
Showing 5 changed files with 247 additions and 140 deletions.
6 changes: 5 additions & 1 deletion contracts/utils/src/ArenaController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ contract ArenaController {
uint160 sqrtPriceX96;
address manager;
uint256 lexPrice;
PoolKey pool;
address fetcher;
}

constructor(uint256 fee, uint256 initialPrice) {
Expand Down Expand Up @@ -53,7 +55,9 @@ contract ArenaController {
currentTick: tick,
sqrtPriceX96: sqrtPriceX96,
manager: address(poolManager),
lexPrice: lex.price()
lexPrice: lex.price(),
pool: poolKey,
fetcher: address(fetcher)
});
}

Expand Down
51 changes: 37 additions & 14 deletions src/arena.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use std::collections::HashMap;

use alloy::{primitives::Uint, providers::ProviderBuilder, signers::local::PrivateKeySigner};

use alloy::providers::WalletProvider;
use alloy::providers::Provider;
use super::*;
use crate::{
config::Config,
Expand Down Expand Up @@ -69,6 +70,10 @@ impl<V> Arena<V> {
None,
signal.currentTick,
signal.sqrtPriceX96,
signal.manager,
signal.pool,
signal.fetcher,
self.feed.current_value(),
);

strategy
Expand All @@ -88,23 +93,37 @@ impl<V> Arena<V> {
None,
signal.currentTick,
signal.sqrtPriceX96,
signal.manager,
signal.pool,
signal.fetcher,
self.feed.current_value(),
);

controller
.setPrice(
alloy::primitives::utils::parse_ether(&self.feed.step().to_string())
.map_err(ArenaError::ConversionError)?,
)
.send()
.await
.map_err(ArenaError::ContractError)?
.watch()
.await
.map_err(|e| ArenaError::PendingTransactionError(e))?;

self.arbitrageur.init(&signal, admin_provider.clone()).await;

for step in 0..config.steps {
controller
.setPrice(
alloy::primitives::utils::parse_ether(&self.feed.step().to_string())
.map_err(ArenaError::ConversionError)?,
)
.nonce(
admin_provider
.get_transaction_count(admin_provider.default_signer_address())
.await
.unwrap(),
)
.send()
.await
.map_err(ArenaError::ContractError)?
.watch()
.await
.map_err(|e| ArenaError::PendingTransactionError(e))?;

self.arbitrageur
.arbitrage(&signal, admin_provider.clone())
.await;

for (idx, strategy) in self.strategies.iter_mut().enumerate() {
let signal = controller.constructSignal().call().await?._0;

Expand All @@ -113,6 +132,10 @@ impl<V> Arena<V> {
Some(step),
signal.currentTick,
signal.sqrtPriceX96,
signal.manager,
signal.pool,
signal.fetcher,
self.feed.current_value(),
);

strategy
Expand Down Expand Up @@ -228,4 +251,4 @@ impl<V> ArenaBuilder<V> {
providers,
}
}
}
}
2 changes: 1 addition & 1 deletion src/artifacts/ArenaController.json

Large diffs are not rendered by default.

244 changes: 127 additions & 117 deletions src/engine/arbitrageur.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
use std::str::FromStr;

use async_trait::async_trait;
use rug::{ops::Pow, Float};

use crate::{AnvilProvider, Signal};
use super::*;
use crate::{
types::{
fetcher::Fetcher,
swap::{IPoolManager::SwapParams, PoolSwapTest, PoolSwapTest::TestSettings},
},
AnvilProvider, Signal,
};

/// Generic trait allowing user defined arbitrage strategies.
#[async_trait]
Expand All @@ -12,122 +22,122 @@ pub trait Arbitrageur {
async fn arbitrage(&mut self, signal: &Signal, provider: AnvilProvider);
}

// /// Default implementation of an [`Arbitrageur`] that uses the closed-form optimal swap amount to determine the optimal arbitrage.
// #[derive(Default)]
// pub struct DefaultArbitrageur {
// swapper: Option<Address>,
// }

// #[async_trait]
// impl Arbitrageur for DefaultArbitrageur {
// async fn init(&mut self, signal: &Signal, provider: AnvilProvider) {
// let swapper = PoolSwapTest::deploy(provider.clone(), signal.manager)
// .await
// .unwrap();

// self.swapper = Some(*swapper.address());
// }

// async fn arbitrage(&mut self, signal: &Signal, provider: AnvilProvider) {
// // arbitrageur is always initialized before event loop starts, so unwrap should never fail.
// let swapper = PoolSwapTest::new(self.swapper.unwrap(), provider.clone());

// 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.as_i64());

// let (start, end) = (
// current_tick.clone().min(&target_tick),
// current_tick.clone().max(&target_tick),
// );

// let (a, b) = self
// .get_tick_range_liquidity(
// signal,
// provider,
// start.to_i32_saturating().unwrap(),
// end.to_i32_saturating().unwrap(),
// )
// .await;

// let k = a.clone() * b.clone();

// // closed form optimal swap solution, ref: https://arxiv.org/pdf/1911.03380
// let fee: u64 = signal.pool.fee.to_string().parse().unwrap();
// let optimal_swap = Float::with_val(53, 0).max(&(a.clone() - (k / (fee * (a / b)))));

// let zero_for_one = current_tick > target_tick;

// let swap_params = SwapParams {
// amountSpecified: Signed::from_str(&optimal_swap.to_string()).unwrap(),
// zeroForOne: zero_for_one,
// sqrtPriceLimitX96: signal.sqrt_price_x96,
// };

// let test_settings = TestSettings {
// takeClaims: false,
// settleUsingBurn: false,
// };

// swapper
// .swap(
// signal.pool.clone().into(),
// swap_params,
// test_settings,
// Bytes::new(),
// )
// .send()
// .await
// .unwrap()
// .watch()
// .await
// .unwrap();
// }
// }

// 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.clone());

// 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,
// Signed::from_str(&tick.to_string()).unwrap(),
// )
// .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)
// }
// }
/// Default implementation of an [`Arbitrageur`] that uses the closed-form optimal swap amount to determine the optimal arbitrage.
#[derive(Default)]
pub struct DefaultArbitrageur {
swapper: Option<Address>,
}

#[async_trait]
impl Arbitrageur for DefaultArbitrageur {
async fn init(&mut self, signal: &Signal, provider: AnvilProvider) {
let swapper = PoolSwapTest::deploy(provider.clone(), signal.manager)
.await
.unwrap();

self.swapper = Some(*swapper.address());
}

async fn arbitrage(&mut self, signal: &Signal, provider: AnvilProvider) {
// arbitrageur is always initialized before event loop starts, so unwrap should never fail.
let swapper = PoolSwapTest::new(self.swapper.unwrap(), provider.clone());

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.as_i64());

let (start, end) = (
current_tick.clone().min(&target_tick),
current_tick.clone().max(&target_tick),
);

let (a, b) = self
.get_tick_range_liquidity(
signal,
provider,
start.to_i32_saturating().unwrap(),
end.to_i32_saturating().unwrap(),
)
.await;

let k = a.clone() * b.clone();

// closed form optimal swap solution, ref: https://arxiv.org/pdf/1911.03380
let fee: u64 = signal.pool.fee.to_string().parse().unwrap();
let optimal_swap = Float::with_val(53, 0).max(&(a.clone() - (k / (fee * (a / b)))));

let zero_for_one = current_tick > target_tick;

let swap_params = SwapParams {
amountSpecified: Signed::from_str(&optimal_swap.to_string()).unwrap(),
zeroForOne: zero_for_one,
sqrtPriceLimitX96: signal.sqrt_price_x96,
};

let test_settings = TestSettings {
takeClaims: false,
settleUsingBurn: false,
};

swapper
.swap(
signal.pool.clone().into(),
swap_params,
test_settings,
Bytes::new(),
)
.send()
.await
.unwrap()
.watch()
.await
.unwrap();
}
}

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.clone());

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,
Signed::from_str(&tick.to_string()).unwrap(),
)
.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;
Expand Down
Loading

0 comments on commit 84d05ee

Please sign in to comment.