Skip to main content

gmsol_sdk/simulation/
simulator.rs

1use std::{
2    collections::{BTreeMap, HashMap},
3    sync::Arc,
4};
5
6use gmsol_model::{
7    action::swap::SwapReport,
8    price::{Price, Prices},
9    MarketAction, SwapMarketMutExt,
10};
11use gmsol_programs::{
12    gmsol_store::types::{
13        CreateDepositParams, CreateGlvDepositParams, CreateGlvWithdrawalParams, CreateShiftParams,
14        CreateWithdrawalParams, MarketMeta,
15    },
16    model::{MarketModel, VirtualInventoryModel},
17};
18use solana_sdk::pubkey::Pubkey;
19
20use crate::{
21    builders::order::{CreateOrderKind, CreateOrderParams},
22    glv::{calculator::GlvCalculator, model::GlvModel},
23    market::caluclator::MarketCalculator,
24    simulation::order::OrderSimulation,
25};
26
27use super::{
28    deposit::{DepositSimulation, DepositSimulationBuilder},
29    glv_deposit::{GlvDepositSimulation, GlvDepositSimulationBuilder},
30    glv_withdrawal::{GlvWithdrawalSimulation, GlvWithdrawalSimulationBuilder},
31    order::OrderSimulationBuilder,
32    shift::{ShiftSimulation, ShiftSimulationBuilder},
33    withdrawal::{WithdrawalSimulation, WithdrawalSimulationBuilder},
34};
35
36/// Order Simulation Builder.
37pub type OrderSimulationBuilderForSimulator<'a> = OrderSimulationBuilder<
38    'a,
39    (
40        (&'a mut Simulator,),
41        (CreateOrderKind,),
42        (&'a CreateOrderParams,),
43        (&'a Pubkey,),
44        (),
45        (),
46        (),
47        (),
48    ),
49>;
50
51/// Deposit Simulation Builder for Simulator.
52pub type DepositSimulationBuilderForSimulator<'a> = DepositSimulationBuilder<
53    'a,
54    (
55        (&'a mut Simulator,),
56        (&'a CreateDepositParams,),
57        (&'a Pubkey,),
58        (),
59        (),
60        (),
61        (),
62    ),
63>;
64
65/// Withdrawal Simulation Builder for Simulator.
66pub type WithdrawalSimulationBuilderForSimulator<'a> = WithdrawalSimulationBuilder<
67    'a,
68    (
69        (&'a mut Simulator,),
70        (&'a CreateWithdrawalParams,),
71        (&'a Pubkey,),
72        (),
73        (),
74        (),
75        (),
76    ),
77>;
78
79/// Shift Simulation Builder for Simulator.
80pub type ShiftSimulationBuilderForSimulator<'a> = ShiftSimulationBuilder<
81    'a,
82    (
83        (&'a mut Simulator,),
84        (&'a CreateShiftParams,),
85        (&'a Pubkey,),
86        (&'a Pubkey,),
87    ),
88>;
89
90/// GLV Deposit Simulation Builder for Simulator.
91pub type GlvDepositSimulationBuilderForSimulator<'a> = GlvDepositSimulationBuilder<
92    'a,
93    (
94        (&'a mut Simulator,),
95        (&'a CreateGlvDepositParams,),
96        (&'a Pubkey,),
97        (&'a Pubkey,),
98        (),
99        (),
100        (),
101        (),
102    ),
103>;
104
105/// GLV Withdrawal Simulation Builder for Simulator.
106pub type GlvWithdrawalSimulationBuilderForSimulator<'a> = GlvWithdrawalSimulationBuilder<
107    'a,
108    (
109        (&'a mut Simulator,),
110        (&'a CreateGlvWithdrawalParams,),
111        (&'a Pubkey,),
112        (&'a Pubkey,),
113        (),
114        (),
115        (),
116        (),
117    ),
118>;
119
120/// Price State.
121pub type PriceState = Option<Arc<Price<u128>>>;
122
123/// A simulator for actions.
124#[derive(Debug, Clone)]
125pub struct Simulator {
126    tokens: HashMap<Pubkey, TokenState>,
127    markets: HashMap<Pubkey, MarketModel>,
128    glvs: HashMap<Pubkey, GlvModel>,
129    vis: BTreeMap<Pubkey, VirtualInventoryModel>,
130}
131
132impl Simulator {
133    /// Create from parts.
134    pub fn from_parts(
135        tokens: HashMap<Pubkey, TokenState>,
136        markets: HashMap<Pubkey, MarketModel>,
137        glvs: HashMap<Pubkey, GlvModel>,
138        vis: BTreeMap<Pubkey, VirtualInventoryModel>,
139    ) -> Self {
140        Self {
141            tokens,
142            markets,
143            glvs,
144            vis,
145        }
146    }
147
148    /// Get market by its market token.
149    pub fn get_market(&self, market_token: &Pubkey) -> Option<&MarketModel> {
150        self.markets.get(market_token)
151    }
152
153    /// Get market mutably by its market token.
154    pub fn get_market_mut(&mut self, market_token: &Pubkey) -> Option<&mut MarketModel> {
155        self.markets.get_mut(market_token)
156    }
157
158    /// Get prices for the given token.
159    pub fn get_price(&self, token: &Pubkey) -> Option<Price<u128>> {
160        Some(*self.tokens.get(token)?.price.as_deref()?)
161    }
162
163    /// Upsert the prices for the give token.
164    ///
165    /// # Errors
166    /// Returns error if the token does not exist in the simulator.
167    pub fn insert_price(
168        &mut self,
169        token: &Pubkey,
170        price: Arc<Price<u128>>,
171    ) -> crate::Result<&mut Self> {
172        let state = self.tokens.get_mut(token).ok_or_else(|| {
173            crate::Error::custom(format!(
174                "[sim] token `{token}` is not found in the simulator"
175            ))
176        })?;
177        state.price = Some(price);
178        Ok(self)
179    }
180
181    /// Get prices for the given market meta.
182    pub fn get_prices(&self, meta: &MarketMeta) -> Option<Prices<u128>> {
183        let index_token_price = self.get_price(&meta.index_token_mint)?;
184        let long_token_price = self.get_price(&meta.long_token_mint)?;
185        let short_token_price = self.get_price(&meta.short_token_mint)?;
186        Some(Prices {
187            index_token_price,
188            long_token_price,
189            short_token_price,
190        })
191    }
192
193    pub(crate) fn get_prices_and_meta_for_market(
194        &self,
195        market_token: &Pubkey,
196    ) -> crate::Result<(Prices<u128>, &MarketMeta)> {
197        let market = self.markets.get(market_token).ok_or_else(|| {
198            crate::Error::custom(format!(
199                "[sim] market `{market_token}` not found in the simulator"
200            ))
201        })?;
202        let meta = &market.meta;
203        let prices = self.get_prices(meta).ok_or_else(|| {
204            crate::Error::custom(format!(
205                "[sim] prices for market `{market_token}` are not ready in the simulator"
206            ))
207        })?;
208        Ok((prices, meta))
209    }
210
211    pub(crate) fn get_prices_for_market(
212        &self,
213        market_token: &Pubkey,
214    ) -> crate::Result<Prices<u128>> {
215        Ok(self.get_prices_and_meta_for_market(market_token)?.0)
216    }
217
218    pub(crate) fn get_market_with_prices(
219        &self,
220        market_token: &Pubkey,
221    ) -> crate::Result<(&MarketModel, Prices<u128>)> {
222        let prices = self.get_prices_for_market(market_token)?;
223        let market = self.get_market(market_token).ok_or_else(|| {
224            crate::Error::custom(format!(
225                "[sim] market `{market_token}` not found in the simulator"
226            ))
227        })?;
228        Ok((market, prices))
229    }
230
231    /// Get mutable references to a market and the global VI map.
232    ///
233    /// This helper allows passing `&mut MarketModel` and `&mut BTreeMap<Pubkey, VirtualInventoryModel>`
234    /// to `MarketModel::with_vi_models` without cloning virtual inventories.
235    pub(crate) fn get_market_and_vis_mut(
236        &mut self,
237        market_token: &Pubkey,
238    ) -> crate::Result<(
239        &mut MarketModel,
240        &mut BTreeMap<Pubkey, VirtualInventoryModel>,
241    )> {
242        let Simulator {
243            tokens: _,
244            markets,
245            glvs: _,
246            vis,
247        } = self;
248
249        let market = markets.get_mut(market_token).ok_or_else(|| {
250            crate::Error::custom(format!(
251                "[sim] market `{market_token}` not found in the simulator"
252            ))
253        })?;
254
255        Ok((market, vis))
256    }
257
258    /// Get GLV by GLV token address.
259    pub fn get_glv(&self, glv_token: &Pubkey) -> Option<&GlvModel> {
260        self.glvs.get(glv_token)
261    }
262
263    /// Get GLV by GLV token address mutably.
264    pub fn get_glv_mut(&mut self, glv_token: &Pubkey) -> Option<&mut GlvModel> {
265        self.glvs.get_mut(glv_token)
266    }
267
268    /// Insert GLV model.
269    pub fn insert_glv(&mut self, glv: GlvModel) -> Option<GlvModel> {
270        self.glvs.insert(glv.glv_token, glv)
271    }
272
273    /// Get a mutable reference to the global virtual inventory map.
274    ///
275    /// This is used by simulations that need to attach VI models to cloned
276    /// `MarketModel` instances via `MarketModel::with_vi_models` without
277    /// cloning the underlying virtual inventory state.
278    pub(crate) fn vis_mut(&mut self) -> &mut BTreeMap<Pubkey, VirtualInventoryModel> {
279        &mut self.vis
280    }
281
282    /// Swap along the provided path.
283    ///
284    /// # Arguments
285    /// * `path` - The path of market tokens to swap along
286    /// * `source_token` - The source token to swap from
287    /// * `amount` - The amount to swap
288    /// * `options` - Optional simulation options. If `None`, default options are used.
289    pub fn swap_along_path(
290        &mut self,
291        path: &[Pubkey],
292        source_token: &Pubkey,
293        amount: u128,
294        options: Option<SimulationOptions>,
295    ) -> crate::Result<SwapOutput> {
296        self.swap_along_path_with_options(path, source_token, amount, options.unwrap_or_default())
297    }
298
299    /// Swap along the provided path with options.
300    pub(crate) fn swap_along_path_with_options(
301        &mut self,
302        path: &[Pubkey],
303        source_token: &Pubkey,
304        mut amount: u128,
305        options: SimulationOptions,
306    ) -> crate::Result<SwapOutput> {
307        let mut current_token = *source_token;
308
309        let mut reports = Vec::with_capacity(path.len());
310        for market_token in path {
311            // Fetch prices first; this only needs an immutable borrow.
312            let prices = self.get_prices_for_market(market_token)?;
313
314            // Then borrow market (and VI map if needed) mutably.
315            let (market, maybe_vi_map) = if options.disable_vis {
316                (
317                    self.get_market_mut(market_token).ok_or_else(|| {
318                        crate::Error::custom(format!(
319                            "[sim] market `{market_token}` not found in the simulator"
320                        ))
321                    })?,
322                    None,
323                )
324            } else {
325                let (market, vi_map) = self.get_market_and_vis_mut(market_token)?;
326                (market, Some(vi_map))
327            };
328
329            let meta = &market.meta;
330            if meta.long_token_mint == meta.short_token_mint {
331                return Err(crate::Error::custom(format!(
332                    "[swap] `{market_token}` is not a swappable market"
333                )));
334            }
335            let is_token_in_long = if meta.long_token_mint == current_token {
336                current_token = meta.short_token_mint;
337                true
338            } else if meta.short_token_mint == current_token {
339                current_token = meta.long_token_mint;
340                false
341            } else {
342                return Err(crate::Error::custom(format!(
343                    "[swap] invalid swap step. Current step: {market_token}"
344                )));
345            };
346            let report = match maybe_vi_map {
347                None => market.with_vis_disabled(|market| {
348                    market.swap(is_token_in_long, amount, prices)?.execute()
349                })?,
350                Some(vi_map) => market.with_vi_models(vi_map, |market| {
351                    market.swap(is_token_in_long, amount, prices)?.execute()
352                })?,
353            };
354            amount = *report.token_out_amount();
355            reports.push(report);
356        }
357
358        Ok(SwapOutput {
359            output_token: current_token,
360            amount,
361            reports,
362        })
363    }
364
365    /// Get token states.
366    pub fn tokens(&self) -> impl Iterator<Item = (&Pubkey, &TokenState)> {
367        self.tokens.iter()
368    }
369
370    /// Get market states.
371    pub fn markets(&self) -> impl Iterator<Item = (&Pubkey, &MarketModel)> {
372        self.markets.iter()
373    }
374
375    /// Get GLV states.
376    pub fn glvs(&self) -> impl Iterator<Item = (&Pubkey, &GlvModel)> {
377        self.glvs.iter()
378    }
379
380    /// Insert virtual inventory model.
381    pub fn insert_vi(
382        &mut self,
383        vi_address: Pubkey,
384        vi: VirtualInventoryModel,
385    ) -> Option<VirtualInventoryModel> {
386        self.vis.insert(vi_address, vi)
387    }
388
389    /// Get virtual inventory model by address.
390    pub fn get_vi(&self, vi_address: &Pubkey) -> Option<&VirtualInventoryModel> {
391        self.vis.get(vi_address)
392    }
393
394    /// Get all virtual inventory states.
395    pub fn vis(&self) -> impl Iterator<Item = (&Pubkey, &VirtualInventoryModel)> {
396        self.vis.iter()
397    }
398
399    /// Create a builder for order simulation.
400    pub fn simulate_order<'a>(
401        &'a mut self,
402        kind: CreateOrderKind,
403        params: &'a CreateOrderParams,
404        collateral_or_swap_out_token: &'a Pubkey,
405    ) -> OrderSimulationBuilderForSimulator<'a> {
406        OrderSimulation::builder()
407            .simulator(self)
408            .kind(kind)
409            .params(params)
410            .collateral_or_swap_out_token(collateral_or_swap_out_token)
411    }
412
413    /// Create a builder for deposit simulation.
414    pub fn simulate_deposit<'a>(
415        &'a mut self,
416        market_token: &'a Pubkey,
417        params: &'a CreateDepositParams,
418    ) -> DepositSimulationBuilderForSimulator<'a> {
419        DepositSimulation::builder()
420            .simulator(self)
421            .market_token(market_token)
422            .params(params)
423    }
424
425    /// Create a builder for withdrawal simulation.
426    pub fn simulate_withdrawal<'a>(
427        &'a mut self,
428        market_token: &'a Pubkey,
429        params: &'a CreateWithdrawalParams,
430    ) -> WithdrawalSimulationBuilderForSimulator<'a> {
431        WithdrawalSimulation::builder()
432            .simulator(self)
433            .market_token(market_token)
434            .params(params)
435    }
436
437    /// Create a builder for GLV deposit simulation.
438    pub fn simulate_glv_deposit<'a>(
439        &'a mut self,
440        glv_token: &'a Pubkey,
441        market_token: &'a Pubkey,
442        params: &'a CreateGlvDepositParams,
443    ) -> GlvDepositSimulationBuilderForSimulator<'a> {
444        GlvDepositSimulation::builder()
445            .simulator(self)
446            .glv_token(glv_token)
447            .market_token(market_token)
448            .params(params)
449    }
450
451    /// Create a builder for GLV withdrawal simulation.
452    pub fn simulate_glv_withdrawal<'a>(
453        &'a mut self,
454        glv_token: &'a Pubkey,
455        market_token: &'a Pubkey,
456        params: &'a CreateGlvWithdrawalParams,
457    ) -> GlvWithdrawalSimulationBuilderForSimulator<'a> {
458        GlvWithdrawalSimulation::builder()
459            .simulator(self)
460            .glv_token(glv_token)
461            .market_token(market_token)
462            .params(params)
463    }
464
465    /// Create a builder for shift simulation.
466    pub fn simulate_shift<'a>(
467        &'a mut self,
468        from_market_token: &'a Pubkey,
469        to_market_token: &'a Pubkey,
470        params: &'a CreateShiftParams,
471    ) -> ShiftSimulationBuilderForSimulator<'a> {
472        ShiftSimulation::builder()
473            .simulator(self)
474            .from_market_token(from_market_token)
475            .to_market_token(to_market_token)
476            .params(params)
477    }
478}
479
480/// Options for simulation.
481#[derive(Debug, Default, Clone)]
482pub struct SimulationOptions {
483    /// Whether to skip the validation for limit price.
484    pub skip_limit_price_validation: bool,
485    /// Whether to disable the use of virtual inventories during simulation.
486    pub disable_vis: bool,
487}
488
489/// Token state for [`Simulator`].
490#[derive(Debug, Clone)]
491pub struct TokenState {
492    price: PriceState,
493}
494
495impl TokenState {
496    /// Create from [`PriceState`].
497    pub fn from_price(price: PriceState) -> Self {
498        Self { price }
499    }
500
501    /// Get price state.
502    pub fn price(&self) -> &PriceState {
503        &self.price
504    }
505}
506
507/// Swap output.
508#[derive(Debug, Clone)]
509pub struct SwapOutput {
510    pub(crate) output_token: Pubkey,
511    pub(crate) amount: u128,
512    pub(crate) reports: Vec<SwapReport<u128, i128>>,
513}
514
515impl SwapOutput {
516    /// Returns the output token.
517    pub fn output_token(&self) -> &Pubkey {
518        &self.output_token
519    }
520
521    /// Returns the output amount.
522    pub fn amount(&self) -> u128 {
523        self.amount
524    }
525
526    /// Returns the swap reports.
527    pub fn reports(&self) -> &[SwapReport<u128, i128>] {
528        &self.reports
529    }
530}
531
532impl MarketCalculator for Simulator {
533    fn get_market_model(&self, market_token: &Pubkey) -> Option<&MarketModel> {
534        self.get_market(market_token)
535    }
536
537    fn get_token_price(&self, token: &Pubkey) -> Option<Price<u128>> {
538        self.get_price(token)
539    }
540}
541
542impl GlvCalculator for Simulator {
543    fn get_glv_model(&self, glv_token: &Pubkey) -> Option<&GlvModel> {
544        self.get_glv(glv_token)
545    }
546}