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#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
20pub enum V2PoolType {
21 UniswapV2,
22 Stable,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct UniswapV2Pool {
28 pub pool_type: V2PoolType,
30 pub address: Address,
32 pub token0: Address,
34 pub token1: Address,
36 pub decimals0: u128,
38 pub decimals1: u128,
40 pub reserve0: U256,
42 pub reserve1: U256,
44 pub fee: U256,
46 pub last_updated: u64,
48 pub created_at: u64,
50}
51
52impl UniswapV2Pool {
53 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 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 pub fn constant_product(&self) -> U256 {
91 self.reserve0 * self.reserve1
92 }
93
94 pub fn exp18() -> U256 {
96 U256::from(EXP18)
97 }
98
99 pub fn is_valid(&self) -> bool {
101 !self.reserve0.is_zero() && !self.reserve1.is_zero()
102 }
103
104 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 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 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; } 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}