use alloy_primitives::{U160, U256};
use rust_decimal::prelude::ToPrimitive;
use rust_decimal_macros::dec;
use crate::{
defi::{
Token,
data::swap::RawSwapData,
tick_map::{
full_math::FullMath, sqrt_price_math::decode_sqrt_price_x96_to_price_tokens_adjusted,
},
},
enums::OrderSide,
types::{Price, Quantity, fixed::FIXED_PRECISION},
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SwapTradeInfo {
pub order_side: OrderSide,
pub quantity_base: Quantity,
pub quantity_quote: Quantity,
pub spot_price: Price,
pub execution_price: Price,
pub is_inverted: bool,
pub spot_price_before: Option<Price>,
}
impl SwapTradeInfo {
pub fn set_spot_price_before(&mut self, price: Price) {
self.spot_price_before = Some(price);
}
pub fn get_price_impact_bps(&self) -> anyhow::Result<u32> {
if let Some(spot_price_before) = self.spot_price_before {
let price_change = self.spot_price - spot_price_before;
let price_impact =
(price_change.as_decimal() / spot_price_before.as_decimal()).abs() * dec!(10_000);
Ok(price_impact.round().to_u32().unwrap_or(0))
} else {
anyhow::bail!("Cannot calculate price impact, the spot price before is not set");
}
}
pub fn get_slippage_bps(&self) -> anyhow::Result<u32> {
if let Some(spot_price_before) = self.spot_price_before {
let price_change = self.execution_price - spot_price_before;
let slippage =
(price_change.as_decimal() / spot_price_before.as_decimal()).abs() * dec!(10_000);
Ok(slippage.round().to_u32().unwrap_or(0))
} else {
anyhow::bail!("Cannot calculate slippage, the spot price before is not set")
}
}
}
#[derive(Debug)]
pub struct SwapTradeInfoCalculator<'a> {
token0: &'a Token,
token1: &'a Token,
pub is_inverted: bool,
raw_swap_data: RawSwapData,
}
impl<'a> SwapTradeInfoCalculator<'a> {
pub fn new(token0: &'a Token, token1: &'a Token, raw_swap_data: RawSwapData) -> Self {
let is_inverted = token0.get_token_priority() < token1.get_token_priority();
Self {
token0,
token1,
is_inverted,
raw_swap_data,
}
}
pub fn zero_for_one(&self) -> bool {
self.raw_swap_data.amount0.is_positive()
}
pub fn compute(&self, sqrt_price_x96_before: Option<U160>) -> anyhow::Result<SwapTradeInfo> {
let spot_price_before = if let Some(sqrt_price_x96_before) = sqrt_price_x96_before {
Some(decode_sqrt_price_x96_to_price_tokens_adjusted(
sqrt_price_x96_before,
self.token0.decimals,
self.token1.decimals,
self.is_inverted,
)?)
} else {
None
};
Ok(SwapTradeInfo {
order_side: self.order_side(),
quantity_base: self.quantity_base()?,
quantity_quote: self.quantity_quote()?,
spot_price: self.spot_price()?,
execution_price: self.execution_price()?,
is_inverted: self.is_inverted,
spot_price_before,
})
}
pub fn order_side(&self) -> OrderSide {
let zero_for_one = self.zero_for_one();
if self.is_inverted {
if zero_for_one {
OrderSide::Buy
} else {
OrderSide::Sell
}
} else {
if zero_for_one {
OrderSide::Sell
} else {
OrderSide::Buy
}
}
}
pub fn quantity_base(&self) -> anyhow::Result<Quantity> {
let (amount, precision) = if self.is_inverted {
(
self.raw_swap_data.amount1.unsigned_abs(),
self.token1.decimals,
)
} else {
(
self.raw_swap_data.amount0.unsigned_abs(),
self.token0.decimals,
)
};
Quantity::from_u256(amount, precision)
}
pub fn quantity_quote(&self) -> anyhow::Result<Quantity> {
let (amount, precision) = if self.is_inverted {
(
self.raw_swap_data.amount0.unsigned_abs(),
self.token0.decimals,
)
} else {
(
self.raw_swap_data.amount1.unsigned_abs(),
self.token1.decimals,
)
};
Quantity::from_u256(amount, precision)
}
fn spot_price(&self) -> anyhow::Result<Price> {
decode_sqrt_price_x96_to_price_tokens_adjusted(
self.raw_swap_data.sqrt_price_x96,
self.token0.decimals,
self.token1.decimals,
self.is_inverted, )
}
fn execution_price(&self) -> anyhow::Result<Price> {
let amount0 = self.raw_swap_data.amount0.unsigned_abs();
let amount1 = self.raw_swap_data.amount1.unsigned_abs();
if amount0.is_zero() || amount1.is_zero() {
anyhow::bail!("Cannot calculate execution price with zero amounts");
}
let (quote_amount, base_amount, quote_decimals, base_decimals) = if self.is_inverted {
(amount0, amount1, self.token0.decimals, self.token1.decimals)
} else {
(amount1, amount0, self.token1.decimals, self.token0.decimals)
};
let base_decimals_scalar = U256::from(10u128.pow(base_decimals as u32));
let quote_decimals_scalar = U256::from(10u128.pow(quote_decimals as u32));
let fixed_scalar = U256::from(10u128.pow(FIXED_PRECISION as u32));
let numerator_step1 = FullMath::mul_div(quote_amount, base_decimals_scalar, U256::from(1))?;
let numerator_final = FullMath::mul_div(numerator_step1, fixed_scalar, U256::from(1))?;
let denominator = FullMath::mul_div(base_amount, quote_decimals_scalar, U256::from(1))?;
let price_raw_u256 = FullMath::mul_div(numerator_final, U256::from(1), denominator)?;
anyhow::ensure!(
price_raw_u256 <= U256::from(i128::MAX as u128),
"Price overflow: {price_raw_u256} exceeds i128::MAX"
);
let price_raw = price_raw_u256.to::<i128>();
Ok(Price::from_raw(price_raw, FIXED_PRECISION))
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use alloy_primitives::{I256, U160};
use rstest::rstest;
use rust_decimal_macros::dec;
use super::*;
use crate::defi::stubs::{usdc, weth};
#[rstest]
fn test_swap_trade_info_calculator_calculations_buy(weth: Token, usdc: Token) {
let raw_data = RawSwapData::new(
I256::from_str("-466341596920355889").unwrap(),
I256::from_str("1656236893").unwrap(),
U160::from_str("4720799958938693700000000").unwrap(),
);
let calculator = SwapTradeInfoCalculator::new(&weth, &usdc, raw_data);
let result = calculator.compute(None).unwrap();
assert!(!calculator.is_inverted);
assert_eq!(result.order_side, OrderSide::Buy);
assert_eq!(
result.quantity_base.as_decimal(),
dec!(0.466341596920355889)
);
assert_eq!(result.quantity_quote.as_decimal(), dec!(1656.236893));
assert_eq!(result.spot_price.as_decimal(), dec!(3550.3570265047994091));
assert_eq!(
result.execution_price.as_decimal(),
dec!(3551.5529902061477063)
);
}
#[rstest]
fn test_swap_trade_info_calculator_calculations_sell(weth: Token, usdc: Token) {
let raw_data = RawSwapData::new(
I256::from_str("193450074461093702").unwrap(),
I256::from_str("-691892530").unwrap(),
U160::from_str("4739235524363817533004858").unwrap(),
);
let calculator = SwapTradeInfoCalculator::new(&weth, &usdc, raw_data);
let result = calculator.compute(None).unwrap();
assert_eq!(result.order_side, OrderSide::Sell);
assert_eq!(
result.quantity_base.as_decimal(),
dec!(0.193450074461093702)
);
assert_eq!(result.quantity_quote.as_decimal(), dec!(691.89253));
assert_eq!(result.spot_price.as_decimal(), dec!(3578.1407251651610105));
assert_eq!(
result.execution_price.as_decimal(),
dec!(3576.5947980503469024)
);
}
}