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