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_b, try_get_token_a_from_liquidity, try_get_token_b_from_liquidity, Q64_RESOLUTION, U128,
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: u128 = tick_index_to_sqrt_price(tick_lower_index).into();
229    let upper_sqrt_price: u128 = tick_index_to_sqrt_price(tick_upper_index).into();
230    let liquidity: u128 = liquidity.into();
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    /** Collateral in token A or COMPUTED_AMOUNT. */
276    pub collateral_a: u64,
277    /** Collateral in token B or COMPUTED_AMOUNT. */
278    pub collateral_b: u64,
279    /** Amount to borrow in token A. Must be set to COMPUTED_AMOUNT if collateral_a is COMPUTED_AMOUNT. */
280    pub borrow_a: u64,
281    /** Amount to borrow in token B. Must be set to COMPUTED_AMOUNT if collateral_b is COMPUTED_AMOUNT. */
282    pub borrow_b: u64,
283    /** Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).*/
284    pub protocol_fee_rate: u16,
285    /** Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100). */
286    pub protocol_fee_rate_on_collateral: u16,
287    /** The swap fee rate of a pool denominated in 1e6. */
288    pub swap_fee_rate: u16,
289    /** Current sqrt price. */
290    pub sqrt_price: u128,
291    /** Position lower tick index. */
292    pub tick_lower_index: i32,
293    /** Position upper tick index. */
294    pub tick_upper_index: i32,
295    /** Maximum slippage of the position total amount represented as hundredths of a basis point (0.01% = 100). */
296    pub max_amount_slippage: u32,
297}
298
299#[derive(Debug, Copy, Clone, PartialEq)]
300#[cfg_attr(feature = "wasm", wasm_expose)]
301pub struct IncreaseLpPositionQuoteResult {
302    pub collateral_a: u64,
303    pub collateral_b: u64,
304    pub max_collateral_a: u64,
305    pub max_collateral_b: u64,
306    pub borrow_a: u64,
307    pub borrow_b: u64,
308    pub total_a: u64,
309    pub total_b: u64,
310    pub min_total_a: u64,
311    pub min_total_b: u64,
312    pub swap_input: u64,
313    pub swap_output: u64,
314    pub swap_a_to_b: bool,
315    pub protocol_fee_a: u64,
316    pub protocol_fee_b: u64,
317}
318
319#[cfg_attr(feature = "wasm", wasm_expose)]
320pub fn get_increase_lp_position_quote(args: IncreaseLpPositionQuoteArgs) -> Result<IncreaseLpPositionQuoteResult, CoreError> {
321    let mut collateral_a = args.collateral_a;
322    let mut collateral_b = args.collateral_b;
323    let mut borrow_a = args.borrow_a;
324    let mut borrow_b = args.borrow_b;
325    let sqrt_price = args.sqrt_price;
326
327    if args.tick_lower_index > args.tick_upper_index {
328        return Err("Incorrect position tick index order: the lower tick must be less or equal the upper tick.");
329    }
330
331    if args.max_amount_slippage > HUNDRED_PERCENT {
332        return Err("max_amount_slippage must be in range [0; HUNDRED_PERCENT]");
333    }
334
335    if collateral_a == COMPUTED_AMOUNT && collateral_b == COMPUTED_AMOUNT {
336        return Err("Both collateral amounts can't be set to COMPUTED_AMOUNT");
337    }
338
339    let max_amount_slippage = args.max_amount_slippage;
340
341    let mut max_collateral_a = collateral_a;
342    let mut max_collateral_b = collateral_b;
343
344    let lower_sqrt_price: u128 = tick_index_to_sqrt_price(args.tick_lower_index).into();
345    let upper_sqrt_price: u128 = tick_index_to_sqrt_price(args.tick_upper_index).into();
346
347    if collateral_a == COMPUTED_AMOUNT {
348        if sqrt_price <= lower_sqrt_price {
349            return Err("sqrtPrice must be greater than lower_sqrt_price if collateral A is computed.");
350        } else if sqrt_price < upper_sqrt_price {
351            let liquidity = try_get_liquidity_from_b(collateral_b + borrow_b, lower_sqrt_price, sqrt_price)?;
352            let amount_a = try_get_token_a_from_liquidity(liquidity, sqrt_price, upper_sqrt_price, false)?;
353            collateral_a = (amount_a * collateral_b) / (collateral_b + borrow_b);
354            borrow_a = amount_a - collateral_a;
355            max_collateral_a = collateral_a + ((collateral_a as u128 * max_amount_slippage as u128) / HUNDRED_PERCENT as u128) as u64;
356        } else {
357            collateral_a = 0;
358            max_collateral_a = 0;
359            borrow_a = 0;
360        }
361    } else if collateral_b == COMPUTED_AMOUNT {
362        if sqrt_price <= lower_sqrt_price {
363            collateral_b = 0;
364            max_collateral_b = 0;
365            borrow_b = 0;
366        } else if sqrt_price < upper_sqrt_price {
367            let liquidity = try_get_liquidity_from_a(collateral_a + borrow_a, sqrt_price, upper_sqrt_price)?;
368            let amount_b = try_get_token_b_from_liquidity(liquidity, lower_sqrt_price, sqrt_price, false)?;
369            collateral_b = (amount_b * collateral_a) / (collateral_a + borrow_a);
370            borrow_b = amount_b - collateral_b;
371            max_collateral_b = collateral_b + ((collateral_b as u128 * max_amount_slippage as u128) / HUNDRED_PERCENT as u128) as u64;
372        } else {
373            return Err("sqrtPrice must be less than upper_sqrt_price if collateral B is computed.");
374        }
375    }
376
377    let protocol_fee_a = calculate_tuna_protocol_fee(collateral_a, borrow_a, args.protocol_fee_rate_on_collateral, args.protocol_fee_rate);
378    let provided_a = collateral_a + borrow_a - protocol_fee_a;
379
380    let protocol_fee_b = calculate_tuna_protocol_fee(collateral_b, borrow_b, args.protocol_fee_rate_on_collateral, args.protocol_fee_rate);
381    let provided_b = collateral_b + borrow_b - protocol_fee_b;
382
383    let mut swap_input = 0;
384    let mut swap_output = 0;
385    let mut swap_a_to_b = false;
386    let mut total_a = provided_a;
387    let mut total_b = provided_b;
388
389    if args.collateral_a != COMPUTED_AMOUNT && args.collateral_b != COMPUTED_AMOUNT {
390        let position_ratio = position_ratio_x64(sqrt_price.into(), args.tick_lower_index, args.tick_upper_index);
391        let ratio_a = position_ratio.ratio_a as f64 / Q64_RESOLUTION;
392        let ratio_b = position_ratio.ratio_b as f64 / Q64_RESOLUTION;
393
394        let price = (sqrt_price as f64 / Q64_RESOLUTION).powf(2.0);
395
396        // Estimated total position size.
397        let mut total = (provided_a as f64 * price + provided_b as f64) as u64;
398        total_a = (total as f64 * ratio_a / price) as u64;
399        total_b = (total as f64 * ratio_b) as u64;
400
401        let mut fee_a = 0;
402        let mut fee_b = 0;
403
404        if total_a < provided_a {
405            swap_input = provided_a - total_a;
406            fee_a = swap_input - try_apply_swap_fee(swap_input, args.swap_fee_rate)?;
407            swap_output = ((swap_input - fee_a) as f64 * price) as u64;
408            swap_a_to_b = true;
409        } else if total_b < provided_b {
410            swap_input = provided_b - total_b;
411            fee_b = swap_input - try_apply_swap_fee(swap_input, args.swap_fee_rate)?;
412            swap_output = ((swap_input - fee_b) as f64 / price) as u64;
413            swap_a_to_b = false;
414        }
415
416        // Recompute totals with applied swap fee.
417        total = ((provided_a - fee_a) as f64 * price) as u64 + provided_b - fee_b;
418        total_a = ((total as f64 * ratio_a) / price) as u64;
419        total_b = (total as f64 * ratio_b) as u64;
420    }
421
422    let min_total_a = total_a - ((total_a as u128 * max_amount_slippage as u128) / HUNDRED_PERCENT as u128) as u64;
423    let min_total_b = total_b - ((total_b as u128 * max_amount_slippage as u128) / HUNDRED_PERCENT as u128) as u64;
424
425    Ok(IncreaseLpPositionQuoteResult {
426        collateral_a,
427        collateral_b,
428        max_collateral_a,
429        max_collateral_b,
430        borrow_a,
431        borrow_b,
432        total_a,
433        total_b,
434        min_total_a,
435        min_total_b,
436        swap_input,
437        swap_output,
438        swap_a_to_b,
439        protocol_fee_a,
440        protocol_fee_b,
441    })
442}
443
444#[cfg(all(test, not(feature = "wasm")))]
445mod tests {
446    use crate::{
447        get_increase_lp_position_quote, get_lp_position_liquidation_prices, IncreaseLpPositionQuoteArgs, IncreaseLpPositionQuoteResult,
448        LiquidationPrices, COMPUTED_AMOUNT, HUNDRED_PERCENT,
449    };
450    use fusionamm_core::{price_to_sqrt_price, price_to_tick_index, tick_index_to_sqrt_price, try_get_liquidity_from_b};
451    use once_cell::sync::Lazy;
452
453    pub static TICK_LOWER_INDEX: Lazy<i32> = Lazy::new(|| price_to_tick_index(180.736, 6, 6));
454    pub static TICK_UPPER_INDEX: Lazy<i32> = Lazy::new(|| price_to_tick_index(225.66, 6, 6));
455    pub static SQRT_PRICE: Lazy<u128> = Lazy::new(|| price_to_sqrt_price(213.41, 6, 6));
456    pub static LIQUIDITY: Lazy<u128> =
457        Lazy::new(|| try_get_liquidity_from_b(10000_000_000, tick_index_to_sqrt_price(*TICK_LOWER_INDEX), *SQRT_PRICE).unwrap());
458
459    #[test]
460    fn test_liquidation_price_outside_range_lower() {
461        assert_eq!(
462            get_lp_position_liquidation_prices(
463                *TICK_LOWER_INDEX,
464                *TICK_UPPER_INDEX,
465                *LIQUIDITY,
466                0,                          // leftovers_a
467                0,                          // leftovers_b
468                0,                          // debt_a
469                9807_000_000,               // debt_b
470                HUNDRED_PERCENT * 83 / 100  // liquidation_threshold
471            ),
472            Ok(LiquidationPrices {
473                lower: 176.12815046153585,
474                upper: 0.0
475            })
476        );
477    }
478
479    #[test]
480    fn test_liquidation_price_outside_range_upper() {
481        assert_eq!(
482            get_lp_position_liquidation_prices(
483                *TICK_LOWER_INDEX,
484                *TICK_UPPER_INDEX,
485                *LIQUIDITY,
486                0,                          // leftovers_a
487                0,                          // leftovers_b
488                20_000_000,                 // debt_a
489                0,                          // debt_b
490                HUNDRED_PERCENT * 83 / 100  // liquidation_threshold
491            ),
492            Ok(LiquidationPrices {
493                lower: 0.0,
494                upper: 562.219410388
495            })
496        );
497    }
498
499    #[test]
500    fn test_liquidation_price_outside_range_lower_and_upper() {
501        assert_eq!(
502            get_lp_position_liquidation_prices(
503                *TICK_LOWER_INDEX,
504                *TICK_UPPER_INDEX,
505                *LIQUIDITY,
506                0,                          // leftovers_a
507                0,                          // leftovers_b
508                10_000_000,                 // debt_a
509                5000_000_000,               // debt_b
510                HUNDRED_PERCENT * 83 / 100  // liquidation_threshold
511            ),
512            Ok(LiquidationPrices {
513                lower: 109.45458168998225,
514                upper: 624.4388207760001
515            })
516        );
517    }
518
519    #[test]
520    fn test_liquidation_price_inside_range_lower() {
521        assert_eq!(
522            get_lp_position_liquidation_prices(
523                *TICK_LOWER_INDEX,
524                *TICK_UPPER_INDEX,
525                *LIQUIDITY,
526                0,                          // leftovers_a
527                0,                          // leftovers_b
528                0,                          // debt_a
529                11000_000_000,              // debt_b
530                HUNDRED_PERCENT * 83 / 100  // liquidation_threshold
531            ),
532            Ok(LiquidationPrices {
533                lower: 204.60489065334323,
534                upper: 0.0
535            })
536        );
537    }
538
539    #[test]
540    fn test_liquidation_price_inside_range_upper() {
541        assert_eq!(
542            get_lp_position_liquidation_prices(
543                *TICK_LOWER_INDEX,
544                *TICK_UPPER_INDEX,
545                *LIQUIDITY,
546                0,                          // leftovers_a
547                0,                          // leftovers_b
548                51_000_000,                 // debt_a
549                0,                          // debt_b
550                HUNDRED_PERCENT * 83 / 100  // liquidation_threshold
551            ),
552            Ok(LiquidationPrices {
553                lower: 0.0,
554                upper: 220.16318077637644
555            })
556        );
557    }
558
559    #[test]
560    fn test_liquidation_price_inside_range_lower_and_upper() {
561        assert_eq!(
562            get_lp_position_liquidation_prices(
563                *TICK_LOWER_INDEX,
564                *TICK_UPPER_INDEX,
565                *LIQUIDITY,
566                0,                          // leftovers_a
567                0,                          // leftovers_b
568                11_500_000,                 // debt_a
569                8700_000_000,               // debt_b
570                HUNDRED_PERCENT * 83 / 100  // liquidation_threshold
571            ),
572            Ok(LiquidationPrices {
573                lower: 210.75514596082337,
574                upper: 219.48595430071575
575            })
576        );
577    }
578
579    #[test]
580    fn test_lp_increase_quote_collateral_a_and_b_provided() {
581        assert_eq!(
582            get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
583                collateral_a: 1000000,
584                collateral_b: 1000000,
585                borrow_a: 2000000,
586                borrow_b: 2000000,
587                tick_lower_index: price_to_tick_index(1.0, 1, 1),
588                sqrt_price: price_to_sqrt_price(2.0, 1, 1),
589                tick_upper_index: price_to_tick_index(4.0, 1, 1),
590                protocol_fee_rate: (HUNDRED_PERCENT / 100) as u16,
591                protocol_fee_rate_on_collateral: (HUNDRED_PERCENT / 100) as u16,
592                swap_fee_rate: 10000, // 1%
593                max_amount_slippage: HUNDRED_PERCENT / 10,
594            }),
595            Ok(IncreaseLpPositionQuoteResult {
596                collateral_a: 1000000,
597                collateral_b: 1000000,
598                max_collateral_a: 1000000,
599                max_collateral_b: 1000000,
600                borrow_a: 2000000,
601                borrow_b: 2000000,
602                total_a: 2223701,
603                total_b: 4447744,
604                min_total_a: 2001331,
605                min_total_b: 4002970,
606                swap_input: 742586,
607                swap_output: 1470320,
608                swap_a_to_b: true,
609                protocol_fee_a: 30000,
610                protocol_fee_b: 30000
611            })
612        );
613    }
614
615    #[test]
616    fn test_lp_increase_quote_collateral_a_provided() {
617        assert_eq!(
618            get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
619                collateral_a: 10000000,
620                collateral_b: 0,
621                borrow_a: 0,
622                borrow_b: 0,
623                tick_lower_index: price_to_tick_index(0.25, 6, 9),
624                sqrt_price: price_to_sqrt_price(0.5, 6, 9),
625                tick_upper_index: price_to_tick_index(1.0, 6, 9),
626                protocol_fee_rate: (HUNDRED_PERCENT / 100) as u16,
627                protocol_fee_rate_on_collateral: (HUNDRED_PERCENT / 100) as u16,
628                swap_fee_rate: 10000, // 1%
629                max_amount_slippage: HUNDRED_PERCENT / 10,
630            }),
631            Ok(IncreaseLpPositionQuoteResult {
632                collateral_a: 10000000,
633                collateral_b: 0,
634                max_collateral_a: 10000000,
635                max_collateral_b: 0,
636                borrow_a: 0,
637                borrow_b: 0,
638                total_a: 4925137,
639                total_b: 2462680451,
640                min_total_a: 4432624,
641                min_total_b: 2216412406,
642                swap_input: 4950113,
643                swap_output: 2450305500,
644                swap_a_to_b: true,
645                protocol_fee_a: 100000,
646                protocol_fee_b: 0
647            })
648        );
649    }
650
651    #[test]
652    fn test_lp_increase_quote_collateral_a_provided_b_computed() {
653        assert_eq!(
654            get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
655                collateral_a: 10000000,
656                collateral_b: COMPUTED_AMOUNT,
657                borrow_a: 2000000,
658                borrow_b: COMPUTED_AMOUNT,
659                tick_lower_index: price_to_tick_index(1.0, 1, 1),
660                sqrt_price: price_to_sqrt_price(3.0, 1, 1),
661                tick_upper_index: price_to_tick_index(4.0, 1, 1),
662                protocol_fee_rate: 0,
663                protocol_fee_rate_on_collateral: 0,
664                swap_fee_rate: 0,
665                max_amount_slippage: HUNDRED_PERCENT / 10,
666            }),
667            Ok(IncreaseLpPositionQuoteResult {
668                collateral_a: 10000000,
669                collateral_b: 94660495,
670                max_collateral_a: 10000000,
671                max_collateral_b: 104126544,
672                borrow_a: 2000000,
673                borrow_b: 18932100,
674                total_a: 12000000,
675                total_b: 113592595,
676                min_total_a: 10800000,
677                min_total_b: 102233336,
678                swap_input: 0,
679                swap_output: 0,
680                swap_a_to_b: false,
681                protocol_fee_a: 0,
682                protocol_fee_b: 0
683            })
684        );
685    }
686
687    #[test]
688    fn test_lp_increase_quote_collateral_a_computed_b_provided() {
689        assert_eq!(
690            get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
691                collateral_a: COMPUTED_AMOUNT,
692                collateral_b: 1000000,
693                borrow_a: COMPUTED_AMOUNT,
694                borrow_b: 2000000,
695                tick_lower_index: price_to_tick_index(1.0, 1, 1),
696                sqrt_price: price_to_sqrt_price(3.0, 1, 1),
697                tick_upper_index: price_to_tick_index(4.0, 1, 1),
698                protocol_fee_rate: 0,
699                protocol_fee_rate_on_collateral: 0,
700                swap_fee_rate: 0,
701                max_amount_slippage: HUNDRED_PERCENT / 10,
702            }),
703            Ok(IncreaseLpPositionQuoteResult {
704                collateral_a: 105640,
705                collateral_b: 1000000,
706                max_collateral_a: 116204,
707                max_collateral_b: 1000000,
708                borrow_a: 211282,
709                borrow_b: 2000000,
710                total_a: 316922,
711                total_b: 3000000,
712                min_total_a: 285230,
713                min_total_b: 2700000,
714                swap_input: 0,
715                swap_output: 0,
716                swap_a_to_b: false,
717                protocol_fee_a: 0,
718                protocol_fee_b: 0
719            })
720        );
721    }
722
723    #[test]
724    fn test_lp_increase_quote_one_sided_collateral_a_provided_b_computed() {
725        assert_eq!(
726            get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
727                collateral_a: 10000000,
728                collateral_b: COMPUTED_AMOUNT,
729                borrow_a: 2000000,
730                borrow_b: COMPUTED_AMOUNT,
731                tick_lower_index: price_to_tick_index(1.0, 1, 1),
732                sqrt_price: price_to_sqrt_price(0.5, 1, 1),
733                tick_upper_index: price_to_tick_index(4.0, 1, 1),
734                protocol_fee_rate: 0,
735                protocol_fee_rate_on_collateral: 0,
736                swap_fee_rate: 0,
737                max_amount_slippage: HUNDRED_PERCENT / 10,
738            }),
739            Ok(IncreaseLpPositionQuoteResult {
740                collateral_a: 10000000,
741                collateral_b: 0,
742                max_collateral_a: 10000000,
743                max_collateral_b: 0,
744                borrow_a: 2000000,
745                borrow_b: 0,
746                total_a: 12000000,
747                total_b: 0,
748                min_total_a: 10800000,
749                min_total_b: 0,
750                swap_input: 0,
751                swap_output: 0,
752                swap_a_to_b: false,
753                protocol_fee_a: 0,
754                protocol_fee_b: 0
755            })
756        );
757    }
758
759    #[test]
760    fn test_lp_increase_quote_one_sided_collateral_a_computed_b_provided() {
761        assert_eq!(
762            get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
763                collateral_a: COMPUTED_AMOUNT,
764                collateral_b: 1000000,
765                borrow_a: COMPUTED_AMOUNT,
766                borrow_b: 2000000,
767                tick_lower_index: price_to_tick_index(1.0, 1, 1),
768                sqrt_price: price_to_sqrt_price(5.0, 1, 1),
769                tick_upper_index: price_to_tick_index(4.0, 1, 1),
770                protocol_fee_rate: 0,
771                protocol_fee_rate_on_collateral: 0,
772                swap_fee_rate: 0,
773                max_amount_slippage: HUNDRED_PERCENT / 10,
774            }),
775            Ok(IncreaseLpPositionQuoteResult {
776                collateral_a: 0,
777                collateral_b: 1000000,
778                max_collateral_a: 0,
779                max_collateral_b: 1000000,
780                borrow_a: 0,
781                borrow_b: 2000000,
782                total_a: 0,
783                total_b: 3000000,
784                min_total_a: 0,
785                min_total_b: 2700000,
786                swap_input: 0,
787                swap_output: 0,
788                swap_a_to_b: false,
789                protocol_fee_a: 0,
790                protocol_fee_b: 0
791            })
792        );
793    }
794}