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