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<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}