Skip to content

Commit 66413a4

Browse files
committed
handle unapproved system/token/associatedToken ixs better
1 parent 40e4e85 commit 66413a4

File tree

2 files changed

+120
-48
lines changed

2 files changed

+120
-48
lines changed

auction-server/src/api.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ pub enum InstructionError {
132132
InvalidMemoInstructionCount { expected: usize, found: usize },
133133
InvalidMemoString { expected: String, found: String },
134134
UnsupportedInvocationOfUserWallet,
135+
UnapprovedProgramId(Pubkey),
135136
}
136137

137138
impl std::fmt::Display for InstructionError {
@@ -281,6 +282,9 @@ impl std::fmt::Display for InstructionError {
281282
"Invocation of user wallet is not supported in this instruction"
282283
)
283284
}
285+
InstructionError::UnapprovedProgramId(program_id) => {
286+
write!(f, "Instruction has unapproved program id: {:?}", program_id)
287+
}
284288
}
285289
}
286290
}

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

Lines changed: 116 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -271,14 +271,44 @@ impl Service {
271271
Ok(())
272272
}
273273

274+
/// Checks if the instruction provided meets the criteria for validity.
275+
/// Instruction must either be an approved program instruction or not contain the user wallet in its accounts.
274276
fn validate_swap_transaction_instruction(
275277
&self,
276278
program_id: &Pubkey,
277279
ix: &CompiledInstruction,
278280
user_wallet: &Pubkey,
279281
accounts: &[Pubkey],
282+
) -> Result<(), InstructionError> {
283+
if self
284+
.check_approved_program_instruction(program_id, ix)
285+
.is_ok()
286+
{
287+
Ok(())
288+
} else {
289+
ix.accounts.iter().try_for_each(|i| {
290+
if accounts
291+
.get(*i as usize)
292+
.expect("account index out of bounds")
293+
== user_wallet
294+
{
295+
return Err(InstructionError::UnsupportedInvocationOfUserWallet);
296+
}
297+
Ok(())
298+
})?;
299+
300+
Ok(())
301+
}
302+
}
303+
304+
/// Checks if the instruction is an approved program instruction for swap transactions.
305+
fn check_approved_program_instruction(
306+
&self,
307+
program_id: &Pubkey,
308+
ix: &CompiledInstruction,
280309
) -> Result<(), InstructionError> {
281310
if *program_id == system_program::id() {
311+
// approve only system program transfer instructions
282312
if matches!(
283313
bincode::deserialize::<SystemInstruction>(&ix.data),
284314
Ok(SystemInstruction::Transfer { .. })
@@ -291,8 +321,11 @@ impl Service {
291321
let ix_parsed = TokenInstruction::unpack(&ix.data)
292322
.map_err(InstructionError::InvalidSplTokenInstruction)?;
293323
match ix_parsed {
324+
// approve token program close account instructions
294325
TokenInstruction::CloseAccount { .. } => Ok(()),
326+
// approve token program sync native (for WSOL) instructions
295327
TokenInstruction::SyncNative { .. } => Ok(()),
328+
// other token program instructions are not approved
296329
_ => Err(InstructionError::UnsupportedSplTokenInstruction(format!(
297330
"{:?}",
298331
ix_parsed
@@ -304,28 +337,21 @@ impl Service {
304337
InstructionError::InvalidAssociatedTokenAccountInstruction(e.to_string())
305338
})?;
306339
match ix_parsed {
340+
// approve associated token account creation
307341
AssociatedTokenAccountInstruction::Create => Ok(()),
342+
// approve associated token account idempotent creation
308343
AssociatedTokenAccountInstruction::CreateIdempotent => Ok(()),
344+
// other associated token account instructions are not approved
309345
_ => Err(InstructionError::UnsupportedAssociatedTokenAccountInstruction(ix_parsed)),
310346
}
311347
} else if *program_id == self.config.chain_config.express_relay.program_id
312348
|| *program_id == spl_memo_client::ID
313349
|| *program_id == compute_budget::id()
314350
{
351+
// all express relay program, memo program, and compute budget program instructions are allowed
315352
Ok(())
316353
} else {
317-
ix.accounts.iter().try_for_each(|i| {
318-
if accounts
319-
.get(*i as usize)
320-
.expect("account index out of bounds")
321-
== user_wallet
322-
{
323-
return Err(InstructionError::UnsupportedInvocationOfUserWallet);
324-
}
325-
Ok(())
326-
})?;
327-
328-
Ok(())
354+
Err(InstructionError::UnapprovedProgramId(*program_id))
329355
}
330356
}
331357

@@ -2283,7 +2309,7 @@ mod tests {
22832309
}
22842310

22852311
#[tokio::test]
2286-
async fn test_verify_bid_when_unsupported_system_program_instruction() {
2312+
async fn test_check_approved_program_when_unsupported_system_program_instruction() {
22872313
let (service, opportunities) = get_service(true);
22882314
let opportunity = opportunities.user_token_specified.clone();
22892315
let bid_amount = 1;
@@ -2319,25 +2345,27 @@ mod tests {
23192345
),
23202346
];
23212347
for instruction in instructions.into_iter() {
2322-
let result = get_verify_bid_result(
2323-
service.clone(),
2324-
searcher.insecure_clone(),
2325-
vec![instruction, swap_instruction.clone()],
2326-
opportunities.user_token_specified.clone(),
2327-
)
2328-
.await;
2348+
let program_id = instruction.program_id;
2349+
let transaction = Transaction::new_with_payer(
2350+
&[instruction.clone(), swap_instruction.clone()],
2351+
Some(&searcher.pubkey()),
2352+
);
2353+
let instruction = transaction
2354+
.message()
2355+
.instructions
2356+
.first()
2357+
.expect("Expected at least one instruction")
2358+
.clone();
2359+
let result = service.check_approved_program_instruction(&program_id, &instruction);
23292360
assert_eq!(
23302361
result.unwrap_err(),
2331-
RestError::InvalidInstruction(
2332-
Some(0),
2333-
InstructionError::UnsupportedSystemProgramInstruction
2334-
)
2362+
InstructionError::UnsupportedSystemProgramInstruction
23352363
);
23362364
}
23372365
}
23382366

23392367
#[tokio::test]
2340-
async fn test_verify_bid_when_unsupported_token_instruction() {
2368+
async fn test_check_approved_program_when_unsupported_token_instruction() {
23412369
let (service, opportunities) = get_service(true);
23422370
let opportunity = opportunities.user_token_specified.clone();
23432371
let bid_amount = 1;
@@ -2445,25 +2473,27 @@ mod tests {
24452473
for instruction in instructions.into_iter() {
24462474
let data = instruction.data.clone();
24472475
let ix_parsed = TokenInstruction::unpack(&data).unwrap();
2448-
let result = get_verify_bid_result(
2449-
service.clone(),
2450-
searcher.insecure_clone(),
2451-
vec![instruction, swap_instruction.clone()],
2452-
opportunities.user_token_specified.clone(),
2453-
)
2454-
.await;
2476+
let program_id = instruction.program_id;
2477+
let transaction = Transaction::new_with_payer(
2478+
&[instruction.clone(), swap_instruction.clone()],
2479+
Some(&searcher.pubkey()),
2480+
);
2481+
let instruction = transaction
2482+
.message()
2483+
.instructions
2484+
.first()
2485+
.expect("Expected at least one instruction")
2486+
.clone();
2487+
let result = service.check_approved_program_instruction(&program_id, &instruction);
24552488
assert_eq!(
24562489
result.unwrap_err(),
2457-
RestError::InvalidInstruction(
2458-
Some(0),
2459-
InstructionError::UnsupportedSplTokenInstruction(format!("{:?}", ix_parsed)),
2460-
)
2490+
InstructionError::UnsupportedSplTokenInstruction(format!("{:?}", ix_parsed))
24612491
);
24622492
}
24632493
}
24642494

24652495
#[tokio::test]
2466-
async fn test_verify_bid_when_unsupported_associated_token_account_instruction() {
2496+
async fn test_check_approved_program_when_unsupported_associated_token_account_instruction() {
24672497
let (service, opportunities) = get_service(true);
24682498
let opportunity = opportunities.user_token_specified.clone();
24692499
let bid_amount = 1;
@@ -2483,30 +2513,68 @@ mod tests {
24832513
&Pubkey::new_unique(),
24842514
&spl_token::id(),
24852515
)];
2516+
24862517
for instruction in instructions.into_iter() {
24872518
let data = instruction.data.clone();
24882519
let ix_parsed = AssociatedTokenAccountInstruction::try_from_slice(&data)
24892520
.map_err(|e| {
24902521
InstructionError::InvalidAssociatedTokenAccountInstruction(e.to_string())
24912522
})
24922523
.unwrap();
2493-
let result = get_verify_bid_result(
2494-
service.clone(),
2495-
searcher.insecure_clone(),
2496-
vec![instruction, swap_instruction.clone()],
2497-
opportunities.user_token_specified.clone(),
2498-
)
2499-
.await;
2524+
let program_id = instruction.program_id;
2525+
let transaction = Transaction::new_with_payer(
2526+
&[instruction.clone(), swap_instruction.clone()],
2527+
Some(&searcher.pubkey()),
2528+
);
2529+
let instruction = transaction
2530+
.message()
2531+
.instructions
2532+
.first()
2533+
.expect("Expected at least one instruction")
2534+
.clone();
2535+
let result = service.check_approved_program_instruction(&program_id, &instruction);
25002536
assert_eq!(
25012537
result.unwrap_err(),
2502-
RestError::InvalidInstruction(
2503-
Some(0),
2504-
InstructionError::UnsupportedAssociatedTokenAccountInstruction(ix_parsed),
2505-
)
2538+
InstructionError::UnsupportedAssociatedTokenAccountInstruction(ix_parsed)
25062539
);
25072540
}
25082541
}
25092542

2543+
#[tokio::test]
2544+
async fn test_check_approved_program_when_unapproved_program_id() {
2545+
let (service, opportunities) = get_service(true);
2546+
let opportunity = opportunities.user_token_specified.clone();
2547+
let bid_amount = 1;
2548+
let searcher = Keypair::new();
2549+
let swap_instruction = svm::Svm::get_swap_instruction(GetSwapInstructionParams {
2550+
searcher: searcher.pubkey(),
2551+
opportunity_params: get_opportunity_params(opportunity.clone()),
2552+
bid_amount,
2553+
deadline: (OffsetDateTime::now_utc() + Duration::seconds(30)).unix_timestamp(),
2554+
fee_receiver_relayer: Pubkey::new_unique(),
2555+
relayer_signer: service.config.chain_config.express_relay.relayer.pubkey(),
2556+
})
2557+
.unwrap();
2558+
let program_id = Pubkey::new_unique();
2559+
let instruction = Instruction::new_with_bincode(program_id, &"", vec![]);
2560+
2561+
let transaction = Transaction::new_with_payer(
2562+
&[instruction.clone(), swap_instruction.clone()],
2563+
Some(&searcher.pubkey()),
2564+
);
2565+
let instruction = transaction
2566+
.message()
2567+
.instructions
2568+
.first()
2569+
.expect("Expected at least one instruction")
2570+
.clone();
2571+
let result = service.check_approved_program_instruction(&program_id, &instruction);
2572+
assert_eq!(
2573+
result.unwrap_err(),
2574+
InstructionError::UnapprovedProgramId(program_id)
2575+
);
2576+
}
2577+
25102578
#[tokio::test]
25112579
async fn test_verify_bid_when_arbitrary_program_invokes_user() {
25122580
let (service, opportunities) = get_service(true);

0 commit comments

Comments
 (0)