nautilus_model/defi/tick_map/liquidity_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 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.
29pub fn liquidity_math_add(x: u128, y: i128) -> u128 {
30 if y < 0 {
31 let delta = (-y) as u128;
32 let z = x.wrapping_sub(delta);
33 assert!(
34 z < x,
35 "Liquidity subtraction underflow: x={}, y={}, delta={}, result={}",
36 x,
37 y,
38 delta,
39 z
40 );
41 z
42 } else {
43 let delta = y as u128;
44 let z = x.wrapping_add(delta);
45 assert!(
46 z >= x,
47 "Liquidity addition overflow: x={}, y={}, delta={}, result={}",
48 x,
49 y,
50 delta,
51 z
52 );
53 z
54 }
55}
56
57/// Derives max liquidity per tick from a given tick spacing
58pub fn tick_spacing_to_max_liquidity_per_tick(tick_spacing: i32) -> u128 {
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 = ((max_tick as i64 - min_tick as i64) / tick_spacing as i64) + 1;
65
66 u128::MAX / num_ticks as u128
67}
68
69////////////////////////////////////////////////////////////////////////////////
70// Tests
71////////////////////////////////////////////////////////////////////////////////
72
73#[cfg(test)]
74mod tests {
75 use rstest::rstest;
76
77 use super::*;
78
79 #[rstest]
80 fn test_add() {
81 assert_eq!(liquidity_math_add(1, 0), 1);
82 assert_eq!(liquidity_math_add(1, 1), 2);
83 }
84
85 #[rstest]
86 fn test_subtract_one() {
87 assert_eq!(liquidity_math_add(1, -1), 0);
88 assert_eq!(liquidity_math_add(3, -2), 1);
89 }
90
91 #[rstest]
92 #[should_panic(expected = "Liquidity addition overflow")]
93 fn test_addition_overflow() {
94 let x = u128::MAX - 14; // Close to max so adding 15 will overflow
95 liquidity_math_add(x, 15);
96 }
97
98 #[rstest]
99 #[should_panic(expected = "Liquidity subtraction underflow")]
100 fn test_subtraction_underflow_zero() {
101 liquidity_math_add(0, -1);
102 }
103
104 #[rstest]
105 #[should_panic(expected = "Liquidity subtraction underflow")]
106 fn test_subtraction_underflow() {
107 liquidity_math_add(3, -4);
108 }
109
110 #[rstest]
111 fn test_tick_spacing_to_max_liquidity() {
112 // 0.01 tier ot 1 tick spacing
113 assert_eq!(
114 tick_spacing_to_max_liquidity_per_tick(1),
115 191757530477355301479181766273477
116 );
117 // 0.05 % tier or 10 tick spacing
118 assert_eq!(
119 tick_spacing_to_max_liquidity_per_tick(10),
120 1917569901783203986719870431555990
121 );
122 // 0.3 % tier or 60 tick spacing
123 assert_eq!(
124 tick_spacing_to_max_liquidity_per_tick(60),
125 11505743598341114571880798222544994
126 );
127 // 1.00% tier or 200 tick spacing
128 assert_eq!(
129 tick_spacing_to_max_liquidity_per_tick(200),
130 38350317471085141830651933667504588
131 );
132 }
133}