Skip to main content

riptide_amm/math/
token.rs

1#[cfg(feature = "floats")]
2use libm::pow;
3
4#[cfg(feature = "wasm")]
5use riptide_amm_macros::wasm_expose;
6
7use super::error::{CoreError, AMOUNT_EXCEEDS_MAX_U64, ARITHMETIC_OVERFLOW};
8
9use super::U128;
10
11#[cfg(feature = "floats")]
12#[cfg_attr(feature = "wasm", wasm_expose)]
13pub fn amount_to_ui_amount(amount: u64, decimals: u8) -> f64 {
14    let power = pow(10f64, decimals as f64);
15    amount as f64 / power
16}
17
18#[cfg(feature = "floats")]
19#[cfg_attr(feature = "wasm", wasm_expose)]
20pub fn ui_amount_to_amount(amount: f64, decimals: u8) -> u64 {
21    let power = pow(10f64, decimals as f64);
22    (amount * power) as u64
23}
24
25/// Convert an amount in token A to an amount in token B
26///
27/// # Parameters
28/// * `amount_a` - The amount in token A
29/// * `price` - The Q64.64 price in B/A
30/// * `round_up` - Whether to round up the result
31///
32/// # Returns
33/// * `u64` - The amount in token B
34#[cfg_attr(feature = "wasm", wasm_expose)]
35pub fn a_to_b(amount_a: u64, price: U128, round_up: bool) -> Result<u64, CoreError> {
36    #[allow(clippy::useless_conversion)]
37    let price: u128 = price.into();
38
39    let product = u128::from(amount_a)
40        .checked_mul(price)
41        .ok_or(ARITHMETIC_OVERFLOW)?;
42
43    let quotient = product >> 64;
44    let remainder = product as u64;
45
46    let result = if round_up && remainder > 0 {
47        quotient + 1
48    } else {
49        quotient
50    };
51
52    result.try_into().map_err(|_| AMOUNT_EXCEEDS_MAX_U64)
53}
54
55/// Convert an amount in token B to an amount in token A
56///
57/// # Parameters
58/// * `amount_b` - The amount in token B
59/// * `price` - The Q64.64 price in B/A
60/// * `round_up` - Whether to round up the result
61///
62/// # Returns
63/// * `u64` - The amount in token A
64#[cfg_attr(feature = "wasm", wasm_expose)]
65pub fn b_to_a(amount_b: u64, price: U128, round_up: bool) -> Result<u64, CoreError> {
66    #[allow(clippy::useless_conversion)]
67    let price: u128 = price.into();
68    if price == 0 {
69        return Ok(0);
70    }
71
72    let numerator = u128::from(amount_b)
73        .checked_shl(64)
74        .ok_or(ARITHMETIC_OVERFLOW)?;
75
76    let quotient = numerator.checked_div(price).ok_or(ARITHMETIC_OVERFLOW)?;
77    let remainder = numerator.checked_rem(price).ok_or(ARITHMETIC_OVERFLOW)?;
78
79    let result = if round_up && remainder > 0 {
80        quotient + 1
81    } else {
82        quotient
83    };
84
85    result.try_into().map_err(|_| AMOUNT_EXCEEDS_MAX_U64)
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use rstest::rstest;
92
93    #[cfg(feature = "floats")]
94    #[rstest]
95    #[case(1000000000, 9, 1.0)]
96    #[case(1000000000, 6, 1000.0)]
97    #[case(1000000000, 3, 1000000.0)]
98    fn test_amount_to_ui_amount(
99        #[case] amount: u64,
100        #[case] decimals: u8,
101        #[case] expected_ui_amount: f64,
102    ) {
103        let ui_amount = amount_to_ui_amount(amount, decimals);
104        assert_eq!(ui_amount, expected_ui_amount);
105    }
106
107    #[cfg(feature = "floats")]
108    #[rstest]
109    #[case(1.0, 9, 1000000000)]
110    #[case(1000.0, 6, 1000000000)]
111    #[case(1000000.0, 3, 1000000000)]
112    fn test_ui_amount_to_amount(
113        #[case] ui_amount: f64,
114        #[case] decimals: u8,
115        #[case] expected_amount: u64,
116    ) {
117        let amount = ui_amount_to_amount(ui_amount, decimals);
118        assert_eq!(amount, expected_amount);
119    }
120
121    #[rstest]
122    #[case(100, 1 << 64, true, Ok(100))]
123    #[case(100, 1 << 64, false, Ok(100))]
124    #[case(100, 8 << 64, true, Ok(800))]
125    #[case(100, 8 << 64, false, Ok(800))]
126    #[case(100, (1 << 64) / 8, true, Ok(13))]
127    #[case(100, (1 << 64) / 8, false, Ok(12))]
128    #[case(100, 0, true, Ok(0))]
129    #[case(0, 1 << 64, true, Ok(0))]
130    fn test_a_to_b(
131        #[case] amount_a: u64,
132        #[case] price: u128,
133        #[case] round_up: bool,
134        #[case] expected: Result<u64, CoreError>,
135    ) {
136        let result = a_to_b(amount_a, U128::from(price), round_up);
137        assert_eq!(result, expected);
138    }
139
140    #[rstest]
141    #[case(100, 1 << 64, true, Ok(100))]
142    #[case(100, 1 << 64, false, Ok(100))]
143    #[case(100, 8 << 64, true, Ok(13))]
144    #[case(100, 8 << 64, false, Ok(12))]
145    #[case(100, (1 << 64) / 8, true, Ok(800))]
146    #[case(100, (1 << 64) / 8, false, Ok(800))]
147    #[case(100, 0, true, Ok(0))]
148    #[case(0, 1 << 64, true, Ok(0))]
149    fn test_b_to_a(
150        #[case] amount_b: u64,
151        #[case] price: u128,
152        #[case] round_up: bool,
153        #[case] expected: Result<u64, CoreError>,
154    ) {
155        let result = b_to_a(amount_b, U128::from(price), round_up);
156        assert_eq!(result, expected);
157    }
158}