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]
29#[derive(Default)]
30#[cfg_attr(feature = "debug", derive(Debug))]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32pub struct SwapActionParams {
33 pub primary_length: u8,
35 pub secondary_length: u8,
37 pub num_tokens: u8,
39 pub padding_0: [u8; 1],
41 pub current_market_token: Pubkey,
42 pub paths: [Pubkey; MAX_STEPS],
44 pub tokens: [Pubkey; MAX_TOKENS],
46}
47
48impl SwapActionParams {
49 pub const MAX_TOTAL_LENGTH: usize = MAX_STEPS;
51
52 pub const MAX_TOKENS: usize = MAX_TOKENS;
54
55 pub fn primary_length(&self) -> usize {
57 usize::from(self.primary_length)
58 }
59
60 pub fn secondary_length(&self) -> usize {
62 usize::from(self.secondary_length)
63 }
64
65 pub fn num_tokens(&self) -> usize {
67 usize::from(self.num_tokens)
68 }
69
70 pub fn primary_swap_path(&self) -> &[Pubkey] {
72 let end = self.primary_length();
73 &self.paths[0..end]
74 }
75
76 pub fn secondary_swap_path(&self) -> &[Pubkey] {
78 let start = self.primary_length();
79 let end = start.saturating_add(self.secondary_length());
80 &self.paths[start..end]
81 }
82
83 pub fn validated_primary_swap_path(&self) -> SwapActionParamsResult<&[Pubkey]> {
85 let mut seen: HashSet<&Pubkey> = HashSet::default();
86 if !self
87 .primary_swap_path()
88 .iter()
89 .all(move |token| seen.insert(token))
90 {
91 return Err(SwapActionParamsError::InvalidSwapPath("primary"));
92 }
93
94 Ok(self.primary_swap_path())
95 }
96
97 pub fn validated_secondary_swap_path(&self) -> SwapActionParamsResult<&[Pubkey]> {
99 let mut seen: HashSet<&Pubkey> = HashSet::default();
100 if !self
101 .secondary_swap_path()
102 .iter()
103 .all(move |token| seen.insert(token))
104 {
105 return Err(SwapActionParamsError::InvalidSwapPath("secondary"));
106 }
107
108 Ok(self.secondary_swap_path())
109 }
110
111 pub fn tokens(&self) -> &[Pubkey] {
113 let end = self.num_tokens();
114 &self.tokens[0..end]
115 }
116
117 pub fn to_token_records<'a>(
119 &'a self,
120 map: &'a impl TokenMapAccess,
121 ) -> impl Iterator<Item = TokenConfigResult<TokenRecord>> + 'a {
122 self.tokens().iter().map(|token| {
123 let config = map.get(token).ok_or(TokenConfigError::NotFound)?;
124 TokenRecord::from_config(*token, config)
125 })
126 }
127
128 pub fn to_feeds(&self, map: &impl TokenMapAccess) -> TokenConfigResult<TokensWithFeed> {
130 let records = self
131 .to_token_records(map)
132 .collect::<TokenConfigResult<Vec<_>>>()?;
133 TokensWithFeed::try_from_records(records)
134 }
135
136 pub fn iter(&self) -> impl Iterator<Item = &Pubkey> {
138 self.primary_swap_path()
139 .iter()
140 .chain(self.secondary_swap_path().iter())
141 }
142
143 pub fn unique_market_tokens_excluding_current<'a>(
145 &'a self,
146 current_market_token: &'a Pubkey,
147 ) -> impl Iterator<Item = &'a Pubkey> + 'a {
148 let mut seen = HashSet::from([current_market_token]);
149 self.iter().filter(move |token| seen.insert(token))
150 }
151
152 pub fn first_market_token(&self, is_primary: bool) -> Option<&Pubkey> {
154 if is_primary {
155 self.primary_swap_path().first()
156 } else {
157 self.secondary_swap_path().first()
158 }
159 }
160
161 pub fn last_market_token(&self, is_primary: bool) -> Option<&Pubkey> {
163 if is_primary {
164 self.primary_swap_path().last()
165 } else {
166 self.secondary_swap_path().last()
167 }
168 }
169}
170
171pub trait HasSwapParams {
173 fn swap(&self) -> &SwapActionParams;
175}
176
177impl HasSwapParams for SwapActionParams {
178 fn swap(&self) -> &SwapActionParams {
179 self
180 }
181}