gmsol_sdk/simulation/
withdrawal.rs1use 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#[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 pub fn long_swaps(&self) -> &[SwapReport<u128, i128>] {
24 &self.long_swaps
25 }
26
27 pub fn short_swaps(&self) -> &[SwapReport<u128, i128>] {
29 &self.short_swaps
30 }
31
32 pub fn report(&self) -> &WithdrawReport<u128> {
34 &self.report
35 }
36
37 pub fn long_output_amount(&self) -> u128 {
39 self.long_output_amount
40 }
41
42 pub fn short_output_amount(&self) -> u128 {
44 self.short_output_amount
45 }
46}
47
48#[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 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 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 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 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}