defituna_core/quote/
tuna_spot_position.rs

1#![allow(clippy::collapsible_else_if)]
2#![allow(clippy::too_many_arguments)]
3
4#[cfg(feature = "wasm")]
5use fusionamm_macros::wasm_expose;
6#[cfg(feature = "wasm")]
7use serde::Serialize;
8#[cfg(feature = "wasm")]
9use serde_wasm_bindgen::Serializer;
10#[cfg(feature = "wasm")]
11use wasm_bindgen::prelude::wasm_bindgen;
12#[cfg(feature = "wasm")]
13use wasm_bindgen::JsValue;
14
15use crate::{
16    CoreError, HUNDRED_PERCENT, INVALID_ARGUMENTS, JUPITER_QUOTE_REQUEST_ERROR, JUPITER_SWAP_INSTRUCTIONS_REQUEST_ERROR, TICK_ARRAYS_NOT_PROVIDED,
17    TOKEN_A, TOKEN_B,
18};
19use fusionamm_core::{
20    sqrt_price_to_price, swap_quote_by_input_token, swap_quote_by_output_token, try_get_max_amount_with_slippage_tolerance,
21    try_get_min_amount_with_slippage_tolerance, try_mul_div, FusionPoolFacade, TickArrays, TokenPair,
22};
23use jup_ag::{PrioritizationFeeLamports, QuoteConfig, SwapMode, SwapRequest};
24use libm::{ceil, round};
25use solana_instruction::AccountMeta;
26use solana_pubkey::Pubkey;
27
28pub const DEFAULT_SLIPPAGE_TOLERANCE_BPS: u16 = 100;
29
30#[cfg_attr(feature = "wasm", wasm_expose)]
31pub struct SwapInstruction {
32    pub data: Vec<u8>,
33    pub accounts: Vec<AccountMeta>,
34    pub address_lookup_table_addresses: Vec<Pubkey>,
35}
36
37#[cfg_attr(feature = "wasm", wasm_expose)]
38pub struct IncreaseSpotPositionQuoteResult {
39    /** Required collateral amount */
40    pub collateral: u64,
41    /** Required amount to borrow */
42    pub borrow: u64,
43    /** Estimated position size in the position token. */
44    pub estimated_amount: u64,
45    /** Swap input amount. */
46    pub swap_input_amount: u64,
47    /** Minimum swap output amount according to the provided slippage. */
48    pub min_swap_output_amount: u64,
49    /** Protocol fee in token A */
50    pub protocol_fee_a: u64,
51    /** Protocol fee in token B */
52    pub protocol_fee_b: u64,
53    /** Price impact in percents (100% = 1.0) */
54    pub price_impact: f64,
55    /** Optional jupiter swap instruction data and accounts. */
56    pub jupiter_swap_ix: Option<SwapInstruction>,
57}
58
59/// Spot position increase quote
60///
61/// # Parameters
62/// - `increase_amount`: Position total size in the collateral_token.
63/// - `collateral_token`: Collateral token.
64/// - `position_token`: Token of the position.
65/// - `leverage`: Leverage (1.0 or higher).
66/// - `slippage_tolerance_bps`: An optional slippage tolerance in basis points. Defaults to the global slippage tolerance if not provided.
67/// - `protocol_fee_rate`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
68/// - `protocol_fee_rate_on_collateral`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
69/// - `mint_a`: Token A mint address
70/// - `mint_b`: Token B mint address
71/// - `fusion_pool`: Fusion pool.
72/// - `tick_arrays`: Optional five tick arrays around the current pool price. If not provided, the quote will be calculated using the Jupiter Aggregator.
73///
74/// # Returns
75/// - `IncreaseSpotPositionQuoteResult`: quote result
76pub async fn get_increase_spot_position_quote(
77    increase_amount: u64,
78    collateral_token: u8,
79    position_token: u8,
80    leverage: f64,
81    slippage_tolerance_bps: Option<u16>,
82    protocol_fee_rate: u16,
83    protocol_fee_rate_on_collateral: u16,
84    mint_a: Pubkey,
85    mint_b: Pubkey,
86    fusion_pool: FusionPoolFacade,
87    tick_arrays: Option<TickArrays>,
88) -> Result<IncreaseSpotPositionQuoteResult, CoreError> {
89    if collateral_token > TOKEN_B || position_token > TOKEN_B {
90        return Err(INVALID_ARGUMENTS.into());
91    }
92
93    if leverage < 1.0 {
94        return Err(INVALID_ARGUMENTS.into());
95    }
96
97    let borrow: u64;
98    let mut collateral: u64;
99    let mut estimated_amount: u64 = 0;
100    let mut swap_input_amount: u64;
101    let mut min_swap_output_amount: u64 = 0;
102    let mut price_impact: f64 = 0.0;
103    let mut jupiter_swap_ix: Option<SwapInstruction> = None;
104
105    let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
106    let slippage_tolerance_bps = slippage_tolerance_bps.unwrap_or(DEFAULT_SLIPPAGE_TOLERANCE_BPS);
107
108    let borrowed_token = if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A };
109    let swap_input_token_is_a = borrowed_token == TOKEN_A;
110
111    if borrowed_token == collateral_token {
112        borrow = ceil((increase_amount as f64 * (leverage - 1.0)) / leverage) as u64;
113        collateral = increase_amount - apply_swap_fee(apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?, fusion_pool.fee_rate, false)?;
114        collateral = reverse_apply_swap_fee(collateral, fusion_pool.fee_rate, false)?;
115        collateral = reverse_apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
116
117        swap_input_amount = collateral + borrow;
118    } else {
119        let position_to_borrowed_token_price = if collateral_token == TOKEN_A { price } else { 1.0 / price };
120        let borrow_in_position_token = ceil((increase_amount as f64 * (leverage - 1.0)) / leverage);
121
122        borrow = ceil(borrow_in_position_token * position_to_borrowed_token_price) as u64;
123
124        let borrow_in_position_token_with_fees_applied =
125            apply_swap_fee(apply_tuna_protocol_fee(borrow_in_position_token as u64, protocol_fee_rate, false)?, fusion_pool.fee_rate, false)?;
126
127        collateral = increase_amount - borrow_in_position_token_with_fees_applied;
128        collateral = reverse_apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
129
130        swap_input_amount = borrow;
131    }
132
133    let protocol_fee =
134        calculate_tuna_protocol_fee(collateral_token, borrowed_token, collateral, borrow, protocol_fee_rate_on_collateral, protocol_fee_rate);
135
136    swap_input_amount -= if swap_input_token_is_a { protocol_fee.a } else { protocol_fee.b };
137
138    if position_token == collateral_token {
139        estimated_amount = collateral - if collateral_token == TOKEN_A { protocol_fee.a } else { protocol_fee.b };
140    }
141
142    if swap_input_amount > 0 {
143        if let Some(tick_arrays) = tick_arrays {
144            let quote = swap_quote_by_input_token(swap_input_amount, swap_input_token_is_a, 0, fusion_pool, tick_arrays, None, None)?;
145            estimated_amount += quote.token_est_out;
146            min_swap_output_amount = try_get_min_amount_with_slippage_tolerance(quote.token_est_out, slippage_tolerance_bps)?;
147            let new_price = sqrt_price_to_price(quote.next_sqrt_price.into(), 1, 1);
148            price_impact = (new_price / price - 1.0).abs();
149        } else {
150            let (input_mint, output_mint) = if swap_input_token_is_a { (mint_a, mint_b) } else { (mint_b, mint_a) };
151
152            let quote = jupiter_swap_quote(input_mint, output_mint, swap_input_amount, Some(slippage_tolerance_bps as u64)).await?;
153
154            estimated_amount += quote.out_amount;
155            price_impact = quote.price_impact_pct;
156            min_swap_output_amount = quote.other_amount_threshold;
157            jupiter_swap_ix = Some(SwapInstruction {
158                data: quote.instruction.data,
159                accounts: quote.instruction.accounts,
160                address_lookup_table_addresses: quote.instruction.address_lookup_table_addresses,
161            });
162        }
163    }
164
165    Ok(IncreaseSpotPositionQuoteResult {
166        collateral,
167        borrow,
168        estimated_amount,
169        swap_input_amount,
170        min_swap_output_amount,
171        protocol_fee_a: protocol_fee.a,
172        protocol_fee_b: protocol_fee.b,
173        price_impact,
174        jupiter_swap_ix,
175    })
176}
177
178/// Spot position increase quote
179///
180/// # Parameters
181/// - `increase_amount`: Position total size in the collateral_token.
182/// - `collateral_token`: Collateral token.
183/// - `position_token`: Token of the position.
184/// - `leverage`: Leverage (1.0 or higher).
185/// - `slippage_tolerance_bps`: An optional slippage tolerance in basis points. Defaults to the global slippage tolerance if not provided.
186/// - `protocol_fee_rate`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
187/// - `protocol_fee_rate_on_collateral`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
188/// - `mint_a`: Token A mint address
189/// - `mint_b`: Token B mint address
190/// - `fusion_pool`: Fusion pool.
191/// - `tick_arrays`: Optional five tick arrays around the current pool price. If not provided, the quote will be calculated using the Jupiter Aggregator.
192///
193/// # Returns
194/// - `IncreaseSpotPositionQuoteResult`: quote result
195#[cfg(feature = "wasm")]
196#[wasm_bindgen(js_name = "getIncreaseSpotPositionQuote", skip_jsdoc)]
197pub async fn wasm_get_increase_spot_position_quote(
198    increase_amount: u64,
199    collateral_token: u8,
200    position_token: u8,
201    leverage: f64,
202    slippage_tolerance_bps: Option<u16>,
203    protocol_fee_rate: u16,
204    protocol_fee_rate_on_collateral: u16,
205    mint_a: Pubkey,
206    mint_b: Pubkey,
207    fusion_pool: FusionPoolFacade,
208    tick_arrays: Option<TickArrays>,
209) -> Result<JsValue, JsValue> {
210    let result = get_increase_spot_position_quote(
211        increase_amount,
212        collateral_token,
213        position_token,
214        leverage,
215        slippage_tolerance_bps,
216        protocol_fee_rate,
217        protocol_fee_rate_on_collateral,
218        mint_a,
219        mint_b,
220        fusion_pool,
221        tick_arrays,
222    )
223    .await
224    .map_err(|e| JsValue::from_str(e))?;
225
226    let serializer = Serializer::new().serialize_maps_as_objects(true);
227    let js_value = result.serialize(&serializer).unwrap();
228
229    Ok(js_value)
230}
231
232#[cfg_attr(feature = "wasm", wasm_expose)]
233pub struct DecreaseSpotPositionQuoteResult {
234    /** Position decrease percentage */
235    pub decrease_percent: u32,
236    /** Collateral token of the new position */
237    pub collateral_token: u8,
238    /** Token of the new position */
239    pub position_token: u8,
240    /** Required additional collateral amount */
241    pub collateral: u64,
242    /** Required amount to borrow */
243    pub borrow: u64,
244    /** The maximum acceptable swap input amount for position decrease according to the provided slippage
245     * (if collateral_token == position_token) OR the minimum swap output amount (if collateral_token != position_token).
246     */
247    pub decrease_acceptable_swap_amount: u64,
248    /** The minimum swap output amount for position increase according to the provided slippage. */
249    pub increase_min_swap_output_amount: u64,
250    /** Estimated total amount of the new position */
251    pub estimated_amount: u64,
252    /** Protocol fee in token A */
253    pub protocol_fee_a: u64,
254    /** Protocol fee in token B */
255    pub protocol_fee_b: u64,
256    /** Price impact in percents (100% = 1.0) */
257    pub price_impact: f64,
258    /** Optional jupiter swap instruction data and accounts. */
259    pub jupiter_swap_ix: Option<SwapInstruction>,
260}
261
262/// Spot position decrease quote
263///
264/// # Parameters
265/// - `decrease_amount`: Position total decrease size in the collateral_token.
266/// - `collateral_token`: Collateral token.
267/// - `leverage`: Leverage (1.0 or higher).
268/// - `reduce_only`: Only allow reducing the existing position.
269/// - `slippage_tolerance_bps`: An optional slippage tolerance in basis points. Defaults to the global slippage tolerance if not provided.
270/// - `position_token`: Token of the existing position.
271/// - `position_amount`: Existing position amount in the position_token.
272/// - `position_debt`: Existing position debt in the token opposite to the position_token.
273/// - `protocol_fee_rate`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
274/// - `protocol_fee_rate_on_collateral`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
275/// - `fusion_pool`: Fusion pool.
276/// - `tick_arrays`: Optional five tick arrays around the current pool price. If not provided, the quote will be calculated using the Jupiter Aggregator.
277///
278/// # Returns
279/// - `DecreaseSpotPositionQuoteResult`: quote result
280pub async fn get_decrease_spot_position_quote(
281    decrease_amount: u64,
282    collateral_token: u8,
283    leverage: f64,
284    reduce_only: bool,
285    slippage_tolerance_bps: Option<u16>,
286    position_token: u8,
287    position_amount: u64,
288    position_debt: u64,
289    protocol_fee_rate: u16,
290    protocol_fee_rate_on_collateral: u16,
291    mint_a: Pubkey,
292    mint_b: Pubkey,
293    fusion_pool: FusionPoolFacade,
294    tick_arrays: Option<TickArrays>,
295) -> Result<DecreaseSpotPositionQuoteResult, CoreError> {
296    if collateral_token > TOKEN_B || position_token > TOKEN_B {
297        return Err(INVALID_ARGUMENTS.into());
298    }
299
300    if leverage < 1.0 {
301        return Err(INVALID_ARGUMENTS.into());
302    }
303
304    let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
305    let position_to_borrowed_token_price = if position_token == TOKEN_A { price } else { 1.0 / price };
306    let borrowed_token = if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A };
307    let slippage_tolerance_bps = slippage_tolerance_bps.unwrap_or(DEFAULT_SLIPPAGE_TOLERANCE_BPS);
308
309    let mut collateral = 0;
310    let mut borrow = 0;
311    let estimated_amount: u64;
312    let mut new_position_token = position_token;
313    let mut increase_min_swap_output_amount: u64 = 0;
314    let mut decrease_acceptable_swap_amount: u64 = 0;
315    let mut price_impact = 0.0;
316    let mut jupiter_swap_ix: Option<SwapInstruction> = None;
317
318    let mut decrease_amount_in_position_token = if collateral_token == position_token {
319        decrease_amount
320    } else {
321        round(decrease_amount as f64 / position_to_borrowed_token_price) as u64
322    };
323
324    if reduce_only && decrease_amount_in_position_token > position_amount {
325        decrease_amount_in_position_token = position_amount;
326    }
327
328    let decrease_percent = ((decrease_amount_in_position_token * HUNDRED_PERCENT as u64 / position_amount) as u32).min(HUNDRED_PERCENT);
329
330    if decrease_amount_in_position_token <= position_amount {
331        estimated_amount = position_amount * (HUNDRED_PERCENT - decrease_percent) as u64 / HUNDRED_PERCENT as u64;
332
333        if let Some(tick_arrays) = tick_arrays {
334            let mut next_sqrt_price = fusion_pool.sqrt_price;
335
336            if collateral_token == position_token {
337                if position_debt > 0 {
338                    let amount_out = position_debt * decrease_percent as u64 / HUNDRED_PERCENT as u64;
339                    let swap = swap_quote_by_output_token(amount_out, borrowed_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
340                    next_sqrt_price = swap.next_sqrt_price;
341                    decrease_acceptable_swap_amount = try_get_max_amount_with_slippage_tolerance(swap.token_est_in, slippage_tolerance_bps)?;
342                }
343            } else {
344                let amount_in = position_amount - estimated_amount;
345                let swap = swap_quote_by_input_token(amount_in, position_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
346                next_sqrt_price = swap.next_sqrt_price;
347                decrease_acceptable_swap_amount = try_get_min_amount_with_slippage_tolerance(swap.token_est_out, slippage_tolerance_bps)?;
348            }
349
350            let new_price = sqrt_price_to_price(next_sqrt_price.into(), 1, 1);
351            price_impact = (new_price / price - 1.0).abs();
352        } else {
353            let (input_mint, output_mint) = if position_token == TOKEN_A { (mint_a, mint_b) } else { (mint_b, mint_a) };
354
355            let amount_in = if collateral_token == position_token {
356                if position_debt > 0 {
357                    let mut amount_in = if position_token == TOKEN_A {
358                        (position_debt as f64 / price) as u64
359                    } else {
360                        (position_debt as f64 * price) as u64
361                    };
362                    amount_in = try_get_max_amount_with_slippage_tolerance(amount_in, slippage_tolerance_bps)?;
363                    amount_in.min(position_amount)
364                } else {
365                    0
366                }
367            } else {
368                position_amount - estimated_amount
369            };
370
371            if amount_in > 0 {
372                let quote = jupiter_swap_quote(input_mint, output_mint, amount_in, Some(slippage_tolerance_bps as u64)).await?;
373
374                price_impact = quote.price_impact_pct;
375                decrease_acceptable_swap_amount = quote.other_amount_threshold;
376                jupiter_swap_ix = Some(SwapInstruction {
377                    data: quote.instruction.data,
378                    accounts: quote.instruction.accounts,
379                    address_lookup_table_addresses: quote.instruction.address_lookup_table_addresses,
380                });
381            }
382        }
383    } else {
384        let mut next_sqrt_price = fusion_pool.sqrt_price;
385        new_position_token = if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A };
386        let increase_amount = decrease_amount_in_position_token - position_amount;
387        let mut increase_swap_output_amount: u64 = 0;
388
389        let tick_arrays = tick_arrays.ok_or(TICK_ARRAYS_NOT_PROVIDED)?;
390
391        if position_token == collateral_token {
392            // Example:
393            // collateral_token: A
394            // position_token: A
395            // position_debt: B
396            // new_position_token: B
397            // newBorrowedToken: A
398
399            // A
400            borrow = ((increase_amount as f64 * (leverage - 1.0)) / leverage) as u64;
401            let borrow_with_fees_applied = apply_swap_fee(apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?, fusion_pool.fee_rate, false)?;
402
403            collateral = increase_amount - borrow_with_fees_applied;
404
405            // Decrease swap: A->B
406            let mut amount_in = 0;
407            if position_debt > 0 {
408                let swap =
409                    swap_quote_by_output_token(position_debt, borrowed_token == TOKEN_A, 0, fusion_pool, tick_arrays.clone().into(), None, None)?;
410                amount_in = swap.token_est_in;
411                decrease_acceptable_swap_amount = try_get_max_amount_with_slippage_tolerance(amount_in, slippage_tolerance_bps)?;
412            }
413
414            // Decrease+Increase swap: A->B
415            amount_in += collateral + apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?;
416            if amount_in > 0 {
417                let swap = swap_quote_by_input_token(amount_in, position_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
418                next_sqrt_price = swap.next_sqrt_price;
419                increase_swap_output_amount = swap.token_est_out.saturating_sub(position_debt);
420            }
421
422            // B
423            estimated_amount = increase_swap_output_amount;
424        } else {
425            // Example:
426            // collateral_token: B
427            // position_token: A
428            // new_position_token: B
429            // newBorrowedToken: A
430
431            // Decrease swap A->B
432            let swap = swap_quote_by_input_token(position_amount, position_token == TOKEN_A, 0, fusion_pool, tick_arrays.clone().into(), None, None)?;
433            let decrease_amount_out = swap.token_est_out;
434            decrease_acceptable_swap_amount = try_get_min_amount_with_slippage_tolerance(swap.token_est_out, slippage_tolerance_bps)?;
435
436            borrow = ((increase_amount as f64 * (leverage - 1.0)) / leverage) as u64;
437            let borrow_with_fees_applied = apply_swap_fee(apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?, fusion_pool.fee_rate, false)?;
438
439            collateral = increase_amount - borrow_with_fees_applied;
440            collateral = ceil(collateral as f64 * position_to_borrowed_token_price) as u64;
441
442            // Decrease+Increase swap A->B
443            let amount_in = position_amount + apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?;
444            if amount_in > 0 {
445                let swap = swap_quote_by_input_token(amount_in, position_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
446                next_sqrt_price = swap.next_sqrt_price;
447                increase_swap_output_amount = swap.token_est_out.saturating_sub(decrease_amount_out);
448            }
449
450            // B
451            estimated_amount = increase_swap_output_amount + collateral;
452        }
453
454        collateral = reverse_apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
455
456        let new_price = sqrt_price_to_price(next_sqrt_price.into(), 1, 1);
457        price_impact = (new_price / price - 1.0).abs();
458
459        increase_min_swap_output_amount = try_get_min_amount_with_slippage_tolerance(increase_swap_output_amount, slippage_tolerance_bps)?;
460    }
461
462    let protocol_fee =
463        calculate_tuna_protocol_fee(collateral_token, borrowed_token, collateral, borrow, protocol_fee_rate_on_collateral, protocol_fee_rate);
464
465    Ok(DecreaseSpotPositionQuoteResult {
466        decrease_percent,
467        collateral_token,
468        position_token: new_position_token,
469        collateral,
470        borrow,
471        decrease_acceptable_swap_amount,
472        increase_min_swap_output_amount,
473        estimated_amount,
474        protocol_fee_a: protocol_fee.a,
475        protocol_fee_b: protocol_fee.b,
476        price_impact,
477        jupiter_swap_ix,
478    })
479}
480
481/// Spot position decrease quote
482///
483/// # Parameters
484/// - `decrease_amount`: Position total decrease size in the collateral_token.
485/// - `collateral_token`: Collateral token.
486/// - `leverage`: Leverage (1.0 or higher).
487/// - `reduce_only`: Only allow reducing the existing position.
488/// - `slippage_tolerance_bps`: An optional slippage tolerance in basis points. Defaults to the global slippage tolerance if not provided.
489/// - `position_token`: Token of the existing position.
490/// - `position_amount`: Existing position amount in the position_token.
491/// - `position_debt`: Existing position debt in the token opposite to the position_token.
492/// - `protocol_fee_rate`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
493/// - `protocol_fee_rate_on_collateral`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
494/// - `fusion_pool`: Fusion pool.
495/// - `tick_arrays`: Optional five tick arrays around the current pool price. If not provided, the quote will be calculated using the Jupiter Aggregator.
496///
497/// # Returns
498/// - `DecreaseSpotPositionQuoteResult`: quote result
499#[cfg(feature = "wasm")]
500#[wasm_bindgen(js_name = "getDecreaseSpotPositionQuote", skip_jsdoc)]
501pub async fn wasm_get_decrease_spot_position_quote(
502    decrease_amount: u64,
503    collateral_token: u8,
504    leverage: f64,
505    reduce_only: bool,
506    slippage_tolerance_bps: Option<u16>,
507    position_token: u8,
508    position_amount: u64,
509    position_debt: u64,
510    protocol_fee_rate: u16,
511    protocol_fee_rate_on_collateral: u16,
512    mint_a: Pubkey,
513    mint_b: Pubkey,
514    fusion_pool: FusionPoolFacade,
515    tick_arrays: Option<TickArrays>,
516) -> Result<JsValue, JsValue> {
517    let result = get_decrease_spot_position_quote(
518        decrease_amount,
519        collateral_token,
520        leverage,
521        reduce_only,
522        slippage_tolerance_bps,
523        position_token,
524        position_amount,
525        position_debt,
526        protocol_fee_rate,
527        protocol_fee_rate_on_collateral,
528        mint_a,
529        mint_b,
530        fusion_pool,
531        tick_arrays,
532    )
533    .await
534    .map_err(|e| JsValue::from_str(e))?;
535
536    let serializer = Serializer::new().serialize_maps_as_objects(true);
537    let js_value = result.serialize(&serializer).unwrap();
538
539    Ok(js_value)
540}
541
542/// Returns the liquidation price
543///
544/// # Parameters
545/// - `position_token`: Token of the position
546/// - `amount`: Position total size (decimal)
547/// - `debt`: Position total debt (decimal)
548/// - `liquidation_threshold`: Liquidation threshold of the market (decimal)
549///
550/// # Returns
551/// - `f64`: Decimal liquidation price
552#[cfg_attr(feature = "wasm", wasm_expose)]
553pub fn get_liquidation_price(position_token: u8, amount: f64, debt: f64, liquidation_threshold: f64) -> Result<f64, CoreError> {
554    if debt < 0.0 || amount < 0.0 {
555        return Err(INVALID_ARGUMENTS);
556    }
557
558    if liquidation_threshold <= 0.0 || liquidation_threshold >= 1.0 {
559        return Err(INVALID_ARGUMENTS);
560    }
561
562    if debt == 0.0 || amount == 0.0 {
563        return Ok(0.0);
564    }
565
566    if position_token == TOKEN_A {
567        Ok(debt / (amount * liquidation_threshold))
568    } else {
569        Ok((amount * liquidation_threshold) / debt)
570    }
571}
572
573/// Calculates the maximum tradable amount in the collateral token.
574///
575/// # Parameters
576/// - `collateral_token`: Collateral token.
577/// - `available_balance`: Available wallet balance in the collateral_token.
578/// - `leverage`: Leverage (1.0 or higher).
579/// - `new_position_token`: Token of the new position.
580/// - `position_token`: Token of the existing position. Should be set to new_position_token if position_amount is zero.
581/// - `position_amount`: Existing position amount in the position_token.
582/// - `position_debt`: Existing position debt in the token opposite to the position_token.
583/// - `reduce_only`: Only allow reducing the existing position.///
584/// - `protocol_fee_rate`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
585/// - `protocol_fee_rate_on_collateral`: Protocol fee rate from a market account represented as hundredths of a basis point (0.01% = 100).
586/// - `fusion_pool`: Fusion pool.
587/// - `tick_arrays`: Five tick arrays around the current pool price.
588///
589/// # Returns
590/// - `u64`: the maximum tradable amount
591#[cfg_attr(feature = "wasm", wasm_expose)]
592pub fn get_tradable_amount(
593    collateral_token: u8,
594    available_balance: u64,
595    leverage: f64,
596    new_position_token: u8,
597    reduce_only: bool,
598    position_token: u8,
599    position_amount: u64,
600    position_debt: u64,
601    protocol_fee_rate: u16,
602    protocol_fee_rate_on_collateral: u16,
603    fusion_pool: FusionPoolFacade,
604    tick_arrays: Option<TickArrays>,
605) -> Result<u64, CoreError> {
606    if collateral_token > TOKEN_B || position_token > TOKEN_B {
607        return Err(INVALID_ARGUMENTS.into());
608    }
609
610    if leverage < 1.0 {
611        return Err(INVALID_ARGUMENTS.into());
612    }
613
614    if position_amount == 0 && new_position_token != position_token {
615        return Err(INVALID_ARGUMENTS.into());
616    }
617
618    let tick_arrays = tick_arrays.ok_or(INVALID_ARGUMENTS)?;
619
620    // T = Câ‹…Fcâ‹…Fs + Bâ‹…Fbâ‹…Fs, where: Fc/Fb/Fs - collateral/borrow/swap fee multiplier
621    // B = Tâ‹…(L - 1) / L
622    // => T = Câ‹…Fcâ‹…Fs / (1 - Fbâ‹…Fsâ‹…(L - 1) / L)
623    let add_leverage = |collateral: u64| -> Result<u64, CoreError> {
624        let mut collateral = apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
625        if collateral_token != new_position_token {
626            collateral = apply_swap_fee(collateral, fusion_pool.fee_rate, false)?;
627        }
628
629        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);
630        let total = (collateral as f64 / (1.0 - (fee_multiplier * (leverage - 1.0)) / leverage)) as u64;
631        Ok(total)
632    };
633
634    let available_to_trade = if new_position_token == position_token {
635        add_leverage(available_balance)?
636    } else {
637        let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
638        let position_to_opposite_token_price = if position_token == TOKEN_A { price } else { 1.0 / price };
639
640        if reduce_only {
641            if collateral_token == position_token {
642                position_amount
643            } else {
644                round(position_amount as f64 * position_to_opposite_token_price) as u64
645            }
646        } else {
647            let position_amount_in_collateral_token = if collateral_token == position_token {
648                position_amount
649            } else {
650                round(position_amount as f64 * position_to_opposite_token_price) as u64
651            };
652
653            let position_collateral = if collateral_token == position_token {
654                let swap_in = if position_debt > 0 {
655                    swap_quote_by_output_token(position_debt, position_token == TOKEN_B, 0, fusion_pool, tick_arrays, None, None)?.token_est_in
656                } else {
657                    0
658                };
659                position_amount - swap_in
660            } else {
661                if position_amount > 0 {
662                    let swap_quote = swap_quote_by_input_token(position_amount, position_token == TOKEN_A, 0, fusion_pool, tick_arrays, None, None)?;
663                    swap_quote.token_est_out - position_debt
664                } else {
665                    0
666                }
667            };
668
669            // Add the refunded collateral to the available balance
670            position_amount_in_collateral_token + add_leverage(available_balance + position_collateral)?
671        }
672    };
673
674    Ok(available_to_trade)
675}
676
677pub fn apply_tuna_protocol_fee(amount: u64, protocol_fee_rate: u16, round_up: bool) -> Result<u64, CoreError> {
678    try_mul_div(amount, HUNDRED_PERCENT as u128 - protocol_fee_rate as u128, HUNDRED_PERCENT as u128, round_up)
679}
680
681pub fn reverse_apply_tuna_protocol_fee(amount: u64, protocol_fee_rate: u16, round_up: bool) -> Result<u64, CoreError> {
682    try_mul_div(amount, HUNDRED_PERCENT as u128, HUNDRED_PERCENT as u128 - protocol_fee_rate as u128, round_up)
683}
684
685pub fn apply_swap_fee(amount: u64, fee_rate: u16, round_up: bool) -> Result<u64, CoreError> {
686    try_mul_div(amount, 1_000_000 - fee_rate as u128, 1_000_000, round_up)
687}
688
689pub fn reverse_apply_swap_fee(amount: u64, fee_rate: u16, round_up: bool) -> Result<u64, CoreError> {
690    try_mul_div(amount, 1_000_000, 1_000_000 - fee_rate as u128, round_up)
691}
692
693#[cfg_attr(feature = "wasm", wasm_expose)]
694pub fn calculate_tuna_protocol_fee(
695    collateral_token: u8,
696    borrowed_token: u8,
697    collateral: u64,
698    borrow: u64,
699    protocol_fee_rate_on_collateral: u16,
700    protocol_fee_rate: u16,
701) -> TokenPair {
702    let collateral_a = if collateral_token == TOKEN_A { collateral } else { 0 };
703    let collateral_b = if collateral_token == TOKEN_B { collateral } else { 0 };
704    let borrow_a = if borrowed_token == TOKEN_A { borrow } else { 0 };
705    let borrow_b = if borrowed_token == TOKEN_B { borrow } else { 0 };
706
707    let protocol_fee_a = ((collateral_a as u128 * protocol_fee_rate_on_collateral as u128 + borrow_a as u128 * protocol_fee_rate as u128)
708        / HUNDRED_PERCENT as u128) as u64;
709    let protocol_fee_b = ((collateral_b as u128 * protocol_fee_rate_on_collateral as u128 + borrow_b as u128 * protocol_fee_rate as u128)
710        / HUNDRED_PERCENT as u128) as u64;
711
712    TokenPair {
713        a: protocol_fee_a,
714        b: protocol_fee_b,
715    }
716}
717
718struct JupiterSwapResult {
719    pub instruction: SwapInstruction,
720    pub out_amount: u64,
721    pub other_amount_threshold: u64,
722    pub price_impact_pct: f64,
723}
724
725async fn jupiter_swap_quote(input_mint: Pubkey, output_mint: Pubkey, amount: u64, slippage_bps: Option<u64>) -> Result<JupiterSwapResult, CoreError> {
726    let quote_config = QuoteConfig {
727        slippage_bps,
728        swap_mode: Some(SwapMode::ExactIn),
729        dexes: None,
730        exclude_dexes: None,
731        only_direct_routes: false,
732        as_legacy_transaction: None,
733        platform_fee_bps: None,
734        max_accounts: None,
735    };
736
737    let quote = jup_ag::quote(input_mint, output_mint, amount, quote_config)
738        .await
739        .map_err(|_| JUPITER_QUOTE_REQUEST_ERROR)?;
740
741    #[allow(deprecated)]
742    let swap_request = SwapRequest {
743        user_public_key: Default::default(),
744        wrap_and_unwrap_sol: None,
745        use_shared_accounts: Some(true),
746        fee_account: None,
747        compute_unit_price_micro_lamports: None,
748        prioritization_fee_lamports: PrioritizationFeeLamports::Auto,
749        as_legacy_transaction: None,
750        use_token_ledger: None,
751        destination_token_account: None,
752        quote_response: quote.clone(),
753    };
754
755    let swap_response = jup_ag::swap_instructions(swap_request)
756        .await
757        .map_err(|_| JUPITER_SWAP_INSTRUCTIONS_REQUEST_ERROR)?;
758
759    Ok(JupiterSwapResult {
760        instruction: SwapInstruction {
761            data: swap_response.swap_instruction.data,
762            accounts: swap_response.swap_instruction.accounts,
763            address_lookup_table_addresses: swap_response.address_lookup_table_addresses,
764        },
765        out_amount: quote.out_amount,
766        other_amount_threshold: quote.other_amount_threshold,
767        price_impact_pct: quote.price_impact_pct,
768    })
769}
770
771#[cfg(all(test, not(feature = "wasm")))]
772mod tests {
773    use super::*;
774    use crate::assert_approx_eq;
775    use fusionamm_core::{
776        get_tick_array_start_tick_index, price_to_sqrt_price, sqrt_price_to_tick_index, TickArrayFacade, TickFacade, TICK_ARRAY_SIZE,
777    };
778    use solana_pubkey::pubkey;
779
780    const NATIVE_MINT: Pubkey = pubkey!("So11111111111111111111111111111111111111112");
781    const TUNA_MINT: Pubkey = pubkey!("TUNAfXDZEdQizTMTh3uEvNvYqJmqFHZbEJt8joP4cyx");
782
783    fn test_fusion_pool(sqrt_price: u128) -> FusionPoolFacade {
784        let tick_current_index = sqrt_price_to_tick_index(sqrt_price);
785        FusionPoolFacade {
786            tick_current_index,
787            fee_rate: 3000,
788            liquidity: 10000000000000,
789            sqrt_price,
790            tick_spacing: 2,
791            ..FusionPoolFacade::default()
792        }
793    }
794
795    fn test_tick(liquidity_net: i128) -> TickFacade {
796        TickFacade {
797            initialized: true,
798            liquidity_net,
799            ..TickFacade::default()
800        }
801    }
802
803    fn test_tick_array(start_tick_index: i32) -> TickArrayFacade {
804        TickArrayFacade {
805            start_tick_index,
806            ticks: [test_tick(0); TICK_ARRAY_SIZE],
807        }
808    }
809
810    fn test_tick_arrays(fusion_pool: FusionPoolFacade) -> TickArrays {
811        let tick_spacing = fusion_pool.tick_spacing;
812        let tick_current_index = sqrt_price_to_tick_index(fusion_pool.sqrt_price);
813        let tick_array_start_index = get_tick_array_start_tick_index(tick_current_index, tick_spacing);
814
815        [
816            test_tick_array(tick_array_start_index),
817            test_tick_array(tick_array_start_index + TICK_ARRAY_SIZE as i32 * tick_spacing as i32),
818            test_tick_array(tick_array_start_index + TICK_ARRAY_SIZE as i32 * tick_spacing as i32 * 2),
819            test_tick_array(tick_array_start_index - TICK_ARRAY_SIZE as i32 * tick_spacing as i32),
820            test_tick_array(tick_array_start_index - TICK_ARRAY_SIZE as i32 * tick_spacing as i32 * 2),
821        ]
822        .into()
823    }
824
825    #[test]
826    fn test_get_liquidation_price() {
827        assert_eq!(get_liquidation_price(TOKEN_A, 5.0, 0.0, 0.85), Ok(0.0));
828        assert_eq!(get_liquidation_price(TOKEN_A, 0.0, 5.0, 0.85), Ok(0.0));
829        assert_eq!(get_liquidation_price(TOKEN_A, 5.0, 800.0, 0.85), Ok(188.23529411764707));
830        assert_eq!(get_liquidation_price(TOKEN_B, 1000.0, 4.0, 0.85), Ok(212.5));
831    }
832
833    #[tokio::test]
834    async fn increase_long_position_providing_token_a() {
835        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
836        let fusion_pool = test_fusion_pool(sqrt_price);
837
838        let quote = get_increase_spot_position_quote(
839            5_000_000_000,
840            TOKEN_A,
841            TOKEN_A,
842            5.0,
843            Some(0),
844            (HUNDRED_PERCENT / 100) as u16,
845            (HUNDRED_PERCENT / 200) as u16,
846            NATIVE_MINT,
847            TUNA_MINT,
848            fusion_pool,
849            Some(test_tick_arrays(fusion_pool)),
850        )
851        .await
852        .unwrap();
853
854        assert_eq!(quote.collateral, 1057165829);
855        assert_eq!(quote.borrow, 800000000);
856        assert_eq!(quote.min_swap_output_amount, 3_947_423_011);
857        assert_eq!(quote.estimated_amount, 4_999_303_011);
858        assert_eq!(quote.protocol_fee_a, 5285829);
859        assert_eq!(quote.protocol_fee_b, 8000000);
860        assert_eq!(quote.price_impact, 0.00035316176257027543);
861        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);
862    }
863
864    #[tokio::test]
865    async fn increase_long_position_providing_token_b() {
866        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
867        let fusion_pool = test_fusion_pool(sqrt_price);
868
869        let quote = get_increase_spot_position_quote(
870            5000_000_000,
871            TOKEN_B,
872            TOKEN_A,
873            5.0,
874            Some(0),
875            (HUNDRED_PERCENT / 100) as u16,
876            (HUNDRED_PERCENT / 200) as u16,
877            NATIVE_MINT,
878            TUNA_MINT,
879            fusion_pool,
880            Some(test_tick_arrays(fusion_pool)),
881        )
882        .await
883        .unwrap();
884
885        assert_eq!(quote.collateral, 1060346869);
886        assert_eq!(quote.borrow, 4000000000);
887        assert_eq!(quote.estimated_amount, 24_972_080_293);
888        assert_eq!(quote.protocol_fee_a, 0);
889        assert_eq!(quote.protocol_fee_b, 45301734);
890        assert_eq!(quote.price_impact, 0.0022373179716579372);
891        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);
892    }
893
894    #[tokio::test]
895    async fn increase_short_position_providing_a() {
896        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
897        let fusion_pool = test_fusion_pool(sqrt_price);
898
899        let quote = get_increase_spot_position_quote(
900            5_000_000_000,
901            TOKEN_A,
902            TOKEN_B,
903            5.0,
904            Some(0),
905            (HUNDRED_PERCENT / 100) as u16,
906            (HUNDRED_PERCENT / 200) as u16,
907            NATIVE_MINT,
908            TUNA_MINT,
909            fusion_pool,
910            Some(test_tick_arrays(fusion_pool)),
911        )
912        .await
913        .unwrap();
914
915        assert_eq!(quote.collateral, 1060346869);
916        assert_eq!(quote.borrow, 4000000000);
917        assert_eq!(quote.estimated_amount, 999_776_441);
918        assert_eq!(quote.protocol_fee_a, 45301734);
919        assert_eq!(quote.protocol_fee_b, 0);
920        assert_eq!(quote.price_impact, 0.0004470636400017991);
921        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);
922    }
923
924    #[tokio::test]
925    async fn increase_short_position_providing_b() {
926        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
927        let fusion_pool = test_fusion_pool(sqrt_price);
928
929        let quote = get_increase_spot_position_quote(
930            5000_000_000,
931            TOKEN_B,
932            TOKEN_B,
933            5.0,
934            Some(0),
935            (HUNDRED_PERCENT / 100) as u16,
936            (HUNDRED_PERCENT / 200) as u16,
937            NATIVE_MINT,
938            TUNA_MINT,
939            fusion_pool,
940            Some(test_tick_arrays(fusion_pool)),
941        )
942        .await
943        .unwrap();
944
945        assert_eq!(quote.collateral, 1057165829);
946        assert_eq!(quote.borrow, 20000000000);
947        assert_eq!(quote.estimated_amount, 4996_517_564);
948        assert_eq!(quote.protocol_fee_a, 200000000);
949        assert_eq!(quote.protocol_fee_b, 5285829);
950        assert_eq!(quote.price_impact, 0.0017633175413067637);
951        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);
952    }
953
954    #[tokio::test]
955    async fn increase_quote_with_slippage() {
956        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
957        let fusion_pool = test_fusion_pool(sqrt_price);
958
959        // with slippage 10%
960        let quote = get_increase_spot_position_quote(
961            200_000,
962            TOKEN_B,
963            TOKEN_A,
964            5.0,
965            Some(1000),
966            0,
967            0,
968            NATIVE_MINT,
969            TUNA_MINT,
970            fusion_pool,
971            Some(test_tick_arrays(fusion_pool)),
972        )
973        .await
974        .unwrap();
975        assert_eq!(quote.min_swap_output_amount, 899_994);
976
977        // without slippage
978        let quote = get_increase_spot_position_quote(
979            200_000,
980            TOKEN_B,
981            TOKEN_A,
982            5.0,
983            Some(0),
984            0,
985            0,
986            NATIVE_MINT,
987            TUNA_MINT,
988            fusion_pool,
989            Some(test_tick_arrays(fusion_pool)),
990        )
991        .await
992        .unwrap();
993        assert_eq!(quote.min_swap_output_amount, 999_994);
994    }
995
996    #[tokio::test]
997    async fn decrease_non_leveraged_long_position_providing_a_reduce_only() {
998        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
999        let fusion_pool = test_fusion_pool(sqrt_price);
1000
1001        let quote = get_decrease_spot_position_quote(
1002            1_000_000_000,
1003            TOKEN_A,
1004            1.0,
1005            true,
1006            Some(0),
1007            TOKEN_A,
1008            5_000_000_000, // A
1009            0,             // B
1010            (HUNDRED_PERCENT / 100) as u16,
1011            (HUNDRED_PERCENT / 200) as u16,
1012            NATIVE_MINT,
1013            TUNA_MINT,
1014            fusion_pool,
1015            Some(test_tick_arrays(fusion_pool)),
1016        )
1017        .await
1018        .unwrap();
1019
1020        assert_eq!(quote.decrease_percent, 200000);
1021        assert_eq!(quote.collateral_token, TOKEN_A);
1022        assert_eq!(quote.position_token, TOKEN_A);
1023        assert_eq!(quote.estimated_amount, 4_000_000_000);
1024        assert_eq!(quote.protocol_fee_a, 0);
1025        assert_eq!(quote.protocol_fee_b, 0);
1026        assert_eq!(quote.price_impact, 0.0);
1027        assert_eq!(quote.estimated_amount as f64 / (quote.estimated_amount as f64 - (quote.borrow as f64 / 1000.0) * 200.0), 1.0);
1028    }
1029
1030    #[tokio::test]
1031    async fn decrease_non_leveraged_long_position_providing_b_reduce_only() {
1032        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1033        let fusion_pool = test_fusion_pool(sqrt_price);
1034
1035        let quote = get_decrease_spot_position_quote(
1036            200_000_000,
1037            TOKEN_B,
1038            1.0,
1039            true,
1040            Some(0),
1041            TOKEN_A,
1042            5_000_000_000, // A
1043            0,             // B
1044            (HUNDRED_PERCENT / 100) as u16,
1045            (HUNDRED_PERCENT / 200) as u16,
1046            NATIVE_MINT,
1047            TUNA_MINT,
1048            fusion_pool,
1049            Some(test_tick_arrays(fusion_pool)),
1050        )
1051        .await
1052        .unwrap();
1053
1054        assert_eq!(quote.decrease_percent, 200000);
1055        assert_eq!(quote.collateral_token, TOKEN_B);
1056        assert_eq!(quote.position_token, TOKEN_A);
1057        assert_eq!(quote.estimated_amount, 4_000_000_000);
1058        assert_eq!(quote.protocol_fee_a, 0);
1059        assert_eq!(quote.protocol_fee_b, 0);
1060        assert_eq!(quote.price_impact, 0.00008916842709072448);
1061        assert_eq!(quote.estimated_amount as f64 / (quote.estimated_amount as f64 - (quote.borrow as f64 / 1000.0) * 200.0), 1.0);
1062    }
1063
1064    #[tokio::test]
1065    async fn decrease_long_position_providing_a_reduce_only() {
1066        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1067        let fusion_pool = test_fusion_pool(sqrt_price);
1068
1069        let quote = get_decrease_spot_position_quote(
1070            1_000_000_000,
1071            TOKEN_A,
1072            5.0,
1073            true,
1074            Some(0),
1075            TOKEN_A,
1076            5_000_000_000, // A
1077            800_000_000,   // B
1078            (HUNDRED_PERCENT / 100) as u16,
1079            (HUNDRED_PERCENT / 200) as u16,
1080            NATIVE_MINT,
1081            TUNA_MINT,
1082            fusion_pool,
1083            Some(test_tick_arrays(fusion_pool)),
1084        )
1085        .await
1086        .unwrap();
1087
1088        assert_eq!(quote.decrease_percent, 200000);
1089        assert_eq!(quote.collateral_token, TOKEN_A);
1090        assert_eq!(quote.position_token, TOKEN_A);
1091        assert_eq!(quote.collateral, 0);
1092        assert_eq!(quote.borrow, 0);
1093        assert_eq!(quote.estimated_amount, 4_000_000_000);
1094        assert_eq!(quote.protocol_fee_a, 0);
1095        assert_eq!(quote.protocol_fee_b, 0);
1096        assert_eq!(quote.price_impact, 0.00007155289528004705);
1097
1098        let quote = get_decrease_spot_position_quote(
1099            6_000_000_000,
1100            TOKEN_A,
1101            5.0,
1102            true,
1103            Some(0),
1104            TOKEN_A,
1105            5_000_000_000, // A
1106            800_000_000,   // B
1107            (HUNDRED_PERCENT / 100) as u16,
1108            (HUNDRED_PERCENT / 200) as u16,
1109            NATIVE_MINT,
1110            TUNA_MINT,
1111            fusion_pool,
1112            Some(test_tick_arrays(fusion_pool)),
1113        )
1114        .await
1115        .unwrap();
1116
1117        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1118        assert_eq!(quote.collateral, 0);
1119        assert_eq!(quote.borrow, 0);
1120        assert_eq!(quote.estimated_amount, 0);
1121    }
1122
1123    #[tokio::test]
1124    async fn decrease_long_position_providing_b_reduce_only() {
1125        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1126        let fusion_pool = test_fusion_pool(sqrt_price);
1127
1128        let quote = get_decrease_spot_position_quote(
1129            200_000_000,
1130            TOKEN_B,
1131            5.0,
1132            true,
1133            Some(0),
1134            TOKEN_A,
1135            5_000_000_000, // A
1136            800_000_000,   // B
1137            (HUNDRED_PERCENT / 100) as u16,
1138            (HUNDRED_PERCENT / 200) as u16,
1139            NATIVE_MINT,
1140            TUNA_MINT,
1141            fusion_pool,
1142            Some(test_tick_arrays(fusion_pool)),
1143        )
1144        .await
1145        .unwrap();
1146
1147        assert_eq!(quote.decrease_percent, 200000);
1148        assert_eq!(quote.collateral_token, TOKEN_B);
1149        assert_eq!(quote.position_token, TOKEN_A);
1150        assert_eq!(quote.collateral, 0);
1151        assert_eq!(quote.borrow, 0);
1152        assert_eq!(quote.estimated_amount, 4000000000);
1153        assert_eq!(quote.protocol_fee_a, 0);
1154        assert_eq!(quote.protocol_fee_b, 0);
1155        assert_eq!(quote.price_impact, 0.00008916842709072448);
1156
1157        let quote = get_decrease_spot_position_quote(
1158            1200_000_000,
1159            TOKEN_B,
1160            5.0,
1161            true,
1162            Some(0),
1163            TOKEN_A,
1164            5_000_000_000, // A
1165            800_000_000,   // B
1166            (HUNDRED_PERCENT / 100) as u16,
1167            (HUNDRED_PERCENT / 200) as u16,
1168            NATIVE_MINT,
1169            TUNA_MINT,
1170            fusion_pool,
1171            Some(test_tick_arrays(fusion_pool)),
1172        )
1173        .await
1174        .unwrap();
1175
1176        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1177        assert_eq!(quote.collateral, 0);
1178        assert_eq!(quote.borrow, 0);
1179        assert_eq!(quote.estimated_amount, 0);
1180    }
1181
1182    #[tokio::test]
1183    async fn decrease_long_position_providing_a_without_leverage() {
1184        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1185        let fusion_pool = test_fusion_pool(sqrt_price);
1186
1187        let quote = get_decrease_spot_position_quote(
1188            6_000_000_000,
1189            TOKEN_A,
1190            1.0,
1191            false,
1192            Some(100),
1193            TOKEN_A,
1194            5_000_000_000, // A
1195            800_000_000,   // B
1196            (HUNDRED_PERCENT / 100) as u16,
1197            (HUNDRED_PERCENT / 200) as u16,
1198            NATIVE_MINT,
1199            TUNA_MINT,
1200            fusion_pool,
1201            Some(test_tick_arrays(fusion_pool)),
1202        )
1203        .await
1204        .unwrap();
1205
1206        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1207        assert_eq!(quote.collateral_token, TOKEN_A);
1208        assert_eq!(quote.position_token, TOKEN_B);
1209        assert_eq!(quote.collateral, 1005025125);
1210        assert_eq!(quote.borrow, 0);
1211        assert_eq!(quote.estimated_amount, 199_319_781);
1212        assert_eq!(quote.decrease_acceptable_swap_amount, 4_052_881_482);
1213        assert_eq!(quote.increase_min_swap_output_amount, 197_326_583);
1214        assert_eq!(quote.protocol_fee_a, 5025125);
1215        assert_eq!(quote.protocol_fee_b, 0);
1216        assert_eq!(quote.price_impact, 0.00044685946117650754);
1217    }
1218
1219    #[tokio::test]
1220    async fn decrease_long_position_providing_a() {
1221        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1222        let fusion_pool = test_fusion_pool(sqrt_price);
1223
1224        let quote = get_decrease_spot_position_quote(
1225            6_000_000_000,
1226            TOKEN_A,
1227            5.0,
1228            false,
1229            Some(100),
1230            TOKEN_A,
1231            5_000_000_000, // A
1232            800_000_000,   // B
1233            (HUNDRED_PERCENT / 100) as u16,
1234            (HUNDRED_PERCENT / 200) as u16,
1235            NATIVE_MINT,
1236            TUNA_MINT,
1237            fusion_pool,
1238            Some(test_tick_arrays(fusion_pool)),
1239        )
1240        .await
1241        .unwrap();
1242
1243        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1244        assert_eq!(quote.collateral_token, TOKEN_A);
1245        assert_eq!(quote.position_token, TOKEN_B);
1246        assert_eq!(quote.collateral, 211433165);
1247        assert_eq!(quote.borrow, 800000000);
1248        assert_eq!(quote.estimated_amount, 199_793_344);
1249        assert_eq!(quote.decrease_acceptable_swap_amount, 4_052_881_482);
1250        assert_eq!(quote.increase_min_swap_output_amount, 197_795_410);
1251        assert_eq!(quote.protocol_fee_a, 1057165);
1252        assert_eq!(quote.protocol_fee_b, 8000000);
1253        assert_eq!(quote.price_impact, 0.0004470711974918773);
1254    }
1255
1256    #[tokio::test]
1257    async fn decrease_long_position_providing_b() {
1258        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1259        let fusion_pool = test_fusion_pool(sqrt_price);
1260
1261        let quote = get_decrease_spot_position_quote(
1262            1200_000_000,
1263            TOKEN_B,
1264            5.0,
1265            false,
1266            Some(0),
1267            TOKEN_A,
1268            5_000_000_000, // A
1269            800_000_000,   // B
1270            (HUNDRED_PERCENT / 100) as u16,
1271            (HUNDRED_PERCENT / 200) as u16,
1272            NATIVE_MINT,
1273            TUNA_MINT,
1274            fusion_pool,
1275            Some(test_tick_arrays(fusion_pool)),
1276        )
1277        .await
1278        .unwrap();
1279
1280        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1281        assert_eq!(quote.collateral_token, TOKEN_B);
1282        assert_eq!(quote.position_token, TOKEN_B);
1283        assert_eq!(quote.collateral, 42286633);
1284        assert_eq!(quote.borrow, 800_000_000);
1285        assert_eq!(quote.estimated_amount, 199_924_036);
1286        assert_eq!(quote.decrease_acceptable_swap_amount, 996_777_780);
1287        assert_eq!(quote.increase_min_swap_output_amount, 157_848_836);
1288        assert_eq!(quote.protocol_fee_a, 0);
1289        assert_eq!(quote.protocol_fee_b, 8211433);
1290        assert_eq!(quote.price_impact, 0.0005162980632488212);
1291    }
1292
1293    #[tokio::test]
1294    async fn decrease_non_leveraged_short_position_providing_a() {
1295        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1296        let fusion_pool = test_fusion_pool(sqrt_price);
1297
1298        let quote = get_decrease_spot_position_quote(
1299            6_000_000_000,
1300            TOKEN_A,
1301            1.0,
1302            false,
1303            Some(0),
1304            TOKEN_B,
1305            1000_000_000, // B
1306            0,            // A
1307            (HUNDRED_PERCENT / 100) as u16,
1308            (HUNDRED_PERCENT / 200) as u16,
1309            NATIVE_MINT,
1310            TUNA_MINT,
1311            fusion_pool,
1312            Some(test_tick_arrays(fusion_pool)),
1313        )
1314        .await
1315        .unwrap();
1316
1317        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1318        assert_eq!(quote.collateral_token, TOKEN_A);
1319        assert_eq!(quote.position_token, TOKEN_A);
1320        assert_eq!(quote.collateral, 1005025125);
1321        assert_eq!(quote.borrow, 0);
1322        assert_eq!(quote.estimated_amount, 1_000_000_000);
1323        assert_eq!(quote.protocol_fee_a, 5025125);
1324        assert_eq!(quote.protocol_fee_b, 0);
1325        assert_eq!(quote.price_impact, 0.00044592165429757635);
1326    }
1327
1328    #[tokio::test]
1329    async fn decrease_short_position_providing_a() {
1330        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1331        let fusion_pool = test_fusion_pool(sqrt_price);
1332
1333        let quote = get_decrease_spot_position_quote(
1334            6_000_000_000,
1335            TOKEN_A,
1336            5.0,
1337            false,
1338            Some(0),
1339            TOKEN_B,
1340            1000_000_000,  // B
1341            4_000_000_000, // A
1342            (HUNDRED_PERCENT / 100) as u16,
1343            (HUNDRED_PERCENT / 200) as u16,
1344            NATIVE_MINT,
1345            TUNA_MINT,
1346            fusion_pool,
1347            Some(test_tick_arrays(fusion_pool)),
1348        )
1349        .await
1350        .unwrap();
1351
1352        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1353        assert_eq!(quote.collateral_token, TOKEN_A);
1354        assert_eq!(quote.position_token, TOKEN_A);
1355        assert_eq!(quote.collateral, 211433165);
1356        assert_eq!(quote.borrow, 160000000);
1357        assert_eq!(quote.estimated_amount, 999_620_182);
1358        assert_eq!(quote.decrease_acceptable_swap_amount, 4_983_888_901);
1359        assert_eq!(quote.increase_min_swap_output_amount, 789_244_182);
1360        assert_eq!(quote.protocol_fee_a, 2657165);
1361        assert_eq!(quote.protocol_fee_b, 0);
1362        assert_eq!(quote.price_impact, 0.00051656476403882934);
1363    }
1364
1365    #[tokio::test]
1366    async fn decrease_short_position_providing_b() {
1367        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1368        let fusion_pool = test_fusion_pool(sqrt_price);
1369
1370        let quote = get_decrease_spot_position_quote(
1371            1_200_000_000,
1372            TOKEN_B,
1373            5.0,
1374            false,
1375            Some(0),
1376            TOKEN_B,
1377            1000_000_000,  // B
1378            4_000_000_000, // A
1379            (HUNDRED_PERCENT / 100) as u16,
1380            (HUNDRED_PERCENT / 200) as u16,
1381            NATIVE_MINT,
1382            TUNA_MINT,
1383            fusion_pool,
1384            Some(test_tick_arrays(fusion_pool)),
1385        )
1386        .await
1387        .unwrap();
1388
1389        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1390        assert_eq!(quote.collateral_token, TOKEN_B);
1391        assert_eq!(quote.position_token, TOKEN_A);
1392        assert_eq!(quote.collateral, 42_286_633);
1393        assert_eq!(quote.borrow, 160000000);
1394        assert_eq!(quote.estimated_amount, 998_966_727);
1395        assert_eq!(quote.protocol_fee_a, 1600000);
1396        assert_eq!(quote.protocol_fee_b, 211433);
1397        assert_eq!(quote.price_impact, 0.00044727115960174757);
1398    }
1399
1400    #[tokio::test]
1401    async fn tradable_amount_for_1x_long_position_providing_b() {
1402        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1403        let fusion_pool = test_fusion_pool(sqrt_price);
1404        let tick_arrays = Some(test_tick_arrays(fusion_pool));
1405
1406        let collateral_token = TOKEN_B;
1407        let position_token = TOKEN_A;
1408        let leverage = 1.0;
1409        let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1410        let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1411        let available_balance = 200_000_000;
1412
1413        let tradable_amount = get_tradable_amount(
1414            collateral_token,
1415            available_balance,
1416            leverage,
1417            position_token,
1418            false,
1419            position_token,
1420            0,
1421            0,
1422            protocol_fee_rate,
1423            protocol_fee_rate_on_collateral,
1424            fusion_pool,
1425            tick_arrays.clone(),
1426        )
1427        .unwrap();
1428        assert_eq!(tradable_amount, 198403000);
1429
1430        let quote = get_increase_spot_position_quote(
1431            tradable_amount,
1432            collateral_token,
1433            position_token,
1434            leverage,
1435            Some(0),
1436            protocol_fee_rate,
1437            protocol_fee_rate_on_collateral,
1438            NATIVE_MINT,
1439            TUNA_MINT,
1440            fusion_pool,
1441            tick_arrays,
1442        )
1443        .await
1444        .unwrap();
1445        assert_eq!(quote.collateral, available_balance);
1446    }
1447
1448    #[tokio::test]
1449    async fn tradable_amount_for_5x_long_position_providing_b() {
1450        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1451        let fusion_pool = test_fusion_pool(sqrt_price);
1452        let tick_arrays = Some(test_tick_arrays(fusion_pool));
1453
1454        let collateral_token = TOKEN_B;
1455        let position_token = TOKEN_A;
1456        let leverage = 5.0;
1457        let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1458        let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1459        let available_balance = 10_000_000;
1460
1461        let tradable_amount = get_tradable_amount(
1462            collateral_token,
1463            available_balance,
1464            leverage,
1465            position_token,
1466            false,
1467            position_token,
1468            0,
1469            0,
1470            protocol_fee_rate,
1471            protocol_fee_rate_on_collateral,
1472            fusion_pool,
1473            tick_arrays.clone(),
1474        )
1475        .unwrap();
1476        assert_eq!(tradable_amount, 47154380);
1477
1478        let quote = get_increase_spot_position_quote(
1479            tradable_amount,
1480            collateral_token,
1481            position_token,
1482            leverage,
1483            Some(0),
1484            protocol_fee_rate,
1485            protocol_fee_rate_on_collateral,
1486            NATIVE_MINT,
1487            TUNA_MINT,
1488            fusion_pool,
1489            tick_arrays,
1490        )
1491        .await
1492        .unwrap();
1493        // TODO: fix precision error
1494        assert_eq!(quote.collateral, available_balance + 1);
1495    }
1496
1497    #[tokio::test]
1498    async fn tradable_amount_for_5x_long_position_providing_a() {
1499        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1500        let fusion_pool = test_fusion_pool(sqrt_price);
1501        let tick_arrays = Some(test_tick_arrays(fusion_pool));
1502
1503        let collateral_token = TOKEN_A;
1504        let position_token = TOKEN_A;
1505        let leverage = 5.0;
1506        let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1507        let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1508        let available_balance = 1_000_000_000;
1509
1510        let tradable_amount = get_tradable_amount(
1511            collateral_token,
1512            available_balance,
1513            leverage,
1514            position_token,
1515            false,
1516            position_token,
1517            0,
1518            0,
1519            protocol_fee_rate,
1520            protocol_fee_rate_on_collateral,
1521            fusion_pool,
1522            tick_arrays.clone(),
1523        )
1524        .unwrap();
1525        assert_eq!(tradable_amount, 4729626953);
1526
1527        let quote = get_increase_spot_position_quote(
1528            tradable_amount,
1529            collateral_token,
1530            position_token,
1531            leverage,
1532            Some(0),
1533            protocol_fee_rate,
1534            protocol_fee_rate_on_collateral,
1535            NATIVE_MINT,
1536            TUNA_MINT,
1537            fusion_pool,
1538            tick_arrays,
1539        )
1540        .await
1541        .unwrap();
1542        assert_eq!(quote.collateral, available_balance);
1543        //assert_eq!(quote.estimated_amount, tradable_amount);
1544    }
1545
1546    #[tokio::test]
1547    async fn tradable_amount_for_5x_short_position_providing_b() {
1548        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1549        let fusion_pool = test_fusion_pool(sqrt_price);
1550        let tick_arrays = Some(test_tick_arrays(fusion_pool));
1551
1552        let collateral_token = TOKEN_B;
1553        let position_token = TOKEN_B;
1554        let leverage = 5.0;
1555        let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1556        let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1557        let available_balance = 200_000_000;
1558
1559        let tradable_amount = get_tradable_amount(
1560            collateral_token,
1561            available_balance,
1562            leverage,
1563            position_token,
1564            false,
1565            position_token,
1566            0,
1567            0,
1568            protocol_fee_rate,
1569            protocol_fee_rate_on_collateral,
1570            fusion_pool,
1571            tick_arrays.clone(),
1572        )
1573        .unwrap();
1574        assert_eq!(tradable_amount, 945925390);
1575
1576        let quote = get_increase_spot_position_quote(
1577            tradable_amount,
1578            collateral_token,
1579            position_token,
1580            leverage,
1581            Some(0),
1582            protocol_fee_rate,
1583            protocol_fee_rate_on_collateral,
1584            NATIVE_MINT,
1585            TUNA_MINT,
1586            fusion_pool,
1587            tick_arrays,
1588        )
1589        .await
1590        .unwrap();
1591        // TODO: fix precision error
1592        assert_eq!(quote.collateral, available_balance + 1);
1593        //assert_eq!(quote.estimated_amount, tradable_amount);
1594    }
1595
1596    #[tokio::test]
1597    async fn tradable_amount_for_reducing_existing_long_position() {
1598        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1599        let fusion_pool = test_fusion_pool(sqrt_price);
1600        let tick_arrays = Some(test_tick_arrays(fusion_pool));
1601
1602        for i in 0..2 {
1603            let collateral_token = if i == 0 { TOKEN_A } else { TOKEN_B };
1604            let position_token = if i == 0 { TOKEN_A } else { TOKEN_B };
1605            let leverage = 5.0;
1606            let reduce_only = true;
1607            let position_amount = 5_000_000_000;
1608            let position_debt = 800_000_000;
1609            let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1610            let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1611            let available_balance = 50_000_000_000;
1612
1613            let tradable_amount = get_tradable_amount(
1614                collateral_token,
1615                available_balance,
1616                leverage,
1617                if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A },
1618                reduce_only,
1619                position_token,
1620                position_amount,
1621                position_debt,
1622                protocol_fee_rate,
1623                protocol_fee_rate_on_collateral,
1624                fusion_pool,
1625                tick_arrays.clone(),
1626            )
1627            .unwrap();
1628            assert_eq!(tradable_amount, 5_000_000_000);
1629
1630            let quote = get_decrease_spot_position_quote(
1631                tradable_amount,
1632                collateral_token,
1633                5.0,
1634                reduce_only,
1635                Some(0),
1636                position_token,
1637                position_amount,
1638                position_debt,
1639                protocol_fee_rate,
1640                protocol_fee_rate_on_collateral,
1641                NATIVE_MINT,
1642                TUNA_MINT,
1643                fusion_pool,
1644                tick_arrays.clone(),
1645            )
1646            .await
1647            .unwrap();
1648
1649            assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1650        }
1651    }
1652
1653    #[tokio::test]
1654    async fn tradable_amount_when_inverting_long_position_providing_a() {
1655        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1656        let fusion_pool = test_fusion_pool(sqrt_price);
1657        let tick_arrays = Some(test_tick_arrays(fusion_pool));
1658
1659        let collateral_token = TOKEN_A;
1660        let position_token = TOKEN_A;
1661        let leverage = 5.0;
1662        let reduce_only = false;
1663        let position_amount = 5_000_000_000;
1664        let position_debt = 800_000_000;
1665        let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1666        let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1667        let available_balance = 2_000_000_000;
1668
1669        let tradable_amount = get_tradable_amount(
1670            collateral_token,
1671            available_balance,
1672            leverage,
1673            if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A },
1674            reduce_only,
1675            position_token,
1676            position_amount,
1677            position_debt,
1678            protocol_fee_rate,
1679            protocol_fee_rate_on_collateral,
1680            fusion_pool,
1681            tick_arrays.clone(),
1682        )
1683        .unwrap();
1684        assert_eq!(tradable_amount, 19_086_173_788);
1685
1686        let quote = get_decrease_spot_position_quote(
1687            tradable_amount,
1688            collateral_token,
1689            5.0,
1690            reduce_only,
1691            Some(0),
1692            position_token,
1693            position_amount,
1694            position_debt,
1695            protocol_fee_rate,
1696            protocol_fee_rate_on_collateral,
1697            NATIVE_MINT,
1698            TUNA_MINT,
1699            fusion_pool,
1700            tick_arrays.clone(),
1701        )
1702        .await
1703        .unwrap();
1704
1705        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1706        assert_eq!(quote.collateral, 2_978_284_319);
1707    }
1708
1709    #[tokio::test]
1710    async fn tradable_amount_when_inverting_long_position_providing_b() {
1711        let sqrt_price = price_to_sqrt_price(200.0, 9, 6);
1712        let fusion_pool = test_fusion_pool(sqrt_price);
1713        let tick_arrays = Some(test_tick_arrays(fusion_pool));
1714
1715        let collateral_token = TOKEN_B;
1716        let position_token = TOKEN_A;
1717        let leverage = 5.0;
1718        let reduce_only = false;
1719        let position_amount = 5_000_000_000;
1720        let position_debt = 800_000_000;
1721        let protocol_fee_rate = (HUNDRED_PERCENT / 100) as u16;
1722        let protocol_fee_rate_on_collateral = (HUNDRED_PERCENT / 200) as u16;
1723        let available_balance = 500_000_000;
1724
1725        let tradable_amount = get_tradable_amount(
1726            collateral_token,
1727            available_balance,
1728            leverage,
1729            if position_token == TOKEN_A { TOKEN_B } else { TOKEN_A },
1730            reduce_only,
1731            position_token,
1732            position_amount,
1733            position_debt,
1734            protocol_fee_rate,
1735            protocol_fee_rate_on_collateral,
1736            fusion_pool,
1737            tick_arrays.clone(),
1738        )
1739        .unwrap();
1740        assert_eq!(tradable_amount, 4_295_498_968);
1741
1742        let quote = get_decrease_spot_position_quote(
1743            tradable_amount,
1744            collateral_token,
1745            5.0,
1746            reduce_only,
1747            Some(0),
1748            position_token,
1749            position_amount,
1750            position_debt,
1751            protocol_fee_rate,
1752            protocol_fee_rate_on_collateral,
1753            NATIVE_MINT,
1754            TUNA_MINT,
1755            fusion_pool,
1756            tick_arrays.clone(),
1757        )
1758        .await
1759        .unwrap();
1760
1761        assert_eq!(quote.decrease_percent, HUNDRED_PERCENT);
1762        assert_eq!(quote.collateral, 696_777_779);
1763    }
1764}