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        _cost_amount: &Self::Num,
102        _paid_in_collateral_amount: &Self::Num,
103        _paid_in_secondary_output_amount: &Self::Num,
104        _is_collateral_token_long: bool,
105    ) -> crate::Result<()> {
106        Ok(())
107    }
108}
109
110impl<M: PerpMarket<DECIMALS>, const DECIMALS: u8> PerpMarket<DECIMALS> for &mut M {
111    fn funding_factor_per_second(&self) -> &Self::Signed {
112        (**self).funding_factor_per_second()
113    }
114
115    fn funding_amount_per_size_pool(&self, is_long: bool) -> crate::Result<&Self::Pool> {
116        (**self).funding_amount_per_size_pool(is_long)
117    }
118
119    fn claimable_funding_amount_per_size_pool(&self, is_long: bool) -> crate::Result<&Self::Pool> {
120        (**self).claimable_funding_amount_per_size_pool(is_long)
121    }
122
123    fn funding_amount_per_size_adjustment(&self) -> Self::Num {
124        (**self).funding_amount_per_size_adjustment()
125    }
126
127    fn funding_fee_params(&self) -> crate::Result<FundingFeeParams<Self::Num>> {
128        (**self).funding_fee_params()
129    }
130
131    fn position_params(&self) -> crate::Result<PositionParams<Self::Num>> {
132        (**self).position_params()
133    }
134
135    fn order_fee_params(&self) -> crate::Result<FeeParams<Self::Num>> {
136        (**self).order_fee_params()
137    }
138
139    fn min_collateral_factor_for_open_interest_multiplier(
140        &self,
141        is_long: bool,
142    ) -> crate::Result<Self::Num> {
143        (**self).min_collateral_factor_for_open_interest_multiplier(is_long)
144    }
145
146    fn liquidation_fee_params(&self) -> crate::Result<LiquidationFeeParams<Self::Num>> {
147        (**self).liquidation_fee_params()
148    }
149}
150
151impl<M: PerpMarketMut<DECIMALS>, const DECIMALS: u8> PerpMarketMut<DECIMALS> for &mut M {
152    fn funding_factor_per_second_mut(&mut self) -> &mut Self::Signed {
153        (**self).funding_factor_per_second_mut()
154    }
155
156    fn open_interest_pool_mut(&mut self, is_long: bool) -> crate::Result<&mut Self::Pool> {
157        (**self).open_interest_pool_mut(is_long)
158    }
159
160    fn open_interest_in_tokens_pool_mut(
161        &mut self,
162        is_long: bool,
163    ) -> crate::Result<&mut Self::Pool> {
164        (**self).open_interest_in_tokens_pool_mut(is_long)
165    }
166
167    fn funding_amount_per_size_pool_mut(
168        &mut self,
169        is_long: bool,
170    ) -> crate::Result<&mut Self::Pool> {
171        (**self).funding_amount_per_size_pool_mut(is_long)
172    }
173
174    fn claimable_funding_amount_per_size_pool_mut(
175        &mut self,
176        is_long: bool,
177    ) -> crate::Result<&mut Self::Pool> {
178        (**self).claimable_funding_amount_per_size_pool_mut(is_long)
179    }
180
181    fn collateral_sum_pool_mut(&mut self, is_long: bool) -> crate::Result<&mut Self::Pool> {
182        (**self).collateral_sum_pool_mut(is_long)
183    }
184
185    fn total_borrowing_pool_mut(&mut self) -> crate::Result<&mut Self::Pool> {
186        (**self).total_borrowing_pool_mut()
187    }
188
189    fn just_passed_in_seconds_for_funding(&mut self) -> crate::Result<u64> {
190        (**self).just_passed_in_seconds_for_funding()
191    }
192
193    fn on_insufficient_funding_fee_payment(
194        &mut self,
195        cost_amount: &Self::Num,
196        paid_in_collateral_amount: &Self::Num,
197        paid_in_secondary_output_amount: &Self::Num,
198        is_collateral_token_long: bool,
199    ) -> crate::Result<()> {
200        (**self).on_insufficient_funding_fee_payment(
201            cost_amount,
202            paid_in_collateral_amount,
203            paid_in_secondary_output_amount,
204            is_collateral_token_long,
205        )
206    }
207}
208
209/// Extension trait for [`PerpMarket`].
210pub trait PerpMarketExt<const DECIMALS: u8>: PerpMarket<DECIMALS> {
211    /// Get current funding fee amount per size.
212    #[inline]
213    fn funding_fee_amount_per_size(
214        &self,
215        is_long: bool,
216        is_long_collateral: bool,
217    ) -> crate::Result<Self::Num> {
218        self.funding_amount_per_size_pool(is_long)?
219            .amount(is_long_collateral)
220    }
221
222    /// Get current claimable funding fee amount per size.
223    #[inline]
224    fn claimable_funding_fee_amount_per_size(
225        &self,
226        is_long: bool,
227        is_long_collateral: bool,
228    ) -> crate::Result<Self::Num> {
229        self.claimable_funding_amount_per_size_pool(is_long)?
230            .amount(is_long_collateral)
231    }
232
233    /// Validate open interest reserve.
234    fn validate_open_interest_reserve(
235        &self,
236        prices: &Prices<Self::Num>,
237        is_long: bool,
238    ) -> crate::Result<()> {
239        let pool_value = self.pool_value_without_pnl_for_one_side(prices, is_long, false)?;
240
241        let max_reserved_value =
242            crate::utils::apply_factor(&pool_value, &self.open_interest_reserve_factor()?)
243                .ok_or(crate::Error::Computation("calculating max reserved value"))?;
244
245        let reserved_value = self.reserved_value(&prices.index_token_price, is_long)?;
246
247        if reserved_value > max_reserved_value {
248            Err(crate::Error::InsufficientReserveForOpenInterest(
249                reserved_value.to_string(),
250                max_reserved_value.to_string(),
251            ))
252        } else {
253            Ok(())
254        }
255    }
256
257    /// Get min collateral factor for open interest.
258    fn min_collateral_factor_for_open_interest(
259        &self,
260        delta: &Self::Signed,
261        is_long: bool,
262    ) -> crate::Result<Self::Num> {
263        let next_open_interest = self
264            .open_interest()?
265            .amount(is_long)?
266            .checked_add_with_signed(delta)
267            .ok_or(crate::Error::Computation(
268                "calculating next OI for min collateral factor",
269            ))?;
270        let factor = self.min_collateral_factor_for_open_interest_multiplier(is_long)?;
271        crate::utils::apply_factor(&next_open_interest, &factor).ok_or(crate::Error::Computation(
272            "calculating min collateral factor for OI",
273        ))
274    }
275
276    /// Caps positive position price impact in-place.
277    /// If `impact` is not positive, the function does nothing.
278    fn cap_positive_position_price_impact(
279        &self,
280        index_token_price: &Price<Self::Num>,
281        size_delta_usd: &Self::Signed,
282        impact: &mut Self::Signed,
283    ) -> crate::Result<()> {
284        use crate::{market::PositionImpactMarketExt, num::UnsignedAbs, utils};
285        use num_traits::{CheckedMul, Signed};
286
287        if impact.is_positive() {
288            let impact_pool_amount = self.position_impact_pool_amount()?;
289            // Cap price impact based on pool amount.
290            let max_impact = impact_pool_amount
291                .checked_mul(index_token_price.pick_price(false))
292                .ok_or(crate::Error::Computation(
293                    "overflow calculating max positive position impact based on pool amount",
294                ))?
295                .to_signed()?;
296            if *impact > max_impact {
297                *impact = max_impact;
298            }
299
300            // Cap price impact based on max factor.
301            let params = self.position_params()?;
302            let max_impact_factor = params.max_positive_position_impact_factor();
303            let max_impact = utils::apply_factor(&size_delta_usd.unsigned_abs(), max_impact_factor)
304                .ok_or(crate::Error::Computation(
305                    "calculating max positive position impact based on max factor",
306                ))?
307                .to_signed()?;
308            if *impact > max_impact {
309                *impact = max_impact;
310            }
311        }
312        Ok(())
313    }
314
315    /// Caps negative position price impact in-place.
316    /// If `impact` is not negative, the function does nothing.
317    ///
318    /// # Returns
319    ///
320    /// - The capped amount of the negative `impact`.
321    fn cap_negative_position_price_impact(
322        &self,
323        size_delta_usd: &Self::Signed,
324        for_liquidations: bool,
325        impact: &mut Self::Signed,
326    ) -> crate::Result<Self::Num> {
327        use crate::{num::UnsignedAbs, utils};
328        use num_traits::{CheckedSub, Signed, Zero};
329
330        let mut impact_diff = Zero::zero();
331        if impact.is_negative() {
332            let params = self.position_params()?;
333            let max_impact_factor = if for_liquidations {
334                params.max_position_impact_factor_for_liquidations()
335            } else {
336                params.max_negative_position_impact_factor()
337            };
338            // Although `size_delta_usd` is still used here to calculate the max impact even in the case of liquidation,
339            // partial liquidation is not allowed. Therefore, `size_delta_usd == size_in_usd` always holds,
340            // ensuring consistency with the Solidity version.
341            let min_impact = utils::apply_factor(&size_delta_usd.unsigned_abs(), max_impact_factor)
342                .ok_or(crate::Error::Computation(
343                    "calculating max negative position impact based on max factor",
344                ))?
345                .to_opposite_signed()?;
346            if *impact < min_impact {
347                impact_diff = min_impact
348                    .checked_sub(impact)
349                    .ok_or(crate::Error::Computation(
350                        "overflow calculating impact diff",
351                    ))?
352                    .unsigned_abs();
353                *impact = min_impact;
354            }
355        }
356        Ok(impact_diff)
357    }
358}
359
360impl<M: PerpMarket<DECIMALS>, const DECIMALS: u8> PerpMarketExt<DECIMALS> for M {}
361
362/// Extension trait for [`PerpMarketMut`].
363pub trait PerpMarketMutExt<const DECIMALS: u8>: PerpMarketMut<DECIMALS> {
364    /// Create a [`UpdateFundingState`] action.
365    fn update_funding(
366        &mut self,
367        prices: &Prices<Self::Num>,
368    ) -> crate::Result<UpdateFundingState<&mut Self, DECIMALS>>
369    where
370        Self: Sized,
371    {
372        UpdateFundingState::try_new(self, prices)
373    }
374
375    /// Apply delta to funding amount per size.
376    fn apply_delta_to_funding_amount_per_size(
377        &mut self,
378        is_long: bool,
379        is_long_collateral: bool,
380        delta: &Self::Signed,
381    ) -> crate::Result<()> {
382        self.funding_amount_per_size_pool_mut(is_long)?
383            .apply_delta_amount(is_long_collateral, delta)
384    }
385
386    /// Apply delta to claimable funding amount per size.
387    fn apply_delta_to_claimable_funding_amount_per_size(
388        &mut self,
389        is_long: bool,
390        is_long_collateral: bool,
391        delta: &Self::Signed,
392    ) -> crate::Result<()> {
393        self.claimable_funding_amount_per_size_pool_mut(is_long)?
394            .apply_delta_amount(is_long_collateral, delta)
395    }
396}
397
398impl<M: PerpMarketMut<DECIMALS>, const DECIMALS: u8> PerpMarketMutExt<DECIMALS> for M {}