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};
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_RAW_MIN, 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`
35pub fn encode_sqrt_ratio_x96(amount0: u128, amount1: u128) -> U160 {
36    let amount0_u256 = U256::from(amount0);
37    let amount1_u256 = U256::from(amount1);
38
39    assert!(!amount1_u256.is_zero(), "Division by zero");
40    if amount0_u256.is_zero() {
41        return U160::ZERO;
42    }
43
44    // We need to calculate: sqrt(amount0 / amount1) * 2^96
45    // To maintain precision, we'll calculate: sqrt(amount0 * 2^192 / amount1)
46    // This is because: sqrt(amount0/amount1) * 2^96 = sqrt(amount0 * 2^192 / amount1)
47
48    // First, scale amount0 by 2^192
49    let q192 = U256::from(1u128) << 192;
50
51    // Check if amount0 * 2^192 would overflow
52    if amount0_u256 > U256::MAX / q192 {
53        // If it would overflow, we need to handle it differently
54        // We'll use: sqrt(amount0) * 2^96 / sqrt(amount1)
55        let sqrt_amount0 = FullMath::sqrt(amount0_u256);
56        let sqrt_amount1 = FullMath::sqrt(amount1_u256);
57
58        assert!(!sqrt_amount1.is_zero(), "Division by zero in sqrt");
59
60        let q96 = U256::from(1u128) << 96;
61
62        // Use FullMath for precise division
63        let result = FullMath::mul_div(sqrt_amount0, q96, sqrt_amount1).expect("mul_div overflow");
64
65        // Convert to U160, truncating if necessary
66        return if result > U256::from(U160::MAX) {
67            U160::MAX
68        } else {
69            U160::from(result)
70        };
71    }
72
73    // Standard path: calculate (amount0 * 2^192) / amount1, then sqrt
74    let ratio_q192 = FullMath::mul_div(amount0_u256, q192, amount1_u256).expect("mul_div overflow");
75
76    // Take the square root of the ratio
77    let sqrt_result = FullMath::sqrt(ratio_q192);
78
79    // Convert to U160, truncating if necessary
80    if sqrt_result > U256::from(U160::MAX) {
81        U160::MAX
82    } else {
83        U160::from(sqrt_result)
84    }
85}
86
87/// Calculates the next sqrt price when trading token0 for token1, rounding up.
88fn get_next_sqrt_price_from_amount0_rounding_up(
89    sqrt_price_x96: U160,
90    liquidity: u128,
91    amount: U256,
92    add: bool,
93) -> U160 {
94    if amount.is_zero() {
95        return sqrt_price_x96;
96    }
97    let numerator = U256::from(liquidity) << 96;
98    let sqrt_price_x96 = U256::from(sqrt_price_x96);
99    let product = amount * sqrt_price_x96;
100
101    if add {
102        if product / amount == sqrt_price_x96 {
103            let denominator = numerator + product;
104            if denominator >= numerator {
105                // always fit to 160bits
106                let result = FullMath::mul_div_rounding_up(numerator, sqrt_price_x96, denominator)
107                    .expect("mul_div_rounding_up failed");
108                return U160::from(result);
109            }
110        }
111
112        // Fallback: divRoundingUp(numerator1, (numerator1 / sqrtPX96).add(amount))
113        let fallback_denominator = (numerator / sqrt_price_x96) + amount;
114        let result = FullMath::div_rounding_up(numerator, fallback_denominator)
115            .expect("div_rounding_up failed");
116
117        // Check if result fits in U160
118        assert!(result <= U256::from(U160::MAX), "Result overflows U160");
119        U160::from(result)
120    } else {
121        // require((product = amount * sqrtPX96) / amount == sqrtPX96 && numerator1 > product);
122        assert!(
123            (product / amount) == sqrt_price_x96 && numerator > product,
124            "Invalid conditions for amount0 removal: overflow or underflow detected"
125        );
126
127        let denominator = numerator - product;
128        let result = FullMath::mul_div_rounding_up(numerator, sqrt_price_x96, denominator)
129            .expect("mul_div_rounding_up failed");
130        U160::from(result)
131    }
132}
133
134/// Calculates the next sqrt price when trading token1 for token0, rounding down.
135fn get_next_sqrt_price_from_amount1_rounding_down(
136    sqrt_price_x96: U160,
137    liquidity: u128,
138    amount: U256,
139    add: bool,
140) -> U160 {
141    // if we're adding (subtracting), rounding down requires rounding the quotient down (up)
142    // in both cases, avoid a mulDiv for most inputs
143    if add {
144        let quotient = if amount <= U256::from(U160::MAX) {
145            // We have a small amount and use only bit shifting for efficiency
146            (amount << 96) / U256::from(liquidity)
147        } else {
148            // Use mul_div to prevent overflow
149            FullMath::mul_div(amount, U256::from(1u128) << 96, U256::from(liquidity))
150                .unwrap_or(U256::ZERO)
151        };
152
153        // sqrtPX96.add(quotient).toUint160()
154        U160::from(U256::from(sqrt_price_x96) + quotient)
155    } else {
156        let quotient = if amount <= U256::from(U160::MAX) {
157            // UnsafeMath.divRoundingUp(amount << FixedPoint96.RESOLUTION, liquidity)
158            FullMath::div_rounding_up(amount << 96, U256::from(liquidity)).unwrap_or(U256::ZERO)
159        } else {
160            // FullMath.mulDivRoundingUp(amount, FixedPoint96.Q96, liquidity)
161            FullMath::mul_div_rounding_up(amount, U256::from(1u128) << 96, U256::from(liquidity))
162                .unwrap_or(U256::ZERO)
163        };
164
165        // require(sqrtPX96 > quotient);
166        assert!(
167            U256::from(sqrt_price_x96) > quotient,
168            "sqrt_price_x96 must be greater than quotient"
169        );
170
171        // always fits 160 bits
172        U160::from(U256::from(sqrt_price_x96) - quotient)
173    }
174}
175
176/// Calculates the next sqrt price given an input amount.
177///
178/// # Panics
179/// Panics if `sqrt_price_x96` is zero or if `liquidity` is zero.
180pub fn get_next_sqrt_price_from_input(
181    sqrt_price_x96: U160,
182    liquidity: u128,
183    amount_in: U256,
184    zero_for_one: bool,
185) -> U160 {
186    assert!(
187        sqrt_price_x96 > U160::ZERO,
188        "sqrt_price_x96 must be greater than zero"
189    );
190    assert!(liquidity > 0, "Liquidity must be greater than zero");
191
192    if zero_for_one {
193        get_next_sqrt_price_from_amount0_rounding_up(sqrt_price_x96, liquidity, amount_in, true)
194    } else {
195        get_next_sqrt_price_from_amount1_rounding_down(sqrt_price_x96, liquidity, amount_in, true)
196    }
197}
198
199/// Calculates the next sqrt price given an output amount.
200///
201/// # Panics
202/// Panics if `sqrt_price_x96` is zero or if `liquidity` is zero.
203pub fn get_next_sqrt_price_from_output(
204    sqrt_price_x96: U160,
205    liquidity: u128,
206    amount_out: U256,
207    zero_for_one: bool,
208) -> U160 {
209    assert!(
210        sqrt_price_x96 > U160::ZERO,
211        "sqrt_price_x96 must be greater than zero"
212    );
213    assert!(liquidity > 0, "Liquidity must be greater than zero");
214
215    if zero_for_one {
216        get_next_sqrt_price_from_amount1_rounding_down(sqrt_price_x96, liquidity, amount_out, false)
217    } else {
218        get_next_sqrt_price_from_amount0_rounding_up(sqrt_price_x96, liquidity, amount_out, false)
219    }
220}
221
222/// Calculates the amount of token0 delta between two sqrt price ratios.
223#[must_use]
224pub fn get_amount0_delta(
225    sqrt_ratio_ax96: U160,
226    sqrt_ratio_bx96: U160,
227    liquidity: u128,
228    round_up: bool,
229) -> U256 {
230    let (sqrt_ratio_a, sqrt_ratio_b) = if sqrt_ratio_ax96 > sqrt_ratio_bx96 {
231        (sqrt_ratio_bx96, sqrt_ratio_ax96)
232    } else {
233        (sqrt_ratio_ax96, sqrt_ratio_bx96)
234    };
235
236    let numerator1 = U256::from(liquidity) << 96;
237    let numerator2 = U256::from(sqrt_ratio_b - sqrt_ratio_a);
238
239    if round_up {
240        // Use mul_div_rounding_up for the first operation
241        let result =
242            FullMath::mul_div_rounding_up(numerator1, numerator2, U256::from(sqrt_ratio_b))
243                .unwrap_or(U256::ZERO);
244
245        // Use proper div_rounding_up for the second operation to match Solidity UnsafeMath.divRoundingUp
246        FullMath::div_rounding_up(result, U256::from(sqrt_ratio_a)).unwrap_or(U256::ZERO)
247    } else {
248        let result = FullMath::mul_div(numerator1, numerator2, U256::from(sqrt_ratio_b))
249            .unwrap_or(U256::ZERO);
250        result / U256::from(sqrt_ratio_a)
251    }
252}
253/// Calculates the amount of token1 delta between two sqrt price ratios.
254#[must_use]
255pub fn get_amount1_delta(
256    sqrt_ratio_ax96: U160,
257    sqrt_ratio_bx96: U160,
258    liquidity: u128,
259    round_up: bool,
260) -> U256 {
261    let (sqrt_ratio_a, sqrt_ratio_b) = if sqrt_ratio_ax96 > sqrt_ratio_bx96 {
262        (sqrt_ratio_bx96, sqrt_ratio_ax96)
263    } else {
264        (sqrt_ratio_ax96, sqrt_ratio_bx96)
265    };
266
267    let liquidity_u256 = U256::from(liquidity);
268    let sqrt_ratio_diff = U256::from(sqrt_ratio_b - sqrt_ratio_a);
269    let q96 = U256::from(1u128) << 96;
270
271    if round_up {
272        FullMath::mul_div_rounding_up(liquidity_u256, sqrt_ratio_diff, q96).unwrap_or(U256::ZERO)
273    } else {
274        FullMath::mul_div(liquidity_u256, sqrt_ratio_diff, q96).unwrap_or(U256::ZERO)
275    }
276}
277
278/// Calculates the token amounts required for a given liquidity position.
279#[must_use]
280pub fn get_amounts_for_liquidity(
281    sqrt_ratio_x96: U160,
282    tick_lower: i32,
283    tick_upper: i32,
284    liquidity: u128,
285    round_up: bool,
286) -> (U256, U256) {
287    let sqrt_ratio_lower_x96 = get_sqrt_ratio_at_tick(tick_lower);
288    let sqrt_ratio_upper_x96 = get_sqrt_ratio_at_tick(tick_upper);
289
290    // Ensure lower <= upper
291    let (sqrt_ratio_a, sqrt_ratio_b) = if sqrt_ratio_lower_x96 > sqrt_ratio_upper_x96 {
292        (sqrt_ratio_upper_x96, sqrt_ratio_lower_x96)
293    } else {
294        (sqrt_ratio_lower_x96, sqrt_ratio_upper_x96)
295    };
296
297    let amount0 = if sqrt_ratio_x96 <= sqrt_ratio_a {
298        // Current price is below the range, all liquidity is in token0
299        get_amount0_delta(sqrt_ratio_a, sqrt_ratio_b, liquidity, round_up)
300    } else if sqrt_ratio_x96 < sqrt_ratio_b {
301        // Current price is within the range
302        get_amount0_delta(sqrt_ratio_x96, sqrt_ratio_b, liquidity, round_up)
303    } else {
304        // Current price is above the range, no token0 needed
305        U256::ZERO
306    };
307
308    let amount1 = if sqrt_ratio_x96 < sqrt_ratio_a {
309        // Current price is below the range, no token1 needed
310        U256::ZERO
311    } else if sqrt_ratio_x96 < sqrt_ratio_b {
312        // Current price is within the range
313        get_amount1_delta(sqrt_ratio_a, sqrt_ratio_x96, liquidity, round_up)
314    } else {
315        // Current price is above the range, all liquidity is in token1
316        get_amount1_delta(sqrt_ratio_a, sqrt_ratio_b, liquidity, round_up)
317    };
318
319    (amount0, amount1)
320}
321
322/// Expands an amount to 18 decimal places (multiplies by 10^18).
323pub fn expand_to_18_decimals(amount: u64) -> u128 {
324    amount as u128 * 10u128.pow(18)
325}
326
327/// Converts a sqrt price X96 to a raw Price (token1/token0 ratio without decimal adjustment).
328///
329/// To get fixed-point representation: (sqrtPriceX96^2 * 10^FIXED_PRECISION) / 2^192
330/// We use FullMath::mul_div to handle the overflow from price_x192 * 10^FIXED_PRECISION
331///
332/// # Errors
333///
334/// Returns an error if the price calculation overflows or exceeds `PriceRaw` range.
335pub fn decode_sqrt_price_x96_to_price(sqrt_price_x96: U160) -> anyhow::Result<Price> {
336    let sqrt_price = U256::from(sqrt_price_x96);
337    let price_x192 = sqrt_price * sqrt_price;
338
339    let fixed_scalar = U256::from(10u128.pow(FIXED_PRECISION as u32));
340    let divisor = U256::from(1u128) << 192;
341    let price_raw_u256 = FullMath::mul_div(price_x192, fixed_scalar, divisor)?;
342
343    let price_raw = price_raw_u256
344        .try_into()
345        .map_err(|_| anyhow::anyhow!("Price overflow: {price_raw_u256} exceeds PriceRaw range"))?;
346
347    Ok(Price::from_raw(price_raw, FIXED_PRECISION))
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 the price calculation overflows or exceeds `PriceRaw` range.
364pub fn decode_sqrt_price_x96_to_price_tokens_adjusted(
365    sqrt_price_x96: U160,
366    token0_decimals: u8,
367    token1_decimals: u8,
368    invert: bool,
369) -> anyhow::Result<Price> {
370    let sqrt_price = U256::from(sqrt_price_x96);
371    let price_x192 = sqrt_price * sqrt_price;
372
373    let decimal_diff = token0_decimals as i32 - token1_decimals as i32;
374    let fixed_scalar = U256::from(10u128.pow(FIXED_PRECISION as u32));
375    let divisor_base = U256::from(1u128) << 192;
376
377    let numerator = if invert {
378        if decimal_diff >= 0 {
379            let decimal_adjustment = U256::from(10u128.pow(decimal_diff.unsigned_abs()));
380            let denominator = FullMath::mul_div(price_x192, decimal_adjustment, U256::from(1))?;
381            FullMath::mul_div(divisor_base, fixed_scalar, denominator)?
382        } else {
383            let decimal_adjustment = U256::from(10u128.pow(decimal_diff.unsigned_abs()));
384            let numerator_adjusted =
385                FullMath::mul_div(divisor_base, decimal_adjustment, U256::from(1))?;
386            FullMath::mul_div(numerator_adjusted, fixed_scalar, price_x192)?
387        }
388    } else if decimal_diff >= 0 {
389        let decimal_adjustment = U256::from(10u128.pow(decimal_diff.unsigned_abs()));
390        let temp = FullMath::mul_div(price_x192, decimal_adjustment, U256::from(1))?;
391        FullMath::mul_div(temp, fixed_scalar, divisor_base)?
392    } else {
393        let decimal_adjustment = U256::from(10u128.pow(decimal_diff.unsigned_abs()));
394        let divisor_adjusted = divisor_base * decimal_adjustment;
395        FullMath::mul_div(price_x192, fixed_scalar, divisor_adjusted)?
396    };
397
398    let price_raw: i128 = numerator
399        .try_into()
400        .map_err(|_| anyhow::anyhow!("Price overflow: {numerator} exceeds PriceRaw range"))?;
401
402    // Step 5: Validate price is within valid range before creating Price
403    if price_raw > PRICE_RAW_MAX {
404        anyhow::bail!("Price {price_raw} exceeds maximum valid price {PRICE_RAW_MAX}");
405    }
406    if price_raw < PRICE_RAW_MIN {
407        anyhow::bail!("Price {price_raw} is below minimum valid price {PRICE_RAW_MIN}");
408    }
409
410    Ok(Price::from_raw(price_raw, FIXED_PRECISION))
411}
412
413#[cfg(test)]
414mod tests {
415    // Most of the tests are based on https://github.com/Uniswap/v3-core/blob/main/test/SqrtPriceMath.spec.ts
416    use rstest::*;
417
418    use super::*;
419    use crate::defi::tick_map::full_math::Q96_U160;
420
421    #[rstest]
422    #[should_panic(expected = "sqrt_price_x96 must be greater than zero")]
423    fn test_if_get_next_sqrt_price_from_input_panic_if_price_zero() {
424        let _ = get_next_sqrt_price_from_input(U160::ZERO, 1, U256::ZERO, true);
425    }
426
427    #[rstest]
428    #[should_panic(expected = "Liquidity must be greater than zero")]
429    fn test_if_get_next_sqrt_price_from_input_panic_if_liquidity_zero() {
430        let _ = get_next_sqrt_price_from_input(U160::from(1), 0, U256::ZERO, true);
431    }
432
433    #[rstest]
434    #[should_panic(expected = "Uint conversion error: Value is too large for Uint<160>")]
435    fn test_if_get_next_sqrt_price_from_input_panics_from_big_price() {
436        let price = U160::MAX - U160::from(1);
437        let _ = get_next_sqrt_price_from_input(price, 1024, U256::from(1024), false);
438    }
439
440    #[rstest]
441    fn test_any_input_amount_cannot_underflow_the_price() {
442        // Testing that when we have minimal price(1) and an enormous input amount (2^255)
443        // the price calculation doesn't "underflow" to zero or wrap around to invalid value
444        let price = U160::from(1);
445        let liquidity = 1;
446        let amount_in = U256::from(2).pow(U256::from(255));
447        let result = get_next_sqrt_price_from_input(price, liquidity, amount_in, true);
448        assert_eq!(result, U160::from(1));
449    }
450
451    #[rstest]
452    fn test_returns_input_price_if_amount_in_is_zero_and_zero_for_one_true() {
453        let price = encode_sqrt_ratio_x96(1, 1);
454        let liquidity = expand_to_18_decimals(1) / 10;
455        let result = get_next_sqrt_price_from_input(price, liquidity, U256::ZERO, true);
456        assert_eq!(result, price);
457    }
458
459    #[rstest]
460    fn test_returns_input_price_if_amount_in_is_zero_and_zero_for_one_false() {
461        let price = encode_sqrt_ratio_x96(1, 1);
462        let liquidity = expand_to_18_decimals(1) / 10;
463        let result = get_next_sqrt_price_from_input(price, liquidity, U256::ZERO, false);
464        assert_eq!(result, price);
465    }
466
467    #[rstest]
468    fn test_returns_the_minimum_price_for_max_inputs() {
469        let sqrt_p = U160::MAX;
470        let liquidity = u128::MAX;
471        let max_amount_no_overflow = U256::MAX - (U256::from(liquidity) << 96) / U256::from(sqrt_p);
472        let result =
473            get_next_sqrt_price_from_input(sqrt_p, liquidity, max_amount_no_overflow, true);
474        assert_eq!(result, U160::from(1));
475    }
476
477    #[rstest]
478    fn test_input_amount_of_0_1_token1() {
479        let sqrt_q = get_next_sqrt_price_from_input(
480            encode_sqrt_ratio_x96(1, 1),
481            expand_to_18_decimals(1),
482            U256::from(expand_to_18_decimals(1)) / U256::from(10),
483            false,
484        );
485        assert_eq!(
486            sqrt_q,
487            U160::from_str_radix("87150978765690771352898345369", 10).unwrap()
488        );
489    }
490
491    #[rstest]
492    fn test_input_amount_of_0_1_token0() {
493        let sqrt_q = get_next_sqrt_price_from_input(
494            encode_sqrt_ratio_x96(1, 1),
495            expand_to_18_decimals(1),
496            U256::from(expand_to_18_decimals(1)) / U256::from(10),
497            true,
498        );
499        assert_eq!(
500            sqrt_q,
501            U160::from_str_radix("72025602285694852357767227579", 10).unwrap()
502        );
503    }
504
505    #[rstest]
506    fn test_amount_in_greater_than_uint96_max_and_zero_for_one_true() {
507        let result = get_next_sqrt_price_from_input(
508            encode_sqrt_ratio_x96(1, 1),
509            expand_to_18_decimals(10),
510            U256::from(2).pow(U256::from(100)),
511            true,
512        );
513        assert_eq!(
514            result,
515            U160::from_str_radix("624999999995069620", 10).unwrap()
516        );
517    }
518
519    #[rstest]
520    fn test_can_return_1_with_enough_amount_in_and_zero_for_one_true() {
521        let result = get_next_sqrt_price_from_input(
522            encode_sqrt_ratio_x96(1, 1),
523            1,
524            U256::MAX / U256::from(2),
525            true,
526        );
527        assert_eq!(result, U160::from(1));
528    }
529
530    #[rstest]
531    #[should_panic(
532        expected = "Invalid conditions for amount0 removal: overflow or underflow detected"
533    )]
534    fn test_fails_if_output_amount_is_exactly_virtual_reserves_of_token0() {
535        let price = U160::from_str_radix("20282409603651670423947251286016", 10).unwrap();
536        let liquidity = 1024;
537        let amount_out = U256::from(4);
538        let _ = get_next_sqrt_price_from_output(price, liquidity, amount_out, false);
539    }
540
541    #[rstest]
542    #[should_panic(
543        expected = "Invalid conditions for amount0 removal: overflow or underflow detected"
544    )]
545    fn test_fails_if_output_amount_is_greater_than_virtual_reserves_of_token0() {
546        let price = U160::from_str_radix("20282409603651670423947251286016", 10).unwrap();
547        let liquidity = 1024;
548        let amount_out = U256::from(5);
549        let _ = get_next_sqrt_price_from_output(price, liquidity, amount_out, false);
550    }
551
552    #[rstest]
553    #[should_panic(expected = "sqrt_price_x96 must be greater than quotient")]
554    fn test_fails_if_output_amount_is_greater_than_virtual_reserves_of_token1() {
555        let price = U160::from_str_radix("20282409603651670423947251286016", 10).unwrap();
556        let liquidity = 1024;
557        let amount_out = U256::from(262145);
558        let _ = get_next_sqrt_price_from_output(price, liquidity, amount_out, true);
559    }
560
561    #[rstest]
562    #[should_panic(expected = "sqrt_price_x96 must be greater than quotient")]
563    fn test_fails_if_output_amount_is_exactly_virtual_reserves_of_token1() {
564        let price = U160::from_str_radix("20282409603651670423947251286016", 10).unwrap();
565        let liquidity = 1024;
566        let amount_out = U256::from(262144);
567        let _ = get_next_sqrt_price_from_output(price, liquidity, amount_out, true);
568    }
569
570    #[rstest]
571    fn test_succeeds_if_output_amount_is_just_less_than_virtual_reserves_of_token1() {
572        let price = U160::from_str_radix("20282409603651670423947251286016", 10).unwrap();
573        let liquidity = 1024;
574        let amount_out = U256::from(262143);
575        let result = get_next_sqrt_price_from_output(price, liquidity, amount_out, true);
576        assert_eq!(
577            result,
578            U160::from_str_radix("77371252455336267181195264", 10).unwrap()
579        );
580    }
581
582    #[rstest]
583    fn test_returns_input_price_if_amount_out_is_zero_and_zero_for_one_true() {
584        let price = encode_sqrt_ratio_x96(1, 1);
585        let liquidity = expand_to_18_decimals(1) / 10;
586        let result = get_next_sqrt_price_from_output(price, liquidity, U256::ZERO, true);
587        assert_eq!(result, price);
588    }
589
590    #[rstest]
591    fn test_returns_input_price_if_amount_out_is_zero_and_zero_for_one_false() {
592        let price = encode_sqrt_ratio_x96(1, 1);
593        let liquidity = expand_to_18_decimals(1) / 10;
594        let result = get_next_sqrt_price_from_output(price, liquidity, U256::ZERO, false);
595        assert_eq!(result, price);
596    }
597
598    #[rstest]
599    fn test_output_amount_of_0_1_token1_zero_for_one_false() {
600        let sqrt_q = get_next_sqrt_price_from_output(
601            encode_sqrt_ratio_x96(1, 1),
602            expand_to_18_decimals(1),
603            U256::from(expand_to_18_decimals(1)) / U256::from(10),
604            false,
605        );
606        assert_eq!(
607            sqrt_q,
608            U160::from_str_radix("88031291682515930659493278152", 10).unwrap()
609        );
610    }
611
612    #[rstest]
613    fn test_output_amount_of_0_1_token1_zero_for_one_true() {
614        let sqrt_q = get_next_sqrt_price_from_output(
615            encode_sqrt_ratio_x96(1, 1),
616            expand_to_18_decimals(1),
617            U256::from(expand_to_18_decimals(1)) / U256::from(10),
618            true,
619        );
620        assert_eq!(
621            sqrt_q,
622            U160::from_str_radix("71305346262837903834189555302", 10).unwrap()
623        );
624    }
625
626    #[rstest]
627    #[should_panic(expected = "sqrt_price_x96 must be greater than zero")]
628    fn test_if_get_next_sqrt_price_from_output_panic_if_price_zero() {
629        let _ = get_next_sqrt_price_from_output(U160::ZERO, 1, U256::ZERO, true);
630    }
631
632    #[rstest]
633    #[should_panic(expected = "Liquidity must be greater than zero")]
634    fn test_if_get_next_sqrt_price_from_output_panic_if_liquidity_zero() {
635        let _ = get_next_sqrt_price_from_output(U160::from(1), 0, U256::ZERO, true);
636    }
637
638    #[rstest]
639    fn test_encode_sqrt_ratio_x98_some_values() {
640        assert_eq!(encode_sqrt_ratio_x96(1, 1), Q96_U160);
641        assert_eq!(
642            encode_sqrt_ratio_x96(100, 1),
643            U160::from(792281625142643375935439503360_u128)
644        );
645        assert_eq!(
646            encode_sqrt_ratio_x96(1, 100),
647            U160::from(7922816251426433759354395033_u128)
648        );
649        assert_eq!(
650            encode_sqrt_ratio_x96(111, 333),
651            U160::from(45742400955009932534161870629_u128)
652        );
653        assert_eq!(
654            encode_sqrt_ratio_x96(333, 111),
655            U160::from(137227202865029797602485611888_u128)
656        );
657    }
658
659    #[rstest]
660    fn test_get_amount0_delta_returns_0_if_liquidity_is_0() {
661        let amount0 = get_amount0_delta(
662            encode_sqrt_ratio_x96(1, 1),
663            encode_sqrt_ratio_x96(2, 1),
664            0,
665            true,
666        );
667        assert_eq!(amount0, U256::ZERO);
668    }
669
670    #[rstest]
671    fn test_get_amount0_delta_returns_0_if_prices_are_equal() {
672        let amount0 = get_amount0_delta(
673            encode_sqrt_ratio_x96(1, 1),
674            encode_sqrt_ratio_x96(1, 1),
675            0,
676            true,
677        );
678        assert_eq!(amount0, U256::ZERO);
679    }
680
681    #[rstest]
682    fn test_get_amount0_delta_returns_0_1_amount1_for_price_of_1_to_1_21() {
683        let amount0 = get_amount0_delta(
684            encode_sqrt_ratio_x96(1, 1),
685            encode_sqrt_ratio_x96(121, 100),
686            expand_to_18_decimals(1),
687            true,
688        );
689        assert_eq!(
690            amount0,
691            U256::from_str_radix("90909090909090910", 10).unwrap()
692        );
693
694        let amount0_rounded_down = get_amount0_delta(
695            encode_sqrt_ratio_x96(1, 1),
696            encode_sqrt_ratio_x96(121, 100),
697            expand_to_18_decimals(1),
698            false,
699        );
700
701        assert_eq!(amount0_rounded_down, amount0 - U256::from(1));
702    }
703
704    #[rstest]
705    fn test_get_amount0_delta_works_for_prices_that_overflow() {
706        // Create large prices: 2^90 and 2^96
707        let price_low =
708            encode_sqrt_ratio_x96(U256::from(2).pow(U256::from(90)).try_into().unwrap(), 1);
709        let price_high =
710            encode_sqrt_ratio_x96(U256::from(2).pow(U256::from(96)).try_into().unwrap(), 1);
711
712        let amount0_up = get_amount0_delta(price_low, price_high, expand_to_18_decimals(1), true);
713
714        let amount0_down =
715            get_amount0_delta(price_low, price_high, expand_to_18_decimals(1), false);
716
717        assert_eq!(amount0_up, amount0_down + U256::from(1));
718    }
719
720    #[rstest]
721    fn test_get_amount1_delta_returns_0_if_liquidity_is_0() {
722        let amount1 = get_amount1_delta(
723            encode_sqrt_ratio_x96(1, 1),
724            encode_sqrt_ratio_x96(2, 1),
725            0,
726            true,
727        );
728        assert_eq!(amount1, U256::ZERO);
729    }
730
731    #[rstest]
732    fn test_get_amount1_delta_returns_0_if_prices_are_equal() {
733        let amount1 = get_amount1_delta(
734            encode_sqrt_ratio_x96(1, 1),
735            encode_sqrt_ratio_x96(1, 1),
736            0,
737            true,
738        );
739        assert_eq!(amount1, U256::ZERO);
740    }
741
742    #[rstest]
743    fn test_get_amount1_delta_returns_0_1_amount1_for_price_of_1_to_1_21() {
744        let amount1 = get_amount1_delta(
745            encode_sqrt_ratio_x96(1, 1),
746            encode_sqrt_ratio_x96(121, 100),
747            expand_to_18_decimals(1),
748            true,
749        );
750        assert_eq!(
751            amount1,
752            U256::from_str_radix("100000000000000000", 10).unwrap()
753        );
754
755        let amount1_rounded_down = get_amount1_delta(
756            encode_sqrt_ratio_x96(1, 1),
757            encode_sqrt_ratio_x96(121, 100),
758            expand_to_18_decimals(1),
759            false,
760        );
761
762        assert_eq!(amount1_rounded_down, amount1 - U256::from(1));
763    }
764
765    #[rstest]
766    fn test_decode_sqrt_price_x96_to_price_and_decimal_adjustments() {
767        // Use values from https://blog.uniswap.org/uniswap-v3-math-primer
768        let sqrt_price_x96 =
769            U160::from_str_radix("2018382873588440326581633304624437", 10).unwrap();
770
771        let raw_price = decode_sqrt_price_x96_to_price(sqrt_price_x96).unwrap();
772        assert_eq!(raw_price.as_f64(), 649004842.70137);
773
774        // We want the adjusted price inverted as USDC is token0 and WETH is token1
775        let adjusted_price =
776            decode_sqrt_price_x96_to_price_tokens_adjusted(sqrt_price_x96, 6, 18, true).unwrap();
777        assert_eq!(adjusted_price.as_f64(), 1540.8205520280458);
778    }
779}