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};
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`
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: (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(u32::from(FIXED_PRECISION)));
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 = i32::from(token0_decimals) - i32::from(token1_decimals);
378    let fixed_scalar = U256::from(10u128.pow(u32::from(FIXED_PRECISION)));
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
411    if price_raw < PRICE_RAW_MIN {
412        anyhow::bail!("Price {price_raw} is below minimum valid price {PRICE_RAW_MIN}");
413    }
414
415    Ok(Price::from_raw(price_raw, FIXED_PRECISION))
416}
417
418#[cfg(test)]
419mod tests {
420    // Most of the tests are based on https://github.com/Uniswap/v3-core/blob/main/test/SqrtPriceMath.spec.ts
421    use rstest::*;
422
423    use super::*;
424    use crate::defi::tick_map::full_math::Q96_U160;
425
426    #[rstest]
427    #[should_panic(expected = "sqrt_price_x96 must be greater than zero")]
428    fn test_if_get_next_sqrt_price_from_input_panic_if_price_zero() {
429        let _ = get_next_sqrt_price_from_input(U160::ZERO, 1, U256::ZERO, true);
430    }
431
432    #[rstest]
433    #[should_panic(expected = "Liquidity must be greater than zero")]
434    fn test_if_get_next_sqrt_price_from_input_panic_if_liquidity_zero() {
435        let _ = get_next_sqrt_price_from_input(U160::from(1), 0, U256::ZERO, true);
436    }
437
438    #[rstest]
439    #[should_panic(expected = "Uint conversion error: Value is too large for Uint<160>")]
440    fn test_if_get_next_sqrt_price_from_input_panics_from_big_price() {
441        let price = U160::MAX - U160::from(1);
442        let _ = get_next_sqrt_price_from_input(price, 1024, U256::from(1024), false);
443    }
444
445    #[rstest]
446    fn test_any_input_amount_cannot_underflow_the_price() {
447        // Testing that when we have minimal price(1) and an enormous input amount (2^255)
448        // the price calculation doesn't "underflow" to zero or wrap around to invalid value
449        let price = U160::from(1);
450        let liquidity = 1;
451        let amount_in = U256::from(2).pow(U256::from(255));
452        let result = get_next_sqrt_price_from_input(price, liquidity, amount_in, true);
453        assert_eq!(result, U160::from(1));
454    }
455
456    #[rstest]
457    fn test_returns_input_price_if_amount_in_is_zero_and_zero_for_one_true() {
458        let price = encode_sqrt_ratio_x96(1, 1);
459        let liquidity = expand_to_18_decimals(1) / 10;
460        let result = get_next_sqrt_price_from_input(price, liquidity, U256::ZERO, true);
461        assert_eq!(result, price);
462    }
463
464    #[rstest]
465    fn test_returns_input_price_if_amount_in_is_zero_and_zero_for_one_false() {
466        let price = encode_sqrt_ratio_x96(1, 1);
467        let liquidity = expand_to_18_decimals(1) / 10;
468        let result = get_next_sqrt_price_from_input(price, liquidity, U256::ZERO, false);
469        assert_eq!(result, price);
470    }
471
472    #[rstest]
473    fn test_returns_the_minimum_price_for_max_inputs() {
474        let sqrt_p = U160::MAX;
475        let liquidity = u128::MAX;
476        let max_amount_no_overflow = U256::MAX - (U256::from(liquidity) << 96) / U256::from(sqrt_p);
477        let result =
478            get_next_sqrt_price_from_input(sqrt_p, liquidity, max_amount_no_overflow, true);
479        assert_eq!(result, U160::from(1));
480    }
481
482    #[rstest]
483    fn test_input_amount_of_0_1_token1() {
484        let sqrt_q = get_next_sqrt_price_from_input(
485            encode_sqrt_ratio_x96(1, 1),
486            expand_to_18_decimals(1),
487            U256::from(expand_to_18_decimals(1)) / U256::from(10),
488            false,
489        );
490        assert_eq!(
491            sqrt_q,
492            U160::from_str_radix("87150978765690771352898345369", 10).unwrap()
493        );
494    }
495
496    #[rstest]
497    fn test_input_amount_of_0_1_token0() {
498        let sqrt_q = get_next_sqrt_price_from_input(
499            encode_sqrt_ratio_x96(1, 1),
500            expand_to_18_decimals(1),
501            U256::from(expand_to_18_decimals(1)) / U256::from(10),
502            true,
503        );
504        assert_eq!(
505            sqrt_q,
506            U160::from_str_radix("72025602285694852357767227579", 10).unwrap()
507        );
508    }
509
510    #[rstest]
511    fn test_amount_in_greater_than_uint96_max_and_zero_for_one_true() {
512        let result = get_next_sqrt_price_from_input(
513            encode_sqrt_ratio_x96(1, 1),
514            expand_to_18_decimals(10),
515            U256::from(2).pow(U256::from(100)),
516            true,
517        );
518        assert_eq!(
519            result,
520            U160::from_str_radix("624999999995069620", 10).unwrap()
521        );
522    }
523
524    #[rstest]
525    fn test_can_return_1_with_enough_amount_in_and_zero_for_one_true() {
526        let result = get_next_sqrt_price_from_input(
527            encode_sqrt_ratio_x96(1, 1),
528            1,
529            U256::MAX / U256::from(2),
530            true,
531        );
532        assert_eq!(result, U160::from(1));
533    }
534
535    #[rstest]
536    #[should_panic(
537        expected = "Invalid conditions for amount0 removal: overflow or underflow detected"
538    )]
539    fn test_fails_if_output_amount_is_exactly_virtual_reserves_of_token0() {
540        let price = U160::from_str_radix("20282409603651670423947251286016", 10).unwrap();
541        let liquidity = 1024;
542        let amount_out = U256::from(4);
543        let _ = get_next_sqrt_price_from_output(price, liquidity, amount_out, false);
544    }
545
546    #[rstest]
547    #[should_panic(
548        expected = "Invalid conditions for amount0 removal: overflow or underflow detected"
549    )]
550    fn test_fails_if_output_amount_is_greater_than_virtual_reserves_of_token0() {
551        let price = U160::from_str_radix("20282409603651670423947251286016", 10).unwrap();
552        let liquidity = 1024;
553        let amount_out = U256::from(5);
554        let _ = get_next_sqrt_price_from_output(price, liquidity, amount_out, false);
555    }
556
557    #[rstest]
558    #[should_panic(expected = "sqrt_price_x96 must be greater than quotient")]
559    fn test_fails_if_output_amount_is_greater_than_virtual_reserves_of_token1() {
560        let price = U160::from_str_radix("20282409603651670423947251286016", 10).unwrap();
561        let liquidity = 1024;
562        let amount_out = U256::from(262_145);
563        let _ = get_next_sqrt_price_from_output(price, liquidity, amount_out, true);
564    }
565
566    #[rstest]
567    #[should_panic(expected = "sqrt_price_x96 must be greater than quotient")]
568    fn test_fails_if_output_amount_is_exactly_virtual_reserves_of_token1() {
569        let price = U160::from_str_radix("20282409603651670423947251286016", 10).unwrap();
570        let liquidity = 1024;
571        let amount_out = U256::from(262_144);
572        let _ = get_next_sqrt_price_from_output(price, liquidity, amount_out, true);
573    }
574
575    #[rstest]
576    fn test_succeeds_if_output_amount_is_just_less_than_virtual_reserves_of_token1() {
577        let price = U160::from_str_radix("20282409603651670423947251286016", 10).unwrap();
578        let liquidity = 1024;
579        let amount_out = U256::from(262_143);
580        let result = get_next_sqrt_price_from_output(price, liquidity, amount_out, true);
581        assert_eq!(
582            result,
583            U160::from_str_radix("77371252455336267181195264", 10).unwrap()
584        );
585    }
586
587    #[rstest]
588    fn test_returns_input_price_if_amount_out_is_zero_and_zero_for_one_true() {
589        let price = encode_sqrt_ratio_x96(1, 1);
590        let liquidity = expand_to_18_decimals(1) / 10;
591        let result = get_next_sqrt_price_from_output(price, liquidity, U256::ZERO, true);
592        assert_eq!(result, price);
593    }
594
595    #[rstest]
596    fn test_returns_input_price_if_amount_out_is_zero_and_zero_for_one_false() {
597        let price = encode_sqrt_ratio_x96(1, 1);
598        let liquidity = expand_to_18_decimals(1) / 10;
599        let result = get_next_sqrt_price_from_output(price, liquidity, U256::ZERO, false);
600        assert_eq!(result, price);
601    }
602
603    #[rstest]
604    fn test_output_amount_of_0_1_token1_zero_for_one_false() {
605        let sqrt_q = get_next_sqrt_price_from_output(
606            encode_sqrt_ratio_x96(1, 1),
607            expand_to_18_decimals(1),
608            U256::from(expand_to_18_decimals(1)) / U256::from(10),
609            false,
610        );
611        assert_eq!(
612            sqrt_q,
613            U160::from_str_radix("88031291682515930659493278152", 10).unwrap()
614        );
615    }
616
617    #[rstest]
618    fn test_output_amount_of_0_1_token1_zero_for_one_true() {
619        let sqrt_q = get_next_sqrt_price_from_output(
620            encode_sqrt_ratio_x96(1, 1),
621            expand_to_18_decimals(1),
622            U256::from(expand_to_18_decimals(1)) / U256::from(10),
623            true,
624        );
625        assert_eq!(
626            sqrt_q,
627            U160::from_str_radix("71305346262837903834189555302", 10).unwrap()
628        );
629    }
630
631    #[rstest]
632    #[should_panic(expected = "sqrt_price_x96 must be greater than zero")]
633    fn test_if_get_next_sqrt_price_from_output_panic_if_price_zero() {
634        let _ = get_next_sqrt_price_from_output(U160::ZERO, 1, U256::ZERO, true);
635    }
636
637    #[rstest]
638    #[should_panic(expected = "Liquidity must be greater than zero")]
639    fn test_if_get_next_sqrt_price_from_output_panic_if_liquidity_zero() {
640        let _ = get_next_sqrt_price_from_output(U160::from(1), 0, U256::ZERO, true);
641    }
642
643    #[rstest]
644    fn test_encode_sqrt_ratio_x98_some_values() {
645        assert_eq!(encode_sqrt_ratio_x96(1, 1), Q96_U160);
646        assert_eq!(
647            encode_sqrt_ratio_x96(100, 1),
648            U160::from(792_281_625_142_643_375_935_439_503_360_u128)
649        );
650        assert_eq!(
651            encode_sqrt_ratio_x96(1, 100),
652            U160::from(7_922_816_251_426_433_759_354_395_033_u128)
653        );
654        assert_eq!(
655            encode_sqrt_ratio_x96(111, 333),
656            U160::from(45_742_400_955_009_932_534_161_870_629_u128)
657        );
658        assert_eq!(
659            encode_sqrt_ratio_x96(333, 111),
660            U160::from(137_227_202_865_029_797_602_485_611_888_u128)
661        );
662    }
663
664    #[rstest]
665    fn test_get_amount0_delta_returns_0_if_liquidity_is_0() {
666        let amount0 = get_amount0_delta(
667            encode_sqrt_ratio_x96(1, 1),
668            encode_sqrt_ratio_x96(2, 1),
669            0,
670            true,
671        );
672        assert_eq!(amount0, U256::ZERO);
673    }
674
675    #[rstest]
676    fn test_get_amount0_delta_returns_0_if_prices_are_equal() {
677        let amount0 = get_amount0_delta(
678            encode_sqrt_ratio_x96(1, 1),
679            encode_sqrt_ratio_x96(1, 1),
680            0,
681            true,
682        );
683        assert_eq!(amount0, U256::ZERO);
684    }
685
686    #[rstest]
687    fn test_get_amount0_delta_returns_0_1_amount1_for_price_of_1_to_1_21() {
688        let amount0 = get_amount0_delta(
689            encode_sqrt_ratio_x96(1, 1),
690            encode_sqrt_ratio_x96(121, 100),
691            expand_to_18_decimals(1),
692            true,
693        );
694        assert_eq!(
695            amount0,
696            U256::from_str_radix("90909090909090910", 10).unwrap()
697        );
698
699        let amount0_rounded_down = get_amount0_delta(
700            encode_sqrt_ratio_x96(1, 1),
701            encode_sqrt_ratio_x96(121, 100),
702            expand_to_18_decimals(1),
703            false,
704        );
705
706        assert_eq!(amount0_rounded_down, amount0 - U256::from(1));
707    }
708
709    #[rstest]
710    fn test_get_amount0_delta_works_for_prices_that_overflow() {
711        // Create large prices: 2^90 and 2^96
712        let price_low =
713            encode_sqrt_ratio_x96(U256::from(2).pow(U256::from(90)).try_into().unwrap(), 1);
714        let price_high =
715            encode_sqrt_ratio_x96(U256::from(2).pow(U256::from(96)).try_into().unwrap(), 1);
716
717        let amount0_up = get_amount0_delta(price_low, price_high, expand_to_18_decimals(1), true);
718
719        let amount0_down =
720            get_amount0_delta(price_low, price_high, expand_to_18_decimals(1), false);
721
722        assert_eq!(amount0_up, amount0_down + U256::from(1));
723    }
724
725    #[rstest]
726    fn test_get_amount1_delta_returns_0_if_liquidity_is_0() {
727        let amount1 = get_amount1_delta(
728            encode_sqrt_ratio_x96(1, 1),
729            encode_sqrt_ratio_x96(2, 1),
730            0,
731            true,
732        );
733        assert_eq!(amount1, U256::ZERO);
734    }
735
736    #[rstest]
737    fn test_get_amount1_delta_returns_0_if_prices_are_equal() {
738        let amount1 = get_amount1_delta(
739            encode_sqrt_ratio_x96(1, 1),
740            encode_sqrt_ratio_x96(1, 1),
741            0,
742            true,
743        );
744        assert_eq!(amount1, U256::ZERO);
745    }
746
747    #[rstest]
748    fn test_get_amount1_delta_returns_0_1_amount1_for_price_of_1_to_1_21() {
749        let amount1 = get_amount1_delta(
750            encode_sqrt_ratio_x96(1, 1),
751            encode_sqrt_ratio_x96(121, 100),
752            expand_to_18_decimals(1),
753            true,
754        );
755        assert_eq!(
756            amount1,
757            U256::from_str_radix("100000000000000000", 10).unwrap()
758        );
759
760        let amount1_rounded_down = get_amount1_delta(
761            encode_sqrt_ratio_x96(1, 1),
762            encode_sqrt_ratio_x96(121, 100),
763            expand_to_18_decimals(1),
764            false,
765        );
766
767        assert_eq!(amount1_rounded_down, amount1 - U256::from(1));
768    }
769
770    #[rstest]
771    fn test_decode_sqrt_price_x96_to_price_and_decimal_adjustments() {
772        // Use values from https://blog.uniswap.org/uniswap-v3-math-primer
773        let sqrt_price_x96 =
774            U160::from_str_radix("2018382873588440326581633304624437", 10).unwrap();
775
776        let raw_price = decode_sqrt_price_x96_to_price(sqrt_price_x96).unwrap();
777        assert_eq!(raw_price.as_f64(), 649_004_842.701_37);
778
779        // We want the adjusted price inverted as USDC is token0 and WETH is token1
780        let adjusted_price =
781            decode_sqrt_price_x96_to_price_tokens_adjusted(sqrt_price_x96, 6, 18, true).unwrap();
782        assert_eq!(adjusted_price.as_f64(), 1_540.820_552_028_045_8);
783    }
784}