pool_sync/pools/pool_structures/
v3_structure.rs1use 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
91pub 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 update_position(pool, tick_lower, tick_upper, liquidity_delta);
102
103 if liquidity_delta != 0 && !is_initial_sync {
105 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 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}