1#![allow(clippy::collapsible_else_if)]
2#![allow(clippy::too_many_arguments)]
3
4use crate::{CoreError, HUNDRED_PERCENT, INVALID_ARGUMENTS};
5use fusionamm_core::{try_get_amount_delta_a, try_get_amount_delta_b, Q64_RESOLUTION, U128};
6
7#[cfg(feature = "wasm")]
8use fusionamm_macros::wasm_expose;
9
10#[derive(Debug, Copy, Clone, PartialEq)]
11#[cfg_attr(feature = "wasm", wasm_expose)]
12pub struct LiquidationPrices {
13 pub lower: f64,
14 pub upper: f64,
15}
16
17fn compute_liquidation_prices_inside(
49 lower_sqrt_price: u128,
50 upper_sqrt_price: u128,
51 liquidity: u128,
52 leftovers_a: u64,
53 leftovers_b: u64,
54 debt_a: u64,
55 debt_b: u64,
56 liquidation_threshold: u32,
57) -> Result<LiquidationPrices, CoreError> {
58 let liquidation_threshold_f = liquidation_threshold as f64 / HUNDRED_PERCENT as f64;
59 let liquidity_f = liquidity as f64;
60 let lower_sqrt_price_f = lower_sqrt_price as f64 / Q64_RESOLUTION;
61 let upper_sqrt_price_f = upper_sqrt_price as f64 / Q64_RESOLUTION;
62
63 let a = debt_a as f64 + liquidation_threshold_f * (liquidity_f / upper_sqrt_price_f - leftovers_a as f64);
64 let b = -2.0 * liquidation_threshold_f * liquidity_f;
65 let c = debt_b as f64 + liquidation_threshold_f * (liquidity_f * lower_sqrt_price_f - leftovers_b as f64);
66 let d = b * b - 4.0 * a * c;
67
68 let mut lower_liquidation_sqrt_price = 0.0;
69 let mut upper_liquidation_sqrt_price = 0.0;
70
71 if d >= 0.0 {
72 lower_liquidation_sqrt_price = (-b - d.sqrt()) / (2.0 * a);
73 upper_liquidation_sqrt_price = (-b + d.sqrt()) / (2.0 * a);
74 if lower_liquidation_sqrt_price < 0.0 || lower_liquidation_sqrt_price < lower_sqrt_price_f {
75 lower_liquidation_sqrt_price = 0.0;
76 }
77 if upper_liquidation_sqrt_price < 0.0 || upper_liquidation_sqrt_price > upper_sqrt_price_f {
78 upper_liquidation_sqrt_price = 0.0;
79 }
80 }
81
82 Ok(LiquidationPrices {
83 lower: lower_liquidation_sqrt_price * lower_liquidation_sqrt_price,
84 upper: upper_liquidation_sqrt_price * upper_liquidation_sqrt_price,
85 })
86}
87
88fn calculate_liquidation_outside(
119 amount_a: u64,
120 amount_b: u64,
121 leftovers_a: u64,
122 leftovers_b: u64,
123 debt_a: u64,
124 debt_b: u64,
125 liquidation_threshold: u32,
126) -> Result<f64, CoreError> {
127 let liquidation_threshold_f = liquidation_threshold as f64 / HUNDRED_PERCENT as f64;
128
129 if amount_a == 0 && amount_b == 0 {
130 Ok(0.0)
131 } else if amount_a > 0 && amount_b == 0 {
132 let numerator = debt_b as f64 - liquidation_threshold_f * leftovers_b as f64;
134 let denominator = liquidation_threshold_f * (amount_a + leftovers_a) as f64 - debt_a as f64;
135 Ok(numerator / denominator)
136 } else if amount_a == 0 && amount_b > 0 {
137 let numerator = liquidation_threshold_f * (amount_b + leftovers_b) as f64 - debt_b as f64;
139 let denominator = debt_a as f64 - liquidation_threshold_f * leftovers_a as f64;
140 if denominator == 0.0 {
141 return Ok(0.0);
142 }
143 Ok(numerator / denominator)
144 } else {
145 Err(INVALID_ARGUMENTS)
146 }
147}
148
149fn compute_liquidation_prices_outside(
164 lower_sqrt_price: u128,
165 upper_sqrt_price: u128,
166 liquidity: u128,
167 leftovers_a: u64,
168 leftovers_b: u64,
169 debt_a: u64,
170 debt_b: u64,
171 liquidation_threshold: u32,
172) -> Result<LiquidationPrices, CoreError> {
173 let amount_a = try_get_amount_delta_a(lower_sqrt_price.into(), upper_sqrt_price.into(), liquidity.into(), false)?;
174 let amount_b = try_get_amount_delta_b(lower_sqrt_price.into(), upper_sqrt_price.into(), liquidity.into(), false)?;
175
176 let mut liquidation_price_for_a = calculate_liquidation_outside(amount_a, 0, leftovers_a, leftovers_b, debt_a, debt_b, liquidation_threshold)?;
177 let mut liquidation_price_for_b = calculate_liquidation_outside(0, amount_b, leftovers_a, leftovers_b, debt_a, debt_b, liquidation_threshold)?;
178
179 if liquidation_price_for_a < 0.0 || liquidation_price_for_a > (lower_sqrt_price as f64 / Q64_RESOLUTION).powf(2.0) {
180 liquidation_price_for_a = 0.0;
181 }
182 if liquidation_price_for_b < 0.0 || liquidation_price_for_b < (upper_sqrt_price as f64 / Q64_RESOLUTION).powf(2.0) {
183 liquidation_price_for_b = 0.0;
184 }
185
186 Ok(LiquidationPrices {
187 lower: liquidation_price_for_a,
188 upper: liquidation_price_for_b,
189 })
190}
191
192#[cfg_attr(feature = "wasm", wasm_expose)]
207pub fn get_lp_position_liquidation_prices(
208 lower_sqrt_price: U128,
209 upper_sqrt_price: U128,
210 liquidity: U128,
211 leftovers_a: u64,
212 leftovers_b: u64,
213 debt_a: u64,
214 debt_b: u64,
215 liquidation_threshold: u32,
216) -> Result<LiquidationPrices, CoreError> {
217 let lower_sqrt_price: u128 = lower_sqrt_price.into();
218 let upper_sqrt_price: u128 = upper_sqrt_price.into();
219 let liquidity: u128 = liquidity.into();
220
221 let liquidation_price_inside = compute_liquidation_prices_inside(
222 lower_sqrt_price,
223 upper_sqrt_price,
224 liquidity,
225 leftovers_a,
226 leftovers_b,
227 debt_a,
228 debt_b,
229 liquidation_threshold,
230 )?;
231
232 let liquidation_price_outside = compute_liquidation_prices_outside(
233 lower_sqrt_price,
234 upper_sqrt_price,
235 liquidity,
236 leftovers_a,
237 leftovers_b,
238 debt_a,
239 debt_b,
240 liquidation_threshold,
241 )?;
242
243 let lower_liquidation_price = if liquidation_price_inside.lower > 0.0 {
244 liquidation_price_inside.lower
245 } else {
246 liquidation_price_outside.lower
247 };
248
249 let upper_liquidation_price = if liquidation_price_inside.upper > 0.0 {
250 liquidation_price_inside.upper
251 } else {
252 liquidation_price_outside.upper
253 };
254
255 Ok(LiquidationPrices {
256 lower: lower_liquidation_price,
257 upper: upper_liquidation_price,
258 })
259}
260
261#[cfg(all(test, not(feature = "wasm")))]
262mod tests {
263 use crate::{get_lp_position_liquidation_prices, LiquidationPrices, HUNDRED_PERCENT};
264 use fusionamm_core::{price_to_sqrt_price, try_get_liquidity_from_b};
265 use once_cell::sync::Lazy;
266
267 pub static LOWER_SQRT_PRICE: Lazy<u128> = Lazy::new(|| price_to_sqrt_price(180.736, 6, 6));
268 pub static SQRT_PRICE: Lazy<u128> = Lazy::new(|| price_to_sqrt_price(213.41, 6, 6));
269 pub static UPPER_SQRT_PRICE: Lazy<u128> = Lazy::new(|| price_to_sqrt_price(225.66, 6, 6));
270 pub static LIQUIDITY: Lazy<u128> = Lazy::new(|| try_get_liquidity_from_b(10000_000_000, *LOWER_SQRT_PRICE, *SQRT_PRICE).unwrap());
271
272 #[test]
273 fn test_liquidation_price_outside_range_lower() {
274 assert_eq!(
275 get_lp_position_liquidation_prices(
276 *LOWER_SQRT_PRICE,
277 *UPPER_SQRT_PRICE,
278 *LIQUIDITY,
279 0, 0, 0, 9807_000_000, HUNDRED_PERCENT * 83 / 100 ),
285 Ok(LiquidationPrices {
286 lower: 176.1105408775386,
287 upper: 0.0
288 })
289 );
290 }
291
292 #[test]
293 fn test_liquidation_price_outside_range_upper() {
294 assert_eq!(
295 get_lp_position_liquidation_prices(
296 *LOWER_SQRT_PRICE,
297 *UPPER_SQRT_PRICE,
298 *LIQUIDITY,
299 0, 0, 20_000_000, 0, HUNDRED_PERCENT * 83 / 100 ),
305 Ok(LiquidationPrices {
306 lower: 0.0,
307 upper: 562.303070321
308 })
309 );
310 }
311
312 #[test]
313 fn test_liquidation_price_outside_range_lower_and_upper() {
314 assert_eq!(
315 get_lp_position_liquidation_prices(
316 *LOWER_SQRT_PRICE,
317 *UPPER_SQRT_PRICE,
318 *LIQUIDITY,
319 0, 0, 10_000_000, 5000_000_000, HUNDRED_PERCENT * 83 / 100 ),
325 Ok(LiquidationPrices {
326 lower: 109.44124291015223,
327 upper: 624.606140642
328 })
329 );
330 }
331
332 #[test]
333 fn test_liquidation_price_inside_range_lower() {
334 assert_eq!(
335 get_lp_position_liquidation_prices(
336 *LOWER_SQRT_PRICE,
337 *UPPER_SQRT_PRICE,
338 *LIQUIDITY,
339 0, 0, 0, 11000_000_000, HUNDRED_PERCENT * 83 / 100 ),
345 Ok(LiquidationPrices {
346 lower: 204.54056605629555,
347 upper: 0.0
348 })
349 );
350 }
351
352 #[test]
353 fn test_liquidation_price_inside_range_upper() {
354 assert_eq!(
355 get_lp_position_liquidation_prices(
356 *LOWER_SQRT_PRICE,
357 *UPPER_SQRT_PRICE,
358 *LIQUIDITY,
359 0, 0, 51_000_000, 0, HUNDRED_PERCENT * 83 / 100 ),
365 Ok(LiquidationPrices {
366 lower: 0.0,
367 upper: 220.2000409301412
368 })
369 );
370 }
371
372 #[test]
373 fn test_liquidation_price_inside_range_lower_and_upper() {
374 assert_eq!(
375 get_lp_position_liquidation_prices(
376 *LOWER_SQRT_PRICE,
377 *UPPER_SQRT_PRICE,
378 *LIQUIDITY,
379 0, 0, 11_500_000, 8700_000_000, HUNDRED_PERCENT * 83 / 100 ),
385 Ok(LiquidationPrices {
386 lower: 210.44477952403906,
387 upper: 219.81463329620027
388 })
389 );
390 }
391}