pool_sync_mantle/pools/pool_structures/
v3_structure.rs

1use alloy::dyn_abi::DynSolValue;
2use alloy::primitives::{Address, U256};
3use alloy::rpc::types::Log;
4use alloy::sol_types::SolEvent;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8use crate::events::DataEvents;
9use crate::pools::PoolType;
10
11#[derive(Debug, Clone, Serialize, Deserialize, Default)]
12pub struct UniswapV3Pool {
13    pub address: Address,
14    pub token0: Address,
15    pub token1: Address,
16    pub token0_name: String,
17    pub token1_name: String,
18    pub token0_decimals: u8,
19    pub token1_decimals: u8,
20    pub liquidity: u128,
21    pub sqrt_price: U256,
22    pub fee: u32,
23    pub tick: i32,
24    pub tick_spacing: i32,
25    pub tick_bitmap: HashMap<i16, U256>,
26    pub ticks: HashMap<i32, TickInfo>,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize, Default)]
30pub struct TickInfo {
31    pub liquidity_net: i128,
32    pub initialized: bool,
33    pub liquidity_gross: u128,
34}
35
36pub fn process_tick_data(
37    pool: &mut UniswapV3Pool,
38    log: Log,
39    _pool_type: PoolType,
40    is_initial_sync: bool,
41) {
42    let event_sig = log.topic0().unwrap();
43
44    if *event_sig == DataEvents::Burn::SIGNATURE_HASH {
45        process_burn(pool, log, is_initial_sync);
46    } else if *event_sig == DataEvents::Mint::SIGNATURE_HASH {
47        process_mint(pool, log, is_initial_sync);
48    } else if *event_sig == DataEvents::Swap::SIGNATURE_HASH {
49        process_swap(pool, log);
50    }
51}
52
53fn process_burn(pool: &mut UniswapV3Pool, log: Log, is_initial_sync: bool) {
54    let burn_event = DataEvents::Burn::decode_log(log.as_ref(), true).unwrap();
55    modify_position(
56        pool,
57        burn_event.tickLower.unchecked_into(),
58        burn_event.tickUpper.unchecked_into(),
59        -(burn_event.amount as i128),
60        is_initial_sync,
61    );
62}
63
64fn process_mint(pool: &mut UniswapV3Pool, log: Log, is_initial_sync: bool) {
65    let mint_event = DataEvents::Mint::decode_log(log.as_ref(), true).unwrap();
66    modify_position(
67        pool,
68        mint_event.tickLower.unchecked_into(),
69        mint_event.tickUpper.unchecked_into(),
70        mint_event.amount as i128,
71        is_initial_sync,
72    );
73}
74
75fn process_swap(pool: &mut UniswapV3Pool, log: Log) {
76    let swap_event = DataEvents::Swap::decode_log(log.as_ref(), true).unwrap();
77    pool.tick = swap_event.tick.as_i32();
78    pool.sqrt_price = U256::from(swap_event.sqrtPriceX96);
79    pool.liquidity = swap_event.liquidity;
80}
81
82/// Modifies a positions liquidity in the pool.
83pub fn modify_position(
84    pool: &mut UniswapV3Pool,
85    tick_lower: i32,
86    tick_upper: i32,
87    liquidity_delta: i128,
88    is_initial_sync: bool,
89) {
90    //We are only using this function when a mint or burn event is emitted,
91    //therefore we do not need to checkTicks as that has happened before the event is emitted
92    update_position(pool, tick_lower, tick_upper, liquidity_delta);
93
94    // if it is the initial sync, ignore since liq is populated via contract
95    if liquidity_delta != 0 && !is_initial_sync {
96        //if the tick is between the tick lower and tick upper, update the liquidity between the ticks
97        if pool.tick >= tick_lower && pool.tick < tick_upper {
98            pool.liquidity = if liquidity_delta < 0 {
99                pool.liquidity - ((-liquidity_delta) as u128)
100            } else {
101                pool.liquidity + (liquidity_delta as u128)
102            }
103        }
104    }
105}
106
107pub fn update_position(
108    pool: &mut UniswapV3Pool,
109    tick_lower: i32,
110    tick_upper: i32,
111    liquidity_delta: i128,
112) {
113    let mut flipped_lower = false;
114    let mut flipped_upper = false;
115
116    if liquidity_delta != 0 {
117        flipped_lower = update_tick(pool, tick_lower, liquidity_delta, false);
118        flipped_upper = update_tick(pool, tick_upper, liquidity_delta, true);
119        if flipped_lower {
120            flip_tick(pool, tick_lower, pool.tick_spacing);
121        }
122        if flipped_upper {
123            flip_tick(pool, tick_upper, pool.tick_spacing);
124        }
125    }
126
127    if liquidity_delta < 0 {
128        if flipped_lower {
129            pool.ticks.remove(&tick_lower);
130        }
131
132        if flipped_upper {
133            pool.ticks.remove(&tick_upper);
134        }
135    }
136}
137
138pub fn update_tick(
139    pool: &mut UniswapV3Pool,
140    tick: i32,
141    liquidity_delta: i128,
142    upper: bool,
143) -> bool {
144    let info = match pool.ticks.get_mut(&tick) {
145        Some(info) => info,
146        None => {
147            pool.ticks.insert(tick, TickInfo::default());
148            pool.ticks
149                .get_mut(&tick)
150                .expect("Tick does not exist in ticks")
151        }
152    };
153
154    let liquidity_gross_before = info.liquidity_gross;
155
156    let liquidity_gross_after = if liquidity_delta < 0 {
157        liquidity_gross_before - ((-liquidity_delta) as u128)
158    } else {
159        liquidity_gross_before + (liquidity_delta as u128)
160    };
161
162    // we do not need to check if liqudity_gross_after > maxLiquidity because we are only calling update tick on a burn or mint log.
163    // this should already be validated when a log is
164    let flipped = (liquidity_gross_after == 0) != (liquidity_gross_before == 0);
165
166    if liquidity_gross_before == 0 {
167        info.initialized = true;
168    }
169
170    info.liquidity_gross = liquidity_gross_after;
171
172    info.liquidity_net = if upper {
173        info.liquidity_net - liquidity_delta
174    } else {
175        info.liquidity_net + liquidity_delta
176    };
177
178    flipped
179}
180
181pub fn flip_tick(pool: &mut UniswapV3Pool, tick: i32, tick_spacing: i32) {
182    let (word_pos, bit_pos) = uniswap_v3_math::tick_bitmap::position(tick / tick_spacing);
183    let mask = U256::from(1) << bit_pos;
184
185    if let Some(word) = pool.tick_bitmap.get_mut(&word_pos) {
186        *word ^= mask;
187    } else {
188        pool.tick_bitmap.insert(word_pos, mask);
189    }
190}
191
192impl From<&[DynSolValue]> for UniswapV3Pool {
193    fn from(data: &[DynSolValue]) -> Self {
194        // Safe conversion function for decimals with bounds checking
195        let safe_u8_conversion = |value: &DynSolValue| -> u8 {
196            let uint_val = value.as_uint().unwrap().0;
197            if uint_val > U256::from(255u32) {
198                18 // Default to 18 decimals if value is too large
199            } else {
200                uint_val.try_into().unwrap_or(18)
201            }
202        };
203
204        // Safe conversion function for fee with bounds checking
205        let safe_u32_conversion = |value: &DynSolValue| -> u32 {
206            let uint_val = value.as_uint().unwrap().0;
207            if uint_val > U256::from(u32::MAX) {
208                3000 // Default fee if value is too large
209            } else {
210                uint_val.try_into().unwrap_or(3000)
211            }
212        };
213
214        // Safe conversion function for tick values
215        let safe_i32_conversion = |value: &DynSolValue| -> i32 {
216            match value.as_int() {
217                Some(signed_val) => {
218                    // Try to convert to i32, use default if overflow
219                    signed_val.0.try_into().unwrap_or(0)
220                }
221                None => 0, // Default to 0 if conversion fails
222            }
223        };
224
225        Self {
226            address: data[0].as_address().unwrap(),
227            token0: data[1].as_address().unwrap(),
228            token0_decimals: safe_u8_conversion(&data[2]),
229            token1: data[3].as_address().unwrap(),
230            token1_decimals: safe_u8_conversion(&data[4]),
231            liquidity: data[5].as_uint().unwrap().0.try_into().unwrap_or(0),
232            sqrt_price: data[6].as_uint().unwrap().0,
233            tick: safe_i32_conversion(&data[7]),
234            tick_spacing: safe_i32_conversion(&data[8]),
235            fee: safe_u32_conversion(&data[9]),
236            ..Default::default()
237        }
238    }
239}