defituna_core/quote/
tuna_lp_position.rs

1#![allow(clippy::collapsible_else_if)]
2#![allow(clippy::too_many_arguments)]
3
4use crate::{calculate_tuna_protocol_fee, CoreError, COMPUTED_AMOUNT, HUNDRED_PERCENT, INVALID_ARGUMENTS};
5use fusionamm_core::{
6    position_ratio_x64, tick_index_to_sqrt_price, try_apply_swap_fee, try_get_amount_delta_a, try_get_amount_delta_b, try_get_liquidity_from_a,
7    try_get_liquidity_from_amounts, try_get_liquidity_from_b, try_get_token_a_from_liquidity, try_get_token_b_from_liquidity, Q64_RESOLUTION,
8};
9
10#[cfg(feature = "wasm")]
11use fusionamm_macros::wasm_expose;
12
13#[derive(Debug, Copy, Clone, PartialEq)]
14#[cfg_attr(feature = "wasm", wasm_expose)]
15pub struct LiquidationPrices {
16    pub lower: f64,
17    pub upper: f64,
18}
19
20///
21/// Calculates the liquidation prices inside the position's range.
22///
23/// t - liquidation threshold
24/// Dx - debt x
25/// Dy - debt y
26/// Lx - leftovers x
27/// Ly - leftovers y
28/// √P = x - current price
29/// √Pu = u - upper sqrt price
30/// √Pl = l - lower sqrt price
31/// L - liquidity
32///
33/// t = (Dx⋅P + Dy) / (Δx⋅P + Lx⋅P + Δy + Ly), where
34///   Δx = L⋅(1/x - 1/u)
35///   Δy = L⋅(x - l)
36///
37/// x1,2 = ...
38///
39/// # Parameters
40/// - `lower_sqrt_price`: The lower square root price boundary.
41/// - `upper_sqrt_price`: The upper square root price boundary.
42/// - `liquidity`: The liquidity provided by the user.
43/// - `leftovers_a`: The amount of leftovers A in the existing position.
44/// - `leftovers_b`: The amount of leftovers B in the existing position*
45/// - `debt_a`: The amount of tokens A borrowed.
46/// - `debt_b`: The amount of tokens B borrowed.
47/// - `liquidation_threshold`: The liquidation threshold of the liquidator.
48///
49/// # Returns
50/// - `LiquidationPrices`: An object containing lower/upper liquidation prices.
51fn compute_liquidation_prices_inside(
52    lower_sqrt_price: u128,
53    upper_sqrt_price: u128,
54    liquidity: u128,
55    leftovers_a: u64,
56    leftovers_b: u64,
57    debt_a: u64,
58    debt_b: u64,
59    liquidation_threshold: u32,
60) -> Result<LiquidationPrices, CoreError> {
61    let liquidation_threshold_f = liquidation_threshold as f64 / HUNDRED_PERCENT as f64;
62    let liquidity_f = liquidity as f64;
63    let lower_sqrt_price_f = lower_sqrt_price as f64 / Q64_RESOLUTION;
64    let upper_sqrt_price_f = upper_sqrt_price as f64 / Q64_RESOLUTION;
65
66    let a = debt_a as f64 + liquidation_threshold_f * (liquidity_f / upper_sqrt_price_f - leftovers_a as f64);
67    let b = -2.0 * liquidation_threshold_f * liquidity_f;
68    let c = debt_b as f64 + liquidation_threshold_f * (liquidity_f * lower_sqrt_price_f - leftovers_b as f64);
69    let d = b * b - 4.0 * a * c;
70
71    let mut lower_liquidation_sqrt_price = 0.0;
72    let mut upper_liquidation_sqrt_price = 0.0;
73
74    if d >= 0.0 {
75        lower_liquidation_sqrt_price = (-b - d.sqrt()) / (2.0 * a);
76        upper_liquidation_sqrt_price = (-b + d.sqrt()) / (2.0 * a);
77        if lower_liquidation_sqrt_price < 0.0 || lower_liquidation_sqrt_price < lower_sqrt_price_f {
78            lower_liquidation_sqrt_price = 0.0;
79        }
80        if upper_liquidation_sqrt_price < 0.0 || upper_liquidation_sqrt_price > upper_sqrt_price_f {
81            upper_liquidation_sqrt_price = 0.0;
82        }
83    }
84
85    Ok(LiquidationPrices {
86        lower: lower_liquidation_sqrt_price * lower_liquidation_sqrt_price,
87        upper: upper_liquidation_sqrt_price * upper_liquidation_sqrt_price,
88    })
89}
90
91/// Calculates a liquidation price for the outside range (above OR under).
92///
93/// liquidation_threshold = total_debt / (total_balance + leftovers)
94///
95/// t - liquidation threshold
96/// P - liquidation price
97/// Dx - debt x
98/// Dy - debt y
99/// Lx - leftovers x
100/// Ly - leftovers y
101/// X - amount of x tokens
102/// Y - amount of y tokens
103///
104/// Lower liquidation price:
105/// t = (Dx + Dy / P) / (X + Lx + Ly/P)  =>  P = (Dy - t⋅Ly) / (t⋅(X+Lx) - Dx)
106///
107/// Upper liquidation price:
108/// t = (Dx⋅P + Dy) / (Y + Lx⋅P + Ly)  =>  P = (t⋅(Y + Ly) - Dy) / (Dx - t⋅Lx)
109///
110/// # Parameters
111/// - `amount_a`: The amount of tokens A at the boundary price.
112/// - `amount_b`: The amount of tokens B at the boundary price.
113/// - `leftovers_a`: The amount of leftovers A.
114/// - `leftovers_b`: The amount of leftovers B.
115/// - `debt_a`: The amount of tokens A borrowed.
116/// - `debt_b`: The amount of tokens B borrowed.
117/// - `liquidation_threshold`: - The liquidation threshold of the liquidator.
118///
119/// # Returns
120/// The calculated liquidation price.
121fn calculate_liquidation_outside(
122    amount_a: u64,
123    amount_b: u64,
124    leftovers_a: u64,
125    leftovers_b: u64,
126    debt_a: u64,
127    debt_b: u64,
128    liquidation_threshold: u32,
129) -> Result<f64, CoreError> {
130    let liquidation_threshold_f = liquidation_threshold as f64 / HUNDRED_PERCENT as f64;
131
132    if amount_a == 0 && amount_b == 0 {
133        Ok(0.0)
134    } else if amount_a > 0 && amount_b == 0 {
135        // Lower liquidation price
136        let numerator = debt_b as f64 - liquidation_threshold_f * leftovers_b as f64;
137        let denominator = liquidation_threshold_f * (amount_a + leftovers_a) as f64 - debt_a as f64;
138        Ok(numerator / denominator)
139    } else if amount_a == 0 && amount_b > 0 {
140        // Upper liquidation price
141        let numerator = liquidation_threshold_f * (amount_b + leftovers_b) as f64 - debt_b as f64;
142        let denominator = debt_a as f64 - liquidation_threshold_f * leftovers_a as f64;
143        if denominator == 0.0 {
144            return Ok(0.0);
145        }
146        Ok(numerator / denominator)
147    } else {
148        Err(INVALID_ARGUMENTS)
149    }
150}
151
152/// Calculates the liquidation prices outside the position's range (above AND under).
153///
154/// # Parameters
155/// - `lower_sqrt_price`: The lower square root price boundary.
156/// - `upper_sqrt_price`: The upper square root price boundary.
157/// - `liquidity`: The liquidity provided by the user.
158/// - `leftovers_a`: The amount of leftovers A.
159/// - `leftovers_b`: The amount of leftovers B.///
160/// - `debt_a`: The amount of tokens A borrowed.
161/// - `debt_b`: The amount of tokens B borrowed.
162/// - `liquidation_threshold`: The liquidation threshold of the liquidator.
163///
164/// # Returns
165/// - `LiquidationPrices`: An object containing lower/upper liquidation prices.
166fn compute_liquidation_prices_outside(
167    lower_sqrt_price: u128,
168    upper_sqrt_price: u128,
169    liquidity: u128,
170    leftovers_a: u64,
171    leftovers_b: u64,
172    debt_a: u64,
173    debt_b: u64,
174    liquidation_threshold: u32,
175) -> Result<LiquidationPrices, CoreError> {
176    let amount_a = try_get_amount_delta_a(lower_sqrt_price.into(), upper_sqrt_price.into(), liquidity.into(), false)?;
177    let amount_b = try_get_amount_delta_b(lower_sqrt_price.into(), upper_sqrt_price.into(), liquidity.into(), false)?;
178
179    let mut liquidation_price_for_a = calculate_liquidation_outside(amount_a, 0, leftovers_a, leftovers_b, debt_a, debt_b, liquidation_threshold)?;
180    let mut liquidation_price_for_b = calculate_liquidation_outside(0, amount_b, leftovers_a, leftovers_b, debt_a, debt_b, liquidation_threshold)?;
181
182    if liquidation_price_for_a < 0.0 || liquidation_price_for_a > (lower_sqrt_price as f64 / Q64_RESOLUTION).powf(2.0) {
183        liquidation_price_for_a = 0.0;
184    }
185    if liquidation_price_for_b < 0.0 || liquidation_price_for_b < (upper_sqrt_price as f64 / Q64_RESOLUTION).powf(2.0) {
186        liquidation_price_for_b = 0.0;
187    }
188
189    Ok(LiquidationPrices {
190        lower: liquidation_price_for_a,
191        upper: liquidation_price_for_b,
192    })
193}
194
195/// Computes the liquidation prices for an existing position.
196///
197/// # Parameters
198/// - `tick_lower_index`: The lower tick index of the position.
199/// - `tick_upper_index`: The upper tick index of the position.
200/// - `leftovers_a`: The amount of leftovers A in the position.
201/// - `leftovers_a`: The amount of leftovers B in the position.
202/// - `liquidity`: Liquidity of the position.
203/// - `debt_a`: The amount of tokens A borrowed.
204/// - `debt_b`: The amount of tokens B borrowed.
205/// - `liquidation_threshold`: The liquidation threshold of the market.
206///
207/// # Returns
208/// - `LiquidationPrices`: An object containing lower/upper liquidation prices.
209#[cfg_attr(feature = "wasm", wasm_expose)]
210pub fn get_lp_position_liquidation_prices(
211    tick_lower_index: i32,
212    tick_upper_index: i32,
213    liquidity: u128,
214    leftovers_a: u64,
215    leftovers_b: u64,
216    debt_a: u64,
217    debt_b: u64,
218    liquidation_threshold: u32,
219) -> Result<LiquidationPrices, CoreError> {
220    if tick_lower_index >= tick_upper_index {
221        return Err(INVALID_ARGUMENTS);
222    }
223
224    if liquidation_threshold >= HUNDRED_PERCENT {
225        return Err(INVALID_ARGUMENTS);
226    }
227
228    let lower_sqrt_price = tick_index_to_sqrt_price(tick_lower_index);
229    let upper_sqrt_price = tick_index_to_sqrt_price(tick_upper_index);
230
231    let liquidation_price_inside = compute_liquidation_prices_inside(
232        lower_sqrt_price,
233        upper_sqrt_price,
234        liquidity,
235        leftovers_a,
236        leftovers_b,
237        debt_a,
238        debt_b,
239        liquidation_threshold,
240    )?;
241
242    let liquidation_price_outside = compute_liquidation_prices_outside(
243        lower_sqrt_price,
244        upper_sqrt_price,
245        liquidity,
246        leftovers_a,
247        leftovers_b,
248        debt_a,
249        debt_b,
250        liquidation_threshold,
251    )?;
252
253    let lower_liquidation_price = if liquidation_price_inside.lower > 0.0 {
254        liquidation_price_inside.lower
255    } else {
256        liquidation_price_outside.lower
257    };
258
259    let upper_liquidation_price = if liquidation_price_inside.upper > 0.0 {
260        liquidation_price_inside.upper
261    } else {
262        liquidation_price_outside.upper
263    };
264
265    Ok(LiquidationPrices {
266        lower: lower_liquidation_price,
267        upper: upper_liquidation_price,
268    })
269}
270
271#[derive(Debug, Copy, Clone, PartialEq)]
272#[cfg_attr(feature = "wasm", wasm_expose)]
273pub struct IncreaseLpPositionQuoteArgs {
274    /** Collateral in token A or COMPUTED_AMOUNT. */
275    pub collateral_a: u64,
276    /** Collateral in token B or COMPUTED_AMOUNT. */
277    pub collateral_b: u64,
278    /** Amount to borrow in token A. Must be set to COMPUTED_AMOUNT if collateral_a is COMPUTED_AMOUNT. */
279    pub borrow_a: u64,
280    /** Amount to borrow in token B. Must be set to COMPUTED_AMOUNT if collateral_b is COMPUTED_AMOUNT. */
281    pub borrow_b: u64,
282    /** Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).*/
283    pub protocol_fee_rate: u16,
284    /** Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100). */
285    pub protocol_fee_rate_on_collateral: u16,
286    /** The swap fee rate of a pool denominated in 1e6. */
287    pub swap_fee_rate: u16,
288    /** Current sqrt price. */
289    pub sqrt_price: u128,
290    /** Position lower tick index. */
291    pub tick_lower_index: i32,
292    /** Position upper tick index. */
293    pub tick_upper_index: i32,
294    /** Maximum slippage of the position total amount represented as hundredths of a basis point (0.01% = 100). */
295    pub max_amount_slippage: u32,
296    /** The liquidation threshold of the market. */
297    pub liquidation_threshold: u32,
298}
299
300#[derive(Debug, Copy, Clone, PartialEq)]
301#[cfg_attr(feature = "wasm", wasm_expose)]
302pub struct IncreaseLpPositionQuoteResult {
303    pub collateral_a: u64,
304    pub collateral_b: u64,
305    pub max_collateral_a: u64,
306    pub max_collateral_b: u64,
307    pub borrow_a: u64,
308    pub borrow_b: u64,
309    pub total_a: u64,
310    pub total_b: u64,
311    pub min_total_a: u64,
312    pub min_total_b: u64,
313    pub swap_input: u64,
314    pub swap_output: u64,
315    pub swap_a_to_b: bool,
316    pub protocol_fee_a: u64,
317    pub protocol_fee_b: u64,
318    pub liquidation_lower_price: f64,
319    pub liquidation_upper_price: f64,
320}
321
322#[cfg_attr(feature = "wasm", wasm_expose)]
323pub fn get_increase_lp_position_quote(args: IncreaseLpPositionQuoteArgs) -> Result<IncreaseLpPositionQuoteResult, CoreError> {
324    let mut collateral_a = args.collateral_a;
325    let mut collateral_b = args.collateral_b;
326    let mut borrow_a = args.borrow_a;
327    let mut borrow_b = args.borrow_b;
328    let sqrt_price = args.sqrt_price;
329
330    if args.tick_lower_index > args.tick_upper_index {
331        return Err("Incorrect position tick index order: the lower tick must be less or equal the upper tick.");
332    }
333
334    if args.max_amount_slippage > HUNDRED_PERCENT {
335        return Err("max_amount_slippage must be in range [0; HUNDRED_PERCENT]");
336    }
337
338    if collateral_a == COMPUTED_AMOUNT && collateral_b == COMPUTED_AMOUNT {
339        return Err("Both collateral amounts can't be set to COMPUTED_AMOUNT");
340    }
341
342    let max_amount_slippage = args.max_amount_slippage;
343
344    let mut max_collateral_a = collateral_a;
345    let mut max_collateral_b = collateral_b;
346
347    let lower_sqrt_price = tick_index_to_sqrt_price(args.tick_lower_index);
348    let upper_sqrt_price = tick_index_to_sqrt_price(args.tick_upper_index);
349
350    if collateral_a == COMPUTED_AMOUNT {
351        if sqrt_price <= lower_sqrt_price {
352            return Err("sqrtPrice must be greater than lower_sqrt_price if collateral A is computed.");
353        } else if sqrt_price < upper_sqrt_price {
354            let liquidity = try_get_liquidity_from_b(collateral_b + borrow_b, lower_sqrt_price, sqrt_price)?;
355            let amount_a = try_get_token_a_from_liquidity(liquidity, sqrt_price, upper_sqrt_price, false)?;
356            collateral_a = (amount_a * collateral_b) / (collateral_b + borrow_b);
357            borrow_a = amount_a - collateral_a;
358            max_collateral_a = collateral_a + ((collateral_a as u128 * max_amount_slippage as u128) / HUNDRED_PERCENT as u128) as u64;
359        } else {
360            collateral_a = 0;
361            max_collateral_a = 0;
362            borrow_a = 0;
363        }
364    } else if collateral_b == COMPUTED_AMOUNT {
365        if sqrt_price <= lower_sqrt_price {
366            collateral_b = 0;
367            max_collateral_b = 0;
368            borrow_b = 0;
369        } else if sqrt_price < upper_sqrt_price {
370            let liquidity = try_get_liquidity_from_a(collateral_a + borrow_a, sqrt_price, upper_sqrt_price)?;
371            let amount_b = try_get_token_b_from_liquidity(liquidity, lower_sqrt_price, sqrt_price, false)?;
372            collateral_b = (amount_b * collateral_a) / (collateral_a + borrow_a);
373            borrow_b = amount_b - collateral_b;
374            max_collateral_b = collateral_b + ((collateral_b as u128 * max_amount_slippage as u128) / HUNDRED_PERCENT as u128) as u64;
375        } else {
376            return Err("sqrtPrice must be less than upper_sqrt_price if collateral B is computed.");
377        }
378    }
379
380    let protocol_fee_a = calculate_tuna_protocol_fee(collateral_a, borrow_a, args.protocol_fee_rate_on_collateral, args.protocol_fee_rate);
381    let provided_a = collateral_a + borrow_a - protocol_fee_a;
382
383    let protocol_fee_b = calculate_tuna_protocol_fee(collateral_b, borrow_b, args.protocol_fee_rate_on_collateral, args.protocol_fee_rate);
384    let provided_b = collateral_b + borrow_b - protocol_fee_b;
385
386    let mut swap_input = 0;
387    let mut swap_output = 0;
388    let mut swap_a_to_b = false;
389    let mut total_a = provided_a;
390    let mut total_b = provided_b;
391
392    if args.collateral_a != COMPUTED_AMOUNT && args.collateral_b != COMPUTED_AMOUNT {
393        let position_ratio = position_ratio_x64(sqrt_price.into(), args.tick_lower_index, args.tick_upper_index);
394        let ratio_a = position_ratio.ratio_a as f64 / Q64_RESOLUTION;
395        let ratio_b = position_ratio.ratio_b as f64 / Q64_RESOLUTION;
396
397        let price = (sqrt_price as f64 / Q64_RESOLUTION).powf(2.0);
398
399        // Estimated total position size.
400        let mut total = (provided_a as f64 * price + provided_b as f64) as u64;
401        total_a = (total as f64 * ratio_a / price) as u64;
402        total_b = (total as f64 * ratio_b) as u64;
403
404        let mut fee_a = 0;
405        let mut fee_b = 0;
406
407        if total_a < provided_a {
408            swap_input = provided_a - total_a;
409            fee_a = swap_input - try_apply_swap_fee(swap_input, args.swap_fee_rate)?;
410            swap_output = ((swap_input - fee_a) as f64 * price) as u64;
411            swap_a_to_b = true;
412        } else if total_b < provided_b {
413            swap_input = provided_b - total_b;
414            fee_b = swap_input - try_apply_swap_fee(swap_input, args.swap_fee_rate)?;
415            swap_output = ((swap_input - fee_b) as f64 / price) as u64;
416            swap_a_to_b = false;
417        }
418
419        // Recompute totals with applied swap fee.
420        total = ((provided_a - fee_a) as f64 * price) as u64 + provided_b - fee_b;
421        total_a = ((total as f64 * ratio_a) / price) as u64;
422        total_b = (total as f64 * ratio_b) as u64;
423    }
424
425    let min_total_a = total_a - ((total_a as u128 * max_amount_slippage as u128) / HUNDRED_PERCENT as u128) as u64;
426    let min_total_b = total_b - ((total_b as u128 * max_amount_slippage as u128) / HUNDRED_PERCENT as u128) as u64;
427
428    let liquidity = try_get_liquidity_from_amounts(sqrt_price, lower_sqrt_price, upper_sqrt_price, total_a, total_b)?;
429    let liquidation_prices = get_lp_position_liquidation_prices(
430        args.tick_lower_index,
431        args.tick_upper_index,
432        liquidity,
433        0,
434        0,
435        borrow_a,
436        borrow_b,
437        args.liquidation_threshold,
438    )?;
439
440    Ok(IncreaseLpPositionQuoteResult {
441        collateral_a,
442        collateral_b,
443        max_collateral_a,
444        max_collateral_b,
445        borrow_a,
446        borrow_b,
447        total_a,
448        total_b,
449        min_total_a,
450        min_total_b,
451        swap_input,
452        swap_output,
453        swap_a_to_b,
454        protocol_fee_a,
455        protocol_fee_b,
456        liquidation_lower_price: liquidation_prices.lower,
457        liquidation_upper_price: liquidation_prices.upper,
458    })
459}
460
461#[cfg(all(test, not(feature = "wasm")))]
462mod tests {
463    use crate::{
464        get_increase_lp_position_quote, get_lp_position_liquidation_prices, IncreaseLpPositionQuoteArgs, IncreaseLpPositionQuoteResult,
465        LiquidationPrices, COMPUTED_AMOUNT, HUNDRED_PERCENT,
466    };
467    use fusionamm_core::{price_to_sqrt_price, price_to_tick_index, tick_index_to_sqrt_price, try_get_liquidity_from_b};
468    use once_cell::sync::Lazy;
469
470    pub static TICK_LOWER_INDEX: Lazy<i32> = Lazy::new(|| price_to_tick_index(180.736, 6, 6));
471    pub static TICK_UPPER_INDEX: Lazy<i32> = Lazy::new(|| price_to_tick_index(225.66, 6, 6));
472    pub static SQRT_PRICE: Lazy<u128> = Lazy::new(|| price_to_sqrt_price(213.41, 6, 6));
473    pub static LIQUIDITY: Lazy<u128> =
474        Lazy::new(|| try_get_liquidity_from_b(10000_000_000, tick_index_to_sqrt_price(*TICK_LOWER_INDEX), *SQRT_PRICE).unwrap());
475
476    #[test]
477    fn test_liquidation_price_outside_range_lower() {
478        assert_eq!(
479            get_lp_position_liquidation_prices(
480                *TICK_LOWER_INDEX,
481                *TICK_UPPER_INDEX,
482                *LIQUIDITY,
483                0,                          // leftovers_a
484                0,                          // leftovers_b
485                0,                          // debt_a
486                9807_000_000,               // debt_b
487                HUNDRED_PERCENT * 83 / 100  // liquidation_threshold
488            ),
489            Ok(LiquidationPrices {
490                lower: 176.12815046153585,
491                upper: 0.0
492            })
493        );
494    }
495
496    #[test]
497    fn test_liquidation_price_outside_range_upper() {
498        assert_eq!(
499            get_lp_position_liquidation_prices(
500                *TICK_LOWER_INDEX,
501                *TICK_UPPER_INDEX,
502                *LIQUIDITY,
503                0,                          // leftovers_a
504                0,                          // leftovers_b
505                20_000_000,                 // debt_a
506                0,                          // debt_b
507                HUNDRED_PERCENT * 83 / 100  // liquidation_threshold
508            ),
509            Ok(LiquidationPrices {
510                lower: 0.0,
511                upper: 562.219410388
512            })
513        );
514    }
515
516    #[test]
517    fn test_liquidation_price_outside_range_lower_and_upper() {
518        assert_eq!(
519            get_lp_position_liquidation_prices(
520                *TICK_LOWER_INDEX,
521                *TICK_UPPER_INDEX,
522                *LIQUIDITY,
523                0,                          // leftovers_a
524                0,                          // leftovers_b
525                10_000_000,                 // debt_a
526                5000_000_000,               // debt_b
527                HUNDRED_PERCENT * 83 / 100  // liquidation_threshold
528            ),
529            Ok(LiquidationPrices {
530                lower: 109.45458168998225,
531                upper: 624.4388207760001
532            })
533        );
534    }
535
536    #[test]
537    fn test_liquidation_price_inside_range_lower() {
538        assert_eq!(
539            get_lp_position_liquidation_prices(
540                *TICK_LOWER_INDEX,
541                *TICK_UPPER_INDEX,
542                *LIQUIDITY,
543                0,                          // leftovers_a
544                0,                          // leftovers_b
545                0,                          // debt_a
546                11000_000_000,              // debt_b
547                HUNDRED_PERCENT * 83 / 100  // liquidation_threshold
548            ),
549            Ok(LiquidationPrices {
550                lower: 204.60489065334323,
551                upper: 0.0
552            })
553        );
554    }
555
556    #[test]
557    fn test_liquidation_price_inside_range_upper() {
558        assert_eq!(
559            get_lp_position_liquidation_prices(
560                *TICK_LOWER_INDEX,
561                *TICK_UPPER_INDEX,
562                *LIQUIDITY,
563                0,                          // leftovers_a
564                0,                          // leftovers_b
565                51_000_000,                 // debt_a
566                0,                          // debt_b
567                HUNDRED_PERCENT * 83 / 100  // liquidation_threshold
568            ),
569            Ok(LiquidationPrices {
570                lower: 0.0,
571                upper: 220.16318077637644
572            })
573        );
574    }
575
576    #[test]
577    fn test_liquidation_price_inside_range_lower_and_upper() {
578        assert_eq!(
579            get_lp_position_liquidation_prices(
580                *TICK_LOWER_INDEX,
581                *TICK_UPPER_INDEX,
582                *LIQUIDITY,
583                0,                          // leftovers_a
584                0,                          // leftovers_b
585                11_500_000,                 // debt_a
586                8700_000_000,               // debt_b
587                HUNDRED_PERCENT * 83 / 100  // liquidation_threshold
588            ),
589            Ok(LiquidationPrices {
590                lower: 210.75514596082337,
591                upper: 219.48595430071575
592            })
593        );
594    }
595
596    #[test]
597    fn test_lp_increase_quote_collateral_a_and_b_provided() {
598        assert_eq!(
599            get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
600                collateral_a: 1000000,
601                collateral_b: 1000000,
602                borrow_a: 2000000,
603                borrow_b: 2000000,
604                tick_lower_index: price_to_tick_index(1.0, 1, 1),
605                sqrt_price: price_to_sqrt_price(2.0, 1, 1),
606                tick_upper_index: price_to_tick_index(4.0, 1, 1),
607                protocol_fee_rate: (HUNDRED_PERCENT / 100) as u16,
608                protocol_fee_rate_on_collateral: (HUNDRED_PERCENT / 100) as u16,
609                swap_fee_rate: 10000, // 1%
610                max_amount_slippage: HUNDRED_PERCENT / 10,
611                liquidation_threshold: HUNDRED_PERCENT * 83 / 100,
612            }),
613            Ok(IncreaseLpPositionQuoteResult {
614                collateral_a: 1000000,
615                collateral_b: 1000000,
616                max_collateral_a: 1000000,
617                max_collateral_b: 1000000,
618                borrow_a: 2000000,
619                borrow_b: 2000000,
620                total_a: 2223701,
621                total_b: 4447744,
622                min_total_a: 2001331,
623                min_total_b: 4002970,
624                swap_input: 742586,
625                swap_output: 1470320,
626                swap_a_to_b: true,
627                protocol_fee_a: 30000,
628                protocol_fee_b: 30000,
629                liquidation_lower_price: 0.8143170288470588,
630                liquidation_upper_price: 3.4020457909456225,
631            })
632        );
633    }
634
635    #[test]
636    fn test_lp_increase_quote_collateral_a_provided() {
637        assert_eq!(
638            get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
639                collateral_a: 10000000,
640                collateral_b: 0,
641                borrow_a: 0,
642                borrow_b: 0,
643                tick_lower_index: price_to_tick_index(0.25, 6, 9),
644                sqrt_price: price_to_sqrt_price(0.5, 6, 9),
645                tick_upper_index: price_to_tick_index(1.0, 6, 9),
646                protocol_fee_rate: (HUNDRED_PERCENT / 100) as u16,
647                protocol_fee_rate_on_collateral: (HUNDRED_PERCENT / 100) as u16,
648                swap_fee_rate: 10000, // 1%
649                max_amount_slippage: HUNDRED_PERCENT / 10,
650                liquidation_threshold: HUNDRED_PERCENT * 83 / 100,
651            }),
652            Ok(IncreaseLpPositionQuoteResult {
653                collateral_a: 10000000,
654                collateral_b: 0,
655                max_collateral_a: 10000000,
656                max_collateral_b: 0,
657                borrow_a: 0,
658                borrow_b: 0,
659                total_a: 4925137,
660                total_b: 2462680451,
661                min_total_a: 4432624,
662                min_total_b: 2216412406,
663                swap_input: 4950113,
664                swap_output: 2450305500,
665                swap_a_to_b: true,
666                protocol_fee_a: 100000,
667                protocol_fee_b: 0,
668                liquidation_lower_price: 0.0,
669                liquidation_upper_price: 0.0,
670            })
671        );
672    }
673
674    #[test]
675    fn test_lp_increase_quote_collateral_a_provided_b_computed() {
676        assert_eq!(
677            get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
678                collateral_a: 10000000,
679                collateral_b: COMPUTED_AMOUNT,
680                borrow_a: 2000000,
681                borrow_b: COMPUTED_AMOUNT,
682                tick_lower_index: price_to_tick_index(1.0, 1, 1),
683                sqrt_price: price_to_sqrt_price(3.0, 1, 1),
684                tick_upper_index: price_to_tick_index(4.0, 1, 1),
685                protocol_fee_rate: 0,
686                protocol_fee_rate_on_collateral: 0,
687                swap_fee_rate: 0,
688                max_amount_slippage: HUNDRED_PERCENT / 10,
689                liquidation_threshold: HUNDRED_PERCENT * 83 / 100,
690            }),
691            Ok(IncreaseLpPositionQuoteResult {
692                collateral_a: 10000000,
693                collateral_b: 94660495,
694                max_collateral_a: 10000000,
695                max_collateral_b: 104126544,
696                borrow_a: 2000000,
697                borrow_b: 18932100,
698                total_a: 12000000,
699                total_b: 113592595,
700                min_total_a: 10800000,
701                min_total_b: 102233336,
702                swap_input: 0,
703                swap_output: 0,
704                swap_a_to_b: false,
705                protocol_fee_a: 0,
706                protocol_fee_b: 0,
707                liquidation_lower_price: 0.30342990360419136,
708                liquidation_upper_price: 54.925553349999994
709            })
710        );
711    }
712
713    #[test]
714    fn test_lp_increase_quote_collateral_a_computed_b_provided() {
715        assert_eq!(
716            get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
717                collateral_a: COMPUTED_AMOUNT,
718                collateral_b: 1000000,
719                borrow_a: COMPUTED_AMOUNT,
720                borrow_b: 2000000,
721                tick_lower_index: price_to_tick_index(1.0, 1, 1),
722                sqrt_price: price_to_sqrt_price(3.0, 1, 1),
723                tick_upper_index: price_to_tick_index(4.0, 1, 1),
724                protocol_fee_rate: 0,
725                protocol_fee_rate_on_collateral: 0,
726                swap_fee_rate: 0,
727                max_amount_slippage: HUNDRED_PERCENT / 10,
728                liquidation_threshold: HUNDRED_PERCENT * 83 / 100,
729            }),
730            Ok(IncreaseLpPositionQuoteResult {
731                collateral_a: 105640,
732                collateral_b: 1000000,
733                max_collateral_a: 116204,
734                max_collateral_b: 1000000,
735                borrow_a: 211282,
736                borrow_b: 2000000,
737                total_a: 316922,
738                total_b: 3000000,
739                min_total_a: 285230,
740                min_total_b: 2700000,
741                swap_input: 0,
742                swap_output: 0,
743                swap_a_to_b: false,
744                protocol_fee_a: 0,
745                protocol_fee_b: 0,
746                liquidation_lower_price: 1.4306915613018771,
747                liquidation_upper_price: 6.63182675287057
748            })
749        );
750    }
751
752    #[test]
753    fn test_lp_increase_quote_one_sided_collateral_a_provided_b_computed() {
754        assert_eq!(
755            get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
756                collateral_a: 10000000,
757                collateral_b: COMPUTED_AMOUNT,
758                borrow_a: 2000000,
759                borrow_b: COMPUTED_AMOUNT,
760                tick_lower_index: price_to_tick_index(1.0, 1, 1),
761                sqrt_price: price_to_sqrt_price(0.5, 1, 1),
762                tick_upper_index: price_to_tick_index(4.0, 1, 1),
763                protocol_fee_rate: 0,
764                protocol_fee_rate_on_collateral: 0,
765                swap_fee_rate: 0,
766                max_amount_slippage: HUNDRED_PERCENT / 10,
767                liquidation_threshold: HUNDRED_PERCENT * 83 / 100,
768            }),
769            Ok(IncreaseLpPositionQuoteResult {
770                collateral_a: 10000000,
771                collateral_b: 0,
772                max_collateral_a: 10000000,
773                max_collateral_b: 0,
774                borrow_a: 2000000,
775                borrow_b: 0,
776                total_a: 12000000,
777                total_b: 0,
778                min_total_a: 10800000,
779                min_total_b: 0,
780                swap_input: 0,
781                swap_output: 0,
782                swap_a_to_b: false,
783                protocol_fee_a: 0,
784                protocol_fee_b: 0,
785                liquidation_lower_price: 0.0,
786                liquidation_upper_price: 9.959682525
787            })
788        );
789    }
790
791    #[test]
792    fn test_lp_increase_quote_one_sided_collateral_a_computed_b_provided() {
793        assert_eq!(
794            get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
795                collateral_a: COMPUTED_AMOUNT,
796                collateral_b: 1000000,
797                borrow_a: COMPUTED_AMOUNT,
798                borrow_b: 2000000,
799                tick_lower_index: price_to_tick_index(1.0, 1, 1),
800                sqrt_price: price_to_sqrt_price(5.0, 1, 1),
801                tick_upper_index: price_to_tick_index(4.0, 1, 1),
802                protocol_fee_rate: 0,
803                protocol_fee_rate_on_collateral: 0,
804                swap_fee_rate: 0,
805                max_amount_slippage: HUNDRED_PERCENT / 10,
806                liquidation_threshold: HUNDRED_PERCENT * 83 / 100,
807            }),
808            Ok(IncreaseLpPositionQuoteResult {
809                collateral_a: 0,
810                collateral_b: 1000000,
811                max_collateral_a: 0,
812                max_collateral_b: 1000000,
813                borrow_a: 0,
814                borrow_b: 2000000,
815                total_a: 0,
816                total_b: 3000000,
817                min_total_a: 0,
818                min_total_b: 2700000,
819                swap_input: 0,
820                swap_output: 0,
821                swap_a_to_b: false,
822                protocol_fee_a: 0,
823                protocol_fee_b: 0,
824                liquidation_lower_price: 1.8840617719481452,
825                liquidation_upper_price: 0.0
826            })
827        );
828    }
829
830    #[test]
831    fn test_lp_increase_quote_verify_liquidation_prices() {
832        let quote = get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
833            collateral_a: 0,
834            collateral_b: 1000_000_000,
835            borrow_a: 3_000_000,
836            borrow_b: 100_000_000,
837            tick_lower_index: price_to_tick_index(180.736, 6, 6),
838            sqrt_price: price_to_sqrt_price(213.41, 6, 6),
839            tick_upper_index: price_to_tick_index(225.66, 6, 6),
840            protocol_fee_rate: 500,
841            protocol_fee_rate_on_collateral: 500,
842            swap_fee_rate: 40,
843            max_amount_slippage: HUNDRED_PERCENT / 10,
844            liquidation_threshold: HUNDRED_PERCENT * 83 / 100,
845        })
846        .unwrap();
847
848        assert_eq!(quote.liquidation_lower_price, 23.805241869982023);
849        assert_eq!(quote.liquidation_upper_price, 451.38033819333333);
850    }
851}