Skip to main content

gmsol_sdk/market_graph/
mod.rs

1use std::{
2    collections::{hash_map::Entry, BTreeMap, HashMap, HashSet},
3    fmt,
4    sync::Arc,
5};
6
7use either::Either;
8use gmsol_model::price::{Price, Prices};
9use gmsol_programs::{
10    gmsol_store::types::MarketMeta,
11    model::{MarketModel, VirtualInventoryModel},
12};
13use gmsol_utils::pubkey::DEFAULT_PUBKEY;
14use petgraph::{
15    graph::{EdgeIndex, NodeIndex},
16    prelude::StableDiGraph,
17    visit::{EdgeRef, IntoNodeIdentifiers, NodeIndexable},
18};
19use rust_decimal::{Decimal, MathematicalOps};
20use solana_sdk::pubkey::Pubkey;
21
22#[allow(deprecated)]
23#[cfg(simulation)]
24use crate::market_graph::simulation::order::{OrderSimulation, OrderSimulationBuilder};
25
26#[cfg(simulation)]
27use crate::{
28    builders::order::{CreateOrderKind, CreateOrderParams},
29    serde::StringPubkey,
30    simulation::Simulator,
31};
32
33#[cfg(simulation)]
34pub use crate::simulation::SwapOutput;
35
36use self::estimation::SwapEstimation;
37
38pub use self::{
39    config::MarketGraphConfig, error::MarketGraphError, estimation::SwapEstimationParams,
40};
41
42/// Estimation.
43pub mod estimation;
44
45/// Config.
46pub mod config;
47
48/// Error type.
49pub mod error;
50
51/// Execution simulation.
52#[deprecated(since = "0.8.0", note = "use `Simulator` instead")]
53#[cfg(simulation)]
54pub mod simulation;
55
56type Graph = StableDiGraph<Node, Edge>;
57
58/// Order Simulation Builder.
59#[deprecated(
60    since = "0.8.0",
61    note = "use `OrderSimulationBuilderForSimulator` instead"
62)]
63#[allow(deprecated)]
64#[cfg(simulation)]
65pub type OrderSimulationBuilderForGraph<'a> = OrderSimulationBuilder<
66    'a,
67    (
68        (&'a MarketGraph,),
69        (CreateOrderKind,),
70        (&'a CreateOrderParams,),
71        (&'a Pubkey,),
72        (),
73        (),
74        (),
75        (),
76    ),
77>;
78
79#[derive(Debug, Clone)]
80struct Node {
81    #[cfg_attr(not(simulation), allow(dead_code))]
82    token: Pubkey,
83    price: Option<Arc<Price<u128>>>,
84}
85
86impl Node {
87    fn new(token: Pubkey) -> Self {
88        Self { token, price: None }
89    }
90}
91
92#[derive(Debug, Clone)]
93struct Edge {
94    market_token: Pubkey,
95    estimated: Option<SwapEstimation>,
96}
97
98impl Edge {
99    fn new(market_token: Pubkey, estimated: Option<SwapEstimation>) -> Self {
100        Self {
101            market_token,
102            estimated,
103        }
104    }
105
106    fn cost(&self) -> Option<Decimal> {
107        Some(-self.estimated.as_ref()?.ln_exchange_rate)
108    }
109}
110
111#[derive(Debug, Clone)]
112struct IndexTokenState {
113    node: Node,
114    markets: HashSet<Pubkey>,
115}
116
117#[derive(Debug, Clone)]
118struct CollateralTokenState {
119    ix: NodeIndex,
120    markets: HashSet<Pubkey>,
121}
122
123#[derive(Debug, Clone)]
124struct MarketState {
125    market: MarketModel,
126    long_edge: EdgeIndex,
127    short_edge: EdgeIndex,
128}
129
130impl MarketState {
131    fn new(market: MarketModel, long_edge: EdgeIndex, short_edge: EdgeIndex) -> Self {
132        Self {
133            market,
134            long_edge,
135            short_edge,
136        }
137    }
138}
139
140/// Market Graph.
141#[derive(Debug, Clone)]
142pub struct MarketGraph {
143    index_tokens: HashMap<Pubkey, IndexTokenState>,
144    collateral_tokens: HashMap<Pubkey, CollateralTokenState>,
145    markets: HashMap<Pubkey, MarketState>,
146    graph: Graph,
147    config: MarketGraphConfig,
148    vis: BTreeMap<Pubkey, VirtualInventoryModel>,
149}
150
151type Distances = Vec<Option<Decimal>>;
152type Predecessors = Vec<Option<(NodeIndex, Pubkey)>>;
153
154impl Default for MarketGraph {
155    fn default() -> Self {
156        Self::with_config(MarketGraphConfig::default())
157    }
158}
159
160impl MarketGraph {
161    /// Create from the given [`MarketGraphConfig`].
162    pub fn with_config(config: MarketGraphConfig) -> Self {
163        Self {
164            index_tokens: Default::default(),
165            collateral_tokens: Default::default(),
166            markets: Default::default(),
167            graph: Default::default(),
168            config,
169            vis: Default::default(),
170        }
171    }
172
173    /// Insert or update a market.
174    ///
175    /// Return `true` if the market is newly inserted.
176    pub fn insert_market(&mut self, market: MarketModel) -> bool {
177        self.insert_market_with_options(market, true)
178    }
179
180    /// Insert or update a market.
181    ///
182    /// Return `true` if the market is newly inserted.
183    pub fn insert_market_with_options(
184        &mut self,
185        market: MarketModel,
186        update_estimation: bool,
187    ) -> bool {
188        let key = market.meta.market_token_mint;
189        let (long_token_ix, short_token_ix) = self.insert_tokens_with_meta(&market.meta);
190        match self.markets.entry(key) {
191            Entry::Vacant(e) => {
192                let long_edge =
193                    self.graph
194                        .add_edge(long_token_ix, short_token_ix, Edge::new(key, None));
195                let short_edge =
196                    self.graph
197                        .add_edge(short_token_ix, long_token_ix, Edge::new(key, None));
198                e.insert(MarketState::new(market, long_edge, short_edge));
199                if update_estimation {
200                    self.update_estimation(Some(&key));
201                }
202                true
203            }
204            Entry::Occupied(mut e) => {
205                let state = e.get_mut();
206                state.market = market;
207                if update_estimation {
208                    self.update_estimation(Some(&key));
209                }
210                false
211            }
212        }
213    }
214
215    /// Insert or update virtual inventory.
216    pub fn insert_vi_options(
217        &mut self,
218        vi_address: Pubkey,
219        vi: VirtualInventoryModel,
220        update_estimation: bool,
221    ) -> Option<VirtualInventoryModel> {
222        let old = self.vis.insert(vi_address, vi);
223        if update_estimation {
224            self.update_estimation(None);
225        }
226        old
227    }
228
229    /// Get virtual inventory by address.
230    pub fn get_vi(&self, vi_address: &Pubkey) -> Option<&VirtualInventoryModel> {
231        self.vis.get(vi_address)
232    }
233
234    /// Remove virtual inventory.
235    pub fn remove_vi(&mut self, vi_address: &Pubkey) -> Option<VirtualInventoryModel> {
236        self.vis.remove(vi_address)
237    }
238
239    /// Get all virtual inventory states.
240    pub fn vis(&self) -> impl Iterator<Item = (&Pubkey, &VirtualInventoryModel)> {
241        self.vis.iter()
242    }
243
244    fn update_estimation(&mut self, only: Option<&Pubkey>) {
245        let markets = only
246            .map(|token| Either::Left(self.markets.get(token).into_iter()))
247            .unwrap_or_else(|| Either::Right(self.markets.values()));
248        for state in markets {
249            let prices = self.get_prices(&state.market.meta);
250            let vi_for_swaps = (state.market.virtual_inventory_for_swaps != DEFAULT_PUBKEY)
251                .then_some(state.market.virtual_inventory_for_swaps)
252                .and_then(|vi_addr| self.vis.get(&vi_addr))
253                .map(|vi| (state.market.virtual_inventory_for_swaps, vi));
254
255            let long_edge = self
256                .graph
257                .edge_weight_mut(state.long_edge)
258                .expect("internal: inconsistent market map");
259            long_edge.estimated = self
260                .config
261                .estimate(&state.market, true, prices, vi_for_swaps);
262            let short_edge = self
263                .graph
264                .edge_weight_mut(state.short_edge)
265                .expect("internal: inconsistent market map");
266            short_edge.estimated = self
267                .config
268                .estimate(&state.market, false, prices, vi_for_swaps);
269        }
270    }
271
272    pub(crate) fn update_token_price_state(&mut self, token: &Pubkey, price: Arc<Price<u128>>) {
273        if let Some(state) = self.index_tokens.get_mut(token) {
274            state.node.price = Some(price.clone());
275        }
276        if let Some(state) = self.collateral_tokens.get(token) {
277            self.graph
278                .node_weight_mut(state.ix)
279                .expect("internal: inconsistent token map")
280                .price = Some(price);
281        }
282        let related_markets_for_index_token = self
283            .index_tokens
284            .get(token)
285            .map(|state| state.markets.iter())
286            .into_iter()
287            .flatten();
288        let related_markets_for_collateral_token = self
289            .collateral_tokens
290            .get(token)
291            .map(|state| state.markets.iter())
292            .into_iter()
293            .flatten();
294        let related_markets = related_markets_for_index_token
295            .chain(related_markets_for_collateral_token)
296            .copied()
297            .collect::<HashSet<_>>();
298        for market_token in related_markets {
299            self.update_estimation(Some(&market_token));
300        }
301    }
302
303    /// Update token price.
304    ///
305    /// Return `true` if the token exists.
306    pub fn update_token_price(&mut self, token: &Pubkey, price: &Price<u128>) {
307        self.update_token_price_state(token, Arc::new(*price));
308    }
309
310    /// Update value for the estimation.
311    pub fn update_value(&mut self, value: u128) {
312        let mut config = self.config;
313        config.swap_estimation_params.value = value;
314        self.update_config(config, true);
315    }
316
317    /// Update base cost.
318    pub fn update_base_cost(&mut self, base_cost: u128) {
319        let mut config = self.config;
320        config.swap_estimation_params.base_cost = base_cost;
321        self.update_config(config, true);
322    }
323
324    /// Update max steps.
325    pub fn update_max_steps(&mut self, max_steps: usize) {
326        self.update_config(
327            MarketGraphConfig {
328                max_steps,
329                ..self.config
330            },
331            false,
332        );
333    }
334
335    /// Update config.
336    fn update_config(&mut self, config: MarketGraphConfig, should_update_estimation: bool) {
337        self.config = config;
338        if should_update_estimation {
339            self.update_estimation(None);
340        }
341    }
342
343    fn insert_collateral_token(&mut self, token: Pubkey, market_token: Pubkey) -> NodeIndex {
344        match self.collateral_tokens.entry(token) {
345            Entry::Vacant(e) => {
346                let ix = self.graph.add_node(Node::new(token));
347                let state = CollateralTokenState {
348                    ix,
349                    markets: HashSet::from([market_token]),
350                };
351                e.insert(state);
352                ix
353            }
354            Entry::Occupied(mut e) => {
355                e.get_mut().markets.insert(market_token);
356                e.get().ix
357            }
358        }
359    }
360
361    fn insert_index_token(&mut self, index_token: Pubkey, market_token: Pubkey) {
362        self.index_tokens
363            .entry(index_token)
364            .or_insert_with(|| IndexTokenState {
365                markets: HashSet::default(),
366                node: Node::new(index_token),
367            })
368            .markets
369            .insert(market_token);
370    }
371
372    fn insert_tokens_with_meta(&mut self, meta: &MarketMeta) -> (NodeIndex, NodeIndex) {
373        self.insert_index_token(meta.index_token_mint, meta.market_token_mint);
374        let long_token_ix =
375            self.insert_collateral_token(meta.long_token_mint, meta.market_token_mint);
376        let short_token_ix =
377            self.insert_collateral_token(meta.short_token_mint, meta.market_token_mint);
378        (long_token_ix, short_token_ix)
379    }
380
381    fn get_token_node(&self, token: &Pubkey) -> Option<&Node> {
382        if let Some(state) = self.index_tokens.get(token) {
383            Some(&state.node)
384        } else {
385            let state = self.collateral_tokens.get(token)?;
386            self.graph.node_weight(state.ix)
387        }
388    }
389
390    fn get_price(&self, token: &Pubkey) -> Option<Price<u128>> {
391        self.get_token_node(token)
392            .and_then(|node| node.price.as_deref().copied())
393    }
394
395    fn get_prices(&self, meta: &MarketMeta) -> Option<Prices<u128>> {
396        let index_token_price = self.get_price(&meta.index_token_mint)?;
397        let long_token_price = self.get_price(&meta.long_token_mint)?;
398        let short_token_price = self.get_price(&meta.short_token_mint)?;
399        Some(Prices {
400            index_token_price,
401            long_token_price,
402            short_token_price,
403        })
404    }
405
406    /// Get market by its market token.
407    pub fn get_market(&self, market_token: &Pubkey) -> Option<&MarketModel> {
408        Some(&self.markets.get(market_token)?.market)
409    }
410
411    /// Get all markets.
412    pub fn markets(&self) -> impl Iterator<Item = &MarketModel> {
413        self.markets.values().map(|state| &state.market)
414    }
415
416    /// Get all market tokens.
417    pub fn market_tokens(&self) -> impl Iterator<Item = &Pubkey> {
418        self.markets.keys()
419    }
420
421    /// Get all index tokens.
422    pub fn index_tokens(&self) -> impl Iterator<Item = &Pubkey> {
423        self.index_tokens.keys()
424    }
425
426    fn to_index(&self, ix: NodeIndex) -> usize {
427        self.graph.to_index(ix)
428    }
429
430    /// Bellman-Ford algorithm with a maximum step limit.
431    ///
432    /// It computes the shortest paths in the subgraph reachable from the source
433    /// within at most `max_steps` steps.
434    fn bellman_ford(&self, source: &Pubkey) -> crate::Result<(Distances, Predecessors)> {
435        let source = self
436            .collateral_tokens
437            .get(source)
438            .ok_or_else(|| crate::Error::custom("the source is not a known collateral token"))?
439            .ix;
440
441        let g = &self.graph;
442        let max_steps = self.config.max_steps;
443        let mut predecessors = vec![None; g.node_bound()];
444        let mut distances = vec![None; g.node_bound()];
445        distances[self.to_index(source)] = Some(Decimal::ZERO);
446
447        let mut result_distances = None;
448
449        for steps in 1..self.graph.node_count() {
450            let mut did_update = false;
451            for i in g.node_identifiers() {
452                for edge in g.edges(i) {
453                    let j = edge.target();
454                    let Some(w) = edge.weight().cost() else {
455                        continue;
456                    };
457                    let Some(d) = distances[self.to_index(i)] else {
458                        continue;
459                    };
460                    if distances[self.to_index(j)]
461                        .map(|current| d + w < current)
462                        .unwrap_or(true)
463                    {
464                        distances[self.to_index(j)] = distances[self.to_index(i)].map(|d| d + w);
465
466                        // Only update predecessors if the current step is within `max_steps`.
467                        if steps <= max_steps {
468                            predecessors[self.to_index(j)] = Some((i, edge.weight().market_token));
469                        }
470
471                        did_update = true;
472                    }
473                }
474            }
475
476            if !did_update {
477                break;
478            }
479
480            // Cache the result within the `max_steps`.
481            if steps == max_steps {
482                result_distances = Some(distances.clone());
483            }
484        }
485
486        // Check for negative weight cycle.
487        for i in g.node_identifiers() {
488            for edge in g.edges(i) {
489                let j = edge.target();
490                let Some(w) = edge.weight().cost() else {
491                    continue;
492                };
493                let Some(d) = distances[self.to_index(i)] else {
494                    continue;
495                };
496                if distances[self.to_index(j)]
497                    .map(|jd| d + w < jd)
498                    .unwrap_or(true)
499                {
500                    return Err(MarketGraphError::NegativeCycle.into());
501                }
502            }
503        }
504
505        Ok((result_distances.unwrap_or(distances), predecessors))
506    }
507
508    fn dfs(&self, source: &Pubkey) -> crate::Result<(Distances, Predecessors)> {
509        let source = self
510            .collateral_tokens
511            .get(source)
512            .ok_or_else(|| crate::Error::custom("the source is not a known collateral token"))?
513            .ix;
514
515        let g = &self.graph;
516        let mut predecessors = vec![None; g.node_bound()];
517        let mut distances = vec![None; g.node_bound()];
518        let mut visited_nodes = HashSet::<NodeIndex>::new();
519
520        self.dfs_recursive(
521            source,
522            Some(Decimal::ZERO),
523            None,
524            0,
525            &mut visited_nodes,
526            &mut distances,
527            &mut predecessors,
528        );
529
530        Ok((distances, predecessors))
531    }
532
533    #[allow(clippy::too_many_arguments)]
534    fn dfs_recursive(
535        &self,
536        current: NodeIndex,
537        distance: Option<Decimal>,
538        predecessor: Option<(NodeIndex, Pubkey)>,
539        steps: usize,
540        visited_nodes: &mut HashSet<NodeIndex>,
541        distances: &mut Distances,
542        predecessors: &mut Predecessors,
543    ) {
544        let i = current;
545        if steps > self.config.max_steps {
546            return;
547        }
548        let Some(d) = distance else {
549            return;
550        };
551        let best_d = distances[self.to_index(i)];
552        if best_d.map(|best| d >= best).unwrap_or(false) {
553            return;
554        }
555
556        visited_nodes.insert(i);
557
558        distances[self.to_index(i)] = Some(d);
559        predecessors[self.to_index(i)] = predecessor;
560
561        for edge in self.graph.edges(i) {
562            let j = edge.target();
563            if visited_nodes.contains(&j) {
564                continue;
565            }
566            self.dfs_recursive(
567                j,
568                edge.weight().cost().map(|w| w + d),
569                Some((i, edge.weight().market_token)),
570                steps + 1,
571                visited_nodes,
572                distances,
573                predecessors,
574            );
575        }
576
577        visited_nodes.remove(&i);
578    }
579
580    /// Find the best swap path for the given source and target.
581    pub fn best_swap_paths(
582        &self,
583        source: &Pubkey,
584        skip_bellman_ford: bool,
585    ) -> crate::Result<BestSwapPaths<'_>> {
586        let (distances, predecessors, arbitrage_exists) = if skip_bellman_ford {
587            let results = self.dfs(source)?;
588            (results.0, results.1, None)
589        } else {
590            match self.bellman_ford(source) {
591                Ok(results) => (results.0, results.1, Some(false)),
592                // Fallback to DFS.
593                Err(crate::Error::MarketGraph(MarketGraphError::NegativeCycle)) => {
594                    let results = self.dfs(source)?;
595                    (results.0, results.1, Some(true))
596                }
597                Err(err) => return Err(err),
598            }
599        };
600
601        Ok(BestSwapPaths {
602            graph: self,
603            source: *source,
604            distances,
605            predecessors,
606            arbitrage_exists,
607        })
608    }
609
610    /// Swap along the provided path with the given price updater.
611    #[deprecated(since = "0.8.0", note = "use `to_simulator` instead")]
612    #[cfg(simulation)]
613    pub fn swap_along_path_with_price_updater(
614        &self,
615        path: &[Pubkey],
616        source_token: &Pubkey,
617        mut amount: u128,
618        mut price_updater: impl FnMut(&MarketMeta, &mut Prices<u128>) -> crate::Result<()>,
619    ) -> crate::Result<SwapOutput> {
620        use crate::model::{MarketAction, SwapMarketMutExt};
621
622        let mut current_token = *source_token;
623
624        let mut reports = Vec::with_capacity(path.len());
625        for market_token in path {
626            let mut market = self
627                .get_market(market_token)
628                .ok_or_else(|| {
629                    crate::Error::custom(format!(
630                        "[swap] market `{market_token}` not found in the graph"
631                    ))
632                })?
633                .clone();
634            let meta = &market.meta;
635            if meta.long_token_mint == meta.short_token_mint {
636                return Err(crate::Error::custom(format!(
637                    "[swap] `{market_token} is not a swappable market"
638                )));
639            }
640            let is_token_in_long = if meta.long_token_mint == current_token {
641                current_token = meta.short_token_mint;
642                true
643            } else if meta.short_token_mint == current_token {
644                current_token = meta.long_token_mint;
645                false
646            } else {
647                return Err(crate::Error::custom(format!(
648                    "[swap] invalid swap step. Current step: {market_token}"
649                )));
650            };
651            let mut prices = self.get_prices(meta).ok_or_else(|| {
652                crate::Error::custom(format!("[swap] prices for {market_token} are not ready"))
653            })?;
654            (price_updater)(meta, &mut prices)?;
655            let report = market.with_vis_disabled(|market| {
656                market.swap(is_token_in_long, amount, prices)?.execute()
657            })?;
658            amount = *report.token_out_amount();
659            reports.push(report);
660        }
661
662        Ok(SwapOutput {
663            output_token: current_token,
664            amount,
665            reports,
666        })
667    }
668
669    /// Swap along the provided path.
670    #[deprecated(since = "0.8.0", note = "use `to_simulator` instead")]
671    #[allow(deprecated)]
672    #[cfg(simulation)]
673    pub fn swap_along_path(
674        &self,
675        path: &[Pubkey],
676        source_token: &Pubkey,
677        amount: u128,
678    ) -> crate::Result<SwapOutput> {
679        self.swap_along_path_with_price_updater(path, source_token, amount, |_meta, _prices| Ok(()))
680    }
681
682    /// Create a builder for order simulation.
683    #[deprecated(since = "0.8.0", note = "use `to_simulator` instead")]
684    #[allow(deprecated)]
685    #[cfg(simulation)]
686    pub fn simulate_order<'a>(
687        &'a self,
688        kind: CreateOrderKind,
689        params: &'a CreateOrderParams,
690        collateral_or_swap_out_token: &'a Pubkey,
691    ) -> OrderSimulationBuilderForGraph<'a> {
692        OrderSimulation::builder()
693            .graph(self)
694            .kind(kind)
695            .params(params)
696            .collateral_or_swap_out_token(collateral_or_swap_out_token)
697    }
698
699    /// Update with [`Simulator`].
700    #[cfg(simulation)]
701    pub fn update_with_simulator(
702        &mut self,
703        simulator: &Simulator,
704        options: UpdateGraphWithSimulatorOptions,
705    ) {
706        let update_vis = !options.skip_vis_update;
707        let update_markets = !options.skip_markets_update;
708        let update_token_prices = options.update_token_prices;
709
710        if update_vis {
711            for (vi_address, vi) in simulator.vis() {
712                self.insert_vi_options(*vi_address, vi.clone(), false);
713            }
714        }
715
716        if update_markets {
717            for (_, market) in simulator.markets() {
718                self.insert_market_with_options(market.clone(), false);
719            }
720        }
721
722        if update_token_prices {
723            for (token, state) in simulator.tokens() {
724                if let Some(price) = state.price() {
725                    self.update_token_price_state(token, price.clone());
726                }
727            }
728        } else {
729            self.update_estimation(None);
730        }
731    }
732
733    /// Create a [`Simulator`].
734    #[cfg(simulation)]
735    pub fn to_simulator(&self, options: CreateGraphSimulatorOptions) -> Simulator {
736        use crate::simulation::simulator::TokenState;
737
738        let market_filter = options.market_filter.as_ref();
739        let mut token_filter: Option<HashSet<_>> = None;
740        let markets = self
741            .markets
742            .iter()
743            .filter_map(|(market_token, state)| {
744                if let Some(filter) = market_filter {
745                    if !filter.contains(market_token) {
746                        return None;
747                    }
748                    let token_filter = token_filter.get_or_insert_default();
749                    token_filter.insert(state.market.meta.index_token_mint);
750                    token_filter.insert(state.market.meta.long_token_mint);
751                    token_filter.insert(state.market.meta.short_token_mint);
752                };
753
754                Some((*market_token, state.market.clone()))
755            })
756            .collect();
757
758        let index_token_prices = self
759            .index_tokens
760            .iter()
761            .map(|(token, state)| (*token, state.node.price.clone()));
762        let collateral_token_prices = self
763            .graph
764            .node_weights()
765            .map(|node| (node.token, node.price.clone()));
766
767        let tokens = index_token_prices
768            .chain(collateral_token_prices)
769            .filter(|(token, _)| {
770                if let Some(token_filter) = token_filter.as_ref() {
771                    token_filter.contains(token)
772                } else {
773                    true
774                }
775            })
776            .map(|(token, price)| (token, TokenState::from_price(price)))
777            .collect();
778
779        crate::simulation::Simulator::from_parts(
780            tokens,
781            markets,
782            Default::default(),
783            self.vis.clone(),
784        )
785    }
786}
787
788/// Options for creating [`Simulator`].
789#[cfg(simulation)]
790#[derive(Debug, Default, Clone)]
791#[cfg_attr(serde, derive(serde::Serialize, serde::Deserialize))]
792#[cfg_attr(js, derive(tsify_next::Tsify))]
793#[cfg_attr(js, tsify(into_wasm_abi, from_wasm_abi))]
794pub struct CreateGraphSimulatorOptions {
795    /// Market filter.
796    #[cfg_attr(serde, serde(default))]
797    pub market_filter: Option<HashSet<StringPubkey>>,
798}
799
800/// Options for updating [`MarketGraph`] with [`Simulator`].
801#[cfg(simulation)]
802#[derive(Debug, Default, Clone)]
803#[cfg_attr(serde, derive(serde::Serialize, serde::Deserialize))]
804#[cfg_attr(js, derive(tsify_next::Tsify))]
805#[cfg_attr(js, tsify(into_wasm_abi, from_wasm_abi))]
806pub struct UpdateGraphWithSimulatorOptions {
807    /// Whether to update token prices with the simulator.
808    #[cfg_attr(serde, serde(default))]
809    pub update_token_prices: bool,
810    /// Whether to not update markets with the simulator.
811    #[cfg_attr(serde, serde(default))]
812    pub skip_markets_update: bool,
813    /// Whether to not update virtual inventories with the simulator.
814    #[cfg_attr(serde, serde(default))]
815    pub skip_vis_update: bool,
816}
817
818/// Best Swap Paths.
819pub struct BestSwapPaths<'a> {
820    graph: &'a MarketGraph,
821    source: Pubkey,
822    distances: Distances,
823    predecessors: Predecessors,
824    arbitrage_exists: Option<bool>,
825}
826
827impl fmt::Debug for BestSwapPaths<'_> {
828    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
829        f.debug_struct("BestSwapPaths")
830            .field("source", &self.source)
831            .field("distances", &self.distances)
832            .field("predecessors", &self.predecessors)
833            .field("arbitrage_exists", &self.arbitrage_exists)
834            .finish()
835    }
836}
837
838impl BestSwapPaths<'_> {
839    /// Get the source.
840    pub fn source(&self) -> &Pubkey {
841        &self.source
842    }
843
844    /// Get swap estimation params.
845    pub fn params(&self) -> &SwapEstimationParams {
846        &self.graph.config.swap_estimation_params
847    }
848
849    /// Return whether there is an arbitrage opportunity.
850    ///
851    /// Return `None` if it is unknown.
852    pub fn arbitrage_exists(&self) -> Option<bool> {
853        self.arbitrage_exists
854    }
855
856    /// Get best swap path to the target.
857    pub fn to(&self, target: &Pubkey) -> (Option<Decimal>, Vec<Pubkey>) {
858        let Self {
859            graph,
860            distances,
861            predecessors,
862            source,
863            ..
864        } = self;
865
866        let Some(target_state) = graph.collateral_tokens.get(target) else {
867            return (None, vec![]);
868        };
869
870        let target_ix = target_state.ix;
871        let target_ix = graph.to_index(target_ix);
872
873        let distance = distances[target_ix];
874
875        if *source == *target {
876            return (distance.map(distance_to_exchange_rate), vec![]);
877        }
878
879        let mut path = vec![];
880        let mut current = predecessors[target_ix];
881        let mut steps = 0;
882        while let Some((predecessor, market_token)) = current.as_ref() {
883            steps += 1;
884            if steps > graph.config.max_steps {
885                return (None, vec![]);
886            }
887            path.push(*market_token);
888            current = predecessors[graph.to_index(*predecessor)];
889        }
890
891        path.reverse();
892
893        (
894            if path.is_empty() {
895                // Since `target != source`, an empty path means there's no valid distance.
896                None
897            } else {
898                distance.map(distance_to_exchange_rate)
899            },
900            path,
901        )
902    }
903}
904
905fn distance_to_exchange_rate(d: Decimal) -> Decimal {
906    (-d).exp()
907}
908
909#[cfg(test)]
910mod tests {
911    use std::sync::Arc;
912
913    use gmsol_programs::gmsol_store::accounts::Market;
914    use petgraph::dot::Dot;
915
916    use crate::{
917        constants,
918        utils::{test::setup_fmt_tracing, zero_copy::try_deserialize_zero_copy_from_base64},
919    };
920
921    use super::*;
922
923    const USDC: &str = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
924    const WSOL: &str = "So11111111111111111111111111111111111111112";
925    const BOME: &str = "ukHH6c7mMyiWCf1b9pnWe25TSpkDDt3H5pQZgZ74J82";
926
927    #[cfg(simulation)]
928    use crate::glv::model::GlvModel;
929    #[cfg(simulation)]
930    const SOL_BALANCED_MARKET_TOKEN: &str = "BwN2FWixP5JyKjJNyD1YcRKN1XhgvFtnzrPrkfyb4DkW";
931    #[cfg(simulation)]
932    const BNB_BALANCED_MARKET_TOKEN: &str = "J42dzcHkgmzU7MARv7k29TrkKaGQ1j2mTaYjkBkN7pn5";
933    #[cfg(simulation)]
934    const AAVE_BALANCED_MARKET_TOKEN: &str = "2dVHXNgzC7vvcsDi89S6crSkX3Y6HzPgZfmWqttEzvmo";
935
936    fn get_market_updates() -> Vec<(String, u64)> {
937        const DATA: &str = include_str!("test_data/markets.csv");
938        DATA.trim()
939            .split('\n')
940            .enumerate()
941            .map(|(idx, data)| {
942                let mut data = data.split(',');
943                let _market_token = data
944                    .next()
945                    .unwrap_or_else(|| panic!("[{idx}] missing market_token"));
946                let market = data
947                    .next()
948                    .unwrap_or_else(|| panic!("[{idx}] missing market data"));
949                let supply = data
950                    .next()
951                    .unwrap_or_else(|| panic!("[{idx}] missing supply"));
952
953                (
954                    market.to_string(),
955                    supply
956                        .parse()
957                        .unwrap_or_else(|_| panic!("[{idx}] invalid supply format")),
958                )
959            })
960            .collect()
961    }
962
963    fn get_price_updates() -> Vec<(i64, Pubkey, Price<u128>)> {
964        const DATA: &str = include_str!("test_data/prices.csv");
965        DATA.trim()
966            .split('\n')
967            .enumerate()
968            .map(|(idx, data)| {
969                let mut data = data.split(',');
970                let ts = data.next().unwrap_or_else(|| panic!("[{idx}] missing ts"));
971                let token = data
972                    .next()
973                    .unwrap_or_else(|| panic!("[{idx}] missing token"));
974                let min = data
975                    .next()
976                    .unwrap_or_else(|| panic!("[{idx}] missing min price"));
977                let max = data
978                    .next()
979                    .unwrap_or_else(|| panic!("[{idx}] missing max price"));
980                (
981                    ts.parse()
982                        .unwrap_or_else(|_| panic!("[{idx}] invalid ts format")),
983                    token
984                        .parse()
985                        .unwrap_or_else(|_| panic!("[{idx}] invalid token format")),
986                    Price {
987                        min: min
988                            .parse()
989                            .unwrap_or_else(|_| panic!("[{idx}] invalid min price format")),
990                        max: max
991                            .parse()
992                            .unwrap_or_else(|_| panic!("[{idx}] invalid max price format")),
993                    },
994                )
995            })
996            .collect()
997    }
998
999    #[cfg(simulation)]
1000    fn get_glv() -> (Pubkey, GlvModel) {
1001        use gmsol_programs::{
1002            bytemuck::Zeroable,
1003            constants::MARKET_USD_UNIT,
1004            gmsol_store::{accounts::Glv, types::GlvMarketConfig},
1005        };
1006
1007        const MARKET_TOKEN_UNIT: u64 = 1_000_000_000;
1008
1009        let usdc: Pubkey = USDC.parse().unwrap();
1010        let wsol: Pubkey = WSOL.parse().unwrap();
1011        let sol_balanced_market_token = SOL_BALANCED_MARKET_TOKEN.parse().unwrap();
1012        let bnb_balanced_market_token = BNB_BALANCED_MARKET_TOKEN.parse().unwrap();
1013        let aave_balanced_market_token = AAVE_BALANCED_MARKET_TOKEN.parse().unwrap();
1014
1015        let glv_token = Pubkey::new_unique();
1016        let mut glv = Glv::zeroed();
1017        glv.glv_token = glv_token;
1018        glv.long_token = wsol;
1019        glv.short_token = usdc;
1020
1021        for market_token in [
1022            sol_balanced_market_token,
1023            bnb_balanced_market_token,
1024            aave_balanced_market_token,
1025        ] {
1026            glv.markets.insert(
1027                &market_token,
1028                GlvMarketConfig {
1029                    max_amount: 10_000 * MARKET_TOKEN_UNIT,
1030                    flags: Zeroable::zeroed(),
1031                    padding_0: Zeroable::zeroed(),
1032                    max_value: 10_000 * MARKET_USD_UNIT,
1033                    balance: 1_000 * MARKET_TOKEN_UNIT,
1034                    padding_1: Zeroable::zeroed(),
1035                },
1036            );
1037        }
1038
1039        let model = GlvModel::new(Arc::new(glv), 3_000 * MARKET_TOKEN_UNIT);
1040
1041        (glv_token, model)
1042    }
1043
1044    fn create_and_update_market_graph() -> crate::Result<(MarketGraph, HashSet<Pubkey>)> {
1045        let mut graph = MarketGraph::default();
1046        let updates = get_market_updates();
1047        let prices = get_price_updates();
1048        let mut market_tokens = HashSet::<Pubkey>::default();
1049
1050        // Update markets.
1051        for (data, supply) in updates {
1052            let market = try_deserialize_zero_copy_from_base64::<Market>(&data)?.0;
1053            market_tokens.insert(market.meta.market_token_mint);
1054            graph.insert_market(MarketModel::from_parts(Arc::new(market), supply));
1055        }
1056
1057        // Update prices.
1058        for (_, token, price) in prices {
1059            graph.update_token_price(&token, &price);
1060        }
1061
1062        Ok((graph, market_tokens))
1063    }
1064
1065    #[test]
1066    fn basic() -> crate::Result<()> {
1067        let _tracing = setup_fmt_tracing("info");
1068
1069        let (mut graph, market_tokens) = create_and_update_market_graph()?;
1070
1071        // Update value.
1072        graph.update_value(10 * constants::MARKET_USD_UNIT);
1073        graph.update_base_cost(constants::MARKET_USD_UNIT / 100);
1074
1075        let num_markets = graph.markets().count();
1076        assert_eq!(num_markets, market_tokens.len());
1077        for market_token in market_tokens {
1078            let market = graph.get_market(&market_token).expect("must exist");
1079            assert_eq!(market.meta.market_token_mint, market_token);
1080        }
1081        println!("{:?}", Dot::new(&graph.graph));
1082        Ok(())
1083    }
1084
1085    #[test]
1086    fn best_swap_path() -> crate::Result<()> {
1087        let _tracing = setup_fmt_tracing("info");
1088
1089        let usdc: Pubkey = USDC.parse().unwrap();
1090        let wsol: Pubkey = WSOL.parse().unwrap();
1091        let bome: Pubkey = BOME.parse().unwrap();
1092
1093        let (mut g, _) = create_and_update_market_graph()?;
1094
1095        g.update_value(constants::MARKET_USD_UNIT);
1096
1097        for steps in 0..=5 {
1098            g.update_max_steps(steps);
1099
1100            let paths = g.best_swap_paths(&wsol, false)?;
1101            let dfs_paths = g.best_swap_paths(&wsol, true)?;
1102
1103            let (rate, best_path) = paths.to(&bome);
1104            let (dfs_rate, dfs_best_path) = dfs_paths.to(&bome);
1105            assert_eq!(rate, dfs_rate);
1106            assert_eq!(best_path, dfs_best_path);
1107
1108            let (rate, best_path) = paths.to(&usdc);
1109            let (dfs_rate, dfs_best_path) = dfs_paths.to(&usdc);
1110            assert_eq!(rate, dfs_rate);
1111            assert_eq!(best_path, dfs_best_path);
1112
1113            for target in [&usdc, &wsol, &bome] {
1114                println!("[{steps}] path to {target}: {:?}", paths.to(target));
1115            }
1116        }
1117
1118        Ok(())
1119    }
1120
1121    #[test]
1122    #[cfg(simulation)]
1123    fn position_order_simulation() -> crate::Result<()> {
1124        use crate::simulation::order::OrderSimulationOutput;
1125
1126        let _tracing = setup_fmt_tracing("info");
1127
1128        let bome: Pubkey = BOME.parse().unwrap();
1129        let wsol: Pubkey = WSOL.parse().unwrap();
1130        let market_token: Pubkey = SOL_BALANCED_MARKET_TOKEN.parse().unwrap();
1131
1132        let (mut g, _) = create_and_update_market_graph()?;
1133
1134        g.update_value(constants::MARKET_USD_UNIT * 6);
1135        g.update_max_steps(5);
1136
1137        let paths = g.best_swap_paths(&bome, false)?;
1138        let (_, best_path) = paths.to(&wsol);
1139
1140        println!("{best_path:?}");
1141
1142        let mut simulator = g.to_simulator(Default::default());
1143        let bome_price = 101468850000;
1144        let sol_price = 10821227000000;
1145        let amount = 5 * constants::MARKET_USD_UNIT / bome_price;
1146        let size = 100 * constants::MARKET_USD_UNIT;
1147        let params = CreateOrderParams::builder()
1148            .amount(amount)
1149            .is_long(true)
1150            .size(size)
1151            .market_token(market_token)
1152            .build();
1153        let output = simulator
1154            .simulate_order(CreateOrderKind::MarketIncrease, &params, &wsol)
1155            .pay_token(Some(&bome))
1156            .swap_path(&best_path)
1157            .build()
1158            .execute_with_options(Default::default())?;
1159
1160        println!("{output:?}");
1161
1162        // Ensure the market graph is in-sync with the simulator.
1163        g.update_with_simulator(&simulator, Default::default());
1164        g.update_value(constants::MARKET_USD_UNIT * 5);
1165        let paths = g.best_swap_paths(&wsol, false)?;
1166        let (_, best_path) = paths.to(&bome);
1167        println!("{best_path:?}");
1168
1169        let size = 50 * constants::MARKET_USD_UNIT;
1170        let params = CreateOrderParams::builder()
1171            .amount(amount)
1172            .is_long(true)
1173            .size(size)
1174            .market_token(market_token)
1175            .trigger_price(sol_price * 101 / 100)
1176            .build();
1177        let OrderSimulationOutput::Increase { position, .. } = output else {
1178            unreachable!()
1179        };
1180        let output = simulator
1181            .simulate_order(CreateOrderKind::LimitDecrease, &params, &wsol)
1182            .receive_token(Some(&bome))
1183            .swap_path(&best_path)
1184            .position(Some(position.position_arc()))
1185            .build()
1186            .update_prices(Default::default())?
1187            .execute_with_options(Default::default());
1188
1189        println!("{output:?}");
1190
1191        Ok(())
1192    }
1193
1194    #[test]
1195    #[cfg(simulation)]
1196    fn swap_order_simulation() -> crate::Result<()> {
1197        let _tracing = setup_fmt_tracing("info");
1198
1199        let bome: Pubkey = BOME.parse().unwrap();
1200        let wsol: Pubkey = WSOL.parse().unwrap();
1201        let market_token: Pubkey = SOL_BALANCED_MARKET_TOKEN.parse().unwrap();
1202
1203        let (mut g, _) = create_and_update_market_graph()?;
1204
1205        g.update_value(constants::MARKET_USD_UNIT * 6);
1206        g.update_max_steps(5);
1207
1208        let paths = g.best_swap_paths(&bome, false)?;
1209        let (_, best_path) = paths.to(&wsol);
1210
1211        println!("{best_path:?}");
1212
1213        let bome_price = 101468850000;
1214        let sol_price = 10821227000000;
1215        let amount = 5 * constants::MARKET_USD_UNIT / bome_price;
1216        let params = CreateOrderParams::builder()
1217            .amount(amount)
1218            .is_long(true)
1219            .size(0)
1220            .market_token(market_token)
1221            .build();
1222        let output = g
1223            .to_simulator(Default::default())
1224            .simulate_order(CreateOrderKind::MarketSwap, &params, &wsol)
1225            .pay_token(Some(&bome))
1226            .swap_path(&best_path)
1227            .build()
1228            .execute_with_options(Default::default())?;
1229
1230        println!("{output:?}");
1231
1232        let amount = 5 * constants::MARKET_USD_UNIT / bome_price;
1233        let params = CreateOrderParams::builder()
1234            .amount(amount)
1235            .is_long(true)
1236            .size(0)
1237            .market_token(market_token)
1238            .min_output(amount * bome_price / sol_price * 102 / 100)
1239            .build();
1240        let output = g
1241            .to_simulator(Default::default())
1242            .simulate_order(CreateOrderKind::LimitSwap, &params, &wsol)
1243            .pay_token(Some(&bome))
1244            .swap_path(&best_path)
1245            .build()
1246            .update_prices(Default::default())?
1247            .execute_with_options(Default::default())?;
1248
1249        println!("{output:?}");
1250
1251        Ok(())
1252    }
1253
1254    #[test]
1255    #[cfg(simulation)]
1256    fn deposit_simulation() -> crate::Result<()> {
1257        use gmsol_programs::gmsol_store::types::CreateDepositParams;
1258
1259        let _tracing = setup_fmt_tracing("info");
1260
1261        let bome: Pubkey = BOME.parse().unwrap();
1262        let wsol: Pubkey = WSOL.parse().unwrap();
1263        let usdc: Pubkey = USDC.parse().unwrap();
1264        let market_token: Pubkey = SOL_BALANCED_MARKET_TOKEN.parse().unwrap();
1265
1266        let (mut g, _) = create_and_update_market_graph()?;
1267
1268        g.update_value(constants::MARKET_USD_UNIT * 6);
1269        g.update_max_steps(5);
1270
1271        let paths = g.best_swap_paths(&bome, false)?;
1272        let (_, best_long_path) = paths.to(&wsol);
1273
1274        let paths = g.best_swap_paths(&wsol, false)?;
1275        let (_, best_short_path) = paths.to(&usdc);
1276
1277        println!("{best_long_path:?}");
1278        println!("{best_short_path:?}");
1279
1280        let bome_price = 101468850000;
1281        let sol_price = 10821227000000;
1282        let long_amount = 5 * constants::MARKET_USD_UNIT / bome_price;
1283        let short_amount = 5 * constants::MARKET_USD_UNIT / sol_price;
1284        let params = CreateDepositParams {
1285            execution_lamports: 0,
1286            long_token_swap_length: best_long_path.len() as u8,
1287            short_token_swap_length: best_short_path.len() as u8,
1288            initial_long_token_amount: long_amount as u64,
1289            initial_short_token_amount: short_amount as u64,
1290            min_market_token_amount: 0,
1291            should_unwrap_native_token: true,
1292        };
1293        let output = g
1294            .to_simulator(Default::default())
1295            .simulate_deposit(&market_token, &params)
1296            .long_pay_token(Some(&bome))
1297            .long_swap_path(&best_long_path)
1298            .short_pay_token(Some(&wsol))
1299            .short_swap_path(&best_short_path)
1300            .build()
1301            .execute_with_options(Default::default())?;
1302
1303        println!("{output:?}");
1304
1305        Ok(())
1306    }
1307
1308    #[test]
1309    #[cfg(simulation)]
1310    fn withdrawal_simulation() -> crate::Result<()> {
1311        use gmsol_programs::gmsol_store::types::CreateWithdrawalParams;
1312
1313        let _tracing = setup_fmt_tracing("info");
1314
1315        let bome: Pubkey = BOME.parse().unwrap();
1316        let wsol: Pubkey = WSOL.parse().unwrap();
1317        let usdc: Pubkey = USDC.parse().unwrap();
1318        let market_token: Pubkey = SOL_BALANCED_MARKET_TOKEN.parse().unwrap();
1319
1320        let (mut g, _) = create_and_update_market_graph()?;
1321
1322        g.update_value(constants::MARKET_USD_UNIT * 6);
1323        g.update_max_steps(5);
1324
1325        let paths = g.best_swap_paths(&wsol, false)?;
1326        let (_, best_long_path) = paths.to(&bome);
1327
1328        let paths = g.best_swap_paths(&usdc, false)?;
1329        let (_, best_short_path) = paths.to(&wsol);
1330
1331        println!("{best_long_path:?}");
1332        println!("{best_short_path:?}");
1333
1334        let market_token_amount = 5 * 1_000_000_000;
1335
1336        let params = CreateWithdrawalParams {
1337            execution_lamports: 0,
1338            long_token_swap_path_length: best_long_path.len() as u8,
1339            short_token_swap_path_length: best_short_path.len() as u8,
1340            market_token_amount,
1341            min_long_token_amount: 0,
1342            min_short_token_amount: 0,
1343            should_unwrap_native_token: true,
1344        };
1345        let output = g
1346            .to_simulator(Default::default())
1347            .simulate_withdrawal(&market_token, &params)
1348            .long_receive_token(Some(&bome))
1349            .long_swap_path(&best_long_path)
1350            .short_receive_token(Some(&wsol))
1351            .short_swap_path(&best_short_path)
1352            .build()
1353            .execute_with_options(Default::default())?;
1354
1355        println!("{output:?}");
1356
1357        Ok(())
1358    }
1359
1360    #[test]
1361    #[cfg(simulation)]
1362    fn shift_simulation() -> crate::Result<()> {
1363        use gmsol_programs::gmsol_store::types::CreateShiftParams;
1364
1365        let _tracing = setup_fmt_tracing("info");
1366
1367        let from_market_token: Pubkey = SOL_BALANCED_MARKET_TOKEN.parse().unwrap();
1368        let to_market_token: Pubkey = BNB_BALANCED_MARKET_TOKEN.parse().unwrap();
1369
1370        let (g, _) = create_and_update_market_graph()?;
1371
1372        let from_market_token_amount = 5 * 1_000_000_000;
1373
1374        let params = CreateShiftParams {
1375            execution_lamports: 0,
1376            from_market_token_amount,
1377            min_to_market_token_amount: 0,
1378        };
1379        let output = g
1380            .to_simulator(Default::default())
1381            .simulate_shift(&from_market_token, &to_market_token, &params)
1382            .build()
1383            .execute_with_options(Default::default())?;
1384
1385        println!("{output:?}");
1386
1387        Ok(())
1388    }
1389
1390    #[test]
1391    #[cfg(simulation)]
1392    fn glv_deposit_and_withdrawal_simulation() -> crate::Result<()> {
1393        use gmsol_programs::gmsol_store::types::{
1394            CreateGlvDepositParams, CreateGlvWithdrawalParams,
1395        };
1396
1397        let _tracing = setup_fmt_tracing("info");
1398
1399        let bome: Pubkey = BOME.parse().unwrap();
1400        let wsol: Pubkey = WSOL.parse().unwrap();
1401        let usdc: Pubkey = USDC.parse().unwrap();
1402        let market_token: Pubkey = SOL_BALANCED_MARKET_TOKEN.parse().unwrap();
1403
1404        let (glv_token, glv) = get_glv();
1405
1406        let initial_supply = glv.supply();
1407
1408        let (mut g, _) = create_and_update_market_graph()?;
1409
1410        g.update_value(constants::MARKET_USD_UNIT * 6);
1411        g.update_max_steps(5);
1412
1413        let paths = g.best_swap_paths(&bome, false)?;
1414        let (_, best_long_path) = paths.to(&wsol);
1415
1416        let paths = g.best_swap_paths(&wsol, false)?;
1417        let (_, best_short_path) = paths.to(&usdc);
1418
1419        println!("{best_long_path:?}");
1420        println!("{best_short_path:?}");
1421
1422        let bome_price = 101468850000;
1423        let sol_price = 10821227000000;
1424        let long_amount = 5 * constants::MARKET_USD_UNIT / bome_price;
1425        let short_amount = 5 * constants::MARKET_USD_UNIT / sol_price;
1426        let params = CreateGlvDepositParams {
1427            execution_lamports: 0,
1428            market_token_amount: 10 * 1_000_000_000,
1429            long_token_swap_length: best_long_path.len() as u8,
1430            short_token_swap_length: best_short_path.len() as u8,
1431            initial_long_token_amount: long_amount as u64,
1432            initial_short_token_amount: short_amount as u64,
1433            min_market_token_amount: 0,
1434            min_glv_token_amount: 0,
1435            should_unwrap_native_token: true,
1436        };
1437
1438        let mut simulator = g.to_simulator(Default::default());
1439
1440        simulator.insert_glv(glv);
1441
1442        let output = simulator
1443            .simulate_glv_deposit(&glv_token, &market_token, &params)
1444            .long_pay_token(Some(&bome))
1445            .long_swap_path(&best_long_path)
1446            .short_pay_token(Some(&wsol))
1447            .short_swap_path(&best_short_path)
1448            .build()
1449            .execute_with_options(Default::default())?;
1450
1451        println!("deposit: {output:?}");
1452
1453        g.update_with_simulator(&simulator, Default::default());
1454
1455        let paths = g.best_swap_paths(&wsol, false)?;
1456        let (_, best_long_path) = paths.to(&bome);
1457
1458        let paths = g.best_swap_paths(&usdc, false)?;
1459        let (_, best_short_path) = paths.to(&wsol);
1460
1461        println!("{best_long_path:?}");
1462        println!("{best_short_path:?}");
1463
1464        let params = CreateGlvWithdrawalParams {
1465            execution_lamports: 0,
1466            glv_token_amount: output.output_amount(),
1467            long_token_swap_length: best_long_path.len() as u8,
1468            short_token_swap_length: best_short_path.len() as u8,
1469            min_final_long_token_amount: 0,
1470            min_final_short_token_amount: 0,
1471            should_unwrap_native_token: true,
1472        };
1473
1474        let output = simulator
1475            .simulate_glv_withdrawal(&glv_token, &market_token, &params)
1476            .long_receive_token(Some(&bome))
1477            .long_swap_path(&best_long_path)
1478            .short_receive_token(Some(&wsol))
1479            .short_swap_path(&best_short_path)
1480            .build()
1481            .execute_with_options(Default::default())?;
1482
1483        println!("withdrawal: {output:?}");
1484
1485        let glv = simulator.get_glv(&glv_token).unwrap();
1486        assert_eq!(glv.supply(), initial_supply);
1487
1488        Ok(())
1489    }
1490}