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, TokenPair,
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 =
107        calculate_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: protocol_fee.a,
132        protocol_fee_b: 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 =
319        calculate_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: protocol_fee.a,
336        protocol_fee_b: 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
490#[cfg_attr(feature = "wasm", wasm_expose)]
491pub fn calculate_tuna_protocol_fee(
492    collateral_token: u8,
493    borrowed_token: u8,
494    collateral: u64,
495    borrow: u64,
496    protocol_fee_rate_on_collateral: u16,
497    protocol_fee_rate: u16,
498) -> TokenPair {
499    let collateral_a = if collateral_token == TOKEN_A { collateral } else { 0 };
500    let collateral_b = if collateral_token == TOKEN_B { collateral } else { 0 };
501    let borrow_a = if borrowed_token == TOKEN_A { borrow } else { 0 };
502    let borrow_b = if borrowed_token == TOKEN_B { borrow } else { 0 };
503
504    let protocol_fee_a = ((collateral_a as u128 * protocol_fee_rate_on_collateral as u128 + borrow_a as u128 * protocol_fee_rate as u128)
505        / HUNDRED_PERCENT as u128) as u64;
506    let protocol_fee_b = ((collateral_b as u128 * protocol_fee_rate_on_collateral as u128 + borrow_b as u128 * protocol_fee_rate as u128)
507        / HUNDRED_PERCENT as u128) as u64;
508
509    TokenPair {
510        a: protocol_fee_a,
511        b: protocol_fee_b,
512    }
513}
514
515#[cfg(all(test, not(feature = "wasm")))]
516mod tests {
517    use super::*;
518    use crate::assert_approx_eq;
519    use fusionamm_core::{
520        get_tick_array_start_tick_index, price_to_sqrt_price, sqrt_price_to_tick_index, TickArrayFacade, TickFacade, TICK_ARRAY_SIZE,
521    };
522
523    fn test_fusion_pool(sqrt_price: u128) -> FusionPoolFacade {
524        let tick_current_index = sqrt_price_to_tick_index(sqrt_price);
525        FusionPoolFacade {
526            tick_current_index,
527            fee_rate: 3000,
528            liquidity: 10000000000000,
529            sqrt_price,
530            tick_spacing: 2,
531            ..FusionPoolFacade::default()
532        }
533    }
534
535    fn test_tick(liquidity_net: i128) -> TickFacade {
536        TickFacade {
537            initialized: true,
538            liquidity_net,
539            ..TickFacade::default()
540        }
541    }
542
543    fn test_tick_array(start_tick_index: i32) -> TickArrayFacade {
544        TickArrayFacade {
545            start_tick_index,
546            ticks: [test_tick(0); TICK_ARRAY_SIZE],
547        }
548    }
549
550    fn test_tick_arrays(fusion_pool: FusionPoolFacade) -> TickArrays {
551        let tick_spacing = fusion_pool.tick_spacing;
552        let tick_current_index = sqrt_price_to_tick_index(fusion_pool.sqrt_price);
553        let tick_array_start_index = get_tick_array_start_tick_index(tick_current_index, tick_spacing);
554
555        [
556            test_tick_array(tick_array_start_index),
557            test_tick_array(tick_array_start_index + TICK_ARRAY_SIZE as i32 * tick_spacing as i32),
558            test_tick_array(tick_array_start_index + TICK_ARRAY_SIZE as i32 * tick_spacing as i32 * 2),
559            test_tick_array(tick_array_start_index - TICK_ARRAY_SIZE as i32 * tick_spacing as i32),
560            test_tick_array(tick_array_start_index - TICK_ARRAY_SIZE as i32 * tick_spacing as i32 * 2),
561        ]
562        .into()
563    }
564
565    #[test]
566    fn test_get_liquidation_price() {
567        assert_eq!(get_liquidation_price(TOKEN_A, 5.0, 0.0, 0.85), Ok(0.0));
568        assert_eq!(get_liquidation_price(TOKEN_A, 0.0, 5.0, 0.85), Ok(0.0));
569        assert_eq!(get_liquidation_price(TOKEN_A, 5.0, 800.0, 0.85), Ok(188.23529411764707));
570        assert_eq!(get_liquidation_price(TOKEN_B, 1000.0, 4.0, 0.85), Ok(212.5));
571    }
572
573    #[test]
574    fn increase_long_position_providing_token_a() {
575        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
576        let fusion_pool = test_fusion_pool(sqrt_price);
577
578        let quote = get_increase_spot_position_quote(
579            5_000_000_000,
580            TOKEN_A,
581            TOKEN_A,
582            5.0,
583            Some(0),
584            (HUNDRED_PERCENT / 100) as u16,
585            (HUNDRED_PERCENT / 200) as u16,
586            fusion_pool,
587            test_tick_arrays(fusion_pool),
588        )
589        .unwrap();
590
591        assert_eq!(quote.collateral, 1057165829);
592        assert_eq!(quote.borrow, 800000000);
593        assert_eq!(quote.min_swap_output_amount, 3_947_423_011);
594        assert_eq!(quote.estimated_amount, 4_999_303_011);
595        assert_eq!(quote.protocol_fee_a, 5285829);
596        assert_eq!(quote.protocol_fee_b, 8000000);
597        assert_eq!(quote.price_impact, 0.03531617625702754);
598        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);
599    }
600
601    #[test]
602    fn increase_long_position_providing_token_b() {
603        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
604        let fusion_pool = test_fusion_pool(sqrt_price);
605
606        let quote = get_increase_spot_position_quote(
607            5000_000_000,
608            TOKEN_B,
609            TOKEN_A,
610            5.0,
611            Some(0),
612            (HUNDRED_PERCENT / 100) as u16,
613            (HUNDRED_PERCENT / 200) as u16,
614            fusion_pool,
615            test_tick_arrays(fusion_pool),
616        )
617        .unwrap();
618
619        assert_eq!(quote.collateral, 1060346869);
620        assert_eq!(quote.borrow, 4000000000);
621        assert_eq!(quote.estimated_amount, 24_972_080_293);
622        assert_eq!(quote.protocol_fee_a, 0);
623        assert_eq!(quote.protocol_fee_b, 45301734);
624        assert_eq!(quote.price_impact, 0.22373179716579372);
625        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);
626    }
627
628    #[test]
629    fn increase_short_position_providing_a() {
630        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
631        let fusion_pool = test_fusion_pool(sqrt_price);
632
633        let quote = get_increase_spot_position_quote(
634            5_000_000_000,
635            TOKEN_A,
636            TOKEN_B,
637            5.0,
638            Some(0),
639            (HUNDRED_PERCENT / 100) as u16,
640            (HUNDRED_PERCENT / 200) as u16,
641            fusion_pool,
642            test_tick_arrays(fusion_pool),
643        )
644        .unwrap();
645
646        assert_eq!(quote.collateral, 1060346869);
647        assert_eq!(quote.borrow, 4000000000);
648        assert_eq!(quote.estimated_amount, 999_776_441);
649        assert_eq!(quote.protocol_fee_a, 45301734);
650        assert_eq!(quote.protocol_fee_b, 0);
651        assert_eq!(quote.price_impact, 0.04470636400017991);
652        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);
653    }
654
655    #[test]
656    fn increase_short_position_providing_b() {
657        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
658        let fusion_pool = test_fusion_pool(sqrt_price);
659
660        let quote = get_increase_spot_position_quote(
661            5000_000_000,
662            TOKEN_B,
663            TOKEN_B,
664            5.0,
665            Some(0),
666            (HUNDRED_PERCENT / 100) as u16,
667            (HUNDRED_PERCENT / 200) as u16,
668            fusion_pool,
669            test_tick_arrays(fusion_pool),
670        )
671        .unwrap();
672
673        assert_eq!(quote.collateral, 1057165829);
674        assert_eq!(quote.borrow, 20000000000);
675        assert_eq!(quote.estimated_amount, 4996_517_564);
676        assert_eq!(quote.protocol_fee_a, 200000000);
677        assert_eq!(quote.protocol_fee_b, 5285829);
678        assert_eq!(quote.price_impact, 0.17633175413067637);
679        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);
680    }
681
682    #[test]
683    fn increase_quote_with_slippage() {
684        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
685        let fusion_pool = test_fusion_pool(sqrt_price);
686
687        // with slippage 10%
688        let quote =
689            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();
690        assert_eq!(quote.min_swap_output_amount, 899_994);
691
692        // without slippage
693        let quote =
694            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();
695        assert_eq!(quote.min_swap_output_amount, 999_994);
696    }
697
698    #[test]
699    fn decrease_non_leveraged_long_position_providing_a_reduce_only() {
700        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
701        let fusion_pool = test_fusion_pool(sqrt_price);
702
703        let quote = get_decrease_spot_position_quote(
704            1_000_000_000,
705            TOKEN_A,
706            1.0,
707            true,
708            Some(0),
709            TOKEN_A,
710            5_000_000_000, // A
711            0,             // B
712            (HUNDRED_PERCENT / 100) as u16,
713            (HUNDRED_PERCENT / 200) as u16,
714            fusion_pool,
715            test_tick_arrays(fusion_pool),
716        )
717        .unwrap();
718
719        assert_eq!(quote.decrease_percent, 200000);
720        assert_eq!(quote.collateral_token, TOKEN_A);
721        assert_eq!(quote.position_token, TOKEN_A);
722        assert_eq!(quote.estimated_amount, 4_000_000_000);
723        assert_eq!(quote.protocol_fee_a, 0);
724        assert_eq!(quote.protocol_fee_b, 0);
725        assert_eq!(quote.price_impact, 0.0);
726        assert_eq!(quote.estimated_amount as f64 / (quote.estimated_amount as f64 - (quote.borrow as f64 / 1000.0) * 200.0), 1.0);
727    }
728
729    #[test]
730    fn decrease_non_leveraged_long_position_providing_b_reduce_only() {
731        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
732        let fusion_pool = test_fusion_pool(sqrt_price);
733
734        let quote = get_decrease_spot_position_quote(
735            200_000_000,
736            TOKEN_B,
737            1.0,
738            true,
739            Some(0),
740            TOKEN_A,
741            5_000_000_000, // A
742            0,             // B
743            (HUNDRED_PERCENT / 100) as u16,
744            (HUNDRED_PERCENT / 200) as u16,
745            fusion_pool,
746            test_tick_arrays(fusion_pool),
747        )
748        .unwrap();
749
750        assert_eq!(quote.decrease_percent, 200000);
751        assert_eq!(quote.collateral_token, TOKEN_B);
752        assert_eq!(quote.position_token, TOKEN_A);
753        assert_eq!(quote.estimated_amount, 4_000_000_000);
754        assert_eq!(quote.protocol_fee_a, 0);
755        assert_eq!(quote.protocol_fee_b, 0);
756        assert_eq!(quote.price_impact, 0.008916842709072448);
757        assert_eq!(quote.estimated_amount as f64 / (quote.estimated_amount as f64 - (quote.borrow as f64 / 1000.0) * 200.0), 1.0);
758    }
759
760    #[test]
761    fn decrease_long_position_providing_a_reduce_only() {
762        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
763        let fusion_pool = test_fusion_pool(sqrt_price);
764
765        let quote = get_decrease_spot_position_quote(
766            1_000_000_000,
767            TOKEN_A,
768            5.0,
769            true,
770            Some(0),
771            TOKEN_A,
772            5_000_000_000, // A
773            800_000_000,   // B
774            (HUNDRED_PERCENT / 100) as u16,
775            (HUNDRED_PERCENT / 200) as u16,
776            fusion_pool,
777            test_tick_arrays(fusion_pool),
778        )
779        .unwrap();
780
781        assert_eq!(quote.decrease_percent, 200000);
782        assert_eq!(quote.collateral_token, TOKEN_A);
783        assert_eq!(quote.position_token, TOKEN_A);
784        assert_eq!(quote.collateral, 0);
785        assert_eq!(quote.borrow, 0);
786        assert_eq!(quote.estimated_amount, 4_000_000_000);
787        assert_eq!(quote.protocol_fee_a, 0);
788        assert_eq!(quote.protocol_fee_b, 0);
789        assert_eq!(quote.price_impact, 0.007155289528004705);
790
791        let quote = get_decrease_spot_position_quote(
792            6_000_000_000,
793            TOKEN_A,
794            5.0,
795            true,
796            Some(0),
797            TOKEN_A,
798            5_000_000_000, // A
799            800_000_000,   // B
800            (HUNDRED_PERCENT / 100) as u16,
801            (HUNDRED_PERCENT / 200) as u16,
802            fusion_pool,
803            test_tick_arrays(fusion_pool),
804        )
805        .unwrap();
806
807        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
808        assert_eq!(quote.collateral, 0);
809        assert_eq!(quote.borrow, 0);
810        assert_eq!(quote.estimated_amount, 0);
811    }
812
813    #[test]
814    fn decrease_long_position_providing_b_reduce_only() {
815        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
816        let fusion_pool = test_fusion_pool(sqrt_price);
817
818        let quote = get_decrease_spot_position_quote(
819            200_000_000,
820            TOKEN_B,
821            5.0,
822            true,
823            Some(0),
824            TOKEN_A,
825            5_000_000_000, // A
826            800_000_000,   // B
827            (HUNDRED_PERCENT / 100) as u16,
828            (HUNDRED_PERCENT / 200) as u16,
829            fusion_pool,
830            test_tick_arrays(fusion_pool),
831        )
832        .unwrap();
833
834        assert_eq!(quote.decrease_percent, 200000);
835        assert_eq!(quote.collateral_token, TOKEN_B);
836        assert_eq!(quote.position_token, TOKEN_A);
837        assert_eq!(quote.collateral, 0);
838        assert_eq!(quote.borrow, 0);
839        assert_eq!(quote.estimated_amount, 4000000000);
840        assert_eq!(quote.protocol_fee_a, 0);
841        assert_eq!(quote.protocol_fee_b, 0);
842        assert_eq!(quote.price_impact, 0.008916842709072448);
843
844        let quote = get_decrease_spot_position_quote(
845            1200_000_000,
846            TOKEN_B,
847            5.0,
848            true,
849            Some(0),
850            TOKEN_A,
851            5_000_000_000, // A
852            800_000_000,   // B
853            (HUNDRED_PERCENT / 100) as u16,
854            (HUNDRED_PERCENT / 200) as u16,
855            fusion_pool,
856            test_tick_arrays(fusion_pool),
857        )
858        .unwrap();
859
860        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
861        assert_eq!(quote.collateral, 0);
862        assert_eq!(quote.borrow, 0);
863        assert_eq!(quote.estimated_amount, 0);
864    }
865
866    #[test]
867    fn decrease_long_position_providing_a_without_leverage() {
868        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
869        let fusion_pool = test_fusion_pool(sqrt_price);
870
871        let quote = get_decrease_spot_position_quote(
872            6_000_000_000,
873            TOKEN_A,
874            1.0,
875            false,
876            Some(100),
877            TOKEN_A,
878            5_000_000_000, // A
879            800_000_000,   // B
880            (HUNDRED_PERCENT / 100) as u16,
881            (HUNDRED_PERCENT / 200) as u16,
882            fusion_pool,
883            test_tick_arrays(fusion_pool),
884        )
885        .unwrap();
886
887        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
888        assert_eq!(quote.collateral_token, TOKEN_A);
889        assert_eq!(quote.position_token, TOKEN_B);
890        assert_eq!(quote.collateral, 1005025125);
891        assert_eq!(quote.borrow, 0);
892        assert_eq!(quote.estimated_amount, 199_319_781);
893        assert_eq!(quote.decrease_acceptable_swap_amount, 4_052_881_482);
894        assert_eq!(quote.increase_min_swap_output_amount, 197_326_583);
895        assert_eq!(quote.protocol_fee_a, 5025125);
896        assert_eq!(quote.protocol_fee_b, 0);
897        assert_eq!(quote.price_impact, 0.044685946117650754);
898    }
899
900    #[test]
901    fn decrease_long_position_providing_a() {
902        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
903        let fusion_pool = test_fusion_pool(sqrt_price);
904
905        let quote = get_decrease_spot_position_quote(
906            6_000_000_000,
907            TOKEN_A,
908            5.0,
909            false,
910            Some(100),
911            TOKEN_A,
912            5_000_000_000, // A
913            800_000_000,   // B
914            (HUNDRED_PERCENT / 100) as u16,
915            (HUNDRED_PERCENT / 200) as u16,
916            fusion_pool,
917            test_tick_arrays(fusion_pool),
918        )
919        .unwrap();
920
921        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
922        assert_eq!(quote.collateral_token, TOKEN_A);
923        assert_eq!(quote.position_token, TOKEN_B);
924        assert_eq!(quote.collateral, 211433165);
925        assert_eq!(quote.borrow, 800000000);
926        assert_eq!(quote.estimated_amount, 199_793_344);
927        assert_eq!(quote.decrease_acceptable_swap_amount, 4_052_881_482);
928        assert_eq!(quote.increase_min_swap_output_amount, 197_795_410);
929        assert_eq!(quote.protocol_fee_a, 1057165);
930        assert_eq!(quote.protocol_fee_b, 8000000);
931        assert_eq!(quote.price_impact, 0.04470711974918773);
932    }
933
934    #[test]
935    fn decrease_long_position_providing_b() {
936        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
937        let fusion_pool = test_fusion_pool(sqrt_price);
938
939        let quote = get_decrease_spot_position_quote(
940            1200_000_000,
941            TOKEN_B,
942            5.0,
943            false,
944            Some(0),
945            TOKEN_A,
946            5_000_000_000, // A
947            800_000_000,   // B
948            (HUNDRED_PERCENT / 100) as u16,
949            (HUNDRED_PERCENT / 200) as u16,
950            fusion_pool,
951            test_tick_arrays(fusion_pool),
952        )
953        .unwrap();
954
955        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
956        assert_eq!(quote.collateral_token, TOKEN_B);
957        assert_eq!(quote.position_token, TOKEN_B);
958        assert_eq!(quote.collateral, 42286633);
959        assert_eq!(quote.borrow, 800_000_000);
960        assert_eq!(quote.estimated_amount, 199_924_036);
961        assert_eq!(quote.decrease_acceptable_swap_amount, 996_777_780);
962        assert_eq!(quote.increase_min_swap_output_amount, 157_848_836);
963        assert_eq!(quote.protocol_fee_a, 0);
964        assert_eq!(quote.protocol_fee_b, 8211433);
965        assert_eq!(quote.price_impact, 0.05162980632488212);
966    }
967
968    #[test]
969    fn decrease_non_leveraged_short_position_providing_a() {
970        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
971        let fusion_pool = test_fusion_pool(sqrt_price);
972
973        let quote = get_decrease_spot_position_quote(
974            6_000_000_000,
975            TOKEN_A,
976            1.0,
977            false,
978            Some(0),
979            TOKEN_B,
980            1000_000_000, // B
981            0,            // A
982            (HUNDRED_PERCENT / 100) as u16,
983            (HUNDRED_PERCENT / 200) as u16,
984            fusion_pool,
985            test_tick_arrays(fusion_pool),
986        )
987        .unwrap();
988
989        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
990        assert_eq!(quote.collateral_token, TOKEN_A);
991        assert_eq!(quote.position_token, TOKEN_A);
992        assert_eq!(quote.collateral, 1005025125);
993        assert_eq!(quote.borrow, 0);
994        assert_eq!(quote.estimated_amount, 1_000_000_000);
995        assert_eq!(quote.protocol_fee_a, 5025125);
996        assert_eq!(quote.protocol_fee_b, 0);
997        assert_eq!(quote.price_impact, 0.044592165429757635);
998    }
999
1000    #[test]
1001    fn decrease_short_position_providing_a() {
1002        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1003        let fusion_pool = test_fusion_pool(sqrt_price);
1004
1005        let quote = get_decrease_spot_position_quote(
1006            6_000_000_000,
1007            TOKEN_A,
1008            5.0,
1009            false,
1010            Some(0),
1011            TOKEN_B,
1012            1000_000_000,  // B
1013            4_000_000_000, // A
1014            (HUNDRED_PERCENT / 100) as u16,
1015            (HUNDRED_PERCENT / 200) as u16,
1016            fusion_pool,
1017            test_tick_arrays(fusion_pool),
1018        )
1019        .unwrap();
1020
1021        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1022        assert_eq!(quote.collateral_token, TOKEN_A);
1023        assert_eq!(quote.position_token, TOKEN_A);
1024        assert_eq!(quote.collateral, 211433165);
1025        assert_eq!(quote.borrow, 160000000);
1026        assert_eq!(quote.estimated_amount, 999_620_182);
1027        assert_eq!(quote.decrease_acceptable_swap_amount, 4_983_888_901);
1028        assert_eq!(quote.increase_min_swap_output_amount, 789_244_182);
1029        assert_eq!(quote.protocol_fee_a, 2657165);
1030        assert_eq!(quote.protocol_fee_b, 0);
1031        assert_eq!(quote.price_impact, 0.051656476403882934);
1032    }
1033
1034    #[test]
1035    fn decrease_short_position_providing_b() {
1036        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1037        let fusion_pool = test_fusion_pool(sqrt_price);
1038
1039        let quote = get_decrease_spot_position_quote(
1040            1_200_000_000,
1041            TOKEN_B,
1042            5.0,
1043            false,
1044            Some(0),
1045            TOKEN_B,
1046            1000_000_000,  // B
1047            4_000_000_000, // A
1048            (HUNDRED_PERCENT / 100) as u16,
1049            (HUNDRED_PERCENT / 200) as u16,
1050            fusion_pool,
1051            test_tick_arrays(fusion_pool),
1052        )
1053        .unwrap();
1054
1055        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1056        assert_eq!(quote.collateral_token, TOKEN_B);
1057        assert_eq!(quote.position_token, TOKEN_A);
1058        assert_eq!(quote.collateral, 42_286_633);
1059        assert_eq!(quote.borrow, 160000000);
1060        assert_eq!(quote.estimated_amount, 998_966_727);
1061        assert_eq!(quote.protocol_fee_a, 1600000);
1062        assert_eq!(quote.protocol_fee_b, 211433);
1063        assert_eq!(quote.price_impact, 0.04472711596017476);
1064    }
1065
1066    #[test]
1067    fn tradable_amount_for_1x_long_position_providing_b() {
1068        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1069        let fusion_pool = test_fusion_pool(sqrt_price);
1070        let tick_arrays = test_tick_arrays(fusion_pool);
1071
1072        let collateral_token = TOKEN_B;
1073        let position_token = TOKEN_A;
1074        let leverage = 1.0;
1075        let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1076        let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1077        let available_balance = 200_000_000;
1078
1079        let tradable_amount = get_tradable_amount(
1080            collateral_token,
1081            available_balance,
1082            leverage,
1083            position_token,
1084            false,
1085            position_token,
1086            0,
1087            0,
1088            protocol_fee_rate,
1089            protocol_fee_rate_on_collateral,
1090            fusion_pool,
1091            tick_arrays.clone(),
1092        )
1093        .unwrap();
1094        assert_eq!(tradable_amount, 198403000);
1095
1096        let quote = get_increase_spot_position_quote(
1097            tradable_amount,
1098            collateral_token,
1099            position_token,
1100            leverage,
1101            Some(0),
1102            protocol_fee_rate,
1103            protocol_fee_rate_on_collateral,
1104            fusion_pool,
1105            tick_arrays,
1106        )
1107        .unwrap();
1108        assert_eq!(quote.collateral, available_balance);
1109    }
1110
1111    #[test]
1112    fn tradable_amount_for_5x_long_position_providing_b() {
1113        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1114        let fusion_pool = test_fusion_pool(sqrt_price);
1115        let tick_arrays = test_tick_arrays(fusion_pool);
1116
1117        let collateral_token = TOKEN_B;
1118        let position_token = TOKEN_A;
1119        let leverage = 5.0;
1120        let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1121        let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1122        let available_balance = 10_000_000;
1123
1124        let tradable_amount = get_tradable_amount(
1125            collateral_token,
1126            available_balance,
1127            leverage,
1128            position_token,
1129            false,
1130            position_token,
1131            0,
1132            0,
1133            protocol_fee_rate,
1134            protocol_fee_rate_on_collateral,
1135            fusion_pool,
1136            tick_arrays.clone(),
1137        )
1138        .unwrap();
1139        assert_eq!(tradable_amount, 47154380);
1140
1141        let quote = get_increase_spot_position_quote(
1142            tradable_amount,
1143            collateral_token,
1144            position_token,
1145            leverage,
1146            Some(0),
1147            protocol_fee_rate,
1148            protocol_fee_rate_on_collateral,
1149            fusion_pool,
1150            tick_arrays,
1151        )
1152        .unwrap();
1153        // TODO: fix precision error
1154        assert_eq!(quote.collateral, available_balance + 1);
1155    }
1156
1157    #[test]
1158    fn tradable_amount_for_5x_long_position_providing_a() {
1159        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1160        let fusion_pool = test_fusion_pool(sqrt_price);
1161        let tick_arrays = test_tick_arrays(fusion_pool);
1162
1163        let collateral_token = TOKEN_A;
1164        let position_token = TOKEN_A;
1165        let leverage = 5.0;
1166        let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1167        let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1168        let available_balance = 1_000_000_000;
1169
1170        let tradable_amount = get_tradable_amount(
1171            collateral_token,
1172            available_balance,
1173            leverage,
1174            position_token,
1175            false,
1176            position_token,
1177            0,
1178            0,
1179            protocol_fee_rate,
1180            protocol_fee_rate_on_collateral,
1181            fusion_pool,
1182            tick_arrays.clone(),
1183        )
1184        .unwrap();
1185        assert_eq!(tradable_amount, 4729626953);
1186
1187        let quote = get_increase_spot_position_quote(
1188            tradable_amount,
1189            collateral_token,
1190            position_token,
1191            leverage,
1192            Some(0),
1193            protocol_fee_rate,
1194            protocol_fee_rate_on_collateral,
1195            fusion_pool,
1196            tick_arrays,
1197        )
1198        .unwrap();
1199        assert_eq!(quote.collateral, available_balance);
1200        //assert_eq!(quote.estimated_amount, tradable_amount);
1201    }
1202
1203    #[test]
1204    fn tradable_amount_for_5x_short_position_providing_b() {
1205        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1206        let fusion_pool = test_fusion_pool(sqrt_price);
1207        let tick_arrays = test_tick_arrays(fusion_pool);
1208
1209        let collateral_token = TOKEN_B;
1210        let position_token = TOKEN_B;
1211        let leverage = 5.0;
1212        let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1213        let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1214        let available_balance = 200_000_000;
1215
1216        let tradable_amount = get_tradable_amount(
1217            collateral_token,
1218            available_balance,
1219            leverage,
1220            position_token,
1221            false,
1222            position_token,
1223            0,
1224            0,
1225            protocol_fee_rate,
1226            protocol_fee_rate_on_collateral,
1227            fusion_pool,
1228            tick_arrays.clone(),
1229        )
1230        .unwrap();
1231        assert_eq!(tradable_amount, 945925390);
1232
1233        let quote = get_increase_spot_position_quote(
1234            tradable_amount,
1235            collateral_token,
1236            position_token,
1237            leverage,
1238            Some(0),
1239            protocol_fee_rate,
1240            protocol_fee_rate_on_collateral,
1241            fusion_pool,
1242            tick_arrays,
1243        )
1244        .unwrap();
1245        // TODO: fix precision error
1246        assert_eq!(quote.collateral, available_balance + 1);
1247        //assert_eq!(quote.estimated_amount, tradable_amount);
1248    }
1249
1250    #[test]
1251    fn tradable_amount_for_reducing_existing_long_position() {
1252        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1253        let fusion_pool = test_fusion_pool(sqrt_price);
1254        let tick_arrays = test_tick_arrays(fusion_pool);
1255
1256        for i in 0..2 {
1257            let collateral_token = if i == 0 { TOKEN_A } else { TOKEN_B };
1258            let position_token = if i == 0 { TOKEN_A } else { TOKEN_B };
1259            let leverage = 5.0;
1260            let reduce_only = true;
1261            let position_amount = 5_000_000_000;
1262            let position_debt = 800_000_000;
1263            let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1264            let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1265            let available_balance = 50_000_000_000;
1266
1267            let tradable_amount = get_tradable_amount(
1268                collateral_token,
1269                available_balance,
1270                leverage,
1271                if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A },
1272                reduce_only,
1273                position_token,
1274                position_amount,
1275                position_debt,
1276                protocol_fee_rate,
1277                protocol_fee_rate_on_collateral,
1278                fusion_pool,
1279                tick_arrays.clone(),
1280            )
1281            .unwrap();
1282            assert_eq!(tradable_amount, 5_000_000_000);
1283
1284            let quote = get_decrease_spot_position_quote(
1285                tradable_amount,
1286                collateral_token,
1287                5.0,
1288                reduce_only,
1289                Some(0),
1290                position_token,
1291                position_amount,
1292                position_debt,
1293                protocol_fee_rate,
1294                protocol_fee_rate_on_collateral,
1295                fusion_pool,
1296                tick_arrays.clone(),
1297            )
1298            .unwrap();
1299
1300            assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1301        }
1302    }
1303
1304    #[test]
1305    fn tradable_amount_when_inverting_long_position_providing_a() {
1306        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1307        let fusion_pool = test_fusion_pool(sqrt_price);
1308        let tick_arrays = test_tick_arrays(fusion_pool);
1309
1310        let collateral_token = TOKEN_A;
1311        let position_token = TOKEN_A;
1312        let leverage = 5.0;
1313        let reduce_only = false;
1314        let position_amount = 5_000_000_000;
1315        let position_debt = 800_000_000;
1316        let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1317        let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1318        let available_balance = 2_000_000_000;
1319
1320        let tradable_amount = get_tradable_amount(
1321            collateral_token,
1322            available_balance,
1323            leverage,
1324            if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A },
1325            reduce_only,
1326            position_token,
1327            position_amount,
1328            position_debt,
1329            protocol_fee_rate,
1330            protocol_fee_rate_on_collateral,
1331            fusion_pool,
1332            tick_arrays.clone(),
1333        )
1334        .unwrap();
1335        assert_eq!(tradable_amount, 19_086_173_788);
1336
1337        let quote = get_decrease_spot_position_quote(
1338            tradable_amount,
1339            collateral_token,
1340            5.0,
1341            reduce_only,
1342            Some(0),
1343            position_token,
1344            position_amount,
1345            position_debt,
1346            protocol_fee_rate,
1347            protocol_fee_rate_on_collateral,
1348            fusion_pool,
1349            tick_arrays.clone(),
1350        )
1351        .unwrap();
1352
1353        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1354        assert_eq!(quote.collateral, 2_978_284_319);
1355    }
1356
1357    #[test]
1358    fn tradable_amount_when_inverting_long_position_providing_b() {
1359        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1360        let fusion_pool = test_fusion_pool(sqrt_price);
1361        let tick_arrays = test_tick_arrays(fusion_pool);
1362
1363        let collateral_token = TOKEN_B;
1364        let position_token = TOKEN_A;
1365        let leverage = 5.0;
1366        let reduce_only = false;
1367        let position_amount = 5_000_000_000;
1368        let position_debt = 800_000_000;
1369        let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1370        let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1371        let available_balance = 500_000_000;
1372
1373        let tradable_amount = get_tradable_amount(
1374            collateral_token,
1375            available_balance,
1376            leverage,
1377            if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A },
1378            reduce_only,
1379            position_token,
1380            position_amount,
1381            position_debt,
1382            protocol_fee_rate,
1383            protocol_fee_rate_on_collateral,
1384            fusion_pool,
1385            tick_arrays.clone(),
1386        )
1387        .unwrap();
1388        assert_eq!(tradable_amount, 4_295_498_968);
1389
1390        let quote = get_decrease_spot_position_quote(
1391            tradable_amount,
1392            collateral_token,
1393            5.0,
1394            reduce_only,
1395            Some(0),
1396            position_token,
1397            position_amount,
1398            position_debt,
1399            protocol_fee_rate,
1400            protocol_fee_rate_on_collateral,
1401            fusion_pool,
1402            tick_arrays.clone(),
1403        )
1404        .unwrap();
1405
1406        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1407        assert_eq!(quote.collateral, 696_777_779);
1408    }
1409}