Skip to content

Commit 8ce1817

Browse files
committed
add transfer restriction on relayer signer
1 parent 38bbb3d commit 8ce1817

File tree

2 files changed

+67
-11
lines changed

2 files changed

+67
-11
lines changed

auction-server/src/api.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ pub enum InstructionError {
130130
MissingCreateAtaInstruction(Pubkey),
131131
MissingMemoInstruction { expected: String },
132132
UnapprovedProgramId(Pubkey),
133+
RelayerTransferInstructionNotAllowed,
133134
}
134135

135136
impl std::fmt::Display for InstructionError {
@@ -265,6 +266,9 @@ impl std::fmt::Display for InstructionError {
265266
InstructionError::UnapprovedProgramId(program_id) => {
266267
write!(f, "Instruction has unapproved program id: {:?}", program_id)
267268
}
269+
InstructionError::RelayerTransferInstructionNotAllowed => {
270+
write!(f, "Relayer transfer instruction is not allowed")
271+
}
268272
}
269273
}
270274
}

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

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,6 @@ impl Service {
506506
async fn extract_transfer_instructions(
507507
&self,
508508
tx: &VersionedTransaction,
509-
from: Pubkey,
510509
) -> Result<Vec<TransferInstructionData>, RestError> {
511510
let instructions: Vec<(usize, &CompiledInstruction)> =
512511
Self::extract_program_instructions(tx, &system_program::id())
@@ -537,23 +536,40 @@ impl Service {
537536
))
538537
}
539538
};
540-
if transfer_instruction.from == from {
541-
result.push(transfer_instruction);
542-
}
539+
result.push(transfer_instruction);
543540
}
544541
Ok(result)
545542
}
546543

547-
// Ensures that any SOL transfer instruction from the user is only carried out when user token mint is SOL and user needs to wrap SOL.
548-
async fn check_user_transfer_instruction(
544+
// Ensures no SOL transfers from relayer. Ensures that any SOL transfer instruction from the user is only carried out when user token mint is SOL and user needs to wrap SOL.
545+
async fn check_transfer_instructions(
549546
&self,
550547
tx: &VersionedTransaction,
551548
transaction_data: &entities::BidTransactionDataSwap,
552549
opportunity_swap_data: &OpportunitySvmProgramSwap,
553550
) -> Result<(), RestError> {
554-
let user_transfer_instructions = self
555-
.extract_transfer_instructions(tx, transaction_data.accounts.user_wallet)
556-
.await?;
551+
let transfer_instructions: Vec<TransferInstructionData> =
552+
self.extract_transfer_instructions(tx).await?;
553+
554+
let relayer_transfer_instructions = transfer_instructions
555+
.iter()
556+
.filter(|instruction| {
557+
instruction.from == self.config.chain_config.express_relay.relayer.pubkey()
558+
})
559+
.collect::<Vec<_>>();
560+
if !relayer_transfer_instructions.is_empty() {
561+
return Err(RestError::InvalidInstruction(
562+
relayer_transfer_instructions
563+
.first()
564+
.map(|instruction| instruction.index),
565+
InstructionError::RelayerTransferInstructionNotAllowed,
566+
));
567+
}
568+
569+
let user_transfer_instructions = transfer_instructions
570+
.iter()
571+
.filter(|instruction| instruction.from == transaction_data.accounts.user_wallet)
572+
.collect::<Vec<_>>();
557573
if user_transfer_instructions.len() > 1 {
558574
return Err(RestError::InvalidInstruction(
559575
user_transfer_instructions
@@ -877,7 +893,7 @@ impl Service {
877893
}
878894
}
879895

880-
// Ensures that the necessary create ATA instructions are present in the transaction (and are correctly paid for).
896+
// Ensures that the necessary create ATA instructions are present in the transaction (and are appropriately paid for).
881897
async fn check_create_ata_instructions(
882898
&self,
883899
tx: &VersionedTransaction,
@@ -990,7 +1006,7 @@ impl Service {
9901006
transaction_data: &entities::BidTransactionDataSwap,
9911007
opportunity_swap_data: &OpportunitySvmProgramSwap,
9921008
) -> Result<(), RestError> {
993-
self.check_user_transfer_instruction(tx, transaction_data, opportunity_swap_data)
1009+
self.check_transfer_instructions(tx, transaction_data, opportunity_swap_data)
9941010
.await?;
9951011
if transaction_data.accounts.mint_user == spl_token::native_mint::id() {
9961012
// User has to wrap Sol
@@ -3232,6 +3248,42 @@ mod tests {
32323248
);
32333249
}
32343250

3251+
#[tokio::test]
3252+
async fn test_verify_bid_when_relayer_transfer_instruction() {
3253+
let (service, opportunities) = get_service(false);
3254+
let searcher = Keypair::new();
3255+
let opportunity = opportunities.user_token_specified.clone();
3256+
let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams {
3257+
searcher: searcher.pubkey(),
3258+
opportunity_params: get_opportunity_params(opportunity.clone()),
3259+
bid_amount: 1,
3260+
deadline: (OffsetDateTime::now_utc() + Duration::seconds(30))
3261+
.unix_timestamp(),
3262+
fee_receiver_relayer: Pubkey::new_unique(),
3263+
relayer_signer: service.config.chain_config.express_relay.relayer.pubkey(),
3264+
})
3265+
.unwrap();
3266+
let transfer_instruction = system_instruction::transfer(
3267+
&service.config.chain_config.express_relay.relayer.pubkey(),
3268+
&Pubkey::new_unique(),
3269+
1,
3270+
);
3271+
let result = get_verify_bid_result(
3272+
service,
3273+
searcher,
3274+
vec![swap_instruction, transfer_instruction],
3275+
opportunity,
3276+
)
3277+
.await;
3278+
assert_eq!(
3279+
result.unwrap_err(),
3280+
RestError::InvalidInstruction(
3281+
Some(1),
3282+
InstructionError::RelayerTransferInstructionNotAllowed
3283+
)
3284+
);
3285+
}
3286+
32353287
#[tokio::test]
32363288
async fn test_verify_bid_when_no_close_account_instruction_is_allowed() {
32373289
let (service, opportunities) = get_service(false);

0 commit comments

Comments
 (0)