gmsol_model/market/
base.rs

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
14/// Base Market trait.
15pub trait BaseMarket<const DECIMALS: u8> {
16    /// Unsigned number type used in the market.
17    type Num: MulDiv<Signed = Self::Signed> + FixedPointOps<DECIMALS>;
18
19    /// Signed number type used in the market.
20    type Signed: UnsignedAbs<Unsigned = Self::Num> + TryFrom<Self::Num> + Num;
21
22    /// Pool type.
23    type Pool: Pool<Num = Self::Num, Signed = Self::Signed>;
24
25    /// Get the liquidity pool.
26    fn liquidity_pool(&self) -> crate::Result<&Self::Pool>;
27
28    /// Get the claimable fee pool.
29    fn claimable_fee_pool(&self) -> crate::Result<&Self::Pool>;
30
31    /// Get the swap impact pool.
32    fn swap_impact_pool(&self) -> crate::Result<&Self::Pool>;
33
34    /// Get the open interest pool.
35    fn open_interest_pool(&self, is_long: bool) -> crate::Result<&Self::Pool>;
36
37    /// Get the open interest in (index) tokens pool.
38    fn open_interest_in_tokens_pool(&self, is_long: bool) -> crate::Result<&Self::Pool>;
39
40    /// Get collateral sum pool.
41    fn collateral_sum_pool(&self, is_long: bool) -> crate::Result<&Self::Pool>;
42
43    /// Get virtual inventory for swaps.
44    fn virtual_inventory_for_swaps_pool(
45        &self,
46    ) -> crate::Result<Option<impl Deref<Target = Self::Pool>>>;
47
48    /// Get virtual inventory for positions.
49    fn virtual_inventory_for_positions_pool(
50        &self,
51    ) -> crate::Result<Option<impl Deref<Target = Self::Pool>>>;
52
53    /// USD value to market token amount divisor.
54    ///
55    /// One should make sure it is non-zero.
56    fn usd_to_amount_divisor(&self) -> Self::Num;
57
58    /// Get max pool amount.
59    fn max_pool_amount(&self, is_long_token: bool) -> crate::Result<Self::Num>;
60
61    /// Get pnl factor config.
62    fn pnl_factor_config(&self, kind: PnlFactorKind, is_long: bool) -> crate::Result<Self::Num>;
63
64    /// Get reserve factor.
65    fn reserve_factor(&self) -> crate::Result<Self::Num>;
66
67    /// Get open interest reserve factor.
68    fn open_interest_reserve_factor(&self) -> crate::Result<Self::Num>;
69
70    /// Get max open interest.
71    fn max_open_interest(&self, is_long: bool) -> crate::Result<Self::Num>;
72
73    /// Returns whether ignore open interest for usage factor.
74    fn ignore_open_interest_for_usage_factor(&self) -> crate::Result<bool>;
75}
76
77/// Base Market trait for mutable access.
78pub trait BaseMarketMut<const DECIMALS: u8>: BaseMarket<DECIMALS> {
79    /// Get the liquidity pool mutably.
80    /// # Requirements
81    /// - This method must return `Ok` if [`BaseMarket::liquidity_pool`] does.
82    /// # Notes
83    /// - Avoid using this function directly unless necessary.
84    ///   Use [`BaseMarketMutExt::apply_delta`] instead.
85    fn liquidity_pool_mut(&mut self) -> crate::Result<&mut Self::Pool>;
86
87    /// Get the mutable reference of the claimable fee pool.
88    /// # Requirements
89    /// - This method must return `Ok` if [`BaseMarket::claimable_fee_pool`] does.
90    fn claimable_fee_pool_mut(&mut self) -> crate::Result<&mut Self::Pool>;
91
92    /// Get virtual inventory for swaps mutably.
93    /// # Requirements
94    /// - This method must return `Ok(Some(_))` if [`BaseMarket::virtual_inventory_for_swaps_pool`] does.
95    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
188/// Extension trait for [`BaseMarket`].
189pub trait BaseMarketExt<const DECIMALS: u8>: BaseMarket<DECIMALS> {
190    /// Get the usd value of primary pool without pnl for one side.
191    #[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    /// Get total open interest as a [`Balance`].
208    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    /// Get total open interest in tokens as a merged [`Balance`].
215    ///
216    /// The long amount is the total long open interest in tokens,
217    /// while the short amount is the total short open interest in tokens.
218    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    /// Get total pnl of the market for one side.
225    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    /// Get pnl factor with pool value.
259    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    /// Get pnl factor.
273    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    /// Validate (primary) pool amount.
285    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    /// Get the excess of pending pnl.
298    ///
299    /// Return `Some` if the pnl factor is exceeded the given kind of pnl factor.
300    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    /// Validate pnl factor.
319    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    /// Validate max pnl.
336    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    /// Get reserved value.
348    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            // For longs calculate the reserved USD based on the open interest and current index_token_price.
355            // This works well for e.g. an ETH / USD market with long collateral token as WETH
356            // the available amount to be reserved would scale with the price of ETH.
357            // This also works for e.g. a SOL / USD market with long collateral token as WETH
358            // if the price of SOL increases more than the price of ETH, additional amounts would be
359            // automatically reserved.
360            self.open_interest_in_tokens()?
361                .long_usd_value(index_token_price.pick_price(true))
362        } else {
363            // For shorts use the open interest as the reserved USD value.
364            // This works well for e.g. an ETH / USD market with short collateral token as USDC
365            // the available amount to be reserved would not change with the price of ETH.
366            self.open_interest()?.short_amount()
367        }
368    }
369
370    /// Validate reserve.
371    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    /// Expected min token balance excluding collateral amount.
391    ///
392    /// # Notes
393    /// Note that **"one token side"** here means calculating based on half of the side.
394    /// For markets where the long token and short token are different, there is no ambiguity.
395    /// However, if the long token and short token are the same, choosing the long token side
396    /// will result in a value that is not actually the total amount of the long token,
397    /// but rather the total amount belonging to the long token side (often only half of it).
398    ///
399    /// For example, if both the long token and the short token are WSOL, and the liquidity
400    /// pool has a total of 1000 WSOL. Then, in a typical pool implementation, the long token
401    /// side of the liquidity pool has only 500 **WSOL**, while the short token side also has 500 WSOL.
402    /// In this case, this function will only consider one side, taking into account only 500 WSOL
403    /// in the calculation.
404    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        // Liquidity Pool Amount
409        let mut balance = self.liquidity_pool()?.amount(is_long_side)?;
410
411        // Swap Impact Pool Amount
412        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        // Claimable Fee Pool Amount
419        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    /// Get total collateral amount for one token side.
429    ///
430    /// # Notes
431    /// Note that **"one token side"** here means calculating based on half of the side.
432    /// (See also [`expected_min_token_balance_excluding_collateral_amount_for_one_token_side`](BaseMarketExt::expected_min_token_balance_excluding_collateral_amount_for_one_token_side)).
433    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    /// Returns the liquidity pool and virtual inventory for swaps pool after applying the delta.
447    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
463/// Extension trait for [`BaseMarketMut`].
464pub trait BaseMarketMutExt<const DECIMALS: u8>: BaseMarketMut<DECIMALS> {
465    /// Apply delta to the primary pool.
466    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    /// Apply delta to claimable fee pool.
489    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/// Pnl Factor Kind.
503#[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    /// For deposit.
527    MaxAfterDeposit,
528    /// For withdrawal.
529    MaxAfterWithdrawal,
530    /// For trader.
531    MaxForTrader,
532    /// For auto-deleveraging.
533    ForAdl,
534    /// Min factor after auto-deleveraging.
535    MinAfterAdl,
536}
537
538/// PnL factor exceeded.
539pub struct PnlFactorExceeded<T: Unsigned> {
540    /// Current PnL factor.
541    pub pnl_factor: T::Signed,
542    /// Max PnL factor.
543    pub max_pnl_factor: T,
544    /// Current pool value.
545    pub pool_value: T,
546}
547
548impl<T: Unsigned> PnlFactorExceeded<T> {
549    /// Get the exceeded pnl.
550    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}