use crate::contracts::{IUniswapV2Pair, IV2PairUint256};
use crate::pool::base::{EventApplicable, PoolInterface, PoolTypeTrait, TopicList};
use crate::pool::PoolType;
use alloy::sol_types::SolEvent;
use alloy::{
primitives::{Address, FixedBytes, U256},
rpc::types::Log,
};
use anyhow::{anyhow, Result};
use log::{debug, trace};
use serde::{Deserialize, Serialize};
use std::any::Any;
use std::fmt;
const FEE_DENOMINATOR: u128 = 1000000;
const EXP18: u128 = 1_000_000_000_000_000_000;
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum V2PoolType {
UniswapV2,
Stable,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UniswapV2Pool {
pub pool_type: V2PoolType,
pub address: Address,
pub token0: Address,
pub token1: Address,
pub decimals0: u128,
pub decimals1: u128,
pub reserve0: U256,
pub reserve1: U256,
pub fee: U256,
pub last_updated: u64,
pub created_at: u64,
}
impl UniswapV2Pool {
pub fn new(
pool_type: V2PoolType,
address: Address,
token0: Address,
token1: Address,
decimals_0: u8,
decimals_1: u8,
reserve0: U256,
reserve1: U256,
fee: U256,
) -> Self {
let current_time = chrono::Utc::now().timestamp() as u64;
Self {
pool_type,
address,
token0,
token1,
decimals0: 10_u128.pow(decimals_0 as u32),
decimals1: 10_u128.pow(decimals_1 as u32),
reserve0,
reserve1,
fee,
last_updated: current_time,
created_at: current_time,
}
}
pub fn update_reserves(&mut self, reserve0: U256, reserve1: U256) -> Result<()> {
self.reserve0 = reserve0;
self.reserve1 = reserve1;
self.last_updated = chrono::Utc::now().timestamp() as u64;
Ok(())
}
pub fn constant_product(&self) -> U256 {
self.reserve0 * self.reserve1
}
pub fn exp18() -> U256 {
U256::from(EXP18)
}
pub fn is_valid(&self) -> bool {
!self.reserve0.is_zero() && !self.reserve1.is_zero()
}
fn calculate_output_0_to_1(&self, amount_in: U256) -> Result<U256> {
if amount_in.is_zero() {
return Err(anyhow!("Input amount cannot be zero"));
}
if !self.is_valid() {
return Err(anyhow!("Pool reserves are invalid"));
}
match self.pool_type {
V2PoolType::UniswapV2 => {
let amount_in_with_fee: alloy::primitives::Uint<256, 4> =
amount_in.saturating_mul(U256::from(U256::from(FEE_DENOMINATOR) - (self.fee)));
let numerator = amount_in_with_fee * self.reserve1;
let denominator = self.reserve0 * U256::from(FEE_DENOMINATOR) + amount_in_with_fee;
let output = numerator / denominator;
if output >= self.reserve1 {
return Err(anyhow!("Insufficient liquidity for swap"));
}
Ok(output)
}
V2PoolType::Stable => self.calculate_stable_output(amount_in, true),
}
}
fn calculate_output_1_to_0(&self, amount_in: U256) -> Result<U256> {
if amount_in.is_zero() {
return Err(anyhow!("Input amount cannot be zero"));
}
if !self.is_valid() {
return Err(anyhow!("Pool reserves are invalid"));
}
match self.pool_type {
V2PoolType::UniswapV2 => {
let amount_in_with_fee =
amount_in.saturating_mul(U256::from(U256::from(FEE_DENOMINATOR) - (self.fee)));
let numerator = amount_in_with_fee * self.reserve0;
let denominator = self.reserve1 * U256::from(FEE_DENOMINATOR) + amount_in_with_fee;
let output = numerator / denominator;
if output >= self.reserve0 {
return Err(anyhow!("Insufficient liquidity for swap"));
}
Ok(output)
}
V2PoolType::Stable => self.calculate_stable_output(amount_in, false),
}
}
fn calculate_stable_output(&self, amount_in: U256, zero_for_one: bool) -> Result<U256> {
let amount_in_with_fee: alloy::primitives::Uint<256, 4> = amount_in
.saturating_mul(U256::from(U256::from(FEE_DENOMINATOR) - (self.fee)))
.checked_div(U256::from(FEE_DENOMINATOR))
.unwrap();
let exp18 = Self::exp18();
let xy = self.k(self.reserve0, self.reserve1);
let reserve0 = (self.reserve0 * exp18) / U256::from(self.decimals0);
let reserve1 = (self.reserve1 * exp18) / U256::from(self.decimals1);
let (reserve_a, reserve_b, decimals_in, decimals_out) = if zero_for_one {
(reserve0, reserve1, self.decimals0, self.decimals1)
} else {
(reserve1, reserve0, self.decimals1, self.decimals0)
};
let amount_in_parsed = (amount_in_with_fee * exp18) / U256::from(decimals_in);
let y = reserve_b - self.get_y(amount_in_parsed + reserve_a, xy, reserve_b)?;
let output = (y * U256::from(decimals_out)) / exp18;
Ok(output)
}
fn k(&self, x: U256, y: U256) -> U256 {
if self.pool_type == V2PoolType::Stable {
let exp18 = Self::exp18();
let _x = (x * exp18) / U256::from(self.decimals0);
let _y = (y * exp18) / U256::from(self.decimals1);
let _a = (_x * _y) / exp18;
let _b = (_x * _x) / exp18 + (_y * _y) / exp18;
return (_a * _b) / exp18; } else {
return x * y;
}
}
pub fn f(x0: U256, y: U256) -> U256 {
let exp18 = Self::exp18();
let _a = (x0 * y) / exp18;
let _b = (x0 * x0) / exp18 + (y * y) / exp18;
return (_a * _b) / exp18;
}
fn d(x0: U256, y: U256) -> U256 {
let exp18 = Self::exp18();
return (U256::from(3) * x0 * ((y * y) / exp18)) / exp18
+ ((((x0 * x0) / exp18) * x0) / exp18);
}
fn get_y(&self, x0: U256, xy: U256, mut y: U256) -> Result<U256> {
let exp18 = Self::exp18();
for _ in 0..255 {
let k = Self::f(x0, y);
if k < xy {
let mut dy = ((xy - k) * exp18) / Self::d(x0, y);
if dy.is_zero() {
if k == xy {
return Ok(y);
}
if self.k(x0, y + U256::ONE) > xy {
return Ok(y + U256::ONE);
}
dy = U256::ONE;
}
y = y + dy;
} else {
let mut dy = ((k - xy) * exp18) / Self::d(x0, y);
if dy.is_zero() {
if k == xy || Self::f(x0, y - U256::ONE) < xy {
return Ok(y);
}
dy = U256::ONE;
}
y = y - dy;
}
}
return Err(anyhow!("!y"));
}
fn calculate_input_0_to_1(&self, amount_out: U256) -> Result<U256> {
if amount_out.is_zero() {
return Err(anyhow!("Output amount cannot be zero"));
}
if !self.is_valid() {
return Err(anyhow!("Pool reserves are invalid"));
}
if amount_out >= self.reserve1 {
return Err(anyhow!("Insufficient liquidity for swap"));
}
let numerator = self.reserve0 * amount_out * U256::from(FEE_DENOMINATOR);
let denominator = (self.reserve1 - amount_out) * (U256::from(FEE_DENOMINATOR) - self.fee);
let input = (numerator / denominator) + U256::from(1);
Ok(input)
}
fn calculate_input_1_to_0(&self, amount_out: U256) -> Result<U256> {
if amount_out.is_zero() {
return Err(anyhow!("Output amount cannot be zero"));
}
if !self.is_valid() {
return Err(anyhow!("Pool reserves are invalid"));
}
if amount_out >= self.reserve0 {
return Err(anyhow!("Insufficient liquidity for swap"));
}
let numerator = self.reserve1 * amount_out * U256::from(FEE_DENOMINATOR);
let denominator = (self.reserve0 - amount_out) * (U256::from(FEE_DENOMINATOR) - self.fee);
let input = (numerator / denominator) + U256::from(1);
Ok(input)
}
}
impl PoolInterface for UniswapV2Pool {
fn calculate_output(&self, token_in: &Address, amount_in: U256) -> Result<U256> {
if token_in == &self.token0 {
self.calculate_output_0_to_1(amount_in)
} else if token_in == &self.token1 {
self.calculate_output_1_to_0(amount_in)
} else {
Err(anyhow!("Token not in pool"))
}
}
fn calculate_input(&self, token_out: &Address, amount_out: U256) -> Result<U256> {
if token_out == &self.token0 {
self.calculate_input_1_to_0(amount_out)
} else if token_out == &self.token1 {
self.calculate_input_0_to_1(amount_out)
} else {
Err(anyhow!("Token not in pool"))
}
}
fn apply_swap(&mut self, token_in: &Address, amount_in: U256, amount_out: U256) -> Result<()> {
if token_in == &self.token0 {
if amount_out >= self.reserve1 {
return Err(anyhow!("Insufficient liquidity for swap"));
}
self.reserve0 += amount_in;
self.reserve1 -= amount_out;
} else if token_in == &self.token1 {
if amount_out >= self.reserve0 {
return Err(anyhow!("Insufficient liquidity for swap"));
}
self.reserve1 += amount_in;
self.reserve0 -= amount_out;
} else {
return Err(anyhow!("Token not in pool"));
}
self.last_updated = chrono::Utc::now().timestamp() as u64;
Ok(())
}
fn address(&self) -> Address {
self.address
}
fn tokens(&self) -> (Address, Address) {
(self.token0, self.token1)
}
fn fee(&self) -> f64 {
self.fee.to::<u128>() as f64 / FEE_DENOMINATOR as f64
}
fn fee_raw(&self) -> u64 {
self.fee.to::<u128>() as u64
}
fn id(&self) -> String {
format!("v2-{}-{}-{}", self.address, self.token0, self.token1)
}
fn log_summary(&self) -> String {
format!(
"V2 Pool {} - {} <> {} (reserves: {}, {})",
self.address, self.token0, self.token1, self.reserve0, self.reserve1
)
}
fn contains_token(&self, token: &Address) -> bool {
*token == self.token0 || *token == self.token1
}
fn clone_box(&self) -> Box<dyn PoolInterface + Send + Sync> {
Box::new(self.clone())
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
impl EventApplicable for UniswapV2Pool {
fn apply_log(&mut self, log: &Log) -> Result<()> {
match log.topic0() {
Some(&IUniswapV2Pair::Sync::SIGNATURE_HASH) => {
let sync_data: IUniswapV2Pair::Sync = log.log_decode()?.inner.data;
debug!(
"Applying V2Sync event to pool {}: reserve0={}, reserve1={}",
self.address, sync_data.reserve0, sync_data.reserve1
);
self.update_reserves(
U256::from(sync_data.reserve0),
U256::from(sync_data.reserve1),
)?;
Ok(())
}
Some(&IV2PairUint256::Sync::SIGNATURE_HASH) => {
let sync_data: IV2PairUint256::Sync = log.log_decode()?.inner.data;
debug!(
"Applying V2Sync event to pool {}: reserve0={}, reserve1={}",
self.address, sync_data.reserve0, sync_data.reserve1
);
self.update_reserves(sync_data.reserve0, sync_data.reserve1)?;
Ok(())
}
Some(&IUniswapV2Pair::Swap::SIGNATURE_HASH) => Ok(()),
_ => {
trace!("Ignoring unknown event for V2 pool");
Ok(())
}
}
}
}
impl TopicList for UniswapV2Pool {
fn topics() -> Vec<FixedBytes<32>> {
vec![
IUniswapV2Pair::Swap::SIGNATURE_HASH,
IUniswapV2Pair::Sync::SIGNATURE_HASH,
IV2PairUint256::Sync::SIGNATURE_HASH,
]
}
fn profitable_topics() -> Vec<FixedBytes<32>> {
vec![IUniswapV2Pair::Swap::SIGNATURE_HASH]
}
}
impl fmt::Display for UniswapV2Pool {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"V2 Pool {} - {} <> {} (reserves: {}, {})",
self.address, self.token0, self.token1, self.reserve0, self.reserve1
)
}
}
impl PoolTypeTrait for UniswapV2Pool {
fn pool_type(&self) -> PoolType {
PoolType::UniswapV2
}
}