gmsol_store/states/common/
swap.rs

1use std::collections::{BTreeSet, HashSet};
2
3use anchor_lang::prelude::*;
4use gmsol_utils::swap::SwapActionParamsError;
5
6use crate::{
7    states::{HasMarketMeta, Market},
8    CoreError,
9};
10
11pub use gmsol_utils::swap::{HasSwapParams, SwapActionParams};
12
13/// Extension trait for [`SwapActionParams`].
14pub(crate) trait SwapActionParamsExt {
15    fn validate_and_init<'info>(
16        &mut self,
17        current_market: &impl HasMarketMeta,
18        primary_length: u8,
19        secondary_length: u8,
20        paths: &'info [AccountInfo<'info>],
21        store: &Pubkey,
22        token_ins: (&Pubkey, &Pubkey),
23        token_outs: (&Pubkey, &Pubkey),
24    ) -> Result<()>;
25
26    fn unpack_markets_for_swap<'info>(
27        &self,
28        current_market_token: &Pubkey,
29        remaining_accounts: &'info [AccountInfo<'info>],
30    ) -> Result<Vec<AccountLoader<'info, Market>>>;
31
32    fn find_first_market<'info>(
33        &self,
34        store: &Pubkey,
35        is_primary: bool,
36        remaining_accounts: &'info [AccountInfo<'info>],
37    ) -> Result<Option<&'info AccountInfo<'info>>>;
38
39    fn find_and_unpack_first_market<'info>(
40        &self,
41        store: &Pubkey,
42        is_primary: bool,
43        remaining_accounts: &'info [AccountInfo<'info>],
44    ) -> Result<Option<AccountLoader<'info, Market>>> {
45        let Some(info) = self.find_first_market(store, is_primary, remaining_accounts)? else {
46            return Ok(None);
47        };
48        let market = AccountLoader::<Market>::try_from(info)?;
49        require_keys_eq!(market.load()?.store, *store, CoreError::StoreMismatched);
50        Ok(Some(market))
51    }
52
53    fn find_last_market<'info>(
54        &self,
55        store: &Pubkey,
56        is_primary: bool,
57        remaining_accounts: &'info [AccountInfo<'info>],
58    ) -> Result<Option<&'info AccountInfo<'info>>>;
59
60    fn find_and_unpack_last_market<'info>(
61        &self,
62        store: &Pubkey,
63        is_primary: bool,
64        remaining_accounts: &'info [AccountInfo<'info>],
65    ) -> Result<Option<AccountLoader<'info, Market>>> {
66        let Some(info) = self.find_last_market(store, is_primary, remaining_accounts)? else {
67            return Ok(None);
68        };
69        let market = AccountLoader::<Market>::try_from(info)?;
70        require_keys_eq!(market.load()?.store, *store, CoreError::StoreMismatched);
71        Ok(Some(market))
72    }
73}
74
75impl SwapActionParamsExt for SwapActionParams {
76    fn validate_and_init<'info>(
77        &mut self,
78        current_market: &impl HasMarketMeta,
79        primary_length: u8,
80        secondary_length: u8,
81        paths: &'info [AccountInfo<'info>],
82        store: &Pubkey,
83        token_ins: (&Pubkey, &Pubkey),
84        token_outs: (&Pubkey, &Pubkey),
85    ) -> Result<()> {
86        let primary_end = usize::from(primary_length);
87        let end = primary_end.saturating_add(usize::from(secondary_length));
88        require_gte!(
89            Self::MAX_TOTAL_LENGTH,
90            end,
91            CoreError::InvalidSwapPathLength
92        );
93
94        require_gte!(paths.len(), end, CoreError::NotEnoughSwapMarkets);
95        let primary_markets = &paths[..primary_end];
96        let secondary_markets = &paths[primary_end..end];
97
98        let (primary_token_in, secondary_token_in) = token_ins;
99        let (primary_token_out, secondary_token_out) = token_outs;
100
101        let meta = current_market.market_meta();
102        let mut tokens = BTreeSet::from([
103            meta.index_token_mint,
104            meta.long_token_mint,
105            meta.short_token_mint,
106        ]);
107        let primary_path = validate_path(
108            &mut tokens,
109            primary_markets,
110            store,
111            primary_token_in,
112            primary_token_out,
113        )?;
114        let secondary_path = validate_path(
115            &mut tokens,
116            secondary_markets,
117            store,
118            secondary_token_in,
119            secondary_token_out,
120        )?;
121
122        require_gte!(Self::MAX_TOKENS, tokens.len(), CoreError::InvalidSwapPath);
123
124        self.primary_length = primary_length;
125        self.secondary_length = secondary_length;
126        self.num_tokens = tokens.len() as u8;
127
128        for (idx, market_token) in primary_path.iter().chain(secondary_path.iter()).enumerate() {
129            self.paths[idx] = *market_token;
130        }
131
132        for (idx, token) in tokens.into_iter().enumerate() {
133            self.tokens[idx] = token;
134        }
135
136        self.current_market_token = meta.market_token_mint;
137
138        Ok(())
139    }
140
141    fn unpack_markets_for_swap<'info>(
142        &self,
143        current_market_token: &Pubkey,
144        remaining_accounts: &'info [AccountInfo<'info>],
145    ) -> Result<Vec<AccountLoader<'info, Market>>> {
146        let len = self
147            .unique_market_tokens_excluding_current(current_market_token)
148            .count();
149        require_gte!(
150            remaining_accounts.len(),
151            len,
152            ErrorCode::AccountNotEnoughKeys
153        );
154        let loaders = unpack_markets(remaining_accounts).collect::<Result<Vec<_>>>()?;
155        Ok(loaders)
156    }
157
158    fn find_first_market<'info>(
159        &self,
160        store: &Pubkey,
161        is_primary: bool,
162        remaining_accounts: &'info [AccountInfo<'info>],
163    ) -> Result<Option<&'info AccountInfo<'info>>> {
164        let path = if is_primary {
165            self.primary_swap_path()
166        } else {
167            self.secondary_swap_path()
168        };
169        let Some(first_market_token) = path.first() else {
170            return Ok(None);
171        };
172        let is_current_market = *first_market_token == self.current_market_token;
173        let target = Market::find_market_address(store, first_market_token, &crate::ID).0;
174
175        match remaining_accounts.iter().find(|info| *info.key == target) {
176            Some(info) => Ok(Some(info)),
177            None if is_current_market => Ok(None),
178            None => err!(CoreError::NotFound),
179        }
180    }
181
182    fn find_last_market<'info>(
183        &self,
184        store: &Pubkey,
185        is_primary: bool,
186        remaining_accounts: &'info [AccountInfo<'info>],
187    ) -> Result<Option<&'info AccountInfo<'info>>> {
188        let path = if is_primary {
189            self.primary_swap_path()
190        } else {
191            self.secondary_swap_path()
192        };
193        let Some(last_market_token) = path.last() else {
194            return Ok(None);
195        };
196        let is_current_market = *last_market_token == self.current_market_token;
197        let target = Market::find_market_address(store, last_market_token, &crate::ID).0;
198
199        match remaining_accounts.iter().find(|info| *info.key == target) {
200            Some(info) => Ok(Some(info)),
201            None if is_current_market => Ok(None),
202            None => err!(CoreError::NotFound),
203        }
204    }
205}
206
207pub(crate) fn unpack_markets<'info>(
208    path: &'info [AccountInfo<'info>],
209) -> impl Iterator<Item = Result<AccountLoader<'info, Market>>> {
210    path.iter().map(AccountLoader::try_from)
211}
212
213fn validate_path<'info>(
214    tokens: &mut BTreeSet<Pubkey>,
215    path: &'info [AccountInfo<'info>],
216    store: &Pubkey,
217    token_in: &Pubkey,
218    token_out: &Pubkey,
219) -> Result<Vec<Pubkey>> {
220    let mut current = *token_in;
221    let mut seen = HashSet::<_>::default();
222
223    let mut validated_market_tokens = Vec::with_capacity(path.len());
224    for market in unpack_markets(path) {
225        let market = market?;
226
227        if !seen.insert(market.key()) {
228            return err!(CoreError::InvalidSwapPath);
229        }
230
231        let market = market.load()?;
232        let meta = market.validated_meta(store)?;
233        if current == meta.long_token_mint {
234            current = meta.short_token_mint;
235        } else if current == meta.short_token_mint {
236            current = meta.long_token_mint
237        } else {
238            return err!(CoreError::InvalidSwapPath);
239        }
240        tokens.insert(meta.index_token_mint);
241        tokens.insert(meta.long_token_mint);
242        tokens.insert(meta.short_token_mint);
243        validated_market_tokens.push(meta.market_token_mint);
244    }
245
246    require_keys_eq!(current, *token_out, CoreError::InvalidSwapPath);
247
248    Ok(validated_market_tokens)
249}
250
251impl From<SwapActionParamsError> for CoreError {
252    fn from(err: SwapActionParamsError) -> Self {
253        msg!("Swap params error: {}", err);
254        match err {
255            SwapActionParamsError::InvalidSwapPath(_) => Self::InvalidSwapPath,
256        }
257    }
258}