Skip to main content

gmsol_sdk/simulation/
shift.rs

1use gmsol_model::{
2    action::{deposit::DepositReport, withdraw::WithdrawReport},
3    LiquidityMarketMutExt, MarketAction,
4};
5use gmsol_programs::{gmsol_store::types::CreateShiftParams, model::SwapPricingKind};
6use solana_sdk::pubkey::Pubkey;
7use typed_builder::TypedBuilder;
8
9use super::{SimulationOptions, Simulator};
10
11/// Shift simulation output.
12#[derive(Debug)]
13pub struct ShiftSimulationOutput {
14    pub(crate) withdraw: Box<WithdrawReport<u128>>,
15    pub(crate) deposit: Box<DepositReport<u128, i128>>,
16}
17
18impl ShiftSimulationOutput {
19    /// Returns the withdrawal report.
20    pub fn withdraw_report(&self) -> &WithdrawReport<u128> {
21        &self.withdraw
22    }
23
24    /// Returns the deposit report.
25    pub fn deposit_report(&self) -> &DepositReport<u128, i128> {
26        &self.deposit
27    }
28}
29
30/// Shift execution simulation.
31#[derive(Debug, TypedBuilder)]
32pub struct ShiftSimulation<'a> {
33    simulator: &'a mut Simulator,
34    params: &'a CreateShiftParams,
35    from_market_token: &'a Pubkey,
36    to_market_token: &'a Pubkey,
37}
38
39impl ShiftSimulation<'_> {
40    /// Execute with options.
41    pub fn execute_with_options(
42        self,
43        options: SimulationOptions,
44    ) -> crate::Result<ShiftSimulationOutput> {
45        let Self {
46            simulator,
47            params,
48            from_market_token,
49            to_market_token,
50        } = self;
51
52        if params.from_market_token_amount == 0 {
53            return Err(crate::Error::custom("[sim] empty shift"));
54        }
55
56        let (from_market, prices_for_from_market) =
57            simulator.get_market_with_prices(from_market_token)?;
58        let (to_market, prices_for_to_market) =
59            simulator.get_market_with_prices(to_market_token)?;
60
61        if from_market.meta.long_token_mint != to_market.meta.long_token_mint
62            || from_market.meta.short_token_mint != to_market.meta.short_token_mint
63        {
64            return Err(crate::Error::custom(format!(
65                "[sim] shift from `{from_market_token}` to `{to_market_token}` is impossible"
66            )));
67        }
68
69        // Execute withdrawal.
70        let withdraw = {
71            let (from_market, maybe_vi_map) = if options.disable_vis {
72                (
73                    simulator
74                        .get_market_mut(from_market_token)
75                        .expect("must exist"),
76                    None,
77                )
78            } else {
79                let (market, vi_map) = simulator.get_market_and_vis_mut(from_market_token)?;
80                (market, Some(vi_map))
81            };
82
83            from_market.with_swap_pricing(SwapPricingKind::Shift, |market| match maybe_vi_map {
84                None => market.with_vis_disabled(|market| {
85                    market
86                        .withdraw(
87                            params.from_market_token_amount.into(),
88                            prices_for_from_market,
89                        )?
90                        .execute()
91                }),
92                Some(vi_map) => market.with_vi_models(vi_map, |market| {
93                    market
94                        .withdraw(
95                            params.from_market_token_amount.into(),
96                            prices_for_from_market,
97                        )?
98                        .execute()
99                }),
100            })?
101        };
102
103        let (long_token_amount, short_token_amount) = (
104            *withdraw.long_token_output(),
105            *withdraw.short_token_output(),
106        );
107
108        if long_token_amount == 0 && short_token_amount == 0 {
109            return Err(crate::Error::custom(
110                "[sim] shift cannot be completed due to empty withdrawal output",
111            ));
112        }
113
114        // Execute deposit.
115        let deposit = {
116            let (to_market, maybe_vi_map) = if options.disable_vis {
117                (
118                    simulator
119                        .get_market_mut(to_market_token)
120                        .expect("must exist"),
121                    None,
122                )
123            } else {
124                let (market, vi_map) = simulator.get_market_and_vis_mut(to_market_token)?;
125                (market, Some(vi_map))
126            };
127
128            to_market.with_swap_pricing(SwapPricingKind::Shift, |market| match maybe_vi_map {
129                None => market.with_vis_disabled(|market| {
130                    market
131                        .deposit(long_token_amount, short_token_amount, prices_for_to_market)?
132                        .execute()
133                }),
134                Some(vi_map) => market.with_vi_models(vi_map, |market| {
135                    market
136                        .deposit(long_token_amount, short_token_amount, prices_for_to_market)?
137                        .execute()
138                }),
139            })?
140        };
141
142        let minted = deposit.minted();
143        let min_to_market_token_amount = params.min_to_market_token_amount;
144        if *minted < u128::from(min_to_market_token_amount) {
145            return Err(crate::Error::custom(format!(
146                "[sim] insufficient output amount: {minted} < {min_to_market_token_amount}",
147            )));
148        }
149
150        Ok(ShiftSimulationOutput {
151            withdraw: Box::new(withdraw),
152            deposit: Box::new(deposit),
153        })
154    }
155}