gmsol_utils/
swap.rs

1use std::collections::HashSet;
2
3use anchor_lang::prelude::*;
4
5use crate::token_config::{
6    TokenConfigError, TokenConfigResult, TokenMapAccess, TokenRecord, TokensWithFeed,
7};
8
9const MAX_STEPS: usize = 10;
10const MAX_TOKENS: usize = 2 * MAX_STEPS + 2 + 3;
11
12/// Swap Parameter error.
13#[derive(Debug, thiserror::Error)]
14pub enum SwapActionParamsError {
15    /// Invalid swap path.
16    #[error("invalid swap path: {0}")]
17    InvalidSwapPath(&'static str),
18}
19
20type SwapActionParamsResult<T> = std::result::Result<T, SwapActionParamsError>;
21
22/// Swap params.
23#[zero_copy]
24#[derive(Default)]
25#[cfg_attr(feature = "debug", derive(Debug))]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27pub struct SwapActionParams {
28    /// The length of primary swap path.
29    pub primary_length: u8,
30    /// The length of secondary swap path.
31    pub secondary_length: u8,
32    /// The number of tokens.
33    pub num_tokens: u8,
34    /// Padding.
35    pub padding_0: [u8; 1],
36    pub current_market_token: Pubkey,
37    /// Swap paths.
38    pub paths: [Pubkey; MAX_STEPS],
39    /// Tokens.
40    pub tokens: [Pubkey; MAX_TOKENS],
41}
42
43impl SwapActionParams {
44    /// Max total length of swap paths.
45    pub const MAX_TOTAL_LENGTH: usize = MAX_STEPS;
46
47    /// Max total number of tokens of swap path.
48    pub const MAX_TOKENS: usize = MAX_TOKENS;
49
50    /// Get the length of primary swap path.
51    pub fn primary_length(&self) -> usize {
52        usize::from(self.primary_length)
53    }
54
55    /// Get the length of secondary swap path.
56    pub fn secondary_length(&self) -> usize {
57        usize::from(self.secondary_length)
58    }
59
60    /// Get the number of tokens.
61    pub fn num_tokens(&self) -> usize {
62        usize::from(self.num_tokens)
63    }
64
65    /// Get primary swap path.
66    pub fn primary_swap_path(&self) -> &[Pubkey] {
67        let end = self.primary_length();
68        &self.paths[0..end]
69    }
70
71    /// Get secondary swap path.
72    pub fn secondary_swap_path(&self) -> &[Pubkey] {
73        let start = self.primary_length();
74        let end = start.saturating_add(self.secondary_length());
75        &self.paths[start..end]
76    }
77
78    /// Get validated primary swap path.
79    pub fn validated_primary_swap_path(&self) -> SwapActionParamsResult<&[Pubkey]> {
80        let mut seen: HashSet<&Pubkey> = HashSet::default();
81        if !self
82            .primary_swap_path()
83            .iter()
84            .all(move |token| seen.insert(token))
85        {
86            return Err(SwapActionParamsError::InvalidSwapPath("primary"));
87        }
88
89        Ok(self.primary_swap_path())
90    }
91
92    /// Get validated secondary swap path.
93    pub fn validated_secondary_swap_path(&self) -> SwapActionParamsResult<&[Pubkey]> {
94        let mut seen: HashSet<&Pubkey> = HashSet::default();
95        if !self
96            .secondary_swap_path()
97            .iter()
98            .all(move |token| seen.insert(token))
99        {
100            return Err(SwapActionParamsError::InvalidSwapPath("secondary"));
101        }
102
103        Ok(self.secondary_swap_path())
104    }
105
106    /// Get all tokens for the action.
107    pub fn tokens(&self) -> &[Pubkey] {
108        let end = self.num_tokens();
109        &self.tokens[0..end]
110    }
111
112    /// Convert to token records.
113    pub fn to_token_records<'a>(
114        &'a self,
115        map: &'a impl TokenMapAccess,
116    ) -> impl Iterator<Item = TokenConfigResult<TokenRecord>> + 'a {
117        self.tokens().iter().map(|token| {
118            let config = map.get(token).ok_or(TokenConfigError::NotFound)?;
119            TokenRecord::from_config(*token, config)
120        })
121    }
122
123    /// Convert to tokens with feed.
124    pub fn to_feeds(&self, map: &impl TokenMapAccess) -> TokenConfigResult<TokensWithFeed> {
125        let records = self
126            .to_token_records(map)
127            .collect::<TokenConfigResult<Vec<_>>>()?;
128        TokensWithFeed::try_from_records(records)
129    }
130
131    /// Iterate over both swap paths, primary path first then secondary path.
132    pub fn iter(&self) -> impl Iterator<Item = &Pubkey> {
133        self.primary_swap_path()
134            .iter()
135            .chain(self.secondary_swap_path().iter())
136    }
137
138    /// Get unique market tokens excluding current market token.
139    pub fn unique_market_tokens_excluding_current<'a>(
140        &'a self,
141        current_market_token: &'a Pubkey,
142    ) -> impl Iterator<Item = &'a Pubkey> + 'a {
143        let mut seen = HashSet::from([current_market_token]);
144        self.iter().filter(move |token| seen.insert(token))
145    }
146
147    /// Get the first market token in the swap path.
148    pub fn first_market_token(&self, is_primary: bool) -> Option<&Pubkey> {
149        if is_primary {
150            self.primary_swap_path().first()
151        } else {
152            self.secondary_swap_path().first()
153        }
154    }
155
156    /// Get the last market token in the swap path.
157    pub fn last_market_token(&self, is_primary: bool) -> Option<&Pubkey> {
158        if is_primary {
159            self.primary_swap_path().last()
160        } else {
161            self.secondary_swap_path().last()
162        }
163    }
164}
165
166/// Has swap parameters.
167pub trait HasSwapParams {
168    /// Get the swap params.
169    fn swap(&self) -> &SwapActionParams;
170}
171
172impl HasSwapParams for SwapActionParams {
173    fn swap(&self) -> &SwapActionParams {
174        self
175    }
176}