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