1
use cosmwasm_std::{StdError, StdResult};
2
use injective_cosmwasm::PriceLevel;
3
use injective_math::FPDecimal;
4
use primitive_types::{U256, U512};
5

            
6
#[derive(Clone, Copy)]
7
pub(crate) enum RoundingDirection {
8
    Down,
9
    Up,
10
}
11

            
12
#[derive(Clone, Copy)]
13
pub(crate) enum LiquidityTarget {
14
    Quantity,
15
    Notional,
16
}
17

            
18
36
pub(crate) fn select_liquidity_levels(
19
36
    levels: &[PriceLevel],
20
36
    total: FPDecimal,
21
36
    target: LiquidityTarget,
22
36
    min_quantity_tick_size: FPDecimal,
23
36
) -> StdResult<Vec<PriceLevel>> {
24
36
    ensure_positive(total, "liquidity target")?;
25
36
    ensure_positive(min_quantity_tick_size, "minimum quantity tick size")?;
26

            
27
36
    let target_raw = match target {
28
20
        LiquidityTarget::Quantity => U512::from(total.num),
29
16
        LiquidityTarget::Notional => total.num.full_mul(FPDecimal::ONE.num),
30
    };
31
36
    let mut consumed = U512::zero();
32
36
    let mut selected = Vec::new();
33

            
34
108
    for level in levels {
35
106
        ensure_positive(level.p, "price level price")?;
36
106
        ensure_positive(level.q, "price level quantity")?;
37

            
38
106
        let level_value = match target {
39
58
            LiquidityTarget::Quantity => U512::from(level.q.num),
40
48
            LiquidityTarget::Notional => level.p.num.full_mul(level.q.num),
41
        };
42
106
        let remaining = target_raw
43
106
            .checked_sub(consumed)
44
106
            .ok_or_else(|| arithmetic_error("liquidity target subtraction underflow"))?;
45

            
46
106
        if level_value < remaining {
47
72
            consumed = consumed
48
72
                .checked_add(level_value)
49
72
                .ok_or_else(|| arithmetic_error("liquidity accumulation overflow"))?;
50
72
            selected.push(level.clone());
51
72
            continue;
52
34
        }
53
34

            
54
34
        if level_value == remaining {
55
4
            selected.push(level.clone());
56
4
            return Ok(selected);
57
30
        }
58

            
59
30
        let required_quantity_raw = match target {
60
14
            LiquidityTarget::Quantity => remaining,
61
16
            LiquidityTarget::Notional => checked_ceil_div(remaining, U512::from(level.p.num))?,
62
        };
63
30
        let rounded_quantity_raw = checked_round_up_to_multiple(required_quantity_raw, U512::from(min_quantity_tick_size.num))?;
64

            
65
30
        if rounded_quantity_raw > U512::from(level.q.num) {
66
            return Err(arithmetic_error("rounded terminal quantity exceeds available liquidity"));
67
30
        }
68
30

            
69
30
        selected.push(PriceLevel {
70
30
            p: level.p,
71
30
            q: FPDecimal::from(to_u256(rounded_quantity_raw)?),
72
        });
73
30
        return Ok(selected);
74
    }
75

            
76
2
    Err(StdError::msg("Not enough liquidity to fulfill order"))
77
36
}
78

            
79
3500
pub(crate) fn weighted_average_to_tick(levels: &[PriceLevel], min_price_tick_size: FPDecimal, direction: RoundingDirection) -> StdResult<FPDecimal> {
80
3500
    ensure_positive(min_price_tick_size, "minimum price tick size")?;
81

            
82
3500
    let mut weighted_notional = U512::zero();
83
3500
    let mut total_quantity = U512::zero();
84

            
85
10528
    for level in levels {
86
7030
        ensure_positive(level.p, "price level price")?;
87
7030
        ensure_positive(level.q, "price level quantity")?;
88

            
89
7030
        weighted_notional = weighted_notional
90
7030
            .checked_add(level.p.num.full_mul(level.q.num))
91
7031
            .ok_or_else(|| arithmetic_error("weighted notional overflow"))?;
92
7028
        total_quantity = total_quantity
93
7028
            .checked_add(U512::from(level.q.num))
94
7028
            .ok_or_else(|| arithmetic_error("total quantity overflow"))?;
95
    }
96

            
97
3498
    if total_quantity.is_zero() {
98
        return Err(arithmetic_error("total quantity must be positive"));
99
3498
    }
100

            
101
3498
    let tick_denominator = total_quantity
102
3498
        .checked_mul(U512::from(min_price_tick_size.num))
103
3498
        .ok_or_else(|| arithmetic_error("weighted average tick denominator overflow"))?;
104
3498
    let tick_count = match direction {
105
1746
        RoundingDirection::Down => weighted_notional / tick_denominator,
106
1752
        RoundingDirection::Up => checked_ceil_div(weighted_notional, tick_denominator)?,
107
    };
108
3498
    let rounded_raw = tick_count
109
3498
        .checked_mul(U512::from(min_price_tick_size.num))
110
3498
        .ok_or_else(|| arithmetic_error("rounded weighted average overflow"))?;
111

            
112
3498
    if rounded_raw.is_zero() {
113
        return Err(arithmetic_error("rounded weighted average must be positive"));
114
3498
    }
115
3498

            
116
3498
    Ok(FPDecimal::from(to_u256(rounded_raw)?))
117
3500
}
118

            
119
26
pub(crate) fn ratio_to_tick(numerator: FPDecimal, denominator: FPDecimal, tick: FPDecimal, direction: RoundingDirection) -> StdResult<FPDecimal> {
120
26
    ensure_positive(numerator, "ratio numerator")?;
121
26
    ensure_positive(denominator, "ratio denominator")?;
122
26
    ensure_positive(tick, "ratio tick")?;
123

            
124
26
    let scaled_numerator = numerator.num.full_mul(FPDecimal::ONE.num);
125
26
    let scaled_denominator = denominator.num.full_mul(tick.num);
126
26
    let tick_count = match direction {
127
12
        RoundingDirection::Down => scaled_numerator / scaled_denominator,
128
14
        RoundingDirection::Up => checked_ceil_div(scaled_numerator, scaled_denominator)?,
129
    };
130
26
    let rounded_raw = tick_count
131
26
        .checked_mul(U512::from(tick.num))
132
26
        .ok_or_else(|| arithmetic_error("rounded ratio overflow"))?;
133

            
134
26
    Ok(FPDecimal::from(to_u256(rounded_raw)?))
135
26
}
136

            
137
52
pub(crate) fn multiply_to_raw(left: FPDecimal, right: FPDecimal, direction: RoundingDirection) -> StdResult<FPDecimal> {
138
52
    ensure_non_negative(left, "multiplication left operand")?;
139
52
    ensure_non_negative(right, "multiplication right operand")?;
140

            
141
52
    let product = left.num.full_mul(right.num);
142
52
    let scale = U512::from(FPDecimal::ONE.num);
143
52
    let result_raw = match direction {
144
14
        RoundingDirection::Down => product / scale,
145
38
        RoundingDirection::Up => checked_ceil_div(product, scale)?,
146
    };
147

            
148
52
    Ok(FPDecimal::from(to_u256(result_raw)?))
149
52
}
150

            
151
1850
fn checked_ceil_div(numerator: U512, denominator: U512) -> StdResult<U512> {
152
1850
    if denominator.is_zero() {
153
        return Err(arithmetic_error("division by zero"));
154
1850
    }
155
1850

            
156
1850
    let quotient = numerator / denominator;
157
1850
    if numerator % denominator == U512::zero() {
158
514
        Ok(quotient)
159
    } else {
160
1336
        quotient
161
1336
            .checked_add(U512::one())
162
1336
            .ok_or_else(|| arithmetic_error("division ceiling overflow"))
163
    }
164
1850
}
165

            
166
30
fn checked_round_up_to_multiple(value: U512, tick: U512) -> StdResult<U512> {
167
30
    checked_ceil_div(value, tick)?
168
30
        .checked_mul(tick)
169
30
        .ok_or_else(|| arithmetic_error("tick rounding overflow"))
170
30
}
171

            
172
17922
fn ensure_positive(value: FPDecimal, name: &str) -> StdResult<()> {
173
17922
    if value.is_zero() || value.is_negative() {
174
        return Err(arithmetic_error(format!("{name} must be positive")));
175
17922
    }
176
17922
    Ok(())
177
17922
}
178

            
179
104
fn ensure_non_negative(value: FPDecimal, name: &str) -> StdResult<()> {
180
104
    if value.is_negative() {
181
        return Err(arithmetic_error(format!("{name} must not be negative")));
182
104
    }
183
104
    Ok(())
184
104
}
185

            
186
3606
fn to_u256(value: U512) -> StdResult<U256> {
187
3606
    U256::try_from(value).map_err(|_| arithmetic_error("result does not fit in FPDecimal"))
188
3606
}
189

            
190
2
fn arithmetic_error(message: impl Into<String>) -> StdError {
191
2
    StdError::msg(format!("exact arithmetic error: {}", message.into()))
192
2
}