gmsol_model/
position.rs

1use std::{fmt, ops::Deref};
2
3use num_traits::{One, Signed, Zero};
4
5use crate::{
6    action::{
7        decrease_position::{DecreasePosition, DecreasePositionFlags, DecreasePositionSwapType},
8        increase_position::IncreasePosition,
9        swap::SwapReport,
10        update_funding_state::unpack_to_funding_amount_delta,
11    },
12    fixed::FixedPointOps,
13    market::{
14        utils::MarketUtils, BaseMarketExt, BorrowingFeeMarket, BorrowingFeeMarketExt, PerpMarket,
15        PerpMarketExt, PositionImpactMarket,
16    },
17    num::{MulDiv, Num, Unsigned, UnsignedAbs},
18    params::fee::{FundingFees, PositionFees},
19    price::{Price, Prices},
20    Balance, BalanceExt, BaseMarket, PerpMarketMut, PnlFactorKind, Pool, PoolExt,
21};
22
23/// Read-only access to the position state.
24pub trait PositionState<const DECIMALS: u8> {
25    /// Unsigned number type.
26    type Num: MulDiv<Signed = Self::Signed> + FixedPointOps<DECIMALS>;
27
28    /// Signed number type.
29    type Signed: UnsignedAbs<Unsigned = Self::Num> + TryFrom<Self::Num> + Num;
30
31    /// Get the collateral amount.
32    fn collateral_amount(&self) -> &Self::Num;
33
34    /// Get a reference to the size (in USD) of the position.
35    fn size_in_usd(&self) -> &Self::Num;
36
37    /// Get a reference to the size (in tokens) of the position.
38    fn size_in_tokens(&self) -> &Self::Num;
39
40    /// Get a reference to last borrowing factor applied by the position.
41    fn borrowing_factor(&self) -> &Self::Num;
42
43    /// Get a reference to the funding fee amount per size.
44    fn funding_fee_amount_per_size(&self) -> &Self::Num;
45
46    /// Get a reference to claimable funding fee amount per size of the given collateral.
47    fn claimable_funding_fee_amount_per_size(&self, is_long_collateral: bool) -> &Self::Num;
48}
49
50/// Mutable access to the position state.
51pub trait PositionStateMut<const DECIMALS: u8>: PositionState<DECIMALS> {
52    /// Get a mutable reference to the collateral amount.
53    fn collateral_amount_mut(&mut self) -> &mut Self::Num;
54
55    /// Get a mutable reference to the size (in USD) of the position.
56    fn size_in_usd_mut(&mut self) -> &mut Self::Num;
57
58    /// Get a mutable reference to the size (in tokens) of the position.
59    fn size_in_tokens_mut(&mut self) -> &mut Self::Num;
60
61    /// Get a mutable reference to last borrowing factor applied by the position.
62    fn borrowing_factor_mut(&mut self) -> &mut Self::Num;
63
64    /// Get a mutable reference to the funding fee amount per size.
65    fn funding_fee_amount_per_size_mut(&mut self) -> &mut Self::Num;
66
67    /// Get a mutable reference to claimable funding fee amount per size of the given collateral.
68    fn claimable_funding_fee_amount_per_size_mut(
69        &mut self,
70        is_long_collateral: bool,
71    ) -> &mut Self::Num;
72}
73
74/// Position with access to its market.
75pub trait Position<const DECIMALS: u8>: PositionState<DECIMALS> {
76    /// Market type.
77    type Market: PerpMarket<DECIMALS, Num = Self::Num, Signed = Self::Signed>;
78
79    /// Get a reference to the market.
80    fn market(&self) -> &Self::Market;
81
82    /// Returns whether the position is a long position.
83    fn is_long(&self) -> bool;
84
85    /// Returns whether the collateral token is the long token of the market.
86    fn is_collateral_token_long(&self) -> bool;
87
88    /// Returns whether the pnl and collateral tokens are the same.
89    fn are_pnl_and_collateral_tokens_the_same(&self) -> bool;
90
91    /// Called from `validate_position` to add supplementary checks.
92    fn on_validate(&self) -> crate::Result<()>;
93}
94
95/// Position with mutable access.
96pub trait PositionMut<const DECIMALS: u8>: Position<DECIMALS> + PositionStateMut<DECIMALS> {
97    /// Get a mutable reference to the market.
98    fn market_mut(&mut self) -> &mut Self::Market;
99
100    /// Increased callback.
101    fn on_increased(&mut self) -> crate::Result<()>;
102
103    /// Decreased callback.
104    fn on_decreased(&mut self) -> crate::Result<()>;
105
106    /// Swapped callback.
107    fn on_swapped(
108        &mut self,
109        ty: DecreasePositionSwapType,
110        report: &SwapReport<Self::Num, <Self::Num as Unsigned>::Signed>,
111    ) -> crate::Result<()>;
112
113    /// Handle swap error.
114    fn on_swap_error(
115        &mut self,
116        ty: DecreasePositionSwapType,
117        error: crate::Error,
118    ) -> crate::Result<()>;
119}
120
121impl<const DECIMALS: u8, P: PositionState<DECIMALS>> PositionState<DECIMALS> for &mut P {
122    type Num = P::Num;
123
124    type Signed = P::Signed;
125
126    fn collateral_amount(&self) -> &Self::Num {
127        (**self).collateral_amount()
128    }
129
130    fn size_in_usd(&self) -> &Self::Num {
131        (**self).size_in_usd()
132    }
133
134    fn size_in_tokens(&self) -> &Self::Num {
135        (**self).size_in_tokens()
136    }
137
138    fn borrowing_factor(&self) -> &Self::Num {
139        (**self).borrowing_factor()
140    }
141
142    fn funding_fee_amount_per_size(&self) -> &Self::Num {
143        (**self).funding_fee_amount_per_size()
144    }
145
146    fn claimable_funding_fee_amount_per_size(&self, is_long_collateral: bool) -> &Self::Num {
147        (**self).claimable_funding_fee_amount_per_size(is_long_collateral)
148    }
149}
150
151impl<const DECIMALS: u8, P: Position<DECIMALS>> Position<DECIMALS> for &mut P {
152    type Market = P::Market;
153
154    fn market(&self) -> &Self::Market {
155        (**self).market()
156    }
157
158    fn is_long(&self) -> bool {
159        (**self).is_long()
160    }
161
162    fn is_collateral_token_long(&self) -> bool {
163        (**self).is_collateral_token_long()
164    }
165
166    fn are_pnl_and_collateral_tokens_the_same(&self) -> bool {
167        (**self).are_pnl_and_collateral_tokens_the_same()
168    }
169
170    fn on_validate(&self) -> crate::Result<()> {
171        (**self).on_validate()
172    }
173}
174
175impl<const DECIMALS: u8, P: PositionStateMut<DECIMALS>> PositionStateMut<DECIMALS> for &mut P {
176    fn collateral_amount_mut(&mut self) -> &mut Self::Num {
177        (**self).collateral_amount_mut()
178    }
179
180    fn size_in_usd_mut(&mut self) -> &mut Self::Num {
181        (**self).size_in_usd_mut()
182    }
183
184    fn size_in_tokens_mut(&mut self) -> &mut Self::Num {
185        (**self).size_in_tokens_mut()
186    }
187
188    fn borrowing_factor_mut(&mut self) -> &mut Self::Num {
189        (**self).borrowing_factor_mut()
190    }
191
192    fn funding_fee_amount_per_size_mut(&mut self) -> &mut Self::Num {
193        (**self).funding_fee_amount_per_size_mut()
194    }
195
196    fn claimable_funding_fee_amount_per_size_mut(
197        &mut self,
198        is_long_collateral: bool,
199    ) -> &mut Self::Num {
200        (**self).claimable_funding_fee_amount_per_size_mut(is_long_collateral)
201    }
202}
203
204impl<const DECIMALS: u8, P: PositionMut<DECIMALS>> PositionMut<DECIMALS> for &mut P {
205    fn market_mut(&mut self) -> &mut Self::Market {
206        (**self).market_mut()
207    }
208
209    fn on_increased(&mut self) -> crate::Result<()> {
210        (**self).on_increased()
211    }
212
213    fn on_decreased(&mut self) -> crate::Result<()> {
214        (**self).on_decreased()
215    }
216
217    fn on_swapped(
218        &mut self,
219        ty: DecreasePositionSwapType,
220        report: &SwapReport<Self::Num, <Self::Num as Unsigned>::Signed>,
221    ) -> crate::Result<()> {
222        (**self).on_swapped(ty, report)
223    }
224
225    fn on_swap_error(
226        &mut self,
227        ty: DecreasePositionSwapType,
228        error: crate::Error,
229    ) -> crate::Result<()> {
230        (**self).on_swap_error(ty, error)
231    }
232}
233
234/// Extension trait for [`PositionState`].
235pub trait PositionStateExt<const DECIMALS: u8>: PositionState<DECIMALS> {
236    /// Return whether the position is considered to be empty **during the decrease position action**.
237    fn is_empty(&self) -> bool {
238        self.size_in_usd().is_zero()
239            && self.size_in_tokens().is_zero()
240            && self.collateral_amount().is_zero()
241    }
242}
243
244impl<const DECIMALS: u8, P: PositionState<DECIMALS> + ?Sized> PositionStateExt<DECIMALS> for P {}
245
246/// Extension trait for [`Position`] with utils.
247pub trait PositionExt<const DECIMALS: u8>: Position<DECIMALS> {
248    /// Check that whether the collateral will be sufficient after paying the given `realized_pnl` and applying `delta_size`.
249    ///
250    /// - Returns the remaining collateral value if sufficient, `None` otherwise.
251    /// - Returns `Err` if failed to finish the calculation.
252    fn will_collateral_be_sufficient(
253        &self,
254        prices: &Prices<Self::Num>,
255        delta: &CollateralDelta<Self::Num>,
256    ) -> crate::Result<WillCollateralBeSufficient<Self::Signed>> {
257        use num_traits::{CheckedAdd, CheckedMul};
258
259        let collateral_price = self.collateral_price(prices);
260
261        let mut remaining_collateral_value = delta
262            .next_collateral_amount
263            .checked_mul(collateral_price.pick_price(false))
264            .ok_or(crate::Error::Computation(
265                "overflow calculating collateral value",
266            ))?
267            .to_signed()?;
268
269        if delta.realized_pnl_value.is_negative() {
270            remaining_collateral_value = remaining_collateral_value
271                .checked_add(&delta.realized_pnl_value)
272                .ok_or(crate::Error::Computation("adding realized pnl"))?;
273        }
274
275        if remaining_collateral_value.is_negative() {
276            return Ok(WillCollateralBeSufficient::Insufficient(
277                remaining_collateral_value,
278            ));
279        }
280
281        let min_collateral_factor = self
282            .market()
283            .min_collateral_factor_for_open_interest(&delta.open_interest_delta, self.is_long())?
284            .max(
285                self.market()
286                    .position_params()?
287                    .min_collateral_factor()
288                    .clone(),
289            );
290
291        match check_collateral(
292            &delta.next_size_in_usd,
293            &min_collateral_factor,
294            None,
295            true,
296            &remaining_collateral_value,
297        )? {
298            CheckCollateralResult::Sufficient => Ok(WillCollateralBeSufficient::Sufficient(
299                remaining_collateral_value,
300            )),
301            CheckCollateralResult::Negative | CheckCollateralResult::MinCollateralForLeverage => {
302                Ok(WillCollateralBeSufficient::Insufficient(
303                    remaining_collateral_value,
304                ))
305            }
306            CheckCollateralResult::MinCollateral | CheckCollateralResult::Zero => unreachable!(),
307        }
308    }
309
310    /// Get collateral price.
311    fn collateral_price<'a>(&self, prices: &'a Prices<Self::Num>) -> &'a Price<Self::Num> {
312        if self.is_collateral_token_long() {
313            &prices.long_token_price
314        } else {
315            &prices.short_token_price
316        }
317    }
318
319    /// Get collateral value.
320    fn collateral_value(&self, prices: &Prices<Self::Num>) -> crate::Result<Self::Num> {
321        use num_traits::CheckedMul;
322
323        let collateral_token_price = self.collateral_price(prices).pick_price(false);
324
325        let collateral_value = self
326            .collateral_amount()
327            .checked_mul(collateral_token_price)
328            .ok_or(crate::Error::Computation(
329                "overflow calculating collateral value",
330            ))?;
331
332        Ok(collateral_value)
333    }
334
335    /// Calculate the pnl value when decreased by the given delta size.
336    ///
337    /// Returns `(pnl_value, uncapped_pnl_value, size_delta_in_tokens)`
338    fn pnl_value(
339        &self,
340        prices: &Prices<Self::Num>,
341        size_delta_usd: &Self::Num,
342    ) -> crate::Result<(Self::Signed, Self::Signed, Self::Num)> {
343        use num_traits::{CheckedMul, CheckedSub};
344
345        let execution_price = &prices
346            .index_token_price
347            .pick_price_for_pnl(self.is_long(), false);
348
349        let position_value: Self::Signed = self
350            .size_in_tokens()
351            .checked_mul(execution_price)
352            .ok_or(crate::Error::Computation(
353                "overflow calculating position value",
354            ))?
355            .try_into()
356            .map_err(|_| crate::Error::Convert)?;
357        let size_in_usd = self
358            .size_in_usd()
359            .clone()
360            .try_into()
361            .map_err(|_| crate::Error::Convert)?;
362        let mut total_pnl = if self.is_long() {
363            position_value.checked_sub(&size_in_usd)
364        } else {
365            size_in_usd.checked_sub(&position_value)
366        }
367        .ok_or(crate::Error::Computation("calculating total pnl"))?;
368        let uncapped_total_pnl = total_pnl.clone();
369
370        if total_pnl.is_positive() {
371            let pool_value =
372                self.market()
373                    .pool_value_without_pnl_for_one_side(prices, self.is_long(), false)?;
374            let pool_pnl = self
375                .market()
376                .pnl(&prices.index_token_price, self.is_long(), true)?;
377            let capped_pool_pnl = self.market().cap_pnl(
378                self.is_long(),
379                &pool_pnl,
380                &pool_value,
381                PnlFactorKind::MaxForTrader,
382            )?;
383
384            // Note: If the PnL is capped at zero, it can still pass this test.
385            // See the `test_zero_max_pnl_factor_for_trader` test for more details.
386            if capped_pool_pnl != pool_pnl
387                && !capped_pool_pnl.is_negative()
388                && pool_pnl.is_positive()
389            {
390                total_pnl = capped_pool_pnl
391                    .unsigned_abs()
392                    .checked_mul_div_with_signed_numerator(&total_pnl, &pool_pnl.unsigned_abs())
393                    .ok_or(crate::Error::Computation("calculating capped total pnl"))?;
394            }
395        }
396
397        let size_delta_in_tokens = if *self.size_in_usd() == *size_delta_usd {
398            self.size_in_tokens().clone()
399        } else if self.is_long() {
400            self.size_in_tokens()
401                .checked_mul_div_ceil(size_delta_usd, self.size_in_usd())
402                .ok_or(crate::Error::Computation(
403                    "calculating size delta in tokens for long",
404                ))?
405        } else {
406            self.size_in_tokens()
407                .checked_mul_div(size_delta_usd, self.size_in_usd())
408                .ok_or(crate::Error::Computation(
409                    "calculating size delta in tokens for short",
410                ))?
411        };
412
413        let pnl_usd = size_delta_in_tokens
414            .checked_mul_div_with_signed_numerator(&total_pnl, self.size_in_tokens())
415            .ok_or(crate::Error::Computation("calculating pnl_usd"))?;
416
417        let uncapped_pnl_usd = size_delta_in_tokens
418            .checked_mul_div_with_signed_numerator(&uncapped_total_pnl, self.size_in_tokens())
419            .ok_or(crate::Error::Computation("calculating uncapped_pnl_usd"))?;
420
421        Ok((pnl_usd, uncapped_pnl_usd, size_delta_in_tokens))
422    }
423
424    /// Validate the position.
425    fn validate(
426        &self,
427        prices: &Prices<Self::Num>,
428        should_validate_min_position_size: bool,
429        should_validate_min_collateral_usd: bool,
430    ) -> crate::Result<()> {
431        if self.size_in_usd().is_zero() || self.size_in_tokens().is_zero() {
432            return Err(crate::Error::InvalidPosition(
433                "size_in_usd or size_in_tokens is zero",
434            ));
435        }
436
437        self.on_validate()?;
438
439        if should_validate_min_position_size
440            && self.size_in_usd() < self.market().position_params()?.min_position_size_usd()
441        {
442            return Err(crate::Error::InvalidPosition("size in usd too small"));
443        }
444
445        if let Some(reason) = self.check_liquidatable(prices, should_validate_min_collateral_usd)? {
446            return Err(crate::Error::Liquidatable(reason));
447        }
448
449        Ok(())
450    }
451
452    /// Check if the position is liquidatable.
453    ///
454    /// Return [`LiquidatableReason`] if it is liquidatable, `None` otherwise.
455    fn check_liquidatable(
456        &self,
457        prices: &Prices<Self::Num>,
458        should_validate_min_collateral_usd: bool,
459    ) -> crate::Result<Option<LiquidatableReason>> {
460        use num_traits::{CheckedAdd, CheckedMul, CheckedSub};
461
462        let size_in_usd = self.size_in_usd();
463
464        let (pnl, _, _) = self.pnl_value(prices, size_in_usd)?;
465
466        let collateral_value = self.collateral_value(prices)?;
467        let collateral_price = self.collateral_price(prices);
468
469        let size_delta_usd = size_in_usd.to_opposite_signed()?;
470
471        let mut price_impact_value = self.position_price_impact(&size_delta_usd)?;
472
473        let has_positive_impact = price_impact_value.is_positive();
474
475        if price_impact_value.is_negative() {
476            self.market().cap_negative_position_price_impact(
477                &size_delta_usd,
478                true,
479                &mut price_impact_value,
480            )?;
481        } else {
482            price_impact_value = Zero::zero();
483        }
484
485        let fees = self.position_fees(
486            collateral_price,
487            size_in_usd,
488            has_positive_impact,
489            // Should not account for liquidation fees to determine if position should be liquidated.
490            false,
491        )?;
492
493        let collateral_cost_value = fees
494            .total_cost_amount()?
495            .checked_mul(collateral_price.pick_price(false))
496            .ok_or(crate::Error::Computation(
497                "overflow calculating collateral cost value",
498            ))?;
499
500        let remaining_collateral_value = collateral_value
501            .to_signed()?
502            .checked_add(&pnl)
503            .and_then(|v| {
504                v.checked_add(&price_impact_value)?
505                    .checked_sub(&collateral_cost_value.to_signed().ok()?)
506            })
507            .ok_or(crate::Error::Computation(
508                "calculating remaining collateral value",
509            ))?;
510
511        let params = self.market().position_params()?;
512
513        match check_collateral(
514            size_in_usd,
515            params.min_collateral_factor(),
516            should_validate_min_collateral_usd.then(|| params.min_collateral_value()),
517            false,
518            &remaining_collateral_value,
519        )? {
520            CheckCollateralResult::Sufficient => Ok(None),
521            CheckCollateralResult::Zero | CheckCollateralResult::Negative => {
522                Ok(Some(LiquidatableReason::NotPositive))
523            }
524            CheckCollateralResult::MinCollateralForLeverage => {
525                Ok(Some(LiquidatableReason::MinCollateralForLeverage))
526            }
527            CheckCollateralResult::MinCollateral => Ok(Some(LiquidatableReason::MinCollateral)),
528        }
529    }
530
531    /// Get position price impact.
532    fn position_price_impact(&self, size_delta_usd: &Self::Signed) -> crate::Result<Self::Signed> {
533        struct ReassignedValues<T> {
534            delta_long_usd_value: T,
535            delta_short_usd_value: T,
536        }
537
538        impl<T: Zero + Clone> ReassignedValues<T> {
539            fn new(is_long: bool, size_delta_usd: &T) -> Self {
540                if is_long {
541                    Self {
542                        delta_long_usd_value: size_delta_usd.clone(),
543                        delta_short_usd_value: Zero::zero(),
544                    }
545                } else {
546                    Self {
547                        delta_long_usd_value: Zero::zero(),
548                        delta_short_usd_value: size_delta_usd.clone(),
549                    }
550                }
551            }
552        }
553
554        // Since the amounts of open interest are already usd amounts,
555        // the price should be `one`.
556        let usd_price = One::one();
557
558        let ReassignedValues {
559            delta_long_usd_value,
560            delta_short_usd_value,
561        } = ReassignedValues::new(self.is_long(), size_delta_usd);
562
563        let price_impact_value = self
564            .market()
565            .open_interest()?
566            .pool_delta_with_values(
567                delta_long_usd_value,
568                delta_short_usd_value,
569                &usd_price,
570                &usd_price,
571            )?
572            .price_impact(&self.market().position_impact_params()?)?;
573        Ok(price_impact_value)
574    }
575
576    /// Get position price impact usd and cap the value if it is positive.
577    #[inline]
578    fn capped_positive_position_price_impact(
579        &self,
580        index_token_price: &Price<Self::Num>,
581        size_delta_usd: &Self::Signed,
582    ) -> crate::Result<Self::Signed> {
583        let mut impact = self.position_price_impact(size_delta_usd)?;
584        self.market().cap_positive_position_price_impact(
585            index_token_price,
586            size_delta_usd,
587            &mut impact,
588        )?;
589        Ok(impact)
590    }
591
592    /// Get capped position price impact usd.
593    ///
594    /// Compare to [`PositionExt::capped_positive_position_price_impact`],
595    /// this method will also cap the negative impact and return the difference before capping.
596    #[inline]
597    fn capped_position_price_impact(
598        &self,
599        index_token_price: &Price<Self::Num>,
600        size_delta_usd: &Self::Signed,
601    ) -> crate::Result<(Self::Signed, Self::Num)> {
602        let mut impact =
603            self.capped_positive_position_price_impact(index_token_price, size_delta_usd)?;
604        let impact_diff =
605            self.market()
606                .cap_negative_position_price_impact(size_delta_usd, false, &mut impact)?;
607        Ok((impact, impact_diff))
608    }
609
610    /// Get pending borrowing fee value of this position.
611    fn pending_borrowing_fee_value(&self) -> crate::Result<Self::Num> {
612        use crate::utils;
613        use num_traits::CheckedSub;
614
615        let latest_factor = self.market().cumulative_borrowing_factor(self.is_long())?;
616        let diff_factor = latest_factor
617            .checked_sub(self.borrowing_factor())
618            .ok_or(crate::Error::Computation("invalid latest borrowing factor"))?;
619        utils::apply_factor(self.size_in_usd(), &diff_factor)
620            .ok_or(crate::Error::Computation("calculating borrowing fee value"))
621    }
622
623    /// Get pending funding fees.
624    fn pending_funding_fees(&self) -> crate::Result<FundingFees<Self::Num>> {
625        let adjustment = self.market().funding_amount_per_size_adjustment();
626        let fees = FundingFees::builder()
627            .amount(
628                unpack_to_funding_amount_delta(
629                    &adjustment,
630                    &self.market().funding_fee_amount_per_size(
631                        self.is_long(),
632                        self.is_collateral_token_long(),
633                    )?,
634                    self.funding_fee_amount_per_size(),
635                    self.size_in_usd(),
636                    true,
637                )
638                .ok_or(crate::Error::Computation("calculating funding fee amount"))?,
639            )
640            .claimable_long_token_amount(
641                unpack_to_funding_amount_delta(
642                    &adjustment,
643                    &self
644                        .market()
645                        .claimable_funding_fee_amount_per_size(self.is_long(), true)?,
646                    self.claimable_funding_fee_amount_per_size(true),
647                    self.size_in_usd(),
648                    false,
649                )
650                .ok_or(crate::Error::Computation(
651                    "calculating claimable long token funding fee amount",
652                ))?,
653            )
654            .claimable_short_token_amount(
655                unpack_to_funding_amount_delta(
656                    &adjustment,
657                    &self
658                        .market()
659                        .claimable_funding_fee_amount_per_size(self.is_long(), false)?,
660                    self.claimable_funding_fee_amount_per_size(false),
661                    self.size_in_usd(),
662                    false,
663                )
664                .ok_or(crate::Error::Computation(
665                    "calculating claimable short token funding fee amount",
666                ))?,
667            )
668            .build();
669        Ok(fees)
670    }
671
672    /// Calculates the [`PositionFees`] generated by changing the position size by the specified `size_delta_usd`.
673    fn position_fees(
674        &self,
675        collateral_token_price: &Price<Self::Num>,
676        size_delta_usd: &Self::Num,
677        is_positive_impact: bool,
678        is_liquidation: bool,
679    ) -> crate::Result<PositionFees<Self::Num>> {
680        debug_assert!(!collateral_token_price.has_zero(), "must be non-zero");
681
682        let liquidation_fees = is_liquidation
683            .then(|| {
684                // Although `size_delta_usd` is used here to calculate liquidation fee, partial liquidation is not allowed.
685                // Therefore, `size_delta_usd == size_in_usd` always holds, ensuring consistency with the Solidity version.
686                self.market()
687                    .liquidation_fee_params()?
688                    .fee(size_delta_usd, collateral_token_price)
689            })
690            .transpose()?;
691
692        let fees = self
693            .market()
694            .order_fee_params()?
695            .base_position_fees(collateral_token_price, size_delta_usd, is_positive_impact)?
696            .set_borrowing_fees(
697                self.market().borrowing_fee_params()?.receiver_factor(),
698                collateral_token_price,
699                self.pending_borrowing_fee_value()?,
700            )?
701            .set_funding_fees(self.pending_funding_fees()?)
702            .set_liquidation_fees(liquidation_fees);
703        Ok(fees)
704    }
705}
706
707impl<const DECIMALS: u8, P: Position<DECIMALS>> PositionExt<DECIMALS> for P {}
708
709/// Extension trait for [`PositionMut`] with utils.
710pub trait PositionMutExt<const DECIMALS: u8>: PositionMut<DECIMALS>
711where
712    Self::Market: PerpMarketMut<DECIMALS, Num = Self::Num, Signed = Self::Signed>,
713{
714    /// Create an action to increase the position.
715    fn increase(
716        &mut self,
717        prices: Prices<Self::Num>,
718        collateral_increment_amount: Self::Num,
719        size_delta_usd: Self::Num,
720        acceptable_price: Option<Self::Num>,
721    ) -> crate::Result<IncreasePosition<&mut Self, DECIMALS>>
722    where
723        Self: Sized,
724    {
725        IncreasePosition::try_new(
726            self,
727            prices,
728            collateral_increment_amount,
729            size_delta_usd,
730            acceptable_price,
731        )
732    }
733
734    /// Create an action to decrease the position.
735    fn decrease(
736        &mut self,
737        prices: Prices<Self::Num>,
738        size_delta_usd: Self::Num,
739        acceptable_price: Option<Self::Num>,
740        collateral_withdrawal_amount: Self::Num,
741        flags: DecreasePositionFlags,
742    ) -> crate::Result<DecreasePosition<&mut Self, DECIMALS>>
743    where
744        Self: Sized,
745    {
746        DecreasePosition::try_new(
747            self,
748            prices,
749            size_delta_usd,
750            acceptable_price,
751            collateral_withdrawal_amount,
752            flags,
753        )
754    }
755
756    /// Update global open interest.
757    fn update_open_interest(
758        &mut self,
759        size_delta_usd: &Self::Signed,
760        size_delta_in_tokens: &Self::Signed,
761    ) -> crate::Result<()> {
762        use num_traits::CheckedAdd;
763
764        if size_delta_usd.is_zero() {
765            return Ok(());
766        }
767        let is_long_collateral = self.is_collateral_token_long();
768        let is_long = self.is_long();
769        let max_open_interest = self.market().max_open_interest(is_long)?;
770
771        let open_interest = self.market_mut().open_interest_pool_mut(is_long)?;
772        if is_long_collateral {
773            open_interest.apply_delta_to_long_amount(size_delta_usd)?;
774        } else {
775            open_interest.apply_delta_to_short_amount(size_delta_usd)?;
776        }
777
778        if size_delta_usd.is_positive() {
779            let is_exceeded = open_interest
780                .long_amount()?
781                .checked_add(&open_interest.short_amount()?)
782                .map(|total| total > max_open_interest)
783                .unwrap_or(true);
784
785            if is_exceeded {
786                return Err(crate::Error::MaxOpenInterestExceeded);
787            }
788        }
789
790        let open_interest_in_tokens = self
791            .market_mut()
792            .open_interest_in_tokens_pool_mut(is_long)?;
793        if is_long_collateral {
794            open_interest_in_tokens.apply_delta_to_long_amount(size_delta_in_tokens)?;
795        } else {
796            open_interest_in_tokens.apply_delta_to_short_amount(size_delta_in_tokens)?;
797        }
798
799        Ok(())
800    }
801
802    /// Update total borrowing.
803    fn update_total_borrowing(
804        &mut self,
805        next_size_in_usd: &Self::Num,
806        next_borrowing_factor: &Self::Num,
807    ) -> crate::Result<()> {
808        let is_long = self.is_long();
809        let previous = crate::utils::apply_factor(self.size_in_usd(), self.borrowing_factor())
810            .ok_or(crate::Error::Computation("calculating previous borrowing"))?;
811
812        let total_borrowing = self.market_mut().total_borrowing_pool_mut()?;
813
814        let delta = {
815            let next = crate::utils::apply_factor(next_size_in_usd, next_borrowing_factor)
816                .ok_or(crate::Error::Computation("calculating next borrowing"))?;
817            next.checked_signed_sub(previous)?
818        };
819
820        total_borrowing.apply_delta_amount(is_long, &delta)?;
821
822        Ok(())
823    }
824}
825
826impl<const DECIMALS: u8, P: PositionMut<DECIMALS>> PositionMutExt<DECIMALS> for P where
827    P::Market: PerpMarketMut<DECIMALS, Num = Self::Num, Signed = Self::Signed>
828{
829}
830
831/// Collateral Delta Values.
832pub struct CollateralDelta<T: Unsigned> {
833    next_size_in_usd: T,
834    next_collateral_amount: T,
835    realized_pnl_value: T::Signed,
836    open_interest_delta: T::Signed,
837}
838
839impl<T: Unsigned> CollateralDelta<T> {
840    /// Create a new collateral delta.
841    pub fn new(
842        next_size_in_usd: T,
843        next_collateral_amount: T,
844        realized_pnl_value: T::Signed,
845        open_interest_delta: T::Signed,
846    ) -> Self {
847        Self {
848            next_size_in_usd,
849            next_collateral_amount,
850            realized_pnl_value,
851            open_interest_delta,
852        }
853    }
854}
855
856/// Will collateral be sufficient.
857#[derive(Clone, Copy)]
858pub enum WillCollateralBeSufficient<T> {
859    /// Will be sufficient.
860    Sufficient(T),
861    /// Won't be sufficient.
862    Insufficient(T),
863}
864
865impl<T> WillCollateralBeSufficient<T> {
866    /// Returns whether it is sufficient.
867    pub fn is_sufficient(&self) -> bool {
868        matches!(self, Self::Sufficient(_))
869    }
870}
871
872impl<T> Deref for WillCollateralBeSufficient<T> {
873    type Target = T;
874
875    fn deref(&self) -> &Self::Target {
876        match self {
877            Self::Sufficient(v) => v,
878            Self::Insufficient(v) => v,
879        }
880    }
881}
882
883/// Liquidatable reason.
884#[derive(Debug, Clone, Copy)]
885pub enum LiquidatableReason {
886    /// Min collateral.
887    MinCollateral,
888    /// Remaining collateral not positive.
889    NotPositive,
890    /// Min collateral for leverage.
891    MinCollateralForLeverage,
892}
893
894impl fmt::Display for LiquidatableReason {
895    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
896        match self {
897            Self::MinCollateral => write!(f, "min collateral"),
898            Self::NotPositive => write!(f, "<= 0"),
899            Self::MinCollateralForLeverage => write!(f, "min collateral for leverage"),
900        }
901    }
902}
903
904enum CheckCollateralResult {
905    Sufficient,
906    Zero,
907    Negative,
908    MinCollateralForLeverage,
909    MinCollateral,
910}
911
912fn check_collateral<T, const DECIMALS: u8>(
913    size_in_usd: &T,
914    min_collateral_factor: &T,
915    min_collateral_value: Option<&T>,
916    allow_zero_collateral: bool,
917    collateral_value: &T::Signed,
918) -> crate::Result<CheckCollateralResult>
919where
920    T: FixedPointOps<DECIMALS>,
921{
922    if collateral_value.is_negative() {
923        if min_collateral_value.is_some() {
924            // Keep the behavior consistent with the Solidity version.
925            Ok(CheckCollateralResult::MinCollateral)
926        } else {
927            Ok(CheckCollateralResult::Negative)
928        }
929    } else {
930        let collateral_value = collateral_value.unsigned_abs();
931
932        if let Some(min_collateral_value) = min_collateral_value {
933            if collateral_value < *min_collateral_value {
934                return Ok(CheckCollateralResult::MinCollateral);
935            }
936        }
937
938        if !allow_zero_collateral && collateral_value.is_zero() {
939            return Ok(CheckCollateralResult::Zero);
940        }
941
942        let min_collateral_usd_for_leverage =
943            crate::utils::apply_factor(size_in_usd, min_collateral_factor).ok_or(
944                crate::Error::Computation("calculating min collateral usd for leverage"),
945            )?;
946
947        if collateral_value < min_collateral_usd_for_leverage {
948            return Ok(CheckCollateralResult::MinCollateralForLeverage);
949        }
950
951        Ok(CheckCollateralResult::Sufficient)
952    }
953}
954
955/// Insolvent Close Step.
956#[derive(Debug, Clone, Copy)]
957#[cfg_attr(
958    feature = "anchor-lang",
959    derive(
960        anchor_lang::AnchorDeserialize,
961        anchor_lang::AnchorSerialize,
962        anchor_lang::InitSpace
963    )
964)]
965#[non_exhaustive]
966pub enum InsolventCloseStep {
967    /// PnL.
968    Pnl,
969    /// Fees.
970    Fees,
971    /// Funding fees.
972    Funding,
973    /// Price impact.
974    Impact,
975    /// Price impact diff.
976    Diff,
977}