Skip to content

Commit 8719866

Browse files
author
Barnabas Ratki
committed
feat: Add ppm precision to swap v2 fees
1 parent 8979f83 commit 8719866

6 files changed

Lines changed: 534 additions & 250 deletions

File tree

contracts/svm/programs/express_relay/src/lib.rs

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,8 @@ pub mod express_relay {
182182
)
183183
}
184184

185-
186185
pub fn swap_internal(ctx: Context<Swap>, data: SwapV2Args) -> Result<()> {
186+
ctx.accounts.check_raw_constraints(data.fee_token)?;
187187
check_deadline(data.deadline)?;
188188
ctx.accounts
189189
.express_relay_metadata
@@ -227,6 +227,7 @@ pub mod express_relay {
227227

228228
Ok(())
229229
}
230+
230231
pub fn swap(ctx: Context<Swap>, data: SwapArgs) -> Result<()> {
231232
let data = ctx.accounts.convert_to_v2(&data);
232233
swap_internal(ctx, data)
@@ -438,31 +439,31 @@ pub struct SwapArgs {
438439
impl SwapArgs {
439440
pub fn convert_to_v2(&self, swap_platform_fee_bps: u64) -> SwapV2Args {
440441
SwapV2Args {
441-
deadline: self.deadline,
442-
amount_searcher: self.amount_searcher,
443-
amount_user: self.amount_user,
444-
fee_token: self.fee_token,
445-
referral_fee_bps: self.referral_fee_bps,
446-
swap_platform_fee_bps,
442+
deadline: self.deadline,
443+
amount_searcher: self.amount_searcher,
444+
amount_user: self.amount_user,
445+
fee_token: self.fee_token,
446+
referral_fee_ppm: u64::from(self.referral_fee_bps) * FEE_BPS_TO_PPM,
447+
swap_platform_fee_ppm: swap_platform_fee_bps * FEE_BPS_TO_PPM,
447448
}
448449
}
449450
}
450451

451452
#[derive(AnchorSerialize, AnchorDeserialize, Eq, PartialEq, Clone, Copy, Debug)]
452453
pub struct SwapV2Args {
453-
// deadline as a unix timestamp in seconds
454+
/// deadline as a unix timestamp in seconds
454455
pub deadline: i64,
455456
pub amount_searcher: u64,
456457
pub amount_user: u64,
457-
// The referral fee is specified in basis points
458-
pub referral_fee_bps: u16,
459-
// Token in which the fees will be paid
458+
/// The referral fee is specified in mill percentile
459+
pub referral_fee_ppm: u64,
460+
/// Token in which the fees will be paid
460461
pub fee_token: FeeToken,
461-
pub swap_platform_fee_bps: u64,
462+
/// The platform fee is specified in mill percentile
463+
pub swap_platform_fee_ppm: u64,
462464
}
463465

464466
#[derive(Accounts)]
465-
#[instruction(data: Box<SwapArgs>)]
466467
pub struct Swap<'info> {
467468
/// Searcher is the party that fulfills the quote request
468469
pub searcher: Signer<'info>,
@@ -523,21 +524,19 @@ pub struct Swap<'info> {
523524
pub mint_searcher: Box<InterfaceAccount<'info, Mint>>,
524525

525526
#[account(mint::token_program = token_program_user)]
527+
/// CHECK: we check that this is set correctly based on the fee token manually since the V2 arguments are incompatible on the wire
526528
pub mint_user: Box<InterfaceAccount<'info, Mint>>,
527529

528530
#[account(
529531
mint::token_program = token_program_fee,
530-
constraint = mint_fee.key() == if data.fee_token == FeeToken::Searcher { mint_searcher.key() } else { mint_user.key() }
531532
)]
532533
pub mint_fee: Box<InterfaceAccount<'info, Mint>>,
533534

534535
// Token programs
535536
pub token_program_searcher: Interface<'info, TokenInterface>,
536537
pub token_program_user: Interface<'info, TokenInterface>,
537538

538-
#[account(
539-
constraint = token_program_fee.key() == if data.fee_token == FeeToken::Searcher { token_program_searcher.key() } else { token_program_user.key() }
540-
)]
539+
/// CHECK: we check that this is set correctly based on the fee token manually since the V2 arguments are incompatible on the wire
541540
pub token_program_fee: Interface<'info, TokenInterface>,
542541

