Skip to main content

nautilus_model/defi/tick_map/
liquidity_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 crate::defi::tick_map::tick::PoolTick;
17
18/// Add a signed liquidity delta to liquidity and panic if it overflows or underflows.
19///
20/// # Returns
21///
22/// The resulting liquidity after applying the delta.
23///
24/// # Panics
25///
26/// This function panics if:
27/// - Adding positive delta causes overflow.
28/// - Subtracting causes underflow.
29#[must_use]
30pub fn liquidity_math_add(x: u128, y: i128) -> u128 {
31    if y < 0 {
32        let delta = y.unsigned_abs();
33        let z = x.wrapping_sub(delta);
34        assert!(
35            z < x,
36            "Liquidity subtraction underflow: x={x}, y={y}, delta={delta}, result={z}"
37        );
38        z
39    } else {
40        let delta = y as u128;
41        let z = x.wrapping_add(delta);
42        assert!(
43            z >= x,
44            "Liquidity addition overflow: x={x}, y={y}, delta={delta}, result={z}"
45        );
46        z
47    }
48}
49
50/// Derives max liquidity per tick from a given tick spacing.
51///
52/// # Panics
53///
54/// Panics if `tick_spacing` is zero.
55#[must_use]
56pub fn tick_spacing_to_max_liquidity_per_tick(tick_spacing: i32) -> u128 {
57    assert!(tick_spacing != 0, "Tick spacing must be non-zero");
58
59    // Calculate min and max tick aligned to tick spacing
60    let min_tick = (PoolTick::MIN_TICK / tick_spacing) * tick_spacing;
61    let max_tick = (PoolTick::MAX_TICK / tick_spacing) * tick_spacing;
62
63    // Calculate total number of ticks, cast to i64 to avoid potential overflow in subtraction
64    let num_ticks = ((i64::from(max_tick) - i64::from(min_tick)) / i64::from(tick_spacing)) + 1;
65
66    u128::MAX / num_ticks as u128
67}
68
69#[cfg(test)]
70mod tests {
71    use rstest::rstest;
72
73    use super::*;
74
75    #[rstest]
76    fn test_add() {
77        assert_eq!(liquidity_math_add(1, 0), 1);
78        assert_eq!(liquidity_math_add(1, 1), 2);
79    }
80
81    #[rstest]
82    fn test_subtract_one() {
83        assert_eq!(liquidity_math_add(1, -1), 0);
84        assert_eq!(liquidity_math_add(3, -2), 1);
85    }
86
87    #[rstest]
88    #[should_panic(expected = "Liquidity addition overflow")]
89    fn test_addition_overflow() {
90        let x = u128::MAX - 14; // Close to max so adding 15 will overflow
91        let _ = liquidity_math_add(x, 15);
92    }
93
94    #[rstest]
95    #[should_panic(expected = "Liquidity subtraction underflow")]
96    fn test_subtraction_underflow_zero() {
97        let _ = liquidity_math_add(0, -1);
98    }
99
100    #[rstest]
101    #[should_panic(expected = "Liquidity subtraction underflow")]
102    fn test_subtraction_underflow() {
103        let _ = liquidity_math_add(3, -4);
104    }
105
106    #[rstest]
107    fn test_tick_spacing_to_max_liquidity() {
108        // 0.01 tier ot 1 tick spacing
109        assert_eq!(
110            tick_spacing_to_max_liquidity_per_tick(1),
111            191_757_530_477_355_301_479_181_766_273_477
112        );
113        // 0.05 % tier or 10 tick spacing
114        assert_eq!(
115            tick_spacing_to_max_liquidity_per_tick(10),
116            1_917_569_901_783_203_986_719_870_431_555_990
117        );
118        // 0.3 % tier or 60 tick spacing
119        assert_eq!(
120            tick_spacing_to_max_liquidity_per_tick(60),
121            11_505_743_598_341_114_571_880_798_222_544_994
122        );
123        // 1.00% tier or 200 tick spacing
124        assert_eq!(
125            tick_spacing_to_max_liquidity_per_tick(200),
126            38_350_317_471_085_141_830_651_933_667_504_588
127        );
128    }
129}