kabu_defi_uniswap_v3_math/
tick_math.rs

1use alloy::primitives::{I256, U256};
2use std::ops::{BitOr, Neg, Shl, Shr};
3
4use crate::{
5    error::UniswapV3MathError, U256_1, U256_1024, U256_127, U256_128, U256_131072, U256_15, U256_16, U256_16384, U256_2, U256_2048,
6    U256_255, U256_256, U256_262144, U256_3, U256_32, U256_32768, U256_4, U256_4096, U256_5, U256_512, U256_524288, U256_6, U256_64,
7    U256_65536, U256_7, U256_8, U256_8192, U256_MAX_TICK,
8};
9
10pub const MIN_TICK: i32 = -887272;
11pub const MAX_TICK: i32 = -MIN_TICK;
12
13pub const MIN_SQRT_RATIO: U256 = U256::from_limbs([4295128739, 0, 0, 0]);
14pub const MAX_SQRT_RATIO: U256 = U256::from_limbs([6743328256752651558, 17280870778742802505, 4294805859, 0]);
15
16pub const SQRT_10001: I256 = I256::from_raw(U256::from_limbs([11745905768312294533, 13863, 0, 0]));
17pub const TICK_LOW: I256 = I256::from_raw(U256::from_limbs([6552757943157144234, 184476617836266586, 0, 0]));
18pub const TICK_HIGH: I256 = I256::from_raw(U256::from_limbs([4998474450511881007, 15793544031827761793, 0, 0]));
19
20pub fn get_sqrt_ratio_at_tick(tick: i32) -> Result<U256, UniswapV3MathError> {
21    let abs_tick = if tick < 0 { U256::from(tick.neg()) } else { U256::from(tick) };
22
23    if abs_tick > U256_MAX_TICK {
24        return Err(UniswapV3MathError::T);
25    }
26
27    let mut ratio = if abs_tick & (U256_1) != U256::ZERO {
28        U256::from_limbs([12262481743371124737, 18445821805675392311, 0, 0])
29    } else {
30        U256::from_limbs([0, 0, 1, 0])
31    };
32
33    if !(abs_tick & U256_2).is_zero() {
34        ratio = (ratio * U256::from_limbs([6459403834229662010, 18444899583751176498, 0, 0])) >> 128
35    }
36    if !(abs_tick & U256_4).is_zero() {
37        ratio = (ratio * U256::from_limbs([17226890335427755468, 18443055278223354162, 0, 0])) >> 128
38    }
39    if !(abs_tick & U256_8).is_zero() {
40        ratio = (ratio * U256::from_limbs([2032852871939366096, 18439367220385604838, 0, 0])) >> 128
41    }
42    if !(abs_tick & U256_16).is_zero() {
43        ratio = (ratio * U256::from_limbs([14545316742740207172, 18431993317065449817, 0, 0])) >> 128
44    }
45    if !(abs_tick & U256_32).is_zero() {
46        ratio = (ratio * U256::from_limbs([5129152022828963008, 18417254355718160513, 0, 0])) >> 128
47    }
48    if !(abs_tick & U256_64).is_zero() {
49        ratio = (ratio * U256::from_limbs([4894419605888772193, 18387811781193591352, 0, 0])) >> 128
50    }
51    if !(abs_tick & U256_128).is_zero() {
52        ratio = (ratio * U256::from_limbs([1280255884321894483, 18329067761203520168, 0, 0])) >> 128
53    }
54    if !(abs_tick & U256_256).is_zero() {
55        ratio = (ratio * U256::from_limbs([15924666964335305636, 18212142134806087854, 0, 0])) >> 128
56    }
57    if !(abs_tick & U256_512).is_zero() {
58        ratio = (ratio * U256::from_limbs([8010504389359918676, 17980523815641551639, 0, 0])) >> 128
59    }
60    if !(abs_tick & U256_1024).is_zero() {
61        ratio = (ratio * U256::from_limbs([10668036004952895731, 17526086738831147013, 0, 0])) >> 128
62    }
63    if !(abs_tick & U256_2048).is_zero() {
64        ratio = (ratio * U256::from_limbs([4878133418470705625, 16651378430235024244, 0, 0])) >> 128
65    }
66    if !(abs_tick & U256_4096).is_zero() {
67        ratio = (ratio * U256::from_limbs([9537173718739605541, 15030750278693429944, 0, 0])) >> 128
68    }
69    if !(abs_tick & U256_8192).is_zero() {
70        ratio = (ratio * U256::from_limbs([9972618978014552549, 12247334978882834399, 0, 0])) >> 128
71    }
72    if !(abs_tick & U256_16384).is_zero() {
73        ratio = (ratio * U256::from_limbs([10428997489610666743, 8131365268884726200, 0, 0])) >> 128
74    }
75    if !(abs_tick & U256_32768).is_zero() {
76        ratio = (ratio * U256::from_limbs([9305304367709015974, 3584323654723342297, 0, 0])) >> 128
77    }
78    if !(abs_tick & U256_65536).is_zero() {
79        ratio = (ratio * U256::from_limbs([14301143598189091785, 696457651847595233, 0, 0])) >> 128
80    }
81    if !(abs_tick & U256_131072).is_zero() {
82        ratio = (ratio * U256::from_limbs([7393154844743099908, 26294789957452057, 0, 0])) >> 128
83    }
84    if !(abs_tick & U256_262144).is_zero() {
85        ratio = (ratio * U256::from_limbs([2209338891292245656, 37481735321082, 0, 0])) >> 128
86    }
87    if !(abs_tick & U256_524288).is_zero() {
88        ratio = (ratio * U256::from_limbs([10518117631919034274, 76158723, 0, 0])) >> 128
89    }
90
91    if tick > 0 {
92        ratio = U256::MAX / ratio;
93    }
94
95    Ok((ratio >> 32) + if (ratio.wrapping_rem(U256_1 << 32)).is_zero() { U256::ZERO } else { U256_1 })
96}
97
98pub fn get_tick_at_sqrt_ratio(sqrt_price_x_96: U256) -> Result<i32, UniswapV3MathError> {
99    if !(sqrt_price_x_96 >= MIN_SQRT_RATIO && sqrt_price_x_96 < MAX_SQRT_RATIO) {
100        return Err(UniswapV3MathError::R);
101    }
102
103    let ratio: U256 = sqrt_price_x_96.shl(32);
104    let mut r = ratio;
105    let mut msb = U256::ZERO;
106
107    let mut f = if r > U256::from_limbs([18446744073709551615, 18446744073709551615, 0, 0]) { U256_1.shl(U256_7) } else { U256::ZERO };
108    msb = msb.bitor(f);
109    r = r.shr(f);
110
111    f = if r > U256::from_limbs([18446744073709551615, 0, 0, 0]) { U256_1.shl(U256_6) } else { U256::ZERO };
112    msb = msb.bitor(f);
113    r = r.shr(f);
114
115    f = if r > U256::from_limbs([4294967295, 0, 0, 0]) { U256_1.shl(U256_5) } else { U256::ZERO };
116    msb = msb.bitor(f);
117    r = r.shr(f);
118
119    f = if r > U256::from_limbs([65535, 0, 0, 0]) { U256_1.shl(U256_4) } else { U256::ZERO };
120    msb = msb.bitor(f);
121    r = r.shr(f);
122
123    f = if r > U256_255 { U256_1.shl(U256_3) } else { U256::ZERO };
124    msb = msb.bitor(f);
125    r = r.shr(f);
126
127    f = if r > U256_15 { U256_1.shl(U256_2) } else { U256::ZERO };
128    msb = msb.bitor(f);
129    r = r.shr(f);
130
131    f = if r > U256_3 { U256_1.shl(U256_1) } else { U256::ZERO };
132    msb = msb.bitor(f);
133    r = r.shr(f);
134
135    f = if r > U256_1 { U256_1 } else { U256::ZERO };
136
137    msb = msb.bitor(f);
138
139    r = if msb >= U256_128 { ratio.shr(msb - U256_127) } else { ratio.shl(U256_127 - msb) };
140
141    let mut log_2: I256 = (I256::from_raw(msb) - I256::from_limbs([128, 0, 0, 0])).shl(64);
142
143    for i in (51..=63).rev() {
144        r = r.overflowing_mul(r).0.shr(U256_127);
145        let f: U256 = r.shr(128);
146        log_2 = log_2.bitor(I256::from_raw(f.shl(i)));
147
148        r = r.shr(f);
149    }
150
151    r = r.overflowing_mul(r).0.shr(U256_127);
152    let f: U256 = r.shr(128);
153    log_2 = log_2.bitor(I256::from_raw(f.shl(50)));
154
155    let log_sqrt10001 = log_2.wrapping_mul(SQRT_10001);
156
157    let tick_low = ((log_sqrt10001 - TICK_LOW) >> 128_u8).low_i32();
158
159    let tick_high = ((log_sqrt10001 + TICK_HIGH) >> 128_u8).low_i32();
160
161    let tick = if tick_low == tick_high {
162        tick_low
163    } else if get_sqrt_ratio_at_tick(tick_high)? <= sqrt_price_x_96 {
164        tick_high
165    } else {
166        tick_low
167    };
168
169    Ok(tick)
170}
171
172#[cfg(test)]
173mod test {
174    use super::*;
175    use std::{ops::Sub, str::FromStr};
176
177    #[test]
178    fn test_get_sqrt_ratio_at_tick_bounds() {
179        // the function should return an error if the tick is out of bounds
180        if let Err(err) = get_sqrt_ratio_at_tick(MIN_TICK - 1) {
181            assert!(matches!(err, UniswapV3MathError::T));
182        } else {
183            panic!("get_qrt_ratio_at_tick did not respect lower tick bound")
184        }
185        if let Err(err) = get_sqrt_ratio_at_tick(MAX_TICK + 1) {
186            assert!(matches!(err, UniswapV3MathError::T));
187        } else {
188            panic!("get_qrt_ratio_at_tick did not respect upper tick bound")
189        }
190    }
191
192    #[test]
193    fn test_get_sqrt_ratio_at_tick_values() {
194        // test individual values for correct results
195        assert_eq!(get_sqrt_ratio_at_tick(MIN_TICK).unwrap(), U256::from(4295128739u64), "sqrt ratio at min incorrect");
196        assert_eq!(get_sqrt_ratio_at_tick(MIN_TICK + 1).unwrap(), U256::from(4295343490u64), "sqrt ratio at min + 1 incorrect");
197        assert_eq!(
198            get_sqrt_ratio_at_tick(MAX_TICK - 1).unwrap(),
199            U256::from_str("1461373636630004318706518188784493106690254656249").unwrap(),
200            "sqrt ratio at max - 1 incorrect"
201        );
202        assert_eq!(
203            get_sqrt_ratio_at_tick(MAX_TICK).unwrap(),
204            U256::from_str("1461446703485210103287273052203988822378723970342").unwrap(),
205            "sqrt ratio at max incorrect"
206        );
207        // checking hard coded values against solidity results
208        assert_eq!(get_sqrt_ratio_at_tick(50).unwrap(), U256::from(79426470787362580746886972461u128), "sqrt ratio at 50 incorrect");
209        assert_eq!(get_sqrt_ratio_at_tick(100).unwrap(), U256::from(79625275426524748796330556128u128), "sqrt ratio at 100 incorrect");
210        assert_eq!(get_sqrt_ratio_at_tick(250).unwrap(), U256::from(80224679980005306637834519095u128), "sqrt ratio at 250 incorrect");
211        assert_eq!(get_sqrt_ratio_at_tick(500).unwrap(), U256::from(81233731461783161732293370115u128), "sqrt ratio at 500 incorrect");
212        assert_eq!(get_sqrt_ratio_at_tick(1000).unwrap(), U256::from(83290069058676223003182343270u128), "sqrt ratio at 1000 incorrect");
213        assert_eq!(get_sqrt_ratio_at_tick(2500).unwrap(), U256::from(89776708723587163891445672585u128), "sqrt ratio at 2500 incorrect");
214        assert_eq!(get_sqrt_ratio_at_tick(3000).unwrap(), U256::from(92049301871182272007977902845u128), "sqrt ratio at 3000 incorrect");
215        assert_eq!(get_sqrt_ratio_at_tick(4000).unwrap(), U256::from(96768528593268422080558758223u128), "sqrt ratio at 4000 incorrect");
216        assert_eq!(get_sqrt_ratio_at_tick(5000).unwrap(), U256::from(101729702841318637793976746270u128), "sqrt ratio at 5000 incorrect");
217        assert_eq!(get_sqrt_ratio_at_tick(50000).unwrap(), U256::from(965075977353221155028623082916u128), "sqrt ratio at 50000 incorrect");
218        assert_eq!(
219            get_sqrt_ratio_at_tick(150000).unwrap(),
220            U256::from(143194173941309278083010301478497u128),
221            "sqrt ratio at 150000 incorrect"
222        );
223        assert_eq!(
224            get_sqrt_ratio_at_tick(250000).unwrap(),
225            U256::from(21246587762933397357449903968194344u128),
226            "sqrt ratio at 250000 incorrect"
227        );
228        assert_eq!(
229            get_sqrt_ratio_at_tick(500000).unwrap(),
230            U256::from_str("5697689776495288729098254600827762987878").unwrap(),
231            "sqrt ratio at 500000 incorrect"
232        );
233        assert_eq!(
234            get_sqrt_ratio_at_tick(738203).unwrap(),
235            U256::from_str("847134979253254120489401328389043031315994541").unwrap(),
236            "sqrt ratio at 738203 incorrect"
237        );
238    }
239
240    #[test]
241    pub fn test_get_tick_at_sqrt_ratio() {
242        //throws for too low
243        let result = get_tick_at_sqrt_ratio(MIN_SQRT_RATIO.sub(U256_1));
244        assert_eq!(
245            result.unwrap_err().to_string(),
246            "Second inequality must be < because the price can never reach the price at the max tick"
247        );
248
249        //throws for too high
250        let result = get_tick_at_sqrt_ratio(MAX_SQRT_RATIO);
251        assert_eq!(
252            result.unwrap_err().to_string(),
253            "Second inequality must be < because the price can never reach the price at the max tick"
254        );
255
256        //ratio of min tick
257        let result = get_tick_at_sqrt_ratio(MIN_SQRT_RATIO).unwrap();
258        assert_eq!(result, MIN_TICK);
259
260        //ratio of min tick + 1
261        let result = get_tick_at_sqrt_ratio(U256::from_str("4295343490").unwrap()).unwrap();
262        assert_eq!(result, MIN_TICK + 1);
263    }
264}