use std::collections::HashSet;
use anchor_lang::prelude::*;
use crate::token_config::{
TokenConfigError, TokenConfigResult, TokenMapAccess, TokenRecord, TokensWithFeed,
};
const MAX_STEPS: usize = 10;
const MAX_TOKENS: usize = 2 * MAX_STEPS + 2 + 3;
#[derive(Debug, thiserror::Error)]
pub enum SwapActionParamsError {
#[error("invalid swap path: {0}")]
InvalidSwapPath(&'static str),
}
type SwapActionParamsResult<T> = std::result::Result<T, SwapActionParamsError>;
#[zero_copy]
#[derive(Default)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SwapActionParams {
pub primary_length: u8,
pub secondary_length: u8,
pub num_tokens: u8,
pub padding_0: [u8; 1],
pub current_market_token: Pubkey,
pub paths: [Pubkey; MAX_STEPS],
pub tokens: [Pubkey; MAX_TOKENS],
}
impl SwapActionParams {
pub const MAX_TOTAL_LENGTH: usize = MAX_STEPS;
pub const MAX_TOKENS: usize = MAX_TOKENS;
pub fn primary_length(&self) -> usize {
usize::from(self.primary_length)
}
pub fn secondary_length(&self) -> usize {
usize::from(self.secondary_length)
}
pub fn num_tokens(&self) -> usize {
usize::from(self.num_tokens)
}
pub fn primary_swap_path(&self) -> &[Pubkey] {
let end = self.primary_length();
&self.paths[0..end]
}
pub fn secondary_swap_path(&self) -> &[Pubkey] {
let start = self.primary_length();
let end = start.saturating_add(self.secondary_length());
&self.paths[start..end]
}
pub fn validated_primary_swap_path(&self) -> SwapActionParamsResult<&[Pubkey]> {
let mut seen: HashSet<&Pubkey> = HashSet::default();
if !self
.primary_swap_path()
.iter()
.all(move |token| seen.insert(token))
{
return Err(SwapActionParamsError::InvalidSwapPath("primary"));
}
Ok(self.primary_swap_path())
}
pub fn validated_secondary_swap_path(&self) -> SwapActionParamsResult<&[Pubkey]> {
let mut seen: HashSet<&Pubkey> = HashSet::default();
if !self
.secondary_swap_path()
.iter()
.all(move |token| seen.insert(token))
{
return Err(SwapActionParamsError::InvalidSwapPath("secondary"));
}
Ok(self.secondary_swap_path())
}
pub fn tokens(&self) -> &[Pubkey] {
let end = self.num_tokens();
&self.tokens[0..end]
}
pub fn to_token_records<'a>(
&'a self,
map: &'a impl TokenMapAccess,
) -> impl Iterator<Item = TokenConfigResult<TokenRecord>> + 'a {
self.tokens().iter().map(|token| {
let config = map.get(token).ok_or(TokenConfigError::NotFound)?;
TokenRecord::from_config(*token, config)
})
}
pub fn to_feeds(&self, map: &impl TokenMapAccess) -> TokenConfigResult<TokensWithFeed> {
let records = self
.to_token_records(map)
.collect::<TokenConfigResult<Vec<_>>>()?;
TokensWithFeed::try_from_records(records)
}
pub fn iter(&self) -> impl Iterator<Item = &Pubkey> {
self.primary_swap_path()
.iter()
.chain(self.secondary_swap_path().iter())
}
pub fn unique_market_tokens_excluding_current<'a>(
&'a self,
current_market_token: &'a Pubkey,
) -> impl Iterator<Item = &'a Pubkey> + 'a {
let mut seen = HashSet::from([current_market_token]);
self.iter().filter(move |token| seen.insert(token))
}
pub fn first_market_token(&self, is_primary: bool) -> Option<&Pubkey> {
if is_primary {
self.primary_swap_path().first()
} else {
self.secondary_swap_path().first()
}
}
pub fn last_market_token(&self, is_primary: bool) -> Option<&Pubkey> {
if is_primary {
self.primary_swap_path().last()
} else {
self.secondary_swap_path().last()
}
}
}
pub trait HasSwapParams {
fn swap(&self) -> &SwapActionParams;
}
impl HasSwapParams for SwapActionParams {
fn swap(&self) -> &SwapActionParams {
self
}
}