gmsol_model/
utils.rs

1use std::cmp::Ordering;
2
3use crate::{
4    fixed::{Fixed, FixedPointOps},
5    num::{MulDiv, Num},
6};
7
8use num_traits::{CheckedMul, One, Zero};
9
10/// Usd value to market token amount.
11///
12/// Returns `None` if the computation cannot be done.
13pub fn usd_to_market_token_amount<T>(
14    usd_value: T,
15    pool_value: T,
16    supply: T,
17    usd_to_amount_divisor: T,
18) -> Option<T>
19where
20    T: MulDiv + Num,
21{
22    if usd_to_amount_divisor.is_zero() {
23        return None;
24    }
25    if supply.is_zero() && pool_value.is_zero() {
26        usd_value.checked_div(&usd_to_amount_divisor)
27    } else if supply.is_zero() && !pool_value.is_zero() {
28        pool_value
29            .checked_add(&usd_value)?
30            .checked_div(&usd_to_amount_divisor)
31    } else {
32        supply.checked_mul_div(&usd_value, &pool_value)
33    }
34}
35
36/// Market token amount to usd value.
37///
38/// Returns `None` if the computation cannot be done or `supply` is zero.
39pub fn market_token_amount_to_usd<T>(amount: &T, pool_value: &T, supply: &T) -> Option<T>
40where
41    T: MulDiv,
42{
43    pool_value.checked_mul_div(amount, supply)
44}
45
46/// Apply factors using this formula: `A * x^E`.
47///
48/// Assuming that all values are "float"s with the same decimals.
49pub fn apply_factors<T, const DECIMALS: u8>(
50    value: T,
51    factor: T,
52    exponent_factor: T,
53) -> crate::Result<T>
54where
55    T: FixedPointOps<DECIMALS>,
56{
57    Ok(apply_exponent_factor_wrapped(value, exponent_factor)
58        .ok_or(crate::Error::PowComputation)?
59        .checked_mul(&Fixed::from_inner(factor))
60        .ok_or(crate::Error::Overflow)?
61        .into_inner())
62}
63
64fn apply_exponent_factor_wrapped<T, const DECIMALS: u8>(
65    value: T,
66    exponent_factor: T,
67) -> Option<Fixed<T, DECIMALS>>
68where
69    T: FixedPointOps<DECIMALS>,
70{
71    let unit = Fixed::ONE;
72    let value = Fixed::from_inner(value);
73    let exponent = Fixed::from_inner(exponent_factor);
74
75    let ans = match value.cmp(&unit) {
76        Ordering::Less => Fixed::zero(),
77        Ordering::Equal => unit,
78        Ordering::Greater => {
79            if exponent.is_zero() {
80                unit
81            } else if exponent.is_one() {
82                value
83            } else {
84                value.checked_pow(&exponent)?
85            }
86        }
87    };
88    Some(ans)
89}
90
91/// Apply exponent factor using this formula: `x^E`.
92///
93/// Assuming that all values are "float"s with the same decimals.
94#[inline]
95pub fn apply_exponent_factor<T, const DECIMALS: u8>(value: T, exponent_factor: T) -> Option<T>
96where
97    T: FixedPointOps<DECIMALS>,
98{
99    Some(apply_exponent_factor_wrapped(value, exponent_factor)?.into_inner())
100}
101
102/// Apply factor using this formula: `A * x`.
103///
104/// Assuming that `value` and `factor` are a fixed-point decimals,
105/// but they do not need to be of the same decimals.
106/// The const type `DECIMALS` is the decimals of `factor`.
107#[inline]
108pub fn apply_factor<T, const DECIMALS: u8>(value: &T, factor: &T) -> Option<T>
109where
110    T: FixedPointOps<DECIMALS>,
111{
112    value.checked_mul_div(factor, &FixedPointOps::UNIT)
113}
114
115/// Convert the `value` to a factor after dividing by the `divisor`.
116///
117/// ## Notes
118/// - Return `zero` if the `divisor` is zero.
119#[inline]
120pub fn div_to_factor<T, const DECIMALS: u8>(
121    value: &T,
122    divisor: &T,
123    round_up_magnitude: bool,
124) -> Option<T>
125where
126    T: FixedPointOps<DECIMALS>,
127{
128    if divisor.is_zero() {
129        return Some(T::zero());
130    }
131
132    if round_up_magnitude {
133        value.checked_mul_div_ceil(&T::UNIT, divisor)
134    } else {
135        value.checked_mul_div(&T::UNIT, divisor)
136    }
137}
138
139/// Convert the `value` to a factor after dividing by the `divisor`.
140///
141/// ## Notes
142/// - Return `zero` if the `divisor` is zero.
143#[inline]
144pub fn div_to_factor_signed<T, const DECIMALS: u8>(
145    value: &T::Signed,
146    divisor: &T,
147) -> Option<T::Signed>
148where
149    T: FixedPointOps<DECIMALS>,
150{
151    if divisor.is_zero() {
152        return Some(Zero::zero());
153    }
154
155    T::UNIT.checked_mul_div_with_signed_numerator(value, divisor)
156}
157
158#[cfg(test)]
159mod tests {
160    #[test]
161    #[allow(clippy::identity_op)]
162    fn test_apply_factor() {
163        use crate::fixed::Fixed;
164        type U64D9 = Fixed<u64, 9>;
165        type U64D10 = Fixed<u64, 10>;
166
167        let x = 12 * *U64D10::ONE.get();
168        let y = 1 * *U64D9::ONE.get();
169        let res = crate::utils::apply_factor::<_, 19>(&x, &y).unwrap();
170        assert_eq!(res, 12u64);
171
172        let x = 100 * *U64D9::ONE.get();
173        let y = 1 * (*U64D9::ONE.get() / 100); // 1%
174        let res = crate::utils::apply_factor::<_, 9>(&x, &y).unwrap();
175        assert_eq!(res, *U64D9::ONE.get());
176
177        let x = 100 * *U64D9::ONE.get();
178        let y = 1 * (*U64D9::ONE.get() + *U64D9::ONE.get() / 10); // 110%
179        let res = crate::utils::apply_factor::<_, 9>(&x, &y).unwrap();
180        assert_eq!(res, 110 * *U64D9::ONE.get());
181    }
182}