gmsol_model/market/
perp.rs

1use crate::{
2    action::update_funding_state::UpdateFundingState,
3    num::Unsigned,
4    params::{
5        fee::{FundingFeeParams, LiquidationFeeParams},
6        FeeParams, PositionParams,
7    },
8    price::{Price, Prices},
9    BalanceExt, BorrowingFeeMarket, PoolExt, PositionImpactMarket, PositionImpactMarketMut,
10    SwapMarket, SwapMarketMut,
11};
12
13use super::BaseMarketExt;
14
15/// A perpetual market.
16pub trait PerpMarket<const DECIMALS: u8>:
17    SwapMarket<DECIMALS> + PositionImpactMarket<DECIMALS> + BorrowingFeeMarket<DECIMALS>
18{
19    /// Get funding factor per second.
20    fn funding_factor_per_second(&self) -> &Self::Signed;
21
22    /// Get funding amount per size pool.
23    fn funding_amount_per_size_pool(&self, is_long: bool) -> crate::Result<&Self::Pool>;
24
25    /// Get claimable funding amount per size pool.
26    fn claimable_funding_amount_per_size_pool(&self, is_long: bool) -> crate::Result<&Self::Pool>;
27
28    /// Adjustment factor for packing funding amount per size.
29    fn funding_amount_per_size_adjustment(&self) -> Self::Num;
30
31    /// Get funding fee params.
32    fn funding_fee_params(&self) -> crate::Result<FundingFeeParams<Self::Num>>;
33
34    /// Get basic position params.
35    fn position_params(&self) -> crate::Result<PositionParams<Self::Num>>;
36
37    /// Get the order fee params.
38    fn order_fee_params(&self) -> crate::Result<FeeParams<Self::Num>>;
39
40    /// Get min collateral factor for open interest multiplier.
41    fn min_collateral_factor_for_open_interest_multiplier(
42        &self,
43        is_long: bool,
44    ) -> crate::Result<Self::Num>;
45
46    /// Get liquidation fee params.
47    fn liquidation_fee_params(&self) -> crate::Result<LiquidationFeeParams<Self::Num>>;
48}
49
50/// A mutable perpetual market.
51pub trait PerpMarketMut<const DECIMALS: u8>:
52    SwapMarketMut<DECIMALS> + PositionImpactMarketMut<DECIMALS> + PerpMarket<DECIMALS>
53{
54    /// Get the just passed time in seconds for the given kind of clock.
55    fn just_passed_in_seconds_for_funding(&mut self) -> crate::Result<u64>;
56
57    /// Get funding factor per second mutably.
58    fn funding_factor_per_second_mut(&mut self) -> &mut Self::Signed;
59
60    /// Get mutable reference of open interest pool.
61    /// # Requirements
62    /// - This method must return `Ok` if
63    ///   [`BaseMarket::open_interest_pool`](crate::BaseMarket::open_interest_pool) does.
64    fn open_interest_pool_mut(&mut self, is_long: bool) -> crate::Result<&mut Self::Pool>;
65
66    /// Get mutable reference of open interest pool.
67    /// # Requirements
68    /// - This method must return `Ok` if
69    ///   [`BaseMarket::open_interest_in_tokens_pool`](crate::BaseMarket::open_interest_in_tokens_pool) does.
70    fn open_interest_in_tokens_pool_mut(&mut self, is_long: bool)
71        -> crate::Result<&mut Self::Pool>;
72
73    /// Get funding amount per size pool mutably.
74    /// # Requirements
75    /// - This method must return `Ok` if [`PerpMarket::funding_amount_per_size_pool`] does.
76    fn funding_amount_per_size_pool_mut(&mut self, is_long: bool)
77        -> crate::Result<&mut Self::Pool>;
78
79    /// Get claimable funding amount per size pool mutably.
80    /// # Requirements
81    /// - This method must return `Ok` if [`PerpMarket::claimable_funding_amount_per_size_pool`] does.
82    fn claimable_funding_amount_per_size_pool_mut(
83        &mut self,
84        is_long: bool,
85    ) -> crate::Result<&mut Self::Pool>;
86
87    /// Get collateral sum pool mutably.
88    /// # Requirements
89    /// - This method must return `Ok` if
90    ///   [`BaseMarket::collateral_sum_pool`](crate::BaseMarket::collateral_sum_pool) does.
91    fn collateral_sum_pool_mut(&mut self, is_long: bool) -> crate::Result<&mut Self::Pool>;
92
93    /// Get total borrowing pool mutably.
94    /// # Requirements
95    /// - This method must return `Ok` if [`BorrowingFeeMarket::total_borrowing_pool`] does.
96    fn total_borrowing_pool_mut(&mut self) -> crate::Result<&mut Self::Pool>;
97
98    /// Insufficient funding fee payment callback.
99    fn on_insufficient_funding_fee_payment(
100        &mut self,
101        _paid_in_collateral_amount: &Self::Num,
102        _cost_amount: &Self::Num,
103    ) -> crate::Result<()> {
104        Ok(())
105    }
106}
107
108impl<M: PerpMarket<DECIMALS>, const DECIMALS: u8> PerpMarket<DECIMALS> for &mut M {
109    fn funding_factor_per_second(&self) -> &Self::Signed {
110        (**self).funding_factor_per_second()
111    }
112
113    fn funding_amount_per_size_pool(&self, is_long: bool) -> crate::Result<&Self::Pool> {
114        (**self).funding_amount_per_size_pool(is_long)
115    }
116
117    fn claimable_funding_amount_per_size_pool(&self, is_long: bool) -> crate::Result<&Self::Pool> {
118        (**self).claimable_funding_amount_per_size_pool(is_long)
119    }
120
121    fn funding_amount_per_size_adjustment(&self) -> Self::Num {
122        (**self).funding_amount_per_size_adjustment()
123    }
124
125    fn funding_fee_params(&self) -> crate::Result<FundingFeeParams<Self::Num>> {
126        (**self).funding_fee_params()
127    }
128
129    fn position_params(&self) -> crate::Result<PositionParams<Self::Num>> {
130        (**self).position_params()
131    }
132
133    fn order_fee_params(&self) -> crate::Result<FeeParams<Self::Num>> {
134        (**self).order_fee_params()
135    }
136
137    fn min_collateral_factor_for_open_interest_multiplier(
138        &self,
139        is_long: bool,
140    ) -> crate::Result<Self::Num> {
141        (**self).min_collateral_factor_for_open_interest_multiplier(is_long)
142    }
143
144    fn liquidation_fee_params(&self) -> crate::Result<LiquidationFeeParams<Self::Num>> {
145        (**self).liquidation_fee_params()
146    }
147}
148
149impl<M: PerpMarketMut<DECIMALS>, const DECIMALS: u8> PerpMarketMut<DECIMALS> for &mut M {
150    fn funding_factor_per_second_mut(&mut self) -> &mut Self::Signed {
151        (**self).funding_factor_per_second_mut()
152    }
153
154    fn open_interest_pool_mut(&mut self, is_long: bool) -> crate::Result<&mut Self::Pool> {
155        (**self).open_interest_pool_mut(is_long)
156    }
157
158    fn open_interest_in_tokens_pool_mut(
159        &mut self,
160        is_long: bool,
161    ) -> crate::Result<&mut Self::Pool> {
162        (**self).open_interest_in_tokens_pool_mut(is_long)
163    }
164
165    fn funding_amount_per_size_pool_mut(
166        &mut self,
167        is_long: bool,
168    ) -> crate::Result<&mut Self::Pool> {
169        (**self).funding_amount_per_size_pool_mut(is_long)
170    }
171
172    fn claimable_funding_amount_per_size_pool_mut(
173        &mut self,
174        is_long: bool,
175    ) -> crate::Result<&mut Self::Pool> {
176        (**self).claimable_funding_amount_per_size_pool_mut(is_long)
177    }
178
179    fn collateral_sum_pool_mut(&mut self, is_long: bool) -> crate::Result<&mut Self::Pool> {
180        (**self).collateral_sum_pool_mut(is_long)
181    }
182
183    fn total_borrowing_pool_mut(&mut self) -> crate::Result<&mut Self::Pool> {
184        (**self).total_borrowing_pool_mut()
185    }
186
187    fn just_passed_in_seconds_for_funding(&mut self) -> crate::Result<u64> {
188        (**self).just_passed_in_seconds_for_funding()
189    }
190
191    fn on_insufficient_funding_fee_payment(
192        &mut self,
193        paid_in_collateral_amount: &Self::Num,
194        cost_amount: &Self::Num,
195    ) -> crate::Result<()> {
196        (**self).on_insufficient_funding_fee_payment(paid_in_collateral_amount, cost_amount)
197    }
198}
199
200/// Extension trait for [`PerpMarket`].
201pub trait PerpMarketExt<const DECIMALS: u8>: PerpMarket<DECIMALS> {
202    /// Get current funding fee amount per size.
203    #[inline]
204    fn funding_fee_amount_per_size(
205        &self,
206        is_long: bool,
207        is_long_collateral: bool,
208    ) -> crate::Result<Self::Num> {
209        self.funding_amount_per_size_pool(is_long)?
210            .amount(is_long_collateral)
211    }
212
213    /// Get current claimable funding fee amount per size.
214    #[inline]
215    fn claimable_funding_fee_amount_per_size(
216        &self,
217        is_long: bool,
218        is_long_collateral: bool,
219    ) -> crate::Result<Self::Num> {
220        self.claimable_funding_amount_per_size_pool(is_long)?
221            .amount(is_long_collateral)
222    }
223
224    /// Validate open interest reserve.
225    fn validate_open_interest_reserve(
226        &self,
227        prices: &Prices<Self::Num>,
228        is_long: bool,
229    ) -> crate::Result<()> {
230        let pool_value = self.pool_value_without_pnl_for_one_side(prices, is_long, false)?;
231
232        let max_reserved_value =
233            crate::utils::apply_factor(&pool_value, &self.open_interest_reserve_factor()?)
234                .ok_or(crate::Error::Computation("calculating max reserved value"))?;
235
236        let reserved_value = self.reserved_value(&prices.index_token_price, is_long)?;
237
238        if reserved_value > max_reserved_value {
239            Err(crate::Error::InsufficientReserveForOpenInterest(
240                reserved_value.to_string(),
241                max_reserved_value.to_string(),
242            ))
243        } else {
244            Ok(())
245        }
246    }
247
248    /// Get min collateral factor for open interest.
249    fn min_collateral_factor_for_open_interest(
250        &self,
251        delta: &Self::Signed,
252        is_long: bool,
253    ) -> crate::Result<Self::Num> {
254        let next_open_interest = self
255            .open_interest()?
256            .amount(is_long)?
257            .checked_add_with_signed(delta)
258            .ok_or(crate::Error::Computation(
259                "calculating next OI for min collateral factor",
260            ))?;
261        let factor = self.min_collateral_factor_for_open_interest_multiplier(is_long)?;
262        crate::utils::apply_factor(&next_open_interest, &factor).ok_or(crate::Error::Computation(
263            "calculating min collateral factor for OI",
264        ))
265    }
266
267    /// Caps positive position price impact in-place.
268    /// If `impact` is not positive, the function does nothing.
269    fn cap_positive_position_price_impact(
270        &self,
271        index_token_price: &Price<Self::Num>,
272        size_delta_usd: &Self::Signed,
273        impact: &mut Self::Signed,
274    ) -> crate::Result<()> {
275        use crate::{market::PositionImpactMarketExt, num::UnsignedAbs, utils};
276        use num_traits::{CheckedMul, Signed};
277
278        if impact.is_positive() {
279            let impact_pool_amount = self.position_impact_pool_amount()?;
280            // Cap price impact based on pool amount.
281            let max_impact = impact_pool_amount
282                .checked_mul(index_token_price.pick_price(false))
283                .ok_or(crate::Error::Computation(
284                    "overflow calculating max positive position impact based on pool amount",
285                ))?
286                .to_signed()?;
287            if *impact > max_impact {
288                *impact = max_impact;
289            }
290
291            // Cap price impact based on max factor.
292            let params = self.position_params()?;
293            let max_impact_factor = params.max_positive_position_impact_factor();
294            let max_impact = utils::apply_factor(&size_delta_usd.unsigned_abs(), max_impact_factor)
295                .ok_or(crate::Error::Computation(
296                    "calculating max positive position impact based on max factor",
297                ))?
298                .to_signed()?;
299            if *impact > max_impact {
300                *impact = max_impact;
301            }
302        }
303        Ok(())
304    }
305
306    /// Caps negative position price impact in-place.
307    /// If `impact` is not negative, the function does nothing.
308    ///
309    /// # Returns
310    ///
311    /// - The capped amount of the negative `impact`.
312    fn cap_negative_position_price_impact(
313        &self,
314        size_delta_usd: &Self::Signed,
315        for_liquidations: bool,
316        impact: &mut Self::Signed,
317    ) -> crate::Result<Self::Num> {
318        use crate::{num::UnsignedAbs, utils};
319        use num_traits::{CheckedSub, Signed, Zero};
320
321        let mut impact_diff = Zero::zero();
322        if impact.is_negative() {
323            let params = self.position_params()?;
324            let max_impact_factor = if for_liquidations {
325                params.max_position_impact_factor_for_liquidations()
326            } else {
327                params.max_negative_position_impact_factor()
328            };
329            // Although `size_delta_usd` is still used here to calculate the max impact even in the case of liquidation,
330            // partial liquidation is not allowed. Therefore, `size_delta_usd == size_in_usd` always holds,
331            // ensuring consistency with the Solidity version.
332            let min_impact = utils::apply_factor(&size_delta_usd.unsigned_abs(), max_impact_factor)
333                .ok_or(crate::Error::Computation(
334                    "calculating max negative position impact based on max factor",
335                ))?
336                .to_opposite_signed()?;
337            if *impact < min_impact {
338                impact_diff = min_impact
339                    .checked_sub(impact)
340                    .ok_or(crate::Error::Computation(
341                        "overflow calculating impact diff",
342                    ))?
343                    .unsigned_abs();
344                *impact = min_impact;
345            }
346        }
347        Ok(impact_diff)
348    }
349}
350
351impl<M: PerpMarket<DECIMALS>, const DECIMALS: u8> PerpMarketExt<DECIMALS> for M {}
352
353/// Extension trait for [`PerpMarketMut`].
354pub trait PerpMarketMutExt<const DECIMALS: u8>: PerpMarketMut<DECIMALS> {
355    /// Create a [`UpdateFundingState`] action.
356    fn update_funding(
357        &mut self,
358        prices: &Prices<Self::Num>,
359    ) -> crate::Result<UpdateFundingState<&mut Self, DECIMALS>>
360    where
361        Self: Sized,
362    {
363        UpdateFundingState::try_new(self, prices)
364    }
365
366    /// Apply delta to funding amount per size.
367    fn apply_delta_to_funding_amount_per_size(
368        &mut self,
369        is_long: bool,
370        is_long_collateral: bool,
371        delta: &Self::Signed,
372    ) -> crate::Result<()> {
373        self.funding_amount_per_size_pool_mut(is_long)?
374            .apply_delta_amount(is_long_collateral, delta)
375    }
376
377    /// Apply delta to claimable funding amount per size.
378    fn apply_delta_to_claimable_funding_amount_per_size(
379        &mut self,
380        is_long: bool,
381        is_long_collateral: bool,
382        delta: &Self::Signed,
383    ) -> crate::Result<()> {
384        self.claimable_funding_amount_per_size_pool_mut(is_long)?
385            .apply_delta_amount(is_long_collateral, delta)
386    }
387}
388
389impl<M: PerpMarketMut<DECIMALS>, const DECIMALS: u8> PerpMarketMutExt<DECIMALS> for M {}