Skip to content

Commit 41baea8

Browse files
authored
Add referral fee ppm to auction server (#527)
1 parent 830ed59 commit 41baea8

16 files changed

Lines changed: 254 additions & 136 deletions

File tree

auction-server/api-types/src/opportunity.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,14 +224,24 @@ pub enum OpportunityParamsV1ProgramSvm {
224224
#[serde_as(as = "DisplayFromStr")]
225225
router_account: Pubkey,
226226

227+
// TODO this should be deleted
227228
/// The referral fee in basis points.
228229
#[schema(example = 10)]
229230
referral_fee_bps: u16,
230231

232+
/// The referral fee in parts per million.
233+
#[schema(example = 1000)]
234+
referral_fee_ppm: u64,
235+
236+
// TODO this should be deleted
231237
/// The platform fee in basis points.
232238
#[schema(example = 10)]
233239
platform_fee_bps: u64,
234240

241+
/// The platform fee in parts per million.
242+
#[schema(example = 1000)]
243+
platform_fee_ppm: u64,
244+
235245
/// Specifies whether the fees are to be paid in the searcher or user token.
236246
#[schema(example = "searcher_token")]
237247
fee_token: FeeToken,
@@ -471,9 +481,9 @@ pub struct ReferralFeeInfo {
471481
#[schema(example = "DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5", value_type = String)]
472482
#[serde_as(as = "DisplayFromStr")]
473483
pub router: Pubkey,
474-
/// The referral fee in basis points.
484+
/// The referral fee in parts per million.
475485
#[schema(example = 10, value_type = u16)]
476-
pub referral_fee_bps: u16,
486+
pub referral_fee_ppm: u64,
477487
}
478488

479489
#[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq, Debug)]

auction-server/src/api.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -319,8 +319,12 @@ pub enum SwapInstructionError {
319319
found: express_relay::FeeToken,
320320
},
321321
ReferralFee {
322-
expected: u16,
323-
found: u16,
322+
expected: u64,
323+
found: u64,
324+
},
325+
PlatformFee {
326+
expected: u64,
327+
found: u64,
324328
},
325329
AssociatedRouterTokenAccount {
326330
expected: Pubkey,
@@ -381,6 +385,11 @@ impl std::fmt::Display for SwapInstructionError {
381385
"Associated token account for router does not match. Expected: {:?} found: {:?}",
382386
expected, found
383387
),
388+
SwapInstructionError::PlatformFee { expected, found } => write!(
389+
f,
390+
"Invalid platform fee ppm {} in swap instruction data. Value does not match the platform fee ppm in swap opportunity {}",
391+
found, expected
392+
),
384393
}
385394
}
386395
}

auction-server/src/auction/service/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,9 +224,10 @@ mod mock_service {
224224
auction: entities::Auction,
225225
) -> Result<VersionedTransaction, RestError>;
226226

227-
pub fn extract_swap_data(
227+
pub async fn extract_swap_data(
228+
&self,
228229
instruction: &CompiledInstruction,
229-
) -> Result<express_relay::SwapArgs, RestError>;
230+
) -> Result<express_relay::SwapV2Args, RestError>;
230231

231232
pub fn get_new_status(
232233
bid: &entities::Bid,

auction-server/src/auction/service/submit_quote.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ impl Service {
8484
&self,
8585
signed_bid: entities::Bid,
8686
auction: entities::Auction,
87-
swap_args: express_relay::SwapArgs,
87+
swap_args: express_relay::SwapV2Args,
8888
lock: entities::BidLock,
8989
) -> Result<(), RestError> {
9090
let _lock = lock.lock().await;
@@ -162,7 +162,7 @@ impl Service {
162162
fn is_within_deadline_buffer(
163163
&self,
164164
chain_id: ChainId,
165-
swap_args: express_relay::SwapArgs,
165+
swap_args: express_relay::SwapV2Args,
166166
) -> bool {
167167
let deadline_buffer_secs = swap_args.deadline - OffsetDateTime::now_utc().unix_timestamp();
168168

@@ -235,7 +235,9 @@ impl Service {
235235
return Err(RestError::BadParameters("Invalid quote.".to_string()));
236236
}
237237

238-
let swap_args = Self::extract_swap_data(&swap_instruction)
238+
let swap_args = self
239+
.extract_swap_data(&swap_instruction)
240+
.await
239241
.map_err(|_| RestError::BadParameters("Invalid quote.".to_string()))?;
240242

241243
let bid_lock = self

auction-server/src/auction/service/verification.rs

Lines changed: 86 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use {
2828
TokenAccountInitializationConfigs,
2929
},
3030
service::{
31+
get_express_relay_metadata::GetExpressRelayMetadataInput,
3132
get_live_opportunities::GetLiveOpportunitiesInput,
3233
get_opportunities::GetLiveOpportunityByIdInput,
3334
get_quote::{
@@ -278,26 +279,50 @@ impl Service {
278279
})
279280
}
280281

281-
pub fn extract_swap_data(
282+
pub async fn extract_swap_data(
283+
&self,
282284
instruction: &CompiledInstruction,
283-
) -> Result<express_relay_svm::SwapArgs, RestError> {
284-
let discriminator = express_relay_svm::instruction::Swap::DISCRIMINATOR;
285-
express_relay_svm::SwapArgs::try_from_slice(
286-
&instruction.data.as_slice()[discriminator.len()..],
287-
)
288-
.map_err(|e| RestError::BadParameters(format!("Invalid swap instruction data: {}", e)))
285+
) -> Result<express_relay_svm::SwapV2Args, RestError> {
286+
if instruction
287+
.data
288+
.starts_with(express_relay_svm::instruction::Swap::DISCRIMINATOR)
289+
{
290+
let discriminator = express_relay_svm::instruction::Swap::DISCRIMINATOR;
291+
let express_relay_metadata = self
292+
.opportunity_service
293+
.get_express_relay_metadata(GetExpressRelayMetadataInput {
294+
chain_id: self.config.chain_id.clone(),
295+
})
296+
.await?;
297+
let swap_args = express_relay_svm::SwapArgs::try_from_slice(
298+
&instruction.data.as_slice()[discriminator.len()..],
299+
)
300+
.map_err(|e| {
301+
RestError::BadParameters(format!("Invalid swap instruction data: {}", e))
302+
})?;
303+
Ok(swap_args.convert_to_v2(express_relay_metadata.swap_platform_fee_bps))
304+
} else {
305+
let discriminator = express_relay_svm::instruction::SwapV2::DISCRIMINATOR;
306+
express_relay_svm::SwapV2Args::try_from_slice(
307+
&instruction.data.as_slice()[discriminator.len()..],
308+
)
309+
.map_err(|e| RestError::BadParameters(format!("Invalid swap instruction data: {}", e)))
310+
}
289311
}
290312

291313
pub fn extract_express_relay_instruction(
292314
&self,
293315
transaction: VersionedTransaction,
294316
instruction_type: BidPaymentInstructionType,
295317
) -> Result<(usize, CompiledInstruction), RestError> {
296-
let discriminator = match instruction_type {
318+
let valid_discriminators = match instruction_type {
297319
BidPaymentInstructionType::SubmitBid => {
298-
express_relay_svm::instruction::SubmitBid::DISCRIMINATOR
320+
vec![express_relay_svm::instruction::SubmitBid::DISCRIMINATOR]
299321
}
300-
BidPaymentInstructionType::Swap => express_relay_svm::instruction::Swap::DISCRIMINATOR,
322+
BidPaymentInstructionType::Swap => vec![
323+
express_relay_svm::instruction::Swap::DISCRIMINATOR,
324+
express_relay_svm::instruction::SwapV2::DISCRIMINATOR,
325+
],
301326
};
302327
let instructions = Self::extract_program_instructions(
303328
&transaction,
@@ -316,7 +341,10 @@ impl Service {
316341
instructions.len(),
317342
)),
318343
}?;
319-
if !instruction.data.starts_with(discriminator) {
344+
if valid_discriminators
345+
.iter()
346+
.all(|discriminator| !instruction.data.starts_with(discriminator))
347+
{
320348
return Err(RestError::BadParameters(
321349
"Wrong instruction type for Express Relay Program".to_string(),
322350
));
@@ -398,7 +426,7 @@ impl Service {
398426
bid_data.transaction.clone(),
399427
BidPaymentInstructionType::Swap,
400428
)?;
401-
let swap_data = Self::extract_swap_data(&swap_instruction)?;
429+
let swap_data = self.extract_swap_data(&swap_instruction).await?;
402430
let SwapAccounts {
403431
user_wallet,
404432
mint_searcher,
@@ -508,11 +536,20 @@ impl Service {
508536
));
509537
}
510538

511-
if swap_data.referral_fee_bps != opportunity_swap_data.referral_fee_bps {
539+
if swap_data.referral_fee_ppm != opportunity_swap_data.referral_fee_ppm {
512540
return Err(RestError::InvalidSwapInstruction(
513541
SwapInstructionError::ReferralFee {
514-
expected: opportunity_swap_data.referral_fee_bps,
515-
found: swap_data.referral_fee_bps,
542+
expected: opportunity_swap_data.referral_fee_ppm,
543+
found: swap_data.referral_fee_ppm,
544+
},
545+
));
546+
}
547+
548+
if swap_data.swap_platform_fee_ppm != opportunity_swap_data.platform_fee_ppm {
549+
return Err(RestError::InvalidSwapInstruction(
550+
SwapInstructionError::PlatformFee {
551+
expected: opportunity_swap_data.platform_fee_ppm,
552+
found: swap_data.swap_platform_fee_ppm,
516553
},
517554
));
518555
}
@@ -604,7 +641,7 @@ impl Service {
604641
async fn check_transfer_instruction(
605642
&self,
606643
tx: &VersionedTransaction,
607-
swap_data: &express_relay_svm::SwapArgs,
644+
swap_data: &express_relay_svm::SwapV2Args,
608645
swap_accounts: &SwapAccounts,
609646
opportunity_swap_data: &OpportunitySvmProgramSwap,
610647
) -> Result<(), RestError> {
@@ -1081,7 +1118,7 @@ impl Service {
10811118
async fn check_wrap_unwrap_native_token_instructions(
10821119
&self,
10831120
tx: &VersionedTransaction,
1084-
swap_data: &express_relay_svm::SwapArgs,
1121+
swap_data: &express_relay_svm::SwapV2Args,
10851122
swap_accounts: &SwapAccounts,
10861123
opportunity_swap_data: &OpportunitySvmProgramSwap,
10871124
) -> Result<(), RestError> {
@@ -1170,7 +1207,7 @@ impl Service {
11701207
bid_data.transaction.clone(),
11711208
BidPaymentInstructionType::Swap,
11721209
)?;
1173-
let swap_data = Self::extract_swap_data(&swap_instruction)?;
1210+
let swap_data = self.extract_swap_data(&swap_instruction).await?;
11741211
let swap_accounts = self
11751212
.extract_swap_accounts(&bid_data.transaction, &swap_instruction)
11761213
.await?;
@@ -1229,7 +1266,7 @@ impl Service {
12291266
&quote_tokens,
12301267
&user_wallet,
12311268
&router_token_account,
1232-
swap_data.referral_fee_bps,
1269+
swap_data.referral_fee_ppm,
12331270
);
12341271

12351272
Ok(BidDataSvm {
@@ -1607,6 +1644,7 @@ mod tests {
16071644
},
16081645
},
16091646
borsh::BorshDeserialize,
1647+
express_relay::state::FEE_BPS_TO_PPM,
16101648
express_relay_api_types::opportunity as opportunity_api,
16111649
express_relay_client::svm::{
16121650
self,
@@ -1654,10 +1692,12 @@ mod tests {
16541692
Self {
16551693
user_wallet_address,
16561694
platform_fee_bps: 0,
1695+
platform_fee_ppm: 0,
16571696
token_program_user: spl_token::id(),
16581697
token_program_searcher: spl_token::id(),
16591698
fee_token: FeeToken::UserToken,
16601699
referral_fee_bps: 10,
1700+
referral_fee_ppm: 1_000,
16611701
user_mint_user_balance: LAMPORTS_PER_SOL,
16621702
token_account_initialization_configs:
16631703
TokenAccountInitializationConfigs::searcher_payer(),
@@ -1721,6 +1761,7 @@ mod tests {
17211761
searcher_token: spl_token::native_mint::id(),
17221762
};
17231763
let referral_fee_bps = 10;
1764+
let referral_fee_ppm = referral_fee_bps * FEE_BPS_TO_PPM;
17241765

17251766
let router_token_account = get_associated_token_address_with_program_id(
17261767
&router,
@@ -1737,25 +1778,25 @@ mod tests {
17371778
&tokens_user_specified,
17381779
&user_wallet_address,
17391780
&router_token_account,
1740-
referral_fee_bps,
1781+
referral_fee_ppm,
17411782
);
17421783
let permission_account_searcher_token_specified = get_quote_virtual_permission_account(
17431784
&tokens_searcher_specified,
17441785
&user_wallet_address,
17451786
&router_token_account,
1746-
referral_fee_bps,
1787+
referral_fee_ppm,
17471788
);
17481789
let permission_account_user_token_wsol = get_quote_virtual_permission_account(
17491790
&tokens_user_wsol,
17501791
&user_wallet_address,
17511792
&router_token_account_wsol,
1752-
referral_fee_bps,
1793+
referral_fee_ppm,
17531794
);
17541795
let permission_account_searcher_token_wsol = get_quote_virtual_permission_account(
17551796
&tokens_searcher_wsol,
17561797
&user_wallet_address,
17571798
&router_token_account,
1758-
referral_fee_bps,
1799+
referral_fee_ppm,
17591800
);
17601801

17611802
let opp_user_token_specified = OpportunitySvm {
@@ -1875,7 +1916,7 @@ mod tests {
18751916
&tokens_user_specified,
18761917
&indicative_price_taker,
18771918
&router_token_account,
1878-
referral_fee_bps,
1919+
referral_fee_ppm,
18791920
);
18801921

18811922
let opp_with_indicative_price_taker = OpportunitySvm {
@@ -2026,6 +2067,24 @@ mod tests {
20262067
.cloned()
20272068
});
20282069

2070+
opportunity_service
2071+
.expect_get_express_relay_metadata()
2072+
.returning(move |_| {
2073+
Ok(express_relay::state::ExpressRelayMetadata {
2074+
admin: Pubkey::new_unique(),
2075+
relayer_signer: Pubkey::new_unique(),
2076+
fee_receiver_relayer: Pubkey::new_unique(),
2077+
// the portion of the bid that goes to the router, in bps
2078+
split_router_default: 10,
2079+
// the portion of the remaining bid (after router fees) that goes to the relayer, in bps
2080+
split_relayer: 20,
2081+
// the portion of the swap amount that should go to the platform (relayer + express relay), in bps
2082+
swap_platform_fee_bps: 30,
2083+
// secondary relayer signer, useful for 0-downtime transitioning to a new relayer
2084+
secondary_relayer_signer: Pubkey::new_unique(),
2085+
})
2086+
});
2087+
20292088
(
20302089
opportunity_service,
20312090
TestOpportunities {
@@ -2897,7 +2956,7 @@ mod tests {
28972956
OpportunitySvmProgram::Swap(program) => program,
28982957
_ => panic!("Expected swap program"),
28992958
};
2900-
program.referral_fee_bps += 1;
2959+
program.referral_fee_ppm += 1;
29012960
opportunity.program = OpportunitySvmProgram::Swap(program.clone());
29022961
let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams {
29032962
searcher: searcher.pubkey(),
@@ -2914,8 +2973,8 @@ mod tests {
29142973
assert_eq!(
29152974
result.unwrap_err(),
29162975
RestError::InvalidSwapInstruction(SwapInstructionError::ReferralFee {
2917-
expected: program.referral_fee_bps - 1,
2918-
found: program.referral_fee_bps,
2976+
expected: program.referral_fee_ppm - 1,
2977+
found: program.referral_fee_ppm,
29192978
})
29202979
);
29212980
}

0 commit comments

Comments
 (0)