1use crate::{
2 config::FeePayerPolicy,
3 error::KoraError,
4 fee::fee::{FeeConfigUtil, TotalFeeCalculation},
5 oracle::PriceSource,
6 state::get_config,
7 token::{interface::TokenMint, token::TokenUtil},
8 transaction::{
9 ParsedSPLInstructionData, ParsedSPLInstructionType, ParsedSystemInstructionData,
10 ParsedSystemInstructionType, VersionedTransactionResolved,
11 },
12};
13use solana_client::nonblocking::rpc_client::RpcClient;
14use solana_sdk::{pubkey::Pubkey, transaction::VersionedTransaction};
15use std::str::FromStr;
16
17use crate::fee::price::PriceModel;
18
19pub struct TransactionValidator {
20 fee_payer_pubkey: Pubkey,
21 max_allowed_lamports: u64,
22 allowed_programs: Vec<Pubkey>,
23 max_signatures: u64,
24 allowed_tokens: Vec<Pubkey>,
25 disallowed_accounts: Vec<Pubkey>,
26 _price_source: PriceSource,
27 fee_payer_policy: FeePayerPolicy,
28 allow_durable_transactions: bool,
29}
30
31impl TransactionValidator {
32 pub fn new(fee_payer_pubkey: Pubkey) -> Result<Self, KoraError> {
33 let config = &get_config()?.validation;
34
35 let allowed_programs = config
37 .allowed_programs
38 .iter()
39 .map(|addr| {
40 Pubkey::from_str(addr).map_err(|e| {
41 KoraError::InternalServerError(format!(
42 "Invalid program address in config: {e}"
43 ))
44 })
45 })
46 .collect::<Result<Vec<Pubkey>, KoraError>>()?;
47
48 Ok(Self {
49 fee_payer_pubkey,
50 max_allowed_lamports: config.max_allowed_lamports,
51 allowed_programs,
52 max_signatures: config.max_signatures,
53 _price_source: config.price_source.clone(),
54 allowed_tokens: config
55 .allowed_tokens
56 .iter()
57 .map(|addr| Pubkey::from_str(addr))
58 .collect::<Result<Vec<Pubkey>, _>>()
59 .map_err(|e| {
60 KoraError::InternalServerError(format!("Invalid allowed token address: {e}"))
61 })?,
62 disallowed_accounts: config
63 .disallowed_accounts
64 .iter()
65 .map(|addr| Pubkey::from_str(addr))
66 .collect::<Result<Vec<Pubkey>, _>>()
67 .map_err(|e| {
68 KoraError::InternalServerError(format!(
69 "Invalid disallowed account address: {e}"
70 ))
71 })?,
72 fee_payer_policy: config.fee_payer_policy.clone(),
73 allow_durable_transactions: config.allow_durable_transactions,
74 })
75 }
76
77 pub async fn fetch_and_validate_token_mint(
78 &self,
79 mint: &Pubkey,
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(rpc_client, mint).await?;
90
91 Ok(mint)
92 }
93
94 pub async fn validate_transaction(
98 &self,
99 transaction_resolved: &mut VersionedTransactionResolved,
100 rpc_client: &RpcClient,
101 ) -> Result<(), KoraError> {
102 if transaction_resolved.all_instructions.is_empty() {
103 return Err(KoraError::InvalidTransaction(
104 "Transaction contains no instructions".to_string(),
105 ));
106 }
107
108 if transaction_resolved.all_account_keys.is_empty() {
109 return Err(KoraError::InvalidTransaction(
110 "Transaction contains no account keys".to_string(),
111 ));
112 }
113
114 self.validate_signatures(&transaction_resolved.transaction)?;
115
116 self.validate_programs(transaction_resolved)?;
117 self.validate_transfer_amounts(transaction_resolved, rpc_client).await?;
118 self.validate_disallowed_accounts(transaction_resolved)?;
119 self.validate_fee_payer_usage(transaction_resolved)?;
120
121 Ok(())
122 }
123
124 pub fn validate_lamport_fee(&self, fee: u64) -> Result<(), KoraError> {
125 if fee > self.max_allowed_lamports {
126 return Err(KoraError::InvalidTransaction(format!(
127 "Fee {} exceeds maximum allowed {}",
128 fee, self.max_allowed_lamports
129 )));
130 }
131 Ok(())
132 }
133
134 fn validate_signatures(&self, transaction: &VersionedTransaction) -> Result<(), KoraError> {
135 if transaction.signatures.len() > self.max_signatures as usize {
136 return Err(KoraError::InvalidTransaction(format!(
137 "Too many signatures: {} > {}",
138 transaction.signatures.len(),
139 self.max_signatures
140 )));
141 }
142
143 if transaction.signatures.is_empty() {
144 return Err(KoraError::InvalidTransaction("No signatures found".to_string()));
145 }
146
147 Ok(())
148 }
149
150 fn validate_programs(
151 &self,
152 transaction_resolved: &VersionedTransactionResolved,
153 ) -> Result<(), KoraError> {
154 for instruction in &transaction_resolved.all_instructions {
155 if !self.allowed_programs.contains(&instruction.program_id) {
156 return Err(KoraError::InvalidTransaction(format!(
157 "Program {} is not in the allowed list",
158 instruction.program_id
159 )));
160 }
161 }
162 Ok(())
163 }
164
165 fn validate_fee_payer_usage(
166 &self,
167 transaction_resolved: &mut VersionedTransactionResolved,
168 ) -> Result<(), KoraError> {
169 let system_instructions = transaction_resolved.get_or_parse_system_instructions()?;
170
171 if !self.allow_durable_transactions
173 && system_instructions
174 .contains_key(&ParsedSystemInstructionType::SystemAdvanceNonceAccount)
175 {
176 return Err(KoraError::InvalidTransaction(
177 "Durable transactions (nonce-based) are not allowed".to_string(),
178 ));
179 }
180
181 validate_system!(self, system_instructions, SystemTransfer,
183 ParsedSystemInstructionData::SystemTransfer { sender, .. } => sender,
184 self.fee_payer_policy.system.allow_transfer, "System Transfer");
185
186 validate_system!(self, system_instructions, SystemAssign,
187 ParsedSystemInstructionData::SystemAssign { authority } => authority,
188 self.fee_payer_policy.system.allow_assign, "System Assign");
189
190 validate_system!(self, system_instructions, SystemAllocate,
191 ParsedSystemInstructionData::SystemAllocate { account } => account,
192 self.fee_payer_policy.system.allow_allocate, "System Allocate");
193
194 validate_system!(self, system_instructions, SystemCreateAccount,
195 ParsedSystemInstructionData::SystemCreateAccount { payer, .. } => payer,
196 self.fee_payer_policy.system.allow_create_account, "System Create Account");
197
198 validate_system!(self, system_instructions, SystemInitializeNonceAccount,
199 ParsedSystemInstructionData::SystemInitializeNonceAccount { nonce_authority, .. } => nonce_authority,
200 self.fee_payer_policy.system.nonce.allow_initialize, "System Initialize Nonce Account");
201
202 validate_system!(self, system_instructions, SystemAdvanceNonceAccount,
203 ParsedSystemInstructionData::SystemAdvanceNonceAccount { nonce_authority, .. } => nonce_authority,
204 self.fee_payer_policy.system.nonce.allow_advance, "System Advance Nonce Account");
205
206 validate_system!(self, system_instructions, SystemAuthorizeNonceAccount,
207 ParsedSystemInstructionData::SystemAuthorizeNonceAccount { nonce_authority, .. } => nonce_authority,
208 self.fee_payer_policy.system.nonce.allow_authorize, "System Authorize Nonce Account");
209
210 validate_system!(self, system_instructions, SystemWithdrawNonceAccount,
213 ParsedSystemInstructionData::SystemWithdrawNonceAccount { nonce_authority, .. } => nonce_authority,
214 self.fee_payer_policy.system.nonce.allow_withdraw, "System Withdraw Nonce Account");
215
216 let spl_instructions = transaction_resolved.get_or_parse_spl_instructions()?;
218
219 validate_spl!(self, spl_instructions, SplTokenTransfer,
220 ParsedSPLInstructionData::SplTokenTransfer { owner, is_2022, .. } => { owner, is_2022 },
221 self.fee_payer_policy.spl_token.allow_transfer,
222 self.fee_payer_policy.token_2022.allow_transfer,
223 "SPL Token Transfer", "Token2022 Token Transfer");
224
225 validate_spl!(self, spl_instructions, SplTokenApprove,
226 ParsedSPLInstructionData::SplTokenApprove { owner, is_2022, .. } => { owner, is_2022 },
227 self.fee_payer_policy.spl_token.allow_approve,
228 self.fee_payer_policy.token_2022.allow_approve,
229 "SPL Token Approve", "Token2022 Token Approve");
230
231 validate_spl!(self, spl_instructions, SplTokenBurn,
232 ParsedSPLInstructionData::SplTokenBurn { owner, is_2022 } => { owner, is_2022 },
233 self.fee_payer_policy.spl_token.allow_burn,
234 self.fee_payer_policy.token_2022.allow_burn,
235 "SPL Token Burn", "Token2022 Token Burn");
236
237 validate_spl!(self, spl_instructions, SplTokenCloseAccount,
238 ParsedSPLInstructionData::SplTokenCloseAccount { owner, is_2022 } => { owner, is_2022 },
239 self.fee_payer_policy.spl_token.allow_close_account,
240 self.fee_payer_policy.token_2022.allow_close_account,
241 "SPL Token Close Account", "Token2022 Token Close Account");
242
243 validate_spl!(self, spl_instructions, SplTokenRevoke,
244 ParsedSPLInstructionData::SplTokenRevoke { owner, is_2022 } => { owner, is_2022 },
245 self.fee_payer_policy.spl_token.allow_revoke,
246 self.fee_payer_policy.token_2022.allow_revoke,
247 "SPL Token Revoke", "Token2022 Token Revoke");
248
249 validate_spl!(self, spl_instructions, SplTokenSetAuthority,
250 ParsedSPLInstructionData::SplTokenSetAuthority { authority, is_2022 } => { authority, is_2022 },
251 self.fee_payer_policy.spl_token.allow_set_authority,
252 self.fee_payer_policy.token_2022.allow_set_authority,
253 "SPL Token SetAuthority", "Token2022 Token SetAuthority");
254
255 validate_spl!(self, spl_instructions, SplTokenMintTo,
256 ParsedSPLInstructionData::SplTokenMintTo { mint_authority, is_2022 } => { mint_authority, is_2022 },
257 self.fee_payer_policy.spl_token.allow_mint_to,
258 self.fee_payer_policy.token_2022.allow_mint_to,
259 "SPL Token MintTo", "Token2022 Token MintTo");
260
261 validate_spl!(self, spl_instructions, SplTokenInitializeMint,
262 ParsedSPLInstructionData::SplTokenInitializeMint { mint_authority, is_2022 } => { mint_authority, is_2022 },
263 self.fee_payer_policy.spl_token.allow_initialize_mint,
264 self.fee_payer_policy.token_2022.allow_initialize_mint,
265 "SPL Token InitializeMint", "Token2022 Token InitializeMint");
266
267 validate_spl!(self, spl_instructions, SplTokenInitializeAccount,
268 ParsedSPLInstructionData::SplTokenInitializeAccount { owner, is_2022 } => { owner, is_2022 },
269 self.fee_payer_policy.spl_token.allow_initialize_account,
270 self.fee_payer_policy.token_2022.allow_initialize_account,
271 "SPL Token InitializeAccount", "Token2022 Token InitializeAccount");
272
273 validate_spl_multisig!(self, spl_instructions, SplTokenInitializeMultisig,
274 ParsedSPLInstructionData::SplTokenInitializeMultisig { signers, is_2022 } => { signers, is_2022 },
275 self.fee_payer_policy.spl_token.allow_initialize_multisig,
276 self.fee_payer_policy.token_2022.allow_initialize_multisig,
277 "SPL Token InitializeMultisig", "Token2022 Token InitializeMultisig");
278
279 validate_spl!(self, spl_instructions, SplTokenFreezeAccount,
280 ParsedSPLInstructionData::SplTokenFreezeAccount { freeze_authority, is_2022 } => { freeze_authority, is_2022 },
281 self.fee_payer_policy.spl_token.allow_freeze_account,
282 self.fee_payer_policy.token_2022.allow_freeze_account,
283 "SPL Token FreezeAccount", "Token2022 Token FreezeAccount");
284
285 validate_spl!(self, spl_instructions, SplTokenThawAccount,
286 ParsedSPLInstructionData::SplTokenThawAccount { freeze_authority, is_2022 } => { freeze_authority, is_2022 },
287 self.fee_payer_policy.spl_token.allow_thaw_account,
288 self.fee_payer_policy.token_2022.allow_thaw_account,
289 "SPL Token ThawAccount", "Token2022 Token ThawAccount");
290
291 Ok(())
292 }
293
294 async fn validate_transfer_amounts(
295 &self,
296 transaction_resolved: &mut VersionedTransactionResolved,
297 rpc_client: &RpcClient,
298 ) -> Result<(), KoraError> {
299 let total_outflow = self.calculate_total_outflow(transaction_resolved, rpc_client).await?;
300
301 if total_outflow > self.max_allowed_lamports {
302 return Err(KoraError::InvalidTransaction(format!(
303 "Total transfer amount {} exceeds maximum allowed {}",
304 total_outflow, self.max_allowed_lamports
305 )));
306 }
307
308 Ok(())
309 }
310
311 fn validate_disallowed_accounts(
312 &self,
313 transaction_resolved: &VersionedTransactionResolved,
314 ) -> Result<(), KoraError> {
315 for instruction in &transaction_resolved.all_instructions {
316 if self.disallowed_accounts.contains(&instruction.program_id) {
317 return Err(KoraError::InvalidTransaction(format!(
318 "Program {} is disallowed",
319 instruction.program_id
320 )));
321 }
322
323 for account_index in instruction.accounts.iter() {
324 if self.disallowed_accounts.contains(&account_index.pubkey) {
325 return Err(KoraError::InvalidTransaction(format!(
326 "Account {} is disallowed",
327 account_index.pubkey
328 )));
329 }
330 }
331 }
332 Ok(())
333 }
334
335 pub fn is_disallowed_account(&self, account: &Pubkey) -> bool {
336 self.disallowed_accounts.contains(account)
337 }
338
339 async fn calculate_total_outflow(
340 &self,
341 transaction_resolved: &mut VersionedTransactionResolved,
342 rpc_client: &RpcClient,
343 ) -> Result<u64, KoraError> {
344 let config = get_config()?;
345 FeeConfigUtil::calculate_fee_payer_outflow(
346 &self.fee_payer_pubkey,
347 transaction_resolved,
348 rpc_client,
349 &config.validation.price_source,
350 )
351 .await
352 }
353
354 pub async fn validate_token_payment(
355 transaction_resolved: &mut VersionedTransactionResolved,
356 required_lamports: u64,
357 rpc_client: &RpcClient,
358 expected_payment_destination: &Pubkey,
359 ) -> Result<(), KoraError> {
360 if TokenUtil::verify_token_payment(
361 transaction_resolved,
362 rpc_client,
363 required_lamports,
364 expected_payment_destination,
365 )
366 .await?
367 {
368 return Ok(());
369 }
370
371 Err(KoraError::InvalidTransaction(format!(
372 "Insufficient token payment. Required {required_lamports} lamports"
373 )))
374 }
375
376 pub fn validate_strict_pricing_with_fee(
377 fee_calculation: &TotalFeeCalculation,
378 ) -> Result<(), KoraError> {
379 let config = get_config()?;
380
381 if !matches!(&config.validation.price.model, PriceModel::Fixed { strict: true, .. }) {
382 return Ok(());
383 }
384
385 let fixed_price_lamports = fee_calculation.total_fee_lamports;
386 let total_fee_lamports = fee_calculation.get_total_fee_lamports()?;
387
388 if fixed_price_lamports < total_fee_lamports {
389 log::error!(
390 "Strict pricing violation: fixed_price_lamports={} < total_fee_lamports={}",
391 fixed_price_lamports,
392 total_fee_lamports
393 );
394 return Err(KoraError::ValidationError(format!(
395 "Strict pricing violation: total fee ({} lamports) exceeds fixed price ({} lamports)",
396 total_fee_lamports,
397 fixed_price_lamports
398 )));
399 }
400
401 Ok(())
402 }
403}
404
405#[cfg(test)]
406mod tests {
407 use crate::{
408 config::{Config, FeePayerPolicy},
409 state::update_config,
410 tests::{
411 account_mock::{MintAccountMockBuilder, TokenAccountMockBuilder},
412 config_mock::{mock_state::setup_config_mock, ConfigMockBuilder},
413 rpc_mock::RpcMockBuilder,
414 },
415 transaction::TransactionUtil,
416 };
417 use serial_test::serial;
418
419 use super::*;
420 use solana_message::{Message, VersionedMessage};
421 use solana_sdk::instruction::Instruction;
422 use solana_system_interface::{
423 instruction::{
424 assign, create_account, create_account_with_seed, transfer, transfer_with_seed,
425 },
426 program::ID as SYSTEM_PROGRAM_ID,
427 };
428
429 fn setup_both_configs(config: Config) {
430 drop(setup_config_mock(config.clone()));
431 update_config(config).unwrap();
432 }
433
434 fn setup_default_config() {
436 let config = ConfigMockBuilder::new()
437 .with_price_source(PriceSource::Mock)
438 .with_allowed_programs(vec![SYSTEM_PROGRAM_ID.to_string()])
439 .with_max_allowed_lamports(1_000_000)
440 .with_fee_payer_policy(FeePayerPolicy::default())
441 .build();
442 setup_both_configs(config);
443 }
444
445 fn setup_config_with_policy(policy: FeePayerPolicy) {
446 let config = ConfigMockBuilder::new()
447 .with_price_source(PriceSource::Mock)
448 .with_allowed_programs(vec![SYSTEM_PROGRAM_ID.to_string()])
449 .with_max_allowed_lamports(1_000_000)
450 .with_fee_payer_policy(policy)
451 .build();
452 setup_both_configs(config);
453 }
454
455 fn setup_spl_config_with_policy(policy: FeePayerPolicy) {
456 let config = ConfigMockBuilder::new()
457 .with_price_source(PriceSource::Mock)
458 .with_allowed_programs(vec![spl_token_interface::id().to_string()])
459 .with_max_allowed_lamports(1_000_000)
460 .with_fee_payer_policy(policy)
461 .build();
462 setup_both_configs(config);
463 }
464
465 fn setup_token2022_config_with_policy(policy: FeePayerPolicy) {
466 let config = ConfigMockBuilder::new()
467 .with_price_source(PriceSource::Mock)
468 .with_allowed_programs(vec![spl_token_2022_interface::id().to_string()])
469 .with_max_allowed_lamports(1_000_000)
470 .with_fee_payer_policy(policy)
471 .build();
472 setup_both_configs(config);
473 }
474
475 #[tokio::test]
476 #[serial]
477 async fn test_validate_transaction() {
478 let fee_payer = Pubkey::new_unique();
479 setup_default_config();
480 let rpc_client = RpcMockBuilder::new().build();
481
482 let validator = TransactionValidator::new(fee_payer).unwrap();
483
484 let recipient = Pubkey::new_unique();
485 let sender = Pubkey::new_unique();
486 let instruction = transfer(&sender, &recipient, 100_000);
487 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
488 let mut transaction =
489 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
490
491 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
492 }
493
494 #[tokio::test]
495 #[serial]
496 async fn test_transfer_amount_limits() {
497 let fee_payer = Pubkey::new_unique();
498 setup_default_config();
499 let rpc_client = RpcMockBuilder::new().build();
500
501 let validator = TransactionValidator::new(fee_payer).unwrap();
502 let sender = Pubkey::new_unique();
503 let recipient = Pubkey::new_unique();
504
505 let instruction = transfer(&sender, &recipient, 2_000_000);
507 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
508 let mut transaction =
509 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
510
511 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
512
513 let instructions =
515 vec![transfer(&sender, &recipient, 500_000), transfer(&sender, &recipient, 500_000)];
516 let message = VersionedMessage::Legacy(Message::new(&instructions, Some(&fee_payer)));
517 let mut transaction =
518 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
519 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
520 }
521
522 #[tokio::test]
523 #[serial]
524 async fn test_validate_programs() {
525 let fee_payer = Pubkey::new_unique();
526 setup_default_config();
527 let rpc_client = RpcMockBuilder::new().build();
528
529 let validator = TransactionValidator::new(fee_payer).unwrap();
530 let sender = Pubkey::new_unique();
531 let recipient = Pubkey::new_unique();
532
533 let instruction = transfer(&sender, &recipient, 1000);
535 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
536 let mut transaction =
537 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
538 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
539
540 let fake_program = Pubkey::new_unique();
542 let instruction = Instruction::new_with_bincode(
544 fake_program,
545 &[0u8],
546 vec![], );
548 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
549 let mut transaction =
550 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
551 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
552 }
553
554 #[tokio::test]
555 #[serial]
556 async fn test_validate_signatures() {
557 let fee_payer = Pubkey::new_unique();
558 let config = ConfigMockBuilder::new()
559 .with_price_source(PriceSource::Mock)
560 .with_allowed_programs(vec![SYSTEM_PROGRAM_ID.to_string()])
561 .with_max_allowed_lamports(1_000_000)
562 .with_max_signatures(2)
563 .with_fee_payer_policy(FeePayerPolicy::default())
564 .build();
565 update_config(config).unwrap();
566
567 let rpc_client = RpcMockBuilder::new().build();
568 let validator = TransactionValidator::new(fee_payer).unwrap();
569 let sender = Pubkey::new_unique();
570 let recipient = Pubkey::new_unique();
571
572 let instructions = vec![
574 transfer(&sender, &recipient, 1000),
575 transfer(&sender, &recipient, 1000),
576 transfer(&sender, &recipient, 1000),
577 ];
578 let message = VersionedMessage::Legacy(Message::new(&instructions, Some(&fee_payer)));
579 let mut transaction =
580 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
581 transaction.transaction.signatures = vec![Default::default(); 3]; assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
583 }
584
585 #[tokio::test]
586 #[serial]
587 async fn test_sign_and_send_transaction_mode() {
588 let fee_payer = Pubkey::new_unique();
589 setup_default_config();
590 let rpc_client = RpcMockBuilder::new().build();
591
592 let validator = TransactionValidator::new(fee_payer).unwrap();
593 let sender = Pubkey::new_unique();
594 let recipient = Pubkey::new_unique();
595
596 let instruction = transfer(&sender, &recipient, 1000);
598 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
599 let mut transaction =
600 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
601 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
602
603 let instruction = transfer(&sender, &recipient, 1000);
605 let message = VersionedMessage::Legacy(Message::new(&[instruction], None)); let mut transaction =
607 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
608 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
609 }
610
611 #[tokio::test]
612 #[serial]
613 async fn test_empty_transaction() {
614 let fee_payer = Pubkey::new_unique();
615 setup_default_config();
616 let rpc_client = RpcMockBuilder::new().build();
617
618 let validator = TransactionValidator::new(fee_payer).unwrap();
619
620 let message = VersionedMessage::Legacy(Message::new(&[], Some(&fee_payer)));
622 let mut transaction =
623 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
624 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
625 }
626
627 #[tokio::test]
628 #[serial]
629 async fn test_disallowed_accounts() {
630 let fee_payer = Pubkey::new_unique();
631 let config = ConfigMockBuilder::new()
632 .with_price_source(PriceSource::Mock)
633 .with_allowed_programs(vec![SYSTEM_PROGRAM_ID.to_string()])
634 .with_max_allowed_lamports(1_000_000)
635 .with_disallowed_accounts(vec![
636 "hndXZGK45hCxfBYvxejAXzCfCujoqkNf7rk4sTB8pek".to_string()
637 ])
638 .with_fee_payer_policy(FeePayerPolicy::default())
639 .build();
640 update_config(config).unwrap();
641
642 let rpc_client = RpcMockBuilder::new().build();
643 let validator = TransactionValidator::new(fee_payer).unwrap();
644 let instruction = transfer(
645 &Pubkey::from_str("hndXZGK45hCxfBYvxejAXzCfCujoqkNf7rk4sTB8pek").unwrap(),
646 &fee_payer,
647 1000,
648 );
649 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
650 let mut transaction =
651 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
652 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
653 }
654
655 #[tokio::test]
656 #[serial]
657 async fn test_fee_payer_policy_sol_transfers() {
658 let fee_payer = Pubkey::new_unique();
659 let recipient = Pubkey::new_unique();
660
661 let rpc_client = RpcMockBuilder::new().build();
663 let mut policy = FeePayerPolicy::default();
664 policy.system.allow_transfer = true;
665 setup_config_with_policy(policy);
666
667 let validator = TransactionValidator::new(fee_payer).unwrap();
668
669 let instruction = transfer(&fee_payer, &recipient, 1000);
670
671 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
672 let mut transaction =
673 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
674 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
675
676 let rpc_client = RpcMockBuilder::new().build();
678 let mut policy = FeePayerPolicy::default();
679 policy.system.allow_transfer = false;
680 setup_config_with_policy(policy);
681
682 let validator = TransactionValidator::new(fee_payer).unwrap();
683
684 let instruction = transfer(&fee_payer, &recipient, 1000);
685 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
686 let mut transaction =
687 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
688 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
689 }
690
691 #[tokio::test]
692 #[serial]
693 async fn test_fee_payer_policy_assign() {
694 let fee_payer = Pubkey::new_unique();
695 let new_owner = Pubkey::new_unique();
696
697 let rpc_client = RpcMockBuilder::new().build();
700
701 let mut policy = FeePayerPolicy::default();
702 policy.system.allow_assign = true;
703 setup_config_with_policy(policy);
704
705 let validator = TransactionValidator::new(fee_payer).unwrap();
706
707 let instruction = assign(&fee_payer, &new_owner);
708 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
709 let mut transaction =
710 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
711 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
712
713 let rpc_client = RpcMockBuilder::new().build();
716
717 let mut policy = FeePayerPolicy::default();
718 policy.system.allow_assign = false;
719 setup_config_with_policy(policy);
720
721 let validator = TransactionValidator::new(fee_payer).unwrap();
722
723 let instruction = assign(&fee_payer, &new_owner);
724 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
725 let mut transaction =
726 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
727 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
728 }
729
730 #[tokio::test]
731 #[serial]
732 async fn test_fee_payer_policy_spl_transfers() {
733 let fee_payer = Pubkey::new_unique();
734
735 let fee_payer_token_account = Pubkey::new_unique();
736 let recipient_token_account = Pubkey::new_unique();
737 let mint = Pubkey::new_unique();
738
739 let source_token_account =
740 TokenAccountMockBuilder::new().with_mint(&mint).with_owner(&fee_payer).build();
741 let mint_account = MintAccountMockBuilder::new().with_decimals(6).build();
742
743 let rpc_client = RpcMockBuilder::new()
745 .build_with_sequential_accounts(vec![&source_token_account, &mint_account]);
746
747 let mut policy = FeePayerPolicy::default();
748 policy.spl_token.allow_transfer = true;
749 setup_spl_config_with_policy(policy);
750
751 let validator = TransactionValidator::new(fee_payer).unwrap();
752
753 let transfer_ix = spl_token_interface::instruction::transfer(
754 &spl_token_interface::id(),
755 &fee_payer_token_account,
756 &recipient_token_account,
757 &fee_payer, &[],
759 1000,
760 )
761 .unwrap();
762
763 let message = VersionedMessage::Legacy(Message::new(&[transfer_ix], Some(&fee_payer)));
764 let mut transaction =
765 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
766 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
767
768 let rpc_client = RpcMockBuilder::new()
770 .build_with_sequential_accounts(vec![&source_token_account, &mint_account]);
771
772 let mut policy = FeePayerPolicy::default();
773 policy.spl_token.allow_transfer = false;
774 setup_spl_config_with_policy(policy);
775
776 let validator = TransactionValidator::new(fee_payer).unwrap();
777
778 let transfer_ix = spl_token_interface::instruction::transfer(
779 &spl_token_interface::id(),
780 &fee_payer_token_account,
781 &recipient_token_account,
782 &fee_payer, &[],
784 1000,
785 )
786 .unwrap();
787
788 let message = VersionedMessage::Legacy(Message::new(&[transfer_ix], Some(&fee_payer)));
789 let mut transaction =
790 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
791 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
792
793 let rpc_client = RpcMockBuilder::new().build();
795 let other_signer = Pubkey::new_unique();
796 let transfer_ix = spl_token_interface::instruction::transfer(
797 &spl_token_interface::id(),
798 &fee_payer_token_account,
799 &recipient_token_account,
800 &other_signer, &[],
802 1000,
803 )
804 .unwrap();
805
806 let message = VersionedMessage::Legacy(Message::new(&[transfer_ix], Some(&fee_payer)));
807 let mut transaction =
808 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
809 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
810 }
811
812 #[tokio::test]
813 #[serial]
814 async fn test_fee_payer_policy_token2022_transfers() {
815 let fee_payer = Pubkey::new_unique();
816
817 let fee_payer_token_account = Pubkey::new_unique();
818 let recipient_token_account = Pubkey::new_unique();
819 let mint = Pubkey::new_unique();
820
821 let rpc_client = RpcMockBuilder::new()
823 .with_mint_account(2) .build();
825 let mut policy = FeePayerPolicy::default();
827 policy.token_2022.allow_transfer = true;
828 setup_token2022_config_with_policy(policy);
829
830 let validator = TransactionValidator::new(fee_payer).unwrap();
831
832 let transfer_ix = spl_token_2022_interface::instruction::transfer_checked(
833 &spl_token_2022_interface::id(),
834 &fee_payer_token_account,
835 &mint,
836 &recipient_token_account,
837 &fee_payer, &[],
839 1,
840 2,
841 )
842 .unwrap();
843
844 let message = VersionedMessage::Legacy(Message::new(&[transfer_ix], Some(&fee_payer)));
845 let mut transaction =
846 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
847 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
848
849 let rpc_client = RpcMockBuilder::new()
851 .with_mint_account(2) .build();
853 let mut policy = FeePayerPolicy::default();
854 policy.token_2022.allow_transfer = false;
855 setup_token2022_config_with_policy(policy);
856
857 let validator = TransactionValidator::new(fee_payer).unwrap();
858
859 let transfer_ix = spl_token_2022_interface::instruction::transfer_checked(
860 &spl_token_2022_interface::id(),
861 &fee_payer_token_account,
862 &mint,
863 &recipient_token_account,
864 &fee_payer, &[],
866 1000,
867 2,
868 )
869 .unwrap();
870
871 let message = VersionedMessage::Legacy(Message::new(&[transfer_ix], Some(&fee_payer)));
872 let mut transaction =
873 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
874
875 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
877
878 let other_signer = Pubkey::new_unique();
880 let transfer_ix = spl_token_2022_interface::instruction::transfer_checked(
881 &spl_token_2022_interface::id(),
882 &fee_payer_token_account,
883 &mint,
884 &recipient_token_account,
885 &other_signer, &[],
887 1000,
888 2,
889 )
890 .unwrap();
891
892 let message = VersionedMessage::Legacy(Message::new(&[transfer_ix], Some(&fee_payer)));
893 let mut transaction =
894 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
895
896 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
898 }
899
900 #[tokio::test]
901 #[serial]
902 async fn test_calculate_total_outflow() {
903 let fee_payer = Pubkey::new_unique();
904 let config = ConfigMockBuilder::new()
905 .with_price_source(PriceSource::Mock)
906 .with_allowed_programs(vec![SYSTEM_PROGRAM_ID.to_string()])
907 .with_max_allowed_lamports(10_000_000)
908 .with_fee_payer_policy(FeePayerPolicy::default())
909 .build();
910 update_config(config).unwrap();
911
912 let rpc_client = RpcMockBuilder::new().build();
913 let validator = TransactionValidator::new(fee_payer).unwrap();
914
915 let recipient = Pubkey::new_unique();
917 let transfer_instruction = transfer(&fee_payer, &recipient, 100_000);
918 let message =
919 VersionedMessage::Legacy(Message::new(&[transfer_instruction], Some(&fee_payer)));
920 let mut transaction =
921 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
922 let outflow =
923 validator.calculate_total_outflow(&mut transaction, &rpc_client).await.unwrap();
924 assert_eq!(outflow, 100_000, "Transfer from fee payer should add to outflow");
925
926 let sender = Pubkey::new_unique();
928 let transfer_instruction = transfer(&sender, &fee_payer, 50_000);
929 let message =
930 VersionedMessage::Legacy(Message::new(&[transfer_instruction], Some(&fee_payer)));
931 let mut transaction =
932 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
933
934 let outflow =
935 validator.calculate_total_outflow(&mut transaction, &rpc_client).await.unwrap();
936 assert_eq!(outflow, 0, "Transfer to fee payer should subtract from outflow"); let new_account = Pubkey::new_unique();
940 let create_instruction = create_account(
941 &fee_payer,
942 &new_account,
943 200_000, 100, &SYSTEM_PROGRAM_ID,
946 );
947 let message =
948 VersionedMessage::Legacy(Message::new(&[create_instruction], Some(&fee_payer)));
949 let mut transaction =
950 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
951 let outflow =
952 validator.calculate_total_outflow(&mut transaction, &rpc_client).await.unwrap();
953 assert_eq!(outflow, 200_000, "CreateAccount funded by fee payer should add to outflow");
954
955 let create_with_seed_instruction = create_account_with_seed(
957 &fee_payer,
958 &new_account,
959 &fee_payer,
960 "test_seed",
961 300_000, 100, &SYSTEM_PROGRAM_ID,
964 );
965 let message = VersionedMessage::Legacy(Message::new(
966 &[create_with_seed_instruction],
967 Some(&fee_payer),
968 ));
969 let mut transaction =
970 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
971 let outflow =
972 validator.calculate_total_outflow(&mut transaction, &rpc_client).await.unwrap();
973 assert_eq!(
974 outflow, 300_000,
975 "CreateAccountWithSeed funded by fee payer should add to outflow"
976 );
977
978 let transfer_with_seed_instruction = transfer_with_seed(
980 &fee_payer,
981 &fee_payer,
982 "test_seed".to_string(),
983 &SYSTEM_PROGRAM_ID,
984 &recipient,
985 150_000,
986 );
987 let message = VersionedMessage::Legacy(Message::new(
988 &[transfer_with_seed_instruction],
989 Some(&fee_payer),
990 ));
991 let mut transaction =
992 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
993 let outflow =
994 validator.calculate_total_outflow(&mut transaction, &rpc_client).await.unwrap();
995 assert_eq!(outflow, 150_000, "TransferWithSeed from fee payer should add to outflow");
996
997 let instructions = vec![
999 transfer(&fee_payer, &recipient, 100_000), transfer(&sender, &fee_payer, 30_000), create_account(&fee_payer, &new_account, 50_000, 100, &SYSTEM_PROGRAM_ID), ];
1003 let message = VersionedMessage::Legacy(Message::new(&instructions, Some(&fee_payer)));
1004 let mut transaction =
1005 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1006 let outflow =
1007 validator.calculate_total_outflow(&mut transaction, &rpc_client).await.unwrap();
1008 assert_eq!(
1009 outflow, 120_000,
1010 "Multiple instructions should sum correctly: 100000 - 30000 + 50000 = 120000"
1011 );
1012
1013 let other_sender = Pubkey::new_unique();
1015 let transfer_instruction = transfer(&other_sender, &recipient, 500_000);
1016 let message =
1017 VersionedMessage::Legacy(Message::new(&[transfer_instruction], Some(&fee_payer)));
1018 let mut transaction =
1019 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1020 let outflow =
1021 validator.calculate_total_outflow(&mut transaction, &rpc_client).await.unwrap();
1022 assert_eq!(outflow, 0, "Transfer from other account should not affect outflow");
1023
1024 let other_funder = Pubkey::new_unique();
1026 let create_instruction =
1027 create_account(&other_funder, &new_account, 1_000_000, 100, &SYSTEM_PROGRAM_ID);
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(&mut transaction, &rpc_client).await.unwrap();
1034 assert_eq!(outflow, 0, "CreateAccount funded by other account should not affect outflow");
1035 }
1036
1037 #[tokio::test]
1038 #[serial]
1039 async fn test_fee_payer_policy_burn() {
1040 let fee_payer = Pubkey::new_unique();
1041 let fee_payer_token_account = Pubkey::new_unique();
1042 let mint = Pubkey::new_unique();
1043
1044 let rpc_client = RpcMockBuilder::new().build();
1047 let mut policy = FeePayerPolicy::default();
1048 policy.spl_token.allow_burn = true;
1049 setup_spl_config_with_policy(policy);
1050
1051 let validator = TransactionValidator::new(fee_payer).unwrap();
1052
1053 let burn_ix = spl_token_interface::instruction::burn(
1054 &spl_token_interface::id(),
1055 &fee_payer_token_account,
1056 &mint,
1057 &fee_payer,
1058 &[],
1059 1000,
1060 )
1061 .unwrap();
1062
1063 let message = VersionedMessage::Legacy(Message::new(&[burn_ix], Some(&fee_payer)));
1064 let mut transaction =
1065 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1066 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
1068
1069 let rpc_client = RpcMockBuilder::new().build();
1072 let mut policy = FeePayerPolicy::default();
1073 policy.spl_token.allow_burn = false;
1074 setup_spl_config_with_policy(policy);
1075
1076 let validator = TransactionValidator::new(fee_payer).unwrap();
1077
1078 let burn_ix = spl_token_interface::instruction::burn(
1079 &spl_token_interface::id(),
1080 &fee_payer_token_account,
1081 &mint,
1082 &fee_payer,
1083 &[],
1084 1000,
1085 )
1086 .unwrap();
1087
1088 let message = VersionedMessage::Legacy(Message::new(&[burn_ix], Some(&fee_payer)));
1089 let mut transaction =
1090 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1091
1092 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1094
1095 let burn_checked_ix = spl_token_interface::instruction::burn_checked(
1097 &spl_token_interface::id(),
1098 &fee_payer_token_account,
1099 &mint,
1100 &fee_payer,
1101 &[],
1102 1000,
1103 2,
1104 )
1105 .unwrap();
1106
1107 let message = VersionedMessage::Legacy(Message::new(&[burn_checked_ix], Some(&fee_payer)));
1108 let mut transaction =
1109 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1110
1111 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1113 }
1114
1115 #[tokio::test]
1116 #[serial]
1117 async fn test_fee_payer_policy_close_account() {
1118 let fee_payer = Pubkey::new_unique();
1119 let fee_payer_token_account = Pubkey::new_unique();
1120 let destination = Pubkey::new_unique();
1121
1122 let rpc_client = RpcMockBuilder::new().build();
1125 let mut policy = FeePayerPolicy::default();
1126 policy.spl_token.allow_close_account = true;
1127 setup_spl_config_with_policy(policy);
1128
1129 let validator = TransactionValidator::new(fee_payer).unwrap();
1130
1131 let close_ix = spl_token_interface::instruction::close_account(
1132 &spl_token_interface::id(),
1133 &fee_payer_token_account,
1134 &destination,
1135 &fee_payer,
1136 &[],
1137 )
1138 .unwrap();
1139
1140 let message = VersionedMessage::Legacy(Message::new(&[close_ix], Some(&fee_payer)));
1141 let mut transaction =
1142 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1143 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
1145
1146 let rpc_client = RpcMockBuilder::new().build();
1148 let mut policy = FeePayerPolicy::default();
1149 policy.spl_token.allow_close_account = false;
1150 setup_spl_config_with_policy(policy);
1151
1152 let validator = TransactionValidator::new(fee_payer).unwrap();
1153
1154 let close_ix = spl_token_interface::instruction::close_account(
1155 &spl_token_interface::id(),
1156 &fee_payer_token_account,
1157 &destination,
1158 &fee_payer,
1159 &[],
1160 )
1161 .unwrap();
1162
1163 let message = VersionedMessage::Legacy(Message::new(&[close_ix], Some(&fee_payer)));
1164 let mut transaction =
1165 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1166
1167 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1169 }
1170
1171 #[tokio::test]
1172 #[serial]
1173 async fn test_fee_payer_policy_approve() {
1174 let fee_payer = Pubkey::new_unique();
1175 let fee_payer_token_account = Pubkey::new_unique();
1176 let delegate = Pubkey::new_unique();
1177
1178 let rpc_client = RpcMockBuilder::new().build();
1181 let mut policy = FeePayerPolicy::default();
1182 policy.spl_token.allow_approve = true;
1183 setup_spl_config_with_policy(policy);
1184
1185 let validator = TransactionValidator::new(fee_payer).unwrap();
1186
1187 let approve_ix = spl_token_interface::instruction::approve(
1188 &spl_token_interface::id(),
1189 &fee_payer_token_account,
1190 &delegate,
1191 &fee_payer,
1192 &[],
1193 1000,
1194 )
1195 .unwrap();
1196
1197 let message = VersionedMessage::Legacy(Message::new(&[approve_ix], Some(&fee_payer)));
1198 let mut transaction =
1199 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1200 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
1202
1203 let rpc_client = RpcMockBuilder::new().build();
1205 let mut policy = FeePayerPolicy::default();
1206 policy.spl_token.allow_approve = false;
1207 setup_spl_config_with_policy(policy);
1208
1209 let validator = TransactionValidator::new(fee_payer).unwrap();
1210
1211 let approve_ix = spl_token_interface::instruction::approve(
1212 &spl_token_interface::id(),
1213 &fee_payer_token_account,
1214 &delegate,
1215 &fee_payer,
1216 &[],
1217 1000,
1218 )
1219 .unwrap();
1220
1221 let message = VersionedMessage::Legacy(Message::new(&[approve_ix], Some(&fee_payer)));
1222 let mut transaction =
1223 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1224
1225 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1227
1228 let mint = Pubkey::new_unique();
1230 let approve_checked_ix = spl_token_interface::instruction::approve_checked(
1231 &spl_token_interface::id(),
1232 &fee_payer_token_account,
1233 &mint,
1234 &delegate,
1235 &fee_payer,
1236 &[],
1237 1000,
1238 2,
1239 )
1240 .unwrap();
1241
1242 let message =
1243 VersionedMessage::Legacy(Message::new(&[approve_checked_ix], Some(&fee_payer)));
1244 let mut transaction =
1245 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1246
1247 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1249 }
1250
1251 #[tokio::test]
1252 #[serial]
1253 async fn test_fee_payer_policy_token2022_burn() {
1254 let fee_payer = Pubkey::new_unique();
1255 let fee_payer_token_account = Pubkey::new_unique();
1256 let mint = Pubkey::new_unique();
1257
1258 let rpc_client = RpcMockBuilder::new().build();
1261 let mut policy = FeePayerPolicy::default();
1262 policy.token_2022.allow_burn = false;
1263 setup_token2022_config_with_policy(policy);
1264
1265 let validator = TransactionValidator::new(fee_payer).unwrap();
1266
1267 let burn_ix = spl_token_2022_interface::instruction::burn(
1268 &spl_token_2022_interface::id(),
1269 &fee_payer_token_account,
1270 &mint,
1271 &fee_payer,
1272 &[],
1273 1000,
1274 )
1275 .unwrap();
1276
1277 let message = VersionedMessage::Legacy(Message::new(&[burn_ix], Some(&fee_payer)));
1278 let mut transaction =
1279 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1280 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1282 }
1283
1284 #[tokio::test]
1285 #[serial]
1286 async fn test_fee_payer_policy_token2022_close_account() {
1287 let fee_payer = Pubkey::new_unique();
1288 let fee_payer_token_account = Pubkey::new_unique();
1289 let destination = Pubkey::new_unique();
1290
1291 let rpc_client = RpcMockBuilder::new().build();
1294 let mut policy = FeePayerPolicy::default();
1295 policy.token_2022.allow_close_account = false;
1296 setup_token2022_config_with_policy(policy);
1297
1298 let validator = TransactionValidator::new(fee_payer).unwrap();
1299
1300 let close_ix = spl_token_2022_interface::instruction::close_account(
1301 &spl_token_2022_interface::id(),
1302 &fee_payer_token_account,
1303 &destination,
1304 &fee_payer,
1305 &[],
1306 )
1307 .unwrap();
1308
1309 let message = VersionedMessage::Legacy(Message::new(&[close_ix], Some(&fee_payer)));
1310 let mut transaction =
1311 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1312 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1314 }
1315
1316 #[tokio::test]
1317 #[serial]
1318 async fn test_fee_payer_policy_token2022_approve() {
1319 let fee_payer = Pubkey::new_unique();
1320 let fee_payer_token_account = Pubkey::new_unique();
1321 let delegate = Pubkey::new_unique();
1322
1323 let rpc_client = RpcMockBuilder::new().build();
1326 let mut policy = FeePayerPolicy::default();
1327 policy.token_2022.allow_approve = true;
1328 setup_token2022_config_with_policy(policy);
1329
1330 let validator = TransactionValidator::new(fee_payer).unwrap();
1331
1332 let approve_ix = spl_token_2022_interface::instruction::approve(
1333 &spl_token_2022_interface::id(),
1334 &fee_payer_token_account,
1335 &delegate,
1336 &fee_payer,
1337 &[],
1338 1000,
1339 )
1340 .unwrap();
1341
1342 let message = VersionedMessage::Legacy(Message::new(&[approve_ix], Some(&fee_payer)));
1343 let mut transaction =
1344 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1345 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
1347
1348 let rpc_client = RpcMockBuilder::new().build();
1351 let mut policy = FeePayerPolicy::default();
1352 policy.token_2022.allow_approve = false;
1353 setup_token2022_config_with_policy(policy);
1354
1355 let validator = TransactionValidator::new(fee_payer).unwrap();
1356
1357 let approve_ix = spl_token_2022_interface::instruction::approve(
1358 &spl_token_2022_interface::id(),
1359 &fee_payer_token_account,
1360 &delegate,
1361 &fee_payer,
1362 &[],
1363 1000,
1364 )
1365 .unwrap();
1366
1367 let message = VersionedMessage::Legacy(Message::new(&[approve_ix], Some(&fee_payer)));
1368 let mut transaction =
1369 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1370
1371 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1373
1374 let mint = Pubkey::new_unique();
1376 let approve_checked_ix = spl_token_2022_interface::instruction::approve_checked(
1377 &spl_token_2022_interface::id(),
1378 &fee_payer_token_account,
1379 &mint,
1380 &delegate,
1381 &fee_payer,
1382 &[],
1383 1000,
1384 2,
1385 )
1386 .unwrap();
1387
1388 let message =
1389 VersionedMessage::Legacy(Message::new(&[approve_checked_ix], Some(&fee_payer)));
1390 let mut transaction =
1391 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1392
1393 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1395 }
1396
1397 #[tokio::test]
1398 #[serial]
1399 async fn test_fee_payer_policy_create_account() {
1400 use solana_system_interface::instruction::create_account;
1401
1402 let fee_payer = Pubkey::new_unique();
1403 let new_account = Pubkey::new_unique();
1404 let owner = Pubkey::new_unique();
1405
1406 let rpc_client = RpcMockBuilder::new().build();
1408 let mut policy = FeePayerPolicy::default();
1409 policy.system.allow_create_account = true;
1410 setup_config_with_policy(policy);
1411
1412 let validator = TransactionValidator::new(fee_payer).unwrap();
1413 let instruction = create_account(&fee_payer, &new_account, 1000, 100, &owner);
1414 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1415 let mut transaction =
1416 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1417 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
1418
1419 let rpc_client = RpcMockBuilder::new().build();
1421 let mut policy = FeePayerPolicy::default();
1422 policy.system.allow_create_account = false;
1423 setup_config_with_policy(policy);
1424
1425 let validator = TransactionValidator::new(fee_payer).unwrap();
1426 let instruction = create_account(&fee_payer, &new_account, 1000, 100, &owner);
1427 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1428 let mut transaction =
1429 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1430 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1431 }
1432
1433 #[tokio::test]
1434 #[serial]
1435 async fn test_fee_payer_policy_allocate() {
1436 use solana_system_interface::instruction::allocate;
1437
1438 let fee_payer = Pubkey::new_unique();
1439
1440 let rpc_client = RpcMockBuilder::new().build();
1442 let mut policy = FeePayerPolicy::default();
1443 policy.system.allow_allocate = true;
1444 setup_config_with_policy(policy);
1445
1446 let validator = TransactionValidator::new(fee_payer).unwrap();
1447 let instruction = allocate(&fee_payer, 100);
1448 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1449 let mut transaction =
1450 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1451 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
1452
1453 let rpc_client = RpcMockBuilder::new().build();
1455 let mut policy = FeePayerPolicy::default();
1456 policy.system.allow_allocate = false;
1457 setup_config_with_policy(policy);
1458
1459 let validator = TransactionValidator::new(fee_payer).unwrap();
1460 let instruction = allocate(&fee_payer, 100);
1461 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1462 let mut transaction =
1463 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1464 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1465 }
1466
1467 #[tokio::test]
1468 #[serial]
1469 async fn test_fee_payer_policy_nonce_initialize() {
1470 use solana_system_interface::instruction::create_nonce_account;
1471
1472 let fee_payer = Pubkey::new_unique();
1473 let nonce_account = Pubkey::new_unique();
1474
1475 let rpc_client = RpcMockBuilder::new().build();
1477 let mut policy = FeePayerPolicy::default();
1478 policy.system.nonce.allow_initialize = true;
1479 setup_config_with_policy(policy);
1480
1481 let validator = TransactionValidator::new(fee_payer).unwrap();
1482 let instructions = create_nonce_account(&fee_payer, &nonce_account, &fee_payer, 1_000_000);
1483 let message =
1485 VersionedMessage::Legacy(Message::new(&[instructions[1].clone()], Some(&fee_payer)));
1486 let mut transaction =
1487 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1488 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
1489
1490 let rpc_client = RpcMockBuilder::new().build();
1492 let mut policy = FeePayerPolicy::default();
1493 policy.system.nonce.allow_initialize = false;
1494 setup_config_with_policy(policy);
1495
1496 let validator = TransactionValidator::new(fee_payer).unwrap();
1497 let instructions = create_nonce_account(&fee_payer, &nonce_account, &fee_payer, 1_000_000);
1498 let message =
1499 VersionedMessage::Legacy(Message::new(&[instructions[1].clone()], Some(&fee_payer)));
1500 let mut transaction =
1501 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1502 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1503 }
1504
1505 #[tokio::test]
1506 #[serial]
1507 async fn test_fee_payer_policy_nonce_advance() {
1508 use solana_system_interface::instruction::advance_nonce_account;
1509
1510 let fee_payer = Pubkey::new_unique();
1511 let nonce_account = Pubkey::new_unique();
1512
1513 let rpc_client = RpcMockBuilder::new().build();
1515 let mut policy = FeePayerPolicy::default();
1516 policy.system.nonce.allow_advance = true;
1517 let config = ConfigMockBuilder::new()
1518 .with_price_source(PriceSource::Mock)
1519 .with_allowed_programs(vec![SYSTEM_PROGRAM_ID.to_string()])
1520 .with_max_allowed_lamports(1_000_000)
1521 .with_fee_payer_policy(policy)
1522 .with_allow_durable_transactions(true)
1523 .build();
1524 update_config(config).unwrap();
1525
1526 let validator = TransactionValidator::new(fee_payer).unwrap();
1527 let instruction = advance_nonce_account(&nonce_account, &fee_payer);
1528 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1529 let mut transaction =
1530 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1531 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
1532
1533 let rpc_client = RpcMockBuilder::new().build();
1535 let mut policy = FeePayerPolicy::default();
1536 policy.system.nonce.allow_advance = false;
1537 let config = ConfigMockBuilder::new()
1538 .with_price_source(PriceSource::Mock)
1539 .with_allowed_programs(vec![SYSTEM_PROGRAM_ID.to_string()])
1540 .with_max_allowed_lamports(1_000_000)
1541 .with_fee_payer_policy(policy)
1542 .with_allow_durable_transactions(true)
1543 .build();
1544 update_config(config).unwrap();
1545
1546 let validator = TransactionValidator::new(fee_payer).unwrap();
1547 let instruction = advance_nonce_account(&nonce_account, &fee_payer);
1548 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1549 let mut transaction =
1550 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1551 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1552 }
1553
1554 #[tokio::test]
1555 #[serial]
1556 async fn test_fee_payer_policy_nonce_withdraw() {
1557 use solana_system_interface::instruction::withdraw_nonce_account;
1558
1559 let fee_payer = Pubkey::new_unique();
1560 let nonce_account = Pubkey::new_unique();
1561 let recipient = Pubkey::new_unique();
1562
1563 let rpc_client = RpcMockBuilder::new().build();
1565 let mut policy = FeePayerPolicy::default();
1566 policy.system.nonce.allow_withdraw = true;
1567 setup_config_with_policy(policy);
1568
1569 let validator = TransactionValidator::new(fee_payer).unwrap();
1570 let instruction = withdraw_nonce_account(&nonce_account, &fee_payer, &recipient, 1000);
1571 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1572 let mut transaction =
1573 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1574 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
1575
1576 let rpc_client = RpcMockBuilder::new().build();
1578 let mut policy = FeePayerPolicy::default();
1579 policy.system.nonce.allow_withdraw = false;
1580 setup_config_with_policy(policy);
1581
1582 let validator = TransactionValidator::new(fee_payer).unwrap();
1583 let instruction = withdraw_nonce_account(&nonce_account, &fee_payer, &recipient, 1000);
1584 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1585 let mut transaction =
1586 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1587 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1588 }
1589
1590 #[tokio::test]
1591 #[serial]
1592 async fn test_fee_payer_policy_nonce_authorize() {
1593 use solana_system_interface::instruction::authorize_nonce_account;
1594
1595 let fee_payer = Pubkey::new_unique();
1596 let nonce_account = Pubkey::new_unique();
1597 let new_authority = Pubkey::new_unique();
1598
1599 let rpc_client = RpcMockBuilder::new().build();
1601 let mut policy = FeePayerPolicy::default();
1602 policy.system.nonce.allow_authorize = true;
1603 setup_config_with_policy(policy);
1604
1605 let validator = TransactionValidator::new(fee_payer).unwrap();
1606 let instruction = authorize_nonce_account(&nonce_account, &fee_payer, &new_authority);
1607 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1608 let mut transaction =
1609 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1610 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
1611
1612 let rpc_client = RpcMockBuilder::new().build();
1614 let mut policy = FeePayerPolicy::default();
1615 policy.system.nonce.allow_authorize = false;
1616 setup_config_with_policy(policy);
1617
1618 let validator = TransactionValidator::new(fee_payer).unwrap();
1619 let instruction = authorize_nonce_account(&nonce_account, &fee_payer, &new_authority);
1620 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1621 let mut transaction =
1622 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1623 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1624 }
1625
1626 #[test]
1627 #[serial]
1628 fn test_strict_pricing_total_exceeds_fixed() {
1629 let mut config = ConfigMockBuilder::new().build();
1630 config.validation.price.model = PriceModel::Fixed {
1631 amount: 5000,
1632 token: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string(),
1633 strict: true,
1634 };
1635 let _ = update_config(config);
1636
1637 let fee_calc = TotalFeeCalculation::new(5000, 3000, 2000, 5000, 0, 0);
1639
1640 let result = TransactionValidator::validate_strict_pricing_with_fee(&fee_calc);
1641
1642 assert!(result.is_err());
1643 if let Err(KoraError::ValidationError(msg)) = result {
1644 assert!(msg.contains("Strict pricing violation"));
1645 assert!(msg.contains("exceeds fixed price"));
1646 } else {
1647 panic!("Expected ValidationError");
1648 }
1649 }
1650
1651 #[test]
1652 #[serial]
1653 fn test_strict_pricing_total_within_fixed() {
1654 let mut config = ConfigMockBuilder::new().build();
1655 config.validation.price.model = PriceModel::Fixed {
1656 amount: 5000,
1657 token: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string(),
1658 strict: true,
1659 };
1660 let _ = update_config(config);
1661
1662 let fee_calc = TotalFeeCalculation::new(5000, 1000, 1000, 1000, 0, 0);
1664
1665 let result = TransactionValidator::validate_strict_pricing_with_fee(&fee_calc);
1666
1667 assert!(result.is_ok());
1668 }
1669
1670 #[test]
1671 #[serial]
1672 fn test_strict_pricing_disabled() {
1673 let mut config = ConfigMockBuilder::new().build();
1674 config.validation.price.model = PriceModel::Fixed {
1675 amount: 5000,
1676 token: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string(),
1677 strict: false, };
1679 let _ = update_config(config);
1680
1681 let fee_calc = TotalFeeCalculation::new(5000, 10000, 0, 0, 0, 0);
1682
1683 let result = TransactionValidator::validate_strict_pricing_with_fee(&fee_calc);
1684
1685 assert!(result.is_ok(), "Should pass when strict=false");
1686 }
1687
1688 #[test]
1689 #[serial]
1690 fn test_strict_pricing_with_margin_pricing() {
1691 use crate::{
1692 fee::price::PriceModel, state::update_config, tests::config_mock::ConfigMockBuilder,
1693 };
1694
1695 let mut config = ConfigMockBuilder::new().build();
1696 config.validation.price.model = PriceModel::Margin { margin: 0.1 };
1697 let _ = update_config(config);
1698
1699 let fee_calc = TotalFeeCalculation::new(5000, 10000, 0, 0, 0, 0);
1700
1701 let result = TransactionValidator::validate_strict_pricing_with_fee(&fee_calc);
1702
1703 assert!(result.is_ok());
1704 }
1705
1706 #[test]
1707 #[serial]
1708 fn test_strict_pricing_exact_match() {
1709 use crate::{
1710 fee::price::PriceModel, state::update_config, tests::config_mock::ConfigMockBuilder,
1711 };
1712
1713 let mut config = ConfigMockBuilder::new().build();
1714 config.validation.price.model = PriceModel::Fixed {
1715 amount: 5000,
1716 token: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string(),
1717 strict: true,
1718 };
1719 let _ = update_config(config);
1720
1721 let fee_calc = TotalFeeCalculation::new(5000, 2000, 1000, 2000, 0, 0);
1723
1724 let result = TransactionValidator::validate_strict_pricing_with_fee(&fee_calc);
1725
1726 assert!(result.is_ok(), "Should pass when total equals fixed price");
1727 }
1728
1729 #[tokio::test]
1730 #[serial]
1731 async fn test_durable_transaction_rejected_by_default() {
1732 use solana_system_interface::instruction::advance_nonce_account;
1733
1734 let fee_payer = Pubkey::new_unique();
1735 let nonce_account = Pubkey::new_unique();
1736 let nonce_authority = Pubkey::new_unique(); setup_default_config();
1740 let rpc_client = RpcMockBuilder::new().build();
1741
1742 let validator = TransactionValidator::new(fee_payer).unwrap();
1743
1744 let instruction = advance_nonce_account(&nonce_account, &nonce_authority);
1746 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1747 let mut transaction =
1748 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1749
1750 let result = validator.validate_transaction(&mut transaction, &rpc_client).await;
1751 assert!(result.is_err());
1752 if let Err(KoraError::InvalidTransaction(msg)) = result {
1753 assert!(msg.contains("Durable transactions"));
1754 assert!(msg.contains("not allowed"));
1755 } else {
1756 panic!("Expected InvalidTransaction error");
1757 }
1758 }
1759
1760 #[tokio::test]
1761 #[serial]
1762 async fn test_durable_transaction_allowed_when_enabled() {
1763 use solana_system_interface::instruction::advance_nonce_account;
1764
1765 let fee_payer = Pubkey::new_unique();
1766 let nonce_account = Pubkey::new_unique();
1767 let nonce_authority = Pubkey::new_unique(); let config = ConfigMockBuilder::new()
1771 .with_price_source(PriceSource::Mock)
1772 .with_allowed_programs(vec![SYSTEM_PROGRAM_ID.to_string()])
1773 .with_max_allowed_lamports(1_000_000)
1774 .with_fee_payer_policy(FeePayerPolicy::default())
1775 .with_allow_durable_transactions(true)
1776 .build();
1777 update_config(config).unwrap();
1778
1779 let rpc_client = RpcMockBuilder::new().build();
1780 let validator = TransactionValidator::new(fee_payer).unwrap();
1781
1782 let instruction = advance_nonce_account(&nonce_account, &nonce_authority);
1784 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1785 let mut transaction =
1786 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1787
1788 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
1790 }
1791
1792 #[tokio::test]
1793 #[serial]
1794 async fn test_non_durable_transaction_passes() {
1795 let fee_payer = Pubkey::new_unique();
1796 let sender = Pubkey::new_unique();
1797 let recipient = Pubkey::new_unique();
1798
1799 setup_default_config();
1801 let rpc_client = RpcMockBuilder::new().build();
1802
1803 let validator = TransactionValidator::new(fee_payer).unwrap();
1804
1805 let instruction = transfer(&sender, &recipient, 1000);
1807 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1808 let mut transaction =
1809 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1810
1811 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
1813 }
1814}