1use crate::{CoreError, PoolTokenFacade, HUNDRED_PERCENT, INVALID_ARGUMENTS};
2use fusionamm_core::{sqrt_price_to_price, swap_quote_by_input_token, swap_quote_by_output_token, try_mul_div, FusionPoolFacade, TickArrays};
3use libm::{ceil, round};
4
5#[cfg(feature = "wasm")]
6use fusionamm_macros::wasm_expose;
7
8#[cfg_attr(feature = "wasm", wasm_expose)]
9pub struct IncreaseSpotPositionQuoteResult {
10 pub collateral: u64,
12 pub borrow: u64,
14 pub estimated_amount: u64,
16 pub swap_input_amount: u64,
18 pub protocol_fee_a: u64,
20 pub protocol_fee_b: u64,
22 pub price_impact: f64,
24}
25
26#[cfg_attr(feature = "wasm", wasm_expose)]
41pub fn get_increase_spot_position_quote(
42 increase_amount: u64,
43 collateral_token: PoolTokenFacade,
44 position_token: PoolTokenFacade,
45 leverage: f64,
46 protocol_fee_rate: u32,
47 protocol_fee_rate_on_collateral: u32,
48 fusion_pool: FusionPoolFacade,
49 tick_arrays: TickArrays,
50) -> Result<IncreaseSpotPositionQuoteResult, CoreError> {
51 if leverage < 1.0 {
52 return Err(INVALID_ARGUMENTS.into());
53 }
54
55 if protocol_fee_rate >= HUNDRED_PERCENT {
56 return Err(INVALID_ARGUMENTS.into());
57 }
58
59 if protocol_fee_rate_on_collateral >= HUNDRED_PERCENT {
60 return Err(INVALID_ARGUMENTS.into());
61 }
62
63 let borrow: u64;
64 let mut collateral: u64;
65 let estimated_amount: u64;
66 let swap_input_amount: u64;
67 let mut next_sqrt_price = fusion_pool.sqrt_price;
68
69 let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
70
71 if position_token != collateral_token {
72 borrow = ceil((increase_amount as f64 * (leverage - 1.0)) / leverage) as u64;
73 collateral = increase_amount - apply_swap_fee(apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?, fusion_pool.fee_rate, false)?;
74 collateral = reverse_apply_swap_fee(collateral, fusion_pool.fee_rate, false)?;
75 collateral = reverse_apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
76
77 swap_input_amount = increase_amount;
78 estimated_amount = round(if collateral_token == PoolTokenFacade::A {
79 increase_amount as f64 * price
80 } else {
81 increase_amount as f64 / price
82 }) as u64;
83 } else {
84 let position_to_borrowed_token_price = if collateral_token == PoolTokenFacade::A { price } else { 1.0 / price };
85 let borrow_in_position_token = ceil((increase_amount as f64 * (leverage - 1.0)) / leverage);
86
87 borrow = ceil(borrow_in_position_token * position_to_borrowed_token_price) as u64;
88
89 let borrow_in_position_token_with_fees_applied =
90 apply_swap_fee(apply_tuna_protocol_fee(borrow_in_position_token as u64, protocol_fee_rate, false)?, fusion_pool.fee_rate, false)?;
91
92 collateral = increase_amount - borrow_in_position_token_with_fees_applied;
93 collateral = reverse_apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
94
95 swap_input_amount = apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?;
96 estimated_amount = increase_amount;
97 }
98
99 if swap_input_amount > 0 {
100 let is_token_a = position_token == PoolTokenFacade::B;
101 next_sqrt_price = swap_quote_by_input_token(swap_input_amount, is_token_a, 0, fusion_pool, tick_arrays, None, None)?.next_sqrt_price;
102 }
103
104 let mut protocol_fee_a = 0;
105 let mut protocol_fee_b = 0;
106
107 if collateral_token == PoolTokenFacade::A {
108 protocol_fee_a += collateral - apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
109 } else {
110 protocol_fee_b += collateral - apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
111 }
112
113 if position_token == PoolTokenFacade::B {
114 protocol_fee_a += borrow - apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?;
115 } else {
116 protocol_fee_b += borrow - apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?;
117 }
118
119 let new_price = sqrt_price_to_price(next_sqrt_price.into(), 1, 1);
120 let price_impact = (new_price / price - 1.0).abs() * 100.0;
121
122 Ok(IncreaseSpotPositionQuoteResult {
123 collateral,
124 borrow,
125 estimated_amount,
126 swap_input_amount,
127 protocol_fee_a,
128 protocol_fee_b,
129 price_impact,
130 })
131}
132
133#[cfg_attr(feature = "wasm", wasm_expose)]
134pub struct DecreaseSpotPositionQuoteResult {
135 pub decrease_percent: u32,
137 pub collateral_token: PoolTokenFacade,
139 pub position_token: PoolTokenFacade,
141 pub collateral: u64,
143 pub borrow: u64,
145 pub swap_input_amount: u64,
147 pub estimated_amount: u64,
149 pub protocol_fee_a: u64,
151 pub protocol_fee_b: u64,
153 pub price_impact: f64,
155}
156
157#[cfg_attr(feature = "wasm", wasm_expose)]
175pub fn get_decrease_spot_position_quote(
176 decrease_amount: u64,
177 collateral_token: PoolTokenFacade,
178 position_token: PoolTokenFacade,
179 leverage: f64,
180 position_amount: u64,
181 position_debt: u64,
182 reduce_only: bool,
183 protocol_fee_rate: u32,
184 protocol_fee_rate_on_collateral: u32,
185 fusion_pool: FusionPoolFacade,
186 tick_arrays: TickArrays,
187) -> Result<DecreaseSpotPositionQuoteResult, CoreError> {
188 if leverage < 1.0 {
189 return Err(INVALID_ARGUMENTS.into());
190 }
191
192 if protocol_fee_rate >= HUNDRED_PERCENT {
193 return Err(INVALID_ARGUMENTS.into());
194 }
195
196 if protocol_fee_rate_on_collateral >= HUNDRED_PERCENT {
197 return Err(INVALID_ARGUMENTS.into());
198 }
199
200 let mut collateral = 0;
201 let mut borrow = 0;
202 let mut swap_input_amount = 0;
203 let estimated_amount: u64;
204 let mut next_sqrt_price = fusion_pool.sqrt_price;
205 let mut new_position_token = position_token;
206 let decrease_percent: u32;
207
208 let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
210 let position_to_opposite_token_price = if position_token == PoolTokenFacade::A { price } else { 1.0 / price };
211
212 let mut decrease_amount_in_position_token = if collateral_token == position_token {
213 decrease_amount
214 } else {
215 round(decrease_amount as f64 / position_to_opposite_token_price) as u64
216 };
217
218 if reduce_only && decrease_amount_in_position_token > position_amount {
219 decrease_amount_in_position_token = position_amount;
220 }
221
222 if decrease_amount_in_position_token <= position_amount {
223 decrease_percent = ((decrease_amount_in_position_token * HUNDRED_PERCENT as u64 / position_amount) as u32).min(HUNDRED_PERCENT);
224
225 estimated_amount = position_amount - decrease_amount_in_position_token;
226
227 if collateral_token == position_token {
228 if position_debt > 0 {
229 let swap_out = position_debt * decrease_percent as u64 / HUNDRED_PERCENT as u64;
230 let swap_quote = swap_quote_by_output_token(swap_out, position_token == PoolTokenFacade::B, 0, fusion_pool, tick_arrays, None, None)?;
231 next_sqrt_price = swap_quote.next_sqrt_price;
232 swap_input_amount = swap_quote.token_est_in;
233 }
234 } else {
235 swap_input_amount = position_amount - position_amount * (HUNDRED_PERCENT - decrease_percent) as u64 / HUNDRED_PERCENT as u64;
236 let swap_quote =
237 swap_quote_by_input_token(swap_input_amount, position_token == PoolTokenFacade::A, 0, fusion_pool, tick_arrays, None, None)?;
238 next_sqrt_price = swap_quote.next_sqrt_price;
239 }
240 } else {
241 decrease_percent = HUNDRED_PERCENT;
242 new_position_token = if position_token == PoolTokenFacade::A {
243 PoolTokenFacade::B
244 } else {
245 PoolTokenFacade::A
246 };
247 let increase_amount = decrease_amount_in_position_token - position_amount;
248
249 if position_token == collateral_token {
250 estimated_amount = round(increase_amount as f64 * position_to_opposite_token_price) as u64;
259
260 borrow = round((increase_amount as f64 * (leverage - 1.0)) / leverage) as u64;
262 let borrow_with_fees_applied = apply_swap_fee(apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?, fusion_pool.fee_rate, false)?;
263
264 collateral = increase_amount - borrow_with_fees_applied;
265
266 if position_debt > 0 {
268 let swap_quote = swap_quote_by_output_token(
269 position_debt,
270 position_token != PoolTokenFacade::A,
271 0,
272 fusion_pool,
273 tick_arrays.clone().into(),
274 None,
275 None,
276 )?;
277 swap_input_amount = swap_quote.token_est_in;
278 }
279
280 swap_input_amount += collateral + apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?;
281 let swap_quote =
282 swap_quote_by_input_token(swap_input_amount, position_token == PoolTokenFacade::A, 0, fusion_pool, tick_arrays, None, None)?;
283 next_sqrt_price = swap_quote.next_sqrt_price;
284
285 collateral = reverse_apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
286 } else {
287 estimated_amount = round(increase_amount as f64 * position_to_opposite_token_price) as u64;
295
296 borrow = round((increase_amount as f64 * (leverage - 1.0)) / leverage) as u64;
297 let borrow_with_fees_applied = apply_swap_fee(apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?, fusion_pool.fee_rate, false)?;
298
299 collateral = increase_amount - borrow_with_fees_applied;
300 collateral = round(collateral as f64 * position_to_opposite_token_price) as u64;
301 collateral = reverse_apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
302
303 swap_input_amount = position_amount + apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?;
305 let swap_quote =
306 swap_quote_by_input_token(swap_input_amount, position_token == PoolTokenFacade::A, 0, fusion_pool, tick_arrays, None, None)?;
307 next_sqrt_price = swap_quote.next_sqrt_price;
308 }
309 }
310
311 let mut protocol_fee_a = 0;
312 let mut protocol_fee_b = 0;
313
314 if collateral_token == PoolTokenFacade::A {
315 protocol_fee_a += collateral - apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
316 } else {
317 protocol_fee_b += collateral - apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
318 }
319
320 if position_token == PoolTokenFacade::B {
321 protocol_fee_a += borrow - apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?;
322 } else {
323 protocol_fee_b += borrow - apply_tuna_protocol_fee(borrow, protocol_fee_rate, false)?;
324 }
325
326 let new_price = sqrt_price_to_price(next_sqrt_price.into(), 1, 1);
327 let price_impact = (new_price / price - 1.0).abs() * 100.0;
328
329 Ok(DecreaseSpotPositionQuoteResult {
330 decrease_percent,
331 collateral_token,
332 position_token: new_position_token,
333 collateral,
334 borrow,
335 swap_input_amount,
336 estimated_amount,
337 protocol_fee_a,
338 protocol_fee_b,
339 price_impact,
340 })
341}
342
343#[cfg_attr(feature = "wasm", wasm_expose)]
354pub fn get_liquidation_price(position_token: PoolTokenFacade, amount: f64, debt: f64, liquidation_threshold: f64) -> Result<f64, CoreError> {
355 if debt < 0.0 || amount < 0.0 {
356 return Err(INVALID_ARGUMENTS);
357 }
358
359 if liquidation_threshold <= 0.0 || liquidation_threshold >= 1.0 {
360 return Err(INVALID_ARGUMENTS);
361 }
362
363 if debt == 0.0 || amount == 0.0 {
364 return Ok(0.0);
365 }
366
367 if position_token == PoolTokenFacade::A {
368 Ok(debt / (amount * liquidation_threshold))
369 } else {
370 Ok((amount * liquidation_threshold) / debt)
371 }
372}
373
374#[cfg_attr(feature = "wasm", wasm_expose)]
393pub fn get_tradable_amount(
394 collateral_token: PoolTokenFacade,
395 available_balance: u64,
396 leverage: f64,
397 new_position_token: PoolTokenFacade,
398 position_token: PoolTokenFacade,
399 position_amount: u64,
400 position_debt: u64,
401 reduce_only: bool,
402 protocol_fee_rate: u32,
403 protocol_fee_rate_on_collateral: u32,
404 fusion_pool: FusionPoolFacade,
405 tick_arrays: TickArrays,
406) -> Result<u64, CoreError> {
407 if leverage < 1.0 {
408 return Err(INVALID_ARGUMENTS.into());
409 }
410
411 if protocol_fee_rate >= HUNDRED_PERCENT {
412 return Err(INVALID_ARGUMENTS.into());
413 }
414
415 if protocol_fee_rate_on_collateral >= HUNDRED_PERCENT {
416 return Err(INVALID_ARGUMENTS.into());
417 }
418
419 if position_amount == 0 && new_position_token != position_token {
420 return Err(INVALID_ARGUMENTS.into());
421 }
422
423 let add_leverage = |collateral: u64| -> Result<u64, CoreError> {
427 let mut collateral = apply_tuna_protocol_fee(collateral, protocol_fee_rate_on_collateral, false)?;
428 if collateral_token != new_position_token {
429 collateral = apply_swap_fee(collateral, fusion_pool.fee_rate, false)?;
430 }
431
432 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);
433 let total = (collateral as f64 / (1.0 - (fee_multiplier * (leverage - 1.0)) / leverage)) as u64;
434 Ok(total)
435 };
436
437 let available_to_trade = if new_position_token == position_token {
438 add_leverage(available_balance)?
439 } else {
440 let price = sqrt_price_to_price(fusion_pool.sqrt_price.into(), 1, 1);
441 let position_to_opposite_token_price = if position_token == PoolTokenFacade::A { price } else { 1.0 / price };
442
443 if reduce_only {
444 if collateral_token == position_token {
445 position_amount
446 } else {
447 round(position_amount as f64 * position_to_opposite_token_price) as u64
448 }
449 } else {
450 let position_amount_in_collateral_token = if collateral_token == position_token {
451 position_amount
452 } else {
453 round(position_amount as f64 * position_to_opposite_token_price) as u64
454 };
455
456 let position_collateral = if collateral_token == position_token {
457 let swap_in = if position_debt > 0 {
458 swap_quote_by_output_token(position_debt, position_token == PoolTokenFacade::B, 0, fusion_pool, tick_arrays, None, None)?
459 .token_est_in
460 } else {
461 0
462 };
463 position_amount - swap_in
464 } else {
465 if position_amount > 0 {
466 let swap_quote =
467 swap_quote_by_input_token(position_amount, position_token == PoolTokenFacade::A, 0, fusion_pool, tick_arrays, None, None)?;
468 swap_quote.token_est_out - position_debt
469 } else {
470 0
471 }
472 };
473
474 position_amount_in_collateral_token + add_leverage(available_balance + position_collateral)?
476 }
477 };
478
479 Ok(available_to_trade)
480}
481
482pub fn apply_tuna_protocol_fee(amount: u64, protocol_fee_rate: u32, round_up: bool) -> Result<u64, CoreError> {
483 try_mul_div(amount, HUNDRED_PERCENT as u128 - protocol_fee_rate as u128, HUNDRED_PERCENT as u128, round_up)
484}
485
486pub fn reverse_apply_tuna_protocol_fee(amount: u64, protocol_fee_rate: u32, round_up: bool) -> Result<u64, CoreError> {
487 try_mul_div(amount, HUNDRED_PERCENT as u128, HUNDRED_PERCENT as u128 - protocol_fee_rate as u128, round_up)
488}
489
490pub fn apply_swap_fee(amount: u64, fee_rate: u16, round_up: bool) -> Result<u64, CoreError> {
491 try_mul_div(amount, 1_000_000 - fee_rate as u128, 1_000_000, round_up)
492}
493
494pub fn reverse_apply_swap_fee(amount: u64, fee_rate: u16, round_up: bool) -> Result<u64, CoreError> {
495 try_mul_div(amount, 1_000_000, 1_000_000 - fee_rate as u128, round_up)
496}
497
498#[cfg(all(test, not(feature = "wasm")))]
499mod tests {
500 use super::*;
501
502 #[test]
503 fn test_get_liquidation_price() {
504 assert_eq!(get_liquidation_price(PoolTokenFacade::A, 5.0, 0.0, 0.85), Ok(0.0));
505 assert_eq!(get_liquidation_price(PoolTokenFacade::A, 0.0, 5.0, 0.85), Ok(0.0));
506 assert_eq!(get_liquidation_price(PoolTokenFacade::A, 5.0, 800.0, 0.85), Ok(188.23529411764707));
507 assert_eq!(get_liquidation_price(PoolTokenFacade::B, 1000.0, 4.0, 0.85), Ok(212.5));
508 }
509}