defituna_core/quote/
tuna_spot_position.rs

1#![allow(clippy::collapsible_else_if)]
2#![allow(clippy::too_many_arguments)]
3
4use crate::{CoreError, HUNDRED_PERCENT, INVALID_ARGUMENTS, TOKEN_A, TOKEN_B};
5use fusionamm_core::{
6    sqrt_price_to_price, swap_quote_by_input_token, swap_quote_by_output_token, try_get_max_amount_with_slippage_tolerance,
7    try_get_min_amount_with_slippage_tolerance, try_mul_div, FusionPoolFacade, TickArrays,
8};
9use libm::{ceil, round};
10
11pub const DEFAULT_SLIPPAGE_TOLERANCE_BPS: u16 = 100;
12
13#[cfg(feature = "wasm")]
14use fusionamm_macros::wasm_expose;
15
16#[cfg_attr(feature = "wasm", wasm_expose)]
17pub struct IncreaseSpotPositionQuoteResult {
18    /** Required collateral amount */
19    pub collateral: u64,
20    /** Required amount to borrow */
21    pub borrow: u64,
22    /** Estimated position size in the position token. */
23    pub estimated_amount: u64,
24    /** Swap input amount. */
25    pub swap_input_amount: u64,
26    /** Minimum swap output amount according to the provided slippage. */
27    pub min_swap_output_amount: u64,
28    /** Protocol fee in token A */
29    pub protocol_fee_a: u64,
30    /** Protocol fee in token B */
31    pub protocol_fee_b: u64,
32    /** Price impact in percents */
33    pub price_impact: f64,
34}
35
36/// Spot position increase quote
37///
38/// # Parameters
39/// - `increase_amount`: Position total size in the collateral_token.
40/// - `collateral_token`: Collateral token.
41/// - `position_token`: Token of the position.
42/// - `leverage`: Leverage (1.0 or higher).
43/// - `slippage_tolerance_bps`: An optional slippage tolerance in basis points. Defaults to the global slippage tolerance if not provided.
44/// - `protocol_fee_rate`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
45/// - `protocol_fee_rate_on_collateral`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
46/// - `fusion_pool`: Fusion pool.
47/// - `tick_arrays`: Five tick arrays around the current pool price.
48///
49/// # Returns
50/// - `IncreaseSpotPositionQuoteResult`: quote result
51#[cfg_attr(feature = "wasm", wasm_expose)]
52pub fn get_increase_spot_position_quote(
53    increase_amount: u64,
54    collateral_token: u8,
55    position_token: u8,
56    leverage: f64,
57    slippage_tolerance_bps: Option<u16>,
58    protocol_fee_rate: u16,
59    protocol_fee_rate_on_collateral: u16,
60    fusion_pool: FusionPoolFacade,
61    tick_arrays: TickArrays,
62) -> Result<IncreaseSpotPositionQuoteResult, CoreError> {
63    if collateral_token > TOKEN_B || position_token > TOKEN_B {
64        return Err(INVALID_ARGUMENTS.into());
65    }
66
67    if leverage < 1.0 {
68        return Err(INVALID_ARGUMENTS.into());
69    }
70
71    let borrow: u64;
72    let mut collateral: u64;
73    let mut estimated_amount: u64 = 0;
74    let mut swap_input_amount: u64;
75    let mut next_sqrt_price = fusion_pool.sqrt_price;
76    let mut min_swap_output_amount: u64 = 0;
77
78    let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
79    let slippage_tolerance_bps = slippage_tolerance_bps.unwrap_or(DEFAULT_SLIPPAGE_TOLERANCE_BPS);
80
81    let borrowed_token = if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A };
82    let swap_input_token_is_a = borrowed_token == TOKEN_A;
83
84    if borrowed_token == collateral_token {
85        borrow = ceil((increase_amount as f64 * (leverage - 1.0)) / leverage) as u64;
86        collateral = increase_amount - apply_swap_fee(apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?, fusion_pool.fee_rate, false)?;
87        collateral = reverse_apply_swap_fee(collateral, fusion_pool.fee_rate, false)?;
88        collateral = reverse_apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
89
90        swap_input_amount = collateral + borrow;
91    } else {
92        let position_to_borrowed_token_price = if collateral_token == TOKEN_A { price } else { 1.0 / price };
93        let borrow_in_position_token = ceil((increase_amount as f64 * (leverage - 1.0)) / leverage);
94
95        borrow = ceil(borrow_in_position_token * position_to_borrowed_token_price) as u64;
96
97        let borrow_in_position_token_with_fees_applied =
98            apply_swap_fee(apply_tuna_protocol_fee(borrow_in_position_token as u64, protocol_fee_rate, false)?, fusion_pool.fee_rate, false)?;
99
100        collateral = increase_amount - borrow_in_position_token_with_fees_applied;
101        collateral = reverse_apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
102
103        swap_input_amount = borrow;
104    }
105
106    let (protocol_fee_a, protocol_fee_b) =
107        compute_tuna_protocol_fee(collateral_token, borrowed_token, collateral, borrow, protocol_fee_rate_on_collateral, protocol_fee_rate);
108
109    swap_input_amount -= if swap_input_token_is_a { protocol_fee_a } else { protocol_fee_b };
110
111    if position_token == collateral_token {
112        estimated_amount = collateral - if collateral_token == TOKEN_A { protocol_fee_a } else { protocol_fee_b };
113    }
114
115    if swap_input_amount > 0 {
116        let swap = swap_quote_by_input_token(swap_input_amount, swap_input_token_is_a, 0, fusion_pool, tick_arrays, None, None)?;
117        estimated_amount += swap.token_est_out;
118        next_sqrt_price = swap.next_sqrt_price;
119        min_swap_output_amount = try_get_min_amount_with_slippage_tolerance(swap.token_est_out, slippage_tolerance_bps)?;
120    }
121
122    let new_price = sqrt_price_to_price(next_sqrt_price.into(), 1, 1);
123    let price_impact = (new_price / price - 1.0).abs() * 100.0;
124
125    Ok(IncreaseSpotPositionQuoteResult {
126        collateral,
127        borrow,
128        estimated_amount,
129        swap_input_amount,
130        min_swap_output_amount,
131        protocol_fee_a,
132        protocol_fee_b,
133        price_impact,
134    })
135}
136
137#[cfg_attr(feature = "wasm", wasm_expose)]
138pub struct DecreaseSpotPositionQuoteResult {
139    /** Position decrease percentage */
140    pub decrease_percent: u32,
141    /** Collateral token of the new position */
142    pub collateral_token: u8,
143    /** Token of the new position */
144    pub position_token: u8,
145    /** Required additional collateral amount */
146    pub collateral: u64,
147    /** Required amount to borrow */
148    pub borrow: u64,
149    /** The maximum acceptable swap input amount for position decrease according to the provided slippage
150     * (if collateral_token == position_token) OR the minimum swap output amount (if collateral_token != position_token).
151     */
152    pub decrease_acceptable_swap_amount: u64,
153    /** The minimum swap output amount for position increase according to the provided slippage. */
154    pub increase_min_swap_output_amount: u64,
155    /** Estimated total amount of the new position */
156    pub estimated_amount: u64,
157    /** Protocol fee in token A */
158    pub protocol_fee_a: u64,
159    /** Protocol fee in token B */
160    pub protocol_fee_b: u64,
161    /** Price impact in percents */
162    pub price_impact: f64,
163}
164
165/// Spot position decrease quote
166///
167/// # Parameters
168/// - `decrease_amount`: Position total decrease size in the collateral_token.
169/// - `collateral_token`: Collateral token.
170/// - `leverage`: Leverage (1.0 or higher).
171/// - `reduce_only`: Only allow reducing the existing position.
172/// - `slippage_tolerance_bps`: An optional slippage tolerance in basis points. Defaults to the global slippage tolerance if not provided.
173/// - `position_token`: Token of the existing position.
174/// - `position_amount`: Existing position amount in the position_token.
175/// - `position_debt`: Existing position debt in the token opposite to the position_token.
176/// - `protocol_fee_rate`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
177/// - `protocol_fee_rate_on_collateral`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
178/// - `fusion_pool`: Fusion pool.
179/// - `tick_arrays`: Five tick arrays around the current pool price.
180///
181/// # Returns
182/// - `DecreaseSpotPositionQuoteResult`: quote result
183#[cfg_attr(feature = "wasm", wasm_expose)]
184pub fn get_decrease_spot_position_quote(
185    decrease_amount: u64,
186    collateral_token: u8,
187    leverage: f64,
188    reduce_only: bool,
189    slippage_tolerance_bps: Option<u16>,
190    position_token: u8,
191    position_amount: u64,
192    position_debt: u64,
193    protocol_fee_rate: u16,
194    protocol_fee_rate_on_collateral: u16,
195    fusion_pool: FusionPoolFacade,
196    tick_arrays: TickArrays,
197) -> Result<DecreaseSpotPositionQuoteResult, CoreError> {
198    if collateral_token > TOKEN_B || position_token > TOKEN_B {
199        return Err(INVALID_ARGUMENTS.into());
200    }
201
202    if leverage < 1.0 {
203        return Err(INVALID_ARGUMENTS.into());
204    }
205
206    let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
207    let position_to_borrowed_token_price = if position_token == TOKEN_A { price } else { 1.0 / price };
208    let borrowed_token = if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A };
209    let slippage_tolerance_bps = slippage_tolerance_bps.unwrap_or(DEFAULT_SLIPPAGE_TOLERANCE_BPS);
210
211    let mut collateral = 0;
212    let mut borrow = 0;
213    let estimated_amount: u64;
214    let mut next_sqrt_price = fusion_pool.sqrt_price;
215    let mut new_position_token = position_token;
216    let decrease_percent: u32;
217    let mut increase_swap_output_amount: u64 = 0;
218    let mut decrease_acceptable_swap_amount: u64 = 0;
219
220    let mut decrease_amount_in_position_token = if collateral_token == position_token {
221        decrease_amount
222    } else {
223        round(decrease_amount as f64 / position_to_borrowed_token_price) as u64
224    };
225
226    if reduce_only && decrease_amount_in_position_token > position_amount {
227        decrease_amount_in_position_token = position_amount;
228    }
229
230    decrease_percent = ((decrease_amount_in_position_token * HUNDRED_PERCENT as u64 / position_amount) as u32).min(HUNDRED_PERCENT);
231
232    if decrease_amount_in_position_token <= position_amount {
233        estimated_amount = position_amount * (HUNDRED_PERCENT - decrease_percent) as u64 / HUNDRED_PERCENT as u64;
234
235        if collateral_token == position_token {
236            if position_debt > 0 {
237                let amount_out = position_debt * decrease_percent as u64 / HUNDRED_PERCENT as u64;
238                let swap = swap_quote_by_output_token(amount_out, borrowed_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
239                next_sqrt_price = swap.next_sqrt_price;
240                decrease_acceptable_swap_amount = try_get_max_amount_with_slippage_tolerance(swap.token_est_in, slippage_tolerance_bps)?;
241            }
242        } else {
243            let amount_in = position_amount - estimated_amount;
244            let swap = swap_quote_by_input_token(amount_in, position_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
245            next_sqrt_price = swap.next_sqrt_price;
246            decrease_acceptable_swap_amount = try_get_min_amount_with_slippage_tolerance(swap.token_est_out, slippage_tolerance_bps)?;
247        }
248    } else {
249        new_position_token = if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A };
250        let increase_amount = decrease_amount_in_position_token - position_amount;
251
252        if position_token == collateral_token {
253            // Example:
254            // collateral_token: A
255            // position_token: A
256            // position_debt: B
257            // new_position_token: B
258            // newBorrowedToken: A
259
260            // A
261            borrow = ((increase_amount as f64 * (leverage - 1.0)) / leverage) as u64;
262            let borrow_with_fees_applied = apply_swap_fee(apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?, fusion_pool.fee_rate, false)?;
263
264            collateral = increase_amount - borrow_with_fees_applied;
265
266            // Decrease swap: A->B
267            let mut amount_in = 0;
268            if position_debt > 0 {
269                let swap =
270                    swap_quote_by_output_token(position_debt, borrowed_token == TOKEN_A, 0, fusion_pool, tick_arrays.clone().into(), None, None)?;
271                amount_in = swap.token_est_in;
272                decrease_acceptable_swap_amount = try_get_max_amount_with_slippage_tolerance(amount_in, slippage_tolerance_bps)?;
273            }
274
275            // Decrease+Increase swap: A->B
276            amount_in += collateral + apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?;
277            if amount_in > 0 {
278                let swap = swap_quote_by_input_token(amount_in, position_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
279                next_sqrt_price = swap.next_sqrt_price;
280                increase_swap_output_amount = swap.token_est_out.saturating_sub(position_debt);
281            }
282
283            // B
284            estimated_amount = increase_swap_output_amount;
285        } else {
286            // Example:
287            // collateral_token: B
288            // position_token: A
289            // new_position_token: B
290            // newBorrowedToken: A
291
292            // Decrease swap A->B
293            let swap = swap_quote_by_input_token(position_amount, position_token == TOKEN_A, 0, fusion_pool, tick_arrays.clone().into(), None, None)?;
294            let decrease_amount_out = swap.token_est_out;
295            decrease_acceptable_swap_amount = try_get_min_amount_with_slippage_tolerance(swap.token_est_out, slippage_tolerance_bps)?;
296
297            borrow = ((increase_amount as f64 * (leverage - 1.0)) / leverage) as u64;
298            let borrow_with_fees_applied = apply_swap_fee(apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?, fusion_pool.fee_rate, false)?;
299
300            collateral = increase_amount - borrow_with_fees_applied;
301            collateral = ceil(collateral as f64 * position_to_borrowed_token_price) as u64;
302
303            // Decrease+Increase swap A->B
304            let amount_in = position_amount + apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?;
305            if amount_in > 0 {
306                let swap = swap_quote_by_input_token(amount_in, position_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
307                next_sqrt_price = swap.next_sqrt_price;
308                increase_swap_output_amount = swap.token_est_out.saturating_sub(decrease_amount_out);
309            }
310
311            // B
312            estimated_amount = increase_swap_output_amount + collateral;
313        }
314
315        collateral = reverse_apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
316    }
317
318    let (protocol_fee_a, protocol_fee_b) =
319        compute_tuna_protocol_fee(collateral_token, borrowed_token, collateral, borrow, protocol_fee_rate_on_collateral, protocol_fee_rate);
320
321    let new_price = sqrt_price_to_price(next_sqrt_price.into(), 1, 1);
322    let price_impact = (new_price / price - 1.0).abs() * 100.0;
323
324    let increase_min_swap_output_amount = try_get_min_amount_with_slippage_tolerance(increase_swap_output_amount, slippage_tolerance_bps)?;
325
326    Ok(DecreaseSpotPositionQuoteResult {
327        decrease_percent,
328        collateral_token,
329        position_token: new_position_token,
330        collateral,
331        borrow,
332        decrease_acceptable_swap_amount,
333        increase_min_swap_output_amount,
334        estimated_amount,
335        protocol_fee_a,
336        protocol_fee_b,
337        price_impact,
338    })
339}
340
341/// Returns the liquidation price
342///
343/// # Parameters
344/// - `position_token`: Token of the position
345/// - `amount`: Position total size (decimal)
346/// - `debt`: Position total debt (decimal)
347/// - `liquidation_threshold`: Liquidation threshold of the market (decimal)
348///
349/// # Returns
350/// - `f64`: Decimal liquidation price
351#[cfg_attr(feature = "wasm", wasm_expose)]
352pub fn get_liquidation_price(position_token: u8, amount: f64, debt: f64, liquidation_threshold: f64) -> Result<f64, CoreError> {
353    if debt < 0.0 || amount < 0.0 {
354        return Err(INVALID_ARGUMENTS);
355    }
356
357    if liquidation_threshold <= 0.0 || liquidation_threshold >= 1.0 {
358        return Err(INVALID_ARGUMENTS);
359    }
360
361    if debt == 0.0 || amount == 0.0 {
362        return Ok(0.0);
363    }
364
365    if position_token == TOKEN_A {
366        Ok(debt / (amount * liquidation_threshold))
367    } else {
368        Ok((amount * liquidation_threshold) / debt)
369    }
370}
371
372/// Calculates the maximum tradable amount in the collateral token.
373///
374/// # Parameters
375/// - `collateral_token`: Collateral token.
376/// - `available_balance`: Available wallet balance in the collateral_token.
377/// - `leverage`: Leverage (1.0 or higher).
378/// - `new_position_token`: Token of the new position.
379/// - `position_token`: Token of the existing position. Should be set to new_position_token if position_amount is zero.
380/// - `position_amount`: Existing position amount in the position_token.
381/// - `position_debt`: Existing position debt in the token opposite to the position_token.
382/// - `reduce_only`: Only allow reducing the existing position.///
383/// - `protocol_fee_rate`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
384/// - `protocol_fee_rate_on_collateral`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
385/// - `fusion_pool`: Fusion pool.
386/// - `tick_arrays`: Five tick arrays around the current pool price.
387///
388/// # Returns
389/// - `u64`: the maximum tradable amount
390#[cfg_attr(feature = "wasm", wasm_expose)]
391pub fn get_tradable_amount(
392    collateral_token: u8,
393    available_balance: u64,
394    leverage: f64,
395    new_position_token: u8,
396    reduce_only: bool,
397    position_token: u8,
398    position_amount: u64,
399    position_debt: u64,
400    protocol_fee_rate: u16,
401    protocol_fee_rate_on_collateral: u16,
402    fusion_pool: FusionPoolFacade,
403    tick_arrays: TickArrays,
404) -> Result<u64, CoreError> {
405    if collateral_token > TOKEN_B || position_token > TOKEN_B {
406        return Err(INVALID_ARGUMENTS.into());
407    }
408
409    if leverage < 1.0 {
410        return Err(INVALID_ARGUMENTS.into());
411    }
412
413    if position_amount == 0 && new_position_token != position_token {
414        return Err(INVALID_ARGUMENTS.into());
415    }
416
417    // T = C⋅Fc⋅Fs + B⋅Fb⋅Fs, where: Fc/Fb/Fs - collateral/borrow/swap fee multiplier
418    // B = T⋅(L - 1) / L
419    // => T = C⋅Fc⋅Fs / (1 - Fb⋅Fs⋅(L - 1) / L)
420    let add_leverage = |collateral: u64| -> Result<u64, CoreError> {
421        let mut collateral = apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
422        if collateral_token != new_position_token {
423            collateral = apply_swap_fee(collateral, fusion_pool.fee_rate, false)?;
424        }
425
426        let fee_multiplier = (1.0 - protocol_fee_rate as f64 / HUNDRED_PERCENT as f64) * (1.0 - fusion_pool.fee_rate as f64 / 1_000_000.0);
427        let total = (collateral as f64 / (1.0 - (fee_multiplier * (leverage - 1.0)) / leverage)) as u64;
428        Ok(total)
429    };
430
431    let available_to_trade = if new_position_token == position_token {
432        add_leverage(available_balance)?
433    } else {
434        let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
435        let position_to_opposite_token_price = if position_token == TOKEN_A { price } else { 1.0 / price };
436
437        if reduce_only {
438            if collateral_token == position_token {
439                position_amount
440            } else {
441                round(position_amount as f64 * position_to_opposite_token_price) as u64
442            }
443        } else {
444            let position_amount_in_collateral_token = if collateral_token == position_token {
445                position_amount
446            } else {
447                round(position_amount as f64 * position_to_opposite_token_price) as u64
448            };
449
450            let position_collateral = if collateral_token == position_token {
451                let swap_in = if position_debt > 0 {
452                    swap_quote_by_output_token(position_debt, position_token == TOKEN_B, 0, fusion_pool, tick_arrays, None, None)?.token_est_in
453                } else {
454                    0
455                };
456                position_amount - swap_in
457            } else {
458                if position_amount > 0 {
459                    let swap_quote = swap_quote_by_input_token(position_amount, position_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
460                    swap_quote.token_est_out - position_debt
461                } else {
462                    0
463                }
464            };
465
466            // Add the refunded collateral to the available balance
467            position_amount_in_collateral_token + add_leverage(available_balance + position_collateral)?
468        }
469    };
470
471    Ok(available_to_trade)
472}
473
474pub fn apply_tuna_protocol_fee(amount: u64, protocol_fee_rate: u16, round_up: bool) -> Result<u64, CoreError> {
475    try_mul_div(amount, HUNDRED_PERCENT as u128 - protocol_fee_rate as u128, HUNDRED_PERCENT as u128, round_up)
476}
477
478pub fn reverse_apply_tuna_protocol_fee(amount: u64, protocol_fee_rate: u16, round_up: bool) -> Result<u64, CoreError> {
479    try_mul_div(amount, HUNDRED_PERCENT as u128, HUNDRED_PERCENT as u128 - protocol_fee_rate as u128, round_up)
480}
481
482pub fn apply_swap_fee(amount: u64, fee_rate: u16, round_up: bool) -> Result<u64, CoreError> {
483    try_mul_div(amount, 1_000_000 - fee_rate as u128, 1_000_000, round_up)
484}
485
486pub fn reverse_apply_swap_fee(amount: u64, fee_rate: u16, round_up: bool) -> Result<u64, CoreError> {
487    try_mul_div(amount, 1_000_000, 1_000_000 - fee_rate as u128, round_up)
488}
489
490pub fn compute_tuna_protocol_fee(
491    collateral_token: u8,
492    borrowed_token: u8,
493    collateral: u64,
494    borrow: u64,
495    protocol_fee_rate_on_collateral: u16,
496    protocol_fee_rate: u16,
497) -> (u64, u64) {
498    let collateral_a = if collateral_token == TOKEN_A { collateral } else { 0 };
499    let collateral_b = if collateral_token == TOKEN_B { collateral } else { 0 };
500    let borrow_a = if borrowed_token == TOKEN_A { borrow } else { 0 };
501    let borrow_b = if borrowed_token == TOKEN_B { borrow } else { 0 };
502
503    let protocol_fee_a = ((collateral_a as u128 * protocol_fee_rate_on_collateral as u128 + borrow_a as u128 * protocol_fee_rate as u128)
504        / HUNDRED_PERCENT as u128) as u64;
505    let protocol_fee_b = ((collateral_b as u128 * protocol_fee_rate_on_collateral as u128 + borrow_b as u128 * protocol_fee_rate as u128)
506        / HUNDRED_PERCENT as u128) as u64;
507
508    (protocol_fee_a, protocol_fee_b)
509}
510
511#[cfg(all(test, not(feature = "wasm")))]
512mod tests {
513    use super::*;
514    use crate::assert_approx_eq;
515    use fusionamm_core::{
516        get_tick_array_start_tick_index, price_to_sqrt_price, sqrt_price_to_tick_index, TickArrayFacade, TickFacade, TICK_ARRAY_SIZE,
517    };
518
519    fn test_fusion_pool(sqrt_price: u128) -> FusionPoolFacade {
520        let tick_current_index = sqrt_price_to_tick_index(sqrt_price);
521        FusionPoolFacade {
522            tick_current_index,
523            fee_rate: 3000,
524            liquidity: 10000000000000,
525            sqrt_price,
526            tick_spacing: 2,
527            ..FusionPoolFacade::default()
528        }
529    }
530
531    fn test_tick(liquidity_net: i128) -> TickFacade {
532        TickFacade {
533            initialized: true,
534            liquidity_net,
535            ..TickFacade::default()
536        }
537    }
538
539    fn test_tick_array(start_tick_index: i32) -> TickArrayFacade {
540        TickArrayFacade {
541            start_tick_index,
542            ticks: [test_tick(0); TICK_ARRAY_SIZE],
543        }
544    }
545
546    fn test_tick_arrays(fusion_pool: FusionPoolFacade) -> TickArrays {
547        let tick_spacing = fusion_pool.tick_spacing;
548        let tick_current_index = sqrt_price_to_tick_index(fusion_pool.sqrt_price);
549        let tick_array_start_index = get_tick_array_start_tick_index(tick_current_index, tick_spacing);
550
551        [
552            test_tick_array(tick_array_start_index),
553            test_tick_array(tick_array_start_index + TICK_ARRAY_SIZE as i32 * tick_spacing as i32),
554            test_tick_array(tick_array_start_index + TICK_ARRAY_SIZE as i32 * tick_spacing as i32 * 2),
555            test_tick_array(tick_array_start_index - TICK_ARRAY_SIZE as i32 * tick_spacing as i32),
556            test_tick_array(tick_array_start_index - TICK_ARRAY_SIZE as i32 * tick_spacing as i32 * 2),
557        ]
558        .into()
559    }
560
561    #[test]
562    fn test_get_liquidation_price() {
563        assert_eq!(get_liquidation_price(TOKEN_A, 5.0, 0.0, 0.85), Ok(0.0));
564        assert_eq!(get_liquidation_price(TOKEN_A, 0.0, 5.0, 0.85), Ok(0.0));
565        assert_eq!(get_liquidation_price(TOKEN_A, 5.0, 800.0, 0.85), Ok(188.23529411764707));
566        assert_eq!(get_liquidation_price(TOKEN_B, 1000.0, 4.0, 0.85), Ok(212.5));
567    }
568
569    #[test]
570    fn increase_long_position_providing_token_a() {
571        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
572        let fusion_pool = test_fusion_pool(sqrt_price);
573
574        let quote = get_increase_spot_position_quote(
575            5_000_000_000,
576            TOKEN_A,
577            TOKEN_A,
578            5.0,
579            Some(0),
580            (HUNDRED_PERCENT / 100) as u16,
581            (HUNDRED_PERCENT / 200) as u16,
582            fusion_pool,
583            test_tick_arrays(fusion_pool),
584        )
585        .unwrap();
586
587        assert_eq!(quote.collateral, 1057165829);
588        assert_eq!(quote.borrow, 800000000);
589        assert_eq!(quote.min_swap_output_amount, 3_947_423_011);
590        assert_eq!(quote.estimated_amount, 4_999_303_011);
591        assert_eq!(quote.protocol_fee_a, 5285829);
592        assert_eq!(quote.protocol_fee_b, 8000000);
593        assert_eq!(quote.price_impact, 0.03531617625702754);
594        assert_approx_eq!(quote.estimated_amount as f64 / (quote.estimated_amount as f64 - (quote.borrow as f64 * 1000.0) / 200.0), 5.0, 0.1);
595    }
596
597    #[test]
598    fn increase_long_position_providing_token_b() {
599        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
600        let fusion_pool = test_fusion_pool(sqrt_price);
601
602        let quote = get_increase_spot_position_quote(
603            5000_000_000,
604            TOKEN_B,
605            TOKEN_A,
606            5.0,
607            Some(0),
608            (HUNDRED_PERCENT / 100) as u16,
609            (HUNDRED_PERCENT / 200) as u16,
610            fusion_pool,
611            test_tick_arrays(fusion_pool),
612        )
613        .unwrap();
614
615        assert_eq!(quote.collateral, 1060346869);
616        assert_eq!(quote.borrow, 4000000000);
617        assert_eq!(quote.estimated_amount, 24_972_080_293);
618        assert_eq!(quote.protocol_fee_a, 0);
619        assert_eq!(quote.protocol_fee_b, 45301734);
620        assert_eq!(quote.price_impact, 0.22373179716579372);
621        assert_approx_eq!(quote.estimated_amount as f64 / (quote.estimated_amount as f64 - (quote.borrow as f64 * 1000.0) / 200.0), 5.0, 0.1);
622    }
623
624    #[test]
625    fn increase_short_position_providing_a() {
626        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
627        let fusion_pool = test_fusion_pool(sqrt_price);
628
629        let quote = get_increase_spot_position_quote(
630            5_000_000_000,
631            TOKEN_A,
632            TOKEN_B,
633            5.0,
634            Some(0),
635            (HUNDRED_PERCENT / 100) as u16,
636            (HUNDRED_PERCENT / 200) as u16,
637            fusion_pool,
638            test_tick_arrays(fusion_pool),
639        )
640        .unwrap();
641
642        assert_eq!(quote.collateral, 1060346869);
643        assert_eq!(quote.borrow, 4000000000);
644        assert_eq!(quote.estimated_amount, 999_776_441);
645        assert_eq!(quote.protocol_fee_a, 45301734);
646        assert_eq!(quote.protocol_fee_b, 0);
647        assert_eq!(quote.price_impact, 0.04470636400017991);
648        assert_approx_eq!(quote.estimated_amount as f64 / (quote.estimated_amount as f64 - (quote.borrow as f64 / 1000.0) * 200.0), 5.0, 0.1);
649    }
650
651    #[test]
652    fn increase_short_position_providing_b() {
653        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
654        let fusion_pool = test_fusion_pool(sqrt_price);
655
656        let quote = get_increase_spot_position_quote(
657            5000_000_000,
658            TOKEN_B,
659            TOKEN_B,
660            5.0,
661            Some(0),
662            (HUNDRED_PERCENT / 100) as u16,
663            (HUNDRED_PERCENT / 200) as u16,
664            fusion_pool,
665            test_tick_arrays(fusion_pool),
666        )
667        .unwrap();
668
669        assert_eq!(quote.collateral, 1057165829);
670        assert_eq!(quote.borrow, 20000000000);
671        assert_eq!(quote.estimated_amount, 4996_517_564);
672        assert_eq!(quote.protocol_fee_a, 200000000);
673        assert_eq!(quote.protocol_fee_b, 5285829);
674        assert_eq!(quote.price_impact, 0.17633175413067637);
675        assert_approx_eq!(quote.estimated_amount as f64 / (quote.estimated_amount as f64 - (quote.borrow as f64 / 1000.0) * 200.0), 5.0, 0.1);
676    }
677
678    #[test]
679    fn increase_quote_with_slippage() {
680        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
681        let fusion_pool = test_fusion_pool(sqrt_price);
682
683        // with slippage 10%
684        let quote =
685            get_increase_spot_position_quote(200_000, TOKEN_B, TOKEN_A, 5.0, Some(1000), 0, 0, fusion_pool, test_tick_arrays(fusion_pool)).unwrap();
686        assert_eq!(quote.min_swap_output_amount, 899_994);
687
688        // without slippage
689        let quote =
690            get_increase_spot_position_quote(200_000, TOKEN_B, TOKEN_A, 5.0, Some(0), 0, 0, fusion_pool, test_tick_arrays(fusion_pool)).unwrap();
691        assert_eq!(quote.min_swap_output_amount, 999_994);
692    }
693
694    #[test]
695    fn decrease_non_leveraged_long_position_providing_a_reduce_only() {
696        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
697        let fusion_pool = test_fusion_pool(sqrt_price);
698
699        let quote = get_decrease_spot_position_quote(
700            1_000_000_000,
701            TOKEN_A,
702            1.0,
703            true,
704            Some(0),
705            TOKEN_A,
706            5_000_000_000, // A
707            0,             // B
708            (HUNDRED_PERCENT / 100) as u16,
709            (HUNDRED_PERCENT / 200) as u16,
710            fusion_pool,
711            test_tick_arrays(fusion_pool),
712        )
713        .unwrap();
714
715        assert_eq!(quote.decrease_percent, 200000);
716        assert_eq!(quote.collateral_token, TOKEN_A);
717        assert_eq!(quote.position_token, TOKEN_A);
718        assert_eq!(quote.estimated_amount, 4_000_000_000);
719        assert_eq!(quote.protocol_fee_a, 0);
720        assert_eq!(quote.protocol_fee_b, 0);
721        assert_eq!(quote.price_impact, 0.0);
722        assert_eq!(quote.estimated_amount as f64 / (quote.estimated_amount as f64 - (quote.borrow as f64 / 1000.0) * 200.0), 1.0);
723    }
724
725    #[test]
726    fn decrease_non_leveraged_long_position_providing_b_reduce_only() {
727        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
728        let fusion_pool = test_fusion_pool(sqrt_price);
729
730        let quote = get_decrease_spot_position_quote(
731            200_000_000,
732            TOKEN_B,
733            1.0,
734            true,
735            Some(0),
736            TOKEN_A,
737            5_000_000_000, // A
738            0,             // B
739            (HUNDRED_PERCENT / 100) as u16,
740            (HUNDRED_PERCENT / 200) as u16,
741            fusion_pool,
742            test_tick_arrays(fusion_pool),
743        )
744        .unwrap();
745
746        assert_eq!(quote.decrease_percent, 200000);
747        assert_eq!(quote.collateral_token, TOKEN_B);
748        assert_eq!(quote.position_token, TOKEN_A);
749        assert_eq!(quote.estimated_amount, 4_000_000_000);
750        assert_eq!(quote.protocol_fee_a, 0);
751        assert_eq!(quote.protocol_fee_b, 0);
752        assert_eq!(quote.price_impact, 0.008916842709072448);
753        assert_eq!(quote.estimated_amount as f64 / (quote.estimated_amount as f64 - (quote.borrow as f64 / 1000.0) * 200.0), 1.0);
754    }
755
756    #[test]
757    fn decrease_long_position_providing_a_reduce_only() {
758        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
759        let fusion_pool = test_fusion_pool(sqrt_price);
760
761        let quote = get_decrease_spot_position_quote(
762            1_000_000_000,
763            TOKEN_A,
764            5.0,
765            true,
766            Some(0),
767            TOKEN_A,
768            5_000_000_000, // A
769            800_000_000,   // B
770            (HUNDRED_PERCENT / 100) as u16,
771            (HUNDRED_PERCENT / 200) as u16,
772            fusion_pool,
773            test_tick_arrays(fusion_pool),
774        )
775        .unwrap();
776
777        assert_eq!(quote.decrease_percent, 200000);
778        assert_eq!(quote.collateral_token, TOKEN_A);
779        assert_eq!(quote.position_token, TOKEN_A);
780        assert_eq!(quote.collateral, 0);
781        assert_eq!(quote.borrow, 0);
782        assert_eq!(quote.estimated_amount, 4_000_000_000);
783        assert_eq!(quote.protocol_fee_a, 0);
784        assert_eq!(quote.protocol_fee_b, 0);
785        assert_eq!(quote.price_impact, 0.007155289528004705);
786
787        let quote = get_decrease_spot_position_quote(
788            6_000_000_000,
789            TOKEN_A,
790            5.0,
791            true,
792            Some(0),
793            TOKEN_A,
794            5_000_000_000, // A
795            800_000_000,   // B
796            (HUNDRED_PERCENT / 100) as u16,
797            (HUNDRED_PERCENT / 200) as u16,
798            fusion_pool,
799            test_tick_arrays(fusion_pool),
800        )
801        .unwrap();
802
803        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
804        assert_eq!(quote.collateral, 0);
805        assert_eq!(quote.borrow, 0);
806        assert_eq!(quote.estimated_amount, 0);
807    }
808
809    #[test]
810    fn decrease_long_position_providing_b_reduce_only() {
811        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
812        let fusion_pool = test_fusion_pool(sqrt_price);
813
814        let quote = get_decrease_spot_position_quote(
815            200_000_000,
816            TOKEN_B,
817            5.0,
818            true,
819            Some(0),
820            TOKEN_A,
821            5_000_000_000, // A
822            800_000_000,   // B
823            (HUNDRED_PERCENT / 100) as u16,
824            (HUNDRED_PERCENT / 200) as u16,
825            fusion_pool,
826            test_tick_arrays(fusion_pool),
827        )
828        .unwrap();
829
830        assert_eq!(quote.decrease_percent, 200000);
831        assert_eq!(quote.collateral_token, TOKEN_B);
832        assert_eq!(quote.position_token, TOKEN_A);
833        assert_eq!(quote.collateral, 0);
834        assert_eq!(quote.borrow, 0);
835        assert_eq!(quote.estimated_amount, 4000000000);
836        assert_eq!(quote.protocol_fee_a, 0);
837        assert_eq!(quote.protocol_fee_b, 0);
838        assert_eq!(quote.price_impact, 0.008916842709072448);
839
840        let quote = get_decrease_spot_position_quote(
841            1200_000_000,
842            TOKEN_B,
843            5.0,
844            true,
845            Some(0),
846            TOKEN_A,
847            5_000_000_000, // A
848            800_000_000,   // B
849            (HUNDRED_PERCENT / 100) as u16,
850            (HUNDRED_PERCENT / 200) as u16,
851            fusion_pool,
852            test_tick_arrays(fusion_pool),
853        )
854        .unwrap();
855
856        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
857        assert_eq!(quote.collateral, 0);
858        assert_eq!(quote.borrow, 0);
859        assert_eq!(quote.estimated_amount, 0);
860    }
861
862    #[test]
863    fn decrease_long_position_providing_a_without_leverage() {
864        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
865        let fusion_pool = test_fusion_pool(sqrt_price);
866
867        let quote = get_decrease_spot_position_quote(
868            6_000_000_000,
869            TOKEN_A,
870            1.0,
871            false,
872            Some(100),
873            TOKEN_A,
874            5_000_000_000, // A
875            800_000_000,   // B
876            (HUNDRED_PERCENT / 100) as u16,
877            (HUNDRED_PERCENT / 200) as u16,
878            fusion_pool,
879            test_tick_arrays(fusion_pool),
880        )
881        .unwrap();
882
883        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
884        assert_eq!(quote.collateral_token, TOKEN_A);
885        assert_eq!(quote.position_token, TOKEN_B);
886        assert_eq!(quote.collateral, 1005025125);
887        assert_eq!(quote.borrow, 0);
888        assert_eq!(quote.estimated_amount, 199_319_781);
889        assert_eq!(quote.decrease_acceptable_swap_amount, 4_052_881_482);
890        assert_eq!(quote.increase_min_swap_output_amount, 197_326_583);
891        assert_eq!(quote.protocol_fee_a, 5025125);
892        assert_eq!(quote.protocol_fee_b, 0);
893        assert_eq!(quote.price_impact, 0.044685946117650754);
894    }
895
896    #[test]
897    fn decrease_long_position_providing_a() {
898        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
899        let fusion_pool = test_fusion_pool(sqrt_price);
900
901        let quote = get_decrease_spot_position_quote(
902            6_000_000_000,
903            TOKEN_A,
904            5.0,
905            false,
906            Some(100),
907            TOKEN_A,
908            5_000_000_000, // A
909            800_000_000,   // B
910            (HUNDRED_PERCENT / 100) as u16,
911            (HUNDRED_PERCENT / 200) as u16,
912            fusion_pool,
913            test_tick_arrays(fusion_pool),
914        )
915        .unwrap();
916
917        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
918        assert_eq!(quote.collateral_token, TOKEN_A);
919        assert_eq!(quote.position_token, TOKEN_B);
920        assert_eq!(quote.collateral, 211433165);
921        assert_eq!(quote.borrow, 800000000);
922        assert_eq!(quote.estimated_amount, 199_793_344);
923        assert_eq!(quote.decrease_acceptable_swap_amount, 4_052_881_482);
924        assert_eq!(quote.increase_min_swap_output_amount, 197_795_410);
925        assert_eq!(quote.protocol_fee_a, 1057165);
926        assert_eq!(quote.protocol_fee_b, 8000000);
927        assert_eq!(quote.price_impact, 0.04470711974918773);
928    }
929
930    #[test]
931    fn decrease_long_position_providing_b() {
932        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
933        let fusion_pool = test_fusion_pool(sqrt_price);
934
935        let quote = get_decrease_spot_position_quote(
936            1200_000_000,
937            TOKEN_B,
938            5.0,
939            false,
940            Some(0),
941            TOKEN_A,
942            5_000_000_000, // A
943            800_000_000,   // B
944            (HUNDRED_PERCENT / 100) as u16,
945            (HUNDRED_PERCENT / 200) as u16,
946            fusion_pool,
947            test_tick_arrays(fusion_pool),
948        )
949        .unwrap();
950
951        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
952        assert_eq!(quote.collateral_token, TOKEN_B);
953        assert_eq!(quote.position_token, TOKEN_B);
954        assert_eq!(quote.collateral, 42286633);
955        assert_eq!(quote.borrow, 800_000_000);
956        assert_eq!(quote.estimated_amount, 199_924_036);
957        assert_eq!(quote.decrease_acceptable_swap_amount, 996_777_780);
958        assert_eq!(quote.increase_min_swap_output_amount, 157_848_836);
959        assert_eq!(quote.protocol_fee_a, 0);
960        assert_eq!(quote.protocol_fee_b, 8211433);
961        assert_eq!(quote.price_impact, 0.05162980632488212);
962    }
963
964    #[test]
965    fn decrease_non_leveraged_short_position_providing_a() {
966        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
967        let fusion_pool = test_fusion_pool(sqrt_price);
968
969        let quote = get_decrease_spot_position_quote(
970            6_000_000_000,
971            TOKEN_A,
972            1.0,
973            false,
974            Some(0),
975            TOKEN_B,
976            1000_000_000, // B
977            0,            // A
978            (HUNDRED_PERCENT / 100) as u16,
979            (HUNDRED_PERCENT / 200) as u16,
980            fusion_pool,
981            test_tick_arrays(fusion_pool),
982        )
983        .unwrap();
984
985        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
986        assert_eq!(quote.collateral_token, TOKEN_A);
987        assert_eq!(quote.position_token, TOKEN_A);
988        assert_eq!(quote.collateral, 1005025125);
989        assert_eq!(quote.borrow, 0);
990        assert_eq!(quote.estimated_amount, 1_000_000_000);
991        assert_eq!(quote.protocol_fee_a, 5025125);
992        assert_eq!(quote.protocol_fee_b, 0);
993        assert_eq!(quote.price_impact, 0.044592165429757635);
994    }
995
996    #[test]
997    fn decrease_short_position_providing_a() {
998        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
999        let fusion_pool = test_fusion_pool(sqrt_price);
1000
1001        let quote = get_decrease_spot_position_quote(
1002            6_000_000_000,
1003            TOKEN_A,
1004            5.0,
1005            false,
1006            Some(0),
1007            TOKEN_B,
1008            1000_000_000,  // B
1009            4_000_000_000, // A
1010            (HUNDRED_PERCENT / 100) as u16,
1011            (HUNDRED_PERCENT / 200) as u16,
1012            fusion_pool,
1013            test_tick_arrays(fusion_pool),
1014        )
1015        .unwrap();
1016
1017        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1018        assert_eq!(quote.collateral_token, TOKEN_A);
1019        assert_eq!(quote.position_token, TOKEN_A);
1020        assert_eq!(quote.collateral, 211433165);
1021        assert_eq!(quote.borrow, 160000000);
1022        assert_eq!(quote.estimated_amount, 999_620_182);
1023        assert_eq!(quote.decrease_acceptable_swap_amount, 4_983_888_901);
1024        assert_eq!(quote.increase_min_swap_output_amount, 789_244_182);
1025        assert_eq!(quote.protocol_fee_a, 2657165);
1026        assert_eq!(quote.protocol_fee_b, 0);
1027        assert_eq!(quote.price_impact, 0.051656476403882934);
1028    }
1029
1030    #[test]
1031    fn decrease_short_position_providing_b() {
1032        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1033        let fusion_pool = test_fusion_pool(sqrt_price);
1034
1035        let quote = get_decrease_spot_position_quote(
1036            1_200_000_000,
1037            TOKEN_B,
1038            5.0,
1039            false,
1040            Some(0),
1041            TOKEN_B,
1042            1000_000_000,  // B
1043            4_000_000_000, // A
1044            (HUNDRED_PERCENT / 100) as u16,
1045            (HUNDRED_PERCENT / 200) as u16,
1046            fusion_pool,
1047            test_tick_arrays(fusion_pool),
1048        )
1049        .unwrap();
1050
1051        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1052        assert_eq!(quote.collateral_token, TOKEN_B);
1053        assert_eq!(quote.position_token, TOKEN_A);
1054        assert_eq!(quote.collateral, 42_286_633);
1055        assert_eq!(quote.borrow, 160000000);
1056        assert_eq!(quote.estimated_amount, 998_966_727);
1057        assert_eq!(quote.protocol_fee_a, 1600000);
1058        assert_eq!(quote.protocol_fee_b, 211433);
1059        assert_eq!(quote.price_impact, 0.04472711596017476);
1060    }
1061
1062    #[test]
1063    fn tradable_amount_for_1x_long_position_providing_b() {
1064        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1065        let fusion_pool = test_fusion_pool(sqrt_price);
1066        let tick_arrays = test_tick_arrays(fusion_pool);
1067
1068        let collateral_token = TOKEN_B;
1069        let position_token = TOKEN_A;
1070        let leverage = 1.0;
1071        let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1072        let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1073        let available_balance = 200_000_000;
1074
1075        let tradable_amount = get_tradable_amount(
1076            collateral_token,
1077            available_balance,
1078            leverage,
1079            position_token,
1080            false,
1081            position_token,
1082            0,
1083            0,
1084            protocol_fee_rate,
1085            protocol_fee_rate_on_collateral,
1086            fusion_pool,
1087            tick_arrays.clone(),
1088        )
1089        .unwrap();
1090        assert_eq!(tradable_amount, 198403000);
1091
1092        let quote = get_increase_spot_position_quote(
1093            tradable_amount,
1094            collateral_token,
1095            position_token,
1096            leverage,
1097            Some(0),
1098            protocol_fee_rate,
1099            protocol_fee_rate_on_collateral,
1100            fusion_pool,
1101            tick_arrays,
1102        )
1103        .unwrap();
1104        assert_eq!(quote.collateral, available_balance);
1105    }
1106
1107    #[test]
1108    fn tradable_amount_for_5x_long_position_providing_b() {
1109        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1110        let fusion_pool = test_fusion_pool(sqrt_price);
1111        let tick_arrays = test_tick_arrays(fusion_pool);
1112
1113        let collateral_token = TOKEN_B;
1114        let position_token = TOKEN_A;
1115        let leverage = 5.0;
1116        let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1117        let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1118        let available_balance = 10_000_000;
1119
1120        let tradable_amount = get_tradable_amount(
1121            collateral_token,
1122            available_balance,
1123            leverage,
1124            position_token,
1125            false,
1126            position_token,
1127            0,
1128            0,
1129            protocol_fee_rate,
1130            protocol_fee_rate_on_collateral,
1131            fusion_pool,
1132            tick_arrays.clone(),
1133        )
1134        .unwrap();
1135        assert_eq!(tradable_amount, 47154380);
1136
1137        let quote = get_increase_spot_position_quote(
1138            tradable_amount,
1139            collateral_token,
1140            position_token,
1141            leverage,
1142            Some(0),
1143            protocol_fee_rate,
1144            protocol_fee_rate_on_collateral,
1145            fusion_pool,
1146            tick_arrays,
1147        )
1148        .unwrap();
1149        // TODO: fix precision error
1150        assert_eq!(quote.collateral, available_balance + 1);
1151    }
1152
1153    #[test]
1154    fn tradable_amount_for_5x_long_position_providing_a() {
1155        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1156        let fusion_pool = test_fusion_pool(sqrt_price);
1157        let tick_arrays = test_tick_arrays(fusion_pool);
1158
1159        let collateral_token = TOKEN_A;
1160        let position_token = TOKEN_A;
1161        let leverage = 5.0;
1162        let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1163        let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1164        let available_balance = 1_000_000_000;
1165
1166        let tradable_amount = get_tradable_amount(
1167            collateral_token,
1168            available_balance,
1169            leverage,
1170            position_token,
1171            false,
1172            position_token,
1173            0,
1174            0,
1175            protocol_fee_rate,
1176            protocol_fee_rate_on_collateral,
1177            fusion_pool,
1178            tick_arrays.clone(),
1179        )
1180        .unwrap();
1181        assert_eq!(tradable_amount, 4729626953);
1182
1183        let quote = get_increase_spot_position_quote(
1184            tradable_amount,
1185            collateral_token,
1186            position_token,
1187            leverage,
1188            Some(0),
1189            protocol_fee_rate,
1190            protocol_fee_rate_on_collateral,
1191            fusion_pool,
1192            tick_arrays,
1193        )
1194        .unwrap();
1195        assert_eq!(quote.collateral, available_balance);
1196        //assert_eq!(quote.estimated_amount, tradable_amount);
1197    }
1198
1199    #[test]
1200    fn tradable_amount_for_5x_short_position_providing_b() {
1201        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1202        let fusion_pool = test_fusion_pool(sqrt_price);
1203        let tick_arrays = test_tick_arrays(fusion_pool);
1204
1205        let collateral_token = TOKEN_B;
1206        let position_token = TOKEN_B;
1207        let leverage = 5.0;
1208        let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1209        let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1210        let available_balance = 200_000_000;
1211
1212        let tradable_amount = get_tradable_amount(
1213            collateral_token,
1214            available_balance,
1215            leverage,
1216            position_token,
1217            false,
1218            position_token,
1219            0,
1220            0,
1221            protocol_fee_rate,
1222            protocol_fee_rate_on_collateral,
1223            fusion_pool,
1224            tick_arrays.clone(),
1225        )
1226        .unwrap();
1227        assert_eq!(tradable_amount, 945925390);
1228
1229        let quote = get_increase_spot_position_quote(
1230            tradable_amount,
1231            collateral_token,
1232            position_token,
1233            leverage,
1234            Some(0),
1235            protocol_fee_rate,
1236            protocol_fee_rate_on_collateral,
1237            fusion_pool,
1238            tick_arrays,
1239        )
1240        .unwrap();
1241        // TODO: fix precision error
1242        assert_eq!(quote.collateral, available_balance + 1);
1243        //assert_eq!(quote.estimated_amount, tradable_amount);
1244    }
1245
1246    #[test]
1247    fn tradable_amount_for_reducing_existing_long_position() {
1248        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1249        let fusion_pool = test_fusion_pool(sqrt_price);
1250        let tick_arrays = test_tick_arrays(fusion_pool);
1251
1252        for i in 0..2 {
1253            let collateral_token = if i == 0 { TOKEN_A } else { TOKEN_B };
1254            let position_token = if i == 0 { TOKEN_A } else { TOKEN_B };
1255            let leverage = 5.0;
1256            let reduce_only = true;
1257            let position_amount = 5_000_000_000;
1258            let position_debt = 800_000_000;
1259            let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1260            let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1261            let available_balance = 50_000_000_000;
1262
1263            let tradable_amount = get_tradable_amount(
1264                collateral_token,
1265                available_balance,
1266                leverage,
1267                if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A },
1268                reduce_only,
1269                position_token,
1270                position_amount,
1271                position_debt,
1272                protocol_fee_rate,
1273                protocol_fee_rate_on_collateral,
1274                fusion_pool,
1275                tick_arrays.clone(),
1276            )
1277            .unwrap();
1278            assert_eq!(tradable_amount, 5_000_000_000);
1279
1280            let quote = get_decrease_spot_position_quote(
1281                tradable_amount,
1282                collateral_token,
1283                5.0,
1284                reduce_only,
1285                Some(0),
1286                position_token,
1287                position_amount,
1288                position_debt,
1289                protocol_fee_rate,
1290                protocol_fee_rate_on_collateral,
1291                fusion_pool,
1292                tick_arrays.clone(),
1293            )
1294            .unwrap();
1295
1296            assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1297        }
1298    }
1299
1300    #[test]
1301    fn tradable_amount_when_inverting_long_position_providing_a() {
1302        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1303        let fusion_pool = test_fusion_pool(sqrt_price);
1304        let tick_arrays = test_tick_arrays(fusion_pool);
1305
1306        let collateral_token = TOKEN_A;
1307        let position_token = TOKEN_A;
1308        let leverage = 5.0;
1309        let reduce_only = false;
1310        let position_amount = 5_000_000_000;
1311        let position_debt = 800_000_000;
1312        let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1313        let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1314        let available_balance = 2_000_000_000;
1315
1316        let tradable_amount = get_tradable_amount(
1317            collateral_token,
1318            available_balance,
1319            leverage,
1320            if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A },
1321            reduce_only,
1322            position_token,
1323            position_amount,
1324            position_debt,
1325            protocol_fee_rate,
1326            protocol_fee_rate_on_collateral,
1327            fusion_pool,
1328            tick_arrays.clone(),
1329        )
1330        .unwrap();
1331        assert_eq!(tradable_amount, 19_086_173_788);
1332
1333        let quote = get_decrease_spot_position_quote(
1334            tradable_amount,
1335            collateral_token,
1336            5.0,
1337            reduce_only,
1338            Some(0),
1339            position_token,
1340            position_amount,
1341            position_debt,
1342            protocol_fee_rate,
1343            protocol_fee_rate_on_collateral,
1344            fusion_pool,
1345            tick_arrays.clone(),
1346        )
1347        .unwrap();
1348
1349        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1350        assert_eq!(quote.collateral, 2_978_284_319);
1351    }
1352
1353    #[test]
1354    fn tradable_amount_when_inverting_long_position_providing_b() {
1355        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1356        let fusion_pool = test_fusion_pool(sqrt_price);
1357        let tick_arrays = test_tick_arrays(fusion_pool);
1358
1359        let collateral_token = TOKEN_B;
1360        let position_token = TOKEN_A;
1361        let leverage = 5.0;
1362        let reduce_only = false;
1363        let position_amount = 5_000_000_000;
1364        let position_debt = 800_000_000;
1365        let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1366        let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1367        let available_balance = 500_000_000;
1368
1369        let tradable_amount = get_tradable_amount(
1370            collateral_token,
1371            available_balance,
1372            leverage,
1373            if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A },
1374            reduce_only,
1375            position_token,
1376            position_amount,
1377            position_debt,
1378            protocol_fee_rate,
1379            protocol_fee_rate_on_collateral,
1380            fusion_pool,
1381            tick_arrays.clone(),
1382        )
1383        .unwrap();
1384        assert_eq!(tradable_amount, 4_295_498_968);
1385
1386        let quote = get_decrease_spot_position_quote(
1387            tradable_amount,
1388            collateral_token,
1389            5.0,
1390            reduce_only,
1391            Some(0),
1392            position_token,
1393            position_amount,
1394            position_debt,
1395            protocol_fee_rate,
1396            protocol_fee_rate_on_collateral,
1397            fusion_pool,
1398            tick_arrays.clone(),
1399        )
1400        .unwrap();
1401
1402        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1403        assert_eq!(quote.collateral, 696_777_779);
1404    }
1405}