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#[derive(Debug, thiserror::Error)]
14pub enum SwapActionParamsError {
15 #[error("invalid swap path: {0}")]
17 InvalidSwapPath(&'static str),
18}
19
20type SwapActionParamsResult<T> = std::result::Result<T, SwapActionParamsError>;
21
22#[zero_copy]
24#[derive(Default)]
25#[cfg_attr(feature = "debug", derive(Debug))]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27pub struct SwapActionParams {
28 pub primary_length: u8,
30 pub secondary_length: u8,
32 pub num_tokens: u8,
34 pub padding_0: [u8; 1],
36 pub current_market_token: Pubkey,
37 pub paths: [Pubkey; MAX_STEPS],
39 pub tokens: [Pubkey; MAX_TOKENS],
41}
42
43impl SwapActionParams {
44 pub const MAX_TOTAL_LENGTH: usize = MAX_STEPS;
46
47 pub const MAX_TOKENS: usize = MAX_TOKENS;
49
50 pub fn primary_length(&self) -> usize {
52 usize::from(self.primary_length)
53 }
54
55 pub fn secondary_length(&self) -> usize {
57 usize::from(self.secondary_length)
58 }
59
60 pub fn num_tokens(&self) -> usize {
62 usize::from(self.num_tokens)
63 }
64
65 pub fn primary_swap_path(&self) -> &[Pubkey] {
67 let end = self.primary_length();
68 &self.paths[0..end]
69 }
70
71 pub fn secondary_swap_path(&self) -> &[Pubkey] {
73 let start = self.primary_length();
74 let end = start.saturating_add(self.secondary_length());
75 &self.paths[start..end]
76 }
77
78 pub fn validated_primary_swap_path(&self) -> SwapActionParamsResult<&[Pubkey]> {
80 let mut seen: HashSet<&Pubkey> = HashSet::default();
81 if !self
82 .primary_swap_path()
83 .iter()
84 .all(move |token| seen.insert(token))
85 {
86 return Err(SwapActionParamsError::InvalidSwapPath("primary"));
87 }
88
89 Ok(self.primary_swap_path())
90 }
91
92 pub fn validated_secondary_swap_path(&self) -> SwapActionParamsResult<&[Pubkey]> {
94 let mut seen: HashSet<&Pubkey> = HashSet::default();
95 if !self
96 .secondary_swap_path()
97 .iter()
98 .all(move |token| seen.insert(token))
99 {
100 return Err(SwapActionParamsError::InvalidSwapPath("secondary"));
101 }
102
103 Ok(self.secondary_swap_path())
104 }
105
106 pub fn tokens(&self) -> &[Pubkey] {
108 let end = self.num_tokens();
109 &self.tokens[0..end]
110 }
111
112 pub fn to_token_records<'a>(
114 &'a self,
115 map: &'a impl TokenMapAccess,
116 ) -> impl Iterator<Item = TokenConfigResult<TokenRecord>> + 'a {
117 self.tokens().iter().map(|token| {
118 let config = map.get(token).ok_or(TokenConfigError::NotFound)?;
119 TokenRecord::from_config(*token, config)
120 })
121 }
122
123 pub fn to_feeds(&self, map: &impl TokenMapAccess) -> TokenConfigResult<TokensWithFeed> {
125 let records = self
126 .to_token_records(map)
127 .collect::<TokenConfigResult<Vec<_>>>()?;
128 TokensWithFeed::try_from_records(records)
129 }
130
131 pub fn iter(&self) -> impl Iterator<Item = &Pubkey> {
133 self.primary_swap_path()
134 .iter()
135 .chain(self.secondary_swap_path().iter())
136 }
137
138 pub fn unique_market_tokens_excluding_current<'a>(
140 &'a self,
141 current_market_token: &'a Pubkey,
142 ) -> impl Iterator<Item = &'a Pubkey> + 'a {
143 let mut seen = HashSet::from([current_market_token]);
144 self.iter().filter(move |token| seen.insert(token))
145 }
146
147 pub fn first_market_token(&self, is_primary: bool) -> Option<&Pubkey> {
149 if is_primary {
150 self.primary_swap_path().first()
151 } else {
152 self.secondary_swap_path().first()
153 }
154 }
155
156 pub fn last_market_token(&self, is_primary: bool) -> Option<&Pubkey> {
158 if is_primary {
159 self.primary_swap_path().last()
160 } else {
161 self.secondary_swap_path().last()
162 }
163 }
164}
165
166pub trait HasSwapParams {
168 fn swap(&self) -> &SwapActionParams;
170}
171
172impl HasSwapParams for SwapActionParams {
173 fn swap(&self) -> &SwapActionParams {
174 self
175 }
176}