gmsol_sdk/simulation/
shift.rs1use 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#[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 pub fn withdraw_report(&self) -> &WithdrawReport<u128> {
21 &self.withdraw
22 }
23
24 pub fn deposit_report(&self) -> &DepositReport<u128, i128> {
26 &self.deposit
27 }
28}
29
30#[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 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 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 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}