diff --git a/contracts/svm/programs/express_relay/src/lib.rs b/contracts/svm/programs/express_relay/src/lib.rs index 091925d2b..15fb765d9 100644 --- a/contracts/svm/programs/express_relay/src/lib.rs +++ b/contracts/svm/programs/express_relay/src/lib.rs @@ -182,8 +182,8 @@ pub mod express_relay { ) } - pub fn swap_internal(ctx: Context, data: SwapV2Args) -> Result<()> { + ctx.accounts.check_raw_constraints(data.fee_token)?; check_deadline(data.deadline)?; ctx.accounts .express_relay_metadata @@ -227,6 +227,7 @@ pub mod express_relay { Ok(()) } + pub fn swap(ctx: Context, data: SwapArgs) -> Result<()> { let data = ctx.accounts.convert_to_v2(&data); swap_internal(ctx, data) @@ -438,31 +439,31 @@ pub struct SwapArgs { impl SwapArgs { pub fn convert_to_v2(&self, swap_platform_fee_bps: u64) -> SwapV2Args { SwapV2Args { - deadline: self.deadline, - amount_searcher: self.amount_searcher, - amount_user: self.amount_user, - fee_token: self.fee_token, - referral_fee_bps: self.referral_fee_bps, - swap_platform_fee_bps, + deadline: self.deadline, + amount_searcher: self.amount_searcher, + amount_user: self.amount_user, + fee_token: self.fee_token, + referral_fee_ppm: u64::from(self.referral_fee_bps) * FEE_BPS_TO_PPM, + swap_platform_fee_ppm: swap_platform_fee_bps * FEE_BPS_TO_PPM, } } } #[derive(AnchorSerialize, AnchorDeserialize, Eq, PartialEq, Clone, Copy, Debug)] pub struct SwapV2Args { - // deadline as a unix timestamp in seconds + /// deadline as a unix timestamp in seconds pub deadline: i64, pub amount_searcher: u64, pub amount_user: u64, - // The referral fee is specified in basis points - pub referral_fee_bps: u16, - // Token in which the fees will be paid + /// The referral fee is specified in parts per million + pub referral_fee_ppm: u64, + /// Token in which the fees will be paid pub fee_token: FeeToken, - pub swap_platform_fee_bps: u64, + /// The platform fee is specified in parts per million + pub swap_platform_fee_ppm: u64, } #[derive(Accounts)] -#[instruction(data: Box)] pub struct Swap<'info> { /// Searcher is the party that fulfills the quote request pub searcher: Signer<'info>, @@ -527,17 +528,15 @@ pub struct Swap<'info> { #[account( mint::token_program = token_program_fee, - constraint = mint_fee.key() == if data.fee_token == FeeToken::Searcher { mint_searcher.key() } else { mint_user.key() } )] + /// CHECK: we check that this is set correctly based on the fee token manually since the V2 arguments are incompatible on the wire pub mint_fee: Box>, // Token programs pub token_program_searcher: Interface<'info, TokenInterface>, pub token_program_user: Interface<'info, TokenInterface>, - #[account( - constraint = token_program_fee.key() == if data.fee_token == FeeToken::Searcher { token_program_searcher.key() } else { token_program_user.key() } - )] + /// CHECK: we check that this is set correctly based on the fee token manually since the V2 arguments are incompatible on the wire pub token_program_fee: Interface<'info, TokenInterface>, /// Express relay configuration diff --git a/contracts/svm/programs/express_relay/src/state.rs b/contracts/svm/programs/express_relay/src/state.rs index 661814e02..ac4bc5633 100644 --- a/contracts/svm/programs/express_relay/src/state.rs +++ b/contracts/svm/programs/express_relay/src/state.rs @@ -2,6 +2,9 @@ use anchor_lang::prelude::*; pub const FEE_SPLIT_PRECISION: u64 = 10_000; +pub const FEE_SPLIT_PRECISION_PPM: u64 = 1_000_000; +pub const FEE_BPS_TO_PPM: u64 = 100; + pub const RESERVE_EXPRESS_RELAY_METADATA: usize = 8 + 152 + 260; pub const SEED_METADATA: &[u8] = b"metadata"; diff --git a/contracts/svm/programs/express_relay/src/swap.rs b/contracts/svm/programs/express_relay/src/swap.rs index d6ee09c6c..f7d0479de 100644 --- a/contracts/svm/programs/express_relay/src/swap.rs +++ b/contracts/svm/programs/express_relay/src/swap.rs @@ -1,7 +1,11 @@ use { crate::{ error::ErrorCode, - state::ExpressRelayMetadata, + state::{ + ExpressRelayMetadata, + FEE_BPS_TO_PPM, + FEE_SPLIT_PRECISION_PPM, + }, token::check_receiver_and_transfer_token_if_needed, FeeToken, Swap, @@ -23,10 +27,46 @@ pub struct PostFeeSwapArgs { } -impl<'info> Swap<'info> { +impl Swap<'_> { pub fn convert_to_v2(&self, args: &SwapArgs) -> SwapV2Args { args.convert_to_v2(self.express_relay_metadata.swap_platform_fee_bps) } +} + +impl<'info> Swap<'info> { + fn check_mint_user(&self, fee_token: FeeToken) -> Result<()> { + let correct_mint_fee_key = if fee_token == FeeToken::Searcher { + self.mint_searcher.key() + } else { + self.mint_user.key() + }; + + // Preserve API compatibility by returning the same error as the anchor validator did + (self.mint_fee.key() == correct_mint_fee_key) + .then_some(()) + .ok_or(AnchorErrorCode::ConstraintRaw.into()) + } + + fn check_token_program_fee(&self, fee_token: FeeToken) -> Result<()> { + let correct_token_program_fee = if fee_token == FeeToken::Searcher { + self.token_program_searcher.key() + } else { + self.token_program_user.key() + }; + + // Preserve API compatibility by returning the same error as the anchor validator did + (self.token_program_fee.key() == correct_token_program_fee) + .then_some(()) + .ok_or(AnchorErrorCode::ConstraintRaw.into()) + } + + pub fn check_raw_constraints(&self, fee_token: FeeToken) -> Result<()> { + self.check_mint_user(fee_token)?; + self.check_token_program_fee(fee_token)?; + + Ok(()) + } + pub fn compute_swap_fees<'a>( &'a self, args: &SwapV2Args, @@ -37,8 +77,8 @@ impl<'info> Swap<'info> { fees, remaining_amount, } = self.express_relay_metadata.compute_swap_fees( - args.referral_fee_bps, - args.swap_platform_fee_bps, + args.referral_fee_ppm, + args.swap_platform_fee_ppm, args.amount_searcher, )?; Ok(( @@ -58,8 +98,8 @@ impl<'info> Swap<'info> { fees, remaining_amount, } = self.express_relay_metadata.compute_swap_fees( - args.referral_fee_bps, - args.swap_platform_fee_bps, + args.referral_fee_ppm, + args.swap_platform_fee_ppm, args.amount_user, )?; Ok(( @@ -165,25 +205,28 @@ impl ExpressRelayMetadata { referral_fee_bps: u16, amount: u64, ) -> Result { - self.compute_swap_fees(referral_fee_bps, self.swap_platform_fee_bps, amount) + let referral_fee_ppms = u64::from(referral_fee_bps) * FEE_BPS_TO_PPM; + let swap_platform_fees_ppms = self.swap_platform_fee_bps * FEE_BPS_TO_PPM; + + self.compute_swap_fees(referral_fee_ppms, swap_platform_fees_ppms, amount) } pub fn compute_swap_fees( &self, - referral_fee_bps: u16, - swap_platform_fee_bps: u64, + referral_fee_ppm: u64, + swap_platform_fee_ppm: u64, amount: u64, ) -> Result { - if u64::from(referral_fee_bps) > FEE_SPLIT_PRECISION { + if referral_fee_ppm > FEE_SPLIT_PRECISION_PPM { return Err(ErrorCode::InvalidReferralFee.into()); } let router_fee = amount - .checked_mul(referral_fee_bps.into()) + .checked_mul(referral_fee_ppm) .ok_or(ProgramError::ArithmeticOverflow)? - / FEE_SPLIT_PRECISION; + / FEE_SPLIT_PRECISION_PPM; let platform_fee = amount - .checked_mul(swap_platform_fee_bps) + .checked_mul(swap_platform_fee_ppm) .ok_or(ProgramError::ArithmeticOverflow)? - / FEE_SPLIT_PRECISION; + / FEE_SPLIT_PRECISION_PPM; let relayer_fee = platform_fee .checked_mul(self.split_relayer) .ok_or(ProgramError::ArithmeticOverflow)? diff --git a/contracts/svm/testing/src/express_relay/swap.rs b/contracts/svm/testing/src/express_relay/swap.rs index e65b7ed62..60112dd1b 100644 --- a/contracts/svm/testing/src/express_relay/swap.rs +++ b/contracts/svm/testing/src/express_relay/swap.rs @@ -19,6 +19,7 @@ use { }, FeeToken, SwapArgs, + SwapV2Args, }, solana_sdk::{ instruction::Instruction, @@ -34,17 +35,42 @@ pub struct SwapParamOverride { pub searcher_ta_mint_user: Option, pub express_relay_fee_receiver_ata: Option, pub relayer_fee_receiver_ata: Option, - pub platform_fee_bps: Option, } -pub struct SwapParams { +pub trait AnyVersionSwapArgs: Sized + Send + Sync { + fn get_fee_token(&self) -> FeeToken; + + fn into_swap_instruction_data(self) -> Vec; +} + +impl AnyVersionSwapArgs for SwapArgs { + fn get_fee_token(&self) -> FeeToken { + self.fee_token + } + + fn into_swap_instruction_data(self) -> Vec { + Swap { data: self }.data() + } +} + +impl AnyVersionSwapArgs for SwapV2Args { + fn get_fee_token(&self) -> FeeToken { + self.fee_token + } + + fn into_swap_instruction_data(self) -> Vec { + SwapV2 { data: self }.data() + } +} + +pub struct SwapParams { pub searcher: Pubkey, pub user: Pubkey, pub router_fee_receiver_ta: Pubkey, pub fee_receiver_relayer: Pubkey, pub token_searcher: Token, pub token_user: Token, - pub swap_args: SwapArgs, + pub swap_args: Args, pub relayer_signer: Pubkey, /// Overrides from default behavior that may result in an invalid instruction /// and are meant to be used for testing. @@ -52,7 +78,7 @@ pub struct SwapParams { } /// Builds a swap instruction. -pub fn create_swap_instruction(swap_params: SwapParams) -> Instruction { +pub fn create_swap_instruction(swap_params: SwapParams) -> Instruction { let SwapParams { searcher, user, @@ -70,12 +96,11 @@ pub fn create_swap_instruction(swap_params: SwapParams) -> Instruction { mint_fee: mint_fee_override, express_relay_fee_receiver_ata, relayer_fee_receiver_ata, - platform_fee_bps, }, } = swap_params; let express_relay_metadata = get_express_relay_metadata_key(); - let mint_fee = mint_fee_override.unwrap_or(match swap_args.fee_token { + let mint_fee = mint_fee_override.unwrap_or(match swap_args.get_fee_token() { FeeToken::Searcher => token_searcher.mint, FeeToken::User => token_user.mint, }); @@ -83,7 +108,7 @@ pub fn create_swap_instruction(swap_params: SwapParams) -> Instruction { let token_program_searcher = token_searcher.token_program; let token_program_user = token_user.token_program; - let token_program_fee = match swap_args.fee_token { + let token_program_fee = match swap_args.get_fee_token() { FeeToken::Searcher => token_program_searcher, FeeToken::User => token_program_user, }; @@ -142,13 +167,7 @@ pub fn create_swap_instruction(swap_params: SwapParams) -> Instruction { } .to_account_metas(None); - let data = match platform_fee_bps { - Some(fee) => SwapV2 { - data: swap_args.convert_to_v2(fee), - } - .data(), - None => Swap { data: swap_args }.data(), - }; + let data = swap_args.into_swap_instruction_data(); Instruction { program_id: express_relay::ID, accounts: accounts_submit_bid, @@ -157,7 +176,9 @@ pub fn create_swap_instruction(swap_params: SwapParams) -> Instruction { } /// Builds a set of instructions to perform a swap, including creating the associated token accounts. -pub fn build_swap_instructions(swap_params: SwapParams) -> Vec { +pub fn build_swap_instructions( + swap_params: SwapParams, +) -> Vec { let SwapParams { searcher, user, @@ -177,11 +198,11 @@ pub fn build_swap_instructions(swap_params: SwapParams) -> Vec { let token_program_searcher = token_searcher.token_program; let token_program_user = token_user.token_program; - let mint_fee = mint_fee_override.unwrap_or(match swap_args.fee_token { + let mint_fee = mint_fee_override.unwrap_or(match swap_args.get_fee_token() { FeeToken::Searcher => token_searcher.mint, FeeToken::User => token_user.mint, }); - let token_program_fee = match swap_args.fee_token { + let token_program_fee = match swap_args.get_fee_token() { FeeToken::Searcher => token_program_searcher, FeeToken::User => token_program_user, }; diff --git a/contracts/svm/testing/src/token.rs b/contracts/svm/testing/src/token.rs index 262ab91ef..d9bcb5bb3 100644 --- a/contracts/svm/testing/src/token.rs +++ b/contracts/svm/testing/src/token.rs @@ -31,6 +31,58 @@ use { }, }; +#[macro_export] +macro_rules! assert_token_balance_matches { + ($svm:expr, $address:expr, $account:expr, $amount:expr $(,)?) => { + let token_account_option = Token::get_token_account_opt($svm, $account); + + if token_account_option.is_none() { + assert_eq!( + 0, + $amount, + "token account balance didn't exist but the expected amount wasn't zero for `{}`", + stringify!($address) + ); + } else { + assert_eq!( + token_account_option.unwrap().amount, + $amount, + "token account balance doesn't match expected value for `{}`", + stringify!($address) + ); + } + }; +} + +#[macro_export] +macro_rules! assert_all_token_balances { + ($svm:expr, $token_user:expr, { + associated: { + $($address:expr => $am_asc:expr),* $(,)? + }, + raw: { + $($raw_address:expr => $am_raw:expr),* $(,)? + } + }) => { + $( + $crate::assert_token_balance_matches!( + $svm, + $address, + &$token_user.get_associated_token_address(&$address), + $token_user.get_amount_with_decimals($am_asc), + ); + )* + $( + $crate::assert_token_balance_matches!( + $svm, + $raw_address, + &$raw_address, + $token_user.get_amount_with_decimals($am_raw), + ); + )* + }; +} + pub struct Token { pub mint: Pubkey, pub decimals: u8, @@ -82,12 +134,18 @@ impl Token { .unwrap(); } - pub fn token_balance_matches(svm: &mut LiteSVM, account: &Pubkey, amount: u64) -> bool { - let token_account_option = &mut svm.get_account(account).map(|account| { + pub fn get_token_account_opt( + svm: &mut LiteSVM, + account: &Pubkey, + ) -> Option { + svm.get_account(account).map(|account| { anchor_spl::token_interface::TokenAccount::try_deserialize(&mut account.data.as_slice()) .unwrap() - }); + }) + } + pub fn token_balance_matches(svm: &mut LiteSVM, account: &Pubkey, amount: u64) -> bool { + let token_account_option = Self::get_token_account_opt(svm, account); if token_account_option.is_none() { return amount == 0; } diff --git a/contracts/svm/testing/tests/swap.rs b/contracts/svm/testing/tests/swap.rs index b50855606..568c3bcf8 100644 --- a/contracts/svm/testing/tests/swap.rs +++ b/contracts/svm/testing/tests/swap.rs @@ -11,6 +11,7 @@ use { state::FEE_SPLIT_PRECISION, FeeToken, SwapArgs, + SwapV2Args, }, litesvm::LiteSVM, solana_sdk::{ @@ -23,6 +24,7 @@ use { }, }, testing::{ + assert_all_token_balances, express_relay::{ helpers::{ get_express_relay_metadata, @@ -175,58 +177,40 @@ fn test_swap_fee_mint_searcher(args: SwapSetupParams) { let express_relay_metadata = get_express_relay_metadata(&mut svm); // searcher token balances - assert!(Token::token_balance_matches( + assert_all_token_balances!( &mut svm, - &token_searcher.get_associated_token_address(&searcher.pubkey()), - token_searcher.get_amount_with_decimals(10.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_searcher.get_associated_token_address(&user.pubkey()), - token_user.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_searcher.get_associated_token_address(&get_express_relay_metadata_key()), - token_searcher.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_searcher.get_associated_token_address(&express_relay_metadata.fee_receiver_relayer), - token_searcher.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &router_ta_mint_searcher, - token_searcher.get_amount_with_decimals(0.), - )); + token_searcher, + { + associated: { + searcher.pubkey() => 10.0, + user.pubkey() => 0.0, + get_express_relay_metadata_key() => 0.0, + express_relay_metadata.fee_receiver_relayer => 0.0, + }, + raw: { + router_ta_mint_searcher => 0.0, + } + } + ); + // user token balances - assert!(Token::token_balance_matches( + assert_all_token_balances!( &mut svm, - &token_user.get_associated_token_address(&searcher.pubkey()), - token_user.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_user.get_associated_token_address(&user.pubkey()), - token_user.get_amount_with_decimals(10.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &router_ta_mint_user, - token_user.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_user.get_associated_token_address(&get_express_relay_metadata_key()), - token_user.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_user.get_associated_token_address(&express_relay_metadata.fee_receiver_relayer), - token_user.get_amount_with_decimals(0.), - )); + token_user, + { + associated: { + searcher.pubkey() => 0.0, + user.pubkey() => 10.0, + get_express_relay_metadata_key() => 0.0, + express_relay_metadata.fee_receiver_relayer => 0.0, + }, + raw: { + router_ta_mint_user => 0.0, + } + } + ); + // searcher token fee let swap_args = SwapArgs { @@ -256,58 +240,39 @@ fn test_swap_fee_mint_searcher(args: SwapSetupParams) { .unwrap(); // searcher token balances - assert!(Token::token_balance_matches( - &mut svm, - &token_searcher.get_associated_token_address(&searcher.pubkey()), - token_searcher.get_amount_with_decimals(9.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_searcher.get_associated_token_address(&user.pubkey()), - token_searcher.get_amount_with_decimals(0.6), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_searcher.get_associated_token_address(&get_express_relay_metadata_key()), - token_searcher.get_amount_with_decimals(0.08), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_searcher.get_associated_token_address(&express_relay_metadata.fee_receiver_relayer), - token_searcher.get_amount_with_decimals(0.02), - )); - assert!(Token::token_balance_matches( + assert_all_token_balances!( &mut svm, - &router_ta_mint_searcher, - token_searcher.get_amount_with_decimals(0.3), - )); + token_searcher, + { + associated: { + searcher.pubkey() => 9.0, + user.pubkey() => 0.6, + get_express_relay_metadata_key() => 0.08, + express_relay_metadata.fee_receiver_relayer => 0.02, + }, + raw: { + router_ta_mint_searcher => 0.3, + } + } + ); + // user token balances - assert!(Token::token_balance_matches( - &mut svm, - &token_user.get_associated_token_address(&searcher.pubkey()), - token_user.get_amount_with_decimals(1.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_user.get_associated_token_address(&user.pubkey()), - token_user.get_amount_with_decimals(9.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_user.get_associated_token_address(&get_express_relay_metadata_key()), - token_user.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( + assert_all_token_balances!( &mut svm, - &token_user.get_associated_token_address(&express_relay_metadata.fee_receiver_relayer), - token_user.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &router_ta_mint_user, - token_user.get_amount_with_decimals(0.), - )); + token_user, + { + associated: { + searcher.pubkey() => 1.0, + user.pubkey() => 9.0, + get_express_relay_metadata_key() => 0.0, + express_relay_metadata.fee_receiver_relayer => 0.0, + }, + raw: { + router_ta_mint_user => 0.0, + } + } + ); } fn test_swap_fee_mint_user(args: SwapSetupParams) { @@ -326,58 +291,39 @@ fn test_swap_fee_mint_user(args: SwapSetupParams) { let express_relay_metadata = get_express_relay_metadata(&mut svm); // searcher token balances - assert!(Token::token_balance_matches( - &mut svm, - &token_searcher.get_associated_token_address(&searcher.pubkey()), - token_searcher.get_amount_with_decimals(10.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_searcher.get_associated_token_address(&user.pubkey()), - token_user.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_searcher.get_associated_token_address(&get_express_relay_metadata_key()), - token_searcher.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_searcher.get_associated_token_address(&express_relay_metadata.fee_receiver_relayer), - token_searcher.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( + assert_all_token_balances!( &mut svm, - &router_ta_mint_searcher, - token_searcher.get_amount_with_decimals(0.), - )); + token_searcher, + { + associated: { + searcher.pubkey() => 10.0, + user.pubkey() => 0.0, + get_express_relay_metadata_key() => 0.0, + express_relay_metadata.fee_receiver_relayer => 0.0, + }, + raw: { + router_ta_mint_searcher => 0.0, + } + } + ); + // user token balances - assert!(Token::token_balance_matches( - &mut svm, - &token_user.get_associated_token_address(&searcher.pubkey()), - token_user.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_user.get_associated_token_address(&user.pubkey()), - token_user.get_amount_with_decimals(10.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &router_ta_mint_user, - token_user.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( + assert_all_token_balances!( &mut svm, - &token_user.get_associated_token_address(&get_express_relay_metadata_key()), - token_user.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_user.get_associated_token_address(&express_relay_metadata.fee_receiver_relayer), - token_user.get_amount_with_decimals(0.), - )); + token_user, + { + associated: { + searcher.pubkey() => 0.0, + user.pubkey() => 10.0, + get_express_relay_metadata_key() => 0.0, + express_relay_metadata.fee_receiver_relayer => 0.0, + }, + raw: { + router_ta_mint_user => 0.0, + } + } + ); let swap_args = SwapArgs { deadline: svm.get_sysvar::().unix_timestamp, @@ -407,58 +353,39 @@ fn test_swap_fee_mint_user(args: SwapSetupParams) { .unwrap(); // searcher token balances - assert!(Token::token_balance_matches( - &mut svm, - &token_searcher.get_associated_token_address(&searcher.pubkey()), - token_searcher.get_amount_with_decimals(9.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_searcher.get_associated_token_address(&user.pubkey()), - token_searcher.get_amount_with_decimals(1.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_searcher.get_associated_token_address(&get_express_relay_metadata_key()), - token_searcher.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_searcher.get_associated_token_address(&express_relay_metadata.fee_receiver_relayer), - token_searcher.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( + assert_all_token_balances!( &mut svm, - &router_ta_mint_searcher, - token_searcher.get_amount_with_decimals(0.), - )); + token_searcher, + { + associated: { + searcher.pubkey() => 9.0, + user.pubkey() => 1.0, + get_express_relay_metadata_key() => 0.0, + express_relay_metadata.fee_receiver_relayer => 0.0, + }, + raw: { + router_ta_mint_searcher => 0.0, + } + } + ); + // user token balances - assert!(Token::token_balance_matches( - &mut svm, - &token_user.get_associated_token_address(&searcher.pubkey()), - token_user.get_amount_with_decimals(0.75), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_user.get_associated_token_address(&user.pubkey()), - token_user.get_amount_with_decimals(9.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_user.get_associated_token_address(&get_express_relay_metadata_key()), - token_user.get_amount_with_decimals(0.08), - )); - assert!(Token::token_balance_matches( + assert_all_token_balances!( &mut svm, - &token_user.get_associated_token_address(&express_relay_metadata.fee_receiver_relayer), - token_user.get_amount_with_decimals(0.02), - )); - assert!(Token::token_balance_matches( - &mut svm, - &router_ta_mint_user, - token_user.get_amount_with_decimals(0.15), - )); + token_user, + { + associated: { + searcher.pubkey() => 0.75, + user.pubkey() => 9.0, + get_express_relay_metadata_key() => 0.08, + express_relay_metadata.fee_receiver_relayer => 0.02, + }, + raw: { + router_ta_mint_user => 0.15, + } + } + ); } #[test] @@ -1488,66 +1415,48 @@ fn test_swap_v2_mint_searcher() { let express_relay_metadata = get_express_relay_metadata(&mut svm); // searcher token balances - assert!(Token::token_balance_matches( - &mut svm, - &token_searcher.get_associated_token_address(&searcher.pubkey()), - token_searcher.get_amount_with_decimals(10.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_searcher.get_associated_token_address(&user.pubkey()), - token_user.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_searcher.get_associated_token_address(&get_express_relay_metadata_key()), - token_searcher.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_searcher.get_associated_token_address(&express_relay_metadata.fee_receiver_relayer), - token_searcher.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( + assert_all_token_balances!( &mut svm, - &router_ta_mint_searcher, - token_searcher.get_amount_with_decimals(0.), - )); + token_searcher, + { + associated: { + searcher.pubkey() => 10.0, + user.pubkey() => 0.0, + get_express_relay_metadata_key() => 0.0, + express_relay_metadata.fee_receiver_relayer => 0.0, + }, + raw: { + router_ta_mint_searcher => 0.0, + } + } + ); // user token balances - assert!(Token::token_balance_matches( + assert_all_token_balances!( &mut svm, - &token_user.get_associated_token_address(&searcher.pubkey()), - token_user.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_user.get_associated_token_address(&user.pubkey()), - token_user.get_amount_with_decimals(10.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &router_ta_mint_user, - token_user.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_user.get_associated_token_address(&get_express_relay_metadata_key()), - token_user.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_user.get_associated_token_address(&express_relay_metadata.fee_receiver_relayer), - token_user.get_amount_with_decimals(0.), - )); + token_user, + { + associated: { + searcher.pubkey() => 0.0, + user.pubkey() => 10.0, + get_express_relay_metadata_key() => 0.0, + express_relay_metadata.fee_receiver_relayer => 0.0, + }, + raw: { + router_ta_mint_user => 0.0, + } + } + ); + // searcher token fee - let swap_args = SwapArgs { - deadline: svm.get_sysvar::().unix_timestamp, - amount_searcher: token_searcher.get_amount_with_decimals(1.), - amount_user: token_user.get_amount_with_decimals(1.), - referral_fee_bps: 3000, - fee_token: FeeToken::Searcher, + let swap_args = SwapV2Args { + deadline: svm.get_sysvar::().unix_timestamp, + amount_searcher: token_searcher.get_amount_with_decimals(1.), + amount_user: token_user.get_amount_with_decimals(1.), + referral_fee_ppm: 300_000, + fee_token: FeeToken::Searcher, + swap_platform_fee_ppm: 200_000, }; let instructions = build_swap_instructions(SwapParams { searcher: searcher.pubkey(), @@ -1558,7 +1467,6 @@ fn test_swap_v2_mint_searcher() { token_user: token_user.clone(), swap_args, overrides: SwapParamOverride { - platform_fee_bps: Some(2000), ..Default::default() }, relayer_signer: relayer_signer.pubkey(), @@ -1572,58 +1480,38 @@ fn test_swap_v2_mint_searcher() { .unwrap(); // searcher token balances - assert!(Token::token_balance_matches( - &mut svm, - &token_searcher.get_associated_token_address(&searcher.pubkey()), - token_searcher.get_amount_with_decimals(9.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_searcher.get_associated_token_address(&user.pubkey()), - token_searcher.get_amount_with_decimals(0.5), - )); - assert!(Token::token_balance_matches( + assert_all_token_balances!( &mut svm, - &token_searcher.get_associated_token_address(&get_express_relay_metadata_key()), - token_searcher.get_amount_with_decimals(0.16), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_searcher.get_associated_token_address(&express_relay_metadata.fee_receiver_relayer), - token_searcher.get_amount_with_decimals(0.04), - )); - assert!(Token::token_balance_matches( - &mut svm, - &router_ta_mint_searcher, - token_searcher.get_amount_with_decimals(0.3), - )); + token_searcher, + { + associated: { + searcher.pubkey() => 9.0, + user.pubkey() => 0.5, + get_express_relay_metadata_key() => 0.16, + express_relay_metadata.fee_receiver_relayer => 0.04, + }, + raw: { + router_ta_mint_searcher => 0.3, + } + } + ); // user token balances - assert!(Token::token_balance_matches( - &mut svm, - &token_user.get_associated_token_address(&searcher.pubkey()), - token_user.get_amount_with_decimals(1.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_user.get_associated_token_address(&user.pubkey()), - token_user.get_amount_with_decimals(9.), - )); - assert!(Token::token_balance_matches( + assert_all_token_balances!( &mut svm, - &token_user.get_associated_token_address(&get_express_relay_metadata_key()), - token_user.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_user.get_associated_token_address(&express_relay_metadata.fee_receiver_relayer), - token_user.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &router_ta_mint_user, - token_user.get_amount_with_decimals(0.), - )); + token_user, + { + associated: { + searcher.pubkey() => 1.0, + user.pubkey() => 9.0, + get_express_relay_metadata_key() => 0.0, + express_relay_metadata.fee_receiver_relayer => 0.0, + }, + raw: { + router_ta_mint_user => 0.0, + } + } + ); } #[test] @@ -1643,65 +1531,164 @@ fn test_swap_v2_mint_user() { let express_relay_metadata = get_express_relay_metadata(&mut svm); // searcher token balances - assert!(Token::token_balance_matches( - &mut svm, - &token_searcher.get_associated_token_address(&searcher.pubkey()), - token_searcher.get_amount_with_decimals(10.), - )); - assert!(Token::token_balance_matches( + assert_all_token_balances!( &mut svm, - &token_searcher.get_associated_token_address(&user.pubkey()), - token_user.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( + token_searcher, + { + associated: { + searcher.pubkey() => 10.0, + user.pubkey() => 0.0, + get_express_relay_metadata_key() => 0.0, + express_relay_metadata.fee_receiver_relayer => 0.0, + }, + raw: { + router_ta_mint_searcher => 0.0, + } + } + ); + + + // user token balances + assert_all_token_balances!( &mut svm, - &token_searcher.get_associated_token_address(&get_express_relay_metadata_key()), - token_searcher.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( + token_user, + { + associated: { + searcher.pubkey() => 0.0, + user.pubkey() => 10.0, + get_express_relay_metadata_key() => 0.0, + express_relay_metadata.fee_receiver_relayer => 0.0, + }, + raw: { + router_ta_mint_user => 0.0, + } + } + ); + + let swap_args = SwapV2Args { + deadline: svm.get_sysvar::().unix_timestamp, + amount_searcher: token_searcher.get_amount_with_decimals(1.), + amount_user: token_user.get_amount_with_decimals(1.), + referral_fee_ppm: 150_000, + fee_token: FeeToken::User, + swap_platform_fee_ppm: 200_000, + }; + + let instructions = build_swap_instructions(SwapParams { + searcher: searcher.pubkey(), + user: user.pubkey(), + router_fee_receiver_ta: router_ta_mint_user, + fee_receiver_relayer: express_relay_metadata.fee_receiver_relayer, + token_searcher: token_searcher.clone(), + token_user: token_user.clone(), + swap_args, + overrides: Default::default(), + relayer_signer: relayer_signer.pubkey(), + }); + submit_transaction( &mut svm, - &token_searcher.get_associated_token_address(&express_relay_metadata.fee_receiver_relayer), - token_searcher.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( + &instructions, + &searcher, + &[&searcher, &user, &relayer_signer], + ) + .unwrap(); + + // searcher token balances + assert_all_token_balances!( &mut svm, - &router_ta_mint_searcher, - token_searcher.get_amount_with_decimals(0.), - )); + token_searcher, + { + associated: { + searcher.pubkey() => 9., + user.pubkey() => 1., + get_express_relay_metadata_key() => 0., + express_relay_metadata.fee_receiver_relayer => 0., + }, + raw: { + router_ta_mint_searcher => 0., + } + } + ); // user token balances - assert!(Token::token_balance_matches( - &mut svm, - &token_user.get_associated_token_address(&searcher.pubkey()), - token_user.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_user.get_associated_token_address(&user.pubkey()), - token_user.get_amount_with_decimals(10.), - )); - assert!(Token::token_balance_matches( + assert_all_token_balances!( &mut svm, - &router_ta_mint_user, - token_user.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( + token_user, + { + associated: { + searcher.pubkey() => 0.65, + user.pubkey() => 9.0, + get_express_relay_metadata_key() => 0.16, + express_relay_metadata.fee_receiver_relayer => 0.04, + }, + raw: { + router_ta_mint_user => 0.15, + } + } + ); +} + +#[test] +fn test_swap_v2_mint_user_ppm_fee() { + let SwapSetupResult { + mut svm, + user, + searcher, + token_searcher, + token_user, + router_ta_mint_searcher, + router_ta_mint_user, + relayer_signer, + .. + } = setup_swap(Default::default()); + + let express_relay_metadata = get_express_relay_metadata(&mut svm); + + token_searcher.airdrop(&mut svm, &searcher.pubkey(), 99990.); + token_user.airdrop(&mut svm, &user.pubkey(), 99990.); + + // searcher token balances + assert_all_token_balances!( &mut svm, - &token_user.get_associated_token_address(&get_express_relay_metadata_key()), - token_user.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( + token_searcher, + { + associated: { + searcher.pubkey() => 100000.0, + user.pubkey() => 0.0, + get_express_relay_metadata_key() => 0.0, + express_relay_metadata.fee_receiver_relayer => 0.0, + }, + raw: { + router_ta_mint_searcher => 0.0, + } + } + ); + + + // user token balances + assert_all_token_balances!( &mut svm, - &token_user.get_associated_token_address(&express_relay_metadata.fee_receiver_relayer), - token_user.get_amount_with_decimals(0.), - )); + token_user, + { + associated: { + searcher.pubkey() => 0.0, + user.pubkey() => 100000.0, + get_express_relay_metadata_key() => 0.0, + express_relay_metadata.fee_receiver_relayer => 0.0, + }, + raw: { + router_ta_mint_user => 0.0, + } + } + ); - let swap_args = SwapArgs { - deadline: svm.get_sysvar::().unix_timestamp, - amount_searcher: token_searcher.get_amount_with_decimals(1.), - amount_user: token_user.get_amount_with_decimals(1.), - referral_fee_bps: 1500, - fee_token: FeeToken::User, + let swap_args = SwapV2Args { + deadline: svm.get_sysvar::().unix_timestamp, + amount_searcher: token_searcher.get_amount_with_decimals(10000.), + amount_user: token_user.get_amount_with_decimals(10000.), + referral_fee_ppm: 1589, + fee_token: FeeToken::User, + swap_platform_fee_ppm: 2222, }; let instructions = build_swap_instructions(SwapParams { @@ -1712,10 +1699,7 @@ fn test_swap_v2_mint_user() { token_searcher: token_searcher.clone(), token_user: token_user.clone(), swap_args, - overrides: SwapParamOverride { - platform_fee_bps: Some(2000), - ..Default::default() - }, + overrides: Default::default(), relayer_signer: relayer_signer.pubkey(), }); submit_transaction( @@ -1727,56 +1711,157 @@ fn test_swap_v2_mint_user() { .unwrap(); // searcher token balances - assert!(Token::token_balance_matches( - &mut svm, - &token_searcher.get_associated_token_address(&searcher.pubkey()), - token_searcher.get_amount_with_decimals(9.), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_searcher.get_associated_token_address(&user.pubkey()), - token_searcher.get_amount_with_decimals(1.), - )); - assert!(Token::token_balance_matches( + assert_all_token_balances!( &mut svm, - &token_searcher.get_associated_token_address(&get_express_relay_metadata_key()), - token_searcher.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( + token_searcher, + { + associated: { + searcher.pubkey() => 90000., + user.pubkey() => 10000., + get_express_relay_metadata_key() => 0., + express_relay_metadata.fee_receiver_relayer => 0., + }, + raw: { + router_ta_mint_searcher => 0., + } + } + ); + + // user token balances + assert_all_token_balances!( &mut svm, - &token_searcher.get_associated_token_address(&express_relay_metadata.fee_receiver_relayer), - token_searcher.get_amount_with_decimals(0.), - )); - assert!(Token::token_balance_matches( + token_user, + { + associated: { + searcher.pubkey() => 9961.8900, + user.pubkey() => 90000.0, + get_express_relay_metadata_key() => 17.7760, + express_relay_metadata.fee_receiver_relayer => 4.4440, + }, + raw: { + router_ta_mint_user => 15.8900, + } + } + ); +} + +#[test] +fn test_swap_v2_mint_searcher_ppm_fee() { + let SwapSetupResult { + mut svm, + user, + searcher, + token_searcher, + token_user, + router_ta_mint_searcher, + router_ta_mint_user, + relayer_signer, + .. + } = setup_swap(Default::default()); + + let express_relay_metadata = get_express_relay_metadata(&mut svm); + + token_searcher.airdrop(&mut svm, &searcher.pubkey(), 99990.); + token_user.airdrop(&mut svm, &user.pubkey(), 99990.); + + // searcher token balances + + // searcher token balances + assert_all_token_balances!( &mut svm, - &router_ta_mint_searcher, - token_searcher.get_amount_with_decimals(0.), - )); + token_searcher, + { + associated: { + searcher.pubkey() => 100000.0, + user.pubkey() => 0.0, + get_express_relay_metadata_key() => 0.0, + express_relay_metadata.fee_receiver_relayer => 0.0, + }, + raw: { + router_ta_mint_searcher => 0.0, + } + } + ); + // user token balances - assert!(Token::token_balance_matches( + assert_all_token_balances!( &mut svm, - &token_user.get_associated_token_address(&searcher.pubkey()), - token_user.get_amount_with_decimals(0.65), - )); - assert!(Token::token_balance_matches( - &mut svm, - &token_user.get_associated_token_address(&user.pubkey()), - token_user.get_amount_with_decimals(9.), - )); - assert!(Token::token_balance_matches( + token_user, + { + associated: { + searcher.pubkey() => 0.0, + user.pubkey() => 100000.0, + get_express_relay_metadata_key() => 0.0, + express_relay_metadata.fee_receiver_relayer => 0.0, + }, + raw: { + router_ta_mint_user => 0.0, + } + } + ); + + // searcher token fee + let swap_args = SwapV2Args { + deadline: svm.get_sysvar::().unix_timestamp, + amount_searcher: token_searcher.get_amount_with_decimals(10000.), + amount_user: token_user.get_amount_with_decimals(10000.), + referral_fee_ppm: 1589, + fee_token: FeeToken::Searcher, + swap_platform_fee_ppm: 2222, + }; + let instructions = build_swap_instructions(SwapParams { + searcher: searcher.pubkey(), + user: user.pubkey(), + router_fee_receiver_ta: router_ta_mint_searcher, + fee_receiver_relayer: express_relay_metadata.fee_receiver_relayer, + token_searcher: token_searcher.clone(), + token_user: token_user.clone(), + swap_args, + overrides: SwapParamOverride { + ..Default::default() + }, + relayer_signer: relayer_signer.pubkey(), + }); + submit_transaction( &mut svm, - &token_user.get_associated_token_address(&get_express_relay_metadata_key()), - token_user.get_amount_with_decimals(0.16), - )); - assert!(Token::token_balance_matches( + &instructions, + &searcher, + &[&searcher, &user, &relayer_signer], + ) + .unwrap(); + + // searcher token balances + assert_all_token_balances!( &mut svm, - &token_user.get_associated_token_address(&express_relay_metadata.fee_receiver_relayer), - token_user.get_amount_with_decimals(0.04), - )); - assert!(Token::token_balance_matches( + token_searcher, + { + associated: { + searcher.pubkey() => 90000.0, + user.pubkey() => 9961.89, + get_express_relay_metadata_key() => 17.7760, + express_relay_metadata.fee_receiver_relayer => 4.4440, + }, + raw: { + router_ta_mint_searcher => 15.8900, + } + } + ); + + // user token balances + assert_all_token_balances!( &mut svm, - &router_ta_mint_user, - token_user.get_amount_with_decimals(0.15), - )); + token_user, + { + associated: { + searcher.pubkey() => 10000., + user.pubkey() => 90000., + get_express_relay_metadata_key() => 0., + express_relay_metadata.fee_receiver_relayer => 0., + }, + raw: { + router_ta_mint_user => 0., + } + } + ); }