gmsol_model/market/
base.rs

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
13/// Base Market trait.
14pub trait BaseMarket<const DECIMALS: u8> {
15    /// Unsigned number type used in the market.
16    type Num: MulDiv<Signed = Self::Signed> + FixedPointOps<DECIMALS>;
17
18    /// Signed number type used in the market.
19    type Signed: UnsignedAbs<Unsigned = Self::Num> + TryFrom<Self::Num> + Num;
20
21    /// Pool type.
22    type Pool: Pool<Num = Self::Num, Signed = Self::Signed>;
23
24    /// Get the liquidity pool.
25    fn liquidity_pool(&self) -> crate::Result<&Self::Pool>;
26
27    /// Get the claimable fee pool.
28    fn claimable_fee_pool(&self) -> crate::Result<&Self::Pool>;
29
30    /// Get the swap impact pool.
31    fn swap_impact_pool(&self) -> crate::Result<&Self::Pool>;
32
33    /// Get the open interest pool.
34    fn open_interest_pool(&self, is_long: bool) -> crate::Result<&Self::Pool>;
35
36    /// Get the open interest in (index) tokens pool.
37    fn open_interest_in_tokens_pool(&self, is_long: bool) -> crate::Result<&Self::Pool>;
38
39    /// Get collateral sum pool.
40    fn collateral_sum_pool(&self, is_long: bool) -> crate::Result<&Self::Pool>;
41
42    /// USD value to market token amount divisor.
43    ///
44    /// One should make sure it is non-zero.
45    fn usd_to_amount_divisor(&self) -> Self::Num;
46
47    /// Get max pool amount.
48    fn max_pool_amount(&self, is_long_token: bool) -> crate::Result<Self::Num>;
49
50    /// Get pnl factor config.
51    fn pnl_factor_config(&self, kind: PnlFactorKind, is_long: bool) -> crate::Result<Self::Num>;
52
53    /// Get reserve factor.
54    fn reserve_factor(&self) -> crate::Result<Self::Num>;
55
56    /// Get open interest reserve factor.
57    fn open_interest_reserve_factor(&self) -> crate::Result<Self::Num>;
58
59    /// Get max open interest.
60    fn max_open_interest(&self, is_long: bool) -> crate::Result<Self::Num>;
61
62    /// Returns whether ignore open interest for usage factor.
63    fn ignore_open_interest_for_usage_factor(&self) -> crate::Result<bool>;
64}
65
66/// Base Market trait for mutable access.
67pub trait BaseMarketMut<const DECIMALS: u8>: BaseMarket<DECIMALS> {
68    /// Get the liquidity pool mutably.
69    /// # Requirements
70    /// - This method must return `Ok` if [`BaseMarket::liquidity_pool`] does.
71    fn liquidity_pool_mut(&mut self) -> crate::Result<&mut Self::Pool>;
72
73    /// Get the mutable reference of the claimable fee pool.
74    /// # Requirements
75    /// - This method must return `Ok` if [`BaseMarket::claimable_fee_pool`] does.
76    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
149/// Extension trait for [`BaseMarket`].
150pub trait BaseMarketExt<const DECIMALS: u8>: BaseMarket<DECIMALS> {
151    /// Get the usd value of primary pool without pnl for one side.
152    #[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    /// Get total open interest as a [`Balance`].
169    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    /// Get total open interest in tokens as a merged [`Balance`].
176    ///
177    /// The long amount is the total long open interest in tokens,
178    /// while the short amount is the total short open interest in tokens.
179    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    /// Get total pnl of the market for one side.
186    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    /// Get pnl factor with pool value.
220    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    /// Get pnl factor.
234    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    /// Validate (primary) pool amount.
246    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    /// Get the excess of pending pnl.
259    ///
260    /// Return `Some` if the pnl factor is exceeded the given kind of pnl factor.
261    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    /// Validate pnl factor.
280    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    /// Validate max pnl.
297    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    /// Get reserved value.
309    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            // For longs calculate the reserved USD based on the open interest and current index_token_price.
316            // This works well for e.g. an ETH / USD market with long collateral token as WETH
317            // the available amount to be reserved would scale with the price of ETH.
318            // This also works for e.g. a SOL / USD market with long collateral token as WETH
319            // if the price of SOL increases more than the price of ETH, additional amounts would be
320            // automatically reserved.
321            self.open_interest_in_tokens()?
322                .long_usd_value(index_token_price.pick_price(true))
323        } else {
324            // For shorts use the open interest as the reserved USD value.
325            // This works well for e.g. an ETH / USD market with short collateral token as USDC
326            // the available amount to be reserved would not change with the price of ETH.
327            self.open_interest()?.short_amount()
328        }
329    }
330
331    /// Validate reserve.
332    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    /// Expected min token balance excluding collateral amount.
352    ///
353    /// # Notes
354    /// Note that **"one token side"** here means calculating based on half of the side.
355    /// For markets where the long token and short token are different, there is no ambiguity.
356    /// However, if the long token and short token are the same, choosing the long token side
357    /// will result in a value that is not actually the total amount of the long token,
358    /// but rather the total amount belonging to the long token side (often only half of it).
359    ///
360    /// For example, if both the long token and the short token are WSOL, and the liquidity
361    /// pool has a total of 1000 WSOL. Then, in a typical pool implementation, the long token
362    /// side of the liquidity pool has only 500 **WSOL**, while the short token side also has 500 WSOL.
363    /// In this case, this function will only consider one side, taking into account only 500 WSOL
364    /// in the calculation.
365    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        // Liquidity Pool Amount
370        let mut balance = self.liquidity_pool()?.amount(is_long_side)?;
371
372        // Swap Impact Pool Amount
373        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        // Claimable Fee Pool Amount
380        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    /// Get total collateral amount for one token side.
390    ///
391    /// # Notes
392    /// Note that **"one token side"** here means calculating based on half of the side.
393    /// (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)).
394    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
410/// Extension trait for [`BaseMarketMut`].
411pub trait BaseMarketMutExt<const DECIMALS: u8>: BaseMarketMut<DECIMALS> {
412    /// Apply delta to the primary pool.
413    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    /// Apply delta to claimable fee pool.
425    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/// Pnl Factor Kind.
439#[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    /// For deposit.
463    MaxAfterDeposit,
464    /// For withdrawal.
465    MaxAfterWithdrawal,
466    /// For trader.
467    MaxForTrader,
468    /// For auto-deleveraging.
469    ForAdl,
470    /// Min factor after auto-deleveraging.
471    MinAfterAdl,
472}
473
474/// PnL factor exceeded.
475pub struct PnlFactorExceeded<T: Unsigned> {
476    /// Current PnL factor.
477    pub pnl_factor: T::Signed,
478    /// Max PnL factor.
479    pub max_pnl_factor: T,
480    /// Current pool value.
481    pub pool_value: T,
482}
483
484impl<T: Unsigned> PnlFactorExceeded<T> {
485    /// Get the exceeded pnl.
486    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}