Skip to main content

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