1use crate::{
2 config::{Config, FeePayerPolicy},
3 error::KoraError,
4 fee::fee::{FeeConfigUtil, TotalFeeCalculation},
5 oracle::PriceSource,
6 token::{interface::TokenMint, token::TokenUtil},
7 transaction::{
8 ParsedSPLInstructionData, ParsedSPLInstructionType, ParsedSystemInstructionData,
9 ParsedSystemInstructionType, VersionedTransactionResolved,
10 },
11};
12use solana_client::nonblocking::rpc_client::RpcClient;
13use solana_sdk::{pubkey::Pubkey, transaction::VersionedTransaction};
14use std::str::FromStr;
15
16use crate::fee::price::PriceModel;
17
18pub struct TransactionValidator {
19 fee_payer_pubkey: Pubkey,
20 max_allowed_lamports: u64,
21 allowed_programs: Vec<Pubkey>,
22 max_signatures: u64,
23 allowed_tokens: Vec<Pubkey>,
24 disallowed_accounts: Vec<Pubkey>,
25 _price_source: PriceSource,
26 fee_payer_policy: FeePayerPolicy,
27 allow_durable_transactions: bool,
28}
29
30impl TransactionValidator {
31 pub fn new(config: &Config, fee_payer_pubkey: Pubkey) -> Result<Self, KoraError> {
32 let config = &config.validation;
33
34 let allowed_programs = config
36 .allowed_programs
37 .iter()
38 .map(|addr| {
39 Pubkey::from_str(addr).map_err(|e| {
40 KoraError::InternalServerError(format!(
41 "Invalid program address in config: {e}"
42 ))
43 })
44 })
45 .collect::<Result<Vec<Pubkey>, KoraError>>()?;
46
47 Ok(Self {
48 fee_payer_pubkey,
49 max_allowed_lamports: config.max_allowed_lamports,
50 allowed_programs,
51 max_signatures: config.max_signatures,
52 _price_source: config.price_source.clone(),
53 allowed_tokens: config
54 .allowed_tokens
55 .iter()
56 .map(|addr| Pubkey::from_str(addr))
57 .collect::<Result<Vec<Pubkey>, _>>()
58 .map_err(|e| {
59 KoraError::InternalServerError(format!("Invalid allowed token address: {e}"))
60 })?,
61 disallowed_accounts: config
62 .disallowed_accounts
63 .iter()
64 .map(|addr| Pubkey::from_str(addr))
65 .collect::<Result<Vec<Pubkey>, _>>()
66 .map_err(|e| {
67 KoraError::InternalServerError(format!(
68 "Invalid disallowed account address: {e}"
69 ))
70 })?,
71 fee_payer_policy: config.fee_payer_policy.clone(),
72 allow_durable_transactions: config.allow_durable_transactions,
73 })
74 }
75
76 pub async fn fetch_and_validate_token_mint(
77 &self,
78 mint: &Pubkey,
79 config: &Config,
80 rpc_client: &RpcClient,
81 ) -> Result<Box<dyn TokenMint + Send + Sync>, KoraError> {
82 if !self.allowed_tokens.contains(mint) {
84 return Err(KoraError::InvalidTransaction(format!(
85 "Mint {mint} is not a valid token mint"
86 )));
87 }
88
89 let mint = TokenUtil::get_mint(config, rpc_client, mint).await?;
90
91 Ok(mint)
92 }
93
94 pub async fn validate_transaction(
98 &self,
99 config: &Config,
100 transaction_resolved: &mut VersionedTransactionResolved,
101 rpc_client: &RpcClient,
102 ) -> Result<(), KoraError> {
103 if transaction_resolved.all_instructions.is_empty() {
104 return Err(KoraError::InvalidTransaction(
105 "Transaction contains no instructions".to_string(),
106 ));
107 }
108
109 if transaction_resolved.all_account_keys.is_empty() {
110 return Err(KoraError::InvalidTransaction(
111 "Transaction contains no account keys".to_string(),
112 ));
113 }
114
115 self.validate_signatures(&transaction_resolved.transaction)?;
116
117 self.validate_programs(transaction_resolved)?;
118 self.validate_transfer_amounts(config, transaction_resolved, rpc_client).await?;
119 self.validate_disallowed_accounts(transaction_resolved)?;
120 self.validate_fee_payer_usage(transaction_resolved)?;
121
122 Ok(())
123 }
124
125 pub fn validate_lamport_fee(&self, fee: u64) -> Result<(), KoraError> {
126 if fee > self.max_allowed_lamports {
127 return Err(KoraError::InvalidTransaction(format!(
128 "Fee {} exceeds maximum allowed {}",
129 fee, self.max_allowed_lamports
130 )));
131 }
132 Ok(())
133 }
134
135 fn validate_signatures(&self, transaction: &VersionedTransaction) -> Result<(), KoraError> {
136 if transaction.signatures.len() > self.max_signatures as usize {
137 return Err(KoraError::InvalidTransaction(format!(
138 "Too many signatures: {} > {}",
139 transaction.signatures.len(),
140 self.max_signatures
141 )));
142 }
143
144 if transaction.signatures.is_empty() {
145 return Err(KoraError::InvalidTransaction("No signatures found".to_string()));
146 }
147
148 Ok(())
149 }
150
151 fn validate_programs(
152 &self,
153 transaction_resolved: &VersionedTransactionResolved,
154 ) -> Result<(), KoraError> {
155 for instruction in &transaction_resolved.all_instructions {
156 if !self.allowed_programs.contains(&instruction.program_id) {
157 return Err(KoraError::InvalidTransaction(format!(
158 "Program {} is not in the allowed list",
159 instruction.program_id
160 )));
161 }
162 }
163 Ok(())
164 }
165
166 fn validate_fee_payer_usage(
167 &self,
168 transaction_resolved: &mut VersionedTransactionResolved,
169 ) -> Result<(), KoraError> {
170 let system_instructions = transaction_resolved.get_or_parse_system_instructions()?;
171
172 if !self.allow_durable_transactions
174 && system_instructions
175 .contains_key(&ParsedSystemInstructionType::SystemAdvanceNonceAccount)
176 {
177 return Err(KoraError::InvalidTransaction(
178 "Durable transactions (nonce-based) are not allowed".to_string(),
179 ));
180 }
181
182 validate_system!(self, system_instructions, SystemTransfer,
184 ParsedSystemInstructionData::SystemTransfer { sender, .. } => sender,
185 self.fee_payer_policy.system.allow_transfer, "System Transfer");
186
187 validate_system!(self, system_instructions, SystemAssign,
188 ParsedSystemInstructionData::SystemAssign { authority } => authority,
189 self.fee_payer_policy.system.allow_assign, "System Assign");
190
191 validate_system!(self, system_instructions, SystemAllocate,
192 ParsedSystemInstructionData::SystemAllocate { account } => account,
193 self.fee_payer_policy.system.allow_allocate, "System Allocate");
194
195 validate_system!(self, system_instructions, SystemCreateAccount,
196 ParsedSystemInstructionData::SystemCreateAccount { payer, .. } => payer,
197 self.fee_payer_policy.system.allow_create_account, "System Create Account");
198
199 validate_system!(self, system_instructions, SystemInitializeNonceAccount,
200 ParsedSystemInstructionData::SystemInitializeNonceAccount { nonce_authority, .. } => nonce_authority,
201 self.fee_payer_policy.system.nonce.allow_initialize, "System Initialize Nonce Account");
202
203 validate_system!(self, system_instructions, SystemAdvanceNonceAccount,
204 ParsedSystemInstructionData::SystemAdvanceNonceAccount { nonce_authority, .. } => nonce_authority,
205 self.fee_payer_policy.system.nonce.allow_advance, "System Advance Nonce Account");
206
207 validate_system!(self, system_instructions, SystemAuthorizeNonceAccount,
208 ParsedSystemInstructionData::SystemAuthorizeNonceAccount { nonce_authority, .. } => nonce_authority,
209 self.fee_payer_policy.system.nonce.allow_authorize, "System Authorize Nonce Account");
210
211 validate_system!(self, system_instructions, SystemWithdrawNonceAccount,
214 ParsedSystemInstructionData::SystemWithdrawNonceAccount { nonce_authority, .. } => nonce_authority,
215 self.fee_payer_policy.system.nonce.allow_withdraw, "System Withdraw Nonce Account");
216
217 let spl_instructions = transaction_resolved.get_or_parse_spl_instructions()?;
219
220 validate_spl!(self, spl_instructions, SplTokenTransfer,
221 ParsedSPLInstructionData::SplTokenTransfer { owner, is_2022, .. } => { owner, is_2022 },
222 self.fee_payer_policy.spl_token.allow_transfer,
223 self.fee_payer_policy.token_2022.allow_transfer,
224 "SPL Token Transfer", "Token2022 Token Transfer");
225
226 validate_spl!(self, spl_instructions, SplTokenApprove,
227 ParsedSPLInstructionData::SplTokenApprove { owner, is_2022, .. } => { owner, is_2022 },
228 self.fee_payer_policy.spl_token.allow_approve,
229 self.fee_payer_policy.token_2022.allow_approve,
230 "SPL Token Approve", "Token2022 Token Approve");
231
232 validate_spl!(self, spl_instructions, SplTokenBurn,
233 ParsedSPLInstructionData::SplTokenBurn { owner, is_2022 } => { owner, is_2022 },
234 self.fee_payer_policy.spl_token.allow_burn,
235 self.fee_payer_policy.token_2022.allow_burn,
236 "SPL Token Burn", "Token2022 Token Burn");
237
238 validate_spl!(self, spl_instructions, SplTokenCloseAccount,
239 ParsedSPLInstructionData::SplTokenCloseAccount { owner, is_2022 } => { owner, is_2022 },
240 self.fee_payer_policy.spl_token.allow_close_account,
241 self.fee_payer_policy.token_2022.allow_close_account,
242 "SPL Token Close Account", "Token2022 Token Close Account");
243
244 validate_spl!(self, spl_instructions, SplTokenRevoke,
245 ParsedSPLInstructionData::SplTokenRevoke { owner, is_2022 } => { owner, is_2022 },
246 self.fee_payer_policy.spl_token.allow_revoke,
247 self.fee_payer_policy.token_2022.allow_revoke,
248 "SPL Token Revoke", "Token2022 Token Revoke");
249
250 validate_spl!(self, spl_instructions, SplTokenSetAuthority,
251 ParsedSPLInstructionData::SplTokenSetAuthority { authority, is_2022 } => { authority, is_2022 },
252 self.fee_payer_policy.spl_token.allow_set_authority,
253 self.fee_payer_policy.token_2022.allow_set_authority,
254 "SPL Token SetAuthority", "Token2022 Token SetAuthority");
255
256 validate_spl!(self, spl_instructions, SplTokenMintTo,
257 ParsedSPLInstructionData::SplTokenMintTo { mint_authority, is_2022 } => { mint_authority, is_2022 },
258 self.fee_payer_policy.spl_token.allow_mint_to,
259 self.fee_payer_policy.token_2022.allow_mint_to,
260 "SPL Token MintTo", "Token2022 Token MintTo");
261
262 validate_spl!(self, spl_instructions, SplTokenInitializeMint,
263 ParsedSPLInstructionData::SplTokenInitializeMint { mint_authority, is_2022 } => { mint_authority, is_2022 },
264 self.fee_payer_policy.spl_token.allow_initialize_mint,
265 self.fee_payer_policy.token_2022.allow_initialize_mint,
266 "SPL Token InitializeMint", "Token2022 Token InitializeMint");
267
268 validate_spl!(self, spl_instructions, SplTokenInitializeAccount,
269 ParsedSPLInstructionData::SplTokenInitializeAccount { owner, is_2022 } => { owner, is_2022 },
270 self.fee_payer_policy.spl_token.allow_initialize_account,
271 self.fee_payer_policy.token_2022.allow_initialize_account,
272 "SPL Token InitializeAccount", "Token2022 Token InitializeAccount");
273
274 validate_spl_multisig!(self, spl_instructions, SplTokenInitializeMultisig,
275 ParsedSPLInstructionData::SplTokenInitializeMultisig { signers, is_2022 } => { signers, is_2022 },
276 self.fee_payer_policy.spl_token.allow_initialize_multisig,
277 self.fee_payer_policy.token_2022.allow_initialize_multisig,
278 "SPL Token InitializeMultisig", "Token2022 Token InitializeMultisig");
279
280 validate_spl!(self, spl_instructions, SplTokenFreezeAccount,
281 ParsedSPLInstructionData::SplTokenFreezeAccount { freeze_authority, is_2022 } => { freeze_authority, is_2022 },
282 self.fee_payer_policy.spl_token.allow_freeze_account,
283 self.fee_payer_policy.token_2022.allow_freeze_account,
284 "SPL Token FreezeAccount", "Token2022 Token FreezeAccount");
285
286 validate_spl!(self, spl_instructions, SplTokenThawAccount,
287 ParsedSPLInstructionData::SplTokenThawAccount { freeze_authority, is_2022 } => { freeze_authority, is_2022 },
288 self.fee_payer_policy.spl_token.allow_thaw_account,
289 self.fee_payer_policy.token_2022.allow_thaw_account,
290 "SPL Token ThawAccount", "Token2022 Token ThawAccount");
291
292 Ok(())
293 }
294
295 async fn validate_transfer_amounts(
296 &self,
297 config: &Config,
298 transaction_resolved: &mut VersionedTransactionResolved,
299 rpc_client: &RpcClient,
300 ) -> Result<(), KoraError> {
301 let total_outflow =
302 self.calculate_total_outflow(config, transaction_resolved, rpc_client).await?;
303
304 if total_outflow > self.max_allowed_lamports {
305 return Err(KoraError::InvalidTransaction(format!(
306 "Total transfer amount {} exceeds maximum allowed {}",
307 total_outflow, self.max_allowed_lamports
308 )));
309 }
310
311 Ok(())
312 }
313
314 fn validate_disallowed_accounts(
315 &self,
316 transaction_resolved: &VersionedTransactionResolved,
317 ) -> Result<(), KoraError> {
318 for instruction in &transaction_resolved.all_instructions {
319 if self.disallowed_accounts.contains(&instruction.program_id) {
320 return Err(KoraError::InvalidTransaction(format!(
321 "Program {} is disallowed",
322 instruction.program_id
323 )));
324 }
325
326 for account_index in instruction.accounts.iter() {
327 if self.disallowed_accounts.contains(&account_index.pubkey) {
328 return Err(KoraError::InvalidTransaction(format!(
329 "Account {} is disallowed",
330 account_index.pubkey
331 )));
332 }
333 }
334 }
335 Ok(())
336 }
337
338 pub fn is_disallowed_account(&self, account: &Pubkey) -> bool {
339 self.disallowed_accounts.contains(account)
340 }
341
342 async fn calculate_total_outflow(
343 &self,
344 config: &Config,
345 transaction_resolved: &mut VersionedTransactionResolved,
346 rpc_client: &RpcClient,
347 ) -> Result<u64, KoraError> {
348 FeeConfigUtil::calculate_fee_payer_outflow(
349 &self.fee_payer_pubkey,
350 transaction_resolved,
351 rpc_client,
352 config,
353 )
354 .await
355 }
356
357 pub async fn validate_token_payment(
358 config: &Config,
359 transaction_resolved: &mut VersionedTransactionResolved,
360 required_lamports: u64,
361 rpc_client: &RpcClient,
362 expected_payment_destination: &Pubkey,
363 ) -> Result<(), KoraError> {
364 if TokenUtil::verify_token_payment(
365 config,
366 transaction_resolved,
367 rpc_client,
368 required_lamports,
369 expected_payment_destination,
370 None,
371 )
372 .await?
373 {
374 return Ok(());
375 }
376
377 Err(KoraError::InvalidTransaction(format!(
378 "Insufficient token payment. Required {required_lamports} lamports"
379 )))
380 }
381
382 pub fn validate_strict_pricing_with_fee(
383 config: &Config,
384 fee_calculation: &TotalFeeCalculation,
385 ) -> Result<(), KoraError> {
386 if !matches!(&config.validation.price.model, PriceModel::Fixed { strict: true, .. }) {
387 return Ok(());
388 }
389
390 let fixed_price_lamports = fee_calculation.total_fee_lamports;
391 let total_fee_lamports = fee_calculation.get_total_fee_lamports()?;
392
393 if fixed_price_lamports < total_fee_lamports {
394 log::error!(
395 "Strict pricing violation: fixed_price_lamports={} < total_fee_lamports={}",
396 fixed_price_lamports,
397 total_fee_lamports
398 );
399 return Err(KoraError::ValidationError(format!(
400 "Strict pricing violation: total fee ({} lamports) exceeds fixed price ({} lamports)",
401 total_fee_lamports,
402 fixed_price_lamports
403 )));
404 }
405
406 Ok(())
407 }
408}
409
410#[cfg(test)]
411mod tests {
412 use crate::{
413 config::{Config, FeePayerPolicy},
414 state::{get_config, update_config},
415 tests::{
416 account_mock::{MintAccountMockBuilder, TokenAccountMockBuilder},
417 config_mock::{mock_state::setup_config_mock, ConfigMockBuilder},
418 rpc_mock::RpcMockBuilder,
419 },
420 transaction::TransactionUtil,
421 };
422 use serial_test::serial;
423
424 use super::*;
425 use solana_message::{Message, VersionedMessage};
426 use solana_sdk::instruction::Instruction;
427 use solana_system_interface::{
428 instruction::{
429 assign, create_account, create_account_with_seed, transfer, transfer_with_seed,
430 },
431 program::ID as SYSTEM_PROGRAM_ID,
432 };
433
434 fn setup_both_configs(config: Config) {
435 drop(setup_config_mock(config.clone()));
436 update_config(config).unwrap();
437 }
438
439 fn setup_default_config() {
441 let config = ConfigMockBuilder::new()
442 .with_price_source(PriceSource::Mock)
443 .with_allowed_programs(vec![SYSTEM_PROGRAM_ID.to_string()])
444 .with_max_allowed_lamports(1_000_000)
445 .with_fee_payer_policy(FeePayerPolicy::default())
446 .build();
447 setup_both_configs(config);
448 }
449
450 fn setup_config_with_policy(policy: FeePayerPolicy) {
451 let config = ConfigMockBuilder::new()
452 .with_price_source(PriceSource::Mock)
453 .with_allowed_programs(vec![SYSTEM_PROGRAM_ID.to_string()])
454 .with_max_allowed_lamports(1_000_000)
455 .with_fee_payer_policy(policy)
456 .build();
457 setup_both_configs(config);
458 }
459
460 fn setup_spl_config_with_policy(policy: FeePayerPolicy) {
461 let config = ConfigMockBuilder::new()
462 .with_price_source(PriceSource::Mock)
463 .with_allowed_programs(vec![spl_token_interface::id().to_string()])
464 .with_max_allowed_lamports(1_000_000)
465 .with_fee_payer_policy(policy)
466 .build();
467 setup_both_configs(config);
468 }
469
470 fn setup_token2022_config_with_policy(policy: FeePayerPolicy) {
471 let config = ConfigMockBuilder::new()
472 .with_price_source(PriceSource::Mock)
473 .with_allowed_programs(vec![spl_token_2022_interface::id().to_string()])
474 .with_max_allowed_lamports(1_000_000)
475 .with_fee_payer_policy(policy)
476 .build();
477 setup_both_configs(config);
478 }
479
480 #[tokio::test]
481 #[serial]
482 async fn test_validate_transaction() {
483 let fee_payer = Pubkey::new_unique();
484 setup_default_config();
485 let rpc_client = RpcMockBuilder::new().build();
486
487 let config = get_config().unwrap();
488 let validator = TransactionValidator::new(config, fee_payer).unwrap();
489
490 let recipient = Pubkey::new_unique();
491 let sender = Pubkey::new_unique();
492 let instruction = transfer(&sender, &recipient, 100_000);
493 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
494 let mut transaction =
495 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
496
497 assert!(validator
498 .validate_transaction(config, &mut transaction, &rpc_client)
499 .await
500 .is_ok());
501 }
502
503 #[tokio::test]
504 #[serial]
505 async fn test_transfer_amount_limits() {
506 let fee_payer = Pubkey::new_unique();
507 setup_default_config();
508 let rpc_client = RpcMockBuilder::new().build();
509
510 let config = get_config().unwrap();
511 let validator = TransactionValidator::new(config, fee_payer).unwrap();
512 let sender = Pubkey::new_unique();
513 let recipient = Pubkey::new_unique();
514
515 let instruction = transfer(&sender, &recipient, 2_000_000);
517 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
518 let mut transaction =
519 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
520
521 assert!(validator
522 .validate_transaction(config, &mut transaction, &rpc_client)
523 .await
524 .is_ok());
525
526 let instructions =
528 vec![transfer(&sender, &recipient, 500_000), transfer(&sender, &recipient, 500_000)];
529 let message = VersionedMessage::Legacy(Message::new(&instructions, Some(&fee_payer)));
530 let mut transaction =
531 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
532 assert!(validator
533 .validate_transaction(config, &mut transaction, &rpc_client)
534 .await
535 .is_ok());
536 }
537
538 #[tokio::test]
539 #[serial]
540 async fn test_validate_programs() {
541 let fee_payer = Pubkey::new_unique();
542 setup_default_config();
543 let rpc_client = RpcMockBuilder::new().build();
544
545 let config = get_config().unwrap();
546 let validator = TransactionValidator::new(config, fee_payer).unwrap();
547 let sender = Pubkey::new_unique();
548 let recipient = Pubkey::new_unique();
549
550 let instruction = transfer(&sender, &recipient, 1000);
552 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
553 let mut transaction =
554 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
555 assert!(validator
556 .validate_transaction(config, &mut transaction, &rpc_client)
557 .await
558 .is_ok());
559
560 let fake_program = Pubkey::new_unique();
562 let instruction = Instruction::new_with_bincode(
564 fake_program,
565 &[0u8],
566 vec![], );
568 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
569 let mut transaction =
570 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
571 assert!(validator
572 .validate_transaction(config, &mut transaction, &rpc_client)
573 .await
574 .is_err());
575 }
576
577 #[tokio::test]
578 #[serial]
579 async fn test_validate_signatures() {
580 let fee_payer = Pubkey::new_unique();
581 let config = ConfigMockBuilder::new()
582 .with_price_source(PriceSource::Mock)
583 .with_allowed_programs(vec![SYSTEM_PROGRAM_ID.to_string()])
584 .with_max_allowed_lamports(1_000_000)
585 .with_max_signatures(2)
586 .with_fee_payer_policy(FeePayerPolicy::default())
587 .build();
588 update_config(config).unwrap();
589
590 let rpc_client = RpcMockBuilder::new().build();
591 let config = get_config().unwrap();
592 let validator = TransactionValidator::new(config, fee_payer).unwrap();
593 let sender = Pubkey::new_unique();
594 let recipient = Pubkey::new_unique();
595
596 let instructions = vec![
598 transfer(&sender, &recipient, 1000),
599 transfer(&sender, &recipient, 1000),
600 transfer(&sender, &recipient, 1000),
601 ];
602 let message = VersionedMessage::Legacy(Message::new(&instructions, Some(&fee_payer)));
603 let mut transaction =
604 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
605 transaction.transaction.signatures = vec![Default::default(); 3]; assert!(validator
607 .validate_transaction(config, &mut transaction, &rpc_client)
608 .await
609 .is_err());
610 }
611
612 #[tokio::test]
613 #[serial]
614 async fn test_sign_and_send_transaction_mode() {
615 let fee_payer = Pubkey::new_unique();
616 setup_default_config();
617 let rpc_client = RpcMockBuilder::new().build();
618
619 let config = get_config().unwrap();
620 let validator = TransactionValidator::new(config, fee_payer).unwrap();
621 let sender = Pubkey::new_unique();
622 let recipient = Pubkey::new_unique();
623
624 let instruction = transfer(&sender, &recipient, 1000);
626 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
627 let mut transaction =
628 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
629 assert!(validator
630 .validate_transaction(config, &mut transaction, &rpc_client)
631 .await
632 .is_ok());
633
634 let instruction = transfer(&sender, &recipient, 1000);
636 let message = VersionedMessage::Legacy(Message::new(&[instruction], None)); let mut transaction =
638 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
639 assert!(validator
640 .validate_transaction(config, &mut transaction, &rpc_client)
641 .await
642 .is_ok());
643 }
644
645 #[tokio::test]
646 #[serial]
647 async fn test_empty_transaction() {
648 let fee_payer = Pubkey::new_unique();
649 setup_default_config();
650 let rpc_client = RpcMockBuilder::new().build();
651
652 let config = get_config().unwrap();
653 let validator = TransactionValidator::new(config, fee_payer).unwrap();
654
655 let message = VersionedMessage::Legacy(Message::new(&[], Some(&fee_payer)));
657 let mut transaction =
658 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
659 assert!(validator
660 .validate_transaction(config, &mut transaction, &rpc_client)
661 .await
662 .is_err());
663 }
664
665 #[tokio::test]
666 #[serial]
667 async fn test_disallowed_accounts() {
668 let fee_payer = Pubkey::new_unique();
669 let config = ConfigMockBuilder::new()
670 .with_price_source(PriceSource::Mock)
671 .with_allowed_programs(vec![SYSTEM_PROGRAM_ID.to_string()])
672 .with_max_allowed_lamports(1_000_000)
673 .with_disallowed_accounts(vec![
674 "hndXZGK45hCxfBYvxejAXzCfCujoqkNf7rk4sTB8pek".to_string()
675 ])
676 .with_fee_payer_policy(FeePayerPolicy::default())
677 .build();
678 update_config(config).unwrap();
679
680 let rpc_client = RpcMockBuilder::new().build();
681 let config = get_config().unwrap();
682 let validator = TransactionValidator::new(config, fee_payer).unwrap();
683 let instruction = transfer(
684 &Pubkey::from_str("hndXZGK45hCxfBYvxejAXzCfCujoqkNf7rk4sTB8pek").unwrap(),
685 &fee_payer,
686 1000,
687 );
688 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
689 let mut transaction =
690 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
691 assert!(validator
692 .validate_transaction(config, &mut transaction, &rpc_client)
693 .await
694 .is_err());
695 }
696
697 #[tokio::test]
698 #[serial]
699 async fn test_fee_payer_policy_sol_transfers() {
700 let fee_payer = Pubkey::new_unique();
701 let recipient = Pubkey::new_unique();
702
703 let rpc_client = RpcMockBuilder::new().build();
705 let mut policy = FeePayerPolicy::default();
706 policy.system.allow_transfer = true;
707 setup_config_with_policy(policy);
708
709 let config = get_config().unwrap();
710 let validator = TransactionValidator::new(config, fee_payer).unwrap();
711
712 let instruction = transfer(&fee_payer, &recipient, 1000);
713
714 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
715 let mut transaction =
716 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
717 assert!(validator
718 .validate_transaction(config, &mut transaction, &rpc_client)
719 .await
720 .is_ok());
721
722 let rpc_client = RpcMockBuilder::new().build();
724 let mut policy = FeePayerPolicy::default();
725 policy.system.allow_transfer = false;
726 setup_config_with_policy(policy);
727
728 let config = get_config().unwrap();
729 let validator = TransactionValidator::new(config, fee_payer).unwrap();
730
731 let instruction = transfer(&fee_payer, &recipient, 1000);
732 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
733 let mut transaction =
734 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
735 assert!(validator
736 .validate_transaction(config, &mut transaction, &rpc_client)
737 .await
738 .is_err());
739 }
740
741 #[tokio::test]
742 #[serial]
743 async fn test_fee_payer_policy_assign() {
744 let fee_payer = Pubkey::new_unique();
745 let new_owner = Pubkey::new_unique();
746
747 let rpc_client = RpcMockBuilder::new().build();
750
751 let mut policy = FeePayerPolicy::default();
752 policy.system.allow_assign = true;
753 setup_config_with_policy(policy);
754
755 let config = get_config().unwrap();
756 let validator = TransactionValidator::new(config, fee_payer).unwrap();
757
758 let instruction = assign(&fee_payer, &new_owner);
759 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
760 let mut transaction =
761 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
762 assert!(validator
763 .validate_transaction(config, &mut transaction, &rpc_client)
764 .await
765 .is_ok());
766
767 let rpc_client = RpcMockBuilder::new().build();
770
771 let mut policy = FeePayerPolicy::default();
772 policy.system.allow_assign = false;
773 setup_config_with_policy(policy);
774
775 let config = get_config().unwrap();
776 let validator = TransactionValidator::new(config, fee_payer).unwrap();
777
778 let instruction = assign(&fee_payer, &new_owner);
779 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
780 let mut transaction =
781 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
782 assert!(validator
783 .validate_transaction(config, &mut transaction, &rpc_client)
784 .await
785 .is_err());
786 }
787
788 #[tokio::test]
789 #[serial]
790 async fn test_fee_payer_policy_spl_transfers() {
791 let fee_payer = Pubkey::new_unique();
792
793 let fee_payer_token_account = Pubkey::new_unique();
794 let recipient_token_account = Pubkey::new_unique();
795 let mint = Pubkey::new_unique();
796
797 let source_token_account =
798 TokenAccountMockBuilder::new().with_mint(&mint).with_owner(&fee_payer).build();
799 let mint_account = MintAccountMockBuilder::new().with_decimals(6).build();
800
801 let rpc_client = RpcMockBuilder::new()
803 .build_with_sequential_accounts(vec![&source_token_account, &mint_account]);
804
805 let mut policy = FeePayerPolicy::default();
806 policy.spl_token.allow_transfer = true;
807 setup_spl_config_with_policy(policy);
808
809 let config = get_config().unwrap();
810 let validator = TransactionValidator::new(config, fee_payer).unwrap();
811
812 let transfer_ix = spl_token_interface::instruction::transfer(
813 &spl_token_interface::id(),
814 &fee_payer_token_account,
815 &recipient_token_account,
816 &fee_payer, &[],
818 1000,
819 )
820 .unwrap();
821
822 let message = VersionedMessage::Legacy(Message::new(&[transfer_ix], Some(&fee_payer)));
823 let mut transaction =
824 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
825 assert!(validator
826 .validate_transaction(config, &mut transaction, &rpc_client)
827 .await
828 .is_ok());
829
830 let rpc_client = RpcMockBuilder::new()
832 .build_with_sequential_accounts(vec![&source_token_account, &mint_account]);
833
834 let mut policy = FeePayerPolicy::default();
835 policy.spl_token.allow_transfer = false;
836 setup_spl_config_with_policy(policy);
837
838 let config = get_config().unwrap();
839 let validator = TransactionValidator::new(config, fee_payer).unwrap();
840
841 let transfer_ix = spl_token_interface::instruction::transfer(
842 &spl_token_interface::id(),
843 &fee_payer_token_account,
844 &recipient_token_account,
845 &fee_payer, &[],
847 1000,
848 )
849 .unwrap();
850
851 let message = VersionedMessage::Legacy(Message::new(&[transfer_ix], Some(&fee_payer)));
852 let mut transaction =
853 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
854 assert!(validator
855 .validate_transaction(config, &mut transaction, &rpc_client)
856 .await
857 .is_err());
858
859 let rpc_client = RpcMockBuilder::new().build();
861 let other_signer = Pubkey::new_unique();
862 let transfer_ix = spl_token_interface::instruction::transfer(
863 &spl_token_interface::id(),
864 &fee_payer_token_account,
865 &recipient_token_account,
866 &other_signer, &[],
868 1000,
869 )
870 .unwrap();
871
872 let message = VersionedMessage::Legacy(Message::new(&[transfer_ix], Some(&fee_payer)));
873 let mut transaction =
874 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
875 assert!(validator
876 .validate_transaction(config, &mut transaction, &rpc_client)
877 .await
878 .is_ok());
879 }
880
881 #[tokio::test]
882 #[serial]
883 async fn test_fee_payer_policy_token2022_transfers() {
884 let fee_payer = Pubkey::new_unique();
885
886 let fee_payer_token_account = Pubkey::new_unique();
887 let recipient_token_account = Pubkey::new_unique();
888 let mint = Pubkey::new_unique();
889
890 let rpc_client = RpcMockBuilder::new()
892 .with_mint_account(2) .build();
894 let mut policy = FeePayerPolicy::default();
896 policy.token_2022.allow_transfer = true;
897 setup_token2022_config_with_policy(policy);
898
899 let config = get_config().unwrap();
900 let validator = TransactionValidator::new(config, fee_payer).unwrap();
901
902 let transfer_ix = spl_token_2022_interface::instruction::transfer_checked(
903 &spl_token_2022_interface::id(),
904 &fee_payer_token_account,
905 &mint,
906 &recipient_token_account,
907 &fee_payer, &[],
909 1,
910 2,
911 )
912 .unwrap();
913
914 let message = VersionedMessage::Legacy(Message::new(&[transfer_ix], Some(&fee_payer)));
915 let mut transaction =
916 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
917 assert!(validator
918 .validate_transaction(config, &mut transaction, &rpc_client)
919 .await
920 .is_ok());
921
922 let rpc_client = RpcMockBuilder::new()
924 .with_mint_account(2) .build();
926 let mut policy = FeePayerPolicy::default();
927 policy.token_2022.allow_transfer = false;
928 setup_token2022_config_with_policy(policy);
929
930 let config = get_config().unwrap();
931 let validator = TransactionValidator::new(config, fee_payer).unwrap();
932
933 let transfer_ix = spl_token_2022_interface::instruction::transfer_checked(
934 &spl_token_2022_interface::id(),
935 &fee_payer_token_account,
936 &mint,
937 &recipient_token_account,
938 &fee_payer, &[],
940 1000,
941 2,
942 )
943 .unwrap();
944
945 let message = VersionedMessage::Legacy(Message::new(&[transfer_ix], Some(&fee_payer)));
946 let mut transaction =
947 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
948
949 assert!(validator
951 .validate_transaction(config, &mut transaction, &rpc_client)
952 .await
953 .is_err());
954
955 let other_signer = Pubkey::new_unique();
957 let transfer_ix = spl_token_2022_interface::instruction::transfer_checked(
958 &spl_token_2022_interface::id(),
959 &fee_payer_token_account,
960 &mint,
961 &recipient_token_account,
962 &other_signer, &[],
964 1000,
965 2,
966 )
967 .unwrap();
968
969 let message = VersionedMessage::Legacy(Message::new(&[transfer_ix], Some(&fee_payer)));
970 let mut transaction =
971 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
972
973 assert!(validator
975 .validate_transaction(config, &mut transaction, &rpc_client)
976 .await
977 .is_ok());
978 }
979
980 #[tokio::test]
981 #[serial]
982 async fn test_calculate_total_outflow() {
983 let fee_payer = Pubkey::new_unique();
984 let config = ConfigMockBuilder::new()
985 .with_price_source(PriceSource::Mock)
986 .with_allowed_programs(vec![SYSTEM_PROGRAM_ID.to_string()])
987 .with_max_allowed_lamports(10_000_000)
988 .with_fee_payer_policy(FeePayerPolicy::default())
989 .build();
990 update_config(config).unwrap();
991
992 let rpc_client = RpcMockBuilder::new().build();
993 let config = get_config().unwrap();
994 let validator = TransactionValidator::new(config, fee_payer).unwrap();
995
996 let recipient = Pubkey::new_unique();
998 let transfer_instruction = transfer(&fee_payer, &recipient, 100_000);
999 let message =
1000 VersionedMessage::Legacy(Message::new(&[transfer_instruction], Some(&fee_payer)));
1001 let mut transaction =
1002 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1003 let outflow =
1004 validator.calculate_total_outflow(config, &mut transaction, &rpc_client).await.unwrap();
1005 assert_eq!(outflow, 100_000, "Transfer from fee payer should add to outflow");
1006
1007 let sender = Pubkey::new_unique();
1009 let transfer_instruction = transfer(&sender, &fee_payer, 50_000);
1010 let message =
1011 VersionedMessage::Legacy(Message::new(&[transfer_instruction], Some(&fee_payer)));
1012 let mut transaction =
1013 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1014
1015 let outflow =
1016 validator.calculate_total_outflow(config, &mut transaction, &rpc_client).await.unwrap();
1017 assert_eq!(outflow, 0, "Transfer to fee payer should subtract from outflow"); let new_account = Pubkey::new_unique();
1021 let create_instruction = create_account(
1022 &fee_payer,
1023 &new_account,
1024 200_000, 100, &SYSTEM_PROGRAM_ID,
1027 );
1028 let message =
1029 VersionedMessage::Legacy(Message::new(&[create_instruction], Some(&fee_payer)));
1030 let mut transaction =
1031 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1032 let outflow =
1033 validator.calculate_total_outflow(config, &mut transaction, &rpc_client).await.unwrap();
1034 assert_eq!(outflow, 200_000, "CreateAccount funded by fee payer should add to outflow");
1035
1036 let create_with_seed_instruction = create_account_with_seed(
1038 &fee_payer,
1039 &new_account,
1040 &fee_payer,
1041 "test_seed",
1042 300_000, 100, &SYSTEM_PROGRAM_ID,
1045 );
1046 let message = VersionedMessage::Legacy(Message::new(
1047 &[create_with_seed_instruction],
1048 Some(&fee_payer),
1049 ));
1050 let mut transaction =
1051 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1052 let outflow =
1053 validator.calculate_total_outflow(config, &mut transaction, &rpc_client).await.unwrap();
1054 assert_eq!(
1055 outflow, 300_000,
1056 "CreateAccountWithSeed funded by fee payer should add to outflow"
1057 );
1058
1059 let transfer_with_seed_instruction = transfer_with_seed(
1061 &fee_payer,
1062 &fee_payer,
1063 "test_seed".to_string(),
1064 &SYSTEM_PROGRAM_ID,
1065 &recipient,
1066 150_000,
1067 );
1068 let message = VersionedMessage::Legacy(Message::new(
1069 &[transfer_with_seed_instruction],
1070 Some(&fee_payer),
1071 ));
1072 let mut transaction =
1073 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1074 let outflow =
1075 validator.calculate_total_outflow(config, &mut transaction, &rpc_client).await.unwrap();
1076 assert_eq!(outflow, 150_000, "TransferWithSeed from fee payer should add to outflow");
1077
1078 let instructions = vec![
1080 transfer(&fee_payer, &recipient, 100_000), transfer(&sender, &fee_payer, 30_000), create_account(&fee_payer, &new_account, 50_000, 100, &SYSTEM_PROGRAM_ID), ];
1084 let message = VersionedMessage::Legacy(Message::new(&instructions, Some(&fee_payer)));
1085 let mut transaction =
1086 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1087 let outflow =
1088 validator.calculate_total_outflow(config, &mut transaction, &rpc_client).await.unwrap();
1089 assert_eq!(
1090 outflow, 120_000,
1091 "Multiple instructions should sum correctly: 100000 - 30000 + 50000 = 120000"
1092 );
1093
1094 let other_sender = Pubkey::new_unique();
1096 let transfer_instruction = transfer(&other_sender, &recipient, 500_000);
1097 let message =
1098 VersionedMessage::Legacy(Message::new(&[transfer_instruction], Some(&fee_payer)));
1099 let mut transaction =
1100 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1101 let outflow =
1102 validator.calculate_total_outflow(config, &mut transaction, &rpc_client).await.unwrap();
1103 assert_eq!(outflow, 0, "Transfer from other account should not affect outflow");
1104
1105 let other_funder = Pubkey::new_unique();
1107 let create_instruction =
1108 create_account(&other_funder, &new_account, 1_000_000, 100, &SYSTEM_PROGRAM_ID);
1109 let message =
1110 VersionedMessage::Legacy(Message::new(&[create_instruction], Some(&fee_payer)));
1111 let mut transaction =
1112 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1113 let outflow =
1114 validator.calculate_total_outflow(config, &mut transaction, &rpc_client).await.unwrap();
1115 assert_eq!(outflow, 0, "CreateAccount funded by other account should not affect outflow");
1116 }
1117
1118 #[tokio::test]
1119 #[serial]
1120 async fn test_fee_payer_policy_burn() {
1121 let fee_payer = Pubkey::new_unique();
1122 let fee_payer_token_account = Pubkey::new_unique();
1123 let mint = Pubkey::new_unique();
1124
1125 let rpc_client = RpcMockBuilder::new().build();
1128 let mut policy = FeePayerPolicy::default();
1129 policy.spl_token.allow_burn = true;
1130 setup_spl_config_with_policy(policy);
1131
1132 let config = get_config().unwrap();
1133 let validator = TransactionValidator::new(config, fee_payer).unwrap();
1134
1135 let burn_ix = spl_token_interface::instruction::burn(
1136 &spl_token_interface::id(),
1137 &fee_payer_token_account,
1138 &mint,
1139 &fee_payer,
1140 &[],
1141 1000,
1142 )
1143 .unwrap();
1144
1145 let message = VersionedMessage::Legacy(Message::new(&[burn_ix], Some(&fee_payer)));
1146 let mut transaction =
1147 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1148 assert!(validator
1150 .validate_transaction(config, &mut transaction, &rpc_client)
1151 .await
1152 .is_ok());
1153
1154 let rpc_client = RpcMockBuilder::new().build();
1157 let mut policy = FeePayerPolicy::default();
1158 policy.spl_token.allow_burn = false;
1159 setup_spl_config_with_policy(policy);
1160
1161 let config = get_config().unwrap();
1162 let validator = TransactionValidator::new(config, fee_payer).unwrap();
1163
1164 let burn_ix = spl_token_interface::instruction::burn(
1165 &spl_token_interface::id(),
1166 &fee_payer_token_account,
1167 &mint,
1168 &fee_payer,
1169 &[],
1170 1000,
1171 )
1172 .unwrap();
1173
1174 let message = VersionedMessage::Legacy(Message::new(&[burn_ix], Some(&fee_payer)));
1175 let mut transaction =
1176 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1177
1178 assert!(validator
1180 .validate_transaction(config, &mut transaction, &rpc_client)
1181 .await
1182 .is_err());
1183
1184 let burn_checked_ix = spl_token_interface::instruction::burn_checked(
1186 &spl_token_interface::id(),
1187 &fee_payer_token_account,
1188 &mint,
1189 &fee_payer,
1190 &[],
1191 1000,
1192 2,
1193 )
1194 .unwrap();
1195
1196 let message = VersionedMessage::Legacy(Message::new(&[burn_checked_ix], Some(&fee_payer)));
1197 let mut transaction =
1198 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1199
1200 assert!(validator
1202 .validate_transaction(config, &mut transaction, &rpc_client)
1203 .await
1204 .is_err());
1205 }
1206
1207 #[tokio::test]
1208 #[serial]
1209 async fn test_fee_payer_policy_close_account() {
1210 let fee_payer = Pubkey::new_unique();
1211 let fee_payer_token_account = Pubkey::new_unique();
1212 let destination = Pubkey::new_unique();
1213
1214 let rpc_client = RpcMockBuilder::new().build();
1217 let mut policy = FeePayerPolicy::default();
1218 policy.spl_token.allow_close_account = true;
1219 setup_spl_config_with_policy(policy);
1220
1221 let config = get_config().unwrap();
1222 let validator = TransactionValidator::new(config, fee_payer).unwrap();
1223
1224 let close_ix = spl_token_interface::instruction::close_account(
1225 &spl_token_interface::id(),
1226 &fee_payer_token_account,
1227 &destination,
1228 &fee_payer,
1229 &[],
1230 )
1231 .unwrap();
1232
1233 let message = VersionedMessage::Legacy(Message::new(&[close_ix], Some(&fee_payer)));
1234 let mut transaction =
1235 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1236 assert!(validator
1238 .validate_transaction(config, &mut transaction, &rpc_client)
1239 .await
1240 .is_ok());
1241
1242 let rpc_client = RpcMockBuilder::new().build();
1244 let mut policy = FeePayerPolicy::default();
1245 policy.spl_token.allow_close_account = false;
1246 setup_spl_config_with_policy(policy);
1247
1248 let config = get_config().unwrap();
1249 let validator = TransactionValidator::new(config, fee_payer).unwrap();
1250
1251 let close_ix = spl_token_interface::instruction::close_account(
1252 &spl_token_interface::id(),
1253 &fee_payer_token_account,
1254 &destination,
1255 &fee_payer,
1256 &[],
1257 )
1258 .unwrap();
1259
1260 let message = VersionedMessage::Legacy(Message::new(&[close_ix], Some(&fee_payer)));
1261 let mut transaction =
1262 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1263
1264 assert!(validator
1266 .validate_transaction(config, &mut transaction, &rpc_client)
1267 .await
1268 .is_err());
1269 }
1270
1271 #[tokio::test]
1272 #[serial]
1273 async fn test_fee_payer_policy_approve() {
1274 let fee_payer = Pubkey::new_unique();
1275 let fee_payer_token_account = Pubkey::new_unique();
1276 let delegate = Pubkey::new_unique();
1277
1278 let rpc_client = RpcMockBuilder::new().build();
1281 let mut policy = FeePayerPolicy::default();
1282 policy.spl_token.allow_approve = true;
1283 setup_spl_config_with_policy(policy);
1284
1285 let config = get_config().unwrap();
1286 let validator = TransactionValidator::new(config, fee_payer).unwrap();
1287
1288 let approve_ix = spl_token_interface::instruction::approve(
1289 &spl_token_interface::id(),
1290 &fee_payer_token_account,
1291 &delegate,
1292 &fee_payer,
1293 &[],
1294 1000,
1295 )
1296 .unwrap();
1297
1298 let message = VersionedMessage::Legacy(Message::new(&[approve_ix], Some(&fee_payer)));
1299 let mut transaction =
1300 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1301 assert!(validator
1303 .validate_transaction(config, &mut transaction, &rpc_client)
1304 .await
1305 .is_ok());
1306
1307 let rpc_client = RpcMockBuilder::new().build();
1309 let mut policy = FeePayerPolicy::default();
1310 policy.spl_token.allow_approve = false;
1311 setup_spl_config_with_policy(policy);
1312
1313 let config = get_config().unwrap();
1314 let validator = TransactionValidator::new(config, fee_payer).unwrap();
1315
1316 let approve_ix = spl_token_interface::instruction::approve(
1317 &spl_token_interface::id(),
1318 &fee_payer_token_account,
1319 &delegate,
1320 &fee_payer,
1321 &[],
1322 1000,
1323 )
1324 .unwrap();
1325
1326 let message = VersionedMessage::Legacy(Message::new(&[approve_ix], Some(&fee_payer)));
1327 let mut transaction =
1328 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1329
1330 assert!(validator
1332 .validate_transaction(config, &mut transaction, &rpc_client)
1333 .await
1334 .is_err());
1335
1336 let mint = Pubkey::new_unique();
1338 let approve_checked_ix = spl_token_interface::instruction::approve_checked(
1339 &spl_token_interface::id(),
1340 &fee_payer_token_account,
1341 &mint,
1342 &delegate,
1343 &fee_payer,
1344 &[],
1345 1000,
1346 2,
1347 )
1348 .unwrap();
1349
1350 let message =
1351 VersionedMessage::Legacy(Message::new(&[approve_checked_ix], Some(&fee_payer)));
1352 let mut transaction =
1353 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1354
1355 assert!(validator
1357 .validate_transaction(config, &mut transaction, &rpc_client)
1358 .await
1359 .is_err());
1360 }
1361
1362 #[tokio::test]
1363 #[serial]
1364 async fn test_fee_payer_policy_token2022_burn() {
1365 let fee_payer = Pubkey::new_unique();
1366 let fee_payer_token_account = Pubkey::new_unique();
1367 let mint = Pubkey::new_unique();
1368
1369 let rpc_client = RpcMockBuilder::new().build();
1372 let mut policy = FeePayerPolicy::default();
1373 policy.token_2022.allow_burn = false;
1374 setup_token2022_config_with_policy(policy);
1375
1376 let config = get_config().unwrap();
1377 let validator = TransactionValidator::new(config, fee_payer).unwrap();
1378
1379 let burn_ix = spl_token_2022_interface::instruction::burn(
1380 &spl_token_2022_interface::id(),
1381 &fee_payer_token_account,
1382 &mint,
1383 &fee_payer,
1384 &[],
1385 1000,
1386 )
1387 .unwrap();
1388
1389 let message = VersionedMessage::Legacy(Message::new(&[burn_ix], Some(&fee_payer)));
1390 let mut transaction =
1391 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1392 assert!(validator
1394 .validate_transaction(config, &mut transaction, &rpc_client)
1395 .await
1396 .is_err());
1397 }
1398
1399 #[tokio::test]
1400 #[serial]
1401 async fn test_fee_payer_policy_token2022_close_account() {
1402 let fee_payer = Pubkey::new_unique();
1403 let fee_payer_token_account = Pubkey::new_unique();
1404 let destination = Pubkey::new_unique();
1405
1406 let rpc_client = RpcMockBuilder::new().build();
1409 let mut policy = FeePayerPolicy::default();
1410 policy.token_2022.allow_close_account = false;
1411 setup_token2022_config_with_policy(policy);
1412
1413 let config = get_config().unwrap();
1414 let validator = TransactionValidator::new(config, fee_payer).unwrap();
1415
1416 let close_ix = spl_token_2022_interface::instruction::close_account(
1417 &spl_token_2022_interface::id(),
1418 &fee_payer_token_account,
1419 &destination,
1420 &fee_payer,
1421 &[],
1422 )
1423 .unwrap();
1424
1425 let message = VersionedMessage::Legacy(Message::new(&[close_ix], Some(&fee_payer)));
1426 let mut transaction =
1427 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1428 assert!(validator
1430 .validate_transaction(config, &mut transaction, &rpc_client)
1431 .await
1432 .is_err());
1433 }
1434
1435 #[tokio::test]
1436 #[serial]
1437 async fn test_fee_payer_policy_token2022_approve() {
1438 let fee_payer = Pubkey::new_unique();
1439 let fee_payer_token_account = Pubkey::new_unique();
1440 let delegate = Pubkey::new_unique();
1441
1442 let rpc_client = RpcMockBuilder::new().build();
1445 let mut policy = FeePayerPolicy::default();
1446 policy.token_2022.allow_approve = true;
1447 setup_token2022_config_with_policy(policy);
1448
1449 let config = get_config().unwrap();
1450 let validator = TransactionValidator::new(config, fee_payer).unwrap();
1451
1452 let approve_ix = spl_token_2022_interface::instruction::approve(
1453 &spl_token_2022_interface::id(),
1454 &fee_payer_token_account,
1455 &delegate,
1456 &fee_payer,
1457 &[],
1458 1000,
1459 )
1460 .unwrap();
1461
1462 let message = VersionedMessage::Legacy(Message::new(&[approve_ix], Some(&fee_payer)));
1463 let mut transaction =
1464 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1465 assert!(validator
1467 .validate_transaction(config, &mut transaction, &rpc_client)
1468 .await
1469 .is_ok());
1470
1471 let rpc_client = RpcMockBuilder::new().build();
1474 let mut policy = FeePayerPolicy::default();
1475 policy.token_2022.allow_approve = false;
1476 setup_token2022_config_with_policy(policy);
1477
1478 let config = get_config().unwrap();
1479 let validator = TransactionValidator::new(config, fee_payer).unwrap();
1480
1481 let approve_ix = spl_token_2022_interface::instruction::approve(
1482 &spl_token_2022_interface::id(),
1483 &fee_payer_token_account,
1484 &delegate,
1485 &fee_payer,
1486 &[],
1487 1000,
1488 )
1489 .unwrap();
1490
1491 let message = VersionedMessage::Legacy(Message::new(&[approve_ix], Some(&fee_payer)));
1492 let mut transaction =
1493 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1494
1495 assert!(validator
1497 .validate_transaction(config, &mut transaction, &rpc_client)
1498 .await
1499 .is_err());
1500
1501 let mint = Pubkey::new_unique();
1503 let approve_checked_ix = spl_token_2022_interface::instruction::approve_checked(
1504 &spl_token_2022_interface::id(),
1505 &fee_payer_token_account,
1506 &mint,
1507 &delegate,
1508 &fee_payer,
1509 &[],
1510 1000,
1511 2,
1512 )
1513 .unwrap();
1514
1515 let message =
1516 VersionedMessage::Legacy(Message::new(&[approve_checked_ix], Some(&fee_payer)));
1517 let mut transaction =
1518 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1519
1520 assert!(validator
1522 .validate_transaction(config, &mut transaction, &rpc_client)
1523 .await
1524 .is_err());
1525 }
1526
1527 #[tokio::test]
1528 #[serial]
1529 async fn test_fee_payer_policy_create_account() {
1530 use solana_system_interface::instruction::create_account;
1531
1532 let fee_payer = Pubkey::new_unique();
1533 let new_account = Pubkey::new_unique();
1534 let owner = Pubkey::new_unique();
1535
1536 let rpc_client = RpcMockBuilder::new().build();
1538 let mut policy = FeePayerPolicy::default();
1539 policy.system.allow_create_account = true;
1540 setup_config_with_policy(policy);
1541
1542 let config = get_config().unwrap();
1543 let validator = TransactionValidator::new(config, fee_payer).unwrap();
1544 let instruction = create_account(&fee_payer, &new_account, 1000, 100, &owner);
1545 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1546 let mut transaction =
1547 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1548 assert!(validator
1549 .validate_transaction(config, &mut transaction, &rpc_client)
1550 .await
1551 .is_ok());
1552
1553 let rpc_client = RpcMockBuilder::new().build();
1555 let mut policy = FeePayerPolicy::default();
1556 policy.system.allow_create_account = false;
1557 setup_config_with_policy(policy);
1558
1559 let config = get_config().unwrap();
1560 let validator = TransactionValidator::new(config, fee_payer).unwrap();
1561 let instruction = create_account(&fee_payer, &new_account, 1000, 100, &owner);
1562 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1563 let mut transaction =
1564 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1565 assert!(validator
1566 .validate_transaction(config, &mut transaction, &rpc_client)
1567 .await
1568 .is_err());
1569 }
1570
1571 #[tokio::test]
1572 #[serial]
1573 async fn test_fee_payer_policy_allocate() {
1574 use solana_system_interface::instruction::allocate;
1575
1576 let fee_payer = Pubkey::new_unique();
1577
1578 let rpc_client = RpcMockBuilder::new().build();
1580 let mut policy = FeePayerPolicy::default();
1581 policy.system.allow_allocate = true;
1582 setup_config_with_policy(policy);
1583
1584 let config = get_config().unwrap();
1585 let validator = TransactionValidator::new(config, fee_payer).unwrap();
1586 let instruction = allocate(&fee_payer, 100);
1587 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1588 let mut transaction =
1589 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1590 assert!(validator
1591 .validate_transaction(config, &mut transaction, &rpc_client)
1592 .await
1593 .is_ok());
1594
1595 let rpc_client = RpcMockBuilder::new().build();
1597 let mut policy = FeePayerPolicy::default();
1598 policy.system.allow_allocate = false;
1599 setup_config_with_policy(policy);
1600
1601 let config = get_config().unwrap();
1602 let validator = TransactionValidator::new(config, fee_payer).unwrap();
1603 let instruction = allocate(&fee_payer, 100);
1604 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1605 let mut transaction =
1606 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1607 assert!(validator
1608 .validate_transaction(config, &mut transaction, &rpc_client)
1609 .await
1610 .is_err());
1611 }
1612
1613 #[tokio::test]
1614 #[serial]
1615 async fn test_fee_payer_policy_nonce_initialize() {
1616 use solana_system_interface::instruction::create_nonce_account;
1617
1618 let fee_payer = Pubkey::new_unique();
1619 let nonce_account = Pubkey::new_unique();
1620
1621 let rpc_client = RpcMockBuilder::new().build();
1623 let mut policy = FeePayerPolicy::default();
1624 policy.system.nonce.allow_initialize = true;
1625 setup_config_with_policy(policy);
1626
1627 let config = get_config().unwrap();
1628 let validator = TransactionValidator::new(config, fee_payer).unwrap();
1629 let instructions = create_nonce_account(&fee_payer, &nonce_account, &fee_payer, 1_000_000);
1630 let message =
1632 VersionedMessage::Legacy(Message::new(&[instructions[1].clone()], Some(&fee_payer)));
1633 let mut transaction =
1634 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1635 assert!(validator
1636 .validate_transaction(config, &mut transaction, &rpc_client)
1637 .await
1638 .is_ok());
1639
1640 let rpc_client = RpcMockBuilder::new().build();
1642 let mut policy = FeePayerPolicy::default();
1643 policy.system.nonce.allow_initialize = false;
1644 setup_config_with_policy(policy);
1645
1646 let config = get_config().unwrap();
1647 let validator = TransactionValidator::new(config, fee_payer).unwrap();
1648 let instructions = create_nonce_account(&fee_payer, &nonce_account, &fee_payer, 1_000_000);
1649 let message =
1650 VersionedMessage::Legacy(Message::new(&[instructions[1].clone()], Some(&fee_payer)));
1651 let mut transaction =
1652 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1653 assert!(validator
1654 .validate_transaction(config, &mut transaction, &rpc_client)
1655 .await
1656 .is_err());
1657 }
1658
1659 #[tokio::test]
1660 #[serial]
1661 async fn test_fee_payer_policy_nonce_advance() {
1662 use solana_system_interface::instruction::advance_nonce_account;
1663
1664 let fee_payer = Pubkey::new_unique();
1665 let nonce_account = Pubkey::new_unique();
1666
1667 let rpc_client = RpcMockBuilder::new().build();
1669 let mut policy = FeePayerPolicy::default();
1670 policy.system.nonce.allow_advance = true;
1671 let config = ConfigMockBuilder::new()
1672 .with_price_source(PriceSource::Mock)
1673 .with_allowed_programs(vec![SYSTEM_PROGRAM_ID.to_string()])
1674 .with_max_allowed_lamports(1_000_000)
1675 .with_fee_payer_policy(policy)
1676 .with_allow_durable_transactions(true)
1677 .build();
1678 update_config(config).unwrap();
1679
1680 let config = get_config().unwrap();
1681 let validator = TransactionValidator::new(config, fee_payer).unwrap();
1682 let instruction = advance_nonce_account(&nonce_account, &fee_payer);
1683 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1684 let mut transaction =
1685 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1686 assert!(validator
1687 .validate_transaction(config, &mut transaction, &rpc_client)
1688 .await
1689 .is_ok());
1690
1691 let rpc_client = RpcMockBuilder::new().build();
1693 let mut policy = FeePayerPolicy::default();
1694 policy.system.nonce.allow_advance = false;
1695 let config = ConfigMockBuilder::new()
1696 .with_price_source(PriceSource::Mock)
1697 .with_allowed_programs(vec![SYSTEM_PROGRAM_ID.to_string()])
1698 .with_max_allowed_lamports(1_000_000)
1699 .with_fee_payer_policy(policy)
1700 .with_allow_durable_transactions(true)
1701 .build();
1702 update_config(config).unwrap();
1703
1704 let config = get_config().unwrap();
1705 let validator = TransactionValidator::new(config, fee_payer).unwrap();
1706 let instruction = advance_nonce_account(&nonce_account, &fee_payer);
1707 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1708 let mut transaction =
1709 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1710 assert!(validator
1711 .validate_transaction(config, &mut transaction, &rpc_client)
1712 .await
1713 .is_err());
1714 }
1715
1716 #[tokio::test]
1717 #[serial]
1718 async fn test_fee_payer_policy_nonce_withdraw() {
1719 use solana_system_interface::instruction::withdraw_nonce_account;
1720
1721 let fee_payer = Pubkey::new_unique();
1722 let nonce_account = Pubkey::new_unique();
1723 let recipient = Pubkey::new_unique();
1724
1725 let rpc_client = RpcMockBuilder::new().build();
1727 let mut policy = FeePayerPolicy::default();
1728 policy.system.nonce.allow_withdraw = true;
1729 setup_config_with_policy(policy);
1730
1731 let config = get_config().unwrap();
1732 let validator = TransactionValidator::new(config, fee_payer).unwrap();
1733 let instruction = withdraw_nonce_account(&nonce_account, &fee_payer, &recipient, 1000);
1734 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1735 let mut transaction =
1736 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1737 assert!(validator
1738 .validate_transaction(config, &mut transaction, &rpc_client)
1739 .await
1740 .is_ok());
1741
1742 let rpc_client = RpcMockBuilder::new().build();
1744 let mut policy = FeePayerPolicy::default();
1745 policy.system.nonce.allow_withdraw = false;
1746 setup_config_with_policy(policy);
1747
1748 let config = get_config().unwrap();
1749 let validator = TransactionValidator::new(config, fee_payer).unwrap();
1750 let instruction = withdraw_nonce_account(&nonce_account, &fee_payer, &recipient, 1000);
1751 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1752 let mut transaction =
1753 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1754 assert!(validator
1755 .validate_transaction(config, &mut transaction, &rpc_client)
1756 .await
1757 .is_err());
1758 }
1759
1760 #[tokio::test]
1761 #[serial]
1762 async fn test_fee_payer_policy_nonce_authorize() {
1763 use solana_system_interface::instruction::authorize_nonce_account;
1764
1765 let fee_payer = Pubkey::new_unique();
1766 let nonce_account = Pubkey::new_unique();
1767 let new_authority = Pubkey::new_unique();
1768
1769 let rpc_client = RpcMockBuilder::new().build();
1771 let mut policy = FeePayerPolicy::default();
1772 policy.system.nonce.allow_authorize = true;
1773 setup_config_with_policy(policy);
1774
1775 let config = get_config().unwrap();
1776 let validator = TransactionValidator::new(config, fee_payer).unwrap();
1777 let instruction = authorize_nonce_account(&nonce_account, &fee_payer, &new_authority);
1778 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1779 let mut transaction =
1780 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1781 assert!(validator
1782 .validate_transaction(config, &mut transaction, &rpc_client)
1783 .await
1784 .is_ok());
1785
1786 let rpc_client = RpcMockBuilder::new().build();
1788 let mut policy = FeePayerPolicy::default();
1789 policy.system.nonce.allow_authorize = false;
1790 setup_config_with_policy(policy);
1791
1792 let config = get_config().unwrap();
1793 let validator = TransactionValidator::new(config, fee_payer).unwrap();
1794 let instruction = authorize_nonce_account(&nonce_account, &fee_payer, &new_authority);
1795 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1796 let mut transaction =
1797 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1798 assert!(validator
1799 .validate_transaction(config, &mut transaction, &rpc_client)
1800 .await
1801 .is_err());
1802 }
1803
1804 #[test]
1805 #[serial]
1806 fn test_strict_pricing_total_exceeds_fixed() {
1807 let mut config = ConfigMockBuilder::new().build();
1808 config.validation.price.model = PriceModel::Fixed {
1809 amount: 5000,
1810 token: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string(),
1811 strict: true,
1812 };
1813 let _ = update_config(config);
1814
1815 let fee_calc = TotalFeeCalculation::new(5000, 3000, 2000, 5000, 0, 0);
1817
1818 let config = get_config().unwrap();
1819 let result = TransactionValidator::validate_strict_pricing_with_fee(config, &fee_calc);
1820
1821 assert!(result.is_err());
1822 if let Err(KoraError::ValidationError(msg)) = result {
1823 assert!(msg.contains("Strict pricing violation"));
1824 assert!(msg.contains("exceeds fixed price"));
1825 } else {
1826 panic!("Expected ValidationError");
1827 }
1828 }
1829
1830 #[test]
1831 #[serial]
1832 fn test_strict_pricing_total_within_fixed() {
1833 let mut config = ConfigMockBuilder::new().build();
1834 config.validation.price.model = PriceModel::Fixed {
1835 amount: 5000,
1836 token: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string(),
1837 strict: true,
1838 };
1839 let _ = update_config(config);
1840
1841 let fee_calc = TotalFeeCalculation::new(5000, 1000, 1000, 1000, 0, 0);
1843
1844 let config = get_config().unwrap();
1845 let result = TransactionValidator::validate_strict_pricing_with_fee(config, &fee_calc);
1846
1847 assert!(result.is_ok());
1848 }
1849
1850 #[test]
1851 #[serial]
1852 fn test_strict_pricing_disabled() {
1853 let mut config = ConfigMockBuilder::new().build();
1854 config.validation.price.model = PriceModel::Fixed {
1855 amount: 5000,
1856 token: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string(),
1857 strict: false, };
1859 let _ = update_config(config);
1860
1861 let fee_calc = TotalFeeCalculation::new(5000, 10000, 0, 0, 0, 0);
1862
1863 let config = get_config().unwrap();
1864 let result = TransactionValidator::validate_strict_pricing_with_fee(config, &fee_calc);
1865
1866 assert!(result.is_ok(), "Should pass when strict=false");
1867 }
1868
1869 #[test]
1870 #[serial]
1871 fn test_strict_pricing_with_margin_pricing() {
1872 use crate::{
1873 fee::price::PriceModel, state::update_config, tests::config_mock::ConfigMockBuilder,
1874 };
1875
1876 let mut config = ConfigMockBuilder::new().build();
1877 config.validation.price.model = PriceModel::Margin { margin: 0.1 };
1878 let _ = update_config(config);
1879
1880 let fee_calc = TotalFeeCalculation::new(5000, 10000, 0, 0, 0, 0);
1881
1882 let config = get_config().unwrap();
1883 let result = TransactionValidator::validate_strict_pricing_with_fee(config, &fee_calc);
1884
1885 assert!(result.is_ok());
1886 }
1887
1888 #[test]
1889 #[serial]
1890 fn test_strict_pricing_exact_match() {
1891 use crate::{
1892 fee::price::PriceModel, state::update_config, tests::config_mock::ConfigMockBuilder,
1893 };
1894
1895 let mut config = ConfigMockBuilder::new().build();
1896 config.validation.price.model = PriceModel::Fixed {
1897 amount: 5000,
1898 token: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string(),
1899 strict: true,
1900 };
1901 let _ = update_config(config);
1902
1903 let fee_calc = TotalFeeCalculation::new(5000, 2000, 1000, 2000, 0, 0);
1905
1906 let config = get_config().unwrap();
1907 let result = TransactionValidator::validate_strict_pricing_with_fee(config, &fee_calc);
1908
1909 assert!(result.is_ok(), "Should pass when total equals fixed price");
1910 }
1911
1912 #[tokio::test]
1913 #[serial]
1914 async fn test_durable_transaction_rejected_by_default() {
1915 use solana_system_interface::instruction::advance_nonce_account;
1916
1917 let fee_payer = Pubkey::new_unique();
1918 let nonce_account = Pubkey::new_unique();
1919 let nonce_authority = Pubkey::new_unique(); setup_default_config();
1923 let rpc_client = RpcMockBuilder::new().build();
1924
1925 let config = get_config().unwrap();
1926 let validator = TransactionValidator::new(config, fee_payer).unwrap();
1927
1928 let instruction = advance_nonce_account(&nonce_account, &nonce_authority);
1930 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1931 let mut transaction =
1932 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1933
1934 let result = validator.validate_transaction(config, &mut transaction, &rpc_client).await;
1935 assert!(result.is_err());
1936 if let Err(KoraError::InvalidTransaction(msg)) = result {
1937 assert!(msg.contains("Durable transactions"));
1938 assert!(msg.contains("not allowed"));
1939 } else {
1940 panic!("Expected InvalidTransaction error");
1941 }
1942 }
1943
1944 #[tokio::test]
1945 #[serial]
1946 async fn test_durable_transaction_allowed_when_enabled() {
1947 use solana_system_interface::instruction::advance_nonce_account;
1948
1949 let fee_payer = Pubkey::new_unique();
1950 let nonce_account = Pubkey::new_unique();
1951 let nonce_authority = Pubkey::new_unique(); let mock_config = ConfigMockBuilder::new()
1955 .with_price_source(PriceSource::Mock)
1956 .with_allowed_programs(vec![SYSTEM_PROGRAM_ID.to_string()])
1957 .with_max_allowed_lamports(1_000_000)
1958 .with_fee_payer_policy(FeePayerPolicy::default())
1959 .with_allow_durable_transactions(true)
1960 .build();
1961 update_config(mock_config).unwrap();
1962
1963 let rpc_client = RpcMockBuilder::new().build();
1964 let config = get_config().unwrap();
1965 let validator = TransactionValidator::new(config, fee_payer).unwrap();
1966
1967 let instruction = advance_nonce_account(&nonce_account, &nonce_authority);
1969 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1970 let mut transaction =
1971 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1972
1973 assert!(validator
1975 .validate_transaction(config, &mut transaction, &rpc_client)
1976 .await
1977 .is_ok());
1978 }
1979
1980 #[tokio::test]
1981 #[serial]
1982 async fn test_non_durable_transaction_passes() {
1983 let fee_payer = Pubkey::new_unique();
1984 let sender = Pubkey::new_unique();
1985 let recipient = Pubkey::new_unique();
1986
1987 setup_default_config();
1989 let rpc_client = RpcMockBuilder::new().build();
1990
1991 let config = get_config().unwrap();
1992 let validator = TransactionValidator::new(config, fee_payer).unwrap();
1993
1994 let instruction = transfer(&sender, &recipient, 1000);
1996 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1997 let mut transaction =
1998 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1999
2000 assert!(validator
2002 .validate_transaction(config, &mut transaction, &rpc_client)
2003 .await
2004 .is_ok());
2005 }
2006
2007 #[tokio::test]
2008 #[serial]
2009 async fn test_fee_payer_policy_revoke() {
2010 let fee_payer = Pubkey::new_unique();
2011 let fee_payer_token_account = Pubkey::new_unique();
2012
2013 let rpc_client = RpcMockBuilder::new().build();
2015 let mut policy = FeePayerPolicy::default();
2016 policy.spl_token.allow_revoke = true;
2017 setup_spl_config_with_policy(policy);
2018
2019 let config = get_config().unwrap();
2020 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2021
2022 let revoke_ix = spl_token_interface::instruction::revoke(
2023 &spl_token_interface::id(),
2024 &fee_payer_token_account,
2025 &fee_payer,
2026 &[],
2027 )
2028 .unwrap();
2029
2030 let message = VersionedMessage::Legacy(Message::new(&[revoke_ix], Some(&fee_payer)));
2031 let mut transaction =
2032 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2033 assert!(validator
2034 .validate_transaction(config, &mut transaction, &rpc_client)
2035 .await
2036 .is_ok());
2037
2038 let rpc_client = RpcMockBuilder::new().build();
2040 let mut policy = FeePayerPolicy::default();
2041 policy.spl_token.allow_revoke = false;
2042 setup_spl_config_with_policy(policy);
2043
2044 let config = get_config().unwrap();
2045 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2046
2047 let revoke_ix = spl_token_interface::instruction::revoke(
2048 &spl_token_interface::id(),
2049 &fee_payer_token_account,
2050 &fee_payer,
2051 &[],
2052 )
2053 .unwrap();
2054
2055 let message = VersionedMessage::Legacy(Message::new(&[revoke_ix], Some(&fee_payer)));
2056 let mut transaction =
2057 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2058 let result = validator.validate_transaction(config, &mut transaction, &rpc_client).await;
2059 if let Err(KoraError::InvalidTransaction(msg)) = result {
2060 assert!(msg.contains("Fee payer cannot be used for"));
2061 } else {
2062 panic!("Expected InvalidTransaction error for revoke policy");
2063 }
2064 }
2065
2066 #[tokio::test]
2067 #[serial]
2068 async fn test_fee_payer_policy_token2022_revoke() {
2069 let fee_payer = Pubkey::new_unique();
2070 let fee_payer_token_account = Pubkey::new_unique();
2071
2072 let rpc_client = RpcMockBuilder::new().build();
2074 let mut policy = FeePayerPolicy::default();
2075 policy.token_2022.allow_revoke = true;
2076 setup_token2022_config_with_policy(policy);
2077
2078 let config = get_config().unwrap();
2079 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2080
2081 let revoke_ix = spl_token_2022_interface::instruction::revoke(
2082 &spl_token_2022_interface::id(),
2083 &fee_payer_token_account,
2084 &fee_payer,
2085 &[],
2086 )
2087 .unwrap();
2088
2089 let message = VersionedMessage::Legacy(Message::new(&[revoke_ix], Some(&fee_payer)));
2090 let mut transaction =
2091 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2092 assert!(validator
2093 .validate_transaction(config, &mut transaction, &rpc_client)
2094 .await
2095 .is_ok());
2096
2097 let rpc_client = RpcMockBuilder::new().build();
2099 let mut policy = FeePayerPolicy::default();
2100 policy.token_2022.allow_revoke = false;
2101 setup_token2022_config_with_policy(policy);
2102
2103 let config = get_config().unwrap();
2104 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2105
2106 let revoke_ix = spl_token_2022_interface::instruction::revoke(
2107 &spl_token_2022_interface::id(),
2108 &fee_payer_token_account,
2109 &fee_payer,
2110 &[],
2111 )
2112 .unwrap();
2113
2114 let message = VersionedMessage::Legacy(Message::new(&[revoke_ix], Some(&fee_payer)));
2115 let mut transaction =
2116 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2117 let result = validator.validate_transaction(config, &mut transaction, &rpc_client).await;
2118 if let Err(KoraError::InvalidTransaction(msg)) = result {
2119 assert!(msg.contains("Fee payer cannot be used for"));
2120 } else {
2121 panic!("Expected InvalidTransaction error for token2022_revoke policy");
2122 }
2123 }
2124
2125 #[tokio::test]
2126 #[serial]
2127 async fn test_fee_payer_policy_set_authority() {
2128 let fee_payer = Pubkey::new_unique();
2129 let fee_payer_token_account = Pubkey::new_unique();
2130 let new_authority = Pubkey::new_unique();
2131
2132 let rpc_client = RpcMockBuilder::new().build();
2134 let mut policy = FeePayerPolicy::default();
2135 policy.spl_token.allow_set_authority = true;
2136 setup_spl_config_with_policy(policy);
2137
2138 let config = get_config().unwrap();
2139 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2140
2141 let set_authority_ix = spl_token_interface::instruction::set_authority(
2142 &spl_token_interface::id(),
2143 &fee_payer_token_account,
2144 Some(&new_authority),
2145 spl_token_interface::instruction::AuthorityType::AccountOwner,
2146 &fee_payer,
2147 &[],
2148 )
2149 .unwrap();
2150
2151 let message = VersionedMessage::Legacy(Message::new(&[set_authority_ix], Some(&fee_payer)));
2152 let mut transaction =
2153 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2154 assert!(validator
2155 .validate_transaction(config, &mut transaction, &rpc_client)
2156 .await
2157 .is_ok());
2158
2159 let rpc_client = RpcMockBuilder::new().build();
2161 let mut policy = FeePayerPolicy::default();
2162 policy.spl_token.allow_set_authority = false;
2163 setup_spl_config_with_policy(policy);
2164
2165 let config = get_config().unwrap();
2166 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2167
2168 let set_authority_ix = spl_token_interface::instruction::set_authority(
2169 &spl_token_interface::id(),
2170 &fee_payer_token_account,
2171 Some(&new_authority),
2172 spl_token_interface::instruction::AuthorityType::AccountOwner,
2173 &fee_payer,
2174 &[],
2175 )
2176 .unwrap();
2177
2178 let message = VersionedMessage::Legacy(Message::new(&[set_authority_ix], Some(&fee_payer)));
2179 let mut transaction =
2180 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2181 let result = validator.validate_transaction(config, &mut transaction, &rpc_client).await;
2182 if let Err(KoraError::InvalidTransaction(msg)) = result {
2183 assert!(msg.contains("Fee payer cannot be used for"));
2184 } else {
2185 panic!("Expected InvalidTransaction error for set_authority policy");
2186 }
2187 }
2188
2189 #[tokio::test]
2190 #[serial]
2191 async fn test_fee_payer_policy_token2022_set_authority() {
2192 let fee_payer = Pubkey::new_unique();
2193 let fee_payer_token_account = Pubkey::new_unique();
2194 let new_authority = Pubkey::new_unique();
2195
2196 let rpc_client = RpcMockBuilder::new().build();
2198 let mut policy = FeePayerPolicy::default();
2199 policy.token_2022.allow_set_authority = true;
2200 setup_token2022_config_with_policy(policy);
2201
2202 let config = get_config().unwrap();
2203 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2204
2205 let set_authority_ix = spl_token_2022_interface::instruction::set_authority(
2206 &spl_token_2022_interface::id(),
2207 &fee_payer_token_account,
2208 Some(&new_authority),
2209 spl_token_2022_interface::instruction::AuthorityType::AccountOwner,
2210 &fee_payer,
2211 &[],
2212 )
2213 .unwrap();
2214
2215 let message = VersionedMessage::Legacy(Message::new(&[set_authority_ix], Some(&fee_payer)));
2216 let mut transaction =
2217 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2218 assert!(validator
2219 .validate_transaction(config, &mut transaction, &rpc_client)
2220 .await
2221 .is_ok());
2222
2223 let rpc_client = RpcMockBuilder::new().build();
2225 let mut policy = FeePayerPolicy::default();
2226 policy.token_2022.allow_set_authority = false;
2227 setup_token2022_config_with_policy(policy);
2228
2229 let config = get_config().unwrap();
2230 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2231
2232 let set_authority_ix = spl_token_2022_interface::instruction::set_authority(
2233 &spl_token_2022_interface::id(),
2234 &fee_payer_token_account,
2235 Some(&new_authority),
2236 spl_token_2022_interface::instruction::AuthorityType::AccountOwner,
2237 &fee_payer,
2238 &[],
2239 )
2240 .unwrap();
2241
2242 let message = VersionedMessage::Legacy(Message::new(&[set_authority_ix], Some(&fee_payer)));
2243 let mut transaction =
2244 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2245 let result = validator.validate_transaction(config, &mut transaction, &rpc_client).await;
2246 if let Err(KoraError::InvalidTransaction(msg)) = result {
2247 assert!(msg.contains("Fee payer cannot be used for"));
2248 } else {
2249 panic!("Expected InvalidTransaction error for token2022_set_authority policy");
2250 }
2251 }
2252
2253 #[tokio::test]
2254 #[serial]
2255 async fn test_fee_payer_policy_mint_to() {
2256 let fee_payer = Pubkey::new_unique();
2257 let mint = Pubkey::new_unique();
2258 let destination_token_account = Pubkey::new_unique();
2259
2260 let rpc_client = RpcMockBuilder::new().build();
2262 let mut policy = FeePayerPolicy::default();
2263 policy.spl_token.allow_mint_to = true;
2264 setup_spl_config_with_policy(policy);
2265
2266 let config = get_config().unwrap();
2267 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2268
2269 let mint_to_ix = spl_token_interface::instruction::mint_to(
2270 &spl_token_interface::id(),
2271 &mint,
2272 &destination_token_account,
2273 &fee_payer,
2274 &[],
2275 1000,
2276 )
2277 .unwrap();
2278
2279 let message = VersionedMessage::Legacy(Message::new(&[mint_to_ix], Some(&fee_payer)));
2280 let mut transaction =
2281 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2282 assert!(validator
2283 .validate_transaction(config, &mut transaction, &rpc_client)
2284 .await
2285 .is_ok());
2286
2287 let rpc_client = RpcMockBuilder::new().build();
2289 let mut policy = FeePayerPolicy::default();
2290 policy.spl_token.allow_mint_to = false;
2291 setup_spl_config_with_policy(policy);
2292
2293 let config = get_config().unwrap();
2294 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2295
2296 let mint_to_ix = spl_token_interface::instruction::mint_to(
2297 &spl_token_interface::id(),
2298 &mint,
2299 &destination_token_account,
2300 &fee_payer,
2301 &[],
2302 1000,
2303 )
2304 .unwrap();
2305
2306 let message = VersionedMessage::Legacy(Message::new(&[mint_to_ix], Some(&fee_payer)));
2307 let mut transaction =
2308 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2309 let result = validator.validate_transaction(config, &mut transaction, &rpc_client).await;
2310 if let Err(KoraError::InvalidTransaction(msg)) = result {
2311 assert!(msg.contains("Fee payer cannot be used for"));
2312 } else {
2313 panic!("Expected InvalidTransaction error for mint_to policy");
2314 }
2315 }
2316
2317 #[tokio::test]
2318 #[serial]
2319 async fn test_fee_payer_policy_token2022_mint_to() {
2320 let fee_payer = Pubkey::new_unique();
2321 let mint = Pubkey::new_unique();
2322 let destination_token_account = Pubkey::new_unique();
2323
2324 let rpc_client = RpcMockBuilder::new().build();
2326 let mut policy = FeePayerPolicy::default();
2327 policy.token_2022.allow_mint_to = true;
2328 setup_token2022_config_with_policy(policy);
2329
2330 let config = get_config().unwrap();
2331 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2332
2333 let mint_to_ix = spl_token_2022_interface::instruction::mint_to(
2334 &spl_token_2022_interface::id(),
2335 &mint,
2336 &destination_token_account,
2337 &fee_payer,
2338 &[],
2339 1000,
2340 )
2341 .unwrap();
2342
2343 let message = VersionedMessage::Legacy(Message::new(&[mint_to_ix], Some(&fee_payer)));
2344 let mut transaction =
2345 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2346 assert!(validator
2347 .validate_transaction(config, &mut transaction, &rpc_client)
2348 .await
2349 .is_ok());
2350
2351 let rpc_client = RpcMockBuilder::new().build();
2353 let mut policy = FeePayerPolicy::default();
2354 policy.token_2022.allow_mint_to = false;
2355 setup_token2022_config_with_policy(policy);
2356
2357 let config = get_config().unwrap();
2358 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2359
2360 let mint_to_ix = spl_token_2022_interface::instruction::mint_to(
2361 &spl_token_2022_interface::id(),
2362 &mint,
2363 &destination_token_account,
2364 &fee_payer,
2365 &[],
2366 1000,
2367 )
2368 .unwrap();
2369
2370 let message = VersionedMessage::Legacy(Message::new(&[mint_to_ix], Some(&fee_payer)));
2371 let mut transaction =
2372 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2373 let result = validator.validate_transaction(config, &mut transaction, &rpc_client).await;
2374 if let Err(KoraError::InvalidTransaction(msg)) = result {
2375 assert!(msg.contains("Fee payer cannot be used for"));
2376 } else {
2377 panic!("Expected InvalidTransaction error for token2022_mint_to policy");
2378 }
2379 }
2380
2381 #[tokio::test]
2382 #[serial]
2383 async fn test_fee_payer_policy_initialize_mint() {
2384 let fee_payer = Pubkey::new_unique();
2385 let mint_account = Pubkey::new_unique();
2386
2387 let rpc_client = RpcMockBuilder::new().build();
2389 let mut policy = FeePayerPolicy::default();
2390 policy.spl_token.allow_initialize_mint = true;
2391 setup_spl_config_with_policy(policy);
2392
2393 let config = get_config().unwrap();
2394 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2395
2396 let init_mint_ix = spl_token_interface::instruction::initialize_mint(
2398 &spl_token_interface::id(),
2399 &mint_account,
2400 &fee_payer,
2401 None,
2402 6,
2403 )
2404 .unwrap();
2405
2406 let message = VersionedMessage::Legacy(Message::new(&[init_mint_ix], Some(&fee_payer)));
2407 let mut transaction =
2408 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2409 assert!(validator
2410 .validate_transaction(config, &mut transaction, &rpc_client)
2411 .await
2412 .is_ok());
2413
2414 let rpc_client = RpcMockBuilder::new().build();
2416 let mut policy = FeePayerPolicy::default();
2417 policy.spl_token.allow_initialize_mint = false;
2418 setup_spl_config_with_policy(policy);
2419
2420 let config = get_config().unwrap();
2421 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2422
2423 let init_mint_ix = spl_token_interface::instruction::initialize_mint(
2424 &spl_token_interface::id(),
2425 &mint_account,
2426 &fee_payer,
2427 None,
2428 6,
2429 )
2430 .unwrap();
2431
2432 let message = VersionedMessage::Legacy(Message::new(&[init_mint_ix], Some(&fee_payer)));
2433 let mut transaction =
2434 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2435 let result = validator.validate_transaction(config, &mut transaction, &rpc_client).await;
2436 if let Err(KoraError::InvalidTransaction(msg)) = result {
2437 assert!(msg.contains("Fee payer cannot be used for"));
2438 } else {
2439 panic!("Expected InvalidTransaction error for initialize_mint policy");
2440 }
2441 }
2442
2443 #[tokio::test]
2444 #[serial]
2445 async fn test_fee_payer_policy_token2022_initialize_mint() {
2446 let fee_payer = Pubkey::new_unique();
2447 let mint_account = Pubkey::new_unique();
2448
2449 let rpc_client = RpcMockBuilder::new().build();
2451 let mut policy = FeePayerPolicy::default();
2452 policy.token_2022.allow_initialize_mint = true;
2453 setup_token2022_config_with_policy(policy);
2454
2455 let config = get_config().unwrap();
2456 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2457
2458 let init_mint_ix = spl_token_2022_interface::instruction::initialize_mint(
2459 &spl_token_2022_interface::id(),
2460 &mint_account,
2461 &fee_payer,
2462 None,
2463 6,
2464 )
2465 .unwrap();
2466
2467 let message = VersionedMessage::Legacy(Message::new(&[init_mint_ix], Some(&fee_payer)));
2468 let mut transaction =
2469 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2470 assert!(validator
2471 .validate_transaction(config, &mut transaction, &rpc_client)
2472 .await
2473 .is_ok());
2474
2475 let rpc_client = RpcMockBuilder::new().build();
2477 let mut policy = FeePayerPolicy::default();
2478 policy.token_2022.allow_initialize_mint = false;
2479 setup_token2022_config_with_policy(policy);
2480
2481 let config = get_config().unwrap();
2482 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2483
2484 let init_mint_ix = spl_token_2022_interface::instruction::initialize_mint(
2485 &spl_token_2022_interface::id(),
2486 &mint_account,
2487 &fee_payer,
2488 None,
2489 6,
2490 )
2491 .unwrap();
2492
2493 let message = VersionedMessage::Legacy(Message::new(&[init_mint_ix], Some(&fee_payer)));
2494 let mut transaction =
2495 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2496 let result = validator.validate_transaction(config, &mut transaction, &rpc_client).await;
2497 if let Err(KoraError::InvalidTransaction(msg)) = result {
2498 assert!(msg.contains("Fee payer cannot be used for"));
2499 } else {
2500 panic!("Expected InvalidTransaction error for token2022_initialize_mint policy");
2501 }
2502 }
2503
2504 #[tokio::test]
2505 #[serial]
2506 async fn test_fee_payer_policy_initialize_account() {
2507 let fee_payer = Pubkey::new_unique();
2508 let token_account = Pubkey::new_unique();
2509 let mint = Pubkey::new_unique();
2510
2511 let rpc_client = RpcMockBuilder::new().build();
2514 let mut policy = FeePayerPolicy::default();
2515 policy.spl_token.allow_initialize_account = true;
2516 setup_spl_config_with_policy(policy);
2517
2518 let config = get_config().unwrap();
2519 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2520
2521 let init_account_ix = spl_token_interface::instruction::initialize_account(
2522 &spl_token_interface::id(),
2523 &token_account,
2524 &mint,
2525 &fee_payer,
2526 )
2527 .unwrap();
2528
2529 let message = VersionedMessage::Legacy(Message::new(&[init_account_ix], Some(&fee_payer)));
2530 let mut transaction =
2531 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2532 assert!(validator
2533 .validate_transaction(config, &mut transaction, &rpc_client)
2534 .await
2535 .is_ok());
2536
2537 let rpc_client = RpcMockBuilder::new().build();
2539 let mut policy = FeePayerPolicy::default();
2540 policy.spl_token.allow_initialize_account = false;
2541 setup_spl_config_with_policy(policy);
2542
2543 let config = get_config().unwrap();
2544 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2545
2546 let init_account_ix = spl_token_interface::instruction::initialize_account(
2547 &spl_token_interface::id(),
2548 &token_account,
2549 &mint,
2550 &fee_payer,
2551 )
2552 .unwrap();
2553
2554 let message = VersionedMessage::Legacy(Message::new(&[init_account_ix], Some(&fee_payer)));
2555 let mut transaction =
2556 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2557 let result = validator.validate_transaction(config, &mut transaction, &rpc_client).await;
2558 if let Err(KoraError::InvalidTransaction(msg)) = result {
2559 assert!(msg.contains("Fee payer cannot be used for"));
2560 } else {
2561 panic!("Expected InvalidTransaction error for initialize_account policy");
2562 }
2563 }
2564
2565 #[tokio::test]
2566 #[serial]
2567 async fn test_fee_payer_policy_token2022_initialize_account() {
2568 let fee_payer = Pubkey::new_unique();
2569 let token_account = Pubkey::new_unique();
2570 let mint = Pubkey::new_unique();
2571
2572 let rpc_client = RpcMockBuilder::new().build();
2574 let mut policy = FeePayerPolicy::default();
2575 policy.token_2022.allow_initialize_account = true;
2576 setup_token2022_config_with_policy(policy);
2577
2578 let config = get_config().unwrap();
2579 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2580
2581 let init_account_ix = spl_token_2022_interface::instruction::initialize_account(
2582 &spl_token_2022_interface::id(),
2583 &token_account,
2584 &mint,
2585 &fee_payer,
2586 )
2587 .unwrap();
2588
2589 let message = VersionedMessage::Legacy(Message::new(&[init_account_ix], Some(&fee_payer)));
2590 let mut transaction =
2591 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2592 assert!(validator
2593 .validate_transaction(config, &mut transaction, &rpc_client)
2594 .await
2595 .is_ok());
2596
2597 let rpc_client = RpcMockBuilder::new().build();
2599 let mut policy = FeePayerPolicy::default();
2600 policy.token_2022.allow_initialize_account = false;
2601 setup_token2022_config_with_policy(policy);
2602
2603 let config = get_config().unwrap();
2604 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2605
2606 let init_account_ix = spl_token_2022_interface::instruction::initialize_account(
2607 &spl_token_2022_interface::id(),
2608 &token_account,
2609 &mint,
2610 &fee_payer,
2611 )
2612 .unwrap();
2613
2614 let message = VersionedMessage::Legacy(Message::new(&[init_account_ix], Some(&fee_payer)));
2615 let mut transaction =
2616 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2617 let result = validator.validate_transaction(config, &mut transaction, &rpc_client).await;
2618 if let Err(KoraError::InvalidTransaction(msg)) = result {
2619 assert!(msg.contains("Fee payer cannot be used for"));
2620 } else {
2621 panic!("Expected InvalidTransaction error for token2022_initialize_account policy");
2622 }
2623 }
2624
2625 #[tokio::test]
2626 #[serial]
2627 async fn test_fee_payer_policy_initialize_multisig() {
2628 let fee_payer = Pubkey::new_unique();
2629 let multisig_account = Pubkey::new_unique();
2630 let other_signer = Pubkey::new_unique();
2631
2632 let rpc_client = RpcMockBuilder::new().build();
2635 let mut policy = FeePayerPolicy::default();
2636 policy.spl_token.allow_initialize_multisig = true;
2637 setup_spl_config_with_policy(policy);
2638
2639 let config = get_config().unwrap();
2640 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2641
2642 let init_multisig_ix = spl_token_interface::instruction::initialize_multisig(
2643 &spl_token_interface::id(),
2644 &multisig_account,
2645 &[&fee_payer, &other_signer],
2646 2,
2647 )
2648 .unwrap();
2649
2650 let message = VersionedMessage::Legacy(Message::new(&[init_multisig_ix], Some(&fee_payer)));
2651 let mut transaction =
2652 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2653 assert!(validator
2654 .validate_transaction(config, &mut transaction, &rpc_client)
2655 .await
2656 .is_ok());
2657
2658 let rpc_client = RpcMockBuilder::new().build();
2660 let mut policy = FeePayerPolicy::default();
2661 policy.spl_token.allow_initialize_multisig = false;
2662 setup_spl_config_with_policy(policy);
2663
2664 let config = get_config().unwrap();
2665 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2666
2667 let init_multisig_ix = spl_token_interface::instruction::initialize_multisig(
2668 &spl_token_interface::id(),
2669 &multisig_account,
2670 &[&fee_payer, &other_signer],
2671 2,
2672 )
2673 .unwrap();
2674
2675 let message = VersionedMessage::Legacy(Message::new(&[init_multisig_ix], Some(&fee_payer)));
2676 let mut transaction =
2677 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2678 let result = validator.validate_transaction(config, &mut transaction, &rpc_client).await;
2679 if let Err(KoraError::InvalidTransaction(msg)) = result {
2680 assert!(msg.contains("Fee payer cannot be used for"));
2681 } else {
2682 panic!("Expected InvalidTransaction error for initialize_multisig policy");
2683 }
2684 }
2685
2686 #[tokio::test]
2687 #[serial]
2688 async fn test_fee_payer_policy_token2022_initialize_multisig() {
2689 let fee_payer = Pubkey::new_unique();
2690 let multisig_account = Pubkey::new_unique();
2691 let other_signer = Pubkey::new_unique();
2692
2693 let rpc_client = RpcMockBuilder::new().build();
2695 let mut policy = FeePayerPolicy::default();
2696 policy.token_2022.allow_initialize_multisig = true;
2697 setup_token2022_config_with_policy(policy);
2698
2699 let config = get_config().unwrap();
2700 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2701
2702 let init_multisig_ix = spl_token_2022_interface::instruction::initialize_multisig(
2703 &spl_token_2022_interface::id(),
2704 &multisig_account,
2705 &[&fee_payer, &other_signer],
2706 2,
2707 )
2708 .unwrap();
2709
2710 let message = VersionedMessage::Legacy(Message::new(&[init_multisig_ix], Some(&fee_payer)));
2711 let mut transaction =
2712 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2713 assert!(validator
2714 .validate_transaction(config, &mut transaction, &rpc_client)
2715 .await
2716 .is_ok());
2717
2718 let rpc_client = RpcMockBuilder::new().build();
2720 let mut policy = FeePayerPolicy::default();
2721 policy.token_2022.allow_initialize_multisig = false;
2722 setup_token2022_config_with_policy(policy);
2723
2724 let config = get_config().unwrap();
2725 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2726
2727 let init_multisig_ix = spl_token_2022_interface::instruction::initialize_multisig(
2728 &spl_token_2022_interface::id(),
2729 &multisig_account,
2730 &[&fee_payer, &other_signer],
2731 2,
2732 )
2733 .unwrap();
2734
2735 let message = VersionedMessage::Legacy(Message::new(&[init_multisig_ix], Some(&fee_payer)));
2736 let mut transaction =
2737 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2738 let result = validator.validate_transaction(config, &mut transaction, &rpc_client).await;
2739 if let Err(KoraError::InvalidTransaction(msg)) = result {
2740 assert!(msg.contains("Fee payer cannot be used for"));
2741 } else {
2742 panic!("Expected InvalidTransaction error for token2022_initialize_multisig policy");
2743 }
2744 }
2745
2746 #[tokio::test]
2747 #[serial]
2748 async fn test_fee_payer_policy_freeze_account() {
2749 let fee_payer = Pubkey::new_unique();
2750 let token_account = Pubkey::new_unique();
2751 let mint = Pubkey::new_unique();
2752
2753 let rpc_client = RpcMockBuilder::new().build();
2756 let mut policy = FeePayerPolicy::default();
2757 policy.spl_token.allow_freeze_account = true;
2758 setup_spl_config_with_policy(policy);
2759
2760 let config = get_config().unwrap();
2761 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2762
2763 let freeze_ix = spl_token_interface::instruction::freeze_account(
2764 &spl_token_interface::id(),
2765 &token_account,
2766 &mint,
2767 &fee_payer,
2768 &[],
2769 )
2770 .unwrap();
2771
2772 let message = VersionedMessage::Legacy(Message::new(&[freeze_ix], Some(&fee_payer)));
2773 let mut transaction =
2774 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2775 assert!(validator
2776 .validate_transaction(config, &mut transaction, &rpc_client)
2777 .await
2778 .is_ok());
2779
2780 let rpc_client = RpcMockBuilder::new().build();
2782 let mut policy = FeePayerPolicy::default();
2783 policy.spl_token.allow_freeze_account = false;
2784 setup_spl_config_with_policy(policy);
2785
2786 let config = get_config().unwrap();
2787 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2788
2789 let freeze_ix = spl_token_interface::instruction::freeze_account(
2790 &spl_token_interface::id(),
2791 &token_account,
2792 &mint,
2793 &fee_payer,
2794 &[],
2795 )
2796 .unwrap();
2797
2798 let message = VersionedMessage::Legacy(Message::new(&[freeze_ix], Some(&fee_payer)));
2799 let mut transaction =
2800 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2801 let result = validator.validate_transaction(config, &mut transaction, &rpc_client).await;
2802 if let Err(KoraError::InvalidTransaction(msg)) = result {
2803 assert!(msg.contains("Fee payer cannot be used for"));
2804 } else {
2805 panic!("Expected InvalidTransaction error for freeze_account policy");
2806 }
2807 }
2808
2809 #[tokio::test]
2810 #[serial]
2811 async fn test_fee_payer_policy_token2022_freeze_account() {
2812 let fee_payer = Pubkey::new_unique();
2813 let token_account = Pubkey::new_unique();
2814 let mint = Pubkey::new_unique();
2815
2816 let rpc_client = RpcMockBuilder::new().build();
2818 let mut policy = FeePayerPolicy::default();
2819 policy.token_2022.allow_freeze_account = true;
2820 setup_token2022_config_with_policy(policy);
2821
2822 let config = get_config().unwrap();
2823 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2824
2825 let freeze_ix = spl_token_2022_interface::instruction::freeze_account(
2826 &spl_token_2022_interface::id(),
2827 &token_account,
2828 &mint,
2829 &fee_payer,
2830 &[],
2831 )
2832 .unwrap();
2833
2834 let message = VersionedMessage::Legacy(Message::new(&[freeze_ix], Some(&fee_payer)));
2835 let mut transaction =
2836 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2837 assert!(validator
2838 .validate_transaction(config, &mut transaction, &rpc_client)
2839 .await
2840 .is_ok());
2841
2842 let rpc_client = RpcMockBuilder::new().build();
2844 let mut policy = FeePayerPolicy::default();
2845 policy.token_2022.allow_freeze_account = false;
2846 setup_token2022_config_with_policy(policy);
2847
2848 let config = get_config().unwrap();
2849 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2850
2851 let freeze_ix = spl_token_2022_interface::instruction::freeze_account(
2852 &spl_token_2022_interface::id(),
2853 &token_account,
2854 &mint,
2855 &fee_payer,
2856 &[],
2857 )
2858 .unwrap();
2859
2860 let message = VersionedMessage::Legacy(Message::new(&[freeze_ix], Some(&fee_payer)));
2861 let mut transaction =
2862 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2863 let result = validator.validate_transaction(config, &mut transaction, &rpc_client).await;
2864 if let Err(KoraError::InvalidTransaction(msg)) = result {
2865 assert!(msg.contains("Fee payer cannot be used for"));
2866 } else {
2867 panic!("Expected InvalidTransaction error for token2022_freeze_account policy");
2868 }
2869 }
2870
2871 #[tokio::test]
2872 #[serial]
2873 async fn test_fee_payer_policy_thaw_account() {
2874 let fee_payer = Pubkey::new_unique();
2875 let token_account = Pubkey::new_unique();
2876 let mint = Pubkey::new_unique();
2877
2878 let rpc_client = RpcMockBuilder::new().build();
2881 let mut policy = FeePayerPolicy::default();
2882 policy.spl_token.allow_thaw_account = true;
2883 setup_spl_config_with_policy(policy);
2884
2885 let config = get_config().unwrap();
2886 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2887
2888 let thaw_ix = spl_token_interface::instruction::thaw_account(
2889 &spl_token_interface::id(),
2890 &token_account,
2891 &mint,
2892 &fee_payer,
2893 &[],
2894 )
2895 .unwrap();
2896
2897 let message = VersionedMessage::Legacy(Message::new(&[thaw_ix], Some(&fee_payer)));
2898 let mut transaction =
2899 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2900 assert!(validator
2901 .validate_transaction(config, &mut transaction, &rpc_client)
2902 .await
2903 .is_ok());
2904
2905 let rpc_client = RpcMockBuilder::new().build();
2907 let mut policy = FeePayerPolicy::default();
2908 policy.spl_token.allow_thaw_account = false;
2909 setup_spl_config_with_policy(policy);
2910
2911 let config = get_config().unwrap();
2912 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2913
2914 let thaw_ix = spl_token_interface::instruction::thaw_account(
2915 &spl_token_interface::id(),
2916 &token_account,
2917 &mint,
2918 &fee_payer,
2919 &[],
2920 )
2921 .unwrap();
2922
2923 let message = VersionedMessage::Legacy(Message::new(&[thaw_ix], Some(&fee_payer)));
2924 let mut transaction =
2925 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2926 let result = validator.validate_transaction(config, &mut transaction, &rpc_client).await;
2927 if let Err(KoraError::InvalidTransaction(msg)) = result {
2928 assert!(msg.contains("Fee payer cannot be used for"));
2929 } else {
2930 panic!("Expected InvalidTransaction error for thaw_account policy");
2931 }
2932 }
2933
2934 #[tokio::test]
2935 #[serial]
2936 async fn test_fee_payer_policy_token2022_thaw_account() {
2937 let fee_payer = Pubkey::new_unique();
2938 let token_account = Pubkey::new_unique();
2939 let mint = Pubkey::new_unique();
2940
2941 let rpc_client = RpcMockBuilder::new().build();
2943 let mut policy = FeePayerPolicy::default();
2944 policy.token_2022.allow_thaw_account = true;
2945 setup_token2022_config_with_policy(policy);
2946
2947 let config = get_config().unwrap();
2948 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2949
2950 let thaw_ix = spl_token_2022_interface::instruction::thaw_account(
2951 &spl_token_2022_interface::id(),
2952 &token_account,
2953 &mint,
2954 &fee_payer,
2955 &[],
2956 )
2957 .unwrap();
2958
2959 let message = VersionedMessage::Legacy(Message::new(&[thaw_ix], Some(&fee_payer)));
2960 let mut transaction =
2961 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2962 assert!(validator
2963 .validate_transaction(config, &mut transaction, &rpc_client)
2964 .await
2965 .is_ok());
2966
2967 let rpc_client = RpcMockBuilder::new().build();
2969 let mut policy = FeePayerPolicy::default();
2970 policy.token_2022.allow_thaw_account = false;
2971 setup_token2022_config_with_policy(policy);
2972
2973 let config = get_config().unwrap();
2974 let validator = TransactionValidator::new(config, fee_payer).unwrap();
2975
2976 let thaw_ix = spl_token_2022_interface::instruction::thaw_account(
2977 &spl_token_2022_interface::id(),
2978 &token_account,
2979 &mint,
2980 &fee_payer,
2981 &[],
2982 )
2983 .unwrap();
2984
2985 let message = VersionedMessage::Legacy(Message::new(&[thaw_ix], Some(&fee_payer)));
2986 let mut transaction =
2987 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
2988 let result = validator.validate_transaction(config, &mut transaction, &rpc_client).await;
2989 if let Err(KoraError::InvalidTransaction(msg)) = result {
2990 assert!(msg.contains("Fee payer cannot be used for"));
2991 } else {
2992 panic!("Expected InvalidTransaction error for token2022_thaw_account policy");
2993 }
2994 }
2995
2996 #[tokio::test]
2997 #[serial]
2998 async fn test_fee_payer_policy_mixed_instructions() {
2999 let fee_payer = Pubkey::new_unique();
3000 let fee_payer_token_account = Pubkey::new_unique();
3001 let mint = Pubkey::new_unique();
3002
3003 let revoke_ix = spl_token_interface::instruction::revoke(
3004 &spl_token_interface::id(),
3005 &fee_payer_token_account,
3006 &fee_payer,
3007 &[],
3008 )
3009 .unwrap();
3010
3011 let burn_ix = spl_token_interface::instruction::burn(
3012 &spl_token_interface::id(),
3013 &fee_payer_token_account,
3014 &mint,
3015 &fee_payer,
3016 &[],
3017 500,
3018 )
3019 .unwrap();
3020
3021 let rpc_client = RpcMockBuilder::new().build();
3023 let mut policy = FeePayerPolicy::default();
3024 policy.spl_token.allow_revoke = true;
3025 policy.spl_token.allow_burn = true;
3026 setup_spl_config_with_policy(policy);
3027
3028 let config = get_config().unwrap();
3029 let validator = TransactionValidator::new(config, fee_payer).unwrap();
3030
3031 let message = VersionedMessage::Legacy(Message::new(
3032 &[revoke_ix.clone(), burn_ix.clone()],
3033 Some(&fee_payer),
3034 ));
3035 let mut transaction =
3036 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
3037 assert!(
3038 validator.validate_transaction(config, &mut transaction, &rpc_client).await.is_ok(),
3039 "Both policies true should pass"
3040 );
3041
3042 let rpc_client = RpcMockBuilder::new().build();
3044 let mut policy = FeePayerPolicy::default();
3045 policy.spl_token.allow_revoke = true;
3046 policy.spl_token.allow_burn = false;
3047 setup_spl_config_with_policy(policy);
3048
3049 let config = get_config().unwrap();
3050 let validator = TransactionValidator::new(config, fee_payer).unwrap();
3051
3052 let message = VersionedMessage::Legacy(Message::new(
3053 &[revoke_ix.clone(), burn_ix.clone()],
3054 Some(&fee_payer),
3055 ));
3056 let mut transaction =
3057 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
3058 let result = validator.validate_transaction(config, &mut transaction, &rpc_client).await;
3059 if let Err(KoraError::InvalidTransaction(msg)) = result {
3060 assert!(msg.contains("Fee payer cannot be used for"));
3061 } else {
3062 panic!("Expected InvalidTransaction error for burn policy");
3063 }
3064
3065 let rpc_client = RpcMockBuilder::new().build();
3067 let mut policy = FeePayerPolicy::default();
3068 policy.spl_token.allow_revoke = false;
3069 policy.spl_token.allow_burn = true;
3070 setup_spl_config_with_policy(policy);
3071
3072 let config = get_config().unwrap();
3073 let validator = TransactionValidator::new(config, fee_payer).unwrap();
3074
3075 let message = VersionedMessage::Legacy(Message::new(
3076 &[revoke_ix.clone(), burn_ix.clone()],
3077 Some(&fee_payer),
3078 ));
3079 let mut transaction =
3080 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
3081 let result = validator.validate_transaction(config, &mut transaction, &rpc_client).await;
3082 if let Err(KoraError::InvalidTransaction(msg)) = result {
3083 assert!(msg.contains("Fee payer cannot be used for"));
3084 } else {
3085 panic!("Expected InvalidTransaction error for revoke policy");
3086 }
3087 }
3088}