kabu_defi_pools/virtual_impl/
uniswapv3.rs1use crate::UniswapV3Pool;
2use crate::db_reader::UniswapV3DbReader;
3use crate::virtual_impl::tick_provider::TickProviderEVMDB;
4use alloy::primitives::{Address, I256, U256};
5use eyre::eyre;
6use kabu_defi_uniswap_v3_math::tick_math::{MAX_SQRT_RATIO, MAX_TICK, MIN_SQRT_RATIO, MIN_TICK};
7use kabu_evm_db::KabuDBError;
8use kabu_types_market::Pool;
9use revm::DatabaseRef;
10
11pub struct UniswapV3PoolVirtual;
12
13pub const U256_1: U256 = U256::from_limbs([1, 0, 0, 0]);
47
48pub struct CurrentState {
53 amount_specified_remaining: I256,
54 amount_calculated: I256,
55 sqrt_price_x_96: U256,
56 tick: i32,
57 liquidity: u128,
58}
59
60#[derive(Default)]
61pub struct StepComputations {
62 pub sqrt_price_start_x_96: U256,
63 pub tick_next: i32,
64 pub initialized: bool,
65 pub sqrt_price_next_x96: U256,
66 pub amount_in: U256,
67 pub amount_out: U256,
68 pub fee_amount: U256,
69}
70
71#[allow(dead_code)]
72pub struct Tick {
73 pub liquidity_gross: u128,
74 pub liquidity_net: i128,
75 pub fee_growth_outside_0_x_128: U256,
76 pub fee_growth_outside_1_x_128: U256,
77 pub tick_cumulative_outside: U256,
78 pub seconds_per_liquidity_outside_x_128: U256,
79 pub seconds_outside: u32,
80 pub initialized: bool,
81}
82
83impl UniswapV3PoolVirtual {
84 pub fn simulate_swap_in_amount_provider<DB: DatabaseRef<Error = KabuDBError> + ?Sized>(
85 db: &DB,
86 pool: &UniswapV3Pool,
87 token_in: &Address,
88 amount_in: U256,
89 ) -> eyre::Result<U256> {
90 if amount_in.is_zero() {
91 return Ok(U256::ZERO);
92 }
93
94 let zero_for_one = *token_in == pool.get_tokens()[0];
95
96 let sqrt_price_limit_x_96 = if zero_for_one { MIN_SQRT_RATIO + U256_1 } else { MAX_SQRT_RATIO - U256_1 };
98
99 let pool_address = match pool.get_address() {
100 kabu_types_market::PoolId::Address(addr) => addr,
101 kabu_types_market::PoolId::B256(_) => return Err(eyre!("B256 pool ID variant not supported")),
102 };
103
104 let slot0 = UniswapV3DbReader::slot0(&db, pool_address)?;
105 let liquidity = UniswapV3DbReader::liquidity(&db, pool_address)?;
106 let tick_spacing = pool.tick_spacing();
107 let fee = pool.fee;
108
109 let mut current_state = CurrentState {
111 sqrt_price_x_96: slot0.sqrtPriceX96.to(), amount_calculated: I256::ZERO, amount_specified_remaining: I256::from_raw(amount_in), tick: slot0.tick.as_i32(), liquidity, };
117
118 let tick_provider = TickProviderEVMDB::new(db, pool_address);
119
120 while current_state.amount_specified_remaining != I256::ZERO && current_state.sqrt_price_x_96 != sqrt_price_limit_x_96 {
121 let mut step = StepComputations {
123 sqrt_price_start_x_96: current_state.sqrt_price_x_96,
125 ..Default::default()
126 };
127
128 (step.tick_next, step.initialized) = kabu_defi_uniswap_v3_math::tick_bitmap::next_initialized_tick_within_one_word(
130 &tick_provider,
131 current_state.tick,
132 tick_spacing as i32,
133 zero_for_one,
134 )?;
135
136 step.tick_next = step.tick_next.clamp(MIN_TICK, MAX_TICK);
139
140 step.sqrt_price_next_x96 = kabu_defi_uniswap_v3_math::tick_math::get_sqrt_ratio_at_tick(step.tick_next)?;
142
143 let swap_target_sqrt_ratio = if zero_for_one {
145 if step.sqrt_price_next_x96 < sqrt_price_limit_x_96 { sqrt_price_limit_x_96 } else { step.sqrt_price_next_x96 }
146 } else if step.sqrt_price_next_x96 > sqrt_price_limit_x_96 {
147 sqrt_price_limit_x_96
148 } else {
149 step.sqrt_price_next_x96
150 };
151
152 (current_state.sqrt_price_x_96, step.amount_in, step.amount_out, step.fee_amount) =
154 kabu_defi_uniswap_v3_math::swap_math::compute_swap_step(
155 current_state.sqrt_price_x_96,
156 swap_target_sqrt_ratio,
157 current_state.liquidity,
158 current_state.amount_specified_remaining,
159 fee,
160 )?;
161
162 current_state.amount_specified_remaining = current_state
164 .amount_specified_remaining
165 .overflowing_sub(I256::from_raw(step.amount_in.overflowing_add(step.fee_amount).0))
166 .0;
167
168 current_state.amount_calculated -= I256::from_raw(step.amount_out);
169
170 if current_state.sqrt_price_x_96 == step.sqrt_price_next_x96 {
172 if step.initialized {
173 let mut liquidity_net: i128 =
174 UniswapV3DbReader::ticks_liquidity_net(&db, pool_address, step.tick_next).unwrap_or_default();
175
176 if zero_for_one {
178 liquidity_net = -liquidity_net;
179 }
180
181 current_state.liquidity = if liquidity_net < 0 {
182 if current_state.liquidity < (-liquidity_net as u128) {
183 return Err(eyre!("LIQUIDITY_UNDERFLOW"));
184 } else {
185 current_state.liquidity - (-liquidity_net as u128)
186 }
187 } else {
188 current_state.liquidity + (liquidity_net as u128)
189 };
190 }
191 current_state.tick = if zero_for_one { step.tick_next.wrapping_sub(1) } else { step.tick_next }
193 } else if current_state.sqrt_price_x_96 != step.sqrt_price_start_x_96 {
196 current_state.tick = kabu_defi_uniswap_v3_math::tick_math::get_tick_at_sqrt_ratio(current_state.sqrt_price_x_96)?;
197 }
198 }
199
200 if current_state.amount_specified_remaining.is_zero() {
201 let amount_out = (-current_state.amount_calculated).into_raw();
202 tracing::trace!("AmountOut : {amount_out}");
203 Ok(amount_out)
204 } else {
205 Err(eyre!("NOT_ENOUGH_LIQUIDITY"))
206 }
207 }
208
209 pub fn simulate_swap_out_amount_provided<DB: DatabaseRef<Error = KabuDBError> + ?Sized>(
210 db: &DB,
211 pool: &UniswapV3Pool,
212 token_in: &Address,
213 amount_out: U256,
214 ) -> eyre::Result<U256> {
215 if amount_out.is_zero() {
216 return Ok(U256::ZERO);
217 }
218
219 let zero_for_one = *token_in == pool.get_tokens()[0];
220
221 let sqrt_price_limit_x_96 = if zero_for_one { MIN_SQRT_RATIO + U256_1 } else { MAX_SQRT_RATIO - U256_1 };
223
224 let pool_address = match pool.get_address() {
225 kabu_types_market::PoolId::Address(addr) => addr,
226 kabu_types_market::PoolId::B256(_) => return Err(eyre!("B256 pool ID variant not supported")),
227 };
228
229 let slot0 = UniswapV3DbReader::slot0(&db, pool_address)?;
230 let liquidity = UniswapV3DbReader::liquidity(&db, pool_address)?;
231 let tick_spacing = pool.tick_spacing();
232 let fee = pool.fee;
233
234 let mut current_state = CurrentState {
236 sqrt_price_x_96: slot0.sqrtPriceX96.to(), amount_calculated: I256::ZERO, amount_specified_remaining: -I256::from_raw(amount_out), tick: slot0.tick.as_i32(), liquidity, };
242
243 while current_state.amount_specified_remaining != I256::ZERO && current_state.sqrt_price_x_96 != sqrt_price_limit_x_96 {
244 let mut step = StepComputations {
246 sqrt_price_start_x_96: current_state.sqrt_price_x_96,
248 ..Default::default()
249 };
250
251 let tick_provider = TickProviderEVMDB::new(&db, pool_address);
252
253 (step.tick_next, step.initialized) = kabu_defi_uniswap_v3_math::tick_bitmap::next_initialized_tick_within_one_word(
255 &tick_provider,
256 current_state.tick,
257 tick_spacing as i32,
258 zero_for_one,
259 )?;
260
261 step.tick_next = step.tick_next.clamp(MIN_TICK, MAX_TICK);
264
265 step.sqrt_price_next_x96 = kabu_defi_uniswap_v3_math::tick_math::get_sqrt_ratio_at_tick(step.tick_next)?;
267
268 let swap_target_sqrt_ratio = if zero_for_one {
270 if step.sqrt_price_next_x96 < sqrt_price_limit_x_96 { sqrt_price_limit_x_96 } else { step.sqrt_price_next_x96 }
271 } else if step.sqrt_price_next_x96 > sqrt_price_limit_x_96 {
272 sqrt_price_limit_x_96
273 } else {
274 step.sqrt_price_next_x96
275 };
276
277 (current_state.sqrt_price_x_96, step.amount_in, step.amount_out, step.fee_amount) =
279 kabu_defi_uniswap_v3_math::swap_math::compute_swap_step(
280 current_state.sqrt_price_x_96,
281 swap_target_sqrt_ratio,
282 current_state.liquidity,
283 current_state.amount_specified_remaining,
284 fee,
285 )?;
286
287 current_state.amount_specified_remaining =
289 current_state.amount_specified_remaining.overflowing_add(I256::from_raw(step.amount_out)).0;
290
291 current_state.amount_calculated =
292 current_state.amount_calculated.overflowing_add(I256::from_raw(step.amount_in.overflowing_add(step.fee_amount).0)).0;
293
294 if current_state.sqrt_price_x_96 == step.sqrt_price_next_x96 {
296 if step.initialized {
297 let mut liquidity_net: i128 =
298 UniswapV3DbReader::ticks_liquidity_net(&db, pool_address, step.tick_next).unwrap_or_default();
299
300 if zero_for_one {
302 liquidity_net = -liquidity_net;
303 }
304
305 current_state.liquidity = if liquidity_net < 0 {
306 if current_state.liquidity < (-liquidity_net as u128) {
307 return Err(eyre!("LIQUIDITY_UNDERFLOW"));
308 } else {
309 current_state.liquidity - (-liquidity_net as u128)
310 }
311 } else {
312 current_state.liquidity + (liquidity_net as u128)
313 };
314 }
315 current_state.tick = if zero_for_one { step.tick_next.wrapping_sub(1) } else { step.tick_next }
317 } else if current_state.sqrt_price_x_96 != step.sqrt_price_start_x_96 {
320 current_state.tick = kabu_defi_uniswap_v3_math::tick_math::get_tick_at_sqrt_ratio(current_state.sqrt_price_x_96)?;
321 }
322 }
323
324 if current_state.amount_specified_remaining.is_zero() {
325 let amount_in = current_state.amount_calculated.into_raw();
326
327 tracing::trace!("Amount In : {amount_in}");
328
329 Ok(amount_in)
330 } else {
331 Err(eyre!("NOT_ENOUGH_LIQUIDITY"))
332 }
333 }
334}
335
336#[cfg(test)]
337mod test {
338 use alloy::primitives::U256;
339 use kabu_defi_uniswap_v3_math::full_math::mul_div_rounding_up;
340
341 #[test]
342 fn test_mul_rounding_up() {
343 let amount = U256::from_limbs([1230267133767, 0, 0, 0]);
344 let ret = mul_div_rounding_up(amount, U256::from(500), U256::from(1e6)).unwrap();
345 assert_eq!(ret, U256::from(615133567u128));
346 }
347}