nautilus_model/defi/tick_map/
sqrt_price_math.rs

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