gmsol_model/pool/
mod.rs

1use num_traits::CheckedSub;
2
3use crate::num::Unsigned;
4
5use self::delta::PoolDelta;
6
7/// Balance.
8pub mod balance;
9
10/// Delta.
11pub mod delta;
12
13pub use self::{
14    balance::{Balance, BalanceExt},
15    delta::Delta,
16};
17
18/// A balance for holding tokens, usd values, or factors
19pub trait Pool: Balance + Sized {
20    /// Apply delta to long amount.
21    fn apply_delta_to_long_amount(&mut self, delta: &Self::Signed) -> crate::Result<()> {
22        *self = self.checked_apply_delta(Delta::new_with_long(delta))?;
23        Ok(())
24    }
25
26    /// Apply delta to short amount.
27    fn apply_delta_to_short_amount(&mut self, delta: &Self::Signed) -> crate::Result<()> {
28        *self = self.checked_apply_delta(Delta::new_with_short(delta))?;
29        Ok(())
30    }
31
32    /// Checked apply delta amounts.
33    fn checked_apply_delta(&self, delta: Delta<&Self::Signed>) -> crate::Result<Self>;
34
35    /// Cancel the amounts, for example, (1000, 200) -> (800, 0).
36    /// # Warning
37    /// - Note that the default implementation may lead to unnecessary
38    ///   failures due to the numeric range limitations of signed integers.
39    fn checked_cancel_amounts(&self) -> crate::Result<Self>
40    where
41        Self::Signed: CheckedSub,
42    {
43        let long_amount = self.long_amount()?;
44        let short_amount = self.short_amount()?;
45        let is_long_side_left = long_amount >= short_amount;
46        let leftover_amount = long_amount.clone().diff(short_amount.clone());
47        let (long_delta, short_delta) = if is_long_side_left {
48            (long_amount.diff(leftover_amount), short_amount)
49        } else {
50            (long_amount, short_amount.diff(leftover_amount))
51        };
52        self.checked_apply_delta(Delta::new_both_sides(
53            true,
54            &long_delta.to_opposite_signed()?,
55            &short_delta.to_opposite_signed()?,
56        ))
57    }
58}
59
60/// Extension trait for [`Pool`] with utils.
61pub trait PoolExt: Pool {
62    /// Apply delta.
63    #[inline]
64    fn apply_delta_amount(&mut self, is_long: bool, delta: &Self::Signed) -> crate::Result<()> {
65        if is_long {
66            self.apply_delta_to_long_amount(delta)
67        } else {
68            self.apply_delta_to_short_amount(delta)
69        }
70    }
71}
72
73impl<P: Pool> PoolExt for P {}
74
75/// Pool kind.
76#[derive(
77    Debug,
78    Clone,
79    Copy,
80    Default,
81    num_enum::TryFromPrimitive,
82    num_enum::IntoPrimitive,
83    PartialEq,
84    Eq,
85    PartialOrd,
86    Ord,
87    Hash,
88)]
89#[cfg_attr(
90    feature = "strum",
91    derive(strum::EnumIter, strum::EnumString, strum::Display)
92)]
93#[cfg_attr(feature = "strum", strum(serialize_all = "snake_case"))]
94#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
95#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
96#[cfg_attr(
97    feature = "anchor-lang",
98    derive(
99        anchor_lang::AnchorDeserialize,
100        anchor_lang::AnchorSerialize,
101        anchor_lang::InitSpace
102    )
103)]
104#[repr(u8)]
105#[non_exhaustive]
106pub enum PoolKind {
107    /// Primary liquidity pool.
108    #[default]
109    Primary,
110    /// Swap impact.
111    SwapImpact,
112    /// Claimable fee.
113    ClaimableFee,
114    /// Open Interest for long.
115    OpenInterestForLong,
116    /// Open Interest for short.
117    OpenInterestForShort,
118    /// Open Interest in tokens for long.
119    OpenInterestInTokensForLong,
120    /// Open Interest in tokens for short.
121    OpenInterestInTokensForShort,
122    /// Position impact.
123    PositionImpact,
124    /// Borrowing factor.
125    BorrowingFactor,
126    /// Funding amount per size for long.
127    FundingAmountPerSizeForLong,
128    /// Funding amount per size for short.
129    FundingAmountPerSizeForShort,
130    /// Claimable funding amount per size for long.
131    ClaimableFundingAmountPerSizeForLong,
132    /// Claimable funding amount per size for short.
133    ClaimableFundingAmountPerSizeForShort,
134    /// Collateral sum for long.
135    CollateralSumForLong,
136    /// Collateral sum for short.
137    CollateralSumForShort,
138    /// Total borrowing.
139    TotalBorrowing,
140}
141
142#[cfg(test)]
143mod tests {
144    use crate::{test::TestPool, Balance};
145
146    use super::{Delta, Pool};
147
148    #[test]
149    fn cancel_amounts() -> crate::Result<()> {
150        let pool = TestPool::<u64>::default();
151
152        let pool_1 = pool.checked_apply_delta(Delta::new_both_sides(true, &1_000, &3_000))?;
153        let expected_1 = pool.checked_apply_delta(Delta::new_both_sides(true, &0, &2_000))?;
154        assert_eq!(pool_1.checked_cancel_amounts()?, expected_1);
155
156        let pool_2 = pool.checked_apply_delta(Delta::new_both_sides(true, &3_005, &3_000))?;
157        let expected_2 = pool.checked_apply_delta(Delta::new_both_sides(true, &5, &0))?;
158        assert_eq!(pool_2.checked_cancel_amounts()?, expected_2);
159
160        let pool_3 = pool.checked_apply_delta(Delta::new_both_sides(true, &3_000, &3_000))?;
161        let expected_3 = pool.checked_apply_delta(Delta::new_both_sides(true, &0, &0))?;
162        assert_eq!(pool_3.checked_cancel_amounts()?, expected_3);
163
164        let pool_4 = pool
165            .checked_apply_delta(Delta::new_both_sides(true, &i64::MAX, &i64::MAX))?
166            .checked_apply_delta(Delta::new_both_sides(true, &i64::MAX, &i64::MAX))?
167            .checked_apply_delta(Delta::new_both_sides(true, &1, &1))?;
168        assert_eq!(pool_4.long_amount()?, u64::MAX);
169        assert_eq!(pool_4.short_amount()?, u64::MAX);
170        // Overflow occurs due to the limitations of the default implementation.
171        assert!(pool_4.checked_cancel_amounts().is_err());
172
173        let pool_5 = pool.checked_apply_delta(Delta::new_both_sides(true, &i64::MAX, &i64::MAX))?;
174        let expected_5 = pool.checked_apply_delta(Delta::new_both_sides(true, &0, &0))?;
175        assert_eq!(pool_5.checked_cancel_amounts()?, expected_5);
176        let pool_5 = pool
177            .checked_apply_delta(Delta::new_both_sides(true, &i64::MAX, &i64::MAX))?
178            .checked_apply_delta(Delta::new_both_sides(true, &i64::MAX, &0))?
179            .checked_apply_delta(Delta::new_both_sides(true, &1, &0))?;
180        let expected_5 = pool
181            .checked_apply_delta(Delta::new_both_sides(true, &i64::MAX, &0))?
182            .checked_apply_delta(Delta::new_both_sides(true, &1, &0))?;
183        assert_eq!(pool_5.long_amount()?, u64::MAX);
184        assert_eq!(pool_5.short_amount()?, i64::MAX.unsigned_abs());
185        assert_eq!(pool_5.checked_cancel_amounts()?, expected_5);
186
187        Ok(())
188    }
189}