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