Skip to main content

evm_dex_pool/v2/
pool.rs

1use crate::contracts::{IUniswapV2Pair, IV2PairUint256};
2use crate::pool::base::{EventApplicable, PoolInterface, PoolTypeTrait, TopicList};
3use crate::pool::PoolType;
4use alloy::sol_types::SolEvent;
5use alloy::{
6    primitives::{Address, FixedBytes, U256},
7    rpc::types::Log,
8};
9use anyhow::{anyhow, Result};
10use log::{debug, trace};
11use serde::{Deserialize, Serialize};
12use std::any::Any;
13use std::fmt;
14
15const FEE_DENOMINATOR: u128 = 1000000;
16const EXP18: u128 = 1_000_000_000_000_000_000;
17
18/// Enum representing the type of V2 pool
19#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
20pub enum V2PoolType {
21    UniswapV2,
22    Stable,
23}
24
25/// UniswapV2 Pool implementation
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct UniswapV2Pool {
28    /// Pool type
29    pub pool_type: V2PoolType,
30    /// Pool address
31    pub address: Address,
32    /// First token address in the pool
33    pub token0: Address,
34    /// Second token address in the pool
35    pub token1: Address,
36    /// Decimals of token0
37    pub decimals0: u128,
38    /// Decimals of token1
39    pub decimals1: u128,
40    /// Reserve of token0
41    pub reserve0: U256,
42    /// Reserve of token1
43    pub reserve1: U256,
44    /// Pool fee (e.g., 0.003 for 0.3%)
45    pub fee: U256,
46    /// Last update timestamp
47    pub last_updated: u64,
48    /// Creation timestamp or block
49    pub created_at: u64,
50}
51
52impl UniswapV2Pool {
53    /// Create a new V2 pool
54    pub fn new(
55        pool_type: V2PoolType,
56        address: Address,
57        token0: Address,
58        token1: Address,
59        decimals_0: u8,
60        decimals_1: u8,
61        reserve0: U256,
62        reserve1: U256,
63        fee: U256,
64    ) -> Self {
65        let current_time = chrono::Utc::now().timestamp() as u64;
66        Self {
67            pool_type,
68            address,
69            token0,
70            token1,
71            decimals0: 10_u128.pow(decimals_0 as u32),
72            decimals1: 10_u128.pow(decimals_1 as u32),
73            reserve0,
74            reserve1,
75            fee,
76            last_updated: current_time,
77            created_at: current_time,
78        }
79    }
80
81    /// Update pool reserves
82    pub fn update_reserves(&mut self, reserve0: U256, reserve1: U256) -> Result<()> {
83        self.reserve0 = reserve0;
84        self.reserve1 = reserve1;
85        self.last_updated = chrono::Utc::now().timestamp() as u64;
86        Ok(())
87    }
88
89    /// Calculate the constant product k = x * y
90    pub fn constant_product(&self) -> U256 {
91        self.reserve0 * self.reserve1
92    }
93
94    /// Get the exp18 value
95    pub fn exp18() -> U256 {
96        U256::from(EXP18)
97    }
98
99    /// Check if the pool is valid (has non-zero reserves)
100    pub fn is_valid(&self) -> bool {
101        !self.reserve0.is_zero() && !self.reserve1.is_zero()
102    }
103
104    /// Calculate the output amount for a swap (token0 -> token1)
105    fn calculate_output_0_to_1(&self, amount_in: U256) -> Result<U256> {
106        if amount_in.is_zero() {
107            return Err(anyhow!("Input amount cannot be zero"));
108        }
109
110        if !self.is_valid() {
111            return Err(anyhow!("Pool reserves are invalid"));
112        }
113        match self.pool_type {
114            V2PoolType::UniswapV2 => {
115                let amount_in_with_fee: alloy::primitives::Uint<256, 4> =
116                    amount_in.saturating_mul(U256::from(U256::from(FEE_DENOMINATOR) - (self.fee)));
117                let numerator = amount_in_with_fee * self.reserve1;
118                let denominator = self.reserve0 * U256::from(FEE_DENOMINATOR) + amount_in_with_fee;
119                let output = numerator / denominator;
120                if output >= self.reserve1 {
121                    return Err(anyhow!("Insufficient liquidity for swap"));
122                }
123                Ok(output)
124            }
125            V2PoolType::Stable => self.calculate_stable_output(amount_in, true),
126        }
127    }
128
129    /// Calculate the output amount for a swap (token1 -> token0)
130    fn calculate_output_1_to_0(&self, amount_in: U256) -> Result<U256> {
131        if amount_in.is_zero() {
132            return Err(anyhow!("Input amount cannot be zero"));
133        }
134
135        if !self.is_valid() {
136            return Err(anyhow!("Pool reserves are invalid"));
137        }
138
139        match self.pool_type {
140            V2PoolType::UniswapV2 => {
141                let amount_in_with_fee =
142                    amount_in.saturating_mul(U256::from(U256::from(FEE_DENOMINATOR) - (self.fee)));
143                let numerator = amount_in_with_fee * self.reserve0;
144                let denominator = self.reserve1 * U256::from(FEE_DENOMINATOR) + amount_in_with_fee;
145                let output = numerator / denominator;
146                if output >= self.reserve0 {
147                    return Err(anyhow!("Insufficient liquidity for swap"));
148                }
149                Ok(output)
150            }
151            V2PoolType::Stable => self.calculate_stable_output(amount_in, false),
152        }
153    }
154
155    /// Unified stable swap output calculation (R7 refactoring)
156    fn calculate_stable_output(&self, amount_in: U256, zero_for_one: bool) -> Result<U256> {
157        let amount_in_with_fee: alloy::primitives::Uint<256, 4> = amount_in
158            .saturating_mul(U256::from(U256::from(FEE_DENOMINATOR) - (self.fee)))
159            .checked_div(U256::from(FEE_DENOMINATOR))
160            .unwrap();
161        let exp18 = Self::exp18();
162        let xy = self.k(self.reserve0, self.reserve1);
163        let reserve0 = (self.reserve0 * exp18) / U256::from(self.decimals0);
164        let reserve1 = (self.reserve1 * exp18) / U256::from(self.decimals1);
165
166        let (reserve_a, reserve_b, decimals_in, decimals_out) = if zero_for_one {
167            (reserve0, reserve1, self.decimals0, self.decimals1)
168        } else {
169            (reserve1, reserve0, self.decimals1, self.decimals0)
170        };
171
172        let amount_in_parsed = (amount_in_with_fee * exp18) / U256::from(decimals_in);
173        let y = reserve_b - self.get_y(amount_in_parsed + reserve_a, xy, reserve_b)?;
174        let output = (y * U256::from(decimals_out)) / exp18;
175
176        Ok(output)
177    }
178
179    fn k(&self, x: U256, y: U256) -> U256 {
180        if self.pool_type == V2PoolType::Stable {
181            let exp18 = Self::exp18();
182            let _x = (x * exp18) / U256::from(self.decimals0);
183            let _y = (y * exp18) / U256::from(self.decimals1);
184            let _a = (_x * _y) / exp18;
185            let _b = (_x * _x) / exp18 + (_y * _y) / exp18;
186            return (_a * _b) / exp18; // x3y+y3x >= k
187        } else {
188            return x * y;
189        }
190    }
191
192    pub fn f(x0: U256, y: U256) -> U256 {
193        let exp18 = Self::exp18();
194        let _a = (x0 * y) / exp18;
195        let _b = (x0 * x0) / exp18 + (y * y) / exp18;
196        return (_a * _b) / exp18;
197    }
198
199    fn d(x0: U256, y: U256) -> U256 {
200        let exp18 = Self::exp18();
201        return (U256::from(3) * x0 * ((y * y) / exp18)) / exp18
202            + ((((x0 * x0) / exp18) * x0) / exp18);
203    }
204
205    fn get_y(&self, x0: U256, xy: U256, mut y: U256) -> Result<U256> {
206        let exp18 = Self::exp18();
207        for _ in 0..255 {
208            let k = Self::f(x0, y);
209            if k < xy {
210                let mut dy = ((xy - k) * exp18) / Self::d(x0, y);
211                if dy.is_zero() {
212                    if k == xy {
213                        return Ok(y);
214                    }
215                    if self.k(x0, y + U256::ONE) > xy {
216                        return Ok(y + U256::ONE);
217                    }
218                    dy = U256::ONE;
219                }
220                y = y + dy;
221            } else {
222                let mut dy = ((k - xy) * exp18) / Self::d(x0, y);
223                if dy.is_zero() {
224                    if k == xy || Self::f(x0, y - U256::ONE) < xy {
225                        return Ok(y);
226                    }
227                    dy = U256::ONE;
228                }
229                y = y - dy;
230            }
231        }
232        return Err(anyhow!("!y"));
233    }
234
235    fn calculate_input_0_to_1(&self, amount_out: U256) -> Result<U256> {
236        if amount_out.is_zero() {
237            return Err(anyhow!("Output amount cannot be zero"));
238        }
239        if !self.is_valid() {
240            return Err(anyhow!("Pool reserves are invalid"));
241        }
242        if amount_out >= self.reserve1 {
243            return Err(anyhow!("Insufficient liquidity for swap"));
244        }
245        let numerator = self.reserve0 * amount_out * U256::from(FEE_DENOMINATOR);
246        let denominator = (self.reserve1 - amount_out) * (U256::from(FEE_DENOMINATOR) - self.fee);
247        let input = (numerator / denominator) + U256::from(1);
248        Ok(input)
249    }
250
251    fn calculate_input_1_to_0(&self, amount_out: U256) -> Result<U256> {
252        if amount_out.is_zero() {
253            return Err(anyhow!("Output amount cannot be zero"));
254        }
255        if !self.is_valid() {
256            return Err(anyhow!("Pool reserves are invalid"));
257        }
258        if amount_out >= self.reserve0 {
259            return Err(anyhow!("Insufficient liquidity for swap"));
260        }
261        let numerator = self.reserve1 * amount_out * U256::from(FEE_DENOMINATOR);
262        let denominator = (self.reserve0 - amount_out) * (U256::from(FEE_DENOMINATOR) - self.fee);
263        let input = (numerator / denominator) + U256::from(1);
264        Ok(input)
265    }
266}
267
268impl PoolInterface for UniswapV2Pool {
269    fn calculate_output(&self, token_in: &Address, amount_in: U256) -> Result<U256> {
270        if token_in == &self.token0 {
271            self.calculate_output_0_to_1(amount_in)
272        } else if token_in == &self.token1 {
273            self.calculate_output_1_to_0(amount_in)
274        } else {
275            Err(anyhow!("Token not in pool"))
276        }
277    }
278
279    fn calculate_input(&self, token_out: &Address, amount_out: U256) -> Result<U256> {
280        if token_out == &self.token0 {
281            self.calculate_input_1_to_0(amount_out)
282        } else if token_out == &self.token1 {
283            self.calculate_input_0_to_1(amount_out)
284        } else {
285            Err(anyhow!("Token not in pool"))
286        }
287    }
288
289    fn apply_swap(&mut self, token_in: &Address, amount_in: U256, amount_out: U256) -> Result<()> {
290        if token_in == &self.token0 {
291            if amount_out >= self.reserve1 {
292                return Err(anyhow!("Insufficient liquidity for swap"));
293            }
294            self.reserve0 += amount_in;
295            self.reserve1 -= amount_out;
296        } else if token_in == &self.token1 {
297            if amount_out >= self.reserve0 {
298                return Err(anyhow!("Insufficient liquidity for swap"));
299            }
300            self.reserve1 += amount_in;
301            self.reserve0 -= amount_out;
302        } else {
303            return Err(anyhow!("Token not in pool"));
304        }
305        self.last_updated = chrono::Utc::now().timestamp() as u64;
306        Ok(())
307    }
308
309    fn address(&self) -> Address {
310        self.address
311    }
312
313    fn tokens(&self) -> (Address, Address) {
314        (self.token0, self.token1)
315    }
316
317    fn fee(&self) -> f64 {
318        self.fee.to::<u128>() as f64 / FEE_DENOMINATOR as f64
319    }
320
321    fn fee_raw(&self) -> u64 {
322        self.fee.to::<u128>() as u64
323    }
324
325    fn id(&self) -> String {
326        format!("v2-{}-{}-{}", self.address, self.token0, self.token1)
327    }
328
329    fn log_summary(&self) -> String {
330        format!(
331            "V2 Pool {} - {} <> {} (reserves: {}, {})",
332            self.address, self.token0, self.token1, self.reserve0, self.reserve1
333        )
334    }
335
336    fn contains_token(&self, token: &Address) -> bool {
337        *token == self.token0 || *token == self.token1
338    }
339
340    fn clone_box(&self) -> Box<dyn PoolInterface + Send + Sync> {
341        Box::new(self.clone())
342    }
343
344    fn as_any(&self) -> &dyn Any {
345        self
346    }
347
348    fn as_any_mut(&mut self) -> &mut dyn Any {
349        self
350    }
351}
352
353impl EventApplicable for UniswapV2Pool {
354    fn apply_log(&mut self, log: &Log) -> Result<()> {
355        match log.topic0() {
356            Some(&IUniswapV2Pair::Sync::SIGNATURE_HASH) => {
357                let sync_data: IUniswapV2Pair::Sync = log.log_decode()?.inner.data;
358                debug!(
359                    "Applying V2Sync event to pool {}: reserve0={}, reserve1={}",
360                    self.address, sync_data.reserve0, sync_data.reserve1
361                );
362                self.update_reserves(
363                    U256::from(sync_data.reserve0),
364                    U256::from(sync_data.reserve1),
365                )?;
366                Ok(())
367            }
368            Some(&IV2PairUint256::Sync::SIGNATURE_HASH) => {
369                let sync_data: IV2PairUint256::Sync = log.log_decode()?.inner.data;
370                debug!(
371                    "Applying V2Sync event to pool {}: reserve0={}, reserve1={}",
372                    self.address, sync_data.reserve0, sync_data.reserve1
373                );
374                self.update_reserves(sync_data.reserve0, sync_data.reserve1)?;
375                Ok(())
376            }
377            Some(&IUniswapV2Pair::Swap::SIGNATURE_HASH) => Ok(()),
378            _ => {
379                trace!("Ignoring unknown event for V2 pool");
380                Ok(())
381            }
382        }
383    }
384}
385
386impl TopicList for UniswapV2Pool {
387    fn topics() -> Vec<FixedBytes<32>> {
388        vec![
389            IUniswapV2Pair::Swap::SIGNATURE_HASH,
390            IUniswapV2Pair::Sync::SIGNATURE_HASH,
391            IV2PairUint256::Sync::SIGNATURE_HASH,
392        ]
393    }
394
395    fn profitable_topics() -> Vec<FixedBytes<32>> {
396        vec![IUniswapV2Pair::Swap::SIGNATURE_HASH]
397    }
398}
399
400impl fmt::Display for UniswapV2Pool {
401    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
402        write!(
403            f,
404            "V2 Pool {} - {} <> {} (reserves: {}, {})",
405            self.address, self.token0, self.token1, self.reserve0, self.reserve1
406        )
407    }
408}
409
410impl PoolTypeTrait for UniswapV2Pool {
411    fn pool_type(&self) -> PoolType {
412        PoolType::UniswapV2
413    }
414}