pool_sync/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, PancakeSwapEvents};
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        || *event_sig == PancakeSwapEvents::Swap::SIGNATURE_HASH
50    {
51        process_swap(pool, log, pool_type);
52    }
53}
54
55fn process_burn(pool: &mut UniswapV3Pool, log: Log, is_initial_sync: bool) {
56    let burn_event = DataEvents::Burn::decode_log(log.as_ref(), true).unwrap();
57    modify_position(
58        pool,
59        burn_event.tickLower.unchecked_into(),
60        burn_event.tickUpper.unchecked_into(),
61        -(burn_event.amount as i128),
62        is_initial_sync,
63    );
64}
65
66fn process_mint(pool: &mut UniswapV3Pool, log: Log, is_initial_sync: bool) {
67    let mint_event = DataEvents::Mint::decode_log(log.as_ref(), true).unwrap();
68    modify_position(
69        pool,
70        mint_event.tickLower.unchecked_into(),
71        mint_event.tickUpper.unchecked_into(),
72        mint_event.amount as i128,
73        is_initial_sync,
74    );
75}
76
77fn process_swap(pool: &mut UniswapV3Pool, log: Log, pool_type: PoolType) {
78    if pool_type == PoolType::PancakeSwapV3 {
79        let swap_event = PancakeSwapEvents::Swap::decode_log(log.as_ref(), true).unwrap();
80        pool.tick = swap_event.tick.as_i32();
81        pool.sqrt_price = U256::from(swap_event.sqrtPriceX96);
82        pool.liquidity = swap_event.liquidity;
83    } else {
84        let swap_event = DataEvents::Swap::decode_log(log.as_ref(), true).unwrap();
85        pool.tick = swap_event.tick.as_i32();
86        pool.sqrt_price = U256::from(swap_event.sqrtPriceX96);
87        pool.liquidity = swap_event.liquidity;
88    }
89}
90
91/// Modifies a positions liquidity in the pool.
92pub fn modify_position(
93    pool: &mut UniswapV3Pool,
94    tick_lower: i32,
95    tick_upper: i32,
96    liquidity_delta: i128,
97    is_initial_sync: bool,
98) {
99    //We are only using this function when a mint or burn event is emitted,
100    //therefore we do not need to checkTicks as that has happened before the event is emitted
101    update_position(pool, tick_lower, tick_upper, liquidity_delta);
102
103    // if it is the initial sync, ignore since liq is populated via contract
104    if liquidity_delta != 0 && !is_initial_sync {
105        //if the tick is between the tick lower and tick upper, update the liquidity between the ticks
106        if pool.tick >= tick_lower && pool.tick < tick_upper {
107            pool.liquidity = if liquidity_delta < 0 {
108                pool.liquidity - ((-liquidity_delta) as u128)
109            } else {
110                pool.liquidity + (liquidity_delta as u128)
111            }
112        }
113    }
114}
115
116pub fn update_position(
117    pool: &mut UniswapV3Pool,
118    tick_lower: i32,
119    tick_upper: i32,
120    liquidity_delta: i128,
121) {
122    let mut flipped_lower = false;
123    let mut flipped_upper = false;
124
125    if liquidity_delta != 0 {
126        flipped_lower = update_tick(pool, tick_lower, liquidity_delta, false);
127        flipped_upper = update_tick(pool, tick_upper, liquidity_delta, true);
128        if flipped_lower {
129            flip_tick(pool, tick_lower, pool.tick_spacing);
130        }
131        if flipped_upper {
132            flip_tick(pool, tick_upper, pool.tick_spacing);
133        }
134    }
135
136    if liquidity_delta < 0 {
137        if flipped_lower {
138            pool.ticks.remove(&tick_lower);
139        }
140
141        if flipped_upper {
142            pool.ticks.remove(&tick_upper);
143        }
144    }
145}
146
147pub fn update_tick(
148    pool: &mut UniswapV3Pool,
149    tick: i32,
150    liquidity_delta: i128,
151    upper: bool,
152) -> bool {
153    let info = match pool.ticks.get_mut(&tick) {
154        Some(info) => info,
155        None => {
156            pool.ticks.insert(tick, TickInfo::default());
157            pool.ticks
158                .get_mut(&tick)
159                .expect("Tick does not exist in ticks")
160        }
161    };
162
163    let liquidity_gross_before = info.liquidity_gross;
164
165    let liquidity_gross_after = if liquidity_delta < 0 {
166        liquidity_gross_before - ((-liquidity_delta) as u128)
167    } else {
168        liquidity_gross_before + (liquidity_delta as u128)
169    };
170
171    // we do not need to check if liqudity_gross_after > maxLiquidity because we are only calling update tick on a burn or mint log.
172    // this should already be validated when a log is
173    let flipped = (liquidity_gross_after == 0) != (liquidity_gross_before == 0);
174
175    if liquidity_gross_before == 0 {
176        info.initialized = true;
177    }
178
179    info.liquidity_gross = liquidity_gross_after;
180
181    info.liquidity_net = if upper {
182        info.liquidity_net - liquidity_delta
183    } else {
184        info.liquidity_net + liquidity_delta
185    };
186
187    flipped
188}
189
190pub fn flip_tick(pool: &mut UniswapV3Pool, tick: i32, tick_spacing: i32) {
191    let (word_pos, bit_pos) = uniswap_v3_math::tick_bitmap::position(tick / tick_spacing);
192    let mask = U256::from(1) << bit_pos;
193
194    if let Some(word) = pool.tick_bitmap.get_mut(&word_pos) {
195        *word ^= mask;
196    } else {
197        pool.tick_bitmap.insert(word_pos, mask);
198    }
199}
200
201impl From<&[DynSolValue]> for UniswapV3Pool {
202    fn from(data: &[DynSolValue]) -> Self {
203        Self {
204            address: data[0].as_address().unwrap(),
205            token0: data[1].as_address().unwrap(),
206            token0_decimals: data[2].as_uint().unwrap().0.to::<u8>(),
207            token1: data[3].as_address().unwrap(),
208            token1_decimals: data[4].as_uint().unwrap().0.to::<u8>(),
209            liquidity: data[5].as_uint().unwrap().0.to::<u128>(),
210            sqrt_price: data[6].as_uint().unwrap().0,
211            tick: data[7].as_int().unwrap().0.as_i32(),
212            tick_spacing: data[8].as_int().unwrap().0.as_i32(),
213            fee: data[9].as_uint().unwrap().0.to::<u32>(),
214            ..Default::default()
215        }
216    }
217}