Skip to content

Commit

Permalink
feat: arbitrageur optimal swap logic
Browse files Browse the repository at this point in the history
  • Loading branch information
ts0yu authored Jul 31, 2024
1 parent f2a13d6 commit 9ccfbdd
Show file tree
Hide file tree
Showing 5 changed files with 487 additions and 25 deletions.
26 changes: 26 additions & 0 deletions contracts/utils/src/Fetcher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {PoolKey} from "v4-core/types/PoolKey.sol";
// https://github.com/Uniswap/v4-core/blob/799dd2cb980319a8d3b827b6a7aa59a606634553/src/libraries/StateLibrary.sol
contract Fetcher {
bytes32 public constant POOLS_SLOT = bytes32(uint256(6));
uint256 public constant TICKS_OFFSET = 4;

function _getPoolStateSlot(PoolId poolId) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(PoolId.unwrap(poolId), POOLS_SLOT));
Expand Down Expand Up @@ -44,4 +45,29 @@ contract Fetcher {
lpFee := and(shr(208, data), 0xFFFFFF)
}
}

function getTickLiquidity(IPoolManager manager, PoolId poolId, int24 tick)
external
view
returns (uint128 liquidityGross, int128 liquidityNet)
{
bytes32 slot = _getTickInfoSlot(poolId, tick);

bytes32 value = manager.extsload(slot);
assembly ("memory-safe") {
liquidityNet := sar(128, value)
liquidityGross := and(value, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
}
}

function _getTickInfoSlot(PoolId poolId, int24 tick) internal pure returns (bytes32) {
// slot key of Pool.State value: `pools[poolId]`
bytes32 stateSlot = _getPoolStateSlot(poolId);

// Pool.State: `mapping(int24 => TickInfo) ticks`
bytes32 ticksMappingSlot = bytes32(uint256(stateSlot) + TICKS_OFFSET);

// slot key of the tick key: `pools[poolId].ticks[tick]
return keccak256(abi.encodePacked(int256(tick), ticksMappingSlot));
}
}
98 changes: 88 additions & 10 deletions src/arbitrageur.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::f64::consts::LN_2;

use super::*;

#[derive(Debug, Default, Deserialize, Serialize, Clone)]
Expand All @@ -9,6 +11,80 @@ pub struct Arbitrageur {
pub liquid_exchange: Option<Address>,
}

impl Arbitrageur {
pub fn profit(dy: f64, p_ext: f64, p_uni: f64, fee: f64, liquidity: f64) -> (f64, f64) {
let sqrt_p_uni = p_uni.sqrt();
let delta_p = dy / (liquidity * sqrt_p_uni);
let new_price = p_uni + delta_p;

let dx = dy / ((1.0 - fee) * new_price);

let revenue = dy * p_ext;

let profit = revenue - dx;
(profit, new_price)
}

pub async fn optimal_swap(&self, p_ext: f64, p_uni: f64, fee: f64, initial_tick: f64) -> f64 {
let (mut a, mut b) = (0.0, 1f64.powi(6));

let mut current_tick = initial_tick;

let mut next_tick_liquidity = self.get_next_tick_liquidity(current_tick as i32).await;
let tol = 1f64.powi(-6);

while b - a > tol {
let mid = (a + b) / 2.0;

let (profit_mid, new_price_mid) =
Arbitrageur::profit(mid, p_ext, p_uni, fee, next_tick_liquidity);
let (profit_next, new_price_next) =
Arbitrageur::profit(mid + tol, p_ext, p_uni, fee, next_tick_liquidity);

if profit_mid > profit_next {
b = mid;
} else {
a = mid;
}

if new_price_mid >= Arbitrageur::get_price_at_tick(current_tick + 1.0) {
current_tick += 1.0;
next_tick_liquidity = self.get_next_tick_liquidity(current_tick as i32).await;
} else if new_price_mid <= Arbitrageur::get_price_at_tick(current_tick - 1.0) {
current_tick -= 1.0;
next_tick_liquidity = self.get_next_tick_liquidity(current_tick as i32).await;
}
}

return (a + b) / 2.0;
}

pub fn get_price_at_tick(tick: f64) -> f64 {
let sqrt = 1.0001f64.powf(tick);
sqrt * sqrt
}

pub async fn get_next_tick_liquidity(&self, tick: i32) -> f64 {
let fetcher_key = FetcherPoolKey {
currency0: self.pool.clone().unwrap().key.currency0,
currency1: self.pool.clone().unwrap().key.currency1,
fee: self.pool.clone().unwrap().key.fee,
tickSpacing: self.pool.clone().unwrap().key.tickSpacing,
hooks: self.pool.clone().unwrap().key.hooks,
};

let fetcher = Fetcher::new(self.fetcher.unwrap(), self.base.client.clone().unwrap());
let id = fetcher.toId(fetcher_key).call().await.unwrap().poolId;

fetcher
.getTickLiquidity(self.deployment.unwrap(), id, tick)
.call()
.await
.unwrap()
.liquidityGross as f64
}
}

#[async_trait::async_trait]
impl Behavior<Message> for Arbitrageur {
async fn startup(
Expand Down Expand Up @@ -45,6 +121,7 @@ impl Behavior<Message> for Arbitrageur {

Ok(Some(messager.clone().stream().unwrap()))
}

async fn process(&mut self, event: Message) -> Result<ControlFlow> {
let _query: Signal = match serde_json::from_str(&event.data) {
Ok(query) => query,
Expand All @@ -56,8 +133,10 @@ impl Behavior<Message> for Arbitrageur {

let manager = PoolManager::new(self.deployment.unwrap(), self.base.client.clone().unwrap());
let fetcher = Fetcher::new(self.fetcher.unwrap(), self.base.client.clone().unwrap());
let liquid_exchange =
LiquidExchange::new(self.liquid_exchange.unwrap(), self.base.client.clone().unwrap());
let liquid_exchange = LiquidExchange::new(
self.liquid_exchange.unwrap(),
self.base.client.clone().unwrap(),
);

let pool = self.pool.clone().unwrap();

Expand All @@ -78,18 +157,17 @@ impl Behavior<Message> for Arbitrageur {

let pricex192 = get_slot0_return.sqrtPriceX96.pow(U256::from(2));
let two_pow_192 = U256::from(1u128) << 192;

let scaled_price: U256 = (pricex192 * U256::from(10u128).pow(U256::from(18))) / two_pow_192;

let lex_price = liquid_exchange
.price()
.call()
.await?._0;

let lex_price = liquid_exchange.price().call().await?._0;

let diff = scaled_price.abs_diff(lex_price);

println!("diff: {}", diff);
println!("tick: {}", get_slot0_return.tick);
let p_uni = f64::from(scaled_price) / 10f64.powi(18);
let p_ext = f64::from(lex_price) / 10f64.powi(18);

println!("tick: {}", get_slot0_return.tick);

Ok(ControlFlow::Continue)
}
Expand Down
Loading

0 comments on commit 9ccfbdd

Please sign in to comment.