Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 16 additions & 17 deletions contracts/svm/programs/express_relay/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,8 @@ pub mod express_relay {
)
}


pub fn swap_internal(ctx: Context<Swap>, data: SwapV2Args) -> Result<()> {
ctx.accounts.check_raw_constraints(data.fee_token)?;
check_deadline(data.deadline)?;
ctx.accounts
.express_relay_metadata
Expand Down Expand Up @@ -227,6 +227,7 @@ pub mod express_relay {

Ok(())
}

pub fn swap(ctx: Context<Swap>, data: SwapArgs) -> Result<()> {
let data = ctx.accounts.convert_to_v2(&data);
swap_internal(ctx, data)
Expand Down Expand Up @@ -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<SwapArgs>)]
pub struct Swap<'info> {
/// Searcher is the party that fulfills the quote request
pub searcher: Signer<'info>,
Expand Down Expand Up @@ -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<InterfaceAccount<'info, Mint>>,

// 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
Expand Down
3 changes: 3 additions & 0 deletions contracts/svm/programs/express_relay/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
71 changes: 57 additions & 14 deletions contracts/svm/programs/express_relay/src/swap.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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,
Expand All @@ -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((
Expand All @@ -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((
Expand Down Expand Up @@ -165,25 +205,28 @@ impl ExpressRelayMetadata {
referral_fee_bps: u16,
amount: u64,
) -> Result<SwapFeesWithRemainingAmount> {
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<SwapFeesWithRemainingAmount> {
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)?
Expand Down
55 changes: 38 additions & 17 deletions contracts/svm/testing/src/express_relay/swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use {
},
FeeToken,
SwapArgs,
SwapV2Args,
},
solana_sdk::{
instruction::Instruction,
Expand All @@ -34,25 +35,50 @@ pub struct SwapParamOverride {
pub searcher_ta_mint_user: Option<Pubkey>,
pub express_relay_fee_receiver_ata: Option<Pubkey>,
pub relayer_fee_receiver_ata: Option<Pubkey>,
pub platform_fee_bps: Option<u64>,
}

pub struct SwapParams {
pub trait AnyVersionSwapArgs: Sized + Send + Sync {
fn get_fee_token(&self) -> FeeToken;

fn into_swap_instruction_data(self) -> Vec<u8>;
}

impl AnyVersionSwapArgs for SwapArgs {
fn get_fee_token(&self) -> FeeToken {
self.fee_token
}

fn into_swap_instruction_data(self) -> Vec<u8> {
Swap { data: self }.data()
}
}

impl AnyVersionSwapArgs for SwapV2Args {
fn get_fee_token(&self) -> FeeToken {
self.fee_token
}

fn into_swap_instruction_data(self) -> Vec<u8> {
SwapV2 { data: self }.data()
}
}

pub struct SwapParams<Args: AnyVersionSwapArgs = SwapArgs> {
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.
pub overrides: SwapParamOverride,
}

/// Builds a swap instruction.
pub fn create_swap_instruction(swap_params: SwapParams) -> Instruction {
pub fn create_swap_instruction(swap_params: SwapParams<impl AnyVersionSwapArgs>) -> Instruction {
let SwapParams {
searcher,
user,
Expand All @@ -70,20 +96,19 @@ 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,
});

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,
};
Expand Down Expand Up @@ -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,
Expand All @@ -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<Instruction> {
pub fn build_swap_instructions(
swap_params: SwapParams<impl AnyVersionSwapArgs>,
) -> Vec<Instruction> {
let SwapParams {
searcher,
user,
Expand All @@ -177,11 +198,11 @@ pub fn build_swap_instructions(swap_params: SwapParams) -> Vec<Instruction> {

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,
};
Expand Down
Loading
Loading