injective_math/
utils.rs

1use crate::FPDecimal;
2use primitive_types::U256;
3
4use cosmwasm_std::StdError;
5use std::cmp::Ordering;
6use std::{fmt::Display, str::FromStr};
7
8#[derive(Default)]
9pub enum RangeEnds {
10    #[default]
11    BothInclusive,
12    MinInclusive,
13    MaxInclusive,
14    Exclusive,
15}
16
17pub fn parse_dec(vs: &str, min: Option<&FPDecimal>, max: Option<&FPDecimal>, range_ends: RangeEnds) -> Result<FPDecimal, StdError> {
18    let v = FPDecimal::must_from_str(vs);
19    ensure_band(&v, min, max, range_ends)?;
20    Ok(v)
21}
22
23pub fn parse_int<T: FromStr + Ord + Display>(vs: &str, min: Option<&T>, max: Option<&T>, range_ends: RangeEnds) -> Result<T, StdError>
24where
25    <T as FromStr>::Err: ToString,
26{
27    match vs.parse::<T>() {
28        Ok(v) => {
29            ensure_band(&v, min, max, range_ends)?;
30            Ok(v)
31        }
32        Err(e) => Err(StdError::generic_err(e.to_string())),
33    }
34}
35
36pub fn ensure_band<T: Ord + Display>(v: &T, min: Option<&T>, max: Option<&T>, range_ends: RangeEnds) -> Result<(), StdError> {
37    if let Some(minv) = min {
38        match range_ends {
39            RangeEnds::BothInclusive | RangeEnds::MinInclusive => {
40                if v < minv {
41                    return Err(StdError::generic_err(format!("value {v} must be >= {minv}")));
42                }
43            }
44            RangeEnds::MaxInclusive | RangeEnds::Exclusive => {
45                if v <= minv {
46                    return Err(StdError::generic_err(format!("value {v} must be > {minv}")));
47                }
48            }
49        }
50    }
51    if let Some(maxv) = max {
52        match range_ends {
53            RangeEnds::BothInclusive | RangeEnds::MaxInclusive => {
54                if v > maxv {
55                    return Err(StdError::generic_err(format!("value {v} must be <= {maxv}")));
56                }
57            }
58            RangeEnds::MinInclusive | RangeEnds::Exclusive => {
59                if v >= maxv {
60                    return Err(StdError::generic_err(format!("value {v} must be < {maxv}")));
61                }
62            }
63        }
64    }
65    Ok(())
66}
67
68pub fn band_error_to_human(err: StdError, value_name: &str) -> StdError {
69    StdError::generic_err(format!("Value '{value_name}' failed validation due to: '{err}'"))
70}
71
72pub fn div_dec(num: FPDecimal, denom: FPDecimal) -> FPDecimal {
73    if denom == FPDecimal::ZERO {
74        denom
75    } else {
76        num / denom
77    }
78}
79
80pub fn floor(num: FPDecimal, min_tick: FPDecimal) -> FPDecimal {
81    // min_tick has to be a positive number
82    assert!(min_tick >= FPDecimal::ZERO);
83    if num.is_zero() {
84        return num;
85    }
86    let remainder = num % min_tick;
87    num - remainder
88}
89
90pub fn round(num: FPDecimal, min_tick: FPDecimal) -> FPDecimal {
91    if min_tick < FPDecimal::must_from_str("0.00000001") {
92        panic!("min_tick should be greater than {}", FPDecimal::must_from_str("0.00000001"));
93    }
94    let num_floor = floor(num, min_tick);
95    let diff = num - num_floor;
96    match diff.cmp(&(min_tick / FPDecimal::TWO)) {
97        Ordering::Less => num_floor,
98        Ordering::Equal => {
99            if num_floor / (min_tick * FPDecimal::TWO) == FPDecimal::ZERO {
100                num_floor
101            } else {
102                num_floor + min_tick
103            }
104        }
105        Ordering::Greater => num_floor + min_tick,
106    }
107}
108
109pub fn round_to_min_tick(num: FPDecimal, min_tick: FPDecimal) -> FPDecimal {
110    if num < min_tick {
111        FPDecimal::ZERO
112    } else {
113        let shifted = div_dec(num, min_tick).int();
114        shifted * min_tick
115    }
116}
117
118pub fn round_to_nearest_tick(num: FPDecimal, min_tick: FPDecimal) -> FPDecimal {
119    if num < min_tick {
120        return FPDecimal::ZERO;
121    }
122
123    let remainder = FPDecimal::from(num.num % min_tick.num);
124    if remainder.num > min_tick.num / U256::from(2u64) {
125        FPDecimal::from(num.num - remainder.num + min_tick.num)
126    } else {
127        FPDecimal::from(num.num - remainder.num)
128    }
129}
130
131pub fn round_up_to_min_tick(num: FPDecimal, min_tick: FPDecimal) -> FPDecimal {
132    if num < min_tick {
133        return min_tick;
134    }
135
136    let remainder = FPDecimal::from(num.num % min_tick.num);
137
138    if remainder.num.is_zero() {
139        return num;
140    }
141
142    FPDecimal::from(num.num - remainder.num + min_tick.num)
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148    use crate::fp_decimal::scale::Scaled;
149
150    #[test]
151    fn test_floor() {
152        assert_eq!(floor(FPDecimal::must_from_str("0"), FPDecimal::must_from_str("0.1")), FPDecimal::ZERO);
153        assert_eq!(
154            floor(FPDecimal::must_from_str("0.13"), FPDecimal::must_from_str("0.1")),
155            FPDecimal::must_from_str("0.1")
156        );
157        assert_eq!(
158            floor(FPDecimal::must_from_str("0.19"), FPDecimal::must_from_str("0.1")),
159            FPDecimal::must_from_str("0.1")
160        );
161        assert_eq!(
162            floor(FPDecimal::must_from_str("1.19"), FPDecimal::must_from_str("0.1")),
163            FPDecimal::must_from_str("1.1")
164        );
165        assert_eq!(floor(FPDecimal::must_from_str("2.19"), FPDecimal::ONE), FPDecimal::TWO);
166
167        assert_eq!(floor(FPDecimal::must_from_str("-0"), FPDecimal::must_from_str("0.1")), FPDecimal::ZERO);
168        assert_eq!(
169            floor(FPDecimal::must_from_str("-0.13"), FPDecimal::must_from_str("0.1")),
170            FPDecimal::must_from_str("-0.2")
171        );
172        assert_eq!(
173            floor(FPDecimal::must_from_str("-0.19"), FPDecimal::must_from_str("0.1")),
174            FPDecimal::must_from_str("-0.2")
175        );
176        assert_eq!(
177            floor(FPDecimal::must_from_str("-1.19"), FPDecimal::must_from_str("0.1")),
178            FPDecimal::must_from_str("-1.2")
179        );
180        assert_eq!(
181            floor(FPDecimal::must_from_str("-2.19"), FPDecimal::must_from_str("0.1")),
182            FPDecimal::must_from_str("-2.2")
183        );
184
185        assert_eq!(floor(FPDecimal::must_from_str("-2.19"), FPDecimal::ONE), FPDecimal::must_from_str("-3"));
186    }
187
188    #[test]
189    fn test_round() {
190        assert_eq!(round(FPDecimal::must_from_str("0.13"), FPDecimal::ONE), FPDecimal::ZERO);
191        assert_eq!(round(FPDecimal::must_from_str("0.49"), FPDecimal::ONE), FPDecimal::ZERO);
192        assert_eq!(round(FPDecimal::must_from_str("0.5"), FPDecimal::ONE), FPDecimal::ZERO);
193        assert_eq!(round(FPDecimal::must_from_str("0.50009"), FPDecimal::ONE), FPDecimal::ONE);
194
195        assert_eq!(round(FPDecimal::must_from_str("-0.13"), FPDecimal::ONE), FPDecimal::ZERO);
196        assert_eq!(round(FPDecimal::must_from_str("-0.49"), FPDecimal::ONE), FPDecimal::ZERO);
197        assert_eq!(round(FPDecimal::must_from_str("-0.5"), FPDecimal::ONE), FPDecimal::ZERO);
198        assert_eq!(round(FPDecimal::must_from_str("-0.51"), FPDecimal::ONE), -FPDecimal::ONE);
199        assert_eq!(round(FPDecimal::must_from_str("-1.50009"), FPDecimal::ONE), -FPDecimal::TWO);
200    }
201
202    #[test]
203    fn test_round_with_scaled_numbers() {
204        assert_eq!(round(FPDecimal::must_from_str("0"), FPDecimal::must_from_str("0.1")), FPDecimal::ZERO);
205        assert_eq!(
206            round(FPDecimal::must_from_str("0.13"), FPDecimal::must_from_str("0.1")),
207            FPDecimal::must_from_str("0.1")
208        );
209        assert_eq!(
210            round(FPDecimal::must_from_str("0.50009"), FPDecimal::must_from_str("0.0001")),
211            FPDecimal::must_from_str("0.5001")
212        );
213
214        assert_eq!(round(FPDecimal::must_from_str("-0"), FPDecimal::must_from_str("0.1")), FPDecimal::ZERO);
215        assert_eq!(
216            round(FPDecimal::must_from_str("-0.13"), FPDecimal::must_from_str("0.1")),
217            FPDecimal::must_from_str("-0.1")
218        );
219        assert_eq!(
220            round(FPDecimal::must_from_str("-0.50009"), FPDecimal::must_from_str("0.0001")),
221            FPDecimal::must_from_str("-0.5001")
222        );
223
224        assert_eq!(round(FPDecimal::must_from_str("-1.50009"), FPDecimal::ONE.scaled(1)), FPDecimal::ZERO);
225        assert_eq!(
226            round(FPDecimal::must_from_str("-1.50009").scaled(1), FPDecimal::ONE.scaled(1)),
227            -FPDecimal::TWO.scaled(1)
228        );
229        assert_eq!(round(FPDecimal::must_from_str("-1.50009"), FPDecimal::ONE.scaled(1)), FPDecimal::ZERO);
230        assert_eq!(
231            round(FPDecimal::must_from_str("-1.50009").scaled(1), FPDecimal::ONE.scaled(1)),
232            -FPDecimal::TWO.scaled(1)
233        );
234    }
235
236    #[test]
237    fn test_div_dec() {
238        assert_eq!(
239            div_dec(FPDecimal::must_from_str("6"), FPDecimal::must_from_str("2")),
240            FPDecimal::must_from_str("3")
241        );
242        assert_eq!(
243            div_dec(FPDecimal::must_from_str("7"), FPDecimal::must_from_str("0")),
244            FPDecimal::must_from_str("0")
245        );
246        assert_eq!(
247            div_dec(FPDecimal::must_from_str("7.5"), FPDecimal::must_from_str("2.5")),
248            FPDecimal::must_from_str("3.0")
249        );
250    }
251
252    #[test]
253    fn test_round_to_min_tick() {
254        assert_eq!(
255            round_to_min_tick(FPDecimal::must_from_str("7.7"), FPDecimal::must_from_str("2.0")),
256            FPDecimal::must_from_str("6.0")
257        );
258        assert_eq!(
259            round_to_min_tick(FPDecimal::must_from_str("1.5"), FPDecimal::must_from_str("2.0")),
260            FPDecimal::must_from_str("0.0")
261        );
262        assert_eq!(
263            round_to_min_tick(FPDecimal::must_from_str("10.0"), FPDecimal::must_from_str("3.0")),
264            FPDecimal::must_from_str("9.0")
265        );
266    }
267
268    #[test]
269    fn round_to_nearest_tick_test() {
270        assert_eq!(
271            round_to_nearest_tick(FPDecimal::must_from_str("7.7"), FPDecimal::must_from_str("2.0")),
272            FPDecimal::must_from_str("8.0")
273        );
274        assert_eq!(
275            round_to_nearest_tick(FPDecimal::must_from_str("1.5"), FPDecimal::must_from_str("2.0")),
276            FPDecimal::must_from_str("0.0")
277        );
278        assert_eq!(
279            round_to_nearest_tick(FPDecimal::must_from_str("2.5"), FPDecimal::must_from_str("2.0")),
280            FPDecimal::must_from_str("2.0")
281        );
282        assert_eq!(
283            round_to_nearest_tick(
284                FPDecimal::must_from_str("0.000000057575228461"),
285                FPDecimal::must_from_str("0.00000000000001")
286            ),
287            FPDecimal::must_from_str("0.00000005757523")
288        );
289
290        assert_eq!(
291            round_to_nearest_tick(FPDecimal::must_from_str("10.0"), FPDecimal::must_from_str("3.0")),
292            FPDecimal::must_from_str("9.0")
293        );
294        // input, expected
295        let data = vec![
296            ["1.09932", "1.1"],
297            ["2.032", "2.03"],
298            ["1.0009932", "1"],
299            ["1.009932", "1.01"],
300            ["0.9932", "0.99"],
301        ];
302        let precision = FPDecimal::must_from_str("0.01");
303
304        for item in &data {
305            let input = FPDecimal::must_from_str(item[0]);
306            let expected = FPDecimal::must_from_str(item[1]);
307
308            let output = round_to_nearest_tick(input, precision);
309            assert_eq!(expected, output);
310        }
311    }
312
313    #[test]
314    fn test_round_up_to_min_tick() {
315        let num = FPDecimal::from(37u128);
316        let min_tick = FPDecimal::from(10u128);
317
318        let result = round_up_to_min_tick(num, min_tick);
319        assert_eq!(result, FPDecimal::from(40u128));
320
321        let num = FPDecimal::must_from_str("0.00000153");
322        let min_tick = FPDecimal::must_from_str("0.000001");
323
324        let result = round_up_to_min_tick(num, min_tick);
325        assert_eq!(result, FPDecimal::must_from_str("0.000002"));
326
327        let num = FPDecimal::must_from_str("0.000001");
328        let min_tick = FPDecimal::must_from_str("0.000001");
329
330        let result = round_up_to_min_tick(num, min_tick);
331        assert_eq!(result, FPDecimal::must_from_str("0.000001"));
332
333        let num = FPDecimal::must_from_str("0.0000001");
334        let min_tick = FPDecimal::must_from_str("0.000001");
335
336        let result = round_up_to_min_tick(num, min_tick);
337        assert_eq!(result, FPDecimal::must_from_str("0.000001"));
338    }
339}