1use crate::{
2 fixed::FixedPointOps,
3 num::{MulDiv, Num, Unsigned, UnsignedAbs},
4 pool::{balance::Merged, Balance, BalanceExt, Pool},
5 price::Price,
6 price::Prices,
7 PoolExt,
8};
9use num_traits::{CheckedAdd, CheckedSub, Signed, Zero};
10
11use super::get_msg_by_side;
12
13pub trait BaseMarket<const DECIMALS: u8> {
15 type Num: MulDiv<Signed = Self::Signed> + FixedPointOps<DECIMALS>;
17
18 type Signed: UnsignedAbs<Unsigned = Self::Num> + TryFrom<Self::Num> + Num;
20
21 type Pool: Pool<Num = Self::Num, Signed = Self::Signed>;
23
24 fn liquidity_pool(&self) -> crate::Result<&Self::Pool>;
26
27 fn claimable_fee_pool(&self) -> crate::Result<&Self::Pool>;
29
30 fn swap_impact_pool(&self) -> crate::Result<&Self::Pool>;
32
33 fn open_interest_pool(&self, is_long: bool) -> crate::Result<&Self::Pool>;
35
36 fn open_interest_in_tokens_pool(&self, is_long: bool) -> crate::Result<&Self::Pool>;
38
39 fn collateral_sum_pool(&self, is_long: bool) -> crate::Result<&Self::Pool>;
41
42 fn usd_to_amount_divisor(&self) -> Self::Num;
46
47 fn max_pool_amount(&self, is_long_token: bool) -> crate::Result<Self::Num>;
49
50 fn pnl_factor_config(&self, kind: PnlFactorKind, is_long: bool) -> crate::Result<Self::Num>;
52
53 fn reserve_factor(&self) -> crate::Result<Self::Num>;
55
56 fn open_interest_reserve_factor(&self) -> crate::Result<Self::Num>;
58
59 fn max_open_interest(&self, is_long: bool) -> crate::Result<Self::Num>;
61
62 fn ignore_open_interest_for_usage_factor(&self) -> crate::Result<bool>;
64}
65
66pub trait BaseMarketMut<const DECIMALS: u8>: BaseMarket<DECIMALS> {
68 fn liquidity_pool_mut(&mut self) -> crate::Result<&mut Self::Pool>;
72
73 fn claimable_fee_pool_mut(&mut self) -> crate::Result<&mut Self::Pool>;
77}
78
79impl<M: BaseMarket<DECIMALS>, const DECIMALS: u8> BaseMarket<DECIMALS> for &mut M {
80 type Num = M::Num;
81
82 type Signed = M::Signed;
83
84 type Pool = M::Pool;
85
86 fn liquidity_pool(&self) -> crate::Result<&Self::Pool> {
87 (**self).liquidity_pool()
88 }
89
90 fn swap_impact_pool(&self) -> crate::Result<&Self::Pool> {
91 (**self).swap_impact_pool()
92 }
93
94 fn claimable_fee_pool(&self) -> crate::Result<&Self::Pool> {
95 (**self).claimable_fee_pool()
96 }
97
98 fn open_interest_pool(&self, is_long: bool) -> crate::Result<&Self::Pool> {
99 (**self).open_interest_pool(is_long)
100 }
101
102 fn open_interest_in_tokens_pool(&self, is_long: bool) -> crate::Result<&Self::Pool> {
103 (**self).open_interest_in_tokens_pool(is_long)
104 }
105
106 fn collateral_sum_pool(&self, is_long: bool) -> crate::Result<&Self::Pool> {
107 (**self).collateral_sum_pool(is_long)
108 }
109
110 fn usd_to_amount_divisor(&self) -> Self::Num {
111 (**self).usd_to_amount_divisor()
112 }
113
114 fn max_pool_amount(&self, is_long_token: bool) -> crate::Result<Self::Num> {
115 (**self).max_pool_amount(is_long_token)
116 }
117
118 fn pnl_factor_config(&self, kind: PnlFactorKind, is_long: bool) -> crate::Result<Self::Num> {
119 (**self).pnl_factor_config(kind, is_long)
120 }
121
122 fn reserve_factor(&self) -> crate::Result<Self::Num> {
123 (**self).reserve_factor()
124 }
125
126 fn open_interest_reserve_factor(&self) -> crate::Result<Self::Num> {
127 (**self).open_interest_reserve_factor()
128 }
129
130 fn max_open_interest(&self, is_long: bool) -> crate::Result<Self::Num> {
131 (**self).max_open_interest(is_long)
132 }
133
134 fn ignore_open_interest_for_usage_factor(&self) -> crate::Result<bool> {
135 (**self).ignore_open_interest_for_usage_factor()
136 }
137}
138
139impl<M: BaseMarketMut<DECIMALS>, const DECIMALS: u8> BaseMarketMut<DECIMALS> for &mut M {
140 fn liquidity_pool_mut(&mut self) -> crate::Result<&mut Self::Pool> {
141 (**self).liquidity_pool_mut()
142 }
143
144 fn claimable_fee_pool_mut(&mut self) -> crate::Result<&mut Self::Pool> {
145 (**self).claimable_fee_pool_mut()
146 }
147}
148
149pub trait BaseMarketExt<const DECIMALS: u8>: BaseMarket<DECIMALS> {
151 #[inline]
153 fn pool_value_without_pnl_for_one_side(
154 &self,
155 prices: &Prices<Self::Num>,
156 is_long: bool,
157 maximize: bool,
158 ) -> crate::Result<Self::Num> {
159 if is_long {
160 self.liquidity_pool()?
161 .long_usd_value(prices.long_token_price.pick_price(maximize))
162 } else {
163 self.liquidity_pool()?
164 .short_usd_value(prices.short_token_price.pick_price(maximize))
165 }
166 }
167
168 fn open_interest(&self) -> crate::Result<Merged<&Self::Pool, &Self::Pool>> {
170 Ok(self
171 .open_interest_pool(true)?
172 .merge(self.open_interest_pool(false)?))
173 }
174
175 fn open_interest_in_tokens(&self) -> crate::Result<Merged<&Self::Pool, &Self::Pool>> {
180 Ok(self
181 .open_interest_in_tokens_pool(true)?
182 .merge(self.open_interest_in_tokens_pool(false)?))
183 }
184
185 fn pnl(
187 &self,
188 index_token_price: &Price<Self::Num>,
189 is_long: bool,
190 maximize: bool,
191 ) -> crate::Result<Self::Signed> {
192 use num_traits::CheckedMul;
193
194 let open_interest = self.open_interest()?.amount(is_long)?;
195 let open_interest_in_tokens = self.open_interest_in_tokens()?.amount(is_long)?;
196 if open_interest.is_zero() && open_interest_in_tokens.is_zero() {
197 return Ok(Zero::zero());
198 }
199
200 let price = index_token_price.pick_price_for_pnl(is_long, maximize);
201
202 let open_interest_value = open_interest_in_tokens
203 .checked_mul(price)
204 .ok_or(crate::Error::Computation("calculating open interest value"))?;
205
206 if is_long {
207 open_interest_value
208 .to_signed()?
209 .checked_sub(&open_interest.to_signed()?)
210 .ok_or(crate::Error::Computation("calculating pnl for long"))
211 } else {
212 open_interest
213 .to_signed()?
214 .checked_sub(&open_interest_value.to_signed()?)
215 .ok_or(crate::Error::Computation("calculating pnl for short"))
216 }
217 }
218
219 fn pnl_factor_with_pool_value(
221 &self,
222 prices: &Prices<Self::Num>,
223 is_long: bool,
224 maximize: bool,
225 ) -> crate::Result<(Self::Signed, Self::Num)> {
226 let pool_value = self.pool_value_without_pnl_for_one_side(prices, is_long, !maximize)?;
227 let pnl = self.pnl(&prices.index_token_price, is_long, maximize)?;
228 crate::utils::div_to_factor_signed(&pnl, &pool_value)
229 .ok_or(crate::Error::Computation("calculating pnl factor"))
230 .map(|factor| (factor, pool_value))
231 }
232
233 fn pnl_factor(
235 &self,
236 prices: &Prices<Self::Num>,
237 is_long: bool,
238 maximize: bool,
239 ) -> crate::Result<Self::Signed> {
240 Ok(self
241 .pnl_factor_with_pool_value(prices, is_long, maximize)?
242 .0)
243 }
244
245 fn validate_pool_amount(&self, is_long_token: bool) -> crate::Result<()> {
247 let amount = self.liquidity_pool()?.amount(is_long_token)?;
248 let max_pool_amount = self.max_pool_amount(is_long_token)?;
249 if amount > max_pool_amount {
250 Err(crate::Error::MaxPoolAmountExceeded(get_msg_by_side(
251 is_long_token,
252 )))
253 } else {
254 Ok(())
255 }
256 }
257
258 fn pnl_factor_exceeded(
262 &self,
263 prices: &Prices<Self::Num>,
264 kind: PnlFactorKind,
265 is_long: bool,
266 ) -> crate::Result<Option<PnlFactorExceeded<Self::Num>>> {
267 let (pnl_factor, pool_value) = self.pnl_factor_with_pool_value(prices, is_long, true)?;
268 let max_pnl_factor = self.pnl_factor_config(kind, is_long)?;
269
270 let is_exceeded = pnl_factor.is_positive() && pnl_factor.unsigned_abs() > max_pnl_factor;
271
272 Ok(is_exceeded.then(|| PnlFactorExceeded {
273 pnl_factor,
274 max_pnl_factor,
275 pool_value,
276 }))
277 }
278
279 fn validate_pnl_factor(
281 &self,
282 prices: &Prices<Self::Num>,
283 kind: PnlFactorKind,
284 is_long: bool,
285 ) -> crate::Result<()> {
286 if self.pnl_factor_exceeded(prices, kind, is_long)?.is_some() {
287 Err(crate::Error::PnlFactorExceeded(
288 kind,
289 get_msg_by_side(is_long),
290 ))
291 } else {
292 Ok(())
293 }
294 }
295
296 fn validate_max_pnl(
298 &self,
299 prices: &Prices<Self::Num>,
300 long_kind: PnlFactorKind,
301 short_kind: PnlFactorKind,
302 ) -> crate::Result<()> {
303 self.validate_pnl_factor(prices, long_kind, true)?;
304 self.validate_pnl_factor(prices, short_kind, false)?;
305 Ok(())
306 }
307
308 fn reserved_value(
310 &self,
311 index_token_price: &Price<Self::Num>,
312 is_long: bool,
313 ) -> crate::Result<Self::Num> {
314 if is_long {
315 self.open_interest_in_tokens()?
322 .long_usd_value(index_token_price.pick_price(true))
323 } else {
324 self.open_interest()?.short_amount()
328 }
329 }
330
331 fn validate_reserve(&self, prices: &Prices<Self::Num>, is_long: bool) -> crate::Result<()> {
333 let pool_value = self.pool_value_without_pnl_for_one_side(prices, is_long, false)?;
334
335 let max_reserved_value =
336 crate::utils::apply_factor(&pool_value, &self.reserve_factor()?)
337 .ok_or(crate::Error::Computation("calculating max reserved value"))?;
338
339 let reserved_value = self.reserved_value(&prices.index_token_price, is_long)?;
340
341 if reserved_value > max_reserved_value {
342 Err(crate::Error::InsufficientReserve(
343 reserved_value.to_string(),
344 max_reserved_value.to_string(),
345 ))
346 } else {
347 Ok(())
348 }
349 }
350
351 fn expected_min_token_balance_excluding_collateral_amount_for_one_token_side(
366 &self,
367 is_long_side: bool,
368 ) -> crate::Result<Self::Num> {
369 let mut balance = self.liquidity_pool()?.amount(is_long_side)?;
371
372 balance = balance
374 .checked_add(&self.swap_impact_pool()?.amount(is_long_side)?)
375 .ok_or(crate::Error::Computation(
376 "overflow adding swap impact pool amount",
377 ))?;
378
379 balance = balance
381 .checked_add(&self.claimable_fee_pool()?.amount(is_long_side)?)
382 .ok_or(crate::Error::Computation(
383 "overflow adding claimable fee amount",
384 ))?;
385
386 Ok(balance)
387 }
388
389 fn total_collateral_amount_for_one_token_side(
395 &self,
396 is_long_side: bool,
397 ) -> crate::Result<Self::Num> {
398 let mut collateral_amount = self.collateral_sum_pool(true)?.amount(is_long_side)?;
399 collateral_amount = collateral_amount
400 .checked_add(&self.collateral_sum_pool(false)?.amount(is_long_side)?)
401 .ok_or(crate::Error::Computation(
402 "calculating total collateral sum for one side",
403 ))?;
404 Ok(collateral_amount)
405 }
406}
407
408impl<M: BaseMarket<DECIMALS> + ?Sized, const DECIMALS: u8> BaseMarketExt<DECIMALS> for M {}
409
410pub trait BaseMarketMutExt<const DECIMALS: u8>: BaseMarketMut<DECIMALS> {
412 fn apply_delta(&mut self, is_long_token: bool, delta: &Self::Signed) -> crate::Result<()> {
414 if is_long_token {
415 self.liquidity_pool_mut()?
416 .apply_delta_to_long_amount(delta)?;
417 } else {
418 self.liquidity_pool_mut()?
419 .apply_delta_to_short_amount(delta)?;
420 }
421 Ok(())
422 }
423
424 fn apply_delta_to_claimable_fee_pool(
426 &mut self,
427 is_long_token: bool,
428 delta: &Self::Signed,
429 ) -> crate::Result<()> {
430 self.claimable_fee_pool_mut()?
431 .apply_delta_amount(is_long_token, delta)?;
432 Ok(())
433 }
434}
435
436impl<M: BaseMarketMut<DECIMALS> + ?Sized, const DECIMALS: u8> BaseMarketMutExt<DECIMALS> for M {}
437
438#[derive(
440 Debug,
441 Clone,
442 Copy,
443 num_enum::TryFromPrimitive,
444 num_enum::IntoPrimitive,
445 PartialEq,
446 Eq,
447 PartialOrd,
448 Ord,
449 Hash,
450)]
451#[cfg_attr(
452 feature = "strum",
453 derive(strum::EnumIter, strum::EnumString, strum::Display)
454)]
455#[cfg_attr(feature = "strum", strum(serialize_all = "snake_case"))]
456#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
457#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
458#[cfg_attr(feature = "js", derive(tsify_next::Tsify))]
459#[repr(u8)]
460#[non_exhaustive]
461pub enum PnlFactorKind {
462 MaxAfterDeposit,
464 MaxAfterWithdrawal,
466 MaxForTrader,
468 ForAdl,
470 MinAfterAdl,
472}
473
474pub struct PnlFactorExceeded<T: Unsigned> {
476 pub pnl_factor: T::Signed,
478 pub max_pnl_factor: T,
480 pub pool_value: T,
482}
483
484impl<T: Unsigned> PnlFactorExceeded<T> {
485 pub fn exceeded_pnl<const DECIMALS: u8>(&self) -> Option<T>
487 where
488 T: CheckedSub,
489 T: FixedPointOps<DECIMALS>,
490 {
491 if !self.pnl_factor.is_positive() || self.pool_value.is_zero() {
492 return None;
493 }
494
495 let pnl_factor = self.pnl_factor.unsigned_abs();
496
497 let diff_factor = pnl_factor.checked_sub(&self.max_pnl_factor)?;
498
499 crate::utils::apply_factor(&self.pool_value, &diff_factor)
500 }
501}