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    pool::delta::{PoolDelta, PriceImpact},
8    price::{Price, Prices},
9    Balance, BalanceExt, BaseMarket, Pool,
10};
11
12use super::BaseMarketMut;
13
14/// A market for swapping tokens.
15pub trait SwapMarket<const DECIMALS: u8>: BaseMarket<DECIMALS> {
16    /// Get swap impact params.
17    fn swap_impact_params(&self) -> crate::Result<PriceImpactParams<Self::Num>>;
18
19    /// Get the swap fee params.
20    fn swap_fee_params(&self) -> crate::Result<FeeParams<Self::Num>>;
21}
22
23/// A mutable market for swapping tokens.
24pub trait SwapMarketMut<const DECIMALS: u8>:
25    SwapMarket<DECIMALS> + BaseMarketMut<DECIMALS>
26{
27    /// Get the swap impact pool mutably.
28    /// # Requirements
29    /// - This method must return `Ok` if [`BaseMarket::swap_impact_pool`] does.
30    fn swap_impact_pool_mut(&mut self) -> crate::Result<&mut Self::Pool>;
31}
32
33impl<M: SwapMarket<DECIMALS>, const DECIMALS: u8> SwapMarket<DECIMALS> for &mut M {
34    fn swap_impact_params(&self) -> crate::Result<PriceImpactParams<Self::Num>> {
35        (**self).swap_impact_params()
36    }
37
38    fn swap_fee_params(&self) -> crate::Result<FeeParams<Self::Num>> {
39        (**self).swap_fee_params()
40    }
41}
42
43impl<M: SwapMarketMut<DECIMALS>, const DECIMALS: u8> SwapMarketMut<DECIMALS> for &mut M {
44    fn swap_impact_pool_mut(&mut self) -> crate::Result<&mut Self::Pool> {
45        (**self).swap_impact_pool_mut()
46    }
47}
48
49/// Extension trait for [`SwapMarket`].
50pub trait SwapMarketExt<const DECIMALS: u8>: SwapMarket<DECIMALS> {
51    /// Calculate swap price impact.
52    fn swap_impact_value(
53        &self,
54        liquidity_pool_delta: &PoolDelta<Self::Num>,
55        include_virtual_inventory_impact: bool,
56    ) -> crate::Result<PriceImpact<Self::Signed>> {
57        let params = self.swap_impact_params()?;
58
59        let impact = liquidity_pool_delta.price_impact(&params)?;
60
61        if !impact.value.is_negative() || !include_virtual_inventory_impact {
62            return Ok(impact);
63        }
64
65        let Some(virtual_inventory) = self.virtual_inventory_for_swaps_pool()? else {
66            return Ok(impact);
67        };
68
69        let delta = liquidity_pool_delta.delta();
70        let long_token_price = liquidity_pool_delta.long_token_price();
71        let short_token_price = liquidity_pool_delta.short_token_price();
72
73        let virtual_inventory_impact = virtual_inventory
74            .pool_delta_with_values(
75                delta.long_value().clone(),
76                delta.short_value().clone(),
77                long_token_price,
78                short_token_price,
79            )?
80            .price_impact(&params)?;
81
82        if virtual_inventory_impact.value < impact.value {
83            Ok(virtual_inventory_impact)
84        } else {
85            Ok(impact)
86        }
87    }
88
89    /// Get the swap impact amount with cap.
90    fn swap_impact_amount_with_cap(
91        &self,
92        is_long_token: bool,
93        price: &Price<Self::Num>,
94        usd_impact: &Self::Signed,
95    ) -> crate::Result<(Self::Signed, Self::Num)> {
96        if price.has_zero() {
97            return Err(crate::Error::DividedByZero);
98        }
99        if usd_impact.is_positive() {
100            let max_price = price.pick_price(true).to_signed()?;
101
102            let mut amount = usd_impact
103                .checked_div(&max_price)
104                .ok_or(crate::Error::Computation("calculating swap impact amount"))?;
105
106            let max_amount = if is_long_token {
107                self.swap_impact_pool()?.long_amount()?
108            } else {
109                self.swap_impact_pool()?.short_amount()?
110            }
111            .to_signed()?;
112
113            let capped_diff_value = if amount > max_amount {
114                let capped_diff_value = amount
115                    .checked_sub(&max_amount)
116                    .map(|diff_amount| diff_amount.unsigned_abs())
117                    .and_then(|diff_amount| diff_amount.checked_mul(price.pick_price(true)))
118                    .ok_or(crate::Error::Computation("calculating capped diff value"))?;
119                amount = max_amount;
120                capped_diff_value
121            } else {
122                Zero::zero()
123            };
124            Ok((amount, capped_diff_value))
125        } else if usd_impact.is_negative() {
126            let price = price.pick_price(false).to_signed()?;
127            let one = Self::Signed::one();
128            // Round up div.
129            let amount = usd_impact
130                .checked_sub(&price)
131                .and_then(|a| a.checked_add(&one)?.checked_div(&price))
132                .ok_or(crate::Error::Computation(
133                    "calculating round up swap impact amount",
134                ))?;
135            Ok((amount, Zero::zero()))
136        } else {
137            Ok((Zero::zero(), Zero::zero()))
138        }
139    }
140}
141
142impl<M: SwapMarket<DECIMALS> + ?Sized, const DECIMALS: u8> SwapMarketExt<DECIMALS> for M {}
143
144/// Extension trait for [`SwapMarketMut`].
145pub trait SwapMarketMutExt<const DECIMALS: u8>: SwapMarketMut<DECIMALS> {
146    /// Create a [`Swap`].
147    fn swap(
148        &mut self,
149        is_token_in_long: bool,
150        token_in_amount: Self::Num,
151        prices: Prices<Self::Num>,
152    ) -> crate::Result<Swap<&mut Self, DECIMALS>>
153    where
154        Self: Sized,
155    {
156        Swap::try_new(self, is_token_in_long, token_in_amount, prices)
157    }
158
159    /// Apply a swap impact value to the price impact pool.
160    ///
161    /// - If it is a positive impact amount, cap the impact amount to the amount available in the price impact pool,
162    ///   and the price impact pool will be decreased by this amount and return.
163    /// - If it is a negative impact amount, the price impact pool will be increased by this amount and return.
164    fn apply_swap_impact_value_with_cap(
165        &mut self,
166        is_long_token: bool,
167        price: &Price<Self::Num>,
168        usd_impact: &Self::Signed,
169    ) -> crate::Result<Self::Num> {
170        let (amount, _) = self.swap_impact_amount_with_cap(is_long_token, price, usd_impact)?;
171        let delta = amount
172            .checked_neg()
173            .ok_or(crate::Error::Computation("negating swap impact delta"))?;
174        if is_long_token {
175            self.swap_impact_pool_mut()?
176                .apply_delta_to_long_amount(&delta)?;
177        } else {
178            self.swap_impact_pool_mut()?
179                .apply_delta_to_short_amount(&delta)?;
180        }
181        Ok(delta.unsigned_abs())
182    }
183}
184
185impl<M: SwapMarketMut<DECIMALS>, const DECIMALS: u8> SwapMarketMutExt<DECIMALS> for M {}