543542
/// Express relay configuration

contracts/svm/programs/express_relay/src/state.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ use anchor_lang::prelude::*;
22

33
pub const FEE_SPLIT_PRECISION: u64 = 10_000;
44

5+
pub const FEE_SPLIT_PRECISION_PPM: u64 = 100_000_000;
6+
pub const FEE_BPS_TO_PPM: u64 = 10_000;
7+
58
pub const RESERVE_EXPRESS_RELAY_METADATA: usize = 8 + 152 + 260;
69
pub const SEED_METADATA: &[u8] = b"metadata";
710

contracts/svm/programs/express_relay/src/swap.rs

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
use {
22
crate::{
33
error::ErrorCode,
4-
state::ExpressRelayMetadata,
4+
state::{
5+
ExpressRelayMetadata,
6+
FEE_BPS_TO_PPM,
7+
FEE_SPLIT_PRECISION_PPM,
8+
},
59
token::check_receiver_and_transfer_token_if_needed,
610
FeeToken,
711
Swap,
@@ -23,10 +27,46 @@ pub struct PostFeeSwapArgs {
2327
}
2428

2529

26-
impl<'info> Swap<'info> {
30+
impl Swap<'_> {
2731
pub fn convert_to_v2(&self, args: &SwapArgs) -> SwapV2Args {
2832
args.convert_to_v2(self.express_relay_metadata.swap_platform_fee_bps)
2933
}
34+
}
35+
36+
impl<'info> Swap<'info> {
37+
fn check_mint_user(&self, fee_token: FeeToken) -> Result<()> {
38+
let correct_mint_fee_key = if fee_token == FeeToken::Searcher {
39+
self.mint_searcher.key()
40+
} else {
41+
self.mint_user.key()
42+
};
43+
44+
// Preserve API compatibility by returning the same error as the anchor validator did
45+
(self.mint_fee.key() == correct_mint_fee_key)
46+
.then_some(())
47+
.ok_or(AnchorErrorCode::ConstraintRaw.into())
48+
}
49+
50+
fn check_token_program_fee(&self, fee_token: FeeToken) -> Result<()> {
51+
let correct_token_program_fee = if fee_token == FeeToken::Searcher {
52+
self.token_program_searcher.key()
53+
} else {
54+
self.token_program_user.key()
55+
};
56+
57+
// Preserve API compatibility by returning the same error as the anchor validator did
58+
(self.token_program_fee.key() == correct_token_program_fee)
59+
.then_some(())
60+
.ok_or(AnchorErrorCode::ConstraintRaw.into())
61+
}
62+
63+
pub fn check_raw_constraints(&self, fee_token: FeeToken) -> Result<()> {
64+
self.check_mint_user(fee_token)?;
65+
self.check_token_program_fee(fee_token)?;
66+
67+
Ok(())
68+
}
69+
3070
pub fn compute_swap_fees<'a>(
3171
&'a self,
3272
args: &SwapV2Args,
@@ -37,8 +77,8 @@ impl<'info> Swap<'info> {
3777
fees,
3878
remaining_amount,
3979
} = self.express_relay_metadata.compute_swap_fees(
40-
args.referral_fee_bps,
41-
args.swap_platform_fee_bps,
80+
args.referral_fee_ppm,
81+
args.swap_platform_fee_ppm,
4282
args.amount_searcher,
4383
)?;
4484
Ok((
@@ -58,8 +98,8 @@ impl<'info> Swap<'info> {
5898
fees,
5999
remaining_amount,
60100
} = self.express_relay_metadata.compute_swap_fees(
61-
args.referral_fee_bps,
62-
args.swap_platform_fee_bps,
101+
args.referral_fee_ppm,
102+
args.swap_platform_fee_ppm,
63103
args.amount_user,
64104
)?;
65105
Ok((
@@ -165,25 +205,28 @@ impl ExpressRelayMetadata {
165205
referral_fee_bps: u16,
166206
amount: u64,
167207
) -> Result<SwapFeesWithRemainingAmount> {
168-
self.compute_swap_fees(referral_fee_bps, self.swap_platform_fee_bps, amount)
208+
let referral_fee_ppms = u64::from(referral_fee_bps) * FEE_BPS_TO_PPM;
209+
let swap_platform_fees_ppms = self.swap_platform_fee_bps * FEE_BPS_TO_PPM;
210+
211+
self.compute_swap_fees(referral_fee_ppms, swap_platform_fees_ppms, amount)
169212
}
170213
pub fn compute_swap_fees(
171214
&self,
172-
referral_fee_bps: u16,
173-
swap_platform_fee_bps: u64,
215+
referral_fee_ppm: u64,
216+
swap_platform_fee_ppm: u64,
174217
amount: u64,
175218
) -> Result<SwapFeesWithRemainingAmount> {
176-
if u64::from(referral_fee_bps) > FEE_SPLIT_PRECISION {
219+
if referral_fee_ppm > FEE_SPLIT_PRECISION_PPM {
177220
return Err(ErrorCode::InvalidReferralFee.into());
178221
}
179222
let router_fee = amount
180-
.checked_mul(referral_fee_bps.into())
223+
.checked_mul(referral_fee_ppm)
181224
.ok_or(ProgramError::ArithmeticOverflow)?
182-
/ FEE_SPLIT_PRECISION;
225+
/ FEE_SPLIT_PRECISION_PPM;
183226
let platform_fee = amount
184-
.checked_mul(swap_platform_fee_bps)
227+
.checked_mul(swap_platform_fee_ppm)
185228
.ok_or(ProgramError::ArithmeticOverflow)?
186-
/ FEE_SPLIT_PRECISION;
229+
/ FEE_SPLIT_PRECISION_PPM;
187230
let relayer_fee = platform_fee
188231
.checked_mul(self.split_relayer)
189232
.ok_or(ProgramError::ArithmeticOverflow)?

contracts/svm/testing/src/express_relay/swap.rs

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use {
1919
},
2020
FeeToken,
2121
SwapArgs,
22+
SwapV2Args,
2223
},
2324
solana_sdk::{
2425
instruction::Instruction,
@@ -34,25 +35,50 @@ pub struct SwapParamOverride {
3435
pub searcher_ta_mint_user: Option<Pubkey>,
3536
pub express_relay_fee_receiver_ata: Option<Pubkey>,
3637
pub relayer_fee_receiver_ata: Option<Pubkey>,
37-
pub platform_fee_bps: Option<u64>,
3838
}
3939

40-
pub struct SwapParams {
40+
pub trait AnyVersionSwapArgs: Sized + Send + Sync {
41+
fn get_fee_token(&self) -> FeeToken;
42+
43+
fn into_swap_instruction_data(self) -> Vec<u8>;
44+
}
45+
46+
impl AnyVersionSwapArgs for SwapArgs {
47+
fn get_fee_token(&self) -> FeeToken {
48+
self.fee_token
49+
}
50+
51+
fn into_swap_instruction_data(self) -> Vec<u8> {
52+
Swap { data: self }.data()
53+
}
54+
}
55+
56+
impl AnyVersionSwapArgs for SwapV2Args {
57+
fn get_fee_token(&self) -> FeeToken {
58+
self.fee_token
59+
}
60+
61+
fn into_swap_instruction_data(self) -> Vec<u8> {
62+
SwapV2 { data: self }.data()
63+
}
64+
}
65+
66+
pub struct SwapParams<Args: AnyVersionSwapArgs = SwapArgs> {
4167
pub searcher: Pubkey,
4268
pub user: Pubkey,
4369
pub router_fee_receiver_ta: Pubkey,
4470
pub fee_receiver_relayer: Pubkey,
4571
pub token_searcher: Token,
4672
pub token_user: Token,
47-
pub swap_args: SwapArgs,
73+
pub swap_args: Args,
4874
pub relayer_signer: Pubkey,
4975
/// Overrides from default behavior that may result in an invalid instruction
5076
/// and are meant to be used for testing.
5177
pub overrides: SwapParamOverride,
5278
}
5379

5480
/// Builds a swap instruction.
55-
pub fn create_swap_instruction(swap_params: SwapParams) -> Instruction {
81+
pub fn create_swap_instruction(swap_params: SwapParams<impl AnyVersionSwapArgs>) -> Instruction {
5682
let SwapParams {
5783
searcher,
5884
user,
@@ -70,20 +96,19 @@ pub fn create_swap_instruction(swap_params: SwapParams) -> Instruction {
7096
mint_fee: mint_fee_override,
7197
express_relay_fee_receiver_ata,
7298
relayer_fee_receiver_ata,
73-
platform_fee_bps,
7499
},
75100
} = swap_params;
76101
let express_relay_metadata = get_express_relay_metadata_key();
77102

78-
let mint_fee = mint_fee_override.unwrap_or(match swap_args.fee_token {
103+
let mint_fee = mint_fee_override.unwrap_or(match swap_args.get_fee_token() {
79104
FeeToken::Searcher => token_searcher.mint,
80105
FeeToken::User => token_user.mint,
81106
});
82107

83108
let token_program_searcher = token_searcher.token_program;
84109
let token_program_user = token_user.token_program;
85110

86-
let token_program_fee = match swap_args.fee_token {
111+
let token_program_fee = match swap_args.get_fee_token() {
87112
FeeToken::Searcher => token_program_searcher,
88113
FeeToken::User => token_program_user,
89114
};
@@ -142,13 +167,7 @@ pub fn create_swap_instruction(swap_params: SwapParams) -> Instruction {
142167
}
143168
.to_account_metas(None);
144169

145-
let data = match platform_fee_bps {
146-
Some(fee) => SwapV2 {
147-
data: swap_args.convert_to_v2(fee),
148-
}
149-
.data(),
150-
None => Swap { data: swap_args }.data(),
151-
};
170+
let data = swap_args.into_swap_instruction_data();
152171
Instruction {
153172
program_id: express_relay::ID,
154173
accounts: accounts_submit_bid,
@@ -157,7 +176,9 @@ pub fn create_swap_instruction(swap_params: SwapParams) -> Instruction {
157176
}
158177

159178
/// Builds a set of instructions to perform a swap, including creating the associated token accounts.
160-
pub fn build_swap_instructions(swap_params: SwapParams) -> Vec<Instruction> {
179+
pub fn build_swap_instructions(
180+
swap_params: SwapParams<impl AnyVersionSwapArgs>,
181+
) -> Vec<Instruction> {
161182
let SwapParams {
162183
searcher,
163184
user,
@@ -177,11 +198,11 @@ pub fn build_swap_instructions(swap_params: SwapParams) -> Vec<Instruction> {
177198

178199
let token_program_searcher = token_searcher.token_program;
179200
let token_program_user = token_user.token_program;
180-
let mint_fee = mint_fee_override.unwrap_or(match swap_args.fee_token {
201+
let mint_fee = mint_fee_override.unwrap_or(match swap_args.get_fee_token() {
181202
FeeToken::Searcher => token_searcher.mint,
182203
FeeToken::User => token_user.mint,
183204
});
184-
let token_program_fee = match swap_args.fee_token {
205+
let token_program_fee = match swap_args.get_fee_token() {
185206
FeeToken::Searcher => token_program_searcher,
186207
FeeToken::User => token_program_user,
187208
};

0 commit comments

Comments
 (0)