gmsol_store/states/common/
swap.rs1use 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
13pub(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}