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: u32,
59    protocol_fee_rate_on_collateral: u32,
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    if protocol_fee_rate >= HUNDRED_PERCENT {
72        return Err(INVALID_ARGUMENTS.into());
73    }
74
75    if protocol_fee_rate_on_collateral >= HUNDRED_PERCENT {
76        return Err(INVALID_ARGUMENTS.into());
77    }
78
79    let borrow: u64;
80    let mut collateral: u64;
81    let mut estimated_amount: u64 = 0;
82    let mut swap_input_amount: u64;
83    let mut next_sqrt_price = fusion_pool.sqrt_price;
84    let mut min_swap_output_amount: u64 = 0;
85
86    let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
87    let slippage_tolerance_bps = slippage_tolerance_bps.unwrap_or(DEFAULT_SLIPPAGE_TOLERANCE_BPS);
88
89    let borrowed_token = if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A };
90    let swap_input_token_is_a = borrowed_token == TOKEN_A;
91
92    if borrowed_token == collateral_token {
93        borrow = ceil((increase_amount as f64 * (leverage - 1.0)) / leverage) as u64;
94        collateral = increase_amount - apply_swap_fee(apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?, fusion_pool.fee_rate, false)?;
95        collateral = reverse_apply_swap_fee(collateral, fusion_pool.fee_rate, false)?;
96        collateral = reverse_apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
97
98        swap_input_amount = collateral + borrow;
99    } else {
100        let position_to_borrowed_token_price = if collateral_token == TOKEN_A { price } else { 1.0 / price };
101        let borrow_in_position_token = ceil((increase_amount as f64 * (leverage - 1.0)) / leverage);
102
103        borrow = ceil(borrow_in_position_token * position_to_borrowed_token_price) as u64;
104
105        let borrow_in_position_token_with_fees_applied =
106            apply_swap_fee(apply_tuna_protocol_fee(borrow_in_position_token as u64, protocol_fee_rate, false)?, fusion_pool.fee_rate, false)?;
107
108        collateral = increase_amount - borrow_in_position_token_with_fees_applied;
109        collateral = reverse_apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
110
111        swap_input_amount = borrow;
112    }
113
114    let (protocol_fee_a, protocol_fee_b) =
115        compute_tuna_protocol_fee(collateral_token, borrowed_token, collateral, borrow, protocol_fee_rate_on_collateral, protocol_fee_rate);
116
117    swap_input_amount -= if swap_input_token_is_a { protocol_fee_a } else { protocol_fee_b };
118
119    if position_token == collateral_token {
120        estimated_amount = collateral - if collateral_token == TOKEN_A { protocol_fee_a } else { protocol_fee_b };
121    }
122
123    if swap_input_amount > 0 {
124        let swap = swap_quote_by_input_token(swap_input_amount, swap_input_token_is_a, 0, fusion_pool, tick_arrays, None, None)?;
125        estimated_amount += swap.token_est_out;
126        next_sqrt_price = swap.next_sqrt_price;
127        min_swap_output_amount = try_get_min_amount_with_slippage_tolerance(swap.token_est_out, slippage_tolerance_bps)?;
128    }
129
130    let new_price = sqrt_price_to_price(next_sqrt_price.into(), 1, 1);
131    let price_impact = (new_price / price - 1.0).abs() * 100.0;
132
133    Ok(IncreaseSpotPositionQuoteResult {
134        collateral,
135        borrow,
136        estimated_amount,
137        swap_input_amount,
138        min_swap_output_amount,
139        protocol_fee_a,
140        protocol_fee_b,
141        price_impact,
142    })
143}
144
145#[cfg_attr(feature = "wasm", wasm_expose)]
146pub struct DecreaseSpotPositionQuoteResult {
147    /** Position decrease percentage */
148    pub decrease_percent: u32,
149    /** Collateral token of the new position */
150    pub collateral_token: u8,
151    /** Token of the new position */
152    pub position_token: u8,
153    /** Required additional collateral amount */
154    pub collateral: u64,
155    /** Required amount to borrow */
156    pub borrow: u64,
157    /** Minimum swap input amount according to the provided slippage. */
158    pub max_swap_input_amount: u64,
159    /** Estimated total amount of the new position */
160    pub estimated_amount: u64,
161    /** Protocol fee in token A */
162    pub protocol_fee_a: u64,
163    /** Protocol fee in token B */
164    pub protocol_fee_b: u64,
165    /** Price impact in percents */
166    pub price_impact: f64,
167}
168
169/// Spot position decrease quote
170///
171/// # Parameters
172/// - `increase_amount`: Position total size in the collateral_token.
173/// - `collateral_token`: Collateral token.
174/// - `leverage`: Leverage (1.0 or higher).
175/// - `reduce_only`: Only allow reducing the existing position.
176/// - `slippage_tolerance_bps`: An optional slippage tolerance in basis points. Defaults to the global slippage tolerance if not provided.
177/// - `position_token`: Token of the existing position.
178/// - `position_amount`: Existing position amount in the position_token.
179/// - `position_debt`: Existing position debt in the token opposite to the position_token.
180/// - `protocol_fee_rate`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
181/// - `protocol_fee_rate_on_collateral`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
182/// - `fusion_pool`: Fusion pool.
183/// - `tick_arrays`: Five tick arrays around the current pool price.
184///
185/// # Returns
186/// - `DecreaseSpotPositionQuoteResult`: quote result
187#[cfg_attr(feature = "wasm", wasm_expose)]
188pub fn get_decrease_spot_position_quote(
189    decrease_amount: u64,
190    collateral_token: u8,
191    leverage: f64,
192    reduce_only: bool,
193    slippage_tolerance_bps: Option<u16>,
194    position_token: u8,
195    position_amount: u64,
196    position_debt: u64,
197    protocol_fee_rate: u32,
198    protocol_fee_rate_on_collateral: u32,
199    fusion_pool: FusionPoolFacade,
200    tick_arrays: TickArrays,
201) -> Result<DecreaseSpotPositionQuoteResult, CoreError> {
202    if collateral_token > TOKEN_B || position_token > TOKEN_B {
203        return Err(INVALID_ARGUMENTS.into());
204    }
205
206    if leverage < 1.0 {
207        return Err(INVALID_ARGUMENTS.into());
208    }
209
210    if protocol_fee_rate >= HUNDRED_PERCENT {
211        return Err(INVALID_ARGUMENTS.into());
212    }
213
214    if protocol_fee_rate_on_collateral >= HUNDRED_PERCENT {
215        return Err(INVALID_ARGUMENTS.into());
216    }
217
218    let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
219    let position_to_borrowed_token_price = if position_token == TOKEN_A { price } else { 1.0 / price };
220    let borrowed_token = if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A };
221    let slippage_tolerance_bps = slippage_tolerance_bps.unwrap_or(DEFAULT_SLIPPAGE_TOLERANCE_BPS);
222
223    let mut collateral = 0;
224    let mut borrow = 0;
225    let mut swap_input_amount = 0;
226    let estimated_amount: u64;
227    let mut next_sqrt_price = fusion_pool.sqrt_price;
228    let mut new_position_token = position_token;
229    let decrease_percent: u32;
230
231    let mut decrease_amount_in_position_token = if collateral_token == position_token {
232        decrease_amount
233    } else {
234        round(decrease_amount as f64 / position_to_borrowed_token_price) as u64
235    };
236
237    if reduce_only && decrease_amount_in_position_token > position_amount {
238        decrease_amount_in_position_token = position_amount;
239    }
240
241    decrease_percent = ((decrease_amount_in_position_token * HUNDRED_PERCENT as u64 / position_amount) as u32).min(HUNDRED_PERCENT);
242
243    if decrease_amount_in_position_token <= position_amount {
244        estimated_amount = position_amount * (HUNDRED_PERCENT - decrease_percent) as u64 / HUNDRED_PERCENT as u64;
245
246        if collateral_token == position_token {
247            if position_debt > 0 {
248                let swap_out = position_debt * decrease_percent as u64 / HUNDRED_PERCENT as u64;
249                let swap = swap_quote_by_output_token(swap_out, borrowed_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
250                next_sqrt_price = swap.next_sqrt_price;
251                swap_input_amount = swap.token_est_in;
252            }
253        } else {
254            swap_input_amount = position_amount - estimated_amount;
255            let swap = swap_quote_by_input_token(swap_input_amount, position_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
256            next_sqrt_price = swap.next_sqrt_price;
257        }
258    } else {
259        new_position_token = if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A };
260        let increase_amount = decrease_amount_in_position_token - position_amount;
261
262        if position_token == collateral_token {
263            // Example:
264            // collateral_token: A
265            // position_token: A
266            // position_debt: B
267            // new_position_token: B
268            // newBorrowedToken: A
269
270            // B
271            estimated_amount = round(increase_amount as f64 * position_to_borrowed_token_price) as u64;
272
273            // A
274            borrow = round((increase_amount as f64 * (leverage - 1.0)) / leverage) as u64;
275            let borrow_with_fees_applied = apply_swap_fee(apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?, fusion_pool.fee_rate, false)?;
276
277            collateral = increase_amount - borrow_with_fees_applied;
278
279            // B->A
280            if position_debt > 0 {
281                let swap =
282                    swap_quote_by_output_token(position_debt, position_token != TOKEN_A, 0, fusion_pool, tick_arrays.clone().into(), None, None)?;
283                swap_input_amount = swap.token_est_in;
284            }
285
286            swap_input_amount += collateral + apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?;
287            let swap = swap_quote_by_input_token(swap_input_amount, position_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
288            next_sqrt_price = swap.next_sqrt_price;
289
290            collateral = reverse_apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
291        } else {
292            // Example:
293            // collateral_token: B
294            // position_token: A
295            // new_position_token: B
296            // newBorrowedToken: A
297
298            // B
299            estimated_amount = round(increase_amount as f64 * position_to_borrowed_token_price) as u64;
300
301            borrow = round((increase_amount as f64 * (leverage - 1.0)) / leverage) as u64;
302            let borrow_with_fees_applied = apply_swap_fee(apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?, fusion_pool.fee_rate, false)?;
303
304            collateral = increase_amount - borrow_with_fees_applied;
305            collateral = round(collateral as f64 * position_to_borrowed_token_price) as u64;
306            collateral = reverse_apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
307
308            // A->B
309            swap_input_amount = position_amount + apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?;
310            let swap_quote = swap_quote_by_input_token(swap_input_amount, position_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
311            next_sqrt_price = swap_quote.next_sqrt_price;
312        }
313    }
314
315    let (protocol_fee_a, protocol_fee_b) =
316        compute_tuna_protocol_fee(collateral_token, borrowed_token, collateral, borrow, protocol_fee_rate_on_collateral, protocol_fee_rate);
317
318    let new_price = sqrt_price_to_price(next_sqrt_price.into(), 1, 1);
319    let price_impact = (new_price / price - 1.0).abs() * 100.0;
320
321    let max_swap_input_amount = try_get_max_amount_with_slippage_tolerance(swap_input_amount, slippage_tolerance_bps)?;
322
323    Ok(DecreaseSpotPositionQuoteResult {
324        decrease_percent,
325        collateral_token,
326        position_token: new_position_token,
327        collateral,
328        borrow,
329        max_swap_input_amount,
330        estimated_amount,
331        protocol_fee_a,
332        protocol_fee_b,
333        price_impact,
334    })
335}
336
337/// Returns the liquidation price
338///
339/// # Parameters
340/// - `position_token`: Token of the position
341/// - `amount`: Position total size (decimal)
342/// - `debt`: Position total debt (decimal)
343/// - `liquidation_threshold`: Liquidation threshold of the market (decimal)
344///
345/// # Returns
346/// - `f64`: Decimal liquidation price
347#[cfg_attr(feature = "wasm", wasm_expose)]
348pub fn get_liquidation_price(position_token: u8, amount: f64, debt: f64, liquidation_threshold: f64) -> Result<f64, CoreError> {
349    if debt < 0.0 || amount < 0.0 {
350        return Err(INVALID_ARGUMENTS);
351    }
352
353    if liquidation_threshold <= 0.0 || liquidation_threshold >= 1.0 {
354        return Err(INVALID_ARGUMENTS);
355    }
356
357    if debt == 0.0 || amount == 0.0 {
358        return Ok(0.0);
359    }
360
361    if position_token == TOKEN_A {
362        Ok(debt / (amount * liquidation_threshold))
363    } else {
364        Ok((amount * liquidation_threshold) / debt)
365    }
366}
367
368/// Calculates the maximum tradable amount in the collateral token.
369///
370/// # Parameters
371/// - `collateral_token`: Collateral token.
372/// - `available_balance`: Available wallet balance in the collateral_token.
373/// - `leverage`: Leverage (1.0 or higher).
374/// - `new_position_token`: Token of the new position.
375/// - `position_token`: Token of the existing position. Should be set to new_position_token if position_amount is zero.
376/// - `position_amount`: Existing position amount in the position_token.
377/// - `position_debt`: Existing position debt in the token opposite to the position_token.
378/// - `reduce_only`: Only allow reducing the existing position.///
379/// - `protocol_fee_rate`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
380/// - `protocol_fee_rate_on_collateral`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
381/// - `fusion_pool`: Fusion pool.
382/// - `tick_arrays`: Five tick arrays around the current pool price.
383///
384/// # Returns
385/// - `u64`: the maximum tradable amount
386#[cfg_attr(feature = "wasm", wasm_expose)]
387pub fn get_tradable_amount(
388    collateral_token: u8,
389    available_balance: u64,
390    leverage: f64,
391    new_position_token: u8,
392    reduce_only: bool,
393    position_token: u8,
394    position_amount: u64,
395    position_debt: u64,
396    protocol_fee_rate: u32,
397    protocol_fee_rate_on_collateral: u32,
398    fusion_pool: FusionPoolFacade,
399    tick_arrays: TickArrays,
400) -> Result<u64, CoreError> {
401    if collateral_token > TOKEN_B || position_token > TOKEN_B {
402        return Err(INVALID_ARGUMENTS.into());
403    }
404
405    if leverage < 1.0 {
406        return Err(INVALID_ARGUMENTS.into());
407    }
408
409    if protocol_fee_rate >= HUNDRED_PERCENT {
410        return Err(INVALID_ARGUMENTS.into());
411    }
412
413    if protocol_fee_rate_on_collateral >= HUNDRED_PERCENT {
414        return Err(INVALID_ARGUMENTS.into());
415    }
416
417    if position_amount == 0 && new_position_token != position_token {
418        return Err(INVALID_ARGUMENTS.into());
419    }
420
421    // T = C⋅Fc⋅Fs + B⋅Fb⋅Fs, where: Fc/Fb/Fs - collateral/borrow/swap fee multiplier
422    // B = T⋅(L - 1) / L
423    // => T = C⋅Fc⋅Fs / (1 - Fb⋅Fs⋅(L - 1) / L)
424    let add_leverage = |collateral: u64| -> Result<u64, CoreError> {
425        let mut collateral = apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
426        if collateral_token != new_position_token {
427            collateral = apply_swap_fee(collateral, fusion_pool.fee_rate, false)?;
428        }
429
430        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);
431        let total = (collateral as f64 / (1.0 - (fee_multiplier * (leverage - 1.0)) / leverage)) as u64;
432        Ok(total)
433    };
434
435    let available_to_trade = if new_position_token == position_token {
436        add_leverage(available_balance)?
437    } else {
438        let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
439        let position_to_opposite_token_price = if position_token == TOKEN_A { price } else { 1.0 / price };
440
441        if reduce_only {
442            if collateral_token == position_token {
443                position_amount
444            } else {
445                round(position_amount as f64 * position_to_opposite_token_price) as u64
446            }
447        } else {
448            let position_amount_in_collateral_token = if collateral_token == position_token {
449                position_amount
450            } else {
451                round(position_amount as f64 * position_to_opposite_token_price) as u64
452            };
453
454            let position_collateral = if collateral_token == position_token {
455                let swap_in = if position_debt > 0 {
456                    swap_quote_by_output_token(position_debt, position_token == TOKEN_B, 0, fusion_pool, tick_arrays, None, None)?.token_est_in
457                } else {
458                    0
459                };
460                position_amount - swap_in
461            } else {
462                if position_amount > 0 {
463                    let swap_quote = swap_quote_by_input_token(position_amount, position_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
464                    swap_quote.token_est_out - position_debt
465                } else {
466                    0
467                }
468            };
469
470            // Add the refunded collateral to the available balance
471            position_amount_in_collateral_token + add_leverage(available_balance + position_collateral)?
472        }
473    };
474
475    Ok(available_to_trade)
476}
477
478pub fn apply_tuna_protocol_fee(amount: u64, protocol_fee_rate: u32, round_up: bool) -> Result<u64, CoreError> {
479    try_mul_div(amount, HUNDRED_PERCENT as u128 - protocol_fee_rate as u128, HUNDRED_PERCENT as u128, round_up)
480}
481
482pub fn reverse_apply_tuna_protocol_fee(amount: u64, protocol_fee_rate: u32, round_up: bool) -> Result<u64, CoreError> {
483    try_mul_div(amount, HUNDRED_PERCENT as u128, HUNDRED_PERCENT as u128 - protocol_fee_rate as u128, round_up)
484}
485
486pub fn apply_swap_fee(amount: u64, fee_rate: u16, round_up: bool) -> Result<u64, CoreError> {
487    try_mul_div(amount, 1_000_000 - fee_rate as u128, 1_000_000, round_up)
488}
489
490pub fn reverse_apply_swap_fee(amount: u64, fee_rate: u16, round_up: bool) -> Result<u64, CoreError> {
491    try_mul_div(amount, 1_000_000, 1_000_000 - fee_rate as u128, round_up)
492}
493
494pub fn compute_tuna_protocol_fee(
495    collateral_token: u8,
496    borrowed_token: u8,
497    collateral: u64,
498    borrow: u64,
499    protocol_fee_rate_on_collateral: u32,
500    protocol_fee_rate: u32,
501) -> (u64, u64) {
502    let collateral_a = if collateral_token == TOKEN_A { collateral } else { 0 };
503    let collateral_b = if collateral_token == TOKEN_B { collateral } else { 0 };
504    let borrow_a = if borrowed_token == TOKEN_A { borrow } else { 0 };
505    let borrow_b = if borrowed_token == TOKEN_B { borrow } else { 0 };
506
507    let protocol_fee_a = ((collateral_a as u128 * protocol_fee_rate_on_collateral as u128 + borrow_a as u128 * protocol_fee_rate as u128)
508        / HUNDRED_PERCENT as u128) as u64;
509    let protocol_fee_b = ((collateral_b as u128 * protocol_fee_rate_on_collateral as u128 + borrow_b as u128 * protocol_fee_rate as u128)
510        / HUNDRED_PERCENT as u128) as u64;
511
512    (protocol_fee_a, protocol_fee_b)
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,
585            HUNDRED_PERCENT / 200,
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,
613            HUNDRED_PERCENT / 200,
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,
640            HUNDRED_PERCENT / 200,
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,
667            HUNDRED_PERCENT / 200,
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,
713            HUNDRED_PERCENT / 200,
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,
744            HUNDRED_PERCENT / 200,
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,
775            HUNDRED_PERCENT / 200,
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,
801            HUNDRED_PERCENT / 200,
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,
828            HUNDRED_PERCENT / 200,
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,
854            HUNDRED_PERCENT / 200,
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(0),
877            TOKEN_A,
878            5_000_000_000, // A
879            800_000_000,   // B
880            HUNDRED_PERCENT / 100,
881            HUNDRED_PERCENT / 200,
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, 200_000_000);
893        assert_eq!(quote.protocol_fee_a, 5025125);
894        assert_eq!(quote.protocol_fee_b, 0);
895        assert_eq!(quote.price_impact, 0.044685946117650754);
896    }
897
898    #[test]
899    fn decrease_long_position_providing_a() {
900        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
901        let fusion_pool = test_fusion_pool(sqrt_price);
902
903        let quote = get_decrease_spot_position_quote(
904            6_000_000_000,
905            TOKEN_A,
906            5.0,
907            false,
908            Some(0),
909            TOKEN_A,
910            5_000_000_000, // A
911            800_000_000,   // B
912            HUNDRED_PERCENT / 100,
913            HUNDRED_PERCENT / 200,
914            fusion_pool,
915            test_tick_arrays(fusion_pool),
916        )
917        .unwrap();
918
919        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
920        assert_eq!(quote.collateral_token, TOKEN_A);
921        assert_eq!(quote.position_token, TOKEN_B);
922        assert_eq!(quote.collateral, 211433165);
923        assert_eq!(quote.borrow, 800000000);
924        assert_eq!(quote.estimated_amount, 200_000_000);
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            1_200_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,
945            HUNDRED_PERCENT / 200,
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, 200000000);
957        assert_eq!(quote.protocol_fee_a, 0);
958        assert_eq!(quote.protocol_fee_b, 8211433);
959        assert_eq!(quote.price_impact, 0.05162980632488212);
960    }
961
962    #[test]
963    fn decrease_non_leveraged_short_position_providing_a() {
964        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
965        let fusion_pool = test_fusion_pool(sqrt_price);
966
967        let quote = get_decrease_spot_position_quote(
968            6_000_000_000,
969            TOKEN_A,
970            1.0,
971            false,
972            Some(0),
973            TOKEN_B,
974            1000_000_000, // B
975            0,            // A
976            HUNDRED_PERCENT / 100,
977            HUNDRED_PERCENT / 200,
978            fusion_pool,
979            test_tick_arrays(fusion_pool),
980        )
981        .unwrap();
982
983        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
984        assert_eq!(quote.collateral_token, TOKEN_A);
985        assert_eq!(quote.position_token, TOKEN_A);
986        assert_eq!(quote.collateral, 1005025125);
987        assert_eq!(quote.borrow, 0);
988        assert_eq!(quote.estimated_amount, 1_000_000_000);
989        assert_eq!(quote.protocol_fee_a, 5025125);
990        assert_eq!(quote.protocol_fee_b, 0);
991        assert_eq!(quote.price_impact, 0.044592165429757635);
992    }
993
994    #[test]
995    fn decrease_short_position_providing_a() {
996        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
997        let fusion_pool = test_fusion_pool(sqrt_price);
998
999        let quote = get_decrease_spot_position_quote(
1000            6_000_000_000,
1001            TOKEN_A,
1002            5.0,
1003            false,
1004            Some(0),
1005            TOKEN_B,
1006            1000_000_000,  // B
1007            4_000_000_000, // A
1008            HUNDRED_PERCENT / 100,
1009            HUNDRED_PERCENT / 200,
1010            fusion_pool,
1011            test_tick_arrays(fusion_pool),
1012        )
1013        .unwrap();
1014
1015        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1016        assert_eq!(quote.collateral_token, TOKEN_A);
1017        assert_eq!(quote.position_token, TOKEN_A);
1018        assert_eq!(quote.collateral, 211433165);
1019        assert_eq!(quote.borrow, 160000000);
1020        assert_eq!(quote.estimated_amount, 1_000_000_000);
1021        assert_eq!(quote.protocol_fee_a, 2657165);
1022        assert_eq!(quote.protocol_fee_b, 0);
1023        assert_eq!(quote.price_impact, 0.051656476403882934);
1024    }
1025
1026    #[test]
1027    fn decrease_short_position_providing_b() {
1028        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1029        let fusion_pool = test_fusion_pool(sqrt_price);
1030
1031        let quote = get_decrease_spot_position_quote(
1032            1_200_000_000,
1033            TOKEN_B,
1034            5.0,
1035            false,
1036            Some(0),
1037            TOKEN_B,
1038            1000_000_000,  // B
1039            4_000_000_000, // A
1040            HUNDRED_PERCENT / 100,
1041            HUNDRED_PERCENT / 200,
1042            fusion_pool,
1043            test_tick_arrays(fusion_pool),
1044        )
1045        .unwrap();
1046
1047        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1048        assert_eq!(quote.collateral_token, TOKEN_B);
1049        assert_eq!(quote.position_token, TOKEN_A);
1050        assert_eq!(quote.collateral, 42_286_633);
1051        assert_eq!(quote.borrow, 160000000);
1052        assert_eq!(quote.estimated_amount, 1_000_000_000);
1053        assert_eq!(quote.protocol_fee_a, 1600000);
1054        assert_eq!(quote.protocol_fee_b, 211433);
1055        assert_eq!(quote.price_impact, 0.04472711596017476);
1056    }
1057
1058    #[test]
1059    fn tradable_amount_for_1x_long_position_providing_b() {
1060        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1061        let fusion_pool = test_fusion_pool(sqrt_price);
1062        let tick_arrays = test_tick_arrays(fusion_pool);
1063
1064        let collateral_token = TOKEN_B;
1065        let position_token = TOKEN_A;
1066        let leverage = 1.0;
1067        let protocol_fee_rate = HUNDRED_PERCENT / 100;
1068        let protocol_fee_rate_on_collateral = HUNDRED_PERCENT / 200;
1069        let available_balance = 200_000_000;
1070
1071        let tradable_amount = get_tradable_amount(
1072            collateral_token,
1073            available_balance,
1074            leverage,
1075            position_token,
1076            false,
1077            position_token,
1078            0,
1079            0,
1080            protocol_fee_rate,
1081            protocol_fee_rate_on_collateral,
1082            fusion_pool,
1083            tick_arrays.clone(),
1084        )
1085        .unwrap();
1086        assert_eq!(tradable_amount, 198403000);
1087
1088        let quote = get_increase_spot_position_quote(
1089            tradable_amount,
1090            collateral_token,
1091            position_token,
1092            leverage,
1093            Some(0),
1094            protocol_fee_rate,
1095            protocol_fee_rate_on_collateral,
1096            fusion_pool,
1097            tick_arrays,
1098        )
1099        .unwrap();
1100        assert_eq!(quote.collateral, available_balance);
1101    }
1102
1103    #[test]
1104    fn tradable_amount_for_5x_long_position_providing_b() {
1105        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1106        let fusion_pool = test_fusion_pool(sqrt_price);
1107        let tick_arrays = test_tick_arrays(fusion_pool);
1108
1109        let collateral_token = TOKEN_B;
1110        let position_token = TOKEN_A;
1111        let leverage = 5.0;
1112        let protocol_fee_rate = HUNDRED_PERCENT / 100;
1113        let protocol_fee_rate_on_collateral = HUNDRED_PERCENT / 200;
1114        let available_balance = 10_000_000;
1115
1116        let tradable_amount = get_tradable_amount(
1117            collateral_token,
1118            available_balance,
1119            leverage,
1120            position_token,
1121            false,
1122            position_token,
1123            0,
1124            0,
1125            protocol_fee_rate,
1126            protocol_fee_rate_on_collateral,
1127            fusion_pool,
1128            tick_arrays.clone(),
1129        )
1130        .unwrap();
1131        assert_eq!(tradable_amount, 47154380);
1132
1133        let quote = get_increase_spot_position_quote(
1134            tradable_amount,
1135            collateral_token,
1136            position_token,
1137            leverage,
1138            Some(0),
1139            protocol_fee_rate,
1140            protocol_fee_rate_on_collateral,
1141            fusion_pool,
1142            tick_arrays,
1143        )
1144        .unwrap();
1145        // TODO: fix precision error
1146        assert_eq!(quote.collateral, available_balance + 1);
1147    }
1148
1149    #[test]
1150    fn tradable_amount_for_5x_long_position_providing_a() {
1151        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1152        let fusion_pool = test_fusion_pool(sqrt_price);
1153        let tick_arrays = test_tick_arrays(fusion_pool);
1154
1155        let collateral_token = TOKEN_A;
1156        let position_token = TOKEN_A;
1157        let leverage = 5.0;
1158        let protocol_fee_rate = HUNDRED_PERCENT / 100;
1159        let protocol_fee_rate_on_collateral = HUNDRED_PERCENT / 200;
1160        let available_balance = 1_000_000_000;
1161
1162        let tradable_amount = get_tradable_amount(
1163            collateral_token,
1164            available_balance,
1165            leverage,
1166            position_token,
1167            false,
1168            position_token,
1169            0,
1170            0,
1171            protocol_fee_rate,
1172            protocol_fee_rate_on_collateral,
1173            fusion_pool,
1174            tick_arrays.clone(),
1175        )
1176        .unwrap();
1177        assert_eq!(tradable_amount, 4729626953);
1178
1179        let quote = get_increase_spot_position_quote(
1180            tradable_amount,
1181            collateral_token,
1182            position_token,
1183            leverage,
1184            Some(0),
1185            protocol_fee_rate,
1186            protocol_fee_rate_on_collateral,
1187            fusion_pool,
1188            tick_arrays,
1189        )
1190        .unwrap();
1191        assert_eq!(quote.collateral, available_balance);
1192        //assert_eq!(quote.estimated_amount, tradable_amount);
1193    }
1194
1195    #[test]
1196    fn tradable_amount_for_5x_short_position_providing_b() {
1197        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1198        let fusion_pool = test_fusion_pool(sqrt_price);
1199        let tick_arrays = test_tick_arrays(fusion_pool);
1200
1201        let collateral_token = TOKEN_B;
1202        let position_token = TOKEN_B;
1203        let leverage = 5.0;
1204        let protocol_fee_rate = HUNDRED_PERCENT / 100;
1205        let protocol_fee_rate_on_collateral = HUNDRED_PERCENT / 200;
1206        let available_balance = 200_000_000;
1207
1208        let tradable_amount = get_tradable_amount(
1209            collateral_token,
1210            available_balance,
1211            leverage,
1212            position_token,
1213            false,
1214            position_token,
1215            0,
1216            0,
1217            protocol_fee_rate,
1218            protocol_fee_rate_on_collateral,
1219            fusion_pool,
1220            tick_arrays.clone(),
1221        )
1222        .unwrap();
1223        assert_eq!(tradable_amount, 945925390);
1224
1225        let quote = get_increase_spot_position_quote(
1226            tradable_amount,
1227            collateral_token,
1228            position_token,
1229            leverage,
1230            Some(0),
1231            protocol_fee_rate,
1232            protocol_fee_rate_on_collateral,
1233            fusion_pool,
1234            tick_arrays,
1235        )
1236        .unwrap();
1237        // TODO: fix precision error
1238        assert_eq!(quote.collateral, available_balance + 1);
1239        //assert_eq!(quote.estimated_amount, tradable_amount);
1240    }
1241
1242    #[test]
1243    fn tradable_amount_for_reducing_existing_long_position() {
1244        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1245        let fusion_pool = test_fusion_pool(sqrt_price);
1246        let tick_arrays = test_tick_arrays(fusion_pool);
1247
1248        for i in 0..2 {
1249            let collateral_token = if i == 0 { TOKEN_A } else { TOKEN_B };
1250            let position_token = if i == 0 { TOKEN_A } else { TOKEN_B };
1251            let leverage = 5.0;
1252            let reduce_only = true;
1253            let position_amount = 5_000_000_000;
1254            let position_debt = 800_000_000;
1255            let protocol_fee_rate = HUNDRED_PERCENT / 100;
1256            let protocol_fee_rate_on_collateral = HUNDRED_PERCENT / 200;
1257            let available_balance = 50_000_000_000;
1258
1259            let tradable_amount = get_tradable_amount(
1260                collateral_token,
1261                available_balance,
1262                leverage,
1263                if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A },
1264                reduce_only,
1265                position_token,
1266                position_amount,
1267                position_debt,
1268                protocol_fee_rate,
1269                protocol_fee_rate_on_collateral,
1270                fusion_pool,
1271                tick_arrays.clone(),
1272            )
1273            .unwrap();
1274            assert_eq!(tradable_amount, 5_000_000_000);
1275
1276            let quote = get_decrease_spot_position_quote(
1277                tradable_amount,
1278                collateral_token,
1279                5.0,
1280                reduce_only,
1281                Some(0),
1282                position_token,
1283                position_amount,
1284                position_debt,
1285                protocol_fee_rate,
1286                protocol_fee_rate_on_collateral,
1287                fusion_pool,
1288                tick_arrays.clone(),
1289            )
1290            .unwrap();
1291
1292            assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1293        }
1294    }
1295
1296    #[test]
1297    fn tradable_amount_when_inverting_long_position_providing_a() {
1298        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1299        let fusion_pool = test_fusion_pool(sqrt_price);
1300        let tick_arrays = test_tick_arrays(fusion_pool);
1301
1302        let collateral_token = TOKEN_A;
1303        let position_token = TOKEN_A;
1304        let leverage = 5.0;
1305        let reduce_only = false;
1306        let position_amount = 5_000_000_000;
1307        let position_debt = 800_000_000;
1308        let protocol_fee_rate = HUNDRED_PERCENT / 100;
1309        let protocol_fee_rate_on_collateral = HUNDRED_PERCENT / 200;
1310        let available_balance = 2_000_000_000;
1311
1312        let tradable_amount = get_tradable_amount(
1313            collateral_token,
1314            available_balance,
1315            leverage,
1316            if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A },
1317            reduce_only,
1318            position_token,
1319            position_amount,
1320            position_debt,
1321            protocol_fee_rate,
1322            protocol_fee_rate_on_collateral,
1323            fusion_pool,
1324            tick_arrays.clone(),
1325        )
1326        .unwrap();
1327        assert_eq!(tradable_amount, 19_086_173_788);
1328
1329        let quote = get_decrease_spot_position_quote(
1330            tradable_amount,
1331            collateral_token,
1332            5.0,
1333            reduce_only,
1334            Some(0),
1335            position_token,
1336            position_amount,
1337            position_debt,
1338            protocol_fee_rate,
1339            protocol_fee_rate_on_collateral,
1340            fusion_pool,
1341            tick_arrays.clone(),
1342        )
1343        .unwrap();
1344
1345        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1346        assert_eq!(quote.collateral, 2_978_284_319);
1347    }
1348
1349    #[test]
1350    fn tradable_amount_when_inverting_long_position_providing_b() {
1351        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1352        let fusion_pool = test_fusion_pool(sqrt_price);
1353        let tick_arrays = test_tick_arrays(fusion_pool);
1354
1355        let collateral_token = TOKEN_B;
1356        let position_token = TOKEN_A;
1357        let leverage = 5.0;
1358        let reduce_only = false;
1359        let position_amount = 5_000_000_000;
1360        let position_debt = 800_000_000;
1361        let protocol_fee_rate = HUNDRED_PERCENT / 100;
1362        let protocol_fee_rate_on_collateral = HUNDRED_PERCENT / 200;
1363        let available_balance = 500_000_000;
1364
1365        let tradable_amount = get_tradable_amount(
1366            collateral_token,
1367            available_balance,
1368            leverage,
1369            if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A },
1370            reduce_only,
1371            position_token,
1372            position_amount,
1373            position_debt,
1374            protocol_fee_rate,
1375            protocol_fee_rate_on_collateral,
1376            fusion_pool,
1377            tick_arrays.clone(),
1378        )
1379        .unwrap();
1380        assert_eq!(tradable_amount, 4_295_498_968);
1381
1382        let quote = get_decrease_spot_position_quote(
1383            tradable_amount,
1384            collateral_token,
1385            5.0,
1386            reduce_only,
1387            Some(0),
1388            position_token,
1389            position_amount,
1390            position_debt,
1391            protocol_fee_rate,
1392            protocol_fee_rate_on_collateral,
1393            fusion_pool,
1394            tick_arrays.clone(),
1395        )
1396        .unwrap();
1397
1398        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1399        assert_eq!(quote.collateral, 696_777_779);
1400    }
1401}