Skip to main content

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, 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
21///
22/// Calculates the liquidation prices inside the position's range.
23///
24/// t - liquidation threshold
25/// Dx - debt x
26/// Dy - debt y
27/// Lx - leftovers x
28/// Ly - leftovers y
29/// √P = x - current price
30/// √Pu = u - upper sqrt price
31/// √Pl = l - lower sqrt price
32/// L - liquidity
33///
34/// t = (Dx⋅P + Dy) / (Δx⋅P + Lx⋅P + Δy + Ly), where
35///   Δx = L⋅(1/x - 1/u)
36///   Δy = L⋅(x - l)
37///
38/// x1,2 = ...
39///
40/// # Parameters
41/// - `lower_sqrt_price`: The lower square root price boundary.
42/// - `upper_sqrt_price`: The upper square root price boundary.
43/// - `liquidity`: The liquidity provided by the user.
44/// - `leftovers_a`: The amount of leftovers A in the existing position.
45/// - `leftovers_b`: The amount of leftovers B in the existing position*
46/// - `debt_a`: The amount of tokens A borrowed.
47/// - `debt_b`: The amount of tokens B borrowed.
48/// - `liquidation_threshold`: The liquidation threshold of the liquidator.
49///
50/// # Returns
51/// - `LiquidationPrices`: An object containing lower/upper liquidation prices.
52fn 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 && a > 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
92/// Calculates a liquidation price for the outside range (above OR under).
93///
94/// liquidation_threshold = total_debt / (total_balance + leftovers)
95///
96/// t - liquidation threshold
97/// P - liquidation price
98/// Dx - debt x
99/// Dy - debt y
100/// Lx - leftovers x
101/// Ly - leftovers y
102/// X - amount of x tokens
103/// Y - amount of y tokens
104///
105/// Lower liquidation price:
106/// t = (Dx + Dy / P) / (X + Lx + Ly/P)  =>  P = (Dy - t⋅Ly) / (t⋅(X+Lx) - Dx)
107///
108/// Upper liquidation price:
109/// t = (Dx⋅P + Dy) / (Y + Lx⋅P + Ly)  =>  P = (t⋅(Y + Ly) - Dy) / (Dx - t⋅Lx)
110///
111/// # Parameters
112/// - `amount_a`: The amount of tokens A at the boundary price.
113/// - `amount_b`: The amount of tokens B at the boundary price.
114/// - `leftovers_a`: The amount of leftovers A.
115/// - `leftovers_b`: The amount of leftovers B.
116/// - `debt_a`: The amount of tokens A borrowed.
117/// - `debt_b`: The amount of tokens B borrowed.
118/// - `liquidation_threshold`: - The liquidation threshold of the liquidator.
119///
120/// # Returns
121/// The calculated liquidation price.
122fn 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        // Lower liquidation price
137        let numerator = debt_b as f64 - liquidation_threshold_f * leftovers_b as f64;
138        let denominator = liquidation_threshold_f * (amount_a as f64 + leftovers_a as f64) - debt_a as f64;
139        Ok(numerator / denominator)
140    } else if amount_a == 0 && amount_b > 0 {
141        // Upper liquidation price
142        let numerator = liquidation_threshold_f * (amount_b as f64 + 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
153/// Calculates the liquidation prices outside the position's range (above AND under).
154///
155/// # Parameters
156/// - `lower_sqrt_price`: The lower square root price boundary.
157/// - `upper_sqrt_price`: The upper square root price boundary.
158/// - `liquidity`: The liquidity provided by the user.
159/// - `leftovers_a`: The amount of leftovers A.
160/// - `leftovers_b`: The amount of leftovers B.
161/// - `debt_a`: The amount of tokens A borrowed.
162/// - `debt_b`: The amount of tokens B borrowed.
163/// - `liquidation_threshold`: The liquidation threshold of the liquidator.
164///
165/// # Returns
166/// - `LiquidationPrices`: An object containing lower/upper liquidation prices.
167fn 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).unwrap_or(u64::MAX);
178    let amount_b = get_amount_b_from_liquidity(liquidity, lower_sqrt_price, upper_sqrt_price, false).unwrap_or(u64::MAX);
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/// Computes the liquidation prices for an existing position.
197///
198/// # Parameters
199/// - `tick_lower_index`: The lower tick index of the position.
200/// - `tick_upper_index`: The upper tick index of the position.
201/// - `leftovers_a`: The amount of leftovers A in the position.
202/// - `leftovers_a`: The amount of leftovers B in the position.
203/// - `liquidity`: Liquidity of the position.
204/// - `debt_a`: The amount of tokens A borrowed.
205/// - `debt_b`: The amount of tokens B borrowed.
206/// - `liquidation_threshold`: The liquidation threshold of the market.
207///
208/// # Returns
209/// - `LiquidationPrices`: An object containing lower/upper liquidation prices.
210#[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    /// 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    /// The liquidation threshold of the market.
296    pub liquidation_threshold: 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 borrow_a: u64,
305    pub borrow_b: u64,
306    pub total_a: u64,
307    pub total_b: u64,
308    pub swap_input: u64,
309    pub swap_output: u64,
310    pub swap_a_to_b: bool,
311    pub protocol_fee_a: u64,
312    pub protocol_fee_b: u64,
313    pub liquidity: u128,
314    pub leverage: f64,
315    pub liquidation_lower_price: f64,
316    pub liquidation_upper_price: f64,
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 than the upper tick.");
329    }
330
331    if collateral_a == COMPUTED_AMOUNT && collateral_b == COMPUTED_AMOUNT {
332        return Err("Both collateral amounts can't be set to COMPUTED_AMOUNT");
333    }
334
335    let lower_sqrt_price = tick_index_to_sqrt_price(args.tick_lower_index);
336    let upper_sqrt_price = tick_index_to_sqrt_price(args.tick_upper_index);
337
338    if collateral_a == COMPUTED_AMOUNT {
339        if sqrt_price <= lower_sqrt_price {
340            return Err("sqrtPrice must be greater than lower_sqrt_price if collateral A is computed.");
341        } else if sqrt_price < upper_sqrt_price {
342            let liquidity = get_liquidity_from_amount_b(collateral_b + borrow_b, lower_sqrt_price, sqrt_price)?;
343            let amount_a = get_amount_a_from_liquidity(liquidity, sqrt_price, upper_sqrt_price, false)?;
344            collateral_a = ((amount_a as u128 * collateral_b as u128) / (collateral_b as u128 + borrow_b as u128)) as u64;
345            borrow_a = amount_a - collateral_a;
346        } else {
347            collateral_a = 0;
348            borrow_a = 0;
349        }
350    } else if collateral_b == COMPUTED_AMOUNT {
351        if sqrt_price <= lower_sqrt_price {
352            collateral_b = 0;
353            borrow_b = 0;
354        } else if sqrt_price < upper_sqrt_price {
355            let liquidity = get_liquidity_from_amount_a(collateral_a + borrow_a, sqrt_price, upper_sqrt_price)?;
356            let amount_b = get_amount_b_from_liquidity(liquidity, lower_sqrt_price, sqrt_price, false)?;
357            collateral_b = ((amount_b as u128 * collateral_a as u128) / (collateral_a as u128 + borrow_a as u128)) as u64;
358            borrow_b = amount_b - collateral_b;
359        } else {
360            return Err("sqrtPrice must be less than upper_sqrt_price if collateral B is computed.");
361        }
362    }
363
364    let protocol_fee_a = calculate_tuna_protocol_fee(collateral_a, borrow_a, args.protocol_fee_rate_on_collateral, args.protocol_fee_rate);
365    let provided_a = collateral_a + borrow_a - protocol_fee_a;
366
367    let protocol_fee_b = calculate_tuna_protocol_fee(collateral_b, borrow_b, args.protocol_fee_rate_on_collateral, args.protocol_fee_rate);
368    let provided_b = collateral_b + borrow_b - protocol_fee_b;
369
370    let mut swap_input = 0;
371    let mut swap_output = 0;
372    let mut swap_a_to_b = false;
373    let mut total_a = provided_a;
374    let mut total_b = provided_b;
375
376    if args.collateral_a != COMPUTED_AMOUNT && args.collateral_b != COMPUTED_AMOUNT {
377        let position_ratio = position_ratio_x64(sqrt_price.into(), args.tick_lower_index, args.tick_upper_index);
378        let ratio_a = position_ratio.ratio_a as f64 / Q64_RESOLUTION;
379        let ratio_b = position_ratio.ratio_b as f64 / Q64_RESOLUTION;
380
381        let price = (sqrt_price as f64 / Q64_RESOLUTION).powf(2.0);
382
383        // Estimated total position size.
384        let mut total = (provided_a as f64 * price + provided_b as f64) as u64;
385        total_a = (total as f64 * ratio_a / price) as u64;
386        total_b = (total as f64 * ratio_b) as u64;
387
388        let mut fee_a = 0;
389        let mut fee_b = 0;
390
391        if total_a < provided_a {
392            swap_input = provided_a - total_a;
393            fee_a = swap_input - try_apply_swap_fee(swap_input, args.swap_fee_rate)?;
394            swap_output = ((swap_input - fee_a) as f64 * price) as u64;
395            swap_a_to_b = true;
396        } else if total_b < provided_b {
397            swap_input = provided_b - total_b;
398            fee_b = swap_input - try_apply_swap_fee(swap_input, args.swap_fee_rate)?;
399            swap_output = ((swap_input - fee_b) as f64 / price) as u64;
400            swap_a_to_b = false;
401        }
402
403        // Recompute totals with applied swap fee.
404        total = ((provided_a - fee_a) as f64 * price) as u64 + provided_b - fee_b;
405        total_a = ((total as f64 * ratio_a) / price) as u64;
406        total_b = (total as f64 * ratio_b) as u64;
407    }
408
409    let liquidity = get_liquidity_from_amounts(sqrt_price, lower_sqrt_price, upper_sqrt_price, total_a, total_b)?;
410    let liquidation_prices = get_lp_position_liquidation_prices(
411        args.tick_lower_index,
412        args.tick_upper_index,
413        liquidity,
414        0,
415        0,
416        borrow_a,
417        borrow_b,
418        args.liquidation_threshold,
419    )?;
420
421    let leverage = compute_leverage(total_a, total_b, borrow_a, borrow_b, sqrt_price)?;
422
423    Ok(IncreaseLpPositionQuoteResult {
424        collateral_a,
425        collateral_b,
426        borrow_a,
427        borrow_b,
428        total_a,
429        total_b,
430        swap_input,
431        swap_output,
432        swap_a_to_b,
433        protocol_fee_a,
434        protocol_fee_b,
435        liquidity,
436        leverage,
437        liquidation_lower_price: liquidation_prices.lower,
438        liquidation_upper_price: liquidation_prices.upper,
439    })
440}
441
442#[derive(Debug, Copy, Clone, PartialEq)]
443#[cfg_attr(feature = "wasm", wasm_expose)]
444pub struct RepayLpPositionDebtQuoteArgs {
445    /** The position liquidity */
446    pub liquidity: u128,
447    /** The current debt of a position in token A. */
448    pub debt_a: u64,
449    /** The current debt of a position in token B. */
450    pub debt_b: u64,
451    /** The leftovers of a position in token A. */
452    pub leftovers_a: u64,
453    /** The leftovers of a position in token B. */
454    pub leftovers_b: u64,
455    /** Position lower tick index. */
456    pub tick_lower_index: i32,
457    /** Position upper tick index. */
458    pub tick_upper_index: i32,
459    /** The amount of token A to repay. */
460    pub repay_a: u64,
461    /** The amount of token B to repay. */
462    pub repay_b: u64,
463    /** Current sqrt price. */
464    pub sqrt_price: u128,
465    /** The liquidation threshold of the market. */
466    pub liquidation_threshold: u32,
467}
468
469#[derive(Debug, Copy, Clone, PartialEq)]
470#[cfg_attr(feature = "wasm", wasm_expose)]
471pub struct RepayLpPositionDebtQuoteResult {
472    pub debt_a: u64,
473    pub debt_b: u64,
474    pub leverage: f64,
475    pub liquidation_lower_price: f64,
476    pub liquidation_upper_price: f64,
477}
478
479#[cfg_attr(feature = "wasm", wasm_expose)]
480pub fn get_repay_lp_position_debt_quote(args: RepayLpPositionDebtQuoteArgs) -> Result<RepayLpPositionDebtQuoteResult, CoreError> {
481    let mut debt_a = args.debt_a;
482    let mut debt_b = args.debt_b;
483    let repay_a = args.repay_a;
484    let repay_b = args.repay_b;
485
486    if args.liquidity == 0 {
487        return Err("Position liquidity can't be zero.");
488    }
489
490    if debt_a < repay_a {
491        return Err("Position debt A is less than the repaid amount.");
492    }
493
494    if debt_b < repay_b {
495        return Err("Position debt b is less than the repaid amount.");
496    }
497
498    debt_a -= repay_a;
499    debt_b -= repay_b;
500
501    let liquidation_prices = get_lp_position_liquidation_prices(
502        args.tick_lower_index,
503        args.tick_upper_index,
504        args.liquidity,
505        args.leftovers_a,
506        args.leftovers_b,
507        debt_a,
508        debt_b,
509        args.liquidation_threshold,
510    )?;
511
512    let lower_sqrt_price = tick_index_to_sqrt_price(args.tick_lower_index);
513    let upper_sqrt_price = tick_index_to_sqrt_price(args.tick_upper_index);
514
515    let total = get_amounts_from_liquidity(args.liquidity, args.sqrt_price, lower_sqrt_price, upper_sqrt_price, false)?;
516    let leverage = compute_leverage(total.a + args.leftovers_a, total.b + args.leftovers_b, debt_a, debt_b, args.sqrt_price)?;
517
518    Ok(RepayLpPositionDebtQuoteResult {
519        debt_a,
520        debt_b,
521        leverage,
522        liquidation_lower_price: liquidation_prices.lower,
523        liquidation_upper_price: liquidation_prices.upper,
524    })
525}
526
527#[cfg_attr(feature = "wasm", wasm_expose)]
528pub fn compute_leverage(total_a: u64, total_b: u64, debt_a: u64, debt_b: u64, sqrt_price: u128) -> Result<f64, CoreError> {
529    let price = sqrt_price_x64_to_price_x64(sqrt_price)?;
530
531    let total = U64F64::from(total_a)
532        .checked_mul(price)
533        .ok_or(ARITHMETIC_OVERFLOW)?
534        .to_num::<u64>()
535        .checked_add(total_b)
536        .ok_or(ARITHMETIC_OVERFLOW)?;
537
538    let debt = U64F64::from(debt_a)
539        .checked_mul(price)
540        .ok_or(ARITHMETIC_OVERFLOW)?
541        .to_num::<u64>()
542        .checked_add(debt_b)
543        .ok_or(ARITHMETIC_OVERFLOW)?;
544
545    // We assume that the leverage of an empty position is always 1.0x.
546    if total == 0 {
547        return Ok(1.0);
548    }
549
550    if debt >= total {
551        return Err("The debt is greater than the total size");
552    }
553
554    let leverage = total as f64 / (total - debt) as f64;
555    Ok(leverage)
556}
557
558#[cfg(all(test, not(feature = "wasm")))]
559mod tests {
560    use crate::{
561        get_increase_lp_position_quote, get_lp_position_liquidation_prices, get_repay_lp_position_debt_quote, IncreaseLpPositionQuoteArgs,
562        IncreaseLpPositionQuoteResult, LiquidationPrices, RepayLpPositionDebtQuoteArgs, COMPUTED_AMOUNT, HUNDRED_PERCENT,
563    };
564    use fusionamm_core::{
565        get_liquidity_from_amount_b, price_to_sqrt_price, price_to_tick_index, tick_index_to_sqrt_price, MAX_TICK_INDEX, MIN_TICK_INDEX,
566    };
567    use once_cell::sync::Lazy;
568
569    pub static TICK_LOWER_INDEX: Lazy<i32> = Lazy::new(|| price_to_tick_index(180.736, 6, 6));
570    pub static TICK_UPPER_INDEX: Lazy<i32> = Lazy::new(|| price_to_tick_index(225.66, 6, 6));
571    pub static SQRT_PRICE: Lazy<u128> = Lazy::new(|| price_to_sqrt_price(213.41, 6, 6));
572    pub static LIQUIDITY: Lazy<u128> =
573        Lazy::new(|| get_liquidity_from_amount_b(10000_000_000, tick_index_to_sqrt_price(*TICK_LOWER_INDEX), *SQRT_PRICE).unwrap());
574
575    #[test]
576    fn test_liquidation_price_outside_range_lower() {
577        assert_eq!(
578            get_lp_position_liquidation_prices(
579                *TICK_LOWER_INDEX,
580                *TICK_UPPER_INDEX,
581                *LIQUIDITY,
582                0,                          // leftovers_a
583                0,                          // leftovers_b
584                0,                          // debt_a
585                9807_000_000,               // debt_b
586                HUNDRED_PERCENT * 83 / 100  // liquidation_threshold
587            ),
588            Ok(LiquidationPrices {
589                lower: 176.12815046153585,
590                upper: 0.0
591            })
592        );
593    }
594
595    #[test]
596    fn test_liquidation_price_outside_range_upper() {
597        assert_eq!(
598            get_lp_position_liquidation_prices(
599                *TICK_LOWER_INDEX,
600                *TICK_UPPER_INDEX,
601                *LIQUIDITY,
602                0,                          // leftovers_a
603                0,                          // leftovers_b
604                20_000_000,                 // debt_a
605                0,                          // debt_b
606                HUNDRED_PERCENT * 83 / 100  // liquidation_threshold
607            ),
608            Ok(LiquidationPrices {
609                lower: 0.0,
610                upper: 562.219410388
611            })
612        );
613    }
614
615    #[test]
616    fn test_liquidation_price_outside_range_lower_and_upper() {
617        assert_eq!(
618            get_lp_position_liquidation_prices(
619                *TICK_LOWER_INDEX,
620                *TICK_UPPER_INDEX,
621                *LIQUIDITY,
622                0,                          // leftovers_a
623                0,                          // leftovers_b
624                10_000_000,                 // debt_a
625                5000_000_000,               // debt_b
626                HUNDRED_PERCENT * 83 / 100  // liquidation_threshold
627            ),
628            Ok(LiquidationPrices {
629                lower: 109.45458168998225,
630                upper: 624.4388207760001
631            })
632        );
633    }
634
635    #[test]
636    fn test_liquidation_price_inside_range_lower() {
637        assert_eq!(
638            get_lp_position_liquidation_prices(
639                *TICK_LOWER_INDEX,
640                *TICK_UPPER_INDEX,
641                *LIQUIDITY,
642                0,                          // leftovers_a
643                0,                          // leftovers_b
644                0,                          // debt_a
645                11000_000_000,              // debt_b
646                HUNDRED_PERCENT * 83 / 100  // liquidation_threshold
647            ),
648            Ok(LiquidationPrices {
649                lower: 204.60489065334323,
650                upper: 0.0
651            })
652        );
653    }
654
655    #[test]
656    fn test_liquidation_price_inside_range_upper() {
657        assert_eq!(
658            get_lp_position_liquidation_prices(
659                *TICK_LOWER_INDEX,
660                *TICK_UPPER_INDEX,
661                *LIQUIDITY,
662                0,                          // leftovers_a
663                0,                          // leftovers_b
664                51_000_000,                 // debt_a
665                0,                          // debt_b
666                HUNDRED_PERCENT * 83 / 100  // liquidation_threshold
667            ),
668            Ok(LiquidationPrices {
669                lower: 0.0,
670                upper: 220.16318077637644
671            })
672        );
673    }
674
675    #[test]
676    fn test_liquidation_price_inside_range_lower_and_upper() {
677        assert_eq!(
678            get_lp_position_liquidation_prices(
679                *TICK_LOWER_INDEX,
680                *TICK_UPPER_INDEX,
681                *LIQUIDITY,
682                0,                          // leftovers_a
683                0,                          // leftovers_b
684                11_500_000,                 // debt_a
685                8700_000_000,               // debt_b
686                HUNDRED_PERCENT * 83 / 100  // liquidation_threshold
687            ),
688            Ok(LiquidationPrices {
689                lower: 210.75514596082337,
690                upper: 219.48595430071575
691            })
692        );
693    }
694
695    #[test]
696    fn test_lp_increase_quote_collateral_a_and_b_provided() {
697        assert_eq!(
698            get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
699                collateral_a: 1000000,
700                collateral_b: 1000000,
701                borrow_a: 2000000,
702                borrow_b: 2000000,
703                tick_lower_index: price_to_tick_index(1.0, 1, 1),
704                sqrt_price: price_to_sqrt_price(2.0, 1, 1),
705                tick_upper_index: price_to_tick_index(4.0, 1, 1),
706                protocol_fee_rate: (HUNDRED_PERCENT / 100) as u16,
707                protocol_fee_rate_on_collateral: (HUNDRED_PERCENT / 100) as u16,
708                swap_fee_rate: 10000, // 1%
709                liquidation_threshold: HUNDRED_PERCENT * 83 / 100,
710            }),
711            Ok(IncreaseLpPositionQuoteResult {
712                collateral_a: 1000000,
713                collateral_b: 1000000,
714                borrow_a: 2000000,
715                borrow_b: 2000000,
716                total_a: 2223701,
717                total_b: 4447744,
718                swap_input: 742586,
719                swap_output: 1470320,
720                swap_a_to_b: true,
721                protocol_fee_a: 30000,
722                protocol_fee_b: 30000,
723                liquidity: 10737803,
724                leverage: 3.0724343435529677,
725                liquidation_lower_price: 0.8143170288470588,
726                liquidation_upper_price: 3.4020457909456225,
727            })
728        );
729    }
730
731    #[test]
732    fn test_lp_increase_quote_collateral_a_provided() {
733        assert_eq!(
734            get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
735                collateral_a: 10000000,
736                collateral_b: 0,
737                borrow_a: 0,
738                borrow_b: 0,
739                tick_lower_index: price_to_tick_index(0.25, 6, 9),
740                sqrt_price: price_to_sqrt_price(0.5, 6, 9),
741                tick_upper_index: price_to_tick_index(1.0, 6, 9),
742                protocol_fee_rate: (HUNDRED_PERCENT / 100) as u16,
743                protocol_fee_rate_on_collateral: (HUNDRED_PERCENT / 100) as u16,
744                swap_fee_rate: 10000, // 1%
745                liquidation_threshold: HUNDRED_PERCENT * 83 / 100,
746            }),
747            Ok(IncreaseLpPositionQuoteResult {
748                collateral_a: 10000000,
749                collateral_b: 0,
750                borrow_a: 0,
751                borrow_b: 0,
752                total_a: 4925137,
753                total_b: 2462680451,
754                swap_input: 4950113,
755                swap_output: 2450305500,
756                swap_a_to_b: true,
757                protocol_fee_a: 100000,
758                protocol_fee_b: 0,
759                liquidity: 376005629,
760                leverage: 1.0,
761                liquidation_lower_price: 0.0,
762                liquidation_upper_price: 0.0,
763            })
764        );
765    }
766
767    #[test]
768    fn test_lp_increase_quote_collateral_a_provided_b_computed() {
769        assert_eq!(
770            get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
771                collateral_a: 10000000,
772                collateral_b: COMPUTED_AMOUNT,
773                borrow_a: 2000000,
774                borrow_b: COMPUTED_AMOUNT,
775                tick_lower_index: price_to_tick_index(1.0, 1, 1),
776                sqrt_price: price_to_sqrt_price(3.0, 1, 1),
777                tick_upper_index: price_to_tick_index(4.0, 1, 1),
778                protocol_fee_rate: 0,
779                protocol_fee_rate_on_collateral: 0,
780                swap_fee_rate: 0,
781                liquidation_threshold: HUNDRED_PERCENT * 83 / 100,
782            }),
783            Ok(IncreaseLpPositionQuoteResult {
784                collateral_a: 10000000,
785                collateral_b: 94660495,
786                borrow_a: 2000000,
787                borrow_b: 18932100,
788                total_a: 12000000,
789                total_b: 113592595,
790                swap_input: 0,
791                swap_output: 0,
792                swap_a_to_b: false,
793                protocol_fee_a: 0,
794                protocol_fee_b: 0,
795                liquidity: 155170370,
796                leverage: 1.2,
797                liquidation_lower_price: 0.30342990360419136,
798                liquidation_upper_price: 54.925553349999994
799            })
800        );
801    }
802
803    #[test]
804    fn test_lp_increase_quote_collateral_a_provided_b_computed_no_leverage() {
805        assert_eq!(
806            get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
807                collateral_a: 30000000000,
808                collateral_b: COMPUTED_AMOUNT,
809                borrow_a: 0,
810                borrow_b: 0,
811                tick_lower_index: price_to_tick_index(1.0, 1, 1),
812                sqrt_price: price_to_sqrt_price(3.0, 1, 1),
813                tick_upper_index: price_to_tick_index(4.0, 1, 1),
814                protocol_fee_rate: 0,
815                protocol_fee_rate_on_collateral: 0,
816                swap_fee_rate: 0,
817                liquidation_threshold: HUNDRED_PERCENT * 83 / 100,
818            }),
819            Ok(IncreaseLpPositionQuoteResult {
820                collateral_a: 30000000000,
821                collateral_b: 283981489799,
822                borrow_a: 0,
823                borrow_b: 0,
824                total_a: 30000000000,
825                total_b: 283981489799,
826                swap_input: 0,
827                swap_output: 0,
828                swap_a_to_b: false,
829                protocol_fee_a: 0,
830                protocol_fee_b: 0,
831                liquidity: 387925929269,
832                leverage: 1.0,
833                liquidation_lower_price: 0.0,
834                liquidation_upper_price: 0.0
835            })
836        );
837    }
838
839    #[test]
840    fn test_lp_increase_quote_collateral_a_computed_b_provided() {
841        assert_eq!(
842            get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
843                collateral_a: COMPUTED_AMOUNT,
844                collateral_b: 1000000,
845                borrow_a: COMPUTED_AMOUNT,
846                borrow_b: 2000000,
847                tick_lower_index: price_to_tick_index(1.0, 1, 1),
848                sqrt_price: price_to_sqrt_price(3.0, 1, 1),
849                tick_upper_index: price_to_tick_index(4.0, 1, 1),
850                protocol_fee_rate: 0,
851                protocol_fee_rate_on_collateral: 0,
852                swap_fee_rate: 0,
853                liquidation_threshold: HUNDRED_PERCENT * 83 / 100,
854            }),
855            Ok(IncreaseLpPositionQuoteResult {
856                collateral_a: 105640,
857                collateral_b: 1000000,
858                borrow_a: 211282,
859                borrow_b: 2000000,
860                total_a: 316922,
861                total_b: 3000000,
862                swap_input: 0,
863                swap_output: 0,
864                swap_a_to_b: false,
865                protocol_fee_a: 0,
866                protocol_fee_b: 0,
867                liquidity: 4098075,
868                leverage: 3.000003796737843,
869                liquidation_lower_price: 1.4306915613018771,
870                liquidation_upper_price: 6.63182675287057
871            })
872        );
873    }
874
875    #[test]
876    fn test_lp_increase_quote_collateral_a_computed_b_provided_no_leverage() {
877        assert_eq!(
878            get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
879                collateral_a: COMPUTED_AMOUNT,
880                collateral_b: 30000000000,
881                borrow_a: 0,
882                borrow_b: 0,
883                tick_lower_index: price_to_tick_index(1.0, 1, 1),
884                sqrt_price: price_to_sqrt_price(3.0, 1, 1),
885                tick_upper_index: price_to_tick_index(4.0, 1, 1),
886                protocol_fee_rate: 0,
887                protocol_fee_rate_on_collateral: 0,
888                swap_fee_rate: 0,
889                liquidation_threshold: HUNDRED_PERCENT * 83 / 100,
890            }),
891            Ok(IncreaseLpPositionQuoteResult {
892                collateral_a: 3169220644,
893                collateral_b: 30000000000,
894                borrow_a: 0,
895                borrow_b: 0,
896                total_a: 3169220644,
897                total_b: 30000000000,
898                swap_input: 0,
899                swap_output: 0,
900                swap_a_to_b: false,
901                protocol_fee_a: 0,
902                protocol_fee_b: 0,
903                liquidity: 40980762112,
904                leverage: 1.0,
905                liquidation_lower_price: 0.0,
906                liquidation_upper_price: 0.0
907            })
908        );
909    }
910
911    #[test]
912    fn test_lp_increase_quote_collateral_a_computed_b_provided_no_leverage_full_range() {
913        assert_eq!(
914            get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
915                collateral_a: COMPUTED_AMOUNT,
916                collateral_b: 30000000000,
917                borrow_a: 0,
918                borrow_b: 0,
919                tick_lower_index: MIN_TICK_INDEX,
920                sqrt_price: price_to_sqrt_price(3.0, 1, 1),
921                tick_upper_index: MAX_TICK_INDEX,
922                protocol_fee_rate: 0,
923                protocol_fee_rate_on_collateral: 0,
924                swap_fee_rate: 0,
925                liquidation_threshold: HUNDRED_PERCENT * 83 / 100,
926            }),
927            Ok(IncreaseLpPositionQuoteResult {
928                collateral_a: 9999999997,
929                collateral_b: 30000000000,
930                borrow_a: 0,
931                borrow_b: 0,
932                total_a: 9999999997,
933                total_b: 30000000000,
934                swap_input: 0,
935                swap_output: 0,
936                swap_a_to_b: false,
937                protocol_fee_a: 0,
938                protocol_fee_b: 0,
939                liquidity: 17320508077,
940                leverage: 1.0,
941                liquidation_lower_price: 0.0,
942                liquidation_upper_price: 0.0
943            })
944        );
945    }
946
947    #[test]
948    fn test_lp_increase_quote_one_sided_collateral_a_provided_b_computed() {
949        assert_eq!(
950            get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
951                collateral_a: 10000000,
952                collateral_b: COMPUTED_AMOUNT,
953                borrow_a: 2000000,
954                borrow_b: COMPUTED_AMOUNT,
955                tick_lower_index: price_to_tick_index(1.0, 1, 1),
956                sqrt_price: price_to_sqrt_price(0.5, 1, 1),
957                tick_upper_index: price_to_tick_index(4.0, 1, 1),
958                protocol_fee_rate: 0,
959                protocol_fee_rate_on_collateral: 0,
960                swap_fee_rate: 0,
961                liquidation_threshold: HUNDRED_PERCENT * 83 / 100,
962            }),
963            Ok(IncreaseLpPositionQuoteResult {
964                collateral_a: 10000000,
965                collateral_b: 0,
966                borrow_a: 2000000,
967                borrow_b: 0,
968                total_a: 12000000,
969                total_b: 0,
970                swap_input: 0,
971                swap_output: 0,
972                swap_a_to_b: false,
973                protocol_fee_a: 0,
974                protocol_fee_b: 0,
975                liquidity: 24000764,
976                leverage: 1.2,
977                liquidation_lower_price: 0.0,
978                liquidation_upper_price: 9.959682525
979            })
980        );
981    }
982
983    #[test]
984    fn test_lp_increase_quote_one_sided_collateral_a_computed_b_provided() {
985        assert_eq!(
986            get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
987                collateral_a: COMPUTED_AMOUNT,
988                collateral_b: 1000000,
989                borrow_a: COMPUTED_AMOUNT,
990                borrow_b: 2000000,
991                tick_lower_index: price_to_tick_index(1.0, 1, 1),
992                sqrt_price: price_to_sqrt_price(5.0, 1, 1),
993                tick_upper_index: price_to_tick_index(4.0, 1, 1),
994                protocol_fee_rate: 0,
995                protocol_fee_rate_on_collateral: 0,
996                swap_fee_rate: 0,
997                liquidation_threshold: HUNDRED_PERCENT * 83 / 100,
998            }),
999            Ok(IncreaseLpPositionQuoteResult {
1000                collateral_a: 0,
1001                collateral_b: 1000000,
1002                borrow_a: 0,
1003                borrow_b: 2000000,
1004                total_a: 0,
1005                total_b: 3000000,
1006                swap_input: 0,
1007                swap_output: 0,
1008                swap_a_to_b: false,
1009                protocol_fee_a: 0,
1010                protocol_fee_b: 0,
1011                liquidity: 3000191,
1012                leverage: 3.0,
1013                liquidation_lower_price: 1.8840617719481452,
1014                liquidation_upper_price: 0.0
1015            })
1016        );
1017    }
1018
1019    #[test]
1020    fn test_lp_increase_quote_verify_liquidation_prices() {
1021        let quote = get_increase_lp_position_quote(IncreaseLpPositionQuoteArgs {
1022            collateral_a: 0,
1023            collateral_b: 1000_000_000,
1024            borrow_a: 3_000_000,
1025            borrow_b: 100_000_000,
1026            tick_lower_index: price_to_tick_index(180.736, 6, 6),
1027            sqrt_price: price_to_sqrt_price(213.41, 6, 6),
1028            tick_upper_index: price_to_tick_index(225.66, 6, 6),
1029            protocol_fee_rate: 500,
1030            protocol_fee_rate_on_collateral: 500,
1031            swap_fee_rate: 40,
1032            liquidation_threshold: HUNDRED_PERCENT * 83 / 100,
1033        })
1034        .unwrap();
1035
1036        assert_eq!(quote.liquidation_lower_price, 23.805241869982023);
1037        assert_eq!(quote.liquidation_upper_price, 451.38033819333333);
1038    }
1039
1040    #[test]
1041    fn test_repay_debt_quote() {
1042        let quote = get_repay_lp_position_debt_quote(RepayLpPositionDebtQuoteArgs {
1043            repay_a: 1_000_000,
1044            repay_b: 30_000_000,
1045            liquidity: 1109671058,
1046            debt_a: 3_000_000,
1047            debt_b: 100_000_000,
1048            leftovers_a: 2,
1049            leftovers_b: 15,
1050            tick_lower_index: price_to_tick_index(180.736, 6, 6),
1051            sqrt_price: price_to_sqrt_price(213.41, 6, 6),
1052            tick_upper_index: price_to_tick_index(225.66, 6, 6),
1053            liquidation_threshold: HUNDRED_PERCENT * 83 / 100,
1054        })
1055        .unwrap();
1056
1057        assert_eq!(quote.debt_a, 2_000_000);
1058        assert_eq!(quote.debt_b, 70_000_000);
1059        assert_eq!(quote.liquidation_lower_price, 13.459576327110664);
1060        assert_eq!(quote.liquidation_upper_price, 692.0710879340029);
1061    }
1062}