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