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::FeePayerPolicy,
409 state::update_config,
410 tests::{config_mock::ConfigMockBuilder, rpc_mock::RpcMockBuilder},
411 transaction::TransactionUtil,
412 };
413 use serial_test::serial;
414
415 use super::*;
416 use solana_message::{Message, VersionedMessage};
417 use solana_sdk::instruction::Instruction;
418 use solana_system_interface::{
419 instruction::{
420 assign, create_account, create_account_with_seed, transfer, transfer_with_seed,
421 },
422 program::ID as SYSTEM_PROGRAM_ID,
423 };
424
425 fn setup_default_config() {
427 let config = ConfigMockBuilder::new()
428 .with_price_source(PriceSource::Mock)
429 .with_allowed_programs(vec![SYSTEM_PROGRAM_ID.to_string()])
430 .with_max_allowed_lamports(1_000_000)
431 .with_fee_payer_policy(FeePayerPolicy::default())
432 .build();
433 update_config(config).unwrap();
434 }
435
436 fn setup_config_with_policy(policy: FeePayerPolicy) {
437 let config = ConfigMockBuilder::new()
438 .with_price_source(PriceSource::Mock)
439 .with_allowed_programs(vec![SYSTEM_PROGRAM_ID.to_string()])
440 .with_max_allowed_lamports(1_000_000)
441 .with_fee_payer_policy(policy)
442 .build();
443 update_config(config).unwrap();
444 }
445
446 fn setup_spl_config_with_policy(policy: FeePayerPolicy) {
447 let config = ConfigMockBuilder::new()
448 .with_price_source(PriceSource::Mock)
449 .with_allowed_programs(vec![spl_token_interface::id().to_string()])
450 .with_max_allowed_lamports(1_000_000)
451 .with_fee_payer_policy(policy)
452 .build();
453 update_config(config).unwrap();
454 }
455
456 fn setup_token2022_config_with_policy(policy: FeePayerPolicy) {
457 let config = ConfigMockBuilder::new()
458 .with_price_source(PriceSource::Mock)
459 .with_allowed_programs(vec![spl_token_2022_interface::id().to_string()])
460 .with_max_allowed_lamports(1_000_000)
461 .with_fee_payer_policy(policy)
462 .build();
463 update_config(config).unwrap();
464 }
465
466 #[tokio::test]
467 #[serial]
468 async fn test_validate_transaction() {
469 let fee_payer = Pubkey::new_unique();
470 setup_default_config();
471 let rpc_client = RpcMockBuilder::new().build();
472
473 let validator = TransactionValidator::new(fee_payer).unwrap();
474
475 let recipient = Pubkey::new_unique();
476 let sender = Pubkey::new_unique();
477 let instruction = transfer(&sender, &recipient, 100_000);
478 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
479 let mut transaction =
480 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
481
482 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
483 }
484
485 #[tokio::test]
486 #[serial]
487 async fn test_transfer_amount_limits() {
488 let fee_payer = Pubkey::new_unique();
489 setup_default_config();
490 let rpc_client = RpcMockBuilder::new().build();
491
492 let validator = TransactionValidator::new(fee_payer).unwrap();
493 let sender = Pubkey::new_unique();
494 let recipient = Pubkey::new_unique();
495
496 let instruction = transfer(&sender, &recipient, 2_000_000);
498 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
499 let mut transaction =
500 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
501
502 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
503
504 let instructions =
506 vec![transfer(&sender, &recipient, 500_000), transfer(&sender, &recipient, 500_000)];
507 let message = VersionedMessage::Legacy(Message::new(&instructions, Some(&fee_payer)));
508 let mut transaction =
509 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
510 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
511 }
512
513 #[tokio::test]
514 #[serial]
515 async fn test_validate_programs() {
516 let fee_payer = Pubkey::new_unique();
517 setup_default_config();
518 let rpc_client = RpcMockBuilder::new().build();
519
520 let validator = TransactionValidator::new(fee_payer).unwrap();
521 let sender = Pubkey::new_unique();
522 let recipient = Pubkey::new_unique();
523
524 let instruction = transfer(&sender, &recipient, 1000);
526 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
527 let mut transaction =
528 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
529 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
530
531 let fake_program = Pubkey::new_unique();
533 let instruction = Instruction::new_with_bincode(
535 fake_program,
536 &[0u8],
537 vec![], );
539 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
540 let mut transaction =
541 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
542 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
543 }
544
545 #[tokio::test]
546 #[serial]
547 async fn test_validate_signatures() {
548 let fee_payer = Pubkey::new_unique();
549 let config = ConfigMockBuilder::new()
550 .with_price_source(PriceSource::Mock)
551 .with_allowed_programs(vec![SYSTEM_PROGRAM_ID.to_string()])
552 .with_max_allowed_lamports(1_000_000)
553 .with_max_signatures(2)
554 .with_fee_payer_policy(FeePayerPolicy::default())
555 .build();
556 update_config(config).unwrap();
557
558 let rpc_client = RpcMockBuilder::new().build();
559 let validator = TransactionValidator::new(fee_payer).unwrap();
560 let sender = Pubkey::new_unique();
561 let recipient = Pubkey::new_unique();
562
563 let instructions = vec![
565 transfer(&sender, &recipient, 1000),
566 transfer(&sender, &recipient, 1000),
567 transfer(&sender, &recipient, 1000),
568 ];
569 let message = VersionedMessage::Legacy(Message::new(&instructions, Some(&fee_payer)));
570 let mut transaction =
571 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
572 transaction.transaction.signatures = vec![Default::default(); 3]; assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
574 }
575
576 #[tokio::test]
577 #[serial]
578 async fn test_sign_and_send_transaction_mode() {
579 let fee_payer = Pubkey::new_unique();
580 setup_default_config();
581 let rpc_client = RpcMockBuilder::new().build();
582
583 let validator = TransactionValidator::new(fee_payer).unwrap();
584 let sender = Pubkey::new_unique();
585 let recipient = Pubkey::new_unique();
586
587 let instruction = transfer(&sender, &recipient, 1000);
589 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
590 let mut transaction =
591 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
592 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
593
594 let instruction = transfer(&sender, &recipient, 1000);
596 let message = VersionedMessage::Legacy(Message::new(&[instruction], None)); let mut transaction =
598 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
599 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
600 }
601
602 #[tokio::test]
603 #[serial]
604 async fn test_empty_transaction() {
605 let fee_payer = Pubkey::new_unique();
606 setup_default_config();
607 let rpc_client = RpcMockBuilder::new().build();
608
609 let validator = TransactionValidator::new(fee_payer).unwrap();
610
611 let message = VersionedMessage::Legacy(Message::new(&[], Some(&fee_payer)));
613 let mut transaction =
614 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
615 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
616 }
617
618 #[tokio::test]
619 #[serial]
620 async fn test_disallowed_accounts() {
621 let fee_payer = Pubkey::new_unique();
622 let config = ConfigMockBuilder::new()
623 .with_price_source(PriceSource::Mock)
624 .with_allowed_programs(vec![SYSTEM_PROGRAM_ID.to_string()])
625 .with_max_allowed_lamports(1_000_000)
626 .with_disallowed_accounts(vec![
627 "hndXZGK45hCxfBYvxejAXzCfCujoqkNf7rk4sTB8pek".to_string()
628 ])
629 .with_fee_payer_policy(FeePayerPolicy::default())
630 .build();
631 update_config(config).unwrap();
632
633 let rpc_client = RpcMockBuilder::new().build();
634 let validator = TransactionValidator::new(fee_payer).unwrap();
635 let instruction = transfer(
636 &Pubkey::from_str("hndXZGK45hCxfBYvxejAXzCfCujoqkNf7rk4sTB8pek").unwrap(),
637 &fee_payer,
638 1000,
639 );
640 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
641 let mut transaction =
642 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
643 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
644 }
645
646 #[tokio::test]
647 #[serial]
648 async fn test_fee_payer_policy_sol_transfers() {
649 let fee_payer = Pubkey::new_unique();
650 let recipient = Pubkey::new_unique();
651
652 let rpc_client = RpcMockBuilder::new().build();
654 let mut policy = FeePayerPolicy::default();
655 policy.system.allow_transfer = true;
656 setup_config_with_policy(policy);
657
658 let validator = TransactionValidator::new(fee_payer).unwrap();
659
660 let instruction = transfer(&fee_payer, &recipient, 1000);
661
662 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
663 let mut transaction =
664 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
665 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
666
667 let rpc_client = RpcMockBuilder::new().build();
669 let mut policy = FeePayerPolicy::default();
670 policy.system.allow_transfer = false;
671 setup_config_with_policy(policy);
672
673 let validator = TransactionValidator::new(fee_payer).unwrap();
674
675 let instruction = transfer(&fee_payer, &recipient, 1000);
676 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
677 let mut transaction =
678 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
679 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
680 }
681
682 #[tokio::test]
683 #[serial]
684 async fn test_fee_payer_policy_assign() {
685 let fee_payer = Pubkey::new_unique();
686 let new_owner = Pubkey::new_unique();
687
688 let rpc_client = RpcMockBuilder::new().build();
691
692 let mut policy = FeePayerPolicy::default();
693 policy.system.allow_assign = true;
694 setup_config_with_policy(policy);
695
696 let validator = TransactionValidator::new(fee_payer).unwrap();
697
698 let instruction = assign(&fee_payer, &new_owner);
699 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
700 let mut transaction =
701 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
702 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
703
704 let rpc_client = RpcMockBuilder::new().build();
707
708 let mut policy = FeePayerPolicy::default();
709 policy.system.allow_assign = false;
710 setup_config_with_policy(policy);
711
712 let validator = TransactionValidator::new(fee_payer).unwrap();
713
714 let instruction = assign(&fee_payer, &new_owner);
715 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
716 let mut transaction =
717 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
718 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
719 }
720
721 #[tokio::test]
722 #[serial]
723 async fn test_fee_payer_policy_spl_transfers() {
724 let fee_payer = Pubkey::new_unique();
725
726 let fee_payer_token_account = Pubkey::new_unique();
727 let recipient_token_account = Pubkey::new_unique();
728
729 let rpc_client = RpcMockBuilder::new().build();
731
732 let mut policy = FeePayerPolicy::default();
733 policy.spl_token.allow_transfer = true;
734 setup_spl_config_with_policy(policy);
735
736 let validator = TransactionValidator::new(fee_payer).unwrap();
737
738 let transfer_ix = spl_token_interface::instruction::transfer(
739 &spl_token_interface::id(),
740 &fee_payer_token_account,
741 &recipient_token_account,
742 &fee_payer, &[],
744 1000,
745 )
746 .unwrap();
747
748 let message = VersionedMessage::Legacy(Message::new(&[transfer_ix], Some(&fee_payer)));
749 let mut transaction =
750 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
751 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
752
753 let rpc_client = RpcMockBuilder::new().build();
755
756 let mut policy = FeePayerPolicy::default();
757 policy.spl_token.allow_transfer = false;
758 setup_spl_config_with_policy(policy);
759
760 let validator = TransactionValidator::new(fee_payer).unwrap();
761
762 let transfer_ix = spl_token_interface::instruction::transfer(
763 &spl_token_interface::id(),
764 &fee_payer_token_account,
765 &recipient_token_account,
766 &fee_payer, &[],
768 1000,
769 )
770 .unwrap();
771
772 let message = VersionedMessage::Legacy(Message::new(&[transfer_ix], Some(&fee_payer)));
773 let mut transaction =
774 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
775 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
776
777 let other_signer = Pubkey::new_unique();
779 let transfer_ix = spl_token_interface::instruction::transfer(
780 &spl_token_interface::id(),
781 &fee_payer_token_account,
782 &recipient_token_account,
783 &other_signer, &[],
785 1000,
786 )
787 .unwrap();
788
789 let message = VersionedMessage::Legacy(Message::new(&[transfer_ix], Some(&fee_payer)));
790 let mut transaction =
791 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
792 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
793 }
794
795 #[tokio::test]
796 #[serial]
797 async fn test_fee_payer_policy_token2022_transfers() {
798 let fee_payer = Pubkey::new_unique();
799
800 let fee_payer_token_account = Pubkey::new_unique();
801 let recipient_token_account = Pubkey::new_unique();
802 let mint = Pubkey::new_unique();
803
804 let rpc_client = RpcMockBuilder::new()
806 .with_mint_account(2) .build();
808 let mut policy = FeePayerPolicy::default();
810 policy.token_2022.allow_transfer = true;
811 setup_token2022_config_with_policy(policy);
812
813 let validator = TransactionValidator::new(fee_payer).unwrap();
814
815 let transfer_ix = spl_token_2022_interface::instruction::transfer_checked(
816 &spl_token_2022_interface::id(),
817 &fee_payer_token_account,
818 &mint,
819 &recipient_token_account,
820 &fee_payer, &[],
822 1,
823 2,
824 )
825 .unwrap();
826
827 let message = VersionedMessage::Legacy(Message::new(&[transfer_ix], Some(&fee_payer)));
828 let mut transaction =
829 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
830 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
831
832 let rpc_client = RpcMockBuilder::new()
834 .with_mint_account(2) .build();
836 let mut policy = FeePayerPolicy::default();
837 policy.token_2022.allow_transfer = false;
838 setup_token2022_config_with_policy(policy);
839
840 let validator = TransactionValidator::new(fee_payer).unwrap();
841
842 let transfer_ix = spl_token_2022_interface::instruction::transfer_checked(
843 &spl_token_2022_interface::id(),
844 &fee_payer_token_account,
845 &mint,
846 &recipient_token_account,
847 &fee_payer, &[],
849 1000,
850 2,
851 )
852 .unwrap();
853
854 let message = VersionedMessage::Legacy(Message::new(&[transfer_ix], Some(&fee_payer)));
855 let mut transaction =
856 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
857
858 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
860
861 let other_signer = Pubkey::new_unique();
863 let transfer_ix = spl_token_2022_interface::instruction::transfer_checked(
864 &spl_token_2022_interface::id(),
865 &fee_payer_token_account,
866 &mint,
867 &recipient_token_account,
868 &other_signer, &[],
870 1000,
871 2,
872 )
873 .unwrap();
874
875 let message = VersionedMessage::Legacy(Message::new(&[transfer_ix], Some(&fee_payer)));
876 let mut transaction =
877 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
878
879 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
881 }
882
883 #[tokio::test]
884 #[serial]
885 async fn test_calculate_total_outflow() {
886 let fee_payer = Pubkey::new_unique();
887 let config = ConfigMockBuilder::new()
888 .with_price_source(PriceSource::Mock)
889 .with_allowed_programs(vec![SYSTEM_PROGRAM_ID.to_string()])
890 .with_max_allowed_lamports(10_000_000)
891 .with_fee_payer_policy(FeePayerPolicy::default())
892 .build();
893 update_config(config).unwrap();
894
895 let rpc_client = RpcMockBuilder::new().build();
896 let validator = TransactionValidator::new(fee_payer).unwrap();
897
898 let recipient = Pubkey::new_unique();
900 let transfer_instruction = transfer(&fee_payer, &recipient, 100_000);
901 let message =
902 VersionedMessage::Legacy(Message::new(&[transfer_instruction], Some(&fee_payer)));
903 let mut transaction =
904 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
905 let outflow =
906 validator.calculate_total_outflow(&mut transaction, &rpc_client).await.unwrap();
907 assert_eq!(outflow, 100_000, "Transfer from fee payer should add to outflow");
908
909 let sender = Pubkey::new_unique();
911 let transfer_instruction = transfer(&sender, &fee_payer, 50_000);
912 let message =
913 VersionedMessage::Legacy(Message::new(&[transfer_instruction], Some(&fee_payer)));
914 let mut transaction =
915 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
916
917 let outflow =
918 validator.calculate_total_outflow(&mut transaction, &rpc_client).await.unwrap();
919 assert_eq!(outflow, 0, "Transfer to fee payer should subtract from outflow"); let new_account = Pubkey::new_unique();
923 let create_instruction = create_account(
924 &fee_payer,
925 &new_account,
926 200_000, 100, &SYSTEM_PROGRAM_ID,
929 );
930 let message =
931 VersionedMessage::Legacy(Message::new(&[create_instruction], Some(&fee_payer)));
932 let mut transaction =
933 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
934 let outflow =
935 validator.calculate_total_outflow(&mut transaction, &rpc_client).await.unwrap();
936 assert_eq!(outflow, 200_000, "CreateAccount funded by fee payer should add to outflow");
937
938 let create_with_seed_instruction = create_account_with_seed(
940 &fee_payer,
941 &new_account,
942 &fee_payer,
943 "test_seed",
944 300_000, 100, &SYSTEM_PROGRAM_ID,
947 );
948 let message = VersionedMessage::Legacy(Message::new(
949 &[create_with_seed_instruction],
950 Some(&fee_payer),
951 ));
952 let mut transaction =
953 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
954 let outflow =
955 validator.calculate_total_outflow(&mut transaction, &rpc_client).await.unwrap();
956 assert_eq!(
957 outflow, 300_000,
958 "CreateAccountWithSeed funded by fee payer should add to outflow"
959 );
960
961 let transfer_with_seed_instruction = transfer_with_seed(
963 &fee_payer,
964 &fee_payer,
965 "test_seed".to_string(),
966 &SYSTEM_PROGRAM_ID,
967 &recipient,
968 150_000,
969 );
970 let message = VersionedMessage::Legacy(Message::new(
971 &[transfer_with_seed_instruction],
972 Some(&fee_payer),
973 ));
974 let mut transaction =
975 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
976 let outflow =
977 validator.calculate_total_outflow(&mut transaction, &rpc_client).await.unwrap();
978 assert_eq!(outflow, 150_000, "TransferWithSeed from fee payer should add to outflow");
979
980 let instructions = vec![
982 transfer(&fee_payer, &recipient, 100_000), transfer(&sender, &fee_payer, 30_000), create_account(&fee_payer, &new_account, 50_000, 100, &SYSTEM_PROGRAM_ID), ];
986 let message = VersionedMessage::Legacy(Message::new(&instructions, Some(&fee_payer)));
987 let mut transaction =
988 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
989 let outflow =
990 validator.calculate_total_outflow(&mut transaction, &rpc_client).await.unwrap();
991 assert_eq!(
992 outflow, 120_000,
993 "Multiple instructions should sum correctly: 100000 - 30000 + 50000 = 120000"
994 );
995
996 let other_sender = Pubkey::new_unique();
998 let transfer_instruction = transfer(&other_sender, &recipient, 500_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(&mut transaction, &rpc_client).await.unwrap();
1005 assert_eq!(outflow, 0, "Transfer from other account should not affect outflow");
1006
1007 let other_funder = Pubkey::new_unique();
1009 let create_instruction =
1010 create_account(&other_funder, &new_account, 1_000_000, 100, &SYSTEM_PROGRAM_ID);
1011 let message =
1012 VersionedMessage::Legacy(Message::new(&[create_instruction], Some(&fee_payer)));
1013 let mut transaction =
1014 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1015 let outflow =
1016 validator.calculate_total_outflow(&mut transaction, &rpc_client).await.unwrap();
1017 assert_eq!(outflow, 0, "CreateAccount funded by other account should not affect outflow");
1018 }
1019
1020 #[tokio::test]
1021 #[serial]
1022 async fn test_fee_payer_policy_burn() {
1023 let fee_payer = Pubkey::new_unique();
1024 let fee_payer_token_account = Pubkey::new_unique();
1025 let mint = Pubkey::new_unique();
1026
1027 let rpc_client = RpcMockBuilder::new().build();
1030 let mut policy = FeePayerPolicy::default();
1031 policy.spl_token.allow_burn = true;
1032 setup_spl_config_with_policy(policy);
1033
1034 let validator = TransactionValidator::new(fee_payer).unwrap();
1035
1036 let burn_ix = spl_token_interface::instruction::burn(
1037 &spl_token_interface::id(),
1038 &fee_payer_token_account,
1039 &mint,
1040 &fee_payer,
1041 &[],
1042 1000,
1043 )
1044 .unwrap();
1045
1046 let message = VersionedMessage::Legacy(Message::new(&[burn_ix], Some(&fee_payer)));
1047 let mut transaction =
1048 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1049 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
1051
1052 let rpc_client = RpcMockBuilder::new().build();
1055 let mut policy = FeePayerPolicy::default();
1056 policy.spl_token.allow_burn = false;
1057 setup_spl_config_with_policy(policy);
1058
1059 let validator = TransactionValidator::new(fee_payer).unwrap();
1060
1061 let burn_ix = spl_token_interface::instruction::burn(
1062 &spl_token_interface::id(),
1063 &fee_payer_token_account,
1064 &mint,
1065 &fee_payer,
1066 &[],
1067 1000,
1068 )
1069 .unwrap();
1070
1071 let message = VersionedMessage::Legacy(Message::new(&[burn_ix], Some(&fee_payer)));
1072 let mut transaction =
1073 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1074
1075 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1077
1078 let burn_checked_ix = spl_token_interface::instruction::burn_checked(
1080 &spl_token_interface::id(),
1081 &fee_payer_token_account,
1082 &mint,
1083 &fee_payer,
1084 &[],
1085 1000,
1086 2,
1087 )
1088 .unwrap();
1089
1090 let message = VersionedMessage::Legacy(Message::new(&[burn_checked_ix], Some(&fee_payer)));
1091 let mut transaction =
1092 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1093
1094 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1096 }
1097
1098 #[tokio::test]
1099 #[serial]
1100 async fn test_fee_payer_policy_close_account() {
1101 let fee_payer = Pubkey::new_unique();
1102 let fee_payer_token_account = Pubkey::new_unique();
1103 let destination = Pubkey::new_unique();
1104
1105 let rpc_client = RpcMockBuilder::new().build();
1108 let mut policy = FeePayerPolicy::default();
1109 policy.spl_token.allow_close_account = true;
1110 setup_spl_config_with_policy(policy);
1111
1112 let validator = TransactionValidator::new(fee_payer).unwrap();
1113
1114 let close_ix = spl_token_interface::instruction::close_account(
1115 &spl_token_interface::id(),
1116 &fee_payer_token_account,
1117 &destination,
1118 &fee_payer,
1119 &[],
1120 )
1121 .unwrap();
1122
1123 let message = VersionedMessage::Legacy(Message::new(&[close_ix], Some(&fee_payer)));
1124 let mut transaction =
1125 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1126 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
1128
1129 let rpc_client = RpcMockBuilder::new().build();
1131 let mut policy = FeePayerPolicy::default();
1132 policy.spl_token.allow_close_account = false;
1133 setup_spl_config_with_policy(policy);
1134
1135 let validator = TransactionValidator::new(fee_payer).unwrap();
1136
1137 let close_ix = spl_token_interface::instruction::close_account(
1138 &spl_token_interface::id(),
1139 &fee_payer_token_account,
1140 &destination,
1141 &fee_payer,
1142 &[],
1143 )
1144 .unwrap();
1145
1146 let message = VersionedMessage::Legacy(Message::new(&[close_ix], Some(&fee_payer)));
1147 let mut transaction =
1148 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1149
1150 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1152 }
1153
1154 #[tokio::test]
1155 #[serial]
1156 async fn test_fee_payer_policy_approve() {
1157 let fee_payer = Pubkey::new_unique();
1158 let fee_payer_token_account = Pubkey::new_unique();
1159 let delegate = Pubkey::new_unique();
1160
1161 let rpc_client = RpcMockBuilder::new().build();
1164 let mut policy = FeePayerPolicy::default();
1165 policy.spl_token.allow_approve = true;
1166 setup_spl_config_with_policy(policy);
1167
1168 let validator = TransactionValidator::new(fee_payer).unwrap();
1169
1170 let approve_ix = spl_token_interface::instruction::approve(
1171 &spl_token_interface::id(),
1172 &fee_payer_token_account,
1173 &delegate,
1174 &fee_payer,
1175 &[],
1176 1000,
1177 )
1178 .unwrap();
1179
1180 let message = VersionedMessage::Legacy(Message::new(&[approve_ix], Some(&fee_payer)));
1181 let mut transaction =
1182 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1183 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
1185
1186 let rpc_client = RpcMockBuilder::new().build();
1188 let mut policy = FeePayerPolicy::default();
1189 policy.spl_token.allow_approve = false;
1190 setup_spl_config_with_policy(policy);
1191
1192 let validator = TransactionValidator::new(fee_payer).unwrap();
1193
1194 let approve_ix = spl_token_interface::instruction::approve(
1195 &spl_token_interface::id(),
1196 &fee_payer_token_account,
1197 &delegate,
1198 &fee_payer,
1199 &[],
1200 1000,
1201 )
1202 .unwrap();
1203
1204 let message = VersionedMessage::Legacy(Message::new(&[approve_ix], Some(&fee_payer)));
1205 let mut transaction =
1206 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1207
1208 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1210
1211 let mint = Pubkey::new_unique();
1213 let approve_checked_ix = spl_token_interface::instruction::approve_checked(
1214 &spl_token_interface::id(),
1215 &fee_payer_token_account,
1216 &mint,
1217 &delegate,
1218 &fee_payer,
1219 &[],
1220 1000,
1221 2,
1222 )
1223 .unwrap();
1224
1225 let message =
1226 VersionedMessage::Legacy(Message::new(&[approve_checked_ix], Some(&fee_payer)));
1227 let mut transaction =
1228 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1229
1230 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1232 }
1233
1234 #[tokio::test]
1235 #[serial]
1236 async fn test_fee_payer_policy_token2022_burn() {
1237 let fee_payer = Pubkey::new_unique();
1238 let fee_payer_token_account = Pubkey::new_unique();
1239 let mint = Pubkey::new_unique();
1240
1241 let rpc_client = RpcMockBuilder::new().build();
1244 let mut policy = FeePayerPolicy::default();
1245 policy.token_2022.allow_burn = false;
1246 setup_token2022_config_with_policy(policy);
1247
1248 let validator = TransactionValidator::new(fee_payer).unwrap();
1249
1250 let burn_ix = spl_token_2022_interface::instruction::burn(
1251 &spl_token_2022_interface::id(),
1252 &fee_payer_token_account,
1253 &mint,
1254 &fee_payer,
1255 &[],
1256 1000,
1257 )
1258 .unwrap();
1259
1260 let message = VersionedMessage::Legacy(Message::new(&[burn_ix], Some(&fee_payer)));
1261 let mut transaction =
1262 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1263 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1265 }
1266
1267 #[tokio::test]
1268 #[serial]
1269 async fn test_fee_payer_policy_token2022_close_account() {
1270 let fee_payer = Pubkey::new_unique();
1271 let fee_payer_token_account = Pubkey::new_unique();
1272 let destination = Pubkey::new_unique();
1273
1274 let rpc_client = RpcMockBuilder::new().build();
1277 let mut policy = FeePayerPolicy::default();
1278 policy.token_2022.allow_close_account = false;
1279 setup_token2022_config_with_policy(policy);
1280
1281 let validator = TransactionValidator::new(fee_payer).unwrap();
1282
1283 let close_ix = spl_token_2022_interface::instruction::close_account(
1284 &spl_token_2022_interface::id(),
1285 &fee_payer_token_account,
1286 &destination,
1287 &fee_payer,
1288 &[],
1289 )
1290 .unwrap();
1291
1292 let message = VersionedMessage::Legacy(Message::new(&[close_ix], Some(&fee_payer)));
1293 let mut transaction =
1294 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1295 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1297 }
1298
1299 #[tokio::test]
1300 #[serial]
1301 async fn test_fee_payer_policy_token2022_approve() {
1302 let fee_payer = Pubkey::new_unique();
1303 let fee_payer_token_account = Pubkey::new_unique();
1304 let delegate = Pubkey::new_unique();
1305
1306 let rpc_client = RpcMockBuilder::new().build();
1309 let mut policy = FeePayerPolicy::default();
1310 policy.token_2022.allow_approve = true;
1311 setup_token2022_config_with_policy(policy);
1312
1313 let validator = TransactionValidator::new(fee_payer).unwrap();
1314
1315 let approve_ix = spl_token_2022_interface::instruction::approve(
1316 &spl_token_2022_interface::id(),
1317 &fee_payer_token_account,
1318 &delegate,
1319 &fee_payer,
1320 &[],
1321 1000,
1322 )
1323 .unwrap();
1324
1325 let message = VersionedMessage::Legacy(Message::new(&[approve_ix], Some(&fee_payer)));
1326 let mut transaction =
1327 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1328 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
1330
1331 let rpc_client = RpcMockBuilder::new().build();
1334 let mut policy = FeePayerPolicy::default();
1335 policy.token_2022.allow_approve = false;
1336 setup_token2022_config_with_policy(policy);
1337
1338 let validator = TransactionValidator::new(fee_payer).unwrap();
1339
1340 let approve_ix = spl_token_2022_interface::instruction::approve(
1341 &spl_token_2022_interface::id(),
1342 &fee_payer_token_account,
1343 &delegate,
1344 &fee_payer,
1345 &[],
1346 1000,
1347 )
1348 .unwrap();
1349
1350 let message = VersionedMessage::Legacy(Message::new(&[approve_ix], Some(&fee_payer)));
1351 let mut transaction =
1352 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1353
1354 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1356
1357 let mint = Pubkey::new_unique();
1359 let approve_checked_ix = spl_token_2022_interface::instruction::approve_checked(
1360 &spl_token_2022_interface::id(),
1361 &fee_payer_token_account,
1362 &mint,
1363 &delegate,
1364 &fee_payer,
1365 &[],
1366 1000,
1367 2,
1368 )
1369 .unwrap();
1370
1371 let message =
1372 VersionedMessage::Legacy(Message::new(&[approve_checked_ix], Some(&fee_payer)));
1373 let mut transaction =
1374 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1375
1376 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1378 }
1379
1380 #[tokio::test]
1381 #[serial]
1382 async fn test_fee_payer_policy_create_account() {
1383 use solana_system_interface::instruction::create_account;
1384
1385 let fee_payer = Pubkey::new_unique();
1386 let new_account = Pubkey::new_unique();
1387 let owner = Pubkey::new_unique();
1388
1389 let rpc_client = RpcMockBuilder::new().build();
1391 let mut policy = FeePayerPolicy::default();
1392 policy.system.allow_create_account = true;
1393 setup_config_with_policy(policy);
1394
1395 let validator = TransactionValidator::new(fee_payer).unwrap();
1396 let instruction = create_account(&fee_payer, &new_account, 1000, 100, &owner);
1397 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1398 let mut transaction =
1399 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1400 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
1401
1402 let rpc_client = RpcMockBuilder::new().build();
1404 let mut policy = FeePayerPolicy::default();
1405 policy.system.allow_create_account = false;
1406 setup_config_with_policy(policy);
1407
1408 let validator = TransactionValidator::new(fee_payer).unwrap();
1409 let instruction = create_account(&fee_payer, &new_account, 1000, 100, &owner);
1410 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1411 let mut transaction =
1412 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1413 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1414 }
1415
1416 #[tokio::test]
1417 #[serial]
1418 async fn test_fee_payer_policy_allocate() {
1419 use solana_system_interface::instruction::allocate;
1420
1421 let fee_payer = Pubkey::new_unique();
1422
1423 let rpc_client = RpcMockBuilder::new().build();
1425 let mut policy = FeePayerPolicy::default();
1426 policy.system.allow_allocate = true;
1427 setup_config_with_policy(policy);
1428
1429 let validator = TransactionValidator::new(fee_payer).unwrap();
1430 let instruction = allocate(&fee_payer, 100);
1431 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1432 let mut transaction =
1433 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1434 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
1435
1436 let rpc_client = RpcMockBuilder::new().build();
1438 let mut policy = FeePayerPolicy::default();
1439 policy.system.allow_allocate = false;
1440 setup_config_with_policy(policy);
1441
1442 let validator = TransactionValidator::new(fee_payer).unwrap();
1443 let instruction = allocate(&fee_payer, 100);
1444 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1445 let mut transaction =
1446 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1447 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1448 }
1449
1450 #[tokio::test]
1451 #[serial]
1452 async fn test_fee_payer_policy_nonce_initialize() {
1453 use solana_system_interface::instruction::create_nonce_account;
1454
1455 let fee_payer = Pubkey::new_unique();
1456 let nonce_account = Pubkey::new_unique();
1457
1458 let rpc_client = RpcMockBuilder::new().build();
1460 let mut policy = FeePayerPolicy::default();
1461 policy.system.nonce.allow_initialize = true;
1462 setup_config_with_policy(policy);
1463
1464 let validator = TransactionValidator::new(fee_payer).unwrap();
1465 let instructions = create_nonce_account(&fee_payer, &nonce_account, &fee_payer, 1_000_000);
1466 let message =
1468 VersionedMessage::Legacy(Message::new(&[instructions[1].clone()], Some(&fee_payer)));
1469 let mut transaction =
1470 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1471 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
1472
1473 let rpc_client = RpcMockBuilder::new().build();
1475 let mut policy = FeePayerPolicy::default();
1476 policy.system.nonce.allow_initialize = false;
1477 setup_config_with_policy(policy);
1478
1479 let validator = TransactionValidator::new(fee_payer).unwrap();
1480 let instructions = create_nonce_account(&fee_payer, &nonce_account, &fee_payer, 1_000_000);
1481 let message =
1482 VersionedMessage::Legacy(Message::new(&[instructions[1].clone()], Some(&fee_payer)));
1483 let mut transaction =
1484 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1485 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1486 }
1487
1488 #[tokio::test]
1489 #[serial]
1490 async fn test_fee_payer_policy_nonce_advance() {
1491 use solana_system_interface::instruction::advance_nonce_account;
1492
1493 let fee_payer = Pubkey::new_unique();
1494 let nonce_account = Pubkey::new_unique();
1495
1496 let rpc_client = RpcMockBuilder::new().build();
1498 let mut policy = FeePayerPolicy::default();
1499 policy.system.nonce.allow_advance = true;
1500 let config = ConfigMockBuilder::new()
1501 .with_price_source(PriceSource::Mock)
1502 .with_allowed_programs(vec![SYSTEM_PROGRAM_ID.to_string()])
1503 .with_max_allowed_lamports(1_000_000)
1504 .with_fee_payer_policy(policy)
1505 .with_allow_durable_transactions(true)
1506 .build();
1507 update_config(config).unwrap();
1508
1509 let validator = TransactionValidator::new(fee_payer).unwrap();
1510 let instruction = advance_nonce_account(&nonce_account, &fee_payer);
1511 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1512 let mut transaction =
1513 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1514 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
1515
1516 let rpc_client = RpcMockBuilder::new().build();
1518 let mut policy = FeePayerPolicy::default();
1519 policy.system.nonce.allow_advance = false;
1520 let config = ConfigMockBuilder::new()
1521 .with_price_source(PriceSource::Mock)
1522 .with_allowed_programs(vec![SYSTEM_PROGRAM_ID.to_string()])
1523 .with_max_allowed_lamports(1_000_000)
1524 .with_fee_payer_policy(policy)
1525 .with_allow_durable_transactions(true)
1526 .build();
1527 update_config(config).unwrap();
1528
1529 let validator = TransactionValidator::new(fee_payer).unwrap();
1530 let instruction = advance_nonce_account(&nonce_account, &fee_payer);
1531 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1532 let mut transaction =
1533 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1534 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1535 }
1536
1537 #[tokio::test]
1538 #[serial]
1539 async fn test_fee_payer_policy_nonce_withdraw() {
1540 use solana_system_interface::instruction::withdraw_nonce_account;
1541
1542 let fee_payer = Pubkey::new_unique();
1543 let nonce_account = Pubkey::new_unique();
1544 let recipient = Pubkey::new_unique();
1545
1546 let rpc_client = RpcMockBuilder::new().build();
1548 let mut policy = FeePayerPolicy::default();
1549 policy.system.nonce.allow_withdraw = true;
1550 setup_config_with_policy(policy);
1551
1552 let validator = TransactionValidator::new(fee_payer).unwrap();
1553 let instruction = withdraw_nonce_account(&nonce_account, &fee_payer, &recipient, 1000);
1554 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1555 let mut transaction =
1556 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1557 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
1558
1559 let rpc_client = RpcMockBuilder::new().build();
1561 let mut policy = FeePayerPolicy::default();
1562 policy.system.nonce.allow_withdraw = false;
1563 setup_config_with_policy(policy);
1564
1565 let validator = TransactionValidator::new(fee_payer).unwrap();
1566 let instruction = withdraw_nonce_account(&nonce_account, &fee_payer, &recipient, 1000);
1567 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1568 let mut transaction =
1569 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1570 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1571 }
1572
1573 #[tokio::test]
1574 #[serial]
1575 async fn test_fee_payer_policy_nonce_authorize() {
1576 use solana_system_interface::instruction::authorize_nonce_account;
1577
1578 let fee_payer = Pubkey::new_unique();
1579 let nonce_account = Pubkey::new_unique();
1580 let new_authority = Pubkey::new_unique();
1581
1582 let rpc_client = RpcMockBuilder::new().build();
1584 let mut policy = FeePayerPolicy::default();
1585 policy.system.nonce.allow_authorize = true;
1586 setup_config_with_policy(policy);
1587
1588 let validator = TransactionValidator::new(fee_payer).unwrap();
1589 let instruction = authorize_nonce_account(&nonce_account, &fee_payer, &new_authority);
1590 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1591 let mut transaction =
1592 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1593 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
1594
1595 let rpc_client = RpcMockBuilder::new().build();
1597 let mut policy = FeePayerPolicy::default();
1598 policy.system.nonce.allow_authorize = false;
1599 setup_config_with_policy(policy);
1600
1601 let validator = TransactionValidator::new(fee_payer).unwrap();
1602 let instruction = authorize_nonce_account(&nonce_account, &fee_payer, &new_authority);
1603 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1604 let mut transaction =
1605 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1606 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err());
1607 }
1608
1609 #[test]
1610 #[serial]
1611 fn test_strict_pricing_total_exceeds_fixed() {
1612 let mut config = ConfigMockBuilder::new().build();
1613 config.validation.price.model = PriceModel::Fixed {
1614 amount: 5000,
1615 token: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string(),
1616 strict: true,
1617 };
1618 let _ = update_config(config);
1619
1620 let fee_calc = TotalFeeCalculation::new(5000, 3000, 2000, 5000, 0, 0);
1622
1623 let result = TransactionValidator::validate_strict_pricing_with_fee(&fee_calc);
1624
1625 assert!(result.is_err());
1626 if let Err(KoraError::ValidationError(msg)) = result {
1627 assert!(msg.contains("Strict pricing violation"));
1628 assert!(msg.contains("exceeds fixed price"));
1629 } else {
1630 panic!("Expected ValidationError");
1631 }
1632 }
1633
1634 #[test]
1635 #[serial]
1636 fn test_strict_pricing_total_within_fixed() {
1637 let mut config = ConfigMockBuilder::new().build();
1638 config.validation.price.model = PriceModel::Fixed {
1639 amount: 5000,
1640 token: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string(),
1641 strict: true,
1642 };
1643 let _ = update_config(config);
1644
1645 let fee_calc = TotalFeeCalculation::new(5000, 1000, 1000, 1000, 0, 0);
1647
1648 let result = TransactionValidator::validate_strict_pricing_with_fee(&fee_calc);
1649
1650 assert!(result.is_ok());
1651 }
1652
1653 #[test]
1654 #[serial]
1655 fn test_strict_pricing_disabled() {
1656 let mut config = ConfigMockBuilder::new().build();
1657 config.validation.price.model = PriceModel::Fixed {
1658 amount: 5000,
1659 token: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string(),
1660 strict: false, };
1662 let _ = update_config(config);
1663
1664 let fee_calc = TotalFeeCalculation::new(5000, 10000, 0, 0, 0, 0);
1665
1666 let result = TransactionValidator::validate_strict_pricing_with_fee(&fee_calc);
1667
1668 assert!(result.is_ok(), "Should pass when strict=false");
1669 }
1670
1671 #[test]
1672 #[serial]
1673 fn test_strict_pricing_with_margin_pricing() {
1674 use crate::{
1675 fee::price::PriceModel, state::update_config, tests::config_mock::ConfigMockBuilder,
1676 };
1677
1678 let mut config = ConfigMockBuilder::new().build();
1679 config.validation.price.model = PriceModel::Margin { margin: 0.1 };
1680 let _ = update_config(config);
1681
1682 let fee_calc = TotalFeeCalculation::new(5000, 10000, 0, 0, 0, 0);
1683
1684 let result = TransactionValidator::validate_strict_pricing_with_fee(&fee_calc);
1685
1686 assert!(result.is_ok());
1687 }
1688
1689 #[test]
1690 #[serial]
1691 fn test_strict_pricing_exact_match() {
1692 use crate::{
1693 fee::price::PriceModel, state::update_config, tests::config_mock::ConfigMockBuilder,
1694 };
1695
1696 let mut config = ConfigMockBuilder::new().build();
1697 config.validation.price.model = PriceModel::Fixed {
1698 amount: 5000,
1699 token: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string(),
1700 strict: true,
1701 };
1702 let _ = update_config(config);
1703
1704 let fee_calc = TotalFeeCalculation::new(5000, 2000, 1000, 2000, 0, 0);
1706
1707 let result = TransactionValidator::validate_strict_pricing_with_fee(&fee_calc);
1708
1709 assert!(result.is_ok(), "Should pass when total equals fixed price");
1710 }
1711
1712 #[tokio::test]
1713 #[serial]
1714 async fn test_durable_transaction_rejected_by_default() {
1715 use solana_system_interface::instruction::advance_nonce_account;
1716
1717 let fee_payer = Pubkey::new_unique();
1718 let nonce_account = Pubkey::new_unique();
1719 let nonce_authority = Pubkey::new_unique(); setup_default_config();
1723 let rpc_client = RpcMockBuilder::new().build();
1724
1725 let validator = TransactionValidator::new(fee_payer).unwrap();
1726
1727 let instruction = advance_nonce_account(&nonce_account, &nonce_authority);
1729 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1730 let mut transaction =
1731 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1732
1733 let result = validator.validate_transaction(&mut transaction, &rpc_client).await;
1734 assert!(result.is_err());
1735 if let Err(KoraError::InvalidTransaction(msg)) = result {
1736 assert!(msg.contains("Durable transactions"));
1737 assert!(msg.contains("not allowed"));
1738 } else {
1739 panic!("Expected InvalidTransaction error");
1740 }
1741 }
1742
1743 #[tokio::test]
1744 #[serial]
1745 async fn test_durable_transaction_allowed_when_enabled() {
1746 use solana_system_interface::instruction::advance_nonce_account;
1747
1748 let fee_payer = Pubkey::new_unique();
1749 let nonce_account = Pubkey::new_unique();
1750 let nonce_authority = Pubkey::new_unique(); let config = ConfigMockBuilder::new()
1754 .with_price_source(PriceSource::Mock)
1755 .with_allowed_programs(vec![SYSTEM_PROGRAM_ID.to_string()])
1756 .with_max_allowed_lamports(1_000_000)
1757 .with_fee_payer_policy(FeePayerPolicy::default())
1758 .with_allow_durable_transactions(true)
1759 .build();
1760 update_config(config).unwrap();
1761
1762 let rpc_client = RpcMockBuilder::new().build();
1763 let validator = TransactionValidator::new(fee_payer).unwrap();
1764
1765 let instruction = advance_nonce_account(&nonce_account, &nonce_authority);
1767 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1768 let mut transaction =
1769 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1770
1771 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
1773 }
1774
1775 #[tokio::test]
1776 #[serial]
1777 async fn test_non_durable_transaction_passes() {
1778 let fee_payer = Pubkey::new_unique();
1779 let sender = Pubkey::new_unique();
1780 let recipient = Pubkey::new_unique();
1781
1782 setup_default_config();
1784 let rpc_client = RpcMockBuilder::new().build();
1785
1786 let validator = TransactionValidator::new(fee_payer).unwrap();
1787
1788 let instruction = transfer(&sender, &recipient, 1000);
1790 let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer)));
1791 let mut transaction =
1792 TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap();
1793
1794 assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok());
1796 }
1797}