Skip to main content

nautilus_model/defi/tick_map/
sqrt_price_math.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use 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/// Encodes the sqrt ratio of two token amounts as a Q64.96 fixed point number.
25///
26/// Calculates sqrt(amount0 / amount1) * 2^96 to encode the price ratio between
27/// two tokens as a fixed-point number suitable for AMM calculations.
28///
29/// # Panics
30///
31/// This function panics if:
32/// - `amount1` is zero (division by zero)
33/// - `sqrt(amount1)` is zero during overflow handling
34/// - Mathematical operations result in overflow during `mul_div`
35#[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    // We need to calculate: sqrt(amount0 / amount1) * 2^96
46    // To maintain precision, we'll calculate: sqrt(amount0 * 2^192 / amount1)
47    // This is because: sqrt(amount0/amount1) * 2^96 = sqrt(amount0 * 2^192 / amount1)
48
49    // First, scale amount0 by 2^192
50    let q192 = U256::from(1u128) << 192;
51
52    // Check if amount0 * 2^192 would overflow
53    if amount0_u256 > U256::MAX / q192 {
54        // If it would overflow, we need to handle it differently
55        // We'll use: sqrt(amount0) * 2^96 / sqrt(amount1)
56        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        // Use FullMath for precise division
64        let result = FullMath::mul_div(sqrt_amount0, q96, sqrt_amount1).expect("mul_div overflow");
65
66        // Convert to U160, truncating if necessary
67        return if result > U256::from(U160::MAX) {
68            U160::MAX
69        } else {
70            U160::from(result)
71        };
72    }
73
74    // Standard path: calculate (amount0 * 2^192) / amount1, then sqrt
75    let ratio_q192 = FullMath::mul_div(amount0_u256, q192, amount1_u256).expect("mul_div overflow");
76
77    // Take the square root of the ratio
78    let sqrt_result = FullMath::sqrt(ratio_q192);
79
80    // Convert to U160, truncating if necessary
81    if sqrt_result > U256::from(U160::MAX) {
82        U160::MAX
83    } else {
84        U160::from(sqrt_result)
85    }
86}
87
88/// Calculates the next sqrt price when trading token0 for token1, rounding up.
89fn 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                // always fit to 160bits
107                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        // Fallback: divRoundingUp(numerator1, (numerator1 / sqrtPX96).add(amount))
114        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        // Check if result fits in U160
119        assert!(result <= U256::from(U160::MAX), "Result overflows U160");
120        U160::from(result)
121    } else {
122        // require((product = amount * sqrtPX96) / amount == sqrtPX96 && numerator1 > product);
123        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
135/// Calculates the next sqrt price when trading token1 for token0, rounding down.
136fn 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 we're adding (subtracting), rounding down requires rounding the quotient down (up)
143    // in both cases, avoid a mulDiv for most inputs
144    if add {
145        let quotient = if amount <= U256::from(U160::MAX) {
146            // We have a small amount and use only bit shifting for efficiency
147            (amount << 96) / U256::from(liquidity)
148        } else {
149            // Use mul_div to prevent overflow
150            FullMath::mul_div(amount, U256::from(1u128) << 96, U256::from(liquidity))
151                .unwrap_or(U256::ZERO)
152        };
153
154        // sqrtPX96.add(quotient).toUint160()
155        U160::from(U256::from(sqrt_price_x96) + quotient)
156    } else {
157        let quotient = if amount <= U256::from(U160::MAX) {
158            // UnsafeMath.divRoundingUp(amount << FixedPoint96.RESOLUTION, liquidity)
159            FullMath::div_rounding_up(amount << 96, U256::from(liquidity)).unwrap_or(U256::ZERO)
160        } else {
161            // FullMath.mulDivRoundingUp(amount, FixedPoint96.Q96, liquidity)
162            FullMath::mul_div_rounding_up(amount, U256::from(1u128) << 96, U256::from(liquidity))
163                .unwrap_or(U256::ZERO)
164        };
165
166        // require(sqrtPX96 > quotient);
167        assert!(
168            U256::from(sqrt_price_x96) > quotient,
169            "sqrt_price_x96 must be greater than quotient"
170        );
171
172        // always fits 160 bits
173        U160::from(U256::from(sqrt_price_x96) - quotient)
174    }
175}
176
177/// Calculates the next sqrt price given an input amount.
178///
179/// # Panics
180/// Panics if `sqrt_price_x96` is zero or if `liquidity` is zero.
181#[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/// Calculates the next sqrt price given an output amount.
202///
203/// # Panics
204/// Panics if `sqrt_price_x96` is zero or if `liquidity` is zero.
205#[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/// Calculates the amount of token0 delta between two sqrt price ratios.
226#[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        // Use mul_div_rounding_up for the first operation
244        let result =
245            FullMath::mul_div_rounding_up(numerator1, numerator2, U256::from(sqrt_ratio_b))
246                .unwrap_or(U256::ZERO);
247
248        // Use proper div_rounding_up for the second operation to match Solidity UnsafeMath.divRoundingUp
249        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/// Calculates the amount of token1 delta between two sqrt price ratios.
257#[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/// Calculates the token amounts required for a given liquidity position.
282#[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    // Ensure lower <= upper
294    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        // Current price is below the range, all liquidity is in token0
302        get_amount0_delta(sqrt_ratio_a, sqrt_ratio_b, liquidity, round_up)
303    } else if sqrt_ratio_x96 < sqrt_ratio_b {
304        // Current price is within the range
305        get_amount0_delta(sqrt_ratio_x96, sqrt_ratio_b, liquidity, round_up)
306    } else {
307        // Current price is above the range, no token0 needed
308        U256::ZERO
309    };
310
311    let amount1 = if sqrt_ratio_x96 < sqrt_ratio_a {
312        // Current price is below the range, no token1 needed
313        U256::ZERO
314    } else if sqrt_ratio_x96 < sqrt_ratio_b {
315        // Current price is within the range
316        get_amount1_delta(sqrt_ratio_a, sqrt_ratio_x96, liquidity, round_up)
317    } else {
318        // Current price is above the range, all liquidity is in token1
319        get_amount1_delta(sqrt_ratio_a, sqrt_ratio_b, liquidity, round_up)
320    };
321
322    (amount0, amount1)
323}
324
325/// Expands an amount to 18 decimal places (multiplies by 10^18).
326#[must_use]
327pub fn expand_to_18_decimals(amount: u64) -> u128 {
328    u128::from(amount) * 10u128.pow(18)
329}
330
331/// Converts a sqrt price X96 to a raw Price (token1/token0 ratio without decimal adjustment).
332///
333/// To get fixed-point representation: `sqrt_price_x96^2 * 10^FIXED_PRECISION / 2^192`.
334/// Scaling preserves the remainder from the full-width square so flooring occurs only once.
335///
336/// # Errors
337///
338/// Returns an error if:
339/// - The price calculation overflows.
340/// - The result exceeds `PRICE_RAW_MAX`.
341pub 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
350/// Converts a sqrt price X96 to a human-readable spot price adjusted for token decimals.
351///
352/// # Arguments
353/// - `sqrt_price_x96` - The sqrt price in X96 format from the pool
354/// - `token0_decimals` - Number of decimals for token0
355/// - `token1_decimals` - Number of decimals for token1
356/// - `invert` - If true, returns token0/token1; if false, returns token1/token0
357///
358/// # Pool Price Format
359/// Uniswap V3 pools always store price as **token1/token0** where tokens are sorted by address.
360///
361/// # Errors
362///
363/// Returns an error if:
364/// - `sqrt_price_x96` is zero and `invert` is true.
365/// - A token decimal count exceeds `DECIMAL_EXPONENT_MAX` (77).
366/// - The price calculation exceeds its supported wide-integer range.
367/// - The result exceeds `PRICE_RAW_MAX`.
368///
369/// # Notes
370///
371/// Prices smaller than the fixed-point resolution are floored to
372/// `Price::zero(FIXED_PRECISION)`.
373pub 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    // Most of the tests are based on https://github.com/Uniswap/v3-core/blob/main/test/SqrtPriceMath.spec.ts
458    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        // Testing that when we have minimal price(1) and an enormous input amount (2^255)
488        // the price calculation doesn't "underflow" to zero or wrap around to invalid value
489        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        // Create large prices: 2^90 and 2^96
752        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        // Use values from https://blog.uniswap.org/uniswap-v3-math-primer
813        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        // We want the adjusted price inverted as USDC is token0 and WETH is token1
820        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}