1use std::ops::{Deref, DerefMut};
2
3use crate::{
4 fixed::FixedPointOps,
5 num::{MulDiv, Num, Unsigned, UnsignedAbs},
6 pool::{balance::Merged, Balance, BalanceExt, Pool},
7 price::{Price, Prices},
8 Delta, PoolExt,
9};
10use num_traits::{CheckedAdd, CheckedSub, Signed, Zero};
11
12use super::get_msg_by_side;
13
14pub trait BaseMarket<const DECIMALS: u8> {
16 type Num: MulDiv<Signed = Self::Signed> + FixedPointOps<DECIMALS>;
18
19 type Signed: UnsignedAbs<Unsigned = Self::Num> + TryFrom<Self::Num> + Num;
21
22 type Pool: Pool<Num = Self::Num, Signed = Self::Signed>;
24
25 fn liquidity_pool(&self) -> crate::Result<&Self::Pool>;
27
28 fn claimable_fee_pool(&self) -> crate::Result<&Self::Pool>;
30
31 fn swap_impact_pool(&self) -> crate::Result<&Self::Pool>;
33
34 fn open_interest_pool(&self, is_long: bool) -> crate::Result<&Self::Pool>;
36
37 fn open_interest_in_tokens_pool(&self, is_long: bool) -> crate::Result<&Self::Pool>;
39
40 fn collateral_sum_pool(&self, is_long: bool) -> crate::Result<&Self::Pool>;
42
43 fn virtual_inventory_for_swaps_pool(
45 &self,
46 ) -> crate::Result<Option<impl Deref<Target = Self::Pool>>>;
47
48 fn virtual_inventory_for_positions_pool(
50 &self,
51 ) -> crate::Result<Option<impl Deref<Target = Self::Pool>>>;
52
53 fn usd_to_amount_divisor(&self) -> Self::Num;
57
58 fn max_pool_amount(&self, is_long_token: bool) -> crate::Result<Self::Num>;
60
61 fn pnl_factor_config(&self, kind: PnlFactorKind, is_long: bool) -> crate::Result<Self::Num>;
63
64 fn reserve_factor(&self) -> crate::Result<Self::Num>;
66
67 fn open_interest_reserve_factor(&self) -> crate::Result<Self::Num>;
69
70 fn max_open_interest(&self, is_long: bool) -> crate::Result<Self::Num>;
72
73 fn ignore_open_interest_for_usage_factor(&self) -> crate::Result<bool>;
75}
76
77pub trait BaseMarketMut<const DECIMALS: u8>: BaseMarket<DECIMALS> {
79 fn liquidity_pool_mut(&mut self) -> crate::Result<&mut Self::Pool>;
86
87 fn claimable_fee_pool_mut(&mut self) -> crate::Result<&mut Self::Pool>;
91
92 fn virtual_inventory_for_swaps_pool_mut(
96 &mut self,
97 ) -> crate::Result<Option<impl DerefMut<Target = Self::Pool>>>;
98}
99
100impl<M: BaseMarket<DECIMALS>, const DECIMALS: u8> BaseMarket<DECIMALS> for &mut M {
101 type Num = M::Num;
102
103 type Signed = M::Signed;
104
105 type Pool = M::Pool;
106
107 fn liquidity_pool(&self) -> crate::Result<&Self::Pool> {
108 (**self).liquidity_pool()
109 }
110
111 fn swap_impact_pool(&self) -> crate::Result<&Self::Pool> {
112 (**self).swap_impact_pool()
113 }
114
115 fn claimable_fee_pool(&self) -> crate::Result<&Self::Pool> {
116 (**self).claimable_fee_pool()
117 }
118
119 fn open_interest_pool(&self, is_long: bool) -> crate::Result<&Self::Pool> {
120 (**self).open_interest_pool(is_long)
121 }
122
123 fn open_interest_in_tokens_pool(&self, is_long: bool) -> crate::Result<&Self::Pool> {
124 (**self).open_interest_in_tokens_pool(is_long)
125 }
126
127 fn collateral_sum_pool(&self, is_long: bool) -> crate::Result<&Self::Pool> {
128 (**self).collateral_sum_pool(is_long)
129 }
130
131 fn virtual_inventory_for_swaps_pool(
132 &self,
133 ) -> crate::Result<Option<impl Deref<Target = Self::Pool>>> {
134 (**self).virtual_inventory_for_swaps_pool()
135 }
136
137 fn virtual_inventory_for_positions_pool(
138 &self,
139 ) -> crate::Result<Option<impl Deref<Target = Self::Pool>>> {
140 (**self).virtual_inventory_for_positions_pool()
141 }
142
143 fn usd_to_amount_divisor(&self) -> Self::Num {
144 (**self).usd_to_amount_divisor()
145 }
146
147 fn max_pool_amount(&self, is_long_token: bool) -> crate::Result<Self::Num> {
148 (**self).max_pool_amount(is_long_token)
149 }
150
151 fn pnl_factor_config(&self, kind: PnlFactorKind, is_long: bool) -> crate::Result<Self::Num> {
152 (**self).pnl_factor_config(kind, is_long)
153 }
154
155 fn reserve_factor(&self) -> crate::Result<Self::Num> {
156 (**self).reserve_factor()
157 }
158
159 fn open_interest_reserve_factor(&self) -> crate::Result<Self::Num> {
160 (**self).open_interest_reserve_factor()
161 }
162
163 fn max_open_interest(&self, is_long: bool) -> crate::Result<Self::Num> {
164 (**self).max_open_interest(is_long)
165 }
166
167 fn ignore_open_interest_for_usage_factor(&self) -> crate::Result<bool> {
168 (**self).ignore_open_interest_for_usage_factor()
169 }
170}
171
172impl<M: BaseMarketMut<DECIMALS>, const DECIMALS: u8> BaseMarketMut<DECIMALS> for &mut M {
173 fn liquidity_pool_mut(&mut self) -> crate::Result<&mut Self::Pool> {
174 (**self).liquidity_pool_mut()
175 }
176
177 fn claimable_fee_pool_mut(&mut self) -> crate::Result<&mut Self::Pool> {
178 (**self).claimable_fee_pool_mut()
179 }
180
181 fn virtual_inventory_for_swaps_pool_mut(
182 &mut self,
183 ) -> crate::Result<Option<impl DerefMut<Target = Self::Pool>>> {
184 (**self).virtual_inventory_for_swaps_pool_mut()
185 }
186}
187
188pub trait BaseMarketExt<const DECIMALS: u8>: BaseMarket<DECIMALS> {
190 #[inline]
192 fn pool_value_without_pnl_for_one_side(
193 &self,
194 prices: &Prices<Self::Num>,
195 is_long: bool,
196 maximize: bool,
197 ) -> crate::Result<Self::Num> {
198 if is_long {
199 self.liquidity_pool()?
200 .long_usd_value(prices.long_token_price.pick_price(maximize))
201 } else {
202 self.liquidity_pool()?
203 .short_usd_value(prices.short_token_price.pick_price(maximize))
204 }
205 }
206
207 fn open_interest(&self) -> crate::Result<Merged<&Self::Pool, &Self::Pool>> {
209 Ok(self
210 .open_interest_pool(true)?
211 .merge(self.open_interest_pool(false)?))
212 }
213
214 fn open_interest_in_tokens(&self) -> crate::Result<Merged<&Self::Pool, &Self::Pool>> {
219 Ok(self
220 .open_interest_in_tokens_pool(true)?
221 .merge(self.open_interest_in_tokens_pool(false)?))
222 }
223
224 fn pnl(
226 &self,
227 index_token_price: &Price<Self::Num>,
228 is_long: bool,
229 maximize: bool,
230 ) -> crate::Result<Self::Signed> {
231 use num_traits::CheckedMul;
232
233 let open_interest = self.open_interest()?.amount(is_long)?;
234 let open_interest_in_tokens = self.open_interest_in_tokens()?.amount(is_long)?;
235 if open_interest.is_zero() && open_interest_in_tokens.is_zero() {
236 return Ok(Zero::zero());
237 }
238
239 let price = index_token_price.pick_price_for_pnl(is_long, maximize);
240
241 let open_interest_value = open_interest_in_tokens
242 .checked_mul(price)
243 .ok_or(crate::Error::Computation("calculating open interest value"))?;
244
245 if is_long {
246 open_interest_value
247 .to_signed()?
248 .checked_sub(&open_interest.to_signed()?)
249 .ok_or(crate::Error::Computation("calculating pnl for long"))
250 } else {
251 open_interest
252 .to_signed()?
253 .checked_sub(&open_interest_value.to_signed()?)
254 .ok_or(crate::Error::Computation("calculating pnl for short"))
255 }
256 }
257
258 fn pnl_factor_with_pool_value(
260 &self,
261 prices: &Prices<Self::Num>,
262 is_long: bool,
263 maximize: bool,
264 ) -> crate::Result<(Self::Signed, Self::Num)> {
265 let pool_value = self.pool_value_without_pnl_for_one_side(prices, is_long, !maximize)?;
266 let pnl = self.pnl(&prices.index_token_price, is_long, maximize)?;
267 crate::utils::div_to_factor_signed(&pnl, &pool_value)
268 .ok_or(crate::Error::Computation("calculating pnl factor"))
269 .map(|factor| (factor, pool_value))
270 }
271
272 fn pnl_factor(
274 &self,
275 prices: &Prices<Self::Num>,
276 is_long: bool,
277 maximize: bool,
278 ) -> crate::Result<Self::Signed> {
279 Ok(self
280 .pnl_factor_with_pool_value(prices, is_long, maximize)?
281 .0)
282 }
283
284 fn validate_pool_amount(&self, is_long_token: bool) -> crate::Result<()> {
286 let amount = self.liquidity_pool()?.amount(is_long_token)?;
287 let max_pool_amount = self.max_pool_amount(is_long_token)?;
288 if amount > max_pool_amount {
289 Err(crate::Error::MaxPoolAmountExceeded(get_msg_by_side(
290 is_long_token,
291 )))
292 } else {
293 Ok(())
294 }
295 }
296
297 fn pnl_factor_exceeded(
301 &self,
302 prices: &Prices<Self::Num>,
303 kind: PnlFactorKind,
304 is_long: bool,
305 ) -> crate::Result<Option<PnlFactorExceeded<Self::Num>>> {
306 let (pnl_factor, pool_value) = self.pnl_factor_with_pool_value(prices, is_long, true)?;
307 let max_pnl_factor = self.pnl_factor_config(kind, is_long)?;
308
309 let is_exceeded = pnl_factor.is_positive() && pnl_factor.unsigned_abs() > max_pnl_factor;
310
311 Ok(is_exceeded.then(|| PnlFactorExceeded {
312 pnl_factor,
313 max_pnl_factor,
314 pool_value,
315 }))
316 }
317
318 fn validate_pnl_factor(
320 &self,
321 prices: &Prices<Self::Num>,
322 kind: PnlFactorKind,
323 is_long: bool,
324 ) -> crate::Result<()> {
325 if self.pnl_factor_exceeded(prices, kind, is_long)?.is_some() {
326 Err(crate::Error::PnlFactorExceeded(
327 kind,
328 get_msg_by_side(is_long),
329 ))
330 } else {
331 Ok(())
332 }
333 }
334
335 fn validate_max_pnl(
337 &self,
338 prices: &Prices<Self::Num>,
339 long_kind: PnlFactorKind,
340 short_kind: PnlFactorKind,
341 ) -> crate::Result<()> {
342 self.validate_pnl_factor(prices, long_kind, true)?;
343 self.validate_pnl_factor(prices, short_kind, false)?;
344 Ok(())
345 }
346
347 fn reserved_value(
349 &self,
350 index_token_price: &Price<Self::Num>,
351 is_long: bool,
352 ) -> crate::Result<Self::Num> {
353 if is_long {
354 self.open_interest_in_tokens()?
361 .long_usd_value(index_token_price.pick_price(true))
362 } else {
363 self.open_interest()?.short_amount()
367 }
368 }
369
370 fn validate_reserve(&self, prices: &Prices<Self::Num>, is_long: bool) -> crate::Result<()> {
372 let pool_value = self.pool_value_without_pnl_for_one_side(prices, is_long, false)?;
373
374 let max_reserved_value =
375 crate::utils::apply_factor(&pool_value, &self.reserve_factor()?)
376 .ok_or(crate::Error::Computation("calculating max reserved value"))?;
377
378 let reserved_value = self.reserved_value(&prices.index_token_price, is_long)?;
379
380 if reserved_value > max_reserved_value {
381 Err(crate::Error::InsufficientReserve(
382 reserved_value.to_string(),
383 max_reserved_value.to_string(),
384 ))
385 } else {
386 Ok(())
387 }
388 }
389
390 fn expected_min_token_balance_excluding_collateral_amount_for_one_token_side(
405 &self,
406 is_long_side: bool,
407 ) -> crate::Result<Self::Num> {
408 let mut balance = self.liquidity_pool()?.amount(is_long_side)?;
410
411 balance = balance
413 .checked_add(&self.swap_impact_pool()?.amount(is_long_side)?)
414 .ok_or(crate::Error::Computation(
415 "overflow adding swap impact pool amount",
416 ))?;
417
418 balance = balance
420 .checked_add(&self.claimable_fee_pool()?.amount(is_long_side)?)
421 .ok_or(crate::Error::Computation(
422 "overflow adding claimable fee amount",
423 ))?;
424
425 Ok(balance)
426 }
427
428 fn total_collateral_amount_for_one_token_side(
434 &self,
435 is_long_side: bool,
436 ) -> crate::Result<Self::Num> {
437 let mut collateral_amount = self.collateral_sum_pool(true)?.amount(is_long_side)?;
438 collateral_amount = collateral_amount
439 .checked_add(&self.collateral_sum_pool(false)?.amount(is_long_side)?)
440 .ok_or(crate::Error::Computation(
441 "calculating total collateral sum for one side",
442 ))?;
443 Ok(collateral_amount)
444 }
445
446 fn checked_apply_delta(
448 &self,
449 delta: Delta<&Self::Signed>,
450 ) -> crate::Result<(Self::Pool, Option<Self::Pool>)> {
451 let liquidity_pool = self.liquidity_pool()?.checked_apply_delta(delta)?;
452 let virtual_inventory_for_swaps_pool = self
453 .virtual_inventory_for_swaps_pool()?
454 .map(|p| p.checked_apply_delta(delta))
455 .transpose()?;
456
457 Ok((liquidity_pool, virtual_inventory_for_swaps_pool))
458 }
459}
460
461impl<M: BaseMarket<DECIMALS> + ?Sized, const DECIMALS: u8> BaseMarketExt<DECIMALS> for M {}
462
463pub trait BaseMarketMutExt<const DECIMALS: u8>: BaseMarketMut<DECIMALS> {
465 fn apply_delta(&mut self, is_long_token: bool, delta: &Self::Signed) -> crate::Result<()> {
467 let delta = if is_long_token {
468 Delta::new_with_long(delta)
469 } else {
470 Delta::new_with_short(delta)
471 };
472 let (liquidity_pool, virtual_inventory_for_swaps_pool) = self.checked_apply_delta(delta)?;
473
474 *self
475 .liquidity_pool_mut()
476 .expect("liquidity pool must be valid") = liquidity_pool;
477 if let Some(virtual_inventory_for_swaps_pool) = virtual_inventory_for_swaps_pool {
478 *self
479 .virtual_inventory_for_swaps_pool_mut()
480 .expect("virtual inventory for_swaps pool must be valid")
481 .expect("virtual inventory for_swaps pool must exist") =
482 virtual_inventory_for_swaps_pool;
483 }
484
485 Ok(())
486 }
487
488 fn apply_delta_to_claimable_fee_pool(
490 &mut self,
491 is_long_token: bool,
492 delta: &Self::Signed,
493 ) -> crate::Result<()> {
494 self.claimable_fee_pool_mut()?
495 .apply_delta_amount(is_long_token, delta)?;
496 Ok(())
497 }
498}
499
500impl<M: BaseMarketMut<DECIMALS> + ?Sized, const DECIMALS: u8> BaseMarketMutExt<DECIMALS> for M {}
501
502#[derive(
504 Debug,
505 Clone,
506 Copy,
507 num_enum::TryFromPrimitive,
508 num_enum::IntoPrimitive,
509 PartialEq,
510 Eq,
511 PartialOrd,
512 Ord,
513 Hash,
514)]
515#[cfg_attr(
516 feature = "strum",
517 derive(strum::EnumIter, strum::EnumString, strum::Display)
518)]
519#[cfg_attr(feature = "strum", strum(serialize_all = "snake_case"))]
520#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
521#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
522#[cfg_attr(feature = "js", derive(tsify_next::Tsify))]
523#[repr(u8)]
524#[non_exhaustive]
525pub enum PnlFactorKind {
526 MaxAfterDeposit,
528 MaxAfterWithdrawal,
530 MaxForTrader,
532 ForAdl,
534 MinAfterAdl,
536}
537
538pub struct PnlFactorExceeded<T: Unsigned> {
540 pub pnl_factor: T::Signed,
542 pub max_pnl_factor: T,
544 pub pool_value: T,
546}
547
548impl<T: Unsigned> PnlFactorExceeded<T> {
549 pub fn exceeded_pnl<const DECIMALS: u8>(&self) -> Option<T>
551 where
552 T: CheckedSub,
553 T: FixedPointOps<DECIMALS>,
554 {
555 if !self.pnl_factor.is_positive() || self.pool_value.is_zero() {
556 return None;
557 }
558
559 let pnl_factor = self.pnl_factor.unsigned_abs();
560
561 let diff_factor = pnl_factor.checked_sub(&self.max_pnl_factor)?;
562
563 crate::utils::apply_factor(&self.pool_value, &diff_factor)
564 }
565}