gmsol_model/market/
perp.rs

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