1
use crate::exact_math::{multiply_to_raw, ratio_to_tick, select_liquidity_levels, weighted_average_to_tick, LiquidityTarget, RoundingDirection};
2

            
3
use injective_cosmwasm::PriceLevel;
4
use injective_math::FPDecimal;
5
use primitive_types::{U256, U512};
6

            
7
17364
fn raw(value: u128) -> FPDecimal {
8
17364
    FPDecimal::from(U256::from(value))
9
17364
}
10

            
11
6928
fn level(price_raw: u128, quantity_raw: u128) -> PriceLevel {
12
6928
    PriceLevel {
13
6928
        p: raw(price_raw),
14
6928
        q: raw(quantity_raw),
15
6928
    }
16
6928
}
17

            
18
#[test]
19
2
fn weighted_average_rounds_exact_values_around_a_tick() {
20
2
    let tick = raw(100);
21
2

            
22
2
    assert_eq!(weighted_average_to_tick(&[level(99, 1)], tick, RoundingDirection::Up).unwrap(), raw(100));
23
2
    assert_eq!(weighted_average_to_tick(&[level(100, 1)], tick, RoundingDirection::Up).unwrap(), raw(100));
24
2
    assert_eq!(weighted_average_to_tick(&[level(101, 1)], tick, RoundingDirection::Up).unwrap(), raw(200));
25
2
}
26

            
27
#[test]
28
2
fn weighted_average_preserves_the_code_67_witness_remainder() {
29
2
    let levels = vec![
30
2
        PriceLevel {
31
2
            p: raw(100),
32
2
            q: FPDecimal::must_from_str("10000"),
33
2
        },
34
2
        PriceLevel {
35
2
            p: raw(332_900),
36
2
            q: FPDecimal::must_from_str("0.03"),
37
2
        },
38
2
    ];
39
2

            
40
2
    assert_eq!(weighted_average_to_tick(&levels, raw(100), RoundingDirection::Up).unwrap(), raw(200));
41
2
    assert_eq!(weighted_average_to_tick(&levels, raw(100), RoundingDirection::Down).unwrap(), raw(100));
42
2
}
43

            
44
#[test]
45
2
fn weighted_average_does_not_discard_price_quantity_product_remainders() {
46
2
    let levels = vec![level(101, 1)];
47
2

            
48
2
    assert_eq!(weighted_average_to_tick(&levels, raw(100), RoundingDirection::Up).unwrap(), raw(200));
49
2
}
50

            
51
#[test]
52
2
fn notional_clipping_rounds_the_exact_terminal_quantity_to_its_tick() {
53
2
    let levels = vec![level(9_999_999_999_999_999, 1_000)];
54
2

            
55
2
    let selected = select_liquidity_levels(&levels, raw(1), LiquidityTarget::Notional, raw(100)).unwrap();
56
2

            
57
2
    assert_eq!(selected, vec![level(9_999_999_999_999_999, 200)]);
58
2
}
59

            
60
#[test]
61
2
fn quantity_clipping_rounds_directly_to_the_quantity_tick() {
62
2
    let levels = vec![level(100, 1_000)];
63
2

            
64
2
    let selected = select_liquidity_levels(&levels, raw(101), LiquidityTarget::Quantity, raw(100)).unwrap();
65
2

            
66
2
    assert_eq!(selected, vec![level(100, 200)]);
67
2
}
68

            
69
#[test]
70
2
fn ratio_rounds_directly_without_discarding_a_tick_boundary_remainder() {
71
2
    let result = ratio_to_tick(raw(1), raw(9_999_999_999_999_999), raw(100), RoundingDirection::Up).unwrap();
72
2

            
73
2
    assert_eq!(result, raw(200));
74
2
}
75

            
76
#[test]
77
2
fn multiplication_can_round_required_funds_up_at_raw_precision() {
78
2
    assert_eq!(multiply_to_raw(raw(1), raw(1), RoundingDirection::Down).unwrap(), FPDecimal::ZERO);
79
2
    assert_eq!(multiply_to_raw(raw(1), raw(1), RoundingDirection::Up).unwrap(), raw(1));
80
2
}
81

            
82
#[test]
83
2
fn weighted_average_upward_rounding_satisfies_the_exact_invariant() {
84
8
    for tick_raw in [1u128, 10, 100] {
85
30
        for first_tick_count in 1u128..=4 {
86
108
            for second_tick_count in first_tick_count..=6 {
87
540
                for first_quantity_raw in [1u128, 2, 7, 101] {
88
2160
                    for second_quantity_raw in [1u128, 3, 11, 200] {
89
1728
                        let levels = vec![
90
1728
                            level(first_tick_count * tick_raw, first_quantity_raw),
91
1728
                            level(second_tick_count * tick_raw, second_quantity_raw),
92
1728
                        ];
93
1728
                        let rounded = weighted_average_to_tick(&levels, raw(tick_raw), RoundingDirection::Up).unwrap();
94
1728
                        let exact_notional = levels
95
1728
                            .iter()
96
4320
                            .fold(U512::zero(), |sum, item| sum.checked_add(item.p.num.full_mul(item.q.num)).unwrap());
97
1728
                        let total_quantity = levels
98
1728
                            .iter()
99
4320
                            .fold(U512::zero(), |sum, item| sum.checked_add(U512::from(item.q.num)).unwrap());
100
1728
                        let rounded_notional = U512::from(rounded.num).checked_mul(total_quantity).unwrap();
101
1728

            
102
1728
                        assert!(rounded_notional >= exact_notional);
103

            
104
1728
                        let previous_tick = rounded.num - U256::from(tick_raw);
105
1728
                        let previous_notional = U512::from(previous_tick).checked_mul(total_quantity).unwrap();
106
1728
                        assert!(previous_notional < exact_notional);
107
                    }
108
                }
109
            }
110
        }
111
    }
112
2
}
113

            
114
#[test]
115
2
fn weighted_average_downward_rounding_satisfies_the_exact_invariant() {
116
8
    for tick_raw in [1u128, 10, 100] {
117
30
        for first_tick_count in 1u128..=4 {
118
108
            for second_tick_count in first_tick_count..=6 {
119
540
                for first_quantity_raw in [1u128, 2, 7, 101] {
120
2160
                    for second_quantity_raw in [1u128, 3, 11, 200] {
121
1728
                        let levels = vec![
122
1728
                            level(first_tick_count * tick_raw, first_quantity_raw),
123
1728
                            level(second_tick_count * tick_raw, second_quantity_raw),
124
1728
                        ];
125
1728
                        let rounded = weighted_average_to_tick(&levels, raw(tick_raw), RoundingDirection::Down).unwrap();
126
1728
                        let exact_notional = levels
127
1728
                            .iter()
128
4320
                            .fold(U512::zero(), |sum, item| sum.checked_add(item.p.num.full_mul(item.q.num)).unwrap());
129
1728
                        let total_quantity = levels
130
1728
                            .iter()
131
4320
                            .fold(U512::zero(), |sum, item| sum.checked_add(U512::from(item.q.num)).unwrap());
132
1728
                        let rounded_notional = U512::from(rounded.num).checked_mul(total_quantity).unwrap();
133
1728

            
134
1728
                        assert!(rounded_notional <= exact_notional);
135

            
136
1728
                        let next_tick = rounded.num + U256::from(tick_raw);
137
1728
                        let next_notional = U512::from(next_tick).checked_mul(total_quantity).unwrap();
138
1728
                        assert!(next_notional > exact_notional);
139
                    }
140
                }
141
            }
142
        }
143
    }
144
2
}
145

            
146
#[test]
147
2
fn exact_arithmetic_returns_errors_instead_of_overflowing() {
148
2
    let maximum = FPDecimal::from(U256::MAX);
149
2
    let levels = vec![PriceLevel { p: maximum, q: maximum }, PriceLevel { p: maximum, q: maximum }];
150
2

            
151
2
    let error = weighted_average_to_tick(&levels, raw(1), RoundingDirection::Up).unwrap_err();
152
2

            
153
2
    assert!(error.to_string().contains("weighted notional overflow"));
154
2
}