1#![allow(clippy::collapsible_else_if)]
2#![allow(clippy::too_many_arguments)]
3
4use crate::{calculate_tuna_protocol_fee, sqrt_price_x64_to_price_x64, COMPUTED_AMOUNT, HUNDRED_PERCENT, INVALID_ARGUMENTS};
5use fixed::types::U64F64;
6use fusionamm_core::{
7 position_ratio_x64, tick_index_to_sqrt_price, try_apply_swap_fee, try_get_amount_a_from_liquidity, try_get_amount_b_from_liquidity,
8 try_get_amount_delta_a, try_get_amount_delta_b, try_get_amounts_from_liquidity, try_get_liquidity_from_amount_a, try_get_liquidity_from_amount_b,
9 try_get_liquidity_from_amounts, CoreError, ARITHMETIC_OVERFLOW, Q64_RESOLUTION,
10};
11
12#[cfg(feature = "wasm")]
13use fusionamm_macros::wasm_expose;
14
15#[derive(Debug, Copy, Clone, PartialEq)]
16#[cfg_attr(feature = "wasm", wasm_expose)]
17pub struct LiquidationPrices {
18 pub lower: f64,
19 pub upper: f64,
20}
21
22fn compute_liquidation_prices_inside(
54 lower_sqrt_price: u128,
55 upper_sqrt_price: u128,
56 liquidity: u128,
57 leftovers_a: u64,
58 leftovers_b: u64,
59 debt_a: u64,
60 debt_b: u64,
61 liquidation_threshold: u32,
62) -> Result<LiquidationPrices, CoreError> {
63 let liquidation_threshold_f = liquidation_threshold as f64 / HUNDRED_PERCENT as f64;
64 let liquidity_f = liquidity as f64;
65 let lower_sqrt_price_f = lower_sqrt_price as f64 / Q64_RESOLUTION;
66 let upper_sqrt_price_f = upper_sqrt_price as f64 / Q64_RESOLUTION;
67
68 let a = debt_a as f64 + liquidation_threshold_f * (liquidity_f / upper_sqrt_price_f - leftovers_a as f64);
69 let b = -2.0 * liquidation_threshold_f * liquidity_f;
70 let c = debt_b as f64 + liquidation_threshold_f * (liquidity_f * lower_sqrt_price_f - leftovers_b as f64);
71 let d = b * b - 4.0 * a * c;
72
73 let mut lower_liquidation_sqrt_price = 0.0;
74 let mut upper_liquidation_sqrt_price = 0.0;
75
76 if d >= 0.0 {
77 lower_liquidation_sqrt_price = (-b - d.sqrt()) / (2.0 * a);
78 upper_liquidation_sqrt_price = (-b + d.sqrt()) / (2.0 * a);
79 if lower_liquidation_sqrt_price < 0.0 || lower_liquidation_sqrt_price < lower_sqrt_price_f {
80 lower_liquidation_sqrt_price = 0.0;
81 }
82 if upper_liquidation_sqrt_price < 0.0 || upper_liquidation_sqrt_price > upper_sqrt_price_f {
83 upper_liquidation_sqrt_price = 0.0;
84 }
85 }
86
87 Ok(LiquidationPrices {
88 lower: lower_liquidation_sqrt_price * lower_liquidation_sqrt_price,
89 upper: upper_liquidation_sqrt_price * upper_liquidation_sqrt_price,
90 })
91}
92
93fn calculate_liquidation_outside(
124 amount_a: u64,
125 amount_b: u64,
126 leftovers_a: u64,
127 leftovers_b: u64,
128 debt_a: u64,
129 debt_b: u64,
130 liquidation_threshold: u32,
131) -> Result<f64, CoreError> {
132 let liquidation_threshold_f = liquidation_threshold as f64 / HUNDRED_PERCENT as f64;
133
134 if amount_a == 0 && amount_b == 0 {
135 Ok(0.0)
136 } else if amount_a > 0 && amount_b == 0 {
137 let numerator = debt_b as f64 - liquidation_threshold_f * leftovers_b as f64;
139 let denominator = liquidation_threshold_f * (amount_a + leftovers_a) as f64 - debt_a as f64;
140 Ok(numerator / denominator)
141 } else if amount_a == 0 && amount_b > 0 {
142 let numerator = liquidation_threshold_f * (amount_b + leftovers_b) as f64 - debt_b as f64;
144 let denominator = debt_a as f64 - liquidation_threshold_f * leftovers_a as f64;
145 if denominator == 0.0 {
146 return Ok(0.0);
147 }
148 Ok(numerator / denominator)
149 } else {
150 Err(INVALID_ARGUMENTS)
151 }
152}
153
154fn compute_liquidation_prices_outside(
169 lower_sqrt_price: u128,
170 upper_sqrt_price: u128,
171 liquidity: u128,
172 leftovers_a: u64,
173 leftovers_b: u64,
174 debt_a: u64,
175 debt_b: u64,
176 liquidation_threshold: u32,
177) -> Result<LiquidationPrices, CoreError> {
178 let amount_a = try_get_amount_delta_a(lower_sqrt_price.into(), upper_sqrt_price.into(), liquidity.into(), false)?;
179 let amount_b = try_get_amount_delta_b(lower_sqrt_price.into(), upper_sqrt_price.into(), liquidity.into(), false)?;
180
181 let mut liquidation_price_for_a = calculate_liquidation_outside(amount_a, 0, leftovers_a, leftovers_b, debt_a, debt_b, liquidation_threshold)?;
182 let mut liquidation_price_for_b = calculate_liquidation_outside(0, amount_b, leftovers_a, leftovers_b, debt_a, debt_b, liquidation_threshold)?;
183
184 if liquidation_price_for_a < 0.0 || liquidation_price_for_a > (lower_sqrt_price as f64 / Q64_RESOLUTION).powf(2.0) {
185 liquidation_price_for_a = 0.0;
186 }
187 if liquidation_price_for_b < 0.0 || liquidation_price_for_b < (upper_sqrt_price as f64 / Q64_RESOLUTION).powf(2.0) {
188 liquidation_price_for_b = 0.0;
189 }
190
191 Ok(LiquidationPrices {
192 lower: liquidation_price_for_a,
193 upper: liquidation_price_for_b,
194 })
195}
196
197#[cfg_attr(feature = "wasm", wasm_expose)]
212pub fn get_lp_position_liquidation_prices(
213 tick_lower_index: i32,
214 tick_upper_index: i32,
215 liquidity: u128,
216 leftovers_a: u64,
217 leftovers_b: u64,
218 debt_a: u64,
219 debt_b: u64,
220 liquidation_threshold: u32,
221) -> Result<LiquidationPrices, CoreError> {
222 if tick_lower_index >= tick_upper_index {
223 return Err("Incorrect position tick index order: the lower tick must be less then the upper tick.");
224 }
225
226 if liquidation_threshold >= HUNDRED_PERCENT {
227 return Err("Incorrect liquidation_threshold value.");
228 }
229
230 let lower_sqrt_price = tick_index_to_sqrt_price(tick_lower_index);
231 let upper_sqrt_price = tick_index_to_sqrt_price(tick_upper_index);
232
233 let liquidation_price_inside = compute_liquidation_prices_inside(
234 lower_sqrt_price,
235 upper_sqrt_price,
236 liquidity,
237 leftovers_a,
238 leftovers_b,
239 debt_a,
240 debt_b,
241 liquidation_threshold,
242 )?;
243
244 let liquidation_price_outside = compute_liquidation_prices_outside(
245 lower_sqrt_price,
246 upper_sqrt_price,
247 liquidity,
248 leftovers_a,
249 leftovers_b,
250 debt_a,
251 debt_b,
252 liquidation_threshold,
253 )?;
254
255 let lower_liquidation_price = if liquidation_price_inside.lower > 0.0 {
256 liquidation_price_inside.lower
257 } else {
258 liquidation_price_outside.lower
259 };
260
261 let upper_liquidation_price = if liquidation_price_inside.upper > 0.0 {
262 liquidation_price_inside.upper
263 } else {
264 liquidation_price_outside.upper
265 };
266
267 Ok(LiquidationPrices {
268 lower: lower_liquidation_price,
269 upper: upper_liquidation_price,
270 })
271}
272
273#[derive(Debug, Copy, Clone, PartialEq)]
274#[cfg_attr(feature = "wasm", wasm_expose)]
275pub struct IncreaseLpPositionQuoteArgs {
276 pub collateral_a: u64,
278 pub collateral_b: u64,
280 pub borrow_a: u64,
282 pub borrow_b: u64,
284 pub protocol_fee_rate: u16,
286 pub protocol_fee_rate_on_collateral: u16,
288 pub swap_fee_rate: u16,
290 pub sqrt_price: u128,
292 pub tick_lower_index: i32,
294 pub tick_upper_index: i32,
296 pub max_amount_slippage: u32,
298 pub liquidation_threshold: u32,
300}
301
302#[derive(Debug, Copy, Clone, PartialEq)]
303#[cfg_attr(feature = "wasm", wasm_expose)]
304pub struct IncreaseLpPositionQuoteResult {
305 pub collateral_a: u64,
306 pub collateral_b: u64,
307 pub max_collateral_a: u64,
308 pub max_collateral_b: u64,
309 pub borrow_a: u64,
310 pub borrow_b: u64,
311 pub total_a: u64,
312 pub total_b: u64,
313 pub min_total_a: u64,
314 pub min_total_b: u64,
315 pub swap_input: u64,
316 pub swap_output: u64,
317 pub swap_a_to_b: bool,
318 pub protocol_fee_a: u64,
319 pub protocol_fee_b: u64,
320 pub liquidity: u128,
321 pub leverage: f64,
322 pub liquidation_lower_price: f64,
323 pub liquidation_upper_price: f64,
324}
325
326#[cfg_attr(feature = "wasm", wasm_expose)]
327pub fn get_increase_lp_position_quote(args: IncreaseLpPositionQuoteArgs) -> Result<IncreaseLpPositionQuoteResult, CoreError> {
328 let mut collateral_a = args.collateral_a;
329 let mut collateral_b = args.collateral_b;
330 let mut borrow_a = args.borrow_a;
331 let mut borrow_b = args.borrow_b;
332 let sqrt_price = args.sqrt_price;
333
334 if args.tick_lower_index >= args.tick_upper_index {
335 return Err("Incorrect position tick index order: the lower tick must be less than the upper tick.");
336 }
337
338 if args.max_amount_slippage > HUNDRED_PERCENT {
339 return Err("max_amount_slippage must be in range [0; HUNDRED_PERCENT]");
340 }
341
342 if collateral_a == COMPUTED_AMOUNT && collateral_b == COMPUTED_AMOUNT {
343 return Err("Both collateral amounts can't be set to COMPUTED_AMOUNT");
344 }
345
346 let max_amount_slippage = args.max_amount_slippage;
347
348 let mut max_collateral_a = collateral_a;
349 let mut max_collateral_b = collateral_b;
350
351 let lower_sqrt_price = tick_index_to_sqrt_price(args.tick_lower_index);
352 let upper_sqrt_price = tick_index_to_sqrt_price(args.tick_upper_index);
353
354 if collateral_a == COMPUTED_AMOUNT {
355 if sqrt_price <= lower_sqrt_price {
356 return Err("sqrtPrice must be greater than lower_sqrt_price if collateral A is computed.");
357 } else if sqrt_price < upper_sqrt_price {
358 let liquidity = try_get_liquidity_from_amount_b(collateral_b + borrow_b, lower_sqrt_price, sqrt_price)?;
359 let amount_a = try_get_amount_a_from_liquidity(liquidity, sqrt_price, upper_sqrt_price, false)?;
360 collateral_a = (amount_a * collateral_b) / (collateral_b + borrow_b);
361 borrow_a = amount_a - collateral_a;
362 max_collateral_a = collateral_a + ((collateral_a as u128 * max_amount_slippage as u128) / HUNDRED_PERCENT as u128) as u64;
363 } else {
364 collateral_a = 0;
365 max_collateral_a = 0;
366 borrow_a = 0;
367 }
368 } else if collateral_b == COMPUTED_AMOUNT {
369 if sqrt_price <= lower_sqrt_price {
370 collateral_b = 0;
371 max_collateral_b = 0;
372 borrow_b = 0;
373 } else if sqrt_price < upper_sqrt_price {
374 let liquidity = try_get_liquidity_from_amount_a(collateral_a + borrow_a, sqrt_price, upper_sqrt_price)?;
375 let amount_b = try_get_amount_b_from_liquidity(liquidity, lower_sqrt_price, sqrt_price, false)?;
376 collateral_b = (amount_b * collateral_a) / (collateral_a + borrow_a);
377 borrow_b = amount_b - collateral_b;
378 max_collateral_b = collateral_b + ((collateral_b as u128 * max_amount_slippage as u128) / HUNDRED_PERCENT as u128) as u64;
379 } else {
380 return Err("sqrtPrice must be less than upper_sqrt_price if collateral B is computed.");
381 }
382 }
383
384 let protocol_fee_a = calculate_tuna_protocol_fee(collateral_a, borrow_a, args.protocol_fee_rate_on_collateral, args.protocol_fee_rate);
385 let provided_a = collateral_a + borrow_a - protocol_fee_a;
386
387 let protocol_fee_b = calculate_tuna_protocol_fee(collateral_b, borrow_b, args.protocol_fee_rate_on_collateral, args.protocol_fee_rate);
388 let provided_b = collateral_b + borrow_b - protocol_fee_b;
389
390 let mut swap_input = 0;
391 let mut swap_output = 0;
392 let mut swap_a_to_b = false;
393 let mut total_a = provided_a;
394 let mut total_b = provided_b;
395
396 if args.collateral_a != COMPUTED_AMOUNT && args.collateral_b != COMPUTED_AMOUNT {
397 let position_ratio = position_ratio_x64(sqrt_price.into(), args.tick_lower_index, args.tick_upper_index);
398 let ratio_a = position_ratio.ratio_a as f64 / Q64_RESOLUTION;
399 let ratio_b = position_ratio.ratio_b as f64 / Q64_RESOLUTION;
400
401 let price = (sqrt_price as f64 / Q64_RESOLUTION).powf(2.0);
402
403 let mut total = (provided_a as f64 * price + provided_b as f64) as u64;
405 total_a = (total as f64 * ratio_a / price) as u64;
406 total_b = (total as f64 * ratio_b) as u64;
407
408 let mut fee_a = 0;
409 let mut fee_b = 0;
410
411 if total_a < provided_a {
412 swap_input = provided_a - total_a;
413 fee_a = swap_input - try_apply_swap_fee(swap_input, args.swap_fee_rate)?;
414 swap_output = ((swap_input - fee_a) as f64 * price) as u64;
415 swap_a_to_b = true;
416 } else if total_b < provided_b {
417 swap_input = provided_b - total_b;
418 fee_b = swap_input - try_apply_swap_fee(swap_input, args.swap_fee_rate)?;
419 swap_output = ((swap_input - fee_b) as f64 / price) as u64;
420 swap_a_to_b = false;
421 }
422
423 total = ((provided_a - fee_a) as f64 * price) as u64 + provided_b - fee_b;
425 total_a = ((total as f64 * ratio_a) / price) as u64;
426 total_b = (total as f64 * ratio_b) as u64;
427 }
428
429 let min_total_a = total_a - ((total_a as u128 * max_amount_slippage as u128) / HUNDRED_PERCENT as u128) as u64;
430 let min_total_b = total_b - ((total_b as u128 * max_amount_slippage as u128) / HUNDRED_PERCENT as u128) as u64;
431
432 let liquidity = try_get_liquidity_from_amounts(sqrt_price, lower_sqrt_price, upper_sqrt_price, total_a, total_b)?;
433 let liquidation_prices = get_lp_position_liquidation_prices(
434 args.tick_lower_index,
435 args.tick_upper_index,
436 liquidity,
437 0,
438 0,
439 borrow_a,
440 borrow_b,
441 args.liquidation_threshold,
442 )?;
443
444 let leverage = compute_leverage(total_a, total_b, borrow_a, borrow_b, sqrt_price)?;
445
446 Ok(IncreaseLpPositionQuoteResult {
447 collateral_a,
448 collateral_b,
449 max_collateral_a,
450 max_collateral_b,
451 borrow_a,
452 borrow_b,
453 total_a,
454 total_b,
455 min_total_a,
456 min_total_b,
457 swap_input,
458 swap_output,
459 swap_a_to_b,
460 protocol_fee_a,
461 protocol_fee_b,
462 liquidity,
463 leverage,
464 liquidation_lower_price: liquidation_prices.lower,
465 liquidation_upper_price: liquidation_prices.upper,
466 })
467}
468
469#[derive(Debug, Copy, Clone, PartialEq)]
470#[cfg_attr(feature = "wasm", wasm_expose)]
471pub struct RepayLpPositionDebtQuoteArgs {
472 pub liquidity: u128,
474 pub debt_a: u64,
476 pub debt_b: u64,
478 pub leftovers_a: u64,
480 pub leftovers_b: u64,
482 pub tick_lower_index: i32,
484 pub tick_upper_index: i32,
486 pub repay_a: u64,
488 pub repay_b: u64,
490 pub sqrt_price: u128,
492 pub liquidation_threshold: u32,
494}
495
496#[derive(Debug, Copy, Clone, PartialEq)]
497#[cfg_attr(feature = "wasm", wasm_expose)]
498pub struct RepayLpPositionDebtQuoteResult {
499 pub debt_a: u64,
500 pub debt_b: u64,
501 pub leverage: f64,
502 pub liquidation_lower_price: f64,
503 pub liquidation_upper_price: f64,
504}
505
506#[cfg_attr(feature = "wasm", wasm_expose)]
507pub fn get_repay_lp_position_debt_quote(args: RepayLpPositionDebtQuoteArgs) -> Result<RepayLpPositionDebtQuoteResult, CoreError> {
508 let mut debt_a = args.debt_a;
509 let mut debt_b = args.debt_b;
510 let repay_a = args.repay_a;
511 let repay_b = args.repay_b;
512
513 if args.liquidity == 0 {
514 return Err("Position liquidity can't be zero.");
515 }
516
517 if debt_a < repay_a {
518 return Err("Position debt A is less than the repaid amount.");
519 }
520
521 if debt_b < repay_b {
522 return Err("Position debt b is less than the repaid amount.");
523 }
524
525 debt_a -= repay_a;
526 debt_b -= repay_b;
527
528 let liquidation_prices = get_lp_position_liquidation_prices(
529 args.tick_lower_index,
530 args.tick_upper_index,
531 args.liquidity,
532 args.leftovers_a,
533 args.leftovers_b,
534 debt_a,
535 debt_b,
536 args.liquidation_threshold,
537 )?;
538
539 let total = try_get_amounts_from_liquidity(args.liquidity, args.sqrt_price, args.tick_lower_index, args.tick_upper_index, false)?;
540 let leverage = compute_leverage(total.a + args.leftovers_a, total.b + args.leftovers_b, debt_a, debt_b, args.sqrt_price)?;
541
542 Ok(RepayLpPositionDebtQuoteResult {
543 debt_a,
544 debt_b,
545 leverage,
546 liquidation_lower_price: liquidation_prices.lower,
547 liquidation_upper_price: liquidation_prices.upper,
548 })
549}
550
551#[cfg_attr(feature = "wasm", wasm_expose)]
552pub fn compute_leverage(total_a: u64, total_b: u64, debt_a: u64, debt_b: u64, sqrt_price: u128) -> Result<f64, CoreError> {
553 let price = sqrt_price_x64_to_price_x64(sqrt_price)?;
554
555 let total = U64F64::from(total_a)
556 .checked_mul(price)
557 .ok_or(ARITHMETIC_OVERFLOW)?
558 .to_num::<u64>()
559 .checked_add(total_b)
560 .ok_or(ARITHMETIC_OVERFLOW)?;
561
562 let debt = U64F64::from(debt_a)
563 .checked_mul(price)
564 .ok_or(ARITHMETIC_OVERFLOW)?
565 .to_num::<u64>()
566 .checked_add(debt_b)
567 .ok_or(ARITHMETIC_OVERFLOW)?;
568
569 if total == 0 {
571 return Ok(1.0);
572 }
573
574 if debt >= total {
575 return Err("The debt is greater than the total size");
576 }
577
578 let leverage = total as f64 / (total - debt) as f64;
579 Ok(leverage)
580}
581
582#[cfg(all(test, not(feature = "wasm")))]
583mod tests {
584 use crate::{
585 get_increase_lp_position_quote, get_lp_position_liquidation_prices, get_repay_lp_position_debt_quote, IncreaseLpPositionQuoteArgs,
586 IncreaseLpPositionQuoteResult, LiquidationPrices, RepayLpPositionDebtQuoteArgs, COMPUTED_AMOUNT, HUNDRED_PERCENT,
587 };
588 use fusionamm_core::{price_to_sqrt_price, price_to_tick_index, tick_index_to_sqrt_price, try_get_liquidity_from_amount_b};
589 use once_cell::sync::Lazy;
590
591 pub static TICK_LOWER_INDEX: Lazy<i32> = Lazy::new(|| price_to_tick_index(180.736, 6, 6));
592 pub static TICK_UPPER_INDEX: Lazy<i32> = Lazy::new(|| price_to_tick_index(225.66, 6, 6));
593 pub static SQRT_PRICE: Lazy<u128> = Lazy::new(|| price_to_sqrt_price(213.41, 6, 6));
594 pub static LIQUIDITY: Lazy<u128> =
595 Lazy::new(|| try_get_liquidity_from_amount_b(10000_000_000, tick_index_to_sqrt_price(*TICK_LOWER_INDEX), *SQRT_PRICE).unwrap());
596
597 #[test]
598 fn test_liquidation_price_outside_range_lower() {
599 assert_eq!(
600 get_lp_position_liquidation_prices(
601 *TICK_LOWER_INDEX,
602 *TICK_UPPER_INDEX,
603 *LIQUIDITY,
604 0, 0, 0, 9807_000_000, HUNDRED_PERCENT * 83 / 100 ),
610 Ok(LiquidationPrices {
611 lower: 176.12815046153585,
612 upper: 0.0
613 })
614 );
615 }
616
617 #[test]
618 fn test_liquidation_price_outside_range_upper() {
619 assert_eq!(
620 get_lp_position_liquidation_prices(
621 *TICK_LOWER_INDEX,
622 *TICK_UPPER_INDEX,
623 *LIQUIDITY,
624 0, 0, 20_000_000, 0, HUNDRED_PERCENT * 83 / 100 ),
630 Ok(LiquidationPrices {
631 lower: 0.0,
632 upper: 562.219410388
633 })
634 );
635 }
636
637 #[test]
638 fn test_liquidation_price_outside_range_lower_and_upper() {
639 assert_eq!(
640 get_lp_position_liquidation_prices(
641 *TICK_LOWER_INDEX,
642 *TICK_UPPER_INDEX,
643 *LIQUIDITY,
644 0, 0, 10_000_000, 5000_000_000, HUNDRED_PERCENT * 83 / 100 ),
650 Ok(LiquidationPrices {
651 lower: 109.45458168998225,
652 upper: 624.4388207760001
653 })
654 );
655 }
656
657 #[test]
658 fn test_liquidation_price_inside_range_lower() {
659 assert_eq!(
660 get_lp_position_liquidation_prices(
661 *TICK_LOWER_INDEX,
662 *TICK_UPPER_INDEX,
663 *LIQUIDITY,
664 0, 0, 0, 11000_000_000, HUNDRED_PERCENT * 83 / 100 ),
670 Ok(LiquidationPrices {
671 lower: 204.60489065334323,
672 upper: 0.0
673 })
674 );
675 }
676
677 #[test]
678 fn test_liquidation_price_inside_range_upper() {
679 assert_eq!(
680 get_lp_position_liquidation_prices(
681 *TICK_LOWER_INDEX,
682 *TICK_UPPER_INDEX,
683 *LIQUIDITY,
684 0, 0, 51_000_000, 0, HUNDRED_PERCENT * 83 / 100 ),
690 Ok(LiquidationPrices {
691 lower: 0.0,
692 upper: 220.16318077637644
693 })
694 );
695 }
696
697 #[test]
698 fn test_liquidation_price_inside_range_lower_and_upper() {
699 assert_eq!(
700 get_lp_position_liquidation_prices(
701 *TICK_LOWER_INDEX,
702 *TICK_UPPER_INDEX,
703 *LIQUIDITY,
704 0, 0, 11_500_000, 8700_000_000, HUNDRED_PERCENT * 83 / 100 ),
710 Ok(LiquidationPrices {
711 lower: 210.75514596082337,
712 upper: 219.48595430071575
713 })
714 );
715 }
716
717 #[test]
718 fn test_lp_increase_quote_collateral_a_and_b_provided() {
719 assert_eq!(
720 get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
721 collateral_a: 1000000,
722 collateral_b: 1000000,
723 borrow_a: 2000000,
724 borrow_b: 2000000,
725 tick_lower_index: price_to_tick_index(1.0, 1, 1),
726 sqrt_price: price_to_sqrt_price(2.0, 1, 1),
727 tick_upper_index: price_to_tick_index(4.0, 1, 1),
728 protocol_fee_rate: (HUNDRED_PERCENT / 100) as u16,
729 protocol_fee_rate_on_collateral: (HUNDRED_PERCENT / 100) as u16,
730 swap_fee_rate: 10000, max_amount_slippage: HUNDRED_PERCENT / 10,
732 liquidation_threshold: HUNDRED_PERCENT * 83 / 100,
733 }),
734 Ok(IncreaseLpPositionQuoteResult {
735 collateral_a: 1000000,
736 collateral_b: 1000000,
737 max_collateral_a: 1000000,
738 max_collateral_b: 1000000,
739 borrow_a: 2000000,
740 borrow_b: 2000000,
741 total_a: 2223701,
742 total_b: 4447744,
743 min_total_a: 2001331,
744 min_total_b: 4002970,
745 swap_input: 742586,
746 swap_output: 1470320,
747 swap_a_to_b: true,
748 protocol_fee_a: 30000,
749 protocol_fee_b: 30000,
750 liquidity: 10737803,
751 leverage: 3.0724343435529677,
752 liquidation_lower_price: 0.8143170288470588,
753 liquidation_upper_price: 3.4020457909456225,
754 })
755 );
756 }
757
758 #[test]
759 fn test_lp_increase_quote_collateral_a_provided() {
760 assert_eq!(
761 get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
762 collateral_a: 10000000,
763 collateral_b: 0,
764 borrow_a: 0,
765 borrow_b: 0,
766 tick_lower_index: price_to_tick_index(0.25, 6, 9),
767 sqrt_price: price_to_sqrt_price(0.5, 6, 9),
768 tick_upper_index: price_to_tick_index(1.0, 6, 9),
769 protocol_fee_rate: (HUNDRED_PERCENT / 100) as u16,
770 protocol_fee_rate_on_collateral: (HUNDRED_PERCENT / 100) as u16,
771 swap_fee_rate: 10000, max_amount_slippage: HUNDRED_PERCENT / 10,
773 liquidation_threshold: HUNDRED_PERCENT * 83 / 100,
774 }),
775 Ok(IncreaseLpPositionQuoteResult {
776 collateral_a: 10000000,
777 collateral_b: 0,
778 max_collateral_a: 10000000,
779 max_collateral_b: 0,
780 borrow_a: 0,
781 borrow_b: 0,
782 total_a: 4925137,
783 total_b: 2462680451,
784 min_total_a: 4432624,
785 min_total_b: 2216412406,
786 swap_input: 4950113,
787 swap_output: 2450305500,
788 swap_a_to_b: true,
789 protocol_fee_a: 100000,
790 protocol_fee_b: 0,
791 liquidity: 376005629,
792 leverage: 1.0,
793 liquidation_lower_price: 0.0,
794 liquidation_upper_price: 0.0,
795 })
796 );
797 }
798
799 #[test]
800 fn test_lp_increase_quote_collateral_a_provided_b_computed() {
801 assert_eq!(
802 get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
803 collateral_a: 10000000,
804 collateral_b: COMPUTED_AMOUNT,
805 borrow_a: 2000000,
806 borrow_b: COMPUTED_AMOUNT,
807 tick_lower_index: price_to_tick_index(1.0, 1, 1),
808 sqrt_price: price_to_sqrt_price(3.0, 1, 1),
809 tick_upper_index: price_to_tick_index(4.0, 1, 1),
810 protocol_fee_rate: 0,
811 protocol_fee_rate_on_collateral: 0,
812 swap_fee_rate: 0,
813 max_amount_slippage: HUNDRED_PERCENT / 10,
814 liquidation_threshold: HUNDRED_PERCENT * 83 / 100,
815 }),
816 Ok(IncreaseLpPositionQuoteResult {
817 collateral_a: 10000000,
818 collateral_b: 94660495,
819 max_collateral_a: 10000000,
820 max_collateral_b: 104126544,
821 borrow_a: 2000000,
822 borrow_b: 18932100,
823 total_a: 12000000,
824 total_b: 113592595,
825 min_total_a: 10800000,
826 min_total_b: 102233336,
827 swap_input: 0,
828 swap_output: 0,
829 swap_a_to_b: false,
830 protocol_fee_a: 0,
831 protocol_fee_b: 0,
832 liquidity: 155170370,
833 leverage: 1.2,
834 liquidation_lower_price: 0.30342990360419136,
835 liquidation_upper_price: 54.925553349999994
836 })
837 );
838 }
839
840 #[test]
841 fn test_lp_increase_quote_collateral_a_computed_b_provided() {
842 assert_eq!(
843 get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
844 collateral_a: COMPUTED_AMOUNT,
845 collateral_b: 1000000,
846 borrow_a: COMPUTED_AMOUNT,
847 borrow_b: 2000000,
848 tick_lower_index: price_to_tick_index(1.0, 1, 1),
849 sqrt_price: price_to_sqrt_price(3.0, 1, 1),
850 tick_upper_index: price_to_tick_index(4.0, 1, 1),
851 protocol_fee_rate: 0,
852 protocol_fee_rate_on_collateral: 0,
853 swap_fee_rate: 0,
854 max_amount_slippage: HUNDRED_PERCENT / 10,
855 liquidation_threshold: HUNDRED_PERCENT * 83 / 100,
856 }),
857 Ok(IncreaseLpPositionQuoteResult {
858 collateral_a: 105640,
859 collateral_b: 1000000,
860 max_collateral_a: 116204,
861 max_collateral_b: 1000000,
862 borrow_a: 211282,
863 borrow_b: 2000000,
864 total_a: 316922,
865 total_b: 3000000,
866 min_total_a: 285230,
867 min_total_b: 2700000,
868 swap_input: 0,
869 swap_output: 0,
870 swap_a_to_b: false,
871 protocol_fee_a: 0,
872 protocol_fee_b: 0,
873 liquidity: 4098075,
874 leverage: 3.000003796737843,
875 liquidation_lower_price: 1.4306915613018771,
876 liquidation_upper_price: 6.63182675287057
877 })
878 );
879 }
880
881 #[test]
882 fn test_lp_increase_quote_one_sided_collateral_a_provided_b_computed() {
883 assert_eq!(
884 get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
885 collateral_a: 10000000,
886 collateral_b: COMPUTED_AMOUNT,
887 borrow_a: 2000000,
888 borrow_b: COMPUTED_AMOUNT,
889 tick_lower_index: price_to_tick_index(1.0, 1, 1),
890 sqrt_price: price_to_sqrt_price(0.5, 1, 1),
891 tick_upper_index: price_to_tick_index(4.0, 1, 1),
892 protocol_fee_rate: 0,
893 protocol_fee_rate_on_collateral: 0,
894 swap_fee_rate: 0,
895 max_amount_slippage: HUNDRED_PERCENT / 10,
896 liquidation_threshold: HUNDRED_PERCENT * 83 / 100,
897 }),
898 Ok(IncreaseLpPositionQuoteResult {
899 collateral_a: 10000000,
900 collateral_b: 0,
901 max_collateral_a: 10000000,
902 max_collateral_b: 0,
903 borrow_a: 2000000,
904 borrow_b: 0,
905 total_a: 12000000,
906 total_b: 0,
907 min_total_a: 10800000,
908 min_total_b: 0,
909 swap_input: 0,
910 swap_output: 0,
911 swap_a_to_b: false,
912 protocol_fee_a: 0,
913 protocol_fee_b: 0,
914 liquidity: 24000764,
915 leverage: 1.2,
916 liquidation_lower_price: 0.0,
917 liquidation_upper_price: 9.959682525
918 })
919 );
920 }
921
922 #[test]
923 fn test_lp_increase_quote_one_sided_collateral_a_computed_b_provided() {
924 assert_eq!(
925 get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
926 collateral_a: COMPUTED_AMOUNT,
927 collateral_b: 1000000,
928 borrow_a: COMPUTED_AMOUNT,
929 borrow_b: 2000000,
930 tick_lower_index: price_to_tick_index(1.0, 1, 1),
931 sqrt_price: price_to_sqrt_price(5.0, 1, 1),
932 tick_upper_index: price_to_tick_index(4.0, 1, 1),
933 protocol_fee_rate: 0,
934 protocol_fee_rate_on_collateral: 0,
935 swap_fee_rate: 0,
936 max_amount_slippage: HUNDRED_PERCENT / 10,
937 liquidation_threshold: HUNDRED_PERCENT * 83 / 100,
938 }),
939 Ok(IncreaseLpPositionQuoteResult {
940 collateral_a: 0,
941 collateral_b: 1000000,
942 max_collateral_a: 0,
943 max_collateral_b: 1000000,
944 borrow_a: 0,
945 borrow_b: 2000000,
946 total_a: 0,
947 total_b: 3000000,
948 min_total_a: 0,
949 min_total_b: 2700000,
950 swap_input: 0,
951 swap_output: 0,
952 swap_a_to_b: false,
953 protocol_fee_a: 0,
954 protocol_fee_b: 0,
955 liquidity: 3000191,
956 leverage: 3.0,
957 liquidation_lower_price: 1.8840617719481452,
958 liquidation_upper_price: 0.0
959 })
960 );
961 }
962
963 #[test]
964 fn test_lp_increase_quote_verify_liquidation_prices() {
965 let quote = get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
966 collateral_a: 0,
967 collateral_b: 1000_000_000,
968 borrow_a: 3_000_000,
969 borrow_b: 100_000_000,
970 tick_lower_index: price_to_tick_index(180.736, 6, 6),
971 sqrt_price: price_to_sqrt_price(213.41, 6, 6),
972 tick_upper_index: price_to_tick_index(225.66, 6, 6),
973 protocol_fee_rate: 500,
974 protocol_fee_rate_on_collateral: 500,
975 swap_fee_rate: 40,
976 max_amount_slippage: HUNDRED_PERCENT / 10,
977 liquidation_threshold: HUNDRED_PERCENT * 83 / 100,
978 })
979 .unwrap();
980
981 assert_eq!(quote.liquidation_lower_price, 23.805241869982023);
982 assert_eq!(quote.liquidation_upper_price, 451.38033819333333);
983 }
984
985 #[test]
986 fn test_repay_debt_quote() {
987 let quote = get_repay_lp_position_debt_quote(RepayLpPositionDebtQuoteArgs {
988 repay_a: 1_000_000,
989 repay_b: 30_000_000,
990 liquidity: 1109671058,
991 debt_a: 3_000_000,
992 debt_b: 100_000_000,
993 leftovers_a: 2,
994 leftovers_b: 15,
995 tick_lower_index: price_to_tick_index(180.736, 6, 6),
996 sqrt_price: price_to_sqrt_price(213.41, 6, 6),
997 tick_upper_index: price_to_tick_index(225.66, 6, 6),
998 liquidation_threshold: HUNDRED_PERCENT * 83 / 100,
999 })
1000 .unwrap();
1001
1002 assert_eq!(quote.debt_a, 2_000_000);
1003 assert_eq!(quote.debt_b, 70_000_000);
1004 assert_eq!(quote.liquidation_lower_price, 13.459576327110664);
1005 assert_eq!(quote.liquidation_upper_price, 692.0710879340029);
1006 }
1007}