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#[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#[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}