gmsol_model/market/
swap.rs

1use num_traits::{CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedSub, One, Signed, Zero};
2
3use crate::{
4    action::swap::Swap,
5    num::{Unsigned, UnsignedAbs},
6    params::{FeeParams, PriceImpactParams},
7    price::{Price, Prices},
8    Balance, BaseMarket, Pool,
9};
10
11use super::BaseMarketMut;
12
13/// A market for swapping tokens.
14pub trait SwapMarket<const DECIMALS: u8>: BaseMarket<DECIMALS> {
15    /// Get swap impact params.
16    fn swap_impact_params(&self) -> crate::Result<PriceImpactParams<Self::Num>>;
17
18    /// Get the swap fee params.
19    fn swap_fee_params(&self) -> crate::Result<FeeParams<Self::Num>>;
20}
21
22/// A mutable market for swapping tokens.
23pub trait SwapMarketMut<const DECIMALS: u8>:
24    SwapMarket<DECIMALS> + BaseMarketMut<DECIMALS>
25{
26    /// Get the swap impact pool mutably.
27    /// # Requirements
28    /// - This method must return `Ok` if [`BaseMarket::swap_impact_pool`] does.
29    fn swap_impact_pool_mut(&mut self) -> crate::Result<&mut Self::Pool>;
30}
31
32impl<M: SwapMarket<DECIMALS>, const DECIMALS: u8> SwapMarket<DECIMALS> for &mut M {
33    fn swap_impact_params(&self) -> crate::Result<PriceImpactParams<Self::Num>> {
34        (**self).swap_impact_params()
35    }
36
37    fn swap_fee_params(&self) -> crate::Result<FeeParams<Self::Num>> {
38        (**self).swap_fee_params()
39    }
40}
41
42impl<M: SwapMarketMut<DECIMALS>, const DECIMALS: u8> SwapMarketMut<DECIMALS> for &mut M {
43    fn swap_impact_pool_mut(&mut self) -> crate::Result<&mut Self::Pool> {
44        (**self).swap_impact_pool_mut()
45    }
46}
47
48/// Extension trait for [`SwapMarket`].
49pub trait SwapMarketExt<const DECIMALS: u8>: SwapMarket<DECIMALS> {
50    /// Get the swap impact amount with cap.
51    fn swap_impact_amount_with_cap(
52        &self,
53        is_long_token: bool,
54        price: &Price<Self::Num>,
55        usd_impact: &Self::Signed,
56    ) -> crate::Result<(Self::Signed, Self::Num)> {
57        if price.has_zero() {
58            return Err(crate::Error::DividedByZero);
59        }
60        if usd_impact.is_positive() {
61            let max_price = price.pick_price(true).to_signed()?;
62
63            let mut amount = usd_impact
64                .checked_div(&max_price)
65                .ok_or(crate::Error::Computation("calculating swap impact amount"))?;
66
67            let max_amount = if is_long_token {
68                self.swap_impact_pool()?.long_amount()?
69            } else {
70                self.swap_impact_pool()?.short_amount()?
71            }
72            .to_signed()?;
73
74            let capped_diff_value = if amount > max_amount {
75                let capped_diff_value = amount
76                    .checked_sub(&max_amount)
77                    .map(|diff_amount| diff_amount.unsigned_abs())
78                    .and_then(|diff_amount| diff_amount.checked_mul(price.pick_price(true)))
79                    .ok_or(crate::Error::Computation("calculating capped diff value"))?;
80                amount = max_amount;
81                capped_diff_value
82            } else {
83                Zero::zero()
84            };
85            Ok((amount, capped_diff_value))
86        } else if usd_impact.is_negative() {
87            let price = price.pick_price(false).to_signed()?;
88            let one = Self::Signed::one();
89            // Round up div.
90            let amount = usd_impact
91                .checked_sub(&price)
92                .and_then(|a| a.checked_add(&one)?.checked_div(&price))
93                .ok_or(crate::Error::Computation(
94                    "calculating round up swap impact amount",
95                ))?;
96            Ok((amount, Zero::zero()))
97        } else {
98            Ok((Zero::zero(), Zero::zero()))
99        }
100    }
101}
102
103impl<M: SwapMarket<DECIMALS> + ?Sized, const DECIMALS: u8> SwapMarketExt<DECIMALS> for M {}
104
105/// Extension trait for [`SwapMarketMut`].
106pub trait SwapMarketMutExt<const DECIMALS: u8>: SwapMarketMut<DECIMALS> {
107    /// Create a [`Swap`].
108    fn swap(
109        &mut self,
110        is_token_in_long: bool,
111        token_in_amount: Self::Num,
112        prices: Prices<Self::Num>,
113    ) -> crate::Result<Swap<&mut Self, DECIMALS>>
114    where
115        Self: Sized,
116    {
117        Swap::try_new(self, is_token_in_long, token_in_amount, prices)
118    }
119
120    /// Apply a swap impact value to the price impact pool.
121    ///
122    /// - If it is a positive impact amount, cap the impact amount to the amount available in the price impact pool,
123    ///   and the price impact pool will be decreased by this amount and return.
124    /// - If it is a negative impact amount, the price impact pool will be increased by this amount and return.
125    fn apply_swap_impact_value_with_cap(
126        &mut self,
127        is_long_token: bool,
128        price: &Price<Self::Num>,
129        usd_impact: &Self::Signed,
130    ) -> crate::Result<Self::Num> {
131        let (amount, _) = self.swap_impact_amount_with_cap(is_long_token, price, usd_impact)?;
132        let delta = amount
133            .checked_neg()
134            .ok_or(crate::Error::Computation("negating swap impact delta"))?;
135        if is_long_token {
136            self.swap_impact_pool_mut()?
137                .apply_delta_to_long_amount(&delta)?;
138        } else {
139            self.swap_impact_pool_mut()?
140                .apply_delta_to_short_amount(&delta)?;
141        }
142        Ok(delta.unsigned_abs())
143    }
144}
145
146impl<M: SwapMarketMut<DECIMALS>, const DECIMALS: u8> SwapMarketMutExt<DECIMALS> for M {}