1use alloy_primitives::{U160, U256, U512};
17
18use super::full_math::FullMath;
19use crate::{
20 defi::tick_map::tick_math::get_sqrt_ratio_at_tick,
21 types::{PRICE_RAW_MAX, Price, fixed::FIXED_PRECISION},
22};
23
24#[must_use]
36pub fn encode_sqrt_ratio_x96(amount0: u128, amount1: u128) -> U160 {
37 let amount0_u256 = U256::from(amount0);
38 let amount1_u256 = U256::from(amount1);
39
40 assert!(!amount1_u256.is_zero(), "Division by zero");
41 if amount0_u256.is_zero() {
42 return U160::ZERO;
43 }
44
45 let q192 = U256::from(1u128) << 192;
51
52 if amount0_u256 > U256::MAX / q192 {
54 let sqrt_amount0 = FullMath::sqrt(amount0_u256);
57 let sqrt_amount1 = FullMath::sqrt(amount1_u256);
58
59 assert!(!sqrt_amount1.is_zero(), "Division by zero in sqrt");
60
61 let q96 = U256::from(1u128) << 96;
62
63 let result = FullMath::mul_div(sqrt_amount0, q96, sqrt_amount1).expect("mul_div overflow");
65
66 return if result > U256::from(U160::MAX) {
68 U160::MAX
69 } else {
70 U160::from(result)
71 };
72 }
73
74 let ratio_q192 = FullMath::mul_div(amount0_u256, q192, amount1_u256).expect("mul_div overflow");
76
77 let sqrt_result = FullMath::sqrt(ratio_q192);
79
80 if sqrt_result > U256::from(U160::MAX) {
82 U160::MAX
83 } else {
84 U160::from(sqrt_result)
85 }
86}
87
88fn get_next_sqrt_price_from_amount0_rounding_up(
90 sqrt_price_x96: U160,
91 liquidity: u128,
92 amount: U256,
93 add: bool,
94) -> U160 {
95 if amount.is_zero() {
96 return sqrt_price_x96;
97 }
98 let numerator = U256::from(liquidity) << 96;
99 let sqrt_price_x96 = U256::from(sqrt_price_x96);
100 let product = amount * sqrt_price_x96;
101
102 if add {
103 if product / amount == sqrt_price_x96 {
104 let denominator = numerator + product;
105 if denominator >= numerator {
106 let result = FullMath::mul_div_rounding_up(numerator, sqrt_price_x96, denominator)
108 .expect("mul_div_rounding_up failed");
109 return U160::from(result);
110 }
111 }
112
113 let fallback_denominator = (numerator / sqrt_price_x96) + amount;
115 let result = FullMath::div_rounding_up(numerator, fallback_denominator)
116 .expect("div_rounding_up failed");
117
118 assert!(result <= U256::from(U160::MAX), "Result overflows U160");
120 U160::from(result)
121 } else {
122 assert!(
124 (product / amount) == sqrt_price_x96 && numerator > product,
125 "Invalid conditions for amount0 removal: overflow or underflow detected"
126 );
127
128 let denominator = numerator - product;
129 let result = FullMath::mul_div_rounding_up(numerator, sqrt_price_x96, denominator)
130 .expect("mul_div_rounding_up failed");
131 U160::from(result)
132 }
133}
134
135fn get_next_sqrt_price_from_amount1_rounding_down(
137 sqrt_price_x96: U160,
138 liquidity: u128,
139 amount: U256,
140 add: bool,
141) -> U160 {
142 if add {
145 let quotient = if amount <= U256::from(U160::MAX) {
146 (amount << 96) / U256::from(liquidity)
148 } else {
149 FullMath::mul_div(amount, U256::from(1u128) << 96, U256::from(liquidity))
151 .unwrap_or(U256::ZERO)
152 };
153
154 U160::from(U256::from(sqrt_price_x96) + quotient)
156 } else {
157 let quotient = if amount <= U256::from(U160::MAX) {
158 FullMath::div_rounding_up(amount << 96, U256::from(liquidity)).unwrap_or(U256::ZERO)
160 } else {
161 FullMath::mul_div_rounding_up(amount, U256::from(1u128) << 96, U256::from(liquidity))
163 .unwrap_or(U256::ZERO)
164 };
165
166 assert!(
168 U256::from(sqrt_price_x96) > quotient,
169 "sqrt_price_x96 must be greater than quotient"
170 );
171
172 U160::from(U256::from(sqrt_price_x96) - quotient)
174 }
175}
176
177#[must_use]
182pub fn get_next_sqrt_price_from_input(
183 sqrt_price_x96: U160,
184 liquidity: u128,
185 amount_in: U256,
186 zero_for_one: bool,
187) -> U160 {
188 assert!(
189 sqrt_price_x96 > U160::ZERO,
190 "sqrt_price_x96 must be greater than zero"
191 );
192 assert!(liquidity > 0, "Liquidity must be greater than zero");
193
194 if zero_for_one {
195 get_next_sqrt_price_from_amount0_rounding_up(sqrt_price_x96, liquidity, amount_in, true)
196 } else {
197 get_next_sqrt_price_from_amount1_rounding_down(sqrt_price_x96, liquidity, amount_in, true)
198 }
199}
200
201#[must_use]
206pub fn get_next_sqrt_price_from_output(
207 sqrt_price_x96: U160,
208 liquidity: u128,
209 amount_out: U256,
210 zero_for_one: bool,
211) -> U160 {
212 assert!(
213 sqrt_price_x96 > U160::ZERO,
214 "sqrt_price_x96 must be greater than zero"
215 );
216 assert!(liquidity > 0, "Liquidity must be greater than zero");
217
218 if zero_for_one {
219 get_next_sqrt_price_from_amount1_rounding_down(sqrt_price_x96, liquidity, amount_out, false)
220 } else {
221 get_next_sqrt_price_from_amount0_rounding_up(sqrt_price_x96, liquidity, amount_out, false)
222 }
223}
224
225#[must_use]
227pub fn get_amount0_delta(
228 sqrt_ratio_ax96: U160,
229 sqrt_ratio_bx96: U160,
230 liquidity: u128,
231 round_up: bool,
232) -> U256 {
233 let (sqrt_ratio_a, sqrt_ratio_b) = if sqrt_ratio_ax96 > sqrt_ratio_bx96 {
234 (sqrt_ratio_bx96, sqrt_ratio_ax96)
235 } else {
236 (sqrt_ratio_ax96, sqrt_ratio_bx96)
237 };
238
239 let numerator1 = U256::from(liquidity) << 96;
240 let numerator2 = U256::from(sqrt_ratio_b - sqrt_ratio_a);
241
242 if round_up {
243 let result =
245 FullMath::mul_div_rounding_up(numerator1, numerator2, U256::from(sqrt_ratio_b))
246 .unwrap_or(U256::ZERO);
247
248 FullMath::div_rounding_up(result, U256::from(sqrt_ratio_a)).unwrap_or(U256::ZERO)
250 } else {
251 let result = FullMath::mul_div(numerator1, numerator2, U256::from(sqrt_ratio_b))
252 .unwrap_or(U256::ZERO);
253 result / U256::from(sqrt_ratio_a)
254 }
255}
256#[must_use]
258pub fn get_amount1_delta(
259 sqrt_ratio_ax96: U160,
260 sqrt_ratio_bx96: U160,
261 liquidity: u128,
262 round_up: bool,
263) -> U256 {
264 let (sqrt_ratio_a, sqrt_ratio_b) = if sqrt_ratio_ax96 > sqrt_ratio_bx96 {
265 (sqrt_ratio_bx96, sqrt_ratio_ax96)
266 } else {
267 (sqrt_ratio_ax96, sqrt_ratio_bx96)
268 };
269
270 let liquidity_u256 = U256::from(liquidity);
271 let sqrt_ratio_diff = U256::from(sqrt_ratio_b - sqrt_ratio_a);
272 let q96 = U256::from(1u128) << 96;
273
274 if round_up {
275 FullMath::mul_div_rounding_up(liquidity_u256, sqrt_ratio_diff, q96).unwrap_or(U256::ZERO)
276 } else {
277 FullMath::mul_div(liquidity_u256, sqrt_ratio_diff, q96).unwrap_or(U256::ZERO)
278 }
279}
280
281#[must_use]
283pub fn get_amounts_for_liquidity(
284 sqrt_ratio_x96: U160,
285 tick_lower: i32,
286 tick_upper: i32,
287 liquidity: u128,
288 round_up: bool,
289) -> (U256, U256) {
290 let sqrt_ratio_lower_x96 = get_sqrt_ratio_at_tick(tick_lower);
291 let sqrt_ratio_upper_x96 = get_sqrt_ratio_at_tick(tick_upper);
292
293 let (sqrt_ratio_a, sqrt_ratio_b) = if sqrt_ratio_lower_x96 > sqrt_ratio_upper_x96 {
295 (sqrt_ratio_upper_x96, sqrt_ratio_lower_x96)
296 } else {
297 (sqrt_ratio_lower_x96, sqrt_ratio_upper_x96)
298 };
299
300 let amount0 = if sqrt_ratio_x96 <= sqrt_ratio_a {
301 get_amount0_delta(sqrt_ratio_a, sqrt_ratio_b, liquidity, round_up)
303 } else if sqrt_ratio_x96 < sqrt_ratio_b {
304 get_amount0_delta(sqrt_ratio_x96, sqrt_ratio_b, liquidity, round_up)
306 } else {
307 U256::ZERO
309 };
310
311 let amount1 = if sqrt_ratio_x96 < sqrt_ratio_a {
312 U256::ZERO
314 } else if sqrt_ratio_x96 < sqrt_ratio_b {
315 get_amount1_delta(sqrt_ratio_a, sqrt_ratio_x96, liquidity, round_up)
317 } else {
318 get_amount1_delta(sqrt_ratio_a, sqrt_ratio_b, liquidity, round_up)
320 };
321
322 (amount0, amount1)
323}
324
325#[must_use]
327pub fn expand_to_18_decimals(amount: u64) -> u128 {
328 u128::from(amount) * 10u128.pow(18)
329}
330
331pub fn decode_sqrt_price_x96_to_price(sqrt_price_x96: U160) -> anyhow::Result<Price> {
342 let sqrt_price = U256::from(sqrt_price_x96);
343 let fixed_scalar = FullMath::pow10(FIXED_PRECISION)?;
344 let divisor = U256::from(1u128) << 192;
345 let price_raw = FullMath::mul_div_scaled(sqrt_price, sqrt_price, divisor, &[fixed_scalar])?;
346
347 price_from_u256(price_raw)
348}
349
350pub fn decode_sqrt_price_x96_to_price_tokens_adjusted(
374 sqrt_price_x96: U160,
375 token0_decimals: u8,
376 token1_decimals: u8,
377 invert: bool,
378) -> anyhow::Result<Price> {
379 let sqrt_price = U256::from(sqrt_price_x96);
380 let decimal_diff = i32::from(token0_decimals) - i32::from(token1_decimals);
381 let token0_scalar = FullMath::pow10(token0_decimals)?;
382 let token1_scalar = FullMath::pow10(token1_decimals)?;
383 let decimal_adjustment = if decimal_diff >= 0 {
384 token0_scalar / token1_scalar
385 } else {
386 token1_scalar / token0_scalar
387 };
388 let fixed_scalar = FullMath::pow10(FIXED_PRECISION)?;
389 let divisor_base: U256 = U256::from(1u128) << 192;
390
391 let price_raw = if invert {
392 if decimal_diff >= 0 {
393 let numerator = divisor_base
394 .checked_mul(fixed_scalar)
395 .ok_or_else(|| anyhow::anyhow!("Inverted price numerator exceeds U256 range"))?;
396 let price_square: U512 = sqrt_price.widening_mul(sqrt_price);
397 let max_square = U512::from(numerator / decimal_adjustment);
398
399 if price_square > max_square {
400 U256::ZERO
401 } else {
402 let price_square = U256::checked_from_limbs_slice(price_square.as_limbs())
403 .ok_or_else(|| {
404 anyhow::anyhow!("Inverted price denominator exceeds U256 range")
405 })?;
406 let denominator =
407 price_square
408 .checked_mul(decimal_adjustment)
409 .ok_or_else(|| {
410 anyhow::anyhow!("Inverted price denominator exceeds U256 range")
411 })?;
412 FullMath::mul_div(numerator, U256::from(1), denominator)?
413 }
414 } else {
415 let price_square: U512 = sqrt_price.widening_mul(sqrt_price);
416 anyhow::ensure!(
417 !price_square.is_zero(),
418 "Cannot decode inverted price from zero sqrt_price_x96"
419 );
420 let numerator = U512::from(divisor_base)
421 .checked_mul(U512::from(decimal_adjustment))
422 .and_then(|value| value.checked_mul(U512::from(fixed_scalar)))
423 .ok_or_else(|| anyhow::anyhow!("Inverted price numerator exceeds U512 range"))?;
424 let quotient = numerator / price_square;
425 U256::checked_from_limbs_slice(quotient.as_limbs())
426 .ok_or_else(|| anyhow::anyhow!("Inverted price exceeds U256 range"))?
427 }
428 } else if decimal_diff >= 0 {
429 FullMath::mul_div_scaled(
430 sqrt_price,
431 sqrt_price,
432 divisor_base,
433 &[fixed_scalar, decimal_adjustment],
434 )?
435 } else {
436 FullMath::mul_div_scaled(sqrt_price, sqrt_price, divisor_base, &[fixed_scalar])?
437 / decimal_adjustment
438 };
439
440 price_from_u256(price_raw)
441}
442
443pub(crate) fn price_from_u256(price_raw: U256) -> anyhow::Result<Price> {
444 anyhow::ensure!(
445 price_raw <= U256::from(PRICE_RAW_MAX as u128),
446 "Price overflow: {price_raw} exceeds maximum valid raw price {PRICE_RAW_MAX}"
447 );
448 let price_raw: i128 = price_raw
449 .try_into()
450 .map_err(|_| anyhow::anyhow!("Price overflow: {price_raw} exceeds PriceRaw range"))?;
451
452 Price::from_raw_checked(price_raw, FIXED_PRECISION).map_err(Into::into)
453}
454
455#[cfg(test)]
456mod tests {
457 use rstest::*;
459
460 use super::*;
461 use crate::defi::tick_map::{
462 full_math::{DECIMAL_EXPONENT_MAX, Q96_U160},
463 tick_math::MAX_SQRT_RATIO,
464 };
465
466 #[rstest]
467 #[should_panic(expected = "sqrt_price_x96 must be greater than zero")]
468 fn test_if_get_next_sqrt_price_from_input_panic_if_price_zero() {
469 let _ = get_next_sqrt_price_from_input(U160::ZERO, 1, U256::ZERO, true);
470 }
471
472 #[rstest]
473 #[should_panic(expected = "Liquidity must be greater than zero")]
474 fn test_if_get_next_sqrt_price_from_input_panic_if_liquidity_zero() {
475 let _ = get_next_sqrt_price_from_input(U160::from(1), 0, U256::ZERO, true);
476 }
477
478 #[rstest]
479 #[should_panic(expected = "Uint conversion error: Value is too large for Uint<160>")]
480 fn test_if_get_next_sqrt_price_from_input_panics_from_big_price() {
481 let price = U160::MAX - U160::from(1);
482 let _ = get_next_sqrt_price_from_input(price, 1024, U256::from(1024), false);
483 }
484
485 #[rstest]
486 fn test_any_input_amount_cannot_underflow_the_price() {
487 let price = U160::from(1);
490 let liquidity = 1;
491 let amount_in = U256::from(2).pow(U256::from(255));
492 let result = get_next_sqrt_price_from_input(price, liquidity, amount_in, true);
493 assert_eq!(result, U160::from(1));
494 }
495
496 #[rstest]
497 fn test_returns_input_price_if_amount_in_is_zero_and_zero_for_one_true() {
498 let price = encode_sqrt_ratio_x96(1, 1);
499 let liquidity = expand_to_18_decimals(1) / 10;
500 let result = get_next_sqrt_price_from_input(price, liquidity, U256::ZERO, true);
501 assert_eq!(result, price);
502 }
503
504 #[rstest]
505 fn test_returns_input_price_if_amount_in_is_zero_and_zero_for_one_false() {
506 let price = encode_sqrt_ratio_x96(1, 1);
507 let liquidity = expand_to_18_decimals(1) / 10;
508 let result = get_next_sqrt_price_from_input(price, liquidity, U256::ZERO, false);
509 assert_eq!(result, price);
510 }
511
512 #[rstest]
513 fn test_returns_the_minimum_price_for_max_inputs() {
514 let sqrt_p = U160::MAX;
515 let liquidity = u128::MAX;
516 let max_amount_no_overflow = U256::MAX - (U256::from(liquidity) << 96) / U256::from(sqrt_p);
517 let result =
518 get_next_sqrt_price_from_input(sqrt_p, liquidity, max_amount_no_overflow, true);
519 assert_eq!(result, U160::from(1));
520 }
521
522 #[rstest]
523 fn test_input_amount_of_0_1_token1() {
524 let sqrt_q = get_next_sqrt_price_from_input(
525 encode_sqrt_ratio_x96(1, 1),
526 expand_to_18_decimals(1),
527 U256::from(expand_to_18_decimals(1)) / U256::from(10),
528 false,
529 );
530 assert_eq!(
531 sqrt_q,
532 U160::from_str_radix("87150978765690771352898345369", 10).unwrap()
533 );
534 }
535
536 #[rstest]
537 fn test_input_amount_of_0_1_token0() {
538 let sqrt_q = get_next_sqrt_price_from_input(
539 encode_sqrt_ratio_x96(1, 1),
540 expand_to_18_decimals(1),
541 U256::from(expand_to_18_decimals(1)) / U256::from(10),
542 true,
543 );
544 assert_eq!(
545 sqrt_q,
546 U160::from_str_radix("72025602285694852357767227579", 10).unwrap()
547 );
548 }
549
550 #[rstest]
551 fn test_amount_in_greater_than_uint96_max_and_zero_for_one_true() {
552 let result = get_next_sqrt_price_from_input(
553 encode_sqrt_ratio_x96(1, 1),
554 expand_to_18_decimals(10),
555 U256::from(2).pow(U256::from(100)),
556 true,
557 );
558 assert_eq!(
559 result,
560 U160::from_str_radix("624999999995069620", 10).unwrap()
561 );
562 }
563
564 #[rstest]
565 fn test_can_return_1_with_enough_amount_in_and_zero_for_one_true() {
566 let result = get_next_sqrt_price_from_input(
567 encode_sqrt_ratio_x96(1, 1),
568 1,
569 U256::MAX / U256::from(2),
570 true,
571 );
572 assert_eq!(result, U160::from(1));
573 }
574
575 #[rstest]
576 #[should_panic(
577 expected = "Invalid conditions for amount0 removal: overflow or underflow detected"
578 )]
579 fn test_fails_if_output_amount_is_exactly_virtual_reserves_of_token0() {
580 let price = U160::from_str_radix("20282409603651670423947251286016", 10).unwrap();
581 let liquidity = 1024;
582 let amount_out = U256::from(4);
583 let _ = get_next_sqrt_price_from_output(price, liquidity, amount_out, false);
584 }
585
586 #[rstest]
587 #[should_panic(
588 expected = "Invalid conditions for amount0 removal: overflow or underflow detected"
589 )]
590 fn test_fails_if_output_amount_is_greater_than_virtual_reserves_of_token0() {
591 let price = U160::from_str_radix("20282409603651670423947251286016", 10).unwrap();
592 let liquidity = 1024;
593 let amount_out = U256::from(5);
594 let _ = get_next_sqrt_price_from_output(price, liquidity, amount_out, false);
595 }
596
597 #[rstest]
598 #[should_panic(expected = "sqrt_price_x96 must be greater than quotient")]
599 fn test_fails_if_output_amount_is_greater_than_virtual_reserves_of_token1() {
600 let price = U160::from_str_radix("20282409603651670423947251286016", 10).unwrap();
601 let liquidity = 1024;
602 let amount_out = U256::from(262_145);
603 let _ = get_next_sqrt_price_from_output(price, liquidity, amount_out, true);
604 }
605
606 #[rstest]
607 #[should_panic(expected = "sqrt_price_x96 must be greater than quotient")]
608 fn test_fails_if_output_amount_is_exactly_virtual_reserves_of_token1() {
609 let price = U160::from_str_radix("20282409603651670423947251286016", 10).unwrap();
610 let liquidity = 1024;
611 let amount_out = U256::from(262_144);
612 let _ = get_next_sqrt_price_from_output(price, liquidity, amount_out, true);
613 }
614
615 #[rstest]
616 fn test_succeeds_if_output_amount_is_just_less_than_virtual_reserves_of_token1() {
617 let price = U160::from_str_radix("20282409603651670423947251286016", 10).unwrap();
618 let liquidity = 1024;
619 let amount_out = U256::from(262_143);
620 let result = get_next_sqrt_price_from_output(price, liquidity, amount_out, true);
621 assert_eq!(
622 result,
623 U160::from_str_radix("77371252455336267181195264", 10).unwrap()
624 );
625 }
626
627 #[rstest]
628 fn test_returns_input_price_if_amount_out_is_zero_and_zero_for_one_true() {
629 let price = encode_sqrt_ratio_x96(1, 1);
630 let liquidity = expand_to_18_decimals(1) / 10;
631 let result = get_next_sqrt_price_from_output(price, liquidity, U256::ZERO, true);
632 assert_eq!(result, price);
633 }
634
635 #[rstest]
636 fn test_returns_input_price_if_amount_out_is_zero_and_zero_for_one_false() {
637 let price = encode_sqrt_ratio_x96(1, 1);
638 let liquidity = expand_to_18_decimals(1) / 10;
639 let result = get_next_sqrt_price_from_output(price, liquidity, U256::ZERO, false);
640 assert_eq!(result, price);
641 }
642
643 #[rstest]
644 fn test_output_amount_of_0_1_token1_zero_for_one_false() {
645 let sqrt_q = get_next_sqrt_price_from_output(
646 encode_sqrt_ratio_x96(1, 1),
647 expand_to_18_decimals(1),
648 U256::from(expand_to_18_decimals(1)) / U256::from(10),
649 false,
650 );
651 assert_eq!(
652 sqrt_q,
653 U160::from_str_radix("88031291682515930659493278152", 10).unwrap()
654 );
655 }
656
657 #[rstest]
658 fn test_output_amount_of_0_1_token1_zero_for_one_true() {
659 let sqrt_q = get_next_sqrt_price_from_output(
660 encode_sqrt_ratio_x96(1, 1),
661 expand_to_18_decimals(1),
662 U256::from(expand_to_18_decimals(1)) / U256::from(10),
663 true,
664 );
665 assert_eq!(
666 sqrt_q,
667 U160::from_str_radix("71305346262837903834189555302", 10).unwrap()
668 );
669 }
670
671 #[rstest]
672 #[should_panic(expected = "sqrt_price_x96 must be greater than zero")]
673 fn test_if_get_next_sqrt_price_from_output_panic_if_price_zero() {
674 let _ = get_next_sqrt_price_from_output(U160::ZERO, 1, U256::ZERO, true);
675 }
676
677 #[rstest]
678 #[should_panic(expected = "Liquidity must be greater than zero")]
679 fn test_if_get_next_sqrt_price_from_output_panic_if_liquidity_zero() {
680 let _ = get_next_sqrt_price_from_output(U160::from(1), 0, U256::ZERO, true);
681 }
682
683 #[rstest]
684 fn test_encode_sqrt_ratio_x98_some_values() {
685 assert_eq!(encode_sqrt_ratio_x96(1, 1), Q96_U160);
686 assert_eq!(
687 encode_sqrt_ratio_x96(100, 1),
688 U160::from(792_281_625_142_643_375_935_439_503_360_u128)
689 );
690 assert_eq!(
691 encode_sqrt_ratio_x96(1, 100),
692 U160::from(7_922_816_251_426_433_759_354_395_033_u128)
693 );
694 assert_eq!(
695 encode_sqrt_ratio_x96(111, 333),
696 U160::from(45_742_400_955_009_932_534_161_870_629_u128)
697 );
698 assert_eq!(
699 encode_sqrt_ratio_x96(333, 111),
700 U160::from(137_227_202_865_029_797_602_485_611_888_u128)
701 );
702 }
703
704 #[rstest]
705 fn test_get_amount0_delta_returns_0_if_liquidity_is_0() {
706 let amount0 = get_amount0_delta(
707 encode_sqrt_ratio_x96(1, 1),
708 encode_sqrt_ratio_x96(2, 1),
709 0,
710 true,
711 );
712 assert_eq!(amount0, U256::ZERO);
713 }
714
715 #[rstest]
716 fn test_get_amount0_delta_returns_0_if_prices_are_equal() {
717 let amount0 = get_amount0_delta(
718 encode_sqrt_ratio_x96(1, 1),
719 encode_sqrt_ratio_x96(1, 1),
720 0,
721 true,
722 );
723 assert_eq!(amount0, U256::ZERO);
724 }
725
726 #[rstest]
727 fn test_get_amount0_delta_returns_0_1_amount1_for_price_of_1_to_1_21() {
728 let amount0 = get_amount0_delta(
729 encode_sqrt_ratio_x96(1, 1),
730 encode_sqrt_ratio_x96(121, 100),
731 expand_to_18_decimals(1),
732 true,
733 );
734 assert_eq!(
735 amount0,
736 U256::from_str_radix("90909090909090910", 10).unwrap()
737 );
738
739 let amount0_rounded_down = get_amount0_delta(
740 encode_sqrt_ratio_x96(1, 1),
741 encode_sqrt_ratio_x96(121, 100),
742 expand_to_18_decimals(1),
743 false,
744 );
745
746 assert_eq!(amount0_rounded_down, amount0 - U256::from(1));
747 }
748
749 #[rstest]
750 fn test_get_amount0_delta_works_for_prices_that_overflow() {
751 let price_low =
753 encode_sqrt_ratio_x96(U256::from(2).pow(U256::from(90)).try_into().unwrap(), 1);
754 let price_high =
755 encode_sqrt_ratio_x96(U256::from(2).pow(U256::from(96)).try_into().unwrap(), 1);
756
757 let amount0_up = get_amount0_delta(price_low, price_high, expand_to_18_decimals(1), true);
758
759 let amount0_down =
760 get_amount0_delta(price_low, price_high, expand_to_18_decimals(1), false);
761
762 assert_eq!(amount0_up, amount0_down + U256::from(1));
763 }
764
765 #[rstest]
766 fn test_get_amount1_delta_returns_0_if_liquidity_is_0() {
767 let amount1 = get_amount1_delta(
768 encode_sqrt_ratio_x96(1, 1),
769 encode_sqrt_ratio_x96(2, 1),
770 0,
771 true,
772 );
773 assert_eq!(amount1, U256::ZERO);
774 }
775
776 #[rstest]
777 fn test_get_amount1_delta_returns_0_if_prices_are_equal() {
778 let amount1 = get_amount1_delta(
779 encode_sqrt_ratio_x96(1, 1),
780 encode_sqrt_ratio_x96(1, 1),
781 0,
782 true,
783 );
784 assert_eq!(amount1, U256::ZERO);
785 }
786
787 #[rstest]
788 fn test_get_amount1_delta_returns_0_1_amount1_for_price_of_1_to_1_21() {
789 let amount1 = get_amount1_delta(
790 encode_sqrt_ratio_x96(1, 1),
791 encode_sqrt_ratio_x96(121, 100),
792 expand_to_18_decimals(1),
793 true,
794 );
795 assert_eq!(
796 amount1,
797 U256::from_str_radix("100000000000000000", 10).unwrap()
798 );
799
800 let amount1_rounded_down = get_amount1_delta(
801 encode_sqrt_ratio_x96(1, 1),
802 encode_sqrt_ratio_x96(121, 100),
803 expand_to_18_decimals(1),
804 false,
805 );
806
807 assert_eq!(amount1_rounded_down, amount1 - U256::from(1));
808 }
809
810 #[rstest]
811 fn test_decode_sqrt_price_x96_to_price_and_decimal_adjustments() {
812 let sqrt_price_x96 =
814 U160::from_str_radix("2018382873588440326581633304624437", 10).unwrap();
815
816 let raw_price = decode_sqrt_price_x96_to_price(sqrt_price_x96).unwrap();
817 assert_eq!(raw_price.as_f64(), 649_004_842.701_37);
818
819 let adjusted_price =
821 decode_sqrt_price_x96_to_price_tokens_adjusted(sqrt_price_x96, 6, 18, true).unwrap();
822 assert_eq!(adjusted_price.as_f64(), 1_540.820_552_028_045_8);
823 }
824
825 #[rstest]
826 #[case::normal_positive_difference(2, 0, false, 1_000_000_000_000_000_000)]
827 #[case::normal_negative_difference(0, 2, false, 100_000_000_000_000)]
828 #[case::inverted_positive_difference(2, 0, true, 100_000_000_000_000)]
829 #[case::inverted_negative_difference(0, 2, true, 1_000_000_000_000_000_000)]
830 fn test_decode_sqrt_price_x96_to_price_adjusts_direction_and_decimals_exactly(
831 #[case] token0_decimals: u8,
832 #[case] token1_decimals: u8,
833 #[case] invert: bool,
834 #[case] expected_raw: i128,
835 ) {
836 let result = decode_sqrt_price_x96_to_price_tokens_adjusted(
837 Q96_U160,
838 token0_decimals,
839 token1_decimals,
840 invert,
841 )
842 .unwrap();
843
844 assert_eq!(result, Price::from_raw(expected_raw, FIXED_PRECISION));
845 }
846
847 #[rstest]
848 fn test_decode_sqrt_price_x96_to_price_handles_max_ratio_without_wrapping() {
849 let sqrt_price_x96 = MAX_SQRT_RATIO - U160::from(1);
850
851 let raw_error = decode_sqrt_price_x96_to_price(sqrt_price_x96).unwrap_err();
852 let normal_error =
853 decode_sqrt_price_x96_to_price_tokens_adjusted(sqrt_price_x96, 0, 0, false)
854 .unwrap_err();
855 let inverted =
856 decode_sqrt_price_x96_to_price_tokens_adjusted(sqrt_price_x96, 0, 0, true).unwrap();
857
858 assert!(
859 raw_error
860 .to_string()
861 .contains("exceeds maximum valid raw price")
862 );
863 assert!(
864 normal_error
865 .to_string()
866 .contains("exceeds maximum valid raw price")
867 );
868 assert_eq!(inverted, Price::zero(FIXED_PRECISION));
869 }
870
871 #[rstest]
872 fn test_decode_sqrt_price_x96_to_price_handles_inverted_denominator_boundary() {
873 let sqrt_price_x96 = Q96_U160 * U160::from(100_000_000);
874
875 let result =
876 decode_sqrt_price_x96_to_price_tokens_adjusted(sqrt_price_x96, 0, 0, true).unwrap();
877
878 assert_eq!(result, Price::from_raw(1, FIXED_PRECISION));
879 }
880
881 #[rstest]
882 fn test_decode_sqrt_price_x96_to_price_handles_largest_decimal_exponent() {
883 let valid = decode_sqrt_price_x96_to_price_tokens_adjusted(
884 Q96_U160,
885 0,
886 DECIMAL_EXPONENT_MAX,
887 false,
888 )
889 .unwrap();
890 let normal_overflow = decode_sqrt_price_x96_to_price_tokens_adjusted(
891 Q96_U160,
892 DECIMAL_EXPONENT_MAX,
893 0,
894 false,
895 )
896 .unwrap_err();
897 let inverted_overflow =
898 decode_sqrt_price_x96_to_price_tokens_adjusted(Q96_U160, 0, DECIMAL_EXPONENT_MAX, true)
899 .unwrap_err();
900
901 assert_eq!(valid, Price::zero(FIXED_PRECISION));
902 assert_eq!(
903 normal_overflow.to_string(),
904 "Scaled result exceeds 256-bit range"
905 );
906 assert_eq!(
907 inverted_overflow.to_string(),
908 "Inverted price exceeds U256 range"
909 );
910 }
911
912 #[rstest]
913 fn test_decode_sqrt_price_x96_to_price_rejects_first_unsupported_decimal_exponent() {
914 let error = decode_sqrt_price_x96_to_price_tokens_adjusted(
915 Q96_U160,
916 0,
917 DECIMAL_EXPONENT_MAX + 1,
918 false,
919 )
920 .unwrap_err();
921
922 assert_eq!(
923 error.to_string(),
924 "Decimal exponent 78 exceeds supported maximum 77"
925 );
926 }
927}