Skip to main content

gmsol_sdk/simulation/
withdrawal.rs

1use gmsol_model::{
2    action::{swap::SwapReport, withdraw::WithdrawReport},
3    LiquidityMarketMutExt, MarketAction,
4};
5use gmsol_programs::gmsol_store::types::CreateWithdrawalParams;
6use solana_sdk::pubkey::Pubkey;
7use typed_builder::TypedBuilder;
8
9use super::{SimulationOptions, Simulator};
10
11/// Withdrawal simulation output.
12#[derive(Debug)]
13pub struct WithdrawalSimulationOutput {
14    pub(crate) report: Box<WithdrawReport<u128>>,
15    pub(crate) long_swaps: Vec<SwapReport<u128, i128>>,
16    pub(crate) short_swaps: Vec<SwapReport<u128, i128>>,
17    pub(crate) long_output_amount: u128,
18    pub(crate) short_output_amount: u128,
19}
20
21impl WithdrawalSimulationOutput {
22    /// Returns long swap reports.
23    pub fn long_swaps(&self) -> &[SwapReport<u128, i128>] {
24        &self.long_swaps
25    }
26
27    /// Returns short swap reports.
28    pub fn short_swaps(&self) -> &[SwapReport<u128, i128>] {
29        &self.short_swaps
30    }
31
32    /// Returns withdrawal report.
33    pub fn report(&self) -> &WithdrawReport<u128> {
34        &self.report
35    }
36
37    /// Returns long output amount.
38    pub fn long_output_amount(&self) -> u128 {
39        self.long_output_amount
40    }
41
42    /// Returns short output amount.
43    pub fn short_output_amount(&self) -> u128 {
44        self.short_output_amount
45    }
46}
47
48/// Withdrawal execution simulation.
49#[derive(Debug, TypedBuilder)]
50pub struct WithdrawalSimulation<'a> {
51    simulator: &'a mut Simulator,
52    params: &'a CreateWithdrawalParams,
53    market_token: &'a Pubkey,
54    #[builder(default)]
55    long_receive_token: Option<&'a Pubkey>,
56    #[builder(default)]
57    long_swap_path: &'a [Pubkey],
58    #[builder(default)]
59    short_receive_token: Option<&'a Pubkey>,
60    #[builder(default)]
61    short_swap_path: &'a [Pubkey],
62}
63
64impl WithdrawalSimulation<'_> {
65    /// Execute with options.
66    pub fn execute_with_options(
67        self,
68        options: SimulationOptions,
69    ) -> crate::Result<WithdrawalSimulationOutput> {
70        let Self {
71            simulator,
72            params,
73            market_token,
74            long_receive_token,
75            long_swap_path,
76            short_receive_token,
77            short_swap_path,
78        } = self;
79
80        if params.market_token_amount == 0 {
81            return Err(crate::Error::custom("[sim] empty withdrawal"));
82        }
83
84        let prices = simulator.get_prices_for_market(market_token)?;
85        let (market, maybe_vi_map) = if options.disable_vis {
86            (
87                simulator.get_market_mut(market_token).expect("must exist"),
88                None,
89            )
90        } else {
91            let (market, vi_map) = simulator.get_market_and_vis_mut(market_token)?;
92            (market, Some(vi_map))
93        };
94        let meta = &market.meta;
95        let long_token = meta.long_token_mint;
96        let short_token = meta.short_token_mint;
97        let long_receive_token = long_receive_token.copied().unwrap_or(long_token);
98        let short_receive_token = short_receive_token.copied().unwrap_or(short_token);
99
100        // Execute withdrawal.
101        let report = match maybe_vi_map {
102            None => market.with_vis_disabled(|market| {
103                market
104                    .withdraw(u128::from(params.market_token_amount), prices)?
105                    .execute()
106            })?,
107            Some(vi_map) => market.with_vi_models(vi_map, |market| {
108                market
109                    .withdraw(u128::from(params.market_token_amount), prices)?
110                    .execute()
111            })?,
112        };
113
114        let (long_amount, short_amount) =
115            (*report.long_token_output(), *report.short_token_output());
116
117        // Execute swap for long side.
118        let (long_swaps, long_output_amount) = if long_amount == 0 {
119            (vec![], 0)
120        } else {
121            let swap_output = simulator.swap_along_path_with_options(
122                long_swap_path,
123                &long_token,
124                long_amount,
125                options.clone(),
126            )?;
127
128            if swap_output.output_token != long_receive_token {
129                return Err(crate::Error::custom("[sim] invalid long swap path"));
130            }
131
132            (swap_output.reports, swap_output.amount)
133        };
134
135        let min_receive_amount = params.min_long_token_amount;
136        if long_output_amount < u128::from(min_receive_amount) {
137            return Err(crate::Error::custom(format!(
138                "[sim] insufficient long output amount: {long_output_amount} < {min_receive_amount}",
139            )));
140        }
141
142        // Execute swap for short side.
143        let (short_swaps, short_output_amount) = if short_amount == 0 {
144            (vec![], 0)
145        } else {
146            let swap_output = simulator.swap_along_path_with_options(
147                short_swap_path,
148                &short_token,
149                short_amount,
150                options.clone(),
151            )?;
152
153            if swap_output.output_token != short_receive_token {
154                return Err(crate::Error::custom("[sim] invalid short swap path"));
155            }
156
157            (swap_output.reports, swap_output.amount)
158        };
159
160        let min_receive_amount = params.min_short_token_amount;
161        if short_output_amount < u128::from(min_receive_amount) {
162            return Err(crate::Error::custom(format!(
163                "[sim] insufficient short output amount: {short_output_amount} < {min_receive_amount}",
164            )));
165        }
166
167        Ok(WithdrawalSimulationOutput {
168            report: Box::new(report),
169            long_swaps,
170            short_swaps,
171            long_output_amount,
172            short_output_amount,
173        })
174    }
175}