defituna_core/quote/
tuna_spot_position.rs

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