1use ethnum::U256;
2#[cfg(feature = "floats")]
3use libm::pow;
4
5#[cfg(feature = "wasm")]
6use riptide_amm_macros::wasm_expose;
7
8use super::{AMOUNT_EXCEEDS_MAX_I32, PER_M_DENOMINATOR};
9
10use super::error::{CoreError, AMOUNT_EXCEEDS_MAX_U64, ARITHMETIC_OVERFLOW};
11
12use super::U128;
13
14#[cfg(feature = "floats")]
15#[cfg_attr(feature = "wasm", wasm_expose)]
16pub fn amount_to_ui_amount(amount: u64, decimals: u8) -> f64 {
17 let power = pow(10f64, decimals as f64);
18 amount as f64 / power
19}
20
21#[cfg(feature = "floats")]
22#[cfg_attr(feature = "wasm", wasm_expose)]
23pub fn ui_amount_to_amount(amount: f64, decimals: u8) -> u64 {
24 let power = pow(10f64, decimals as f64);
25 (amount * power) as u64
26}
27
28#[cfg_attr(feature = "wasm", wasm_expose)]
38pub fn a_to_b(amount_a: u64, price: U128, round_up: bool) -> Result<u64, CoreError> {
39 #[allow(clippy::useless_conversion)]
40 let price: u128 = price.into();
41
42 let product = u128::from(amount_a)
43 .checked_mul(price)
44 .ok_or(ARITHMETIC_OVERFLOW)?;
45
46 let quotient = product >> 64;
47 let remainder = product as u64;
48
49 let result = if round_up && remainder > 0 {
50 quotient + 1
51 } else {
52 quotient
53 };
54
55 result.try_into().map_err(|_| AMOUNT_EXCEEDS_MAX_U64)
56}
57
58#[cfg_attr(feature = "wasm", wasm_expose)]
68pub fn b_to_a(amount_b: u64, price: U128, round_up: bool) -> Result<u64, CoreError> {
69 #[allow(clippy::useless_conversion)]
70 let price: u128 = price.into();
71 if price == 0 {
72 return Ok(0);
73 }
74
75 let numerator = u128::from(amount_b)
76 .checked_shl(64)
77 .ok_or(ARITHMETIC_OVERFLOW)?;
78
79 let quotient = numerator.checked_div(price).ok_or(ARITHMETIC_OVERFLOW)?;
80 let remainder = numerator.checked_rem(price).ok_or(ARITHMETIC_OVERFLOW)?;
81
82 let result = if round_up && remainder > 0 {
83 quotient + 1
84 } else {
85 quotient
86 };
87
88 result.try_into().map_err(|_| AMOUNT_EXCEEDS_MAX_U64)
89}
90
91#[cfg_attr(feature = "wasm", wasm_expose)]
95pub fn deviation_per_m(
96 price_q64_64: U128,
97 reserves_a: u64,
98 reserves_b: u64,
99) -> Result<i32, CoreError> {
100 let value_a = U256::from(reserves_a as u128)
101 .checked_mul(U256::from(price_q64_64))
102 .ok_or(ARITHMETIC_OVERFLOW)?;
103 let value_b = U256::from(reserves_b as u128)
104 .checked_shl(64)
105 .ok_or(ARITHMETIC_OVERFLOW)?;
106 let total = value_a.checked_add(value_b).ok_or(ARITHMETIC_OVERFLOW)?;
107 if total == U256::ZERO {
108 return Ok(0);
109 }
110 let twice_value_a = value_a
111 .checked_mul(U256::from(2u128))
112 .ok_or(ARITHMETIC_OVERFLOW)?;
113 let ratio_per_m = twice_value_a
114 .checked_mul(U256::from(PER_M_DENOMINATOR as u128))
115 .ok_or(ARITHMETIC_OVERFLOW)?
116 .checked_div(total)
117 .ok_or(ARITHMETIC_OVERFLOW)?;
118 let ratio: i32 = ratio_per_m.try_into().map_err(|_| AMOUNT_EXCEEDS_MAX_I32)?;
119 ratio
120 .checked_sub(PER_M_DENOMINATOR)
121 .ok_or(ARITHMETIC_OVERFLOW)
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use rstest::rstest;
128
129 #[cfg(feature = "floats")]
130 #[rstest]
131 #[case(1000000000, 9, 1.0)]
132 #[case(1000000000, 6, 1000.0)]
133 #[case(1000000000, 3, 1000000.0)]
134 fn test_amount_to_ui_amount(
135 #[case] amount: u64,
136 #[case] decimals: u8,
137 #[case] expected_ui_amount: f64,
138 ) {
139 let ui_amount = amount_to_ui_amount(amount, decimals);
140 assert_eq!(ui_amount, expected_ui_amount);
141 }
142
143 #[cfg(feature = "floats")]
144 #[rstest]
145 #[case(1.0, 9, 1000000000)]
146 #[case(1000.0, 6, 1000000000)]
147 #[case(1000000.0, 3, 1000000000)]
148 fn test_ui_amount_to_amount(
149 #[case] ui_amount: f64,
150 #[case] decimals: u8,
151 #[case] expected_amount: u64,
152 ) {
153 let amount = ui_amount_to_amount(ui_amount, decimals);
154 assert_eq!(amount, expected_amount);
155 }
156
157 #[rstest]
158 #[case(100, 1 << 64, true, Ok(100))]
159 #[case(100, 1 << 64, false, Ok(100))]
160 #[case(100, 8 << 64, true, Ok(800))]
161 #[case(100, 8 << 64, false, Ok(800))]
162 #[case(100, (1 << 64) / 8, true, Ok(13))]
163 #[case(100, (1 << 64) / 8, false, Ok(12))]
164 #[case(100, 0, true, Ok(0))]
165 #[case(0, 1 << 64, true, Ok(0))]
166 fn test_a_to_b(
167 #[case] amount_a: u64,
168 #[case] price: u128,
169 #[case] round_up: bool,
170 #[case] expected: Result<u64, CoreError>,
171 ) {
172 let result = a_to_b(amount_a, U128::from(price), round_up);
173 assert_eq!(result, expected);
174 }
175
176 #[rstest]
177 #[case(100, 1 << 64, true, Ok(100))]
178 #[case(100, 1 << 64, false, Ok(100))]
179 #[case(100, 8 << 64, true, Ok(13))]
180 #[case(100, 8 << 64, false, Ok(12))]
181 #[case(100, (1 << 64) / 8, true, Ok(800))]
182 #[case(100, (1 << 64) / 8, false, Ok(800))]
183 #[case(100, 0, true, Ok(0))]
184 #[case(0, 1 << 64, true, Ok(0))]
185 fn test_b_to_a(
186 #[case] amount_b: u64,
187 #[case] price: u128,
188 #[case] round_up: bool,
189 #[case] expected: Result<u64, CoreError>,
190 ) {
191 let result = b_to_a(amount_b, U128::from(price), round_up);
192 assert_eq!(result, expected);
193 }
194
195 #[rstest]
196 #[case(1 << 64, 500, 500, 0)] #[case(1 << 64, 750, 250, 500_000)] #[case(1 << 64, 250, 750, -500_000)] #[case(1 << 64, 1000, 0, 1_000_000)] #[case(1 << 64, 0, 1000, -1_000_000)] fn test_deviation_per_m(
202 #[case] price: u128,
203 #[case] reserves_a: u64,
204 #[case] reserves_b: u64,
205 #[case] expected: i32,
206 ) {
207 let result = deviation_per_m(U128::from(price), reserves_a, reserves_b).unwrap();
208 assert_eq!(result, expected);
209 }
210
211 #[test]
212 fn test_deviation_zero_reserves() {
213 assert_eq!(deviation_per_m(U128::from(1u128 << 64), 0, 0).unwrap(), 0);
214 }
215}