From dbdf83847beaf9a3dfe34e96726145768ba5db61 Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Tue, 11 Nov 2025 09:42:41 -0500 Subject: [PATCH 1/8] fix: limit access to config's singleton --- crates/lib/src/fee/fee.rs | 34 +++-- .../method/estimate_transaction_fee.rs | 2 + .../method/sign_and_send_transaction.rs | 5 +- .../src/rpc_server/method/sign_transaction.rs | 5 +- .../rpc_server/method/transfer_transaction.rs | 5 +- .../src/transaction/versioned_transaction.rs | 14 +- .../src/validator/transaction_validator.rs | 144 +++++++++++------- 7 files changed, 137 insertions(+), 72 deletions(-) diff --git a/crates/lib/src/fee/fee.rs b/crates/lib/src/fee/fee.rs index e3f0f915..fc6a97ed 100644 --- a/crates/lib/src/fee/fee.rs +++ b/crates/lib/src/fee/fee.rs @@ -1,6 +1,7 @@ use std::str::FromStr; use crate::{ + config::Config, constant::{ESTIMATED_LAMPORTS_FOR_PAYMENT_INSTRUCTION, LAMPORTS_PER_SIGNATURE}, error::KoraError, fee::price::PriceModel, @@ -18,10 +19,10 @@ use crate::{ use solana_message::Message; #[cfg(not(test))] -use {crate::cache::CacheUtil, crate::state::get_config}; +use crate::cache::CacheUtil; #[cfg(test)] -use crate::tests::{cache_mock::MockCacheUtil as CacheUtil, config_mock::mock_state::get_config}; +use crate::tests::cache_mock::MockCacheUtil as CacheUtil; use solana_client::nonblocking::rpc_client::RpcClient; use solana_message::VersionedMessage; use solana_sdk::pubkey::Pubkey; @@ -137,11 +138,11 @@ impl FeeConfigUtil { /// Analyze payment instructions in transaction /// Returns (has_payment, total_transfer_fees) async fn analyze_payment_instructions( + config: &Config, resolved_transaction: &mut VersionedTransactionResolved, rpc_client: &RpcClient, fee_payer: &Pubkey, ) -> Result<(bool, u64), KoraError> { - let config = get_config()?; let payment_destination = config.kora.get_payment_address(fee_payer)?; let mut has_payment = false; let mut total_transfer_fees = 0u64; @@ -215,6 +216,7 @@ impl FeeConfigUtil { } async fn estimate_transaction_fee( + config: &Config, rpc_client: &RpcClient, transaction: &mut VersionedTransactionResolved, fee_payer: &Pubkey, @@ -234,7 +236,6 @@ impl FeeConfigUtil { } // Calculate fee payer outflow if fee payer is provided, to better estimate the potential fee - let config = get_config()?; let fee_payer_outflow = FeeConfigUtil::calculate_fee_payer_outflow( fee_payer, transaction, @@ -245,7 +246,7 @@ impl FeeConfigUtil { // Analyze payment instructions (checks if payment exists + calculates Token2022 fees) let (has_payment, transfer_fee_config_amount) = - FeeConfigUtil::analyze_payment_instructions(transaction, rpc_client, fee_payer).await?; + FeeConfigUtil::analyze_payment_instructions(config, transaction, rpc_client, fee_payer).await?; // If payment is required but not found, add estimated payment instruction fee let fee_for_payment_instruction = if is_payment_required && !has_payment { @@ -277,14 +278,13 @@ impl FeeConfigUtil { /// Main entry point for fee calculation with Kora's price model applied pub async fn estimate_kora_fee( + config: &Config, rpc_client: &RpcClient, transaction: &mut VersionedTransactionResolved, fee_payer: &Pubkey, is_payment_required: bool, price_source: PriceSource, ) -> Result { - let config = get_config()?; - match &config.validation.price.model { PriceModel::Free => Ok(TotalFeeCalculation::new_fixed(0)), PriceModel::Fixed { strict, .. } => { @@ -296,6 +296,7 @@ impl FeeConfigUtil { if *strict { let fee_calculation = Self::estimate_transaction_fee( + config, rpc_client, transaction, fee_payer, @@ -318,6 +319,7 @@ impl FeeConfigUtil { PriceModel::Margin { .. } => { // Get the raw transaction let fee_calculation = Self::estimate_transaction_fee( + config, rpc_client, transaction, fee_payer, @@ -345,6 +347,7 @@ impl FeeConfigUtil { /// Calculate the fee in a specific token if provided pub async fn calculate_fee_in_token( + config: &Config, rpc_client: &RpcClient, fee_in_lamports: u64, fee_token: Option<&str>, @@ -354,7 +357,6 @@ impl FeeConfigUtil { KoraError::InvalidTransaction("Invalid fee token mint address".to_string()) })?; - let config = get_config()?; let validation_config = &config.validation; if !validation_config.supports_token(fee_token) { @@ -520,7 +522,7 @@ mod tests { create_mock_rpc_client_with_account, create_mock_token_account, setup_or_get_test_config, setup_or_get_test_signer, }, - config_mock::ConfigMockBuilder, + config_mock::{mock_state::get_config, ConfigMockBuilder}, rpc_mock::RpcMockBuilder, }, token::{interface::TokenInterface, spl_token::TokenProgram}, @@ -957,7 +959,9 @@ mod tests { let mut resolved_transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); + let config = get_config().unwrap(); let (has_payment, transfer_fees) = FeeConfigUtil::analyze_payment_instructions( + &config, &mut resolved_transaction, &mocked_rpc_client, &signer, @@ -986,7 +990,9 @@ mod tests { let mut resolved_transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); + let config = get_config().unwrap(); let (has_payment, transfer_fees) = FeeConfigUtil::analyze_payment_instructions( + &config, &mut resolved_transaction, &mocked_rpc_client, &signer, @@ -1032,7 +1038,9 @@ mod tests { let mut resolved_transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); + let config = get_config().unwrap(); let (has_payment, transfer_fees) = FeeConfigUtil::analyze_payment_instructions( + &config, &mut resolved_transaction, &mocked_rpc_client, &signer, @@ -1063,7 +1071,9 @@ mod tests { let mut resolved_transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); + let config = get_config().unwrap(); let result = FeeConfigUtil::estimate_transaction_fee( + &config, &mocked_rpc_client, &mut resolved_transaction, &fee_payer.pubkey(), @@ -1093,7 +1103,9 @@ mod tests { let mut resolved_transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); + let config = get_config().unwrap(); let result = FeeConfigUtil::estimate_transaction_fee( + &config, &mocked_rpc_client, &mut resolved_transaction, &kora_fee_payer.pubkey(), @@ -1130,7 +1142,9 @@ mod tests { let mut resolved_transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); + let config = get_config().unwrap(); let result = FeeConfigUtil::estimate_transaction_fee( + &config, &mocked_rpc_client, &mut resolved_transaction, &fee_payer.pubkey(), @@ -1186,7 +1200,9 @@ mod tests { let mut resolved_transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); + let config = get_config().unwrap(); let (has_payment, transfer_fees) = FeeConfigUtil::analyze_payment_instructions( + &config, &mut resolved_transaction, &mocked_rpc_client, &signer, diff --git a/crates/lib/src/rpc_server/method/estimate_transaction_fee.rs b/crates/lib/src/rpc_server/method/estimate_transaction_fee.rs index b2b046d0..7249a1fe 100644 --- a/crates/lib/src/rpc_server/method/estimate_transaction_fee.rs +++ b/crates/lib/src/rpc_server/method/estimate_transaction_fee.rs @@ -63,6 +63,7 @@ pub async fn estimate_transaction_fee( .await?; let fee_calculation = FeeConfigUtil::estimate_kora_fee( + &config, rpc_client, &mut resolved_transaction, &fee_payer, @@ -75,6 +76,7 @@ pub async fn estimate_transaction_fee( // Calculate fee in token if requested let fee_in_token = FeeConfigUtil::calculate_fee_in_token( + &config, rpc_client, fee_in_lamports, request.fee_token.as_deref(), diff --git a/crates/lib/src/rpc_server/method/sign_and_send_transaction.rs b/crates/lib/src/rpc_server/method/sign_and_send_transaction.rs index 530e75e6..c58e8cfa 100644 --- a/crates/lib/src/rpc_server/method/sign_and_send_transaction.rs +++ b/crates/lib/src/rpc_server/method/sign_and_send_transaction.rs @@ -6,7 +6,7 @@ use std::sync::Arc; use utoipa::ToSchema; use crate::{ - state::get_request_signer_with_signer_key, + state::{get_config, get_request_signer_with_signer_key}, transaction::{TransactionUtil, VersionedTransactionOps, VersionedTransactionResolved}, KoraError, }; @@ -39,6 +39,7 @@ pub async fn sign_and_send_transaction( UsageTracker::check_transaction_usage_limit(&transaction).await?; let signer = get_request_signer_with_signer_key(request.signer_key.as_deref())?; + let config = get_config()?; let mut resolved_transaction = VersionedTransactionResolved::from_transaction( &transaction, @@ -48,7 +49,7 @@ pub async fn sign_and_send_transaction( .await?; let (_, signed_transaction) = - resolved_transaction.sign_and_send_transaction(&signer, rpc_client).await?; + resolved_transaction.sign_and_send_transaction(&config, &signer, rpc_client).await?; Ok(SignAndSendTransactionResponse { signed_transaction, diff --git a/crates/lib/src/rpc_server/method/sign_transaction.rs b/crates/lib/src/rpc_server/method/sign_transaction.rs index c2b24d9e..240787cd 100644 --- a/crates/lib/src/rpc_server/method/sign_transaction.rs +++ b/crates/lib/src/rpc_server/method/sign_transaction.rs @@ -1,6 +1,6 @@ use crate::{ rpc_server::middleware_utils::default_sig_verify, - state::get_request_signer_with_signer_key, + state::{get_config, get_request_signer_with_signer_key}, transaction::{TransactionUtil, VersionedTransactionOps, VersionedTransactionResolved}, usage_limit::UsageTracker, KoraError, @@ -39,6 +39,7 @@ pub async fn sign_transaction( UsageTracker::check_transaction_usage_limit(&transaction).await?; let signer = get_request_signer_with_signer_key(request.signer_key.as_deref())?; + let config = get_config()?; let mut resolved_transaction = VersionedTransactionResolved::from_transaction( &transaction, @@ -48,7 +49,7 @@ pub async fn sign_transaction( .await?; let (signed_transaction, _) = - resolved_transaction.sign_transaction(&signer, rpc_client).await?; + resolved_transaction.sign_transaction(&config, &signer, rpc_client).await?; let encoded = TransactionUtil::encode_versioned_transaction(&signed_transaction)?; diff --git a/crates/lib/src/rpc_server/method/transfer_transaction.rs b/crates/lib/src/rpc_server/method/transfer_transaction.rs index acf8cfdf..01b01155 100644 --- a/crates/lib/src/rpc_server/method/transfer_transaction.rs +++ b/crates/lib/src/rpc_server/method/transfer_transaction.rs @@ -10,7 +10,7 @@ use utoipa::ToSchema; use crate::{ constant::NATIVE_SOL, - state::get_request_signer_with_signer_key, + state::{get_config, get_request_signer_with_signer_key}, transaction::{ TransactionUtil, VersionedMessageExt, VersionedTransactionOps, VersionedTransactionResolved, }, @@ -43,9 +43,10 @@ pub async fn transfer_transaction( request: TransferTransactionRequest, ) -> Result { let signer = get_request_signer_with_signer_key(request.signer_key.as_deref())?; + let config = get_config()?; let fee_payer = signer.pubkey(); - let validator = TransactionValidator::new(fee_payer)?; + let validator = TransactionValidator::new(config, fee_payer)?; let source = Pubkey::from_str(&request.source) .map_err(|e| KoraError::ValidationError(format!("Invalid source address: {e}")))?; diff --git a/crates/lib/src/transaction/versioned_transaction.rs b/crates/lib/src/transaction/versioned_transaction.rs index 33b4e7a6..6223ad4e 100644 --- a/crates/lib/src/transaction/versioned_transaction.rs +++ b/crates/lib/src/transaction/versioned_transaction.rs @@ -12,9 +12,9 @@ use std::{collections::HashMap, ops::Deref}; use solana_transaction_status_client_types::{UiInstruction, UiTransactionEncoding}; use crate::{ + config::Config, error::KoraError, fee::fee::{FeeConfigUtil, TransactionFeeUtil}, - state::get_config, transaction::{ instruction_util::IxUtils, ParsedSPLInstructionData, ParsedSPLInstructionType, ParsedSystemInstructionData, ParsedSystemInstructionType, @@ -58,11 +58,13 @@ pub trait VersionedTransactionOps { async fn sign_transaction( &mut self, + config: &Config, signer: &std::sync::Arc, rpc_client: &RpcClient, ) -> Result<(VersionedTransaction, String), KoraError>; async fn sign_and_send_transaction( &mut self, + config: &Config, signer: &std::sync::Arc, rpc_client: &RpcClient, ) -> Result<(String, String), KoraError>; @@ -242,18 +244,19 @@ impl VersionedTransactionOps for VersionedTransactionResolved { async fn sign_transaction( &mut self, + config: &Config, signer: &std::sync::Arc, rpc_client: &RpcClient, ) -> Result<(VersionedTransaction, String), KoraError> { let fee_payer = signer.pubkey(); - let config = &get_config()?; - let validator = TransactionValidator::new(fee_payer)?; + let validator = TransactionValidator::new(config, fee_payer)?; // Validate transaction and accounts (already resolved) validator.validate_transaction(self, rpc_client).await?; // Calculate fee and validate payment if price model requires it let fee_calculation = FeeConfigUtil::estimate_kora_fee( + &config, rpc_client, self, &fee_payer, @@ -280,7 +283,7 @@ impl VersionedTransactionOps for VersionedTransactionResolved { .await?; // Validate strict pricing if enabled - TransactionValidator::validate_strict_pricing_with_fee(&fee_calculation)?; + TransactionValidator::validate_strict_pricing_with_fee(&config, &fee_calculation)?; } // Get latest blockhash and update transaction @@ -317,11 +320,12 @@ impl VersionedTransactionOps for VersionedTransactionResolved { async fn sign_and_send_transaction( &mut self, + config: &Config, signer: &std::sync::Arc, rpc_client: &RpcClient, ) -> Result<(String, String), KoraError> { // Payment validation is handled in sign_transaction - let (transaction, encoded) = self.sign_transaction(signer, rpc_client).await?; + let (transaction, encoded) = self.sign_transaction(config, signer, rpc_client).await?; // Send and confirm transaction let signature = rpc_client diff --git a/crates/lib/src/validator/transaction_validator.rs b/crates/lib/src/validator/transaction_validator.rs index 5303ffad..4073a410 100644 --- a/crates/lib/src/validator/transaction_validator.rs +++ b/crates/lib/src/validator/transaction_validator.rs @@ -1,9 +1,8 @@ use crate::{ - config::FeePayerPolicy, + config::{Config, FeePayerPolicy}, error::KoraError, fee::fee::{FeeConfigUtil, TotalFeeCalculation}, oracle::PriceSource, - state::get_config, token::{interface::TokenMint, token::TokenUtil}, transaction::{ ParsedSPLInstructionData, ParsedSPLInstructionType, ParsedSystemInstructionData, @@ -28,8 +27,8 @@ pub struct TransactionValidator { } impl TransactionValidator { - pub fn new(fee_payer_pubkey: Pubkey) -> Result { - let config = &get_config()?.validation; + pub fn new(config: &Config, fee_payer_pubkey: Pubkey) -> Result { + let config = &config.validation; // Convert string program IDs to Pubkeys let allowed_programs = config @@ -329,12 +328,11 @@ impl TransactionValidator { transaction_resolved: &mut VersionedTransactionResolved, rpc_client: &RpcClient, ) -> Result { - let config = get_config()?; FeeConfigUtil::calculate_fee_payer_outflow( &self.fee_payer_pubkey, transaction_resolved, rpc_client, - &config.validation.price_source, + &self._price_source, ) .await } @@ -362,10 +360,9 @@ impl TransactionValidator { } pub fn validate_strict_pricing_with_fee( + config: &Config, fee_calculation: &TotalFeeCalculation, ) -> Result<(), KoraError> { - let config = get_config()?; - if !matches!(&config.validation.price.model, PriceModel::Fixed { strict: true, .. }) { return Ok(()); } @@ -394,7 +391,7 @@ impl TransactionValidator { mod tests { use crate::{ config::FeePayerPolicy, - state::update_config, + state::{get_config, update_config}, tests::{config_mock::ConfigMockBuilder, rpc_mock::RpcMockBuilder}, transaction::TransactionUtil, }; @@ -458,7 +455,8 @@ mod tests { setup_default_config(); let rpc_client = RpcMockBuilder::new().build(); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let recipient = Pubkey::new_unique(); let sender = Pubkey::new_unique(); @@ -477,7 +475,8 @@ mod tests { setup_default_config(); let rpc_client = RpcMockBuilder::new().build(); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let sender = Pubkey::new_unique(); let recipient = Pubkey::new_unique(); @@ -505,7 +504,8 @@ mod tests { setup_default_config(); let rpc_client = RpcMockBuilder::new().build(); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let sender = Pubkey::new_unique(); let recipient = Pubkey::new_unique(); @@ -544,7 +544,8 @@ mod tests { update_config(config).unwrap(); let rpc_client = RpcMockBuilder::new().build(); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let sender = Pubkey::new_unique(); let recipient = Pubkey::new_unique(); @@ -568,7 +569,8 @@ mod tests { setup_default_config(); let rpc_client = RpcMockBuilder::new().build(); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let sender = Pubkey::new_unique(); let recipient = Pubkey::new_unique(); @@ -594,7 +596,8 @@ mod tests { setup_default_config(); let rpc_client = RpcMockBuilder::new().build(); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); // Create an empty message using Message::new with empty instructions let message = VersionedMessage::Legacy(Message::new(&[], Some(&fee_payer))); @@ -619,7 +622,8 @@ mod tests { update_config(config).unwrap(); let rpc_client = RpcMockBuilder::new().build(); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let instruction = transfer( &Pubkey::from_str("hndXZGK45hCxfBYvxejAXzCfCujoqkNf7rk4sTB8pek").unwrap(), &fee_payer, @@ -643,7 +647,8 @@ mod tests { policy.system.allow_transfer = true; setup_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let instruction = transfer(&fee_payer, &recipient, 1000); @@ -658,7 +663,8 @@ mod tests { policy.system.allow_transfer = false; setup_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let instruction = transfer(&fee_payer, &recipient, 1000); let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); @@ -681,7 +687,8 @@ mod tests { policy.system.allow_assign = true; setup_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let instruction = assign(&fee_payer, &new_owner); let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); @@ -697,7 +704,8 @@ mod tests { policy.system.allow_assign = false; setup_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let instruction = assign(&fee_payer, &new_owner); let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); @@ -721,7 +729,8 @@ mod tests { policy.spl_token.allow_transfer = true; setup_spl_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let transfer_ix = spl_token_interface::instruction::transfer( &spl_token_interface::id(), @@ -745,7 +754,8 @@ mod tests { policy.spl_token.allow_transfer = false; setup_spl_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let transfer_ix = spl_token_interface::instruction::transfer( &spl_token_interface::id(), @@ -798,7 +808,8 @@ mod tests { policy.token_2022.allow_transfer = true; setup_token2022_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let transfer_ix = spl_token_2022_interface::instruction::transfer_checked( &spl_token_2022_interface::id(), @@ -825,7 +836,8 @@ mod tests { policy.token_2022.allow_transfer = false; setup_token2022_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let transfer_ix = spl_token_2022_interface::instruction::transfer_checked( &spl_token_2022_interface::id(), @@ -881,7 +893,8 @@ mod tests { update_config(config).unwrap(); let rpc_client = RpcMockBuilder::new().build(); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); // Test 1: Fee payer as sender in Transfer - should add to outflow let recipient = Pubkey::new_unique(); @@ -1019,7 +1032,8 @@ mod tests { policy.spl_token.allow_burn = true; setup_spl_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let burn_ix = spl_token_interface::instruction::burn( &spl_token_interface::id(), @@ -1044,7 +1058,8 @@ mod tests { policy.spl_token.allow_burn = false; setup_spl_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let burn_ix = spl_token_interface::instruction::burn( &spl_token_interface::id(), @@ -1097,7 +1112,8 @@ mod tests { policy.spl_token.allow_close_account = true; setup_spl_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let close_ix = spl_token_interface::instruction::close_account( &spl_token_interface::id(), @@ -1120,7 +1136,8 @@ mod tests { policy.spl_token.allow_close_account = false; setup_spl_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let close_ix = spl_token_interface::instruction::close_account( &spl_token_interface::id(), @@ -1153,7 +1170,8 @@ mod tests { policy.spl_token.allow_approve = true; setup_spl_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let approve_ix = spl_token_interface::instruction::approve( &spl_token_interface::id(), @@ -1177,7 +1195,8 @@ mod tests { policy.spl_token.allow_approve = false; setup_spl_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let approve_ix = spl_token_interface::instruction::approve( &spl_token_interface::id(), @@ -1233,7 +1252,8 @@ mod tests { policy.token_2022.allow_burn = false; setup_token2022_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let burn_ix = spl_token_2022_interface::instruction::burn( &spl_token_2022_interface::id(), @@ -1266,7 +1286,8 @@ mod tests { policy.token_2022.allow_close_account = false; setup_token2022_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let close_ix = spl_token_2022_interface::instruction::close_account( &spl_token_2022_interface::id(), @@ -1298,7 +1319,8 @@ mod tests { policy.token_2022.allow_approve = true; setup_token2022_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let approve_ix = spl_token_2022_interface::instruction::approve( &spl_token_2022_interface::id(), @@ -1323,7 +1345,8 @@ mod tests { policy.token_2022.allow_approve = false; setup_token2022_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let approve_ix = spl_token_2022_interface::instruction::approve( &spl_token_2022_interface::id(), @@ -1380,7 +1403,8 @@ mod tests { policy.system.allow_create_account = true; setup_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let instruction = create_account(&fee_payer, &new_account, 1000, 100, &owner); let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = @@ -1393,7 +1417,8 @@ mod tests { policy.system.allow_create_account = false; setup_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let instruction = create_account(&fee_payer, &new_account, 1000, 100, &owner); let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = @@ -1414,7 +1439,8 @@ mod tests { policy.system.allow_allocate = true; setup_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let instruction = allocate(&fee_payer, 100); let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = @@ -1427,7 +1453,8 @@ mod tests { policy.system.allow_allocate = false; setup_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let instruction = allocate(&fee_payer, 100); let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = @@ -1449,7 +1476,8 @@ mod tests { policy.system.nonce.allow_initialize = true; setup_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let instructions = create_nonce_account(&fee_payer, &nonce_account, &fee_payer, 1_000_000); // Only test the InitializeNonceAccount instruction (second one) let message = @@ -1464,7 +1492,8 @@ mod tests { policy.system.nonce.allow_initialize = false; setup_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let instructions = create_nonce_account(&fee_payer, &nonce_account, &fee_payer, 1_000_000); let message = VersionedMessage::Legacy(Message::new(&[instructions[1].clone()], Some(&fee_payer))); @@ -1487,7 +1516,8 @@ mod tests { policy.system.nonce.allow_advance = true; setup_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let instruction = advance_nonce_account(&nonce_account, &fee_payer); let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = @@ -1500,7 +1530,8 @@ mod tests { policy.system.nonce.allow_advance = false; setup_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let instruction = advance_nonce_account(&nonce_account, &fee_payer); let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = @@ -1523,7 +1554,8 @@ mod tests { policy.system.nonce.allow_withdraw = true; setup_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let instruction = withdraw_nonce_account(&nonce_account, &fee_payer, &recipient, 1000); let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = @@ -1536,7 +1568,8 @@ mod tests { policy.system.nonce.allow_withdraw = false; setup_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let instruction = withdraw_nonce_account(&nonce_account, &fee_payer, &recipient, 1000); let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = @@ -1559,7 +1592,8 @@ mod tests { policy.system.nonce.allow_authorize = true; setup_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let instruction = authorize_nonce_account(&nonce_account, &fee_payer, &new_authority); let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = @@ -1572,7 +1606,8 @@ mod tests { policy.system.nonce.allow_authorize = false; setup_config_with_policy(policy); - let validator = TransactionValidator::new(fee_payer).unwrap(); + let config = get_config().unwrap(); + let validator = TransactionValidator::new(&config, fee_payer).unwrap(); let instruction = authorize_nonce_account(&nonce_account, &fee_payer, &new_authority); let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = @@ -1594,7 +1629,8 @@ mod tests { // Fixed price = 5000, but total = 3000 + 2000 + 5000 = 10000 > 5000 let fee_calc = TotalFeeCalculation::new(5000, 3000, 2000, 5000, 0, 0); - let result = TransactionValidator::validate_strict_pricing_with_fee(&fee_calc); + let config = get_config().unwrap(); + let result = TransactionValidator::validate_strict_pricing_with_fee(&config, &fee_calc); assert!(result.is_err()); if let Err(KoraError::ValidationError(msg)) = result { @@ -1619,7 +1655,8 @@ mod tests { // Fixed price = 5000, total = 1000 + 1000 + 1000 = 3000 < 5000 let fee_calc = TotalFeeCalculation::new(5000, 1000, 1000, 1000, 0, 0); - let result = TransactionValidator::validate_strict_pricing_with_fee(&fee_calc); + let config = get_config().unwrap(); + let result = TransactionValidator::validate_strict_pricing_with_fee(&config, &fee_calc); assert!(result.is_ok()); } @@ -1637,7 +1674,8 @@ mod tests { let fee_calc = TotalFeeCalculation::new(5000, 10000, 0, 0, 0, 0); - let result = TransactionValidator::validate_strict_pricing_with_fee(&fee_calc); + let config = get_config().unwrap(); + let result = TransactionValidator::validate_strict_pricing_with_fee(&config, &fee_calc); assert!(result.is_ok(), "Should pass when strict=false"); } @@ -1655,7 +1693,8 @@ mod tests { let fee_calc = TotalFeeCalculation::new(5000, 10000, 0, 0, 0, 0); - let result = TransactionValidator::validate_strict_pricing_with_fee(&fee_calc); + let config = get_config().unwrap(); + let result = TransactionValidator::validate_strict_pricing_with_fee(&config, &fee_calc); assert!(result.is_ok()); } @@ -1678,7 +1717,8 @@ mod tests { // Total exactly equals fixed price (5000 = 5000) let fee_calc = TotalFeeCalculation::new(5000, 2000, 1000, 2000, 0, 0); - let result = TransactionValidator::validate_strict_pricing_with_fee(&fee_calc); + let config = get_config().unwrap(); + let result = TransactionValidator::validate_strict_pricing_with_fee(&config, &fee_calc); assert!(result.is_ok(), "Should pass when total equals fixed price"); } From 1a4f47793ae8ca53083beda6a537346af644cf60 Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Tue, 11 Nov 2025 10:41:25 -0500 Subject: [PATCH 2/8] fix: pull config out of the usage tracker --- crates/lib/src/admin/token_util.rs | 19 +-- crates/lib/src/cache.rs | 29 ++--- crates/lib/src/fee/fee.rs | 39 +++++- crates/lib/src/metrics/balance.rs | 11 +- .../method/sign_and_send_transaction.rs | 5 +- .../src/rpc_server/method/sign_transaction.rs | 5 +- .../rpc_server/method/transfer_transaction.rs | 6 +- crates/lib/src/tests/cache_mock.rs | 1 + crates/lib/src/token/token.rs | 47 ++++--- .../src/transaction/versioned_transaction.rs | 8 +- crates/lib/src/usage_limit/usage_tracker.rs | 14 ++- crates/lib/src/validator/account_validator.rs | 17 ++- crates/lib/src/validator/config_validator.rs | 8 +- .../src/validator/transaction_validator.rs | 119 +++++++++--------- 14 files changed, 202 insertions(+), 126 deletions(-) diff --git a/crates/lib/src/admin/token_util.rs b/crates/lib/src/admin/token_util.rs index 33c72b65..5dac569b 100644 --- a/crates/lib/src/admin/token_util.rs +++ b/crates/lib/src/admin/token_util.rs @@ -1,4 +1,5 @@ use crate::{ + config::Config, error::KoraError, state::{get_request_signer_with_signer_key, get_signer_pool}, token::token::TokenType, @@ -94,7 +95,8 @@ pub async fn initialize_atas_with_chunk_size( for address in addresses_to_initialize_atas { println!("Initializing ATAs for address: {address}"); - let atas_to_create = find_missing_atas(rpc_client, address).await?; + let config = get_config()?; + let atas_to_create = find_missing_atas(&config, rpc_client, address).await?; if atas_to_create.is_empty() { println!("✓ All required ATAs already exist for address: {address}"); @@ -245,11 +247,10 @@ async fn create_atas_for_signer( } pub async fn find_missing_atas( + config: &Config, rpc_client: &RpcClient, payment_address: &Pubkey, ) -> Result, KoraError> { - let config = get_config()?; - // Parse all allowed SPL paid token mints let mut token_mints = Vec::new(); for token_str in &config.validation.allowed_spl_paid_tokens { @@ -273,14 +274,14 @@ pub async fn find_missing_atas( for mint in &token_mints { let ata = get_associated_token_address(payment_address, mint); - match CacheUtil::get_account(rpc_client, &ata, false).await { + match CacheUtil::get_account(&config, rpc_client, &ata, false).await { Ok(_) => { println!("✓ ATA already exists for token {mint}: {ata}"); } Err(_) => { // Fetch mint account to determine if it's SPL or Token2022 let mint_account = - CacheUtil::get_account(rpc_client, mint, false).await.map_err(|e| { + CacheUtil::get_account(&config, rpc_client, mint, false).await.map_err(|e| { KoraError::RpcError(format!("Failed to fetch mint account for {mint}: {e}")) })?; @@ -328,7 +329,8 @@ mod tests { let rpc_client = create_mock_rpc_client_account_not_found(); let payment_address = Pubkey::new_unique(); - let result = find_missing_atas(&rpc_client, &payment_address).await.unwrap(); + let config = get_config().unwrap(); + let result = find_missing_atas(&config, &rpc_client, &payment_address).await.unwrap(); assert!(result.is_empty(), "Should return empty vec when no SPL tokens configured"); } @@ -366,9 +368,10 @@ mod tests { cache_ctx .expect() .times(3) - .returning(move |_, _, _| responses_clone.lock().unwrap().pop_front().unwrap()); + .returning(move |_, _, _, _| responses_clone.lock().unwrap().pop_front().unwrap()); - let result = find_missing_atas(&rpc_client, &payment_address).await; + let config = get_config().unwrap(); + let result = find_missing_atas(&config, &rpc_client, &payment_address).await; assert!(result.is_ok(), "Should handle SPL tokens with proper mocking"); let atas = result.unwrap(); diff --git a/crates/lib/src/cache.rs b/crates/lib/src/cache.rs index d4711b4a..e450edd1 100644 --- a/crates/lib/src/cache.rs +++ b/crates/lib/src/cache.rs @@ -33,7 +33,7 @@ impl CacheUtil { pub async fn init() -> Result<(), KoraError> { let config = get_config()?; - let pool = if CacheUtil::is_cache_enabled() { + let pool = if CacheUtil::is_cache_enabled(&config) { let redis_url = config.kora.cache.url.as_ref().ok_or(KoraError::ConfigError)?; let cfg = deadpool_redis::Config::from_url(redis_url); @@ -179,23 +179,19 @@ impl CacheUtil { } /// Check if cache is enabled and available - fn is_cache_enabled() -> bool { - match get_config() { - Ok(config) => config.kora.cache.enabled && config.kora.cache.url.is_some(), - Err(_) => false, - } + fn is_cache_enabled(config: &crate::config::Config) -> bool { + config.kora.cache.enabled && config.kora.cache.url.is_some() } /// Get account from cache with optional force refresh pub async fn get_account( + config: &crate::config::Config, rpc_client: &RpcClient, pubkey: &Pubkey, force_refresh: bool, ) -> Result { - let config = get_config()?; - // If cache is disabled or force refresh is requested, go directly to RPC - if !CacheUtil::is_cache_enabled() { + if !CacheUtil::is_cache_enabled(config) { return Self::get_account_from_rpc(rpc_client, pubkey).await; } @@ -264,7 +260,8 @@ mod tests { async fn test_is_cache_enabled_disabled() { let _m = ConfigMockBuilder::new().with_cache_enabled(false).build_and_setup(); - assert!(!CacheUtil::is_cache_enabled()); + let config = get_config().unwrap(); + assert!(!CacheUtil::is_cache_enabled(&config)); } #[tokio::test] @@ -275,7 +272,8 @@ mod tests { .build_and_setup(); // Without URL, cache should be disabled - assert!(!CacheUtil::is_cache_enabled()); + let config = get_config().unwrap(); + assert!(!CacheUtil::is_cache_enabled(&config)); } #[tokio::test] @@ -286,7 +284,8 @@ mod tests { .build_and_setup(); // Give time for config to be set up - assert!(CacheUtil::is_cache_enabled()); + let config = get_config().unwrap(); + assert!(CacheUtil::is_cache_enabled(&config)); } #[tokio::test] @@ -336,7 +335,8 @@ mod tests { let rpc_client = RpcMockBuilder::new().with_account_info(&expected_account).build(); - let result = CacheUtil::get_account(&rpc_client, &pubkey, false).await; + let config = get_config().unwrap(); + let result = CacheUtil::get_account(&config, &rpc_client, &pubkey, false).await; assert!(result.is_ok()); let account = result.unwrap(); @@ -355,7 +355,8 @@ mod tests { let rpc_client = RpcMockBuilder::new().with_account_info(&expected_account).build(); // force_refresh = true should always go to RPC - let result = CacheUtil::get_account(&rpc_client, &pubkey, true).await; + let config = get_config().unwrap(); + let result = CacheUtil::get_account(&config, &rpc_client, &pubkey, true).await; assert!(result.is_ok()); let account = result.unwrap(); diff --git a/crates/lib/src/fee/fee.rs b/crates/lib/src/fee/fee.rs index fc6a97ed..673bc1f8 100644 --- a/crates/lib/src/fee/fee.rs +++ b/crates/lib/src/fee/fee.rs @@ -107,6 +107,7 @@ impl FeeConfigUtil { /// Helper function to check if a token transfer instruction is a payment to Kora /// Returns Some(token_account_data) if it's a payment, None otherwise async fn get_payment_instruction_info( + config: &Config, rpc_client: &RpcClient, destination_address: &Pubkey, payment_destination: &Pubkey, @@ -114,7 +115,7 @@ impl FeeConfigUtil { ) -> Result>, KoraError> { // Get destination account - handle missing accounts based on skip_missing_accounts let destination_account = - match CacheUtil::get_account(rpc_client, destination_address, false).await { + match CacheUtil::get_account(config, rpc_client, destination_address, false).await { Ok(account) => account, Err(_) if skip_missing_accounts => { return Ok(None); @@ -163,6 +164,7 @@ impl FeeConfigUtil { { // Check if this is a payment to Kora let payment_info = Self::get_payment_instruction_info( + config, rpc_client, destination_address, &payment_destination, @@ -177,7 +179,7 @@ impl FeeConfigUtil { if *is_2022 { if let Some(mint_pubkey) = mint { let mint_account = - CacheUtil::get_account(rpc_client, mint_pubkey, true).await?; + CacheUtil::get_account(config, rpc_client, mint_pubkey, true).await?; let token_program = TokenType::get_token_program_from_owner(&mint_account.owner)?; @@ -237,6 +239,7 @@ impl FeeConfigUtil { // Calculate fee payer outflow if fee payer is provided, to better estimate the potential fee let fee_payer_outflow = FeeConfigUtil::calculate_fee_payer_outflow( + config, fee_payer, transaction, rpc_client, @@ -382,6 +385,7 @@ impl FeeConfigUtil { /// Calculate the total outflow (SOL + SPL token value) that could occur for a fee payer account in a transaction. /// This includes SOL transfers, account creation, SPL token transfers, and other operations that could drain the fee payer's balance. pub async fn calculate_fee_payer_outflow( + config: &Config, fee_payer_pubkey: &Pubkey, transaction: &mut VersionedTransactionResolved, rpc_client: &RpcClient, @@ -448,6 +452,7 @@ impl FeeConfigUtil { if !spl_transfers.is_empty() { let spl_outflow = TokenUtil::calculate_spl_transfers_value_in_lamports( + config, spl_transfers, fee_payer_pubkey, price_source, @@ -636,7 +641,9 @@ mod tests { let mut resolved_transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); + let config = get_config().unwrap(); let outflow = FeeConfigUtil::calculate_fee_payer_outflow( + &config, &fee_payer, &mut resolved_transaction, &mocked_rpc_client, @@ -653,7 +660,9 @@ mod tests { VersionedMessage::Legacy(Message::new(&[transfer_instruction], Some(&fee_payer))); let mut resolved_transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); + let config = get_config().unwrap(); let outflow = FeeConfigUtil::calculate_fee_payer_outflow( + &config, &fee_payer, &mut resolved_transaction, &mocked_rpc_client, @@ -670,7 +679,9 @@ mod tests { VersionedMessage::Legacy(Message::new(&[transfer_instruction], Some(&fee_payer))); let mut resolved_transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); + let config = get_config().unwrap(); let outflow = FeeConfigUtil::calculate_fee_payer_outflow( + &config, &fee_payer, &mut resolved_transaction, &mocked_rpc_client, @@ -701,7 +712,9 @@ mod tests { VersionedMessage::Legacy(Message::new(&[transfer_instruction], Some(&fee_payer))); let mut resolved_transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); + let config = get_config().unwrap(); let outflow = FeeConfigUtil::calculate_fee_payer_outflow( + &config, &fee_payer, &mut resolved_transaction, &mocked_rpc_client, @@ -725,7 +738,9 @@ mod tests { VersionedMessage::Legacy(Message::new(&[transfer_instruction], Some(&fee_payer))); let mut resolved_transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); + let config = get_config().unwrap(); let outflow = FeeConfigUtil::calculate_fee_payer_outflow( + &config, &fee_payer, &mut resolved_transaction, &mocked_rpc_client, @@ -753,7 +768,9 @@ mod tests { VersionedMessage::Legacy(Message::new(&[create_instruction], Some(&fee_payer))); let mut resolved_transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); + let config = get_config().unwrap(); let outflow = FeeConfigUtil::calculate_fee_payer_outflow( + &config, &fee_payer, &mut resolved_transaction, &mocked_rpc_client, @@ -771,7 +788,9 @@ mod tests { VersionedMessage::Legacy(Message::new(&[create_instruction], Some(&fee_payer))); let mut resolved_transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); + let config = get_config().unwrap(); let outflow = FeeConfigUtil::calculate_fee_payer_outflow( + &config, &fee_payer, &mut resolved_transaction, &mocked_rpc_client, @@ -803,7 +822,9 @@ mod tests { VersionedMessage::Legacy(Message::new(&[create_instruction], Some(&fee_payer))); let mut resolved_transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); + let config = get_config().unwrap(); let outflow = FeeConfigUtil::calculate_fee_payer_outflow( + &config, &fee_payer, &mut resolved_transaction, &mocked_rpc_client, @@ -832,7 +853,9 @@ mod tests { VersionedMessage::Legacy(Message::new(&[withdraw_instruction], Some(&fee_payer))); let mut resolved_transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); + let config = get_config().unwrap(); let outflow = FeeConfigUtil::calculate_fee_payer_outflow( + &config, &fee_payer, &mut resolved_transaction, &mocked_rpc_client, @@ -853,7 +876,9 @@ mod tests { VersionedMessage::Legacy(Message::new(&[withdraw_instruction], Some(&fee_payer))); let mut resolved_transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); + let config = get_config().unwrap(); let outflow = FeeConfigUtil::calculate_fee_payer_outflow( + &config, &fee_payer, &mut resolved_transaction, &mocked_rpc_client, @@ -885,7 +910,9 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&instructions, Some(&fee_payer))); let mut resolved_transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); + let config = get_config().unwrap(); let outflow = FeeConfigUtil::calculate_fee_payer_outflow( + &config, &fee_payer, &mut resolved_transaction, &mocked_rpc_client, @@ -915,7 +942,9 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut resolved_transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); + let config = get_config().unwrap(); let outflow = FeeConfigUtil::calculate_fee_payer_outflow( + &config, &fee_payer, &mut resolved_transaction, &mocked_rpc_client, @@ -938,7 +967,7 @@ mod tests { let mocked_rpc_client = create_mock_rpc_client_with_account(&mocked_account); // Set up cache expectation for token account lookup - cache_ctx.expect().times(1).returning(move |_, _, _| Ok(mocked_account.clone())); + cache_ctx.expect().times(1).returning(move |_, _, _, _| Ok(mocked_account.clone())); let sender = Keypair::new(); @@ -1017,7 +1046,7 @@ mod tests { let mocked_rpc_client = create_mock_rpc_client_with_account(&mocked_account); // Set up cache expectation for token account lookup - cache_ctx.expect().times(1).returning(move |_, _, _| Ok(mocked_account.clone())); + cache_ctx.expect().times(1).returning(move |_, _, _, _| Ok(mocked_account.clone())); // Create token accounts let sender_token_account = get_associated_token_address(&sender.pubkey(), &mint); @@ -1172,7 +1201,7 @@ mod tests { let mocked_account = create_mock_token_account(&signer, &mint); let mocked_rpc_client = create_mock_rpc_client_with_account(&mocked_account); - cache_ctx.expect().times(2).returning(move |_, _, _| Ok(mocked_account.clone())); + cache_ctx.expect().times(2).returning(move |_, _, _, _| Ok(mocked_account.clone())); let sender = Keypair::new(); let sender_token_account = get_associated_token_address(&sender.pubkey(), &mint); diff --git a/crates/lib/src/metrics/balance.rs b/crates/lib/src/metrics/balance.rs index 2d6db7ee..c4d427a5 100644 --- a/crates/lib/src/metrics/balance.rs +++ b/crates/lib/src/metrics/balance.rs @@ -52,6 +52,15 @@ impl BalanceTracker { // Get all signers in the pool let signers_info = get_signers_info()?; + // Fetch config once for all signers + let config = match get_config() { + Ok(c) => c, + Err(e) => { + log::warn!("Failed to get config in metrics: {e}"); + return Ok(()); + } + }; + if let Some(gauge_vec) = SIGNER_BALANCE_GAUGES.get() { let mut balance_results = Vec::new(); @@ -64,7 +73,7 @@ impl BalanceTracker { )) })?; - match CacheUtil::get_account(rpc_client, &pubkey, false).await { + match CacheUtil::get_account(&config, rpc_client, &pubkey, false).await { Ok(account) => { balance_results.push((signer_info, account.lamports)); } diff --git a/crates/lib/src/rpc_server/method/sign_and_send_transaction.rs b/crates/lib/src/rpc_server/method/sign_and_send_transaction.rs index c58e8cfa..f9da1ed6 100644 --- a/crates/lib/src/rpc_server/method/sign_and_send_transaction.rs +++ b/crates/lib/src/rpc_server/method/sign_and_send_transaction.rs @@ -35,11 +35,12 @@ pub async fn sign_and_send_transaction( ) -> Result { let transaction = TransactionUtil::decode_b64_transaction(&request.transaction)?; + let config = get_config()?; + // Check usage limit for transaction sender - UsageTracker::check_transaction_usage_limit(&transaction).await?; + UsageTracker::check_transaction_usage_limit(&config, &transaction).await?; let signer = get_request_signer_with_signer_key(request.signer_key.as_deref())?; - let config = get_config()?; let mut resolved_transaction = VersionedTransactionResolved::from_transaction( &transaction, diff --git a/crates/lib/src/rpc_server/method/sign_transaction.rs b/crates/lib/src/rpc_server/method/sign_transaction.rs index 240787cd..84f01f0e 100644 --- a/crates/lib/src/rpc_server/method/sign_transaction.rs +++ b/crates/lib/src/rpc_server/method/sign_transaction.rs @@ -35,11 +35,12 @@ pub async fn sign_transaction( ) -> Result { let transaction = TransactionUtil::decode_b64_transaction(&request.transaction)?; + let config = get_config()?; + // Check usage limit for transaction sender - UsageTracker::check_transaction_usage_limit(&transaction).await?; + UsageTracker::check_transaction_usage_limit(&config, &transaction).await?; let signer = get_request_signer_with_signer_key(request.signer_key.as_deref())?; - let config = get_config()?; let mut resolved_transaction = VersionedTransactionResolved::from_transaction( &transaction, diff --git a/crates/lib/src/rpc_server/method/transfer_transaction.rs b/crates/lib/src/rpc_server/method/transfer_transaction.rs index 01b01155..592b25cf 100644 --- a/crates/lib/src/rpc_server/method/transfer_transaction.rs +++ b/crates/lib/src/rpc_server/method/transfer_transaction.rs @@ -83,11 +83,11 @@ pub async fn transfer_transaction( let dest_ata = token_program.get_associated_token_address(&destination, &token_mint.address()); - CacheUtil::get_account(rpc_client, &source_ata, false) + CacheUtil::get_account(config, rpc_client, &source_ata, false) .await .map_err(|_| KoraError::AccountNotFound(source_ata.to_string()))?; - if CacheUtil::get_account(rpc_client, &dest_ata, false).await.is_err() { + if CacheUtil::get_account(config, rpc_client, &dest_ata, false).await.is_err() { instructions.push(token_program.create_associated_token_account_instruction( &fee_payer, &destination, @@ -127,7 +127,7 @@ pub async fn transfer_transaction( VersionedTransactionResolved::from_kora_built_transaction(&transaction)?; // validate transaction before signing - validator.validate_transaction(&mut resolved_transaction, rpc_client).await?; + validator.validate_transaction(config, &mut resolved_transaction, rpc_client).await?; // Find the fee payer position in the account keys let fee_payer_position = resolved_transaction.find_signer_position(&fee_payer)?; diff --git a/crates/lib/src/tests/cache_mock.rs b/crates/lib/src/tests/cache_mock.rs index cf30ee32..e065df26 100644 --- a/crates/lib/src/tests/cache_mock.rs +++ b/crates/lib/src/tests/cache_mock.rs @@ -7,6 +7,7 @@ mock! { pub CacheUtil { pub async fn init() -> Result<(), KoraError>; pub async fn get_account( + config: &crate::config::Config, rpc_client: &RpcClient, pubkey: &Pubkey, force_refresh: bool, diff --git a/crates/lib/src/token/token.rs b/crates/lib/src/token/token.rs index 11e1b5b3..49cf9656 100644 --- a/crates/lib/src/token/token.rs +++ b/crates/lib/src/token/token.rs @@ -69,10 +69,11 @@ impl TokenUtil { } pub async fn get_mint( + config: &crate::config::Config, rpc_client: &RpcClient, mint_pubkey: &Pubkey, ) -> Result, KoraError> { - let mint_account = CacheUtil::get_account(rpc_client, mint_pubkey, false).await?; + let mint_account = CacheUtil::get_account(config, rpc_client, mint_pubkey, false).await?; let token_program = TokenType::get_token_program_from_owner(&mint_account.owner)?; @@ -82,10 +83,11 @@ impl TokenUtil { } pub async fn get_mint_decimals( + config: &crate::config::Config, rpc_client: &RpcClient, mint_pubkey: &Pubkey, ) -> Result { - let mint = Self::get_mint(rpc_client, mint_pubkey).await?; + let mint = Self::get_mint(config, rpc_client, mint_pubkey).await?; Ok(mint.decimals()) } @@ -94,7 +96,8 @@ impl TokenUtil { price_source: PriceSource, rpc_client: &RpcClient, ) -> Result<(TokenPrice, u8), KoraError> { - let decimals = Self::get_mint_decimals(rpc_client, mint).await?; + let config = get_config()?; + let decimals = Self::get_mint_decimals(&config, rpc_client, mint).await?; let oracle = RetryingPriceOracle::new(3, Duration::from_secs(1), get_price_oracle(price_source)); @@ -174,6 +177,7 @@ impl TokenUtil { /// Calculate the total lamports value of SPL token transfers where the fee payer is involved /// This includes both outflow (fee payer as owner/source) and inflow (fee payer owns destination) pub async fn calculate_spl_transfers_value_in_lamports( + config: &crate::config::Config, spl_transfers: &[ParsedSPLInstructionData], fee_payer: &Pubkey, price_source: &PriceSource, @@ -204,7 +208,7 @@ impl TokenUtil { // We need to check the destination token account owner if let Some(mint_pubkey) = mint { // Get destination account to check owner - match CacheUtil::get_account(rpc_client, destination_address, false).await { + match CacheUtil::get_account(config, rpc_client, destination_address, false).await { Ok(dest_account) => { let token_program = TokenType::get_token_program_from_owner(&dest_account.owner)?; @@ -276,7 +280,7 @@ impl TokenUtil { let mut mint_decimals = std::collections::HashMap::new(); for mint in mint_to_transfers.keys() { - let decimals = Self::get_mint_decimals(rpc_client, mint).await?; + let decimals = Self::get_mint_decimals(config, rpc_client, mint).await?; mint_decimals.insert(*mint, decimals); } @@ -329,17 +333,18 @@ impl TokenUtil { /// Validate Token2022 extensions for payment instructions /// This checks if any blocked extensions are present on the payment accounts pub async fn validate_token2022_extensions_for_payment( + config: &crate::config::Config, rpc_client: &RpcClient, source_address: &Pubkey, destination_address: &Pubkey, mint: &Pubkey, ) -> Result<(), KoraError> { - let config = &get_config()?.validation.token_2022; + let token2022_config = &config.validation.token_2022; let token_program = Token2022Program::new(); // Get mint account data and validate mint extensions (force refresh in case extensions are added) - let mint_account = CacheUtil::get_account(rpc_client, mint, true).await?; + let mint_account = CacheUtil::get_account(config, rpc_client, mint, true).await?; let mint_data = mint_account.data; // Unpack the mint state with extensions @@ -352,7 +357,7 @@ impl TokenUtil { // Check each extension type present on the mint for extension_type in mint_with_extensions.get_extension_types() { - if config.is_mint_extension_blocked(*extension_type) { + if token2022_config.is_mint_extension_blocked(*extension_type) { return Err(KoraError::ValidationError(format!( "Blocked mint extension found on mint account {mint}", ))); @@ -360,7 +365,7 @@ impl TokenUtil { } // Check source account extensions (force refresh in case extensions are added) - let source_account = CacheUtil::get_account(rpc_client, source_address, true).await?; + let source_account = CacheUtil::get_account(config, rpc_client, source_address, true).await?; let source_data = source_account.data; let source_state = token_program.unpack_token_account(&source_data)?; @@ -371,7 +376,7 @@ impl TokenUtil { })?; for extension_type in source_with_extensions.get_extension_types() { - if config.is_account_extension_blocked(*extension_type) { + if token2022_config.is_account_extension_blocked(*extension_type) { return Err(KoraError::ValidationError(format!( "Blocked account extension found on source account {source_address}", ))); @@ -380,7 +385,7 @@ impl TokenUtil { // Check destination account extensions (force refresh in case extensions are added) let destination_account = - CacheUtil::get_account(rpc_client, destination_address, true).await?; + CacheUtil::get_account(config, rpc_client, destination_address, true).await?; let destination_data = destination_account.data; let destination_state = token_program.unpack_token_account(&destination_data)?; @@ -391,7 +396,7 @@ impl TokenUtil { })?; for extension_type in destination_with_extensions.get_extension_types() { - if config.is_account_extension_blocked(*extension_type) { + if token2022_config.is_account_extension_blocked(*extension_type) { return Err(KoraError::ValidationError(format!( "Blocked account extension found on destination account {destination_address}", ))); @@ -402,13 +407,13 @@ impl TokenUtil { } pub async fn verify_token_payment( + config: &crate::config::Config, transaction_resolved: &mut VersionedTransactionResolved, rpc_client: &RpcClient, required_lamports: u64, // Wallet address of the owner of the destination token account expected_destination_owner: &Pubkey, ) -> Result { - let config = get_config()?; let mut total_lamport_value = 0u64; for instruction in transaction_resolved @@ -433,7 +438,7 @@ impl TokenUtil { // Validate the destination account is that of the payment address (or signer if none provided) let destination_account = - CacheUtil::get_account(rpc_client, destination_address, false) + CacheUtil::get_account(config, rpc_client, destination_address, false) .await .map_err(|e| KoraError::RpcError(e.to_string()))?; @@ -445,6 +450,7 @@ impl TokenUtil { // For Token2022 payments, validate that blocked extensions are not used if *is_2022 { TokenUtil::validate_token2022_extensions_for_payment( + config, rpc_client, source_address, destination_address, @@ -575,7 +581,8 @@ mod tests_token { let mint = Pubkey::from_str(WSOL_DEVNET_MINT).unwrap(); let rpc_client = RpcMockBuilder::new().with_mint_account(9).build(); - let result = TokenUtil::get_mint(&rpc_client, &mint).await; + let config = get_config().unwrap(); + let result = TokenUtil::get_mint(&config, &rpc_client, &mint).await; assert!(result.is_ok()); let mint_data = result.unwrap(); assert_eq!(mint_data.decimals(), 9); @@ -587,7 +594,8 @@ mod tests_token { let mint = Pubkey::from_str(WSOL_DEVNET_MINT).unwrap(); let rpc_client = RpcMockBuilder::new().with_account_not_found().build(); - let result = TokenUtil::get_mint(&rpc_client, &mint).await; + let config = get_config().unwrap(); + let result = TokenUtil::get_mint(&config, &rpc_client, &mint).await; assert!(result.is_err()); } @@ -597,7 +605,8 @@ mod tests_token { let mint = Pubkey::from_str(WSOL_DEVNET_MINT).unwrap(); let rpc_client = RpcMockBuilder::new().with_mint_account(6).build(); - let result = TokenUtil::get_mint_decimals(&rpc_client, &mint).await; + let config = get_config().unwrap(); + let result = TokenUtil::get_mint_decimals(&config, &rpc_client, &mint).await; assert!(result.is_ok()); assert_eq!(result.unwrap(), 6); } @@ -908,7 +917,9 @@ mod tests_token { let rpc_client = RpcMockBuilder::new().with_account_not_found().build(); + let config = get_config().unwrap(); let result = TokenUtil::validate_token2022_extensions_for_payment( + &config, &rpc_client, &source_address, &destination_address, @@ -933,7 +944,9 @@ mod tests_token { let rpc_client = RpcMockBuilder::new().with_account_info(&source_account).build(); // Test with None mint (should only check account extensions but will fail on dest account lookup) + let config = get_config().unwrap(); let result = TokenUtil::validate_token2022_extensions_for_payment( + &config, &rpc_client, &source_address, &destination_address, diff --git a/crates/lib/src/transaction/versioned_transaction.rs b/crates/lib/src/transaction/versioned_transaction.rs index 6223ad4e..e8838582 100644 --- a/crates/lib/src/transaction/versioned_transaction.rs +++ b/crates/lib/src/transaction/versioned_transaction.rs @@ -15,6 +15,7 @@ use crate::{ config::Config, error::KoraError, fee::fee::{FeeConfigUtil, TransactionFeeUtil}, + state::get_config, transaction::{ instruction_util::IxUtils, ParsedSPLInstructionData, ParsedSPLInstructionType, ParsedSystemInstructionData, ParsedSystemInstructionType, @@ -252,7 +253,7 @@ impl VersionedTransactionOps for VersionedTransactionResolved { let validator = TransactionValidator::new(config, fee_payer)?; // Validate transaction and accounts (already resolved) - validator.validate_transaction(self, rpc_client).await?; + validator.validate_transaction(config, self, rpc_client).await?; // Calculate fee and validate payment if price model requires it let fee_calculation = FeeConfigUtil::estimate_kora_fee( @@ -275,6 +276,7 @@ impl VersionedTransactionOps for VersionedTransactionResolved { // Validate token payment using the resolved transaction TransactionValidator::validate_token_payment( + config, self, required_lamports, rpc_client, @@ -347,10 +349,12 @@ impl LookupTableUtil { ) -> Result, KoraError> { let mut resolved_addresses = Vec::new(); + let config = get_config()?; + // Maybe we can use caching here, there's a chance the lookup tables get updated though, so tbd for lookup in lookup_table_lookups { let lookup_table_account = - CacheUtil::get_account(rpc_client, &lookup.account_key, false).await.map_err( + CacheUtil::get_account(&config, rpc_client, &lookup.account_key, false).await.map_err( |e| KoraError::RpcError(format!("Failed to fetch lookup table: {e}")), )?; diff --git a/crates/lib/src/usage_limit/usage_tracker.rs b/crates/lib/src/usage_limit/usage_tracker.rs index 8bc711b9..84cf6e06 100644 --- a/crates/lib/src/usage_limit/usage_tracker.rs +++ b/crates/lib/src/usage_limit/usage_tracker.rs @@ -6,7 +6,7 @@ use solana_sdk::{pubkey::Pubkey, transaction::VersionedTransaction}; use tokio::sync::OnceCell; use super::usage_store::{RedisUsageStore, UsageStore}; -use crate::{error::KoraError, sanitize_error, state::get_signer_pool}; +use crate::{config::Config, error::KoraError, sanitize_error, state::get_signer_pool}; #[cfg(not(test))] use crate::state::get_config; @@ -205,10 +205,9 @@ impl UsageTracker { /// Check usage limit for transaction sender pub async fn check_transaction_usage_limit( + config: &Config, transaction: &VersionedTransaction, ) -> Result<(), KoraError> { - let config = get_config()?; - if let Some(limiter) = Self::get_usage_limiter()? { let sender = limiter.extract_transaction_sender(transaction)?; if let Some(sender) = sender { @@ -307,7 +306,8 @@ mod tests { // Initialize the usage limiter - it should set to None when disabled let _ = UsageTracker::init_usage_limiter().await; - let result = UsageTracker::check_transaction_usage_limit(&create_mock_transaction()).await; + let config = get_config().unwrap(); + let result = UsageTracker::check_transaction_usage_limit(&config, &create_mock_transaction()).await; match &result { Ok(_) => {} Err(e) => println!("Test failed with error: {e}"), @@ -326,7 +326,8 @@ mod tests { // Initialize with no cache_url - should set limiter to None let _ = UsageTracker::init_usage_limiter().await; - let result = UsageTracker::check_transaction_usage_limit(&create_mock_transaction()).await; + let config = get_config().unwrap(); + let result = UsageTracker::check_transaction_usage_limit(&config, &create_mock_transaction()).await; assert!(result.is_ok()); } @@ -341,7 +342,8 @@ mod tests { // Initialize with no cache_url - should set limiter to None let _ = UsageTracker::init_usage_limiter().await; - let result = UsageTracker::check_transaction_usage_limit(&create_mock_transaction()).await; + let config = get_config().unwrap(); + let result = UsageTracker::check_transaction_usage_limit(&config, &create_mock_transaction()).await; assert!(result.is_err()); assert!(result .unwrap_err() diff --git a/crates/lib/src/validator/account_validator.rs b/crates/lib/src/validator/account_validator.rs index de85ac44..f023aaf4 100644 --- a/crates/lib/src/validator/account_validator.rs +++ b/crates/lib/src/validator/account_validator.rs @@ -132,11 +132,12 @@ impl AccountType { } pub async fn validate_account( + config: &crate::config::Config, rpc_client: &RpcClient, account_pubkey: &Pubkey, expected_account_type: Option, ) -> Result<(), KoraError> { - let account = CacheUtil::get_account(rpc_client, account_pubkey, false).await?; + let account = CacheUtil::get_account(config, rpc_client, account_pubkey, false).await?; if let Some(expected_type) = expected_account_type { expected_type.validate_account_type(&account, account_pubkey)?; @@ -156,7 +157,7 @@ mod tests { create_mock_token_account, AccountMockBuilder, }, common::{MintAccountMockBuilder, TokenAccountMockBuilder}, - config_mock::ConfigMockBuilder, + config_mock::{mock_state::get_config, ConfigMockBuilder}, rpc_mock::{create_mock_rpc_client_account_not_found, create_mock_rpc_client_with_account}, }; @@ -365,7 +366,8 @@ mod tests { let rpc_client = create_mock_rpc_client_with_account(&mint_account); let account_pubkey = Pubkey::new_unique(); - let result = validate_account(&rpc_client, &account_pubkey, Some(AccountType::Mint)).await; + let config = get_config().unwrap(); + let result = validate_account(&config, &rpc_client, &account_pubkey, Some(AccountType::Mint)).await; assert!(result.is_ok()); } @@ -377,7 +379,8 @@ mod tests { let rpc_client = create_mock_rpc_client_with_account(&account); let account_pubkey = Pubkey::new_unique(); - let result = validate_account(&rpc_client, &account_pubkey, None).await; + let config = get_config().unwrap(); + let result = validate_account(&config, &rpc_client, &account_pubkey, None).await; assert!(result.is_ok()); } @@ -388,8 +391,9 @@ mod tests { let rpc_client = create_mock_rpc_client_account_not_found(); let account_pubkey = Pubkey::new_unique(); + let config = get_config().unwrap(); let result = - validate_account(&rpc_client, &account_pubkey, Some(AccountType::System)).await; + validate_account(&config, &rpc_client, &account_pubkey, Some(AccountType::System)).await; assert!(result.is_err()); let error_msg = result.unwrap_err().to_string(); assert!(error_msg.contains("Account") && error_msg.contains("not found")); @@ -403,8 +407,9 @@ mod tests { let rpc_client = create_mock_rpc_client_with_account(&account); let account_pubkey = Pubkey::new_unique(); + let config = get_config().unwrap(); let result = - validate_account(&rpc_client, &account_pubkey, Some(AccountType::System)).await; + validate_account(&config, &rpc_client, &account_pubkey, Some(AccountType::System)).await; assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("is not owned by")); } diff --git a/crates/lib/src/validator/config_validator.rs b/crates/lib/src/validator/config_validator.rs index a3d76718..651608cc 100644 --- a/crates/lib/src/validator/config_validator.rs +++ b/crates/lib/src/validator/config_validator.rs @@ -473,7 +473,7 @@ impl ConfigValidator { for program_str in &config.validation.allowed_programs { if let Ok(program_pubkey) = Pubkey::from_str(program_str) { if let Err(e) = - validate_account(rpc_client, &program_pubkey, Some(AccountType::Program)) + validate_account(config, rpc_client, &program_pubkey, Some(AccountType::Program)) .await { errors.push(format!("Program {program_str} validation failed: {e}")); @@ -485,7 +485,7 @@ impl ConfigValidator { for token_str in &config.validation.allowed_tokens { if let Ok(token_pubkey) = Pubkey::from_str(token_str) { if let Err(e) = - validate_account(rpc_client, &token_pubkey, Some(AccountType::Mint)).await + validate_account(config, rpc_client, &token_pubkey, Some(AccountType::Mint)).await { errors.push(format!("Token {token_str} validation failed: {e}")); } @@ -496,7 +496,7 @@ impl ConfigValidator { for token_str in &config.validation.allowed_spl_paid_tokens { if let Ok(token_pubkey) = Pubkey::from_str(token_str) { if let Err(e) = - validate_account(rpc_client, &token_pubkey, Some(AccountType::Mint)).await + validate_account(config, rpc_client, &token_pubkey, Some(AccountType::Mint)).await { errors.push(format!("SPL paid token {token_str} validation failed: {e}")); } @@ -514,7 +514,7 @@ impl ConfigValidator { // Validate missing ATAs for payment address if let Some(payment_address) = &config.kora.payment_address { if let Ok(payment_address) = Pubkey::from_str(payment_address) { - match find_missing_atas(rpc_client, &payment_address).await { + match find_missing_atas(config, rpc_client, &payment_address).await { Ok(atas_to_create) => { if !atas_to_create.is_empty() { errors.push(format!( diff --git a/crates/lib/src/validator/transaction_validator.rs b/crates/lib/src/validator/transaction_validator.rs index 4073a410..581f28e8 100644 --- a/crates/lib/src/validator/transaction_validator.rs +++ b/crates/lib/src/validator/transaction_validator.rs @@ -83,7 +83,8 @@ impl TransactionValidator { ))); } - let mint = TokenUtil::get_mint(rpc_client, mint).await?; + let config = crate::state::get_config()?; + let mint = TokenUtil::get_mint(&config, rpc_client, mint).await?; Ok(mint) } @@ -93,6 +94,7 @@ impl TransactionValidator { */ pub async fn validate_transaction( &self, + config: &Config, transaction_resolved: &mut VersionedTransactionResolved, rpc_client: &RpcClient, ) -> Result<(), KoraError> { @@ -111,7 +113,7 @@ impl TransactionValidator { self.validate_signatures(&transaction_resolved.transaction)?; self.validate_programs(transaction_resolved)?; - self.validate_transfer_amounts(transaction_resolved, rpc_client).await?; + self.validate_transfer_amounts(config, transaction_resolved, rpc_client).await?; self.validate_disallowed_accounts(transaction_resolved)?; self.validate_fee_payer_usage(transaction_resolved)?; @@ -280,10 +282,11 @@ impl TransactionValidator { async fn validate_transfer_amounts( &self, + config: &Config, transaction_resolved: &mut VersionedTransactionResolved, rpc_client: &RpcClient, ) -> Result<(), KoraError> { - let total_outflow = self.calculate_total_outflow(transaction_resolved, rpc_client).await?; + let total_outflow = self.calculate_total_outflow(config, transaction_resolved, rpc_client).await?; if total_outflow > self.max_allowed_lamports { return Err(KoraError::InvalidTransaction(format!( @@ -325,10 +328,12 @@ impl TransactionValidator { async fn calculate_total_outflow( &self, + config: &Config, transaction_resolved: &mut VersionedTransactionResolved, rpc_client: &RpcClient, ) -> Result { FeeConfigUtil::calculate_fee_payer_outflow( + config, &self.fee_payer_pubkey, transaction_resolved, rpc_client, @@ -338,12 +343,14 @@ impl TransactionValidator { } pub async fn validate_token_payment( + config: &Config, transaction_resolved: &mut VersionedTransactionResolved, required_lamports: u64, rpc_client: &RpcClient, expected_payment_destination: &Pubkey, ) -> Result<(), KoraError> { if TokenUtil::verify_token_payment( + config, transaction_resolved, rpc_client, required_lamports, @@ -465,7 +472,7 @@ mod tests { let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); } #[tokio::test] @@ -486,7 +493,7 @@ mod tests { let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); // Test multiple transfers let instructions = @@ -494,7 +501,7 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&instructions, Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); } #[tokio::test] @@ -514,7 +521,7 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); // Test disallowed program let fake_program = Pubkey::new_unique(); @@ -527,7 +534,7 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); } #[tokio::test] @@ -559,7 +566,7 @@ mod tests { let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); transaction.transaction.signatures = vec![Default::default(); 3]; // Add 3 dummy signatures - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); } #[tokio::test] @@ -579,14 +586,14 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); // Test SignAndSend mode without fee payer (should succeed) let instruction = transfer(&sender, &recipient, 1000); let message = VersionedMessage::Legacy(Message::new(&[instruction], None)); // No fee payer specified let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); } #[tokio::test] @@ -603,7 +610,7 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); } #[tokio::test] @@ -632,7 +639,7 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); } #[tokio::test] @@ -655,7 +662,7 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); // Test with allow_sol_transfers = false let rpc_client = RpcMockBuilder::new().build(); @@ -670,7 +677,7 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); } #[tokio::test] @@ -694,7 +701,7 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); // Test with allow_assign = false @@ -711,7 +718,7 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); } #[tokio::test] @@ -745,7 +752,7 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[transfer_ix], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); // Test with allow_spl_transfers = false let rpc_client = RpcMockBuilder::new().build(); @@ -770,7 +777,7 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[transfer_ix], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); // Test with other account as source - should always pass let other_signer = Pubkey::new_unique(); @@ -787,7 +794,7 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[transfer_ix], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); } #[tokio::test] @@ -826,7 +833,7 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[transfer_ix], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); // Test with allow_token2022_transfers = false let rpc_client = RpcMockBuilder::new() @@ -856,7 +863,7 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should fail because fee payer is not allowed to be source - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); // Test with other account as source - should always pass let other_signer = Pubkey::new_unique(); @@ -877,7 +884,7 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should pass because fee payer is not the source - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); } #[tokio::test] @@ -904,7 +911,7 @@ mod tests { let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); let outflow = - validator.calculate_total_outflow(&mut transaction, &rpc_client).await.unwrap(); + validator.calculate_total_outflow(&config, &mut transaction, &rpc_client).await.unwrap(); assert_eq!(outflow, 100_000, "Transfer from fee payer should add to outflow"); // Test 2: Fee payer as recipient in Transfer - should subtract from outflow (account closure) @@ -916,7 +923,7 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); let outflow = - validator.calculate_total_outflow(&mut transaction, &rpc_client).await.unwrap(); + validator.calculate_total_outflow(&config, &mut transaction, &rpc_client).await.unwrap(); assert_eq!(outflow, 0, "Transfer to fee payer should subtract from outflow"); // 0 - 50_000 = 0 (saturating_sub) // Test 3: Fee payer as funding account in CreateAccount - should add to outflow @@ -933,7 +940,7 @@ mod tests { let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); let outflow = - validator.calculate_total_outflow(&mut transaction, &rpc_client).await.unwrap(); + validator.calculate_total_outflow(&config, &mut transaction, &rpc_client).await.unwrap(); assert_eq!(outflow, 200_000, "CreateAccount funded by fee payer should add to outflow"); // Test 4: Fee payer as funding account in CreateAccountWithSeed - should add to outflow @@ -953,7 +960,7 @@ mod tests { let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); let outflow = - validator.calculate_total_outflow(&mut transaction, &rpc_client).await.unwrap(); + validator.calculate_total_outflow(&config, &mut transaction, &rpc_client).await.unwrap(); assert_eq!( outflow, 300_000, "CreateAccountWithSeed funded by fee payer should add to outflow" @@ -975,7 +982,7 @@ mod tests { let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); let outflow = - validator.calculate_total_outflow(&mut transaction, &rpc_client).await.unwrap(); + validator.calculate_total_outflow(&config, &mut transaction, &rpc_client).await.unwrap(); assert_eq!(outflow, 150_000, "TransferWithSeed from fee payer should add to outflow"); // Test 6: Multiple instructions - should sum correctly @@ -988,7 +995,7 @@ mod tests { let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); let outflow = - validator.calculate_total_outflow(&mut transaction, &rpc_client).await.unwrap(); + validator.calculate_total_outflow(&config, &mut transaction, &rpc_client).await.unwrap(); assert_eq!( outflow, 120_000, "Multiple instructions should sum correctly: 100000 - 30000 + 50000 = 120000" @@ -1002,7 +1009,7 @@ mod tests { let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); let outflow = - validator.calculate_total_outflow(&mut transaction, &rpc_client).await.unwrap(); + validator.calculate_total_outflow(&config, &mut transaction, &rpc_client).await.unwrap(); assert_eq!(outflow, 0, "Transfer from other account should not affect outflow"); // Test 8: Other account funding CreateAccount - should not affect outflow @@ -1014,7 +1021,7 @@ mod tests { let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); let outflow = - validator.calculate_total_outflow(&mut transaction, &rpc_client).await.unwrap(); + validator.calculate_total_outflow(&config, &mut transaction, &rpc_client).await.unwrap(); assert_eq!(outflow, 0, "CreateAccount funded by other account should not affect outflow"); } @@ -1049,7 +1056,7 @@ mod tests { let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should pass because allow_burn is true by default - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); // Test with allow_burn = false @@ -1076,7 +1083,7 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should fail because fee payer cannot burn tokens when allow_burn is false - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); // Test burn_checked instruction let burn_checked_ix = spl_token_interface::instruction::burn_checked( @@ -1095,7 +1102,7 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should also fail for burn_checked - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); } #[tokio::test] @@ -1128,7 +1135,7 @@ mod tests { let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should pass because allow_close_account is true by default - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); // Test with allow_close_account = false let rpc_client = RpcMockBuilder::new().build(); @@ -1153,7 +1160,7 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should fail because fee payer cannot close accounts when allow_close_account is false - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); } #[tokio::test] @@ -1187,7 +1194,7 @@ mod tests { let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should pass because allow_approve is true by default - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); // Test with allow_approve = false let rpc_client = RpcMockBuilder::new().build(); @@ -1213,7 +1220,7 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should fail because fee payer cannot approve when allow_approve is false - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); // Test approve_checked instruction let mint = Pubkey::new_unique(); @@ -1235,7 +1242,7 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should also fail for approve_checked - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); } #[tokio::test] @@ -1269,7 +1276,7 @@ mod tests { let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should fail for Token2022 burn - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); } #[tokio::test] @@ -1302,7 +1309,7 @@ mod tests { let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should fail for Token2022 close account - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); } #[tokio::test] @@ -1336,7 +1343,7 @@ mod tests { let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should pass because allow_approve is true by default - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); // Test with allow_approve = false @@ -1363,7 +1370,7 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should fail because fee payer cannot approve when allow_approve is false - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); // Test approve_checked instruction let mint = Pubkey::new_unique(); @@ -1385,7 +1392,7 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should also fail for approve_checked - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); } #[tokio::test] @@ -1409,7 +1416,7 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); // Test with allow_create_account = false let rpc_client = RpcMockBuilder::new().build(); @@ -1423,7 +1430,7 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); } #[tokio::test] @@ -1445,7 +1452,7 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); // Test with allow_allocate = false let rpc_client = RpcMockBuilder::new().build(); @@ -1459,7 +1466,7 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); } #[tokio::test] @@ -1484,7 +1491,7 @@ mod tests { VersionedMessage::Legacy(Message::new(&[instructions[1].clone()], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); // Test with allow_initialize = false let rpc_client = RpcMockBuilder::new().build(); @@ -1499,7 +1506,7 @@ mod tests { VersionedMessage::Legacy(Message::new(&[instructions[1].clone()], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); } #[tokio::test] @@ -1522,7 +1529,7 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); // Test with allow_advance = false let rpc_client = RpcMockBuilder::new().build(); @@ -1536,7 +1543,7 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); } #[tokio::test] @@ -1560,7 +1567,7 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); // Test with allow_withdraw = false let rpc_client = RpcMockBuilder::new().build(); @@ -1574,7 +1581,7 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); } #[tokio::test] @@ -1598,7 +1605,7 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_ok()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); // Test with allow_authorize = false let rpc_client = RpcMockBuilder::new().build(); @@ -1612,7 +1619,7 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&mut transaction, &rpc_client).await.is_err()); + assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); } #[test] From 2b1be92ac411dcad556f657694625a131c25d25b Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Tue, 11 Nov 2025 12:04:29 -0500 Subject: [PATCH 3/8] fix: remaining occurrences --- .../method/estimate_transaction_fee.rs | 1 + .../method/sign_and_send_transaction.rs | 1 + .../src/rpc_server/method/sign_transaction.rs | 1 + .../src/transaction/versioned_transaction.rs | 40 +++++++++++-------- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/crates/lib/src/rpc_server/method/estimate_transaction_fee.rs b/crates/lib/src/rpc_server/method/estimate_transaction_fee.rs index 7249a1fe..674bad76 100644 --- a/crates/lib/src/rpc_server/method/estimate_transaction_fee.rs +++ b/crates/lib/src/rpc_server/method/estimate_transaction_fee.rs @@ -57,6 +57,7 @@ pub async fn estimate_transaction_fee( let mut resolved_transaction = VersionedTransactionResolved::from_transaction( &transaction, + &config, rpc_client, request.sig_verify, ) diff --git a/crates/lib/src/rpc_server/method/sign_and_send_transaction.rs b/crates/lib/src/rpc_server/method/sign_and_send_transaction.rs index f9da1ed6..33c1c197 100644 --- a/crates/lib/src/rpc_server/method/sign_and_send_transaction.rs +++ b/crates/lib/src/rpc_server/method/sign_and_send_transaction.rs @@ -44,6 +44,7 @@ pub async fn sign_and_send_transaction( let mut resolved_transaction = VersionedTransactionResolved::from_transaction( &transaction, + &config, rpc_client, request.sig_verify, ) diff --git a/crates/lib/src/rpc_server/method/sign_transaction.rs b/crates/lib/src/rpc_server/method/sign_transaction.rs index 84f01f0e..31e2f0ea 100644 --- a/crates/lib/src/rpc_server/method/sign_transaction.rs +++ b/crates/lib/src/rpc_server/method/sign_transaction.rs @@ -44,6 +44,7 @@ pub async fn sign_transaction( let mut resolved_transaction = VersionedTransactionResolved::from_transaction( &transaction, + &config, rpc_client, request.sig_verify, ) diff --git a/crates/lib/src/transaction/versioned_transaction.rs b/crates/lib/src/transaction/versioned_transaction.rs index e8838582..dab524d3 100644 --- a/crates/lib/src/transaction/versioned_transaction.rs +++ b/crates/lib/src/transaction/versioned_transaction.rs @@ -15,7 +15,6 @@ use crate::{ config::Config, error::KoraError, fee::fee::{FeeConfigUtil, TransactionFeeUtil}, - state::get_config, transaction::{ instruction_util::IxUtils, ParsedSPLInstructionData, ParsedSPLInstructionType, ParsedSystemInstructionData, ParsedSystemInstructionType, @@ -74,6 +73,7 @@ pub trait VersionedTransactionOps { impl VersionedTransactionResolved { pub async fn from_transaction( transaction: &VersionedTransaction, + config: &Config, rpc_client: &RpcClient, sig_verify: bool, ) -> Result { @@ -94,6 +94,7 @@ impl VersionedTransactionResolved { VersionedMessage::V0(v0_message) => { // V0 transactions may have lookup tables LookupTableUtil::resolve_lookup_table_addresses( + &config, rpc_client, &v0_message.address_table_lookups, ) @@ -344,13 +345,12 @@ pub struct LookupTableUtil {} impl LookupTableUtil { /// Resolves addresses from lookup tables for V0 transactions pub async fn resolve_lookup_table_addresses( + config: &Config, rpc_client: &RpcClient, lookup_table_lookups: &[MessageAddressTableLookup], ) -> Result, KoraError> { let mut resolved_addresses = Vec::new(); - let config = get_config()?; - // Maybe we can use caching here, there's a chance the lookup tables get updated though, so tbd for lookup in lookup_table_lookups { let lookup_table_account = @@ -670,7 +670,7 @@ mod tests { #[tokio::test] async fn test_from_transaction_legacy() { let config = setup_test_config(); - let _m = setup_config_mock(config); + let _m = setup_config_mock(config.clone()); let keypair = Keypair::new(); let instruction = Instruction::new_with_bytes( @@ -702,7 +702,7 @@ mod tests { let rpc_client = RpcMockBuilder::new().with_custom_mocks(mocks).build(); let resolved = - VersionedTransactionResolved::from_transaction(&transaction, &rpc_client, true) + VersionedTransactionResolved::from_transaction(&transaction, &config, &rpc_client, true) .await .unwrap(); @@ -726,7 +726,7 @@ mod tests { #[tokio::test] async fn test_from_transaction_v0_with_lookup_tables() { let config = setup_test_config(); - let _m = setup_config_mock(config); + let _m = setup_config_mock(config.clone()); let keypair = Keypair::new(); let program_id = Pubkey::new_unique(); @@ -804,7 +804,7 @@ mod tests { let rpc_client = RpcMockBuilder::new().with_custom_mocks(mocks).build(); let resolved = - VersionedTransactionResolved::from_transaction(&transaction, &rpc_client, true) + VersionedTransactionResolved::from_transaction(&transaction, &config, &rpc_client, true) .await .unwrap(); @@ -820,7 +820,7 @@ mod tests { #[tokio::test] async fn test_from_transaction_simulation_failure() { let config = setup_test_config(); - let _m = setup_config_mock(config); + let _m = setup_config_mock(config.clone()); let keypair = Keypair::new(); let instruction = Instruction::new_with_bytes( @@ -849,7 +849,7 @@ mod tests { let rpc_client = RpcMockBuilder::new().with_custom_mocks(mocks).build(); let result = - VersionedTransactionResolved::from_transaction(&transaction, &rpc_client, true).await; + VersionedTransactionResolved::from_transaction(&transaction, &config, &rpc_client, true).await; // The simulation should fail, but the exact error type depends on mock implementation // We expect either an RpcError (from mock deserialization) or InvalidTransaction (from simulation logic) @@ -1010,7 +1010,7 @@ mod tests { #[tokio::test] async fn test_resolve_lookup_table_addresses() { let config = setup_test_config(); - let _m = setup_config_mock(config); + let _m = setup_config_mock(config.clone()); let lookup_account_key = Pubkey::new_unique(); let address1 = Pubkey::new_unique(); @@ -1047,7 +1047,7 @@ mod tests { }]; let resolved_addresses = - LookupTableUtil::resolve_lookup_table_addresses(&rpc_client, &lookups).await.unwrap(); + LookupTableUtil::resolve_lookup_table_addresses(&config, &rpc_client, &lookups).await.unwrap(); assert_eq!(resolved_addresses.len(), 3); assert_eq!(resolved_addresses[0], address1); @@ -1057,17 +1057,23 @@ mod tests { #[tokio::test] async fn test_resolve_lookup_table_addresses_empty() { + let config = setup_test_config(); + let _m = setup_config_mock(config.clone()); + let rpc_client = RpcMockBuilder::new().with_account_not_found().build(); let lookups = vec![]; let resolved_addresses = - LookupTableUtil::resolve_lookup_table_addresses(&rpc_client, &lookups).await.unwrap(); + LookupTableUtil::resolve_lookup_table_addresses(&config, &rpc_client, &lookups).await.unwrap(); assert_eq!(resolved_addresses.len(), 0); } #[tokio::test] async fn test_resolve_lookup_table_addresses_account_not_found() { + let config = setup_test_config(); + let _m = setup_config_mock(config.clone()); + let rpc_client = RpcMockBuilder::new().with_account_not_found().build(); let lookups = vec![solana_message::v0::MessageAddressTableLookup { account_key: Pubkey::new_unique(), @@ -1075,7 +1081,7 @@ mod tests { readonly_indexes: vec![], }]; - let result = LookupTableUtil::resolve_lookup_table_addresses(&rpc_client, &lookups).await; + let result = LookupTableUtil::resolve_lookup_table_addresses(&config, &rpc_client, &lookups).await; assert!(matches!(result, Err(KoraError::RpcError(_)))); if let Err(KoraError::RpcError(msg)) = result { @@ -1086,7 +1092,7 @@ mod tests { #[tokio::test] async fn test_resolve_lookup_table_addresses_invalid_index() { let config = setup_test_config(); - let _m = setup_config_mock(config); + let _m = setup_config_mock(config.clone()); let lookup_account_key = Pubkey::new_unique(); let address1 = Pubkey::new_unique(); @@ -1120,7 +1126,7 @@ mod tests { readonly_indexes: vec![], }]; - let result = LookupTableUtil::resolve_lookup_table_addresses(&rpc_client, &lookups).await; + let result = LookupTableUtil::resolve_lookup_table_addresses(&config, &rpc_client, &lookups).await; assert!(matches!(result, Err(KoraError::InvalidTransaction(_)))); if let Err(KoraError::InvalidTransaction(msg)) = result { @@ -1132,7 +1138,7 @@ mod tests { #[tokio::test] async fn test_resolve_lookup_table_addresses_invalid_readonly_index() { let config = setup_test_config(); - let _m = setup_config_mock(config); + let _m = setup_config_mock(config.clone()); let lookup_account_key = Pubkey::new_unique(); let address1 = Pubkey::new_unique(); @@ -1165,7 +1171,7 @@ mod tests { readonly_indexes: vec![5], // Invalid index }]; - let result = LookupTableUtil::resolve_lookup_table_addresses(&rpc_client, &lookups).await; + let result = LookupTableUtil::resolve_lookup_table_addresses(&config, &rpc_client, &lookups).await; assert!(matches!(result, Err(KoraError::InvalidTransaction(_)))); if let Err(KoraError::InvalidTransaction(msg)) = result { From b7c06bf6602bd41ee074085062e89dbd8459b034 Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Wed, 12 Nov 2025 16:09:43 -0500 Subject: [PATCH 4/8] fix: address feedback --- crates/lib/src/rpc_server/method/transfer_transaction.rs | 2 +- crates/lib/src/validator/transaction_validator.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/lib/src/rpc_server/method/transfer_transaction.rs b/crates/lib/src/rpc_server/method/transfer_transaction.rs index 592b25cf..7f872d30 100644 --- a/crates/lib/src/rpc_server/method/transfer_transaction.rs +++ b/crates/lib/src/rpc_server/method/transfer_transaction.rs @@ -75,7 +75,7 @@ pub async fn transfer_transaction( instructions.push(transfer(&source, &destination, request.amount)); } else { // Handle wrapped SOL and other SPL tokens - let token_mint = validator.fetch_and_validate_token_mint(&token_mint, rpc_client).await?; + let token_mint = validator.fetch_and_validate_token_mint(&token_mint, config, rpc_client).await?; let token_program = token_mint.get_token_program(); let decimals = token_mint.decimals(); diff --git a/crates/lib/src/validator/transaction_validator.rs b/crates/lib/src/validator/transaction_validator.rs index 581f28e8..a098790c 100644 --- a/crates/lib/src/validator/transaction_validator.rs +++ b/crates/lib/src/validator/transaction_validator.rs @@ -74,6 +74,7 @@ impl TransactionValidator { pub async fn fetch_and_validate_token_mint( &self, mint: &Pubkey, + config: &Config, rpc_client: &RpcClient, ) -> Result, KoraError> { // First check if the mint is in allowed tokens @@ -83,8 +84,7 @@ impl TransactionValidator { ))); } - let config = crate::state::get_config()?; - let mint = TokenUtil::get_mint(&config, rpc_client, mint).await?; + let mint = TokenUtil::get_mint(config, rpc_client, mint).await?; Ok(mint) } From 0471d49c20c78e64cf011fc8877a016e973bc128 Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Sat, 15 Nov 2025 01:12:39 -0500 Subject: [PATCH 5/8] chore: cargo fmt --- crates/lib/src/admin/token_util.rs | 9 +- crates/lib/src/fee/fee.rs | 6 +- .../rpc_server/method/transfer_transaction.rs | 3 +- crates/lib/src/token/token.rs | 7 +- .../src/transaction/versioned_transaction.rs | 58 ++-- crates/lib/src/usage_limit/usage_tracker.rs | 9 +- crates/lib/src/validator/account_validator.rs | 9 +- crates/lib/src/validator/config_validator.rs | 16 +- .../src/validator/transaction_validator.rs | 276 ++++++++++++++---- 9 files changed, 293 insertions(+), 100 deletions(-) diff --git a/crates/lib/src/admin/token_util.rs b/crates/lib/src/admin/token_util.rs index 5dac569b..62634200 100644 --- a/crates/lib/src/admin/token_util.rs +++ b/crates/lib/src/admin/token_util.rs @@ -280,10 +280,11 @@ pub async fn find_missing_atas( } Err(_) => { // Fetch mint account to determine if it's SPL or Token2022 - let mint_account = - CacheUtil::get_account(&config, rpc_client, mint, false).await.map_err(|e| { - KoraError::RpcError(format!("Failed to fetch mint account for {mint}: {e}")) - })?; + let mint_account = CacheUtil::get_account(&config, rpc_client, mint, false) + .await + .map_err(|e| { + KoraError::RpcError(format!("Failed to fetch mint account for {mint}: {e}")) + })?; let token_program = TokenType::get_token_program_from_owner(&mint_account.owner)?; diff --git a/crates/lib/src/fee/fee.rs b/crates/lib/src/fee/fee.rs index 673bc1f8..0214a843 100644 --- a/crates/lib/src/fee/fee.rs +++ b/crates/lib/src/fee/fee.rs @@ -179,7 +179,8 @@ impl FeeConfigUtil { if *is_2022 { if let Some(mint_pubkey) = mint { let mint_account = - CacheUtil::get_account(config, rpc_client, mint_pubkey, true).await?; + CacheUtil::get_account(config, rpc_client, mint_pubkey, true) + .await?; let token_program = TokenType::get_token_program_from_owner(&mint_account.owner)?; @@ -249,7 +250,8 @@ impl FeeConfigUtil { // Analyze payment instructions (checks if payment exists + calculates Token2022 fees) let (has_payment, transfer_fee_config_amount) = - FeeConfigUtil::analyze_payment_instructions(config, transaction, rpc_client, fee_payer).await?; + FeeConfigUtil::analyze_payment_instructions(config, transaction, rpc_client, fee_payer) + .await?; // If payment is required but not found, add estimated payment instruction fee let fee_for_payment_instruction = if is_payment_required && !has_payment { diff --git a/crates/lib/src/rpc_server/method/transfer_transaction.rs b/crates/lib/src/rpc_server/method/transfer_transaction.rs index 7f872d30..caffc5b9 100644 --- a/crates/lib/src/rpc_server/method/transfer_transaction.rs +++ b/crates/lib/src/rpc_server/method/transfer_transaction.rs @@ -75,7 +75,8 @@ pub async fn transfer_transaction( instructions.push(transfer(&source, &destination, request.amount)); } else { // Handle wrapped SOL and other SPL tokens - let token_mint = validator.fetch_and_validate_token_mint(&token_mint, config, rpc_client).await?; + let token_mint = + validator.fetch_and_validate_token_mint(&token_mint, config, rpc_client).await?; let token_program = token_mint.get_token_program(); let decimals = token_mint.decimals(); diff --git a/crates/lib/src/token/token.rs b/crates/lib/src/token/token.rs index 49cf9656..21629006 100644 --- a/crates/lib/src/token/token.rs +++ b/crates/lib/src/token/token.rs @@ -208,7 +208,9 @@ impl TokenUtil { // We need to check the destination token account owner if let Some(mint_pubkey) = mint { // Get destination account to check owner - match CacheUtil::get_account(config, rpc_client, destination_address, false).await { + match CacheUtil::get_account(config, rpc_client, destination_address, false) + .await + { Ok(dest_account) => { let token_program = TokenType::get_token_program_from_owner(&dest_account.owner)?; @@ -365,7 +367,8 @@ impl TokenUtil { } // Check source account extensions (force refresh in case extensions are added) - let source_account = CacheUtil::get_account(config, rpc_client, source_address, true).await?; + let source_account = + CacheUtil::get_account(config, rpc_client, source_address, true).await?; let source_data = source_account.data; let source_state = token_program.unpack_token_account(&source_data)?; diff --git a/crates/lib/src/transaction/versioned_transaction.rs b/crates/lib/src/transaction/versioned_transaction.rs index dab524d3..e5242dcd 100644 --- a/crates/lib/src/transaction/versioned_transaction.rs +++ b/crates/lib/src/transaction/versioned_transaction.rs @@ -354,9 +354,11 @@ impl LookupTableUtil { // Maybe we can use caching here, there's a chance the lookup tables get updated though, so tbd for lookup in lookup_table_lookups { let lookup_table_account = - CacheUtil::get_account(&config, rpc_client, &lookup.account_key, false).await.map_err( - |e| KoraError::RpcError(format!("Failed to fetch lookup table: {e}")), - )?; + CacheUtil::get_account(&config, rpc_client, &lookup.account_key, false) + .await + .map_err(|e| { + KoraError::RpcError(format!("Failed to fetch lookup table: {e}")) + })?; // Parse the lookup table account data to get the actual addresses let address_lookup_table = AddressLookupTable::deserialize(&lookup_table_account.data) @@ -701,10 +703,14 @@ mod tests { ); let rpc_client = RpcMockBuilder::new().with_custom_mocks(mocks).build(); - let resolved = - VersionedTransactionResolved::from_transaction(&transaction, &config, &rpc_client, true) - .await - .unwrap(); + let resolved = VersionedTransactionResolved::from_transaction( + &transaction, + &config, + &rpc_client, + true, + ) + .await + .unwrap(); assert_eq!(resolved.transaction, transaction); assert_eq!(resolved.all_account_keys, transaction.message.static_account_keys()); @@ -803,10 +809,14 @@ mod tests { let rpc_client = RpcMockBuilder::new().with_custom_mocks(mocks).build(); - let resolved = - VersionedTransactionResolved::from_transaction(&transaction, &config, &rpc_client, true) - .await - .unwrap(); + let resolved = VersionedTransactionResolved::from_transaction( + &transaction, + &config, + &rpc_client, + true, + ) + .await + .unwrap(); assert_eq!(resolved.transaction, transaction); @@ -848,8 +858,13 @@ mod tests { ); let rpc_client = RpcMockBuilder::new().with_custom_mocks(mocks).build(); - let result = - VersionedTransactionResolved::from_transaction(&transaction, &config, &rpc_client, true).await; + let result = VersionedTransactionResolved::from_transaction( + &transaction, + &config, + &rpc_client, + true, + ) + .await; // The simulation should fail, but the exact error type depends on mock implementation // We expect either an RpcError (from mock deserialization) or InvalidTransaction (from simulation logic) @@ -1047,7 +1062,9 @@ mod tests { }]; let resolved_addresses = - LookupTableUtil::resolve_lookup_table_addresses(&config, &rpc_client, &lookups).await.unwrap(); + LookupTableUtil::resolve_lookup_table_addresses(&config, &rpc_client, &lookups) + .await + .unwrap(); assert_eq!(resolved_addresses.len(), 3); assert_eq!(resolved_addresses[0], address1); @@ -1064,7 +1081,9 @@ mod tests { let lookups = vec![]; let resolved_addresses = - LookupTableUtil::resolve_lookup_table_addresses(&config, &rpc_client, &lookups).await.unwrap(); + LookupTableUtil::resolve_lookup_table_addresses(&config, &rpc_client, &lookups) + .await + .unwrap(); assert_eq!(resolved_addresses.len(), 0); } @@ -1081,7 +1100,8 @@ mod tests { readonly_indexes: vec![], }]; - let result = LookupTableUtil::resolve_lookup_table_addresses(&config, &rpc_client, &lookups).await; + let result = + LookupTableUtil::resolve_lookup_table_addresses(&config, &rpc_client, &lookups).await; assert!(matches!(result, Err(KoraError::RpcError(_)))); if let Err(KoraError::RpcError(msg)) = result { @@ -1126,7 +1146,8 @@ mod tests { readonly_indexes: vec![], }]; - let result = LookupTableUtil::resolve_lookup_table_addresses(&config, &rpc_client, &lookups).await; + let result = + LookupTableUtil::resolve_lookup_table_addresses(&config, &rpc_client, &lookups).await; assert!(matches!(result, Err(KoraError::InvalidTransaction(_)))); if let Err(KoraError::InvalidTransaction(msg)) = result { @@ -1171,7 +1192,8 @@ mod tests { readonly_indexes: vec![5], // Invalid index }]; - let result = LookupTableUtil::resolve_lookup_table_addresses(&config, &rpc_client, &lookups).await; + let result = + LookupTableUtil::resolve_lookup_table_addresses(&config, &rpc_client, &lookups).await; assert!(matches!(result, Err(KoraError::InvalidTransaction(_)))); if let Err(KoraError::InvalidTransaction(msg)) = result { diff --git a/crates/lib/src/usage_limit/usage_tracker.rs b/crates/lib/src/usage_limit/usage_tracker.rs index 84cf6e06..faaedf4c 100644 --- a/crates/lib/src/usage_limit/usage_tracker.rs +++ b/crates/lib/src/usage_limit/usage_tracker.rs @@ -307,7 +307,8 @@ mod tests { let _ = UsageTracker::init_usage_limiter().await; let config = get_config().unwrap(); - let result = UsageTracker::check_transaction_usage_limit(&config, &create_mock_transaction()).await; + let result = + UsageTracker::check_transaction_usage_limit(&config, &create_mock_transaction()).await; match &result { Ok(_) => {} Err(e) => println!("Test failed with error: {e}"), @@ -327,7 +328,8 @@ mod tests { let _ = UsageTracker::init_usage_limiter().await; let config = get_config().unwrap(); - let result = UsageTracker::check_transaction_usage_limit(&config, &create_mock_transaction()).await; + let result = + UsageTracker::check_transaction_usage_limit(&config, &create_mock_transaction()).await; assert!(result.is_ok()); } @@ -343,7 +345,8 @@ mod tests { let _ = UsageTracker::init_usage_limiter().await; let config = get_config().unwrap(); - let result = UsageTracker::check_transaction_usage_limit(&config, &create_mock_transaction()).await; + let result = + UsageTracker::check_transaction_usage_limit(&config, &create_mock_transaction()).await; assert!(result.is_err()); assert!(result .unwrap_err() diff --git a/crates/lib/src/validator/account_validator.rs b/crates/lib/src/validator/account_validator.rs index f023aaf4..5cd2fd31 100644 --- a/crates/lib/src/validator/account_validator.rs +++ b/crates/lib/src/validator/account_validator.rs @@ -367,7 +367,8 @@ mod tests { let account_pubkey = Pubkey::new_unique(); let config = get_config().unwrap(); - let result = validate_account(&config, &rpc_client, &account_pubkey, Some(AccountType::Mint)).await; + let result = + validate_account(&config, &rpc_client, &account_pubkey, Some(AccountType::Mint)).await; assert!(result.is_ok()); } @@ -393,7 +394,8 @@ mod tests { let config = get_config().unwrap(); let result = - validate_account(&config, &rpc_client, &account_pubkey, Some(AccountType::System)).await; + validate_account(&config, &rpc_client, &account_pubkey, Some(AccountType::System)) + .await; assert!(result.is_err()); let error_msg = result.unwrap_err().to_string(); assert!(error_msg.contains("Account") && error_msg.contains("not found")); @@ -409,7 +411,8 @@ mod tests { let config = get_config().unwrap(); let result = - validate_account(&config, &rpc_client, &account_pubkey, Some(AccountType::System)).await; + validate_account(&config, &rpc_client, &account_pubkey, Some(AccountType::System)) + .await; assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("is not owned by")); } diff --git a/crates/lib/src/validator/config_validator.rs b/crates/lib/src/validator/config_validator.rs index 651608cc..bc0b6c60 100644 --- a/crates/lib/src/validator/config_validator.rs +++ b/crates/lib/src/validator/config_validator.rs @@ -472,9 +472,13 @@ impl ConfigValidator { // Validate allowed programs - should be executable for program_str in &config.validation.allowed_programs { if let Ok(program_pubkey) = Pubkey::from_str(program_str) { - if let Err(e) = - validate_account(config, rpc_client, &program_pubkey, Some(AccountType::Program)) - .await + if let Err(e) = validate_account( + config, + rpc_client, + &program_pubkey, + Some(AccountType::Program), + ) + .await { errors.push(format!("Program {program_str} validation failed: {e}")); } @@ -485,7 +489,8 @@ impl ConfigValidator { for token_str in &config.validation.allowed_tokens { if let Ok(token_pubkey) = Pubkey::from_str(token_str) { if let Err(e) = - validate_account(config, rpc_client, &token_pubkey, Some(AccountType::Mint)).await + validate_account(config, rpc_client, &token_pubkey, Some(AccountType::Mint)) + .await { errors.push(format!("Token {token_str} validation failed: {e}")); } @@ -496,7 +501,8 @@ impl ConfigValidator { for token_str in &config.validation.allowed_spl_paid_tokens { if let Ok(token_pubkey) = Pubkey::from_str(token_str) { if let Err(e) = - validate_account(config, rpc_client, &token_pubkey, Some(AccountType::Mint)).await + validate_account(config, rpc_client, &token_pubkey, Some(AccountType::Mint)) + .await { errors.push(format!("SPL paid token {token_str} validation failed: {e}")); } diff --git a/crates/lib/src/validator/transaction_validator.rs b/crates/lib/src/validator/transaction_validator.rs index a098790c..eb2f108d 100644 --- a/crates/lib/src/validator/transaction_validator.rs +++ b/crates/lib/src/validator/transaction_validator.rs @@ -286,7 +286,8 @@ impl TransactionValidator { transaction_resolved: &mut VersionedTransactionResolved, rpc_client: &RpcClient, ) -> Result<(), KoraError> { - let total_outflow = self.calculate_total_outflow(config, transaction_resolved, rpc_client).await?; + let total_outflow = + self.calculate_total_outflow(config, transaction_resolved, rpc_client).await?; if total_outflow > self.max_allowed_lamports { return Err(KoraError::InvalidTransaction(format!( @@ -472,7 +473,10 @@ mod tests { let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_ok()); } #[tokio::test] @@ -493,7 +497,10 @@ mod tests { let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_ok()); // Test multiple transfers let instructions = @@ -501,7 +508,10 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&instructions, Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_ok()); } #[tokio::test] @@ -521,7 +531,10 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_ok()); // Test disallowed program let fake_program = Pubkey::new_unique(); @@ -534,7 +547,10 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_err()); } #[tokio::test] @@ -566,7 +582,10 @@ mod tests { let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); transaction.transaction.signatures = vec![Default::default(); 3]; // Add 3 dummy signatures - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_err()); } #[tokio::test] @@ -586,14 +605,20 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_ok()); // Test SignAndSend mode without fee payer (should succeed) let instruction = transfer(&sender, &recipient, 1000); let message = VersionedMessage::Legacy(Message::new(&[instruction], None)); // No fee payer specified let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_ok()); } #[tokio::test] @@ -610,7 +635,10 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_err()); } #[tokio::test] @@ -639,7 +667,10 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_err()); } #[tokio::test] @@ -662,7 +693,10 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_ok()); // Test with allow_sol_transfers = false let rpc_client = RpcMockBuilder::new().build(); @@ -677,7 +711,10 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_err()); } #[tokio::test] @@ -701,7 +738,10 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_ok()); // Test with allow_assign = false @@ -718,7 +758,10 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_err()); } #[tokio::test] @@ -752,7 +795,10 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[transfer_ix], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_ok()); // Test with allow_spl_transfers = false let rpc_client = RpcMockBuilder::new().build(); @@ -777,7 +823,10 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[transfer_ix], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_err()); // Test with other account as source - should always pass let other_signer = Pubkey::new_unique(); @@ -794,7 +843,10 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[transfer_ix], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_ok()); } #[tokio::test] @@ -833,7 +885,10 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[transfer_ix], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_ok()); // Test with allow_token2022_transfers = false let rpc_client = RpcMockBuilder::new() @@ -863,7 +918,10 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should fail because fee payer is not allowed to be source - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_err()); // Test with other account as source - should always pass let other_signer = Pubkey::new_unique(); @@ -884,7 +942,10 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should pass because fee payer is not the source - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_ok()); } #[tokio::test] @@ -910,8 +971,10 @@ mod tests { VersionedMessage::Legacy(Message::new(&[transfer_instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - let outflow = - validator.calculate_total_outflow(&config, &mut transaction, &rpc_client).await.unwrap(); + let outflow = validator + .calculate_total_outflow(&config, &mut transaction, &rpc_client) + .await + .unwrap(); assert_eq!(outflow, 100_000, "Transfer from fee payer should add to outflow"); // Test 2: Fee payer as recipient in Transfer - should subtract from outflow (account closure) @@ -922,8 +985,10 @@ mod tests { let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - let outflow = - validator.calculate_total_outflow(&config, &mut transaction, &rpc_client).await.unwrap(); + let outflow = validator + .calculate_total_outflow(&config, &mut transaction, &rpc_client) + .await + .unwrap(); assert_eq!(outflow, 0, "Transfer to fee payer should subtract from outflow"); // 0 - 50_000 = 0 (saturating_sub) // Test 3: Fee payer as funding account in CreateAccount - should add to outflow @@ -939,8 +1004,10 @@ mod tests { VersionedMessage::Legacy(Message::new(&[create_instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - let outflow = - validator.calculate_total_outflow(&config, &mut transaction, &rpc_client).await.unwrap(); + let outflow = validator + .calculate_total_outflow(&config, &mut transaction, &rpc_client) + .await + .unwrap(); assert_eq!(outflow, 200_000, "CreateAccount funded by fee payer should add to outflow"); // Test 4: Fee payer as funding account in CreateAccountWithSeed - should add to outflow @@ -959,8 +1026,10 @@ mod tests { )); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - let outflow = - validator.calculate_total_outflow(&config, &mut transaction, &rpc_client).await.unwrap(); + let outflow = validator + .calculate_total_outflow(&config, &mut transaction, &rpc_client) + .await + .unwrap(); assert_eq!( outflow, 300_000, "CreateAccountWithSeed funded by fee payer should add to outflow" @@ -981,8 +1050,10 @@ mod tests { )); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - let outflow = - validator.calculate_total_outflow(&config, &mut transaction, &rpc_client).await.unwrap(); + let outflow = validator + .calculate_total_outflow(&config, &mut transaction, &rpc_client) + .await + .unwrap(); assert_eq!(outflow, 150_000, "TransferWithSeed from fee payer should add to outflow"); // Test 6: Multiple instructions - should sum correctly @@ -994,8 +1065,10 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&instructions, Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - let outflow = - validator.calculate_total_outflow(&config, &mut transaction, &rpc_client).await.unwrap(); + let outflow = validator + .calculate_total_outflow(&config, &mut transaction, &rpc_client) + .await + .unwrap(); assert_eq!( outflow, 120_000, "Multiple instructions should sum correctly: 100000 - 30000 + 50000 = 120000" @@ -1008,8 +1081,10 @@ mod tests { VersionedMessage::Legacy(Message::new(&[transfer_instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - let outflow = - validator.calculate_total_outflow(&config, &mut transaction, &rpc_client).await.unwrap(); + let outflow = validator + .calculate_total_outflow(&config, &mut transaction, &rpc_client) + .await + .unwrap(); assert_eq!(outflow, 0, "Transfer from other account should not affect outflow"); // Test 8: Other account funding CreateAccount - should not affect outflow @@ -1020,8 +1095,10 @@ mod tests { VersionedMessage::Legacy(Message::new(&[create_instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - let outflow = - validator.calculate_total_outflow(&config, &mut transaction, &rpc_client).await.unwrap(); + let outflow = validator + .calculate_total_outflow(&config, &mut transaction, &rpc_client) + .await + .unwrap(); assert_eq!(outflow, 0, "CreateAccount funded by other account should not affect outflow"); } @@ -1056,7 +1133,10 @@ mod tests { let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should pass because allow_burn is true by default - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_ok()); // Test with allow_burn = false @@ -1083,7 +1163,10 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should fail because fee payer cannot burn tokens when allow_burn is false - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_err()); // Test burn_checked instruction let burn_checked_ix = spl_token_interface::instruction::burn_checked( @@ -1102,7 +1185,10 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should also fail for burn_checked - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_err()); } #[tokio::test] @@ -1135,7 +1221,10 @@ mod tests { let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should pass because allow_close_account is true by default - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_ok()); // Test with allow_close_account = false let rpc_client = RpcMockBuilder::new().build(); @@ -1160,7 +1249,10 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should fail because fee payer cannot close accounts when allow_close_account is false - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_err()); } #[tokio::test] @@ -1194,7 +1286,10 @@ mod tests { let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should pass because allow_approve is true by default - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_ok()); // Test with allow_approve = false let rpc_client = RpcMockBuilder::new().build(); @@ -1220,7 +1315,10 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should fail because fee payer cannot approve when allow_approve is false - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_err()); // Test approve_checked instruction let mint = Pubkey::new_unique(); @@ -1242,7 +1340,10 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should also fail for approve_checked - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_err()); } #[tokio::test] @@ -1276,7 +1377,10 @@ mod tests { let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should fail for Token2022 burn - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_err()); } #[tokio::test] @@ -1309,7 +1413,10 @@ mod tests { let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should fail for Token2022 close account - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_err()); } #[tokio::test] @@ -1343,7 +1450,10 @@ mod tests { let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should pass because allow_approve is true by default - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_ok()); // Test with allow_approve = false @@ -1370,7 +1480,10 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should fail because fee payer cannot approve when allow_approve is false - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_err()); // Test approve_checked instruction let mint = Pubkey::new_unique(); @@ -1392,7 +1505,10 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); // Should also fail for approve_checked - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_err()); } #[tokio::test] @@ -1416,7 +1532,10 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_ok()); // Test with allow_create_account = false let rpc_client = RpcMockBuilder::new().build(); @@ -1430,7 +1549,10 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_err()); } #[tokio::test] @@ -1452,7 +1574,10 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_ok()); // Test with allow_allocate = false let rpc_client = RpcMockBuilder::new().build(); @@ -1466,7 +1591,10 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_err()); } #[tokio::test] @@ -1491,7 +1619,10 @@ mod tests { VersionedMessage::Legacy(Message::new(&[instructions[1].clone()], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_ok()); // Test with allow_initialize = false let rpc_client = RpcMockBuilder::new().build(); @@ -1506,7 +1637,10 @@ mod tests { VersionedMessage::Legacy(Message::new(&[instructions[1].clone()], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_err()); } #[tokio::test] @@ -1529,7 +1663,10 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_ok()); // Test with allow_advance = false let rpc_client = RpcMockBuilder::new().build(); @@ -1543,7 +1680,10 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_err()); } #[tokio::test] @@ -1567,7 +1707,10 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_ok()); // Test with allow_withdraw = false let rpc_client = RpcMockBuilder::new().build(); @@ -1581,7 +1724,10 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_err()); } #[tokio::test] @@ -1605,7 +1751,10 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_ok()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_ok()); // Test with allow_authorize = false let rpc_client = RpcMockBuilder::new().build(); @@ -1619,7 +1768,10 @@ mod tests { let message = VersionedMessage::Legacy(Message::new(&[instruction], Some(&fee_payer))); let mut transaction = TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); - assert!(validator.validate_transaction(&config, &mut transaction, &rpc_client).await.is_err()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_err()); } #[test] From bc7700092e5fb46f5be335f3eaf6eb396072243a Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Tue, 18 Nov 2025 11:45:52 -0500 Subject: [PATCH 6/8] fix: refactor remaining get_config --- crates/lib/src/admin/token_util.rs | 3 +- crates/lib/src/cache.rs | 4 +- crates/lib/src/fee/fee.rs | 3 +- crates/lib/src/fee/price.rs | 18 ++++-- crates/lib/src/metrics/balance.rs | 31 +++++----- crates/lib/src/tests/cache_mock.rs | 2 +- crates/lib/src/token/token.rs | 59 ++++++++++++++----- crates/lib/src/validator/account_validator.rs | 2 +- 8 files changed, 81 insertions(+), 41 deletions(-) diff --git a/crates/lib/src/admin/token_util.rs b/crates/lib/src/admin/token_util.rs index 62634200..329fbad7 100644 --- a/crates/lib/src/admin/token_util.rs +++ b/crates/lib/src/admin/token_util.rs @@ -92,10 +92,11 @@ pub async fn initialize_atas_with_chunk_size( compute_unit_limit: Option, chunk_size: usize, ) -> Result<(), KoraError> { + let config = get_config()?; + for address in addresses_to_initialize_atas { println!("Initializing ATAs for address: {address}"); - let config = get_config()?; let atas_to_create = find_missing_atas(&config, rpc_client, address).await?; if atas_to_create.is_empty() { diff --git a/crates/lib/src/cache.rs b/crates/lib/src/cache.rs index e450edd1..8bcfe102 100644 --- a/crates/lib/src/cache.rs +++ b/crates/lib/src/cache.rs @@ -179,13 +179,13 @@ impl CacheUtil { } /// Check if cache is enabled and available - fn is_cache_enabled(config: &crate::config::Config) -> bool { + fn is_cache_enabled(config: &Config) -> bool { config.kora.cache.enabled && config.kora.cache.url.is_some() } /// Get account from cache with optional force refresh pub async fn get_account( - config: &crate::config::Config, + config: &Config, rpc_client: &RpcClient, pubkey: &Pubkey, force_refresh: bool, diff --git a/crates/lib/src/fee/fee.rs b/crates/lib/src/fee/fee.rs index 0214a843..b1e41d7c 100644 --- a/crates/lib/src/fee/fee.rs +++ b/crates/lib/src/fee/fee.rs @@ -296,7 +296,7 @@ impl FeeConfigUtil { let fixed_fee_lamports = config .validation .price - .get_required_lamports_with_fixed(rpc_client, price_source) + .get_required_lamports_with_fixed(config, rpc_client, price_source) .await?; if *strict { @@ -371,6 +371,7 @@ impl FeeConfigUtil { } let fee_value_in_token = TokenUtil::calculate_lamports_value_in_token( + config, fee_in_lamports, &token_mint, &validation_config.price_source, diff --git a/crates/lib/src/fee/price.rs b/crates/lib/src/fee/price.rs index 6981f107..54fb9e00 100644 --- a/crates/lib/src/fee/price.rs +++ b/crates/lib/src/fee/price.rs @@ -1,4 +1,4 @@ -use crate::{error::KoraError, oracle::PriceSource, token::token::TokenUtil}; +use crate::{config::Config, error::KoraError, oracle::PriceSource, token::token::TokenUtil}; use serde::{Deserialize, Serialize}; use solana_client::nonblocking::rpc_client::RpcClient; use solana_sdk::pubkey::Pubkey; @@ -28,11 +28,13 @@ pub struct PriceConfig { impl PriceConfig { pub async fn get_required_lamports_with_fixed( &self, + config: &Config, rpc_client: &RpcClient, price_source: PriceSource, ) -> Result { if let PriceModel::Fixed { amount, token, .. } = &self.model { return TokenUtil::calculate_token_value_in_lamports( + config, *amount, &Pubkey::from_str(token).map_err(|e| { log::error!("Invalid Pubkey for price {e}"); @@ -78,7 +80,10 @@ impl PriceConfig { mod tests { use super::*; - use crate::tests::{common::create_mock_rpc_client_with_mint, config_mock::ConfigMockBuilder}; + use crate::tests::{ + common::create_mock_rpc_client_with_mint, + config_mock::{mock_state::get_config, ConfigMockBuilder}, + }; #[tokio::test] async fn test_margin_model_get_required_lamports() { @@ -110,6 +115,7 @@ mod tests { #[tokio::test] async fn test_fixed_model_get_required_lamports_with_oracle() { let _m = ConfigMockBuilder::new().build_and_setup(); + let config = get_config().unwrap(); let rpc_client = create_mock_rpc_client_with_mint(6); // USDC has 6 decimals let usdc_mint = "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"; @@ -125,7 +131,7 @@ mod tests { let price_source = PriceSource::Mock; let result = - price_config.get_required_lamports_with_fixed(&rpc_client, price_source).await.unwrap(); + price_config.get_required_lamports_with_fixed(&config, &rpc_client, price_source).await.unwrap(); // Expected calculation: // 1,000,000 base units / 10^6 = 1.0 USDC @@ -137,6 +143,7 @@ mod tests { #[tokio::test] async fn test_fixed_model_get_required_lamports_with_custom_price() { let _m = ConfigMockBuilder::new().build_and_setup(); + let config = get_config().unwrap(); let rpc_client = create_mock_rpc_client_with_mint(9); // 9 decimals token let custom_token = "So11111111111111111111111111111111111111112"; // SOL mint @@ -152,7 +159,7 @@ mod tests { let price_source = PriceSource::Mock; let result = - price_config.get_required_lamports_with_fixed(&rpc_client, price_source).await.unwrap(); + price_config.get_required_lamports_with_fixed(&config, &rpc_client, price_source).await.unwrap(); // Expected calculation: // 500,000,000 base units / 10^9 = 0.5 tokens @@ -164,6 +171,7 @@ mod tests { #[tokio::test] async fn test_fixed_model_get_required_lamports_small_amount() { let _m = ConfigMockBuilder::new().build_and_setup(); + let config = get_config().unwrap(); let rpc_client = create_mock_rpc_client_with_mint(6); // USDC has 6 decimals let usdc_mint = "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"; @@ -178,7 +186,7 @@ mod tests { let price_source = PriceSource::Mock; let result = - price_config.get_required_lamports_with_fixed(&rpc_client, price_source).await.unwrap(); + price_config.get_required_lamports_with_fixed(&config, &rpc_client, price_source).await.unwrap(); // Expected calculation: // 1,000 base units / 10^6 = 0.001 USDC diff --git a/crates/lib/src/metrics/balance.rs b/crates/lib/src/metrics/balance.rs index c4d427a5..30dafa7f 100644 --- a/crates/lib/src/metrics/balance.rs +++ b/crates/lib/src/metrics/balance.rs @@ -2,7 +2,7 @@ use crate::state::get_config; #[cfg(test)] use crate::tests::config_mock::mock_state::get_config; -use crate::{cache::CacheUtil, error::KoraError, state::get_signers_info}; +use crate::{cache::CacheUtil, config::Config, error::KoraError, state::get_signers_info}; use prometheus::{register_gauge_vec, GaugeVec}; use solana_client::nonblocking::rpc_client::RpcClient; use solana_sdk::pubkey::Pubkey; @@ -44,7 +44,10 @@ impl BalanceTracker { } /// Track all signers' balances and update Prometheus metrics - pub async fn track_all_signer_balances(rpc_client: &Arc) -> Result<(), KoraError> { + pub async fn track_all_signer_balances( + config: &Config, + rpc_client: &Arc, + ) -> Result<(), KoraError> { if !BalanceTracker::is_enabled() { return Ok(()); } @@ -52,15 +55,6 @@ impl BalanceTracker { // Get all signers in the pool let signers_info = get_signers_info()?; - // Fetch config once for all signers - let config = match get_config() { - Ok(c) => c, - Err(e) => { - log::warn!("Failed to get config in metrics: {e}"); - return Ok(()); - } - }; - if let Some(gauge_vec) = SIGNER_BALANCE_GAUGES.get() { let mut balance_results = Vec::new(); @@ -129,6 +123,9 @@ impl BalanceTracker { let interval_seconds = config.metrics.fee_payer_balance.expiry_seconds; log::info!("Starting multi-signer balance tracking background task with {interval_seconds}s interval"); + // Clone config to move into the spawned task + let config = config.clone(); + // Spawn a background task that runs forever let handle = tokio::spawn(async move { let mut interval = interval(Duration::from_secs(interval_seconds)); @@ -137,7 +134,8 @@ impl BalanceTracker { interval.tick().await; // Track all signer balances, but don't let errors crash the loop - if let Err(e) = BalanceTracker::track_all_signer_balances(&rpc_client).await { + if let Err(e) = BalanceTracker::track_all_signer_balances(&config, &rpc_client).await + { log::warn!("Failed to track signer balances in background task: {e}"); } } @@ -304,8 +302,9 @@ mod tests { ) .build_and_setup(); + let config = get_config().unwrap(); let mock_rpc = RpcMockBuilder::new().build(); - let result = BalanceTracker::track_all_signer_balances(&mock_rpc).await; + let result = BalanceTracker::track_all_signer_balances(&config, &mock_rpc).await; assert!(result.is_ok()); } @@ -326,10 +325,11 @@ mod tests { setup_test_signer_pool(); let _ = BalanceTracker::init().await; + let config = get_config().unwrap(); let account = create_mock_account_with_balance(1_000_000_000); // 1 SOL let mock_rpc = RpcMockBuilder::new().with_account_info(&account).build(); - let result = BalanceTracker::track_all_signer_balances(&mock_rpc).await; + let result = BalanceTracker::track_all_signer_balances(&config, &mock_rpc).await; assert!(result.is_ok()); } @@ -350,9 +350,10 @@ mod tests { setup_test_signer_pool(); let _ = BalanceTracker::init().await; + let config = get_config().unwrap(); let mock_rpc = RpcMockBuilder::new().with_account_not_found().build(); - let result = BalanceTracker::track_all_signer_balances(&mock_rpc).await; + let result = BalanceTracker::track_all_signer_balances(&config, &mock_rpc).await; assert!(result.is_ok()); } diff --git a/crates/lib/src/tests/cache_mock.rs b/crates/lib/src/tests/cache_mock.rs index e065df26..fff91145 100644 --- a/crates/lib/src/tests/cache_mock.rs +++ b/crates/lib/src/tests/cache_mock.rs @@ -7,7 +7,7 @@ mock! { pub CacheUtil { pub async fn init() -> Result<(), KoraError>; pub async fn get_account( - config: &crate::config::Config, + config: &Config, rpc_client: &RpcClient, pubkey: &Pubkey, force_refresh: bool, diff --git a/crates/lib/src/token/token.rs b/crates/lib/src/token/token.rs index 21629006..8ed63341 100644 --- a/crates/lib/src/token/token.rs +++ b/crates/lib/src/token/token.rs @@ -1,4 +1,5 @@ use crate::{ + config::Config, error::KoraError, oracle::{get_price_oracle, PriceSource, RetryingPriceOracle, TokenPrice}, token::{ @@ -21,9 +22,6 @@ use solana_sdk::{native_token::LAMPORTS_PER_SOL, pubkey::Pubkey}; use spl_associated_token_account_interface::address::get_associated_token_address_with_program_id; use std::{collections::HashMap, str::FromStr, time::Duration}; -#[cfg(not(test))] -use crate::state::get_config; - #[cfg(test)] use {crate::tests::config_mock::mock_state::get_config, rust_decimal_macros::dec}; @@ -69,7 +67,7 @@ impl TokenUtil { } pub async fn get_mint( - config: &crate::config::Config, + config: &Config, rpc_client: &RpcClient, mint_pubkey: &Pubkey, ) -> Result, KoraError> { @@ -83,7 +81,7 @@ impl TokenUtil { } pub async fn get_mint_decimals( - config: &crate::config::Config, + config: &Config, rpc_client: &RpcClient, mint_pubkey: &Pubkey, ) -> Result { @@ -92,12 +90,12 @@ impl TokenUtil { } pub async fn get_token_price_and_decimals( + config: &Config, mint: &Pubkey, price_source: PriceSource, rpc_client: &RpcClient, ) -> Result<(TokenPrice, u8), KoraError> { - let config = get_config()?; - let decimals = Self::get_mint_decimals(&config, rpc_client, mint).await?; + let decimals = Self::get_mint_decimals(config, rpc_client, mint).await?; let oracle = RetryingPriceOracle::new(3, Duration::from_secs(1), get_price_oracle(price_source)); @@ -112,13 +110,14 @@ impl TokenUtil { } pub async fn calculate_token_value_in_lamports( + config: &Config, amount: u64, mint: &Pubkey, price_source: PriceSource, rpc_client: &RpcClient, ) -> Result { let (token_price, decimals) = - Self::get_token_price_and_decimals(mint, price_source, rpc_client).await?; + Self::get_token_price_and_decimals(config, mint, price_source, rpc_client).await?; // Convert amount to Decimal with proper scaling let amount_decimal = Decimal::from_u64(amount) @@ -144,13 +143,15 @@ impl TokenUtil { } pub async fn calculate_lamports_value_in_token( + config: &Config, lamports: u64, mint: &Pubkey, price_source: &PriceSource, rpc_client: &RpcClient, ) -> Result { let (token_price, decimals) = - Self::get_token_price_and_decimals(mint, price_source.clone(), rpc_client).await?; + Self::get_token_price_and_decimals(config, mint, price_source.clone(), rpc_client) + .await?; // Convert lamports to SOL using Decimal let lamports_decimal = Decimal::from_u64(lamports) @@ -177,7 +178,7 @@ impl TokenUtil { /// Calculate the total lamports value of SPL token transfers where the fee payer is involved /// This includes both outflow (fee payer as owner/source) and inflow (fee payer owns destination) pub async fn calculate_spl_transfers_value_in_lamports( - config: &crate::config::Config, + config: &Config, spl_transfers: &[ParsedSPLInstructionData], fee_payer: &Pubkey, price_source: &PriceSource, @@ -335,7 +336,7 @@ impl TokenUtil { /// Validate Token2022 extensions for payment instructions /// This checks if any blocked extensions are present on the payment accounts pub async fn validate_token2022_extensions_for_payment( - config: &crate::config::Config, + config: &Config, rpc_client: &RpcClient, source_address: &Pubkey, destination_address: &Pubkey, @@ -410,7 +411,7 @@ impl TokenUtil { } pub async fn verify_token_payment( - config: &crate::config::Config, + config: &Config, transaction_resolved: &mut VersionedTransactionResolved, rpc_client: &RpcClient, required_lamports: u64, @@ -472,6 +473,7 @@ impl TokenUtil { } let lamport_value = TokenUtil::calculate_token_value_in_lamports( + config, *amount, &token_state.mint(), config.validation.price_source.clone(), @@ -617,11 +619,12 @@ mod tests_token { #[tokio::test] async fn test_get_token_price_and_decimals_spl() { let _lock = ConfigMockBuilder::new().build_and_setup(); + let config = get_config().unwrap(); let mint = Pubkey::from_str(WSOL_DEVNET_MINT).unwrap(); let rpc_client = RpcMockBuilder::new().with_mint_account(9).build(); let (token_price, decimals) = - TokenUtil::get_token_price_and_decimals(&mint, PriceSource::Mock, &rpc_client) + TokenUtil::get_token_price_and_decimals(&config, &mint, PriceSource::Mock, &rpc_client) .await .unwrap(); @@ -632,11 +635,12 @@ mod tests_token { #[tokio::test] async fn test_get_token_price_and_decimals_token2022() { let _lock = ConfigMockBuilder::new().build_and_setup(); + let config = get_config().unwrap(); let mint = Pubkey::from_str(USDC_DEVNET_MINT).unwrap(); let rpc_client = RpcMockBuilder::new().with_mint_account(6).build(); let (token_price, decimals) = - TokenUtil::get_token_price_and_decimals(&mint, PriceSource::Mock, &rpc_client) + TokenUtil::get_token_price_and_decimals(&config, &mint, PriceSource::Mock, &rpc_client) .await .unwrap(); @@ -647,22 +651,26 @@ mod tests_token { #[tokio::test] async fn test_get_token_price_and_decimals_account_not_found() { let _lock = ConfigMockBuilder::new().build_and_setup(); + let config = get_config().unwrap(); let mint = Pubkey::from_str(WSOL_DEVNET_MINT).unwrap(); let rpc_client = RpcMockBuilder::new().with_account_not_found().build(); let result = - TokenUtil::get_token_price_and_decimals(&mint, PriceSource::Mock, &rpc_client).await; + TokenUtil::get_token_price_and_decimals(&config, &mint, PriceSource::Mock, &rpc_client) + .await; assert!(result.is_err()); } #[tokio::test] async fn test_calculate_token_value_in_lamports_sol() { let _lock = ConfigMockBuilder::new().build_and_setup(); + let config = get_config().unwrap(); let mint = Pubkey::from_str(WSOL_DEVNET_MINT).unwrap(); let rpc_client = RpcMockBuilder::new().with_mint_account(9).build(); let amount = 1_000_000_000; // 1 SOL in lamports let result = TokenUtil::calculate_token_value_in_lamports( + &config, amount, &mint, PriceSource::Mock, @@ -677,11 +685,13 @@ mod tests_token { #[tokio::test] async fn test_calculate_token_value_in_lamports_usdc() { let _lock = ConfigMockBuilder::new().build_and_setup(); + let config = get_config().unwrap(); let mint = Pubkey::from_str(USDC_DEVNET_MINT).unwrap(); let rpc_client = RpcMockBuilder::new().with_mint_account(6).build(); let amount = 1_000_000; // 1 USDC (6 decimals) let result = TokenUtil::calculate_token_value_in_lamports( + &config, amount, &mint, PriceSource::Mock, @@ -697,11 +707,13 @@ mod tests_token { #[tokio::test] async fn test_calculate_token_value_in_lamports_zero_amount() { let _lock = ConfigMockBuilder::new().build_and_setup(); + let config = get_config().unwrap(); let mint = Pubkey::from_str(WSOL_DEVNET_MINT).unwrap(); let rpc_client = RpcMockBuilder::new().with_mint_account(9).build(); let amount = 0; let result = TokenUtil::calculate_token_value_in_lamports( + &config, amount, &mint, PriceSource::Mock, @@ -716,11 +728,13 @@ mod tests_token { #[tokio::test] async fn test_calculate_token_value_in_lamports_small_amount() { let _lock = ConfigMockBuilder::new().build_and_setup(); + let config = get_config().unwrap(); let mint = Pubkey::from_str(USDC_DEVNET_MINT).unwrap(); let rpc_client = RpcMockBuilder::new().with_mint_account(6).build(); let amount = 1; // 0.000001 USDC (smallest unit) let result = TokenUtil::calculate_token_value_in_lamports( + &config, amount, &mint, PriceSource::Mock, @@ -736,11 +750,13 @@ mod tests_token { #[tokio::test] async fn test_calculate_lamports_value_in_token_sol() { let _lock = ConfigMockBuilder::new().build_and_setup(); + let config = get_config().unwrap(); let mint = Pubkey::from_str(WSOL_DEVNET_MINT).unwrap(); let rpc_client = RpcMockBuilder::new().with_mint_account(9).build(); let lamports = 1_000_000_000; // 1 SOL let result = TokenUtil::calculate_lamports_value_in_token( + &config, lamports, &mint, &PriceSource::Mock, @@ -755,11 +771,13 @@ mod tests_token { #[tokio::test] async fn test_calculate_lamports_value_in_token_usdc() { let _lock = ConfigMockBuilder::new().build_and_setup(); + let config = get_config().unwrap(); let mint = Pubkey::from_str(USDC_DEVNET_MINT).unwrap(); let rpc_client = RpcMockBuilder::new().with_mint_account(6).build(); let lamports = 100_000; // 0.0001 SOL let result = TokenUtil::calculate_lamports_value_in_token( + &config, lamports, &mint, &PriceSource::Mock, @@ -775,11 +793,13 @@ mod tests_token { #[tokio::test] async fn test_calculate_lamports_value_in_token_zero_lamports() { let _lock = ConfigMockBuilder::new().build_and_setup(); + let config = get_config().unwrap(); let mint = Pubkey::from_str(WSOL_DEVNET_MINT).unwrap(); let rpc_client = RpcMockBuilder::new().with_mint_account(9).build(); let lamports = 0; let result = TokenUtil::calculate_lamports_value_in_token( + &config, lamports, &mint, &PriceSource::Mock, @@ -794,6 +814,7 @@ mod tests_token { #[tokio::test] async fn test_calculate_price_functions_consistency() { let _lock = ConfigMockBuilder::new().build_and_setup(); + let config = get_config().unwrap(); // Test that convert to lamports and back to token amount gives approximately the same result let mint = Pubkey::from_str(USDC_DEVNET_MINT).unwrap(); let rpc_client = RpcMockBuilder::new().with_mint_account(6).build(); @@ -802,6 +823,7 @@ mod tests_token { // Convert token amount to lamports let lamports_result = TokenUtil::calculate_token_value_in_lamports( + &config, original_amount, &mint, PriceSource::Mock, @@ -818,6 +840,7 @@ mod tests_token { // Convert lamports back to token amount let recovered_amount_result = TokenUtil::calculate_lamports_value_in_token( + &config, lamports, &mint, &PriceSource::Mock, @@ -833,10 +856,12 @@ mod tests_token { #[tokio::test] async fn test_price_calculation_with_account_error() { let _lock = ConfigMockBuilder::new().build_and_setup(); + let config = get_config().unwrap(); let mint = Pubkey::new_unique(); let rpc_client = RpcMockBuilder::new().with_account_not_found().build(); let result = TokenUtil::calculate_token_value_in_lamports( + &config, 1_000_000, &mint, PriceSource::Mock, @@ -850,10 +875,12 @@ mod tests_token { #[tokio::test] async fn test_lamports_calculation_with_account_error() { let _lock = ConfigMockBuilder::new().build_and_setup(); + let config = get_config().unwrap(); let mint = Pubkey::new_unique(); let rpc_client = RpcMockBuilder::new().with_account_not_found().build(); let result = TokenUtil::calculate_lamports_value_in_token( + &config, 1_000_000, &mint, &PriceSource::Mock, @@ -867,6 +894,7 @@ mod tests_token { #[tokio::test] async fn test_calculate_lamports_value_in_token_decimal_precision() { let _lock = ConfigMockBuilder::new().build_and_setup(); + let config = get_config().unwrap(); let mint = Pubkey::from_str(USDC_DEVNET_MINT).unwrap(); // Explanation (i.e. for case 1) @@ -895,6 +923,7 @@ mod tests_token { for (lamports, expected, description) in test_cases { let rpc_client = RpcMockBuilder::new().with_mint_account(6).build(); let result = TokenUtil::calculate_lamports_value_in_token( + &config, lamports, &mint, &PriceSource::Mock, diff --git a/crates/lib/src/validator/account_validator.rs b/crates/lib/src/validator/account_validator.rs index 5cd2fd31..9256a91e 100644 --- a/crates/lib/src/validator/account_validator.rs +++ b/crates/lib/src/validator/account_validator.rs @@ -132,7 +132,7 @@ impl AccountType { } pub async fn validate_account( - config: &crate::config::Config, + config: &Config, rpc_client: &RpcClient, account_pubkey: &Pubkey, expected_account_type: Option, From 09b346d856314a4e55fc659a5430b979d9208073 Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Tue, 18 Nov 2025 12:02:36 -0500 Subject: [PATCH 7/8] chore: strip redundant price_source --- crates/lib/src/cache.rs | 2 +- crates/lib/src/fee/fee.rs | 107 +++++++----------- crates/lib/src/fee/price.rs | 18 +-- .../method/estimate_transaction_fee.rs | 9 +- crates/lib/src/tests/cache_mock.rs | 2 +- crates/lib/src/token/token.rs | 81 +++++-------- .../src/transaction/versioned_transaction.rs | 5 +- crates/lib/src/validator/account_validator.rs | 2 +- .../src/validator/transaction_validator.rs | 3 +- 9 files changed, 85 insertions(+), 144 deletions(-) diff --git a/crates/lib/src/cache.rs b/crates/lib/src/cache.rs index 8bcfe102..3704d41e 100644 --- a/crates/lib/src/cache.rs +++ b/crates/lib/src/cache.rs @@ -5,7 +5,7 @@ use solana_client::nonblocking::rpc_client::RpcClient; use solana_sdk::{account::Account, pubkey::Pubkey}; use tokio::sync::OnceCell; -use crate::{error::KoraError, sanitize_error}; +use crate::{config::Config, error::KoraError, sanitize_error}; #[cfg(not(test))] use crate::state::get_config; diff --git a/crates/lib/src/fee/fee.rs b/crates/lib/src/fee/fee.rs index b1e41d7c..0d5d28c4 100644 --- a/crates/lib/src/fee/fee.rs +++ b/crates/lib/src/fee/fee.rs @@ -5,7 +5,6 @@ use crate::{ constant::{ESTIMATED_LAMPORTS_FOR_PAYMENT_INSTRUCTION, LAMPORTS_PER_SIGNATURE}, error::KoraError, fee::price::PriceModel, - oracle::PriceSource, token::{ spl_token_2022::Token2022Mint, token::{TokenType, TokenUtil}, @@ -219,11 +218,11 @@ impl FeeConfigUtil { } async fn estimate_transaction_fee( - config: &Config, - rpc_client: &RpcClient, transaction: &mut VersionedTransactionResolved, fee_payer: &Pubkey, is_payment_required: bool, + rpc_client: &RpcClient, + config: &Config, ) -> Result { // Get base transaction fee using resolved transaction to handle lookup tables let base_fee = @@ -239,14 +238,8 @@ impl FeeConfigUtil { } // Calculate fee payer outflow if fee payer is provided, to better estimate the potential fee - let fee_payer_outflow = FeeConfigUtil::calculate_fee_payer_outflow( - config, - fee_payer, - transaction, - rpc_client, - &config.validation.price_source, - ) - .await?; + let fee_payer_outflow = + FeeConfigUtil::calculate_fee_payer_outflow(fee_payer, transaction, rpc_client, config).await?; // Analyze payment instructions (checks if payment exists + calculates Token2022 fees) let (has_payment, transfer_fee_config_amount) = @@ -283,12 +276,11 @@ impl FeeConfigUtil { /// Main entry point for fee calculation with Kora's price model applied pub async fn estimate_kora_fee( - config: &Config, - rpc_client: &RpcClient, transaction: &mut VersionedTransactionResolved, fee_payer: &Pubkey, is_payment_required: bool, - price_source: PriceSource, + rpc_client: &RpcClient, + config: &Config, ) -> Result { match &config.validation.price.model { PriceModel::Free => Ok(TotalFeeCalculation::new_fixed(0)), @@ -296,16 +288,16 @@ impl FeeConfigUtil { let fixed_fee_lamports = config .validation .price - .get_required_lamports_with_fixed(config, rpc_client, price_source) + .get_required_lamports_with_fixed(rpc_client, config) .await?; if *strict { let fee_calculation = Self::estimate_transaction_fee( - config, - rpc_client, transaction, fee_payer, is_payment_required, + rpc_client, + config, ) .await?; @@ -324,11 +316,11 @@ impl FeeConfigUtil { PriceModel::Margin { .. } => { // Get the raw transaction let fee_calculation = Self::estimate_transaction_fee( - config, - rpc_client, transaction, fee_payer, is_payment_required, + rpc_client, + config, ) .await?; @@ -352,10 +344,10 @@ impl FeeConfigUtil { /// Calculate the fee in a specific token if provided pub async fn calculate_fee_in_token( - config: &Config, - rpc_client: &RpcClient, fee_in_lamports: u64, fee_token: Option<&str>, + rpc_client: &RpcClient, + config: &Config, ) -> Result, KoraError> { if let Some(fee_token) = fee_token { let token_mint = Pubkey::from_str(fee_token).map_err(|_| { @@ -370,14 +362,9 @@ impl FeeConfigUtil { ))); } - let fee_value_in_token = TokenUtil::calculate_lamports_value_in_token( - config, - fee_in_lamports, - &token_mint, - &validation_config.price_source, - rpc_client, - ) - .await?; + let fee_value_in_token = + TokenUtil::calculate_lamports_value_in_token(fee_in_lamports, &token_mint, rpc_client, config) + .await?; Ok(Some(fee_value_in_token)) } else { @@ -388,11 +375,10 @@ impl FeeConfigUtil { /// Calculate the total outflow (SOL + SPL token value) that could occur for a fee payer account in a transaction. /// This includes SOL transfers, account creation, SPL token transfers, and other operations that could drain the fee payer's balance. pub async fn calculate_fee_payer_outflow( - config: &Config, fee_payer_pubkey: &Pubkey, transaction: &mut VersionedTransactionResolved, rpc_client: &RpcClient, - price_source: &PriceSource, + config: &Config, ) -> Result { let mut total = 0u64; @@ -454,14 +440,9 @@ impl FeeConfigUtil { spl_instructions.get(&ParsedSPLInstructionType::SplTokenTransfer).unwrap_or(&empty_vec); if !spl_transfers.is_empty() { - let spl_outflow = TokenUtil::calculate_spl_transfers_value_in_lamports( - config, - spl_transfers, - fee_payer_pubkey, - price_source, - rpc_client, - ) - .await?; + let spl_outflow = + TokenUtil::calculate_spl_transfers_value_in_lamports(spl_transfers, fee_payer_pubkey, rpc_client, config) + .await?; total = total.checked_add(spl_outflow).ok_or_else(|| { log::error!("Fee payer outflow overflow: sol={}, spl={}", total, spl_outflow); @@ -646,11 +627,10 @@ mod tests { let config = get_config().unwrap(); let outflow = FeeConfigUtil::calculate_fee_payer_outflow( - &config, &fee_payer, &mut resolved_transaction, &mocked_rpc_client, - &crate::oracle::PriceSource::Mock, + &config, ) .await .unwrap(); @@ -665,11 +645,10 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); let config = get_config().unwrap(); let outflow = FeeConfigUtil::calculate_fee_payer_outflow( - &config, &fee_payer, &mut resolved_transaction, &mocked_rpc_client, - &crate::oracle::PriceSource::Mock, + &config, ) .await .unwrap(); @@ -684,11 +663,10 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); let config = get_config().unwrap(); let outflow = FeeConfigUtil::calculate_fee_payer_outflow( - &config, &fee_payer, &mut resolved_transaction, &mocked_rpc_client, - &crate::oracle::PriceSource::Mock, + &config, ) .await .unwrap(); @@ -717,11 +695,10 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); let config = get_config().unwrap(); let outflow = FeeConfigUtil::calculate_fee_payer_outflow( - &config, &fee_payer, &mut resolved_transaction, &mocked_rpc_client, - &crate::oracle::PriceSource::Mock, + &config, ) .await .unwrap(); @@ -743,11 +720,10 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); let config = get_config().unwrap(); let outflow = FeeConfigUtil::calculate_fee_payer_outflow( - &config, &fee_payer, &mut resolved_transaction, &mocked_rpc_client, - &crate::oracle::PriceSource::Mock, + &config, ) .await .unwrap(); @@ -773,11 +749,10 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); let config = get_config().unwrap(); let outflow = FeeConfigUtil::calculate_fee_payer_outflow( - &config, &fee_payer, &mut resolved_transaction, &mocked_rpc_client, - &crate::oracle::PriceSource::Mock, + &config, ) .await .unwrap(); @@ -793,11 +768,10 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); let config = get_config().unwrap(); let outflow = FeeConfigUtil::calculate_fee_payer_outflow( - &config, &fee_payer, &mut resolved_transaction, &mocked_rpc_client, - &crate::oracle::PriceSource::Mock, + &config, ) .await .unwrap(); @@ -827,11 +801,10 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); let config = get_config().unwrap(); let outflow = FeeConfigUtil::calculate_fee_payer_outflow( - &config, &fee_payer, &mut resolved_transaction, &mocked_rpc_client, - &crate::oracle::PriceSource::Mock, + &config, ) .await .unwrap(); @@ -858,11 +831,10 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); let config = get_config().unwrap(); let outflow = FeeConfigUtil::calculate_fee_payer_outflow( - &config, &fee_payer, &mut resolved_transaction, &mocked_rpc_client, - &crate::oracle::PriceSource::Mock, + &config, ) .await .unwrap(); @@ -881,11 +853,10 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); let config = get_config().unwrap(); let outflow = FeeConfigUtil::calculate_fee_payer_outflow( - &config, &fee_payer, &mut resolved_transaction, &mocked_rpc_client, - &crate::oracle::PriceSource::Mock, + &config, ) .await .unwrap(); @@ -915,11 +886,10 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); let config = get_config().unwrap(); let outflow = FeeConfigUtil::calculate_fee_payer_outflow( - &config, &fee_payer, &mut resolved_transaction, &mocked_rpc_client, - &crate::oracle::PriceSource::Mock, + &config, ) .await .unwrap(); @@ -947,11 +917,10 @@ mod tests { TransactionUtil::new_unsigned_versioned_transaction_resolved(message).unwrap(); let config = get_config().unwrap(); let outflow = FeeConfigUtil::calculate_fee_payer_outflow( - &config, &fee_payer, &mut resolved_transaction, &mocked_rpc_client, - &crate::oracle::PriceSource::Mock, + &config, ) .await .unwrap(); @@ -1105,11 +1074,11 @@ mod tests { let config = get_config().unwrap(); let result = FeeConfigUtil::estimate_transaction_fee( - &config, - &mocked_rpc_client, &mut resolved_transaction, &fee_payer.pubkey(), false, + &mocked_rpc_client, + &config, ) .await .unwrap(); @@ -1137,11 +1106,11 @@ mod tests { let config = get_config().unwrap(); let result = FeeConfigUtil::estimate_transaction_fee( - &config, - &mocked_rpc_client, &mut resolved_transaction, &kora_fee_payer.pubkey(), false, + &mocked_rpc_client, + &config, ) .await .unwrap(); @@ -1176,11 +1145,11 @@ mod tests { let config = get_config().unwrap(); let result = FeeConfigUtil::estimate_transaction_fee( - &config, - &mocked_rpc_client, &mut resolved_transaction, &fee_payer.pubkey(), true, // payment required + &mocked_rpc_client, + &config, ) .await .unwrap(); diff --git a/crates/lib/src/fee/price.rs b/crates/lib/src/fee/price.rs index 54fb9e00..08c907f4 100644 --- a/crates/lib/src/fee/price.rs +++ b/crates/lib/src/fee/price.rs @@ -1,4 +1,4 @@ -use crate::{config::Config, error::KoraError, oracle::PriceSource, token::token::TokenUtil}; +use crate::{config::Config, error::KoraError, token::token::TokenUtil}; use serde::{Deserialize, Serialize}; use solana_client::nonblocking::rpc_client::RpcClient; use solana_sdk::pubkey::Pubkey; @@ -28,21 +28,19 @@ pub struct PriceConfig { impl PriceConfig { pub async fn get_required_lamports_with_fixed( &self, - config: &Config, rpc_client: &RpcClient, - price_source: PriceSource, + config: &Config, ) -> Result { if let PriceModel::Fixed { amount, token, .. } = &self.model { return TokenUtil::calculate_token_value_in_lamports( - config, *amount, &Pubkey::from_str(token).map_err(|e| { log::error!("Invalid Pubkey for price {e}"); KoraError::ConfigError })?, - price_source, rpc_client, + config, ) .await; } @@ -128,10 +126,9 @@ mod tests { }; // Use Mock price source which returns 0.0001 SOL per USDC - let price_source = PriceSource::Mock; let result = - price_config.get_required_lamports_with_fixed(&config, &rpc_client, price_source).await.unwrap(); + price_config.get_required_lamports_with_fixed(&rpc_client, &config).await.unwrap(); // Expected calculation: // 1,000,000 base units / 10^6 = 1.0 USDC @@ -156,10 +153,9 @@ mod tests { }; // Mock oracle returns 1.0 SOL price for SOL mint - let price_source = PriceSource::Mock; let result = - price_config.get_required_lamports_with_fixed(&config, &rpc_client, price_source).await.unwrap(); + price_config.get_required_lamports_with_fixed(&rpc_client, &config).await.unwrap(); // Expected calculation: // 500,000,000 base units / 10^9 = 0.5 tokens @@ -183,10 +179,8 @@ mod tests { }, }; - let price_source = PriceSource::Mock; - let result = - price_config.get_required_lamports_with_fixed(&config, &rpc_client, price_source).await.unwrap(); + price_config.get_required_lamports_with_fixed(&rpc_client, &config).await.unwrap(); // Expected calculation: // 1,000 base units / 10^6 = 0.001 USDC diff --git a/crates/lib/src/rpc_server/method/estimate_transaction_fee.rs b/crates/lib/src/rpc_server/method/estimate_transaction_fee.rs index 674bad76..4c7271c3 100644 --- a/crates/lib/src/rpc_server/method/estimate_transaction_fee.rs +++ b/crates/lib/src/rpc_server/method/estimate_transaction_fee.rs @@ -64,12 +64,11 @@ pub async fn estimate_transaction_fee( .await?; let fee_calculation = FeeConfigUtil::estimate_kora_fee( - &config, - rpc_client, &mut resolved_transaction, &fee_payer, validation_config.is_payment_required(), - validation_config.price_source.clone(), + rpc_client, + &config, ) .await?; @@ -77,10 +76,10 @@ pub async fn estimate_transaction_fee( // Calculate fee in token if requested let fee_in_token = FeeConfigUtil::calculate_fee_in_token( - &config, - rpc_client, fee_in_lamports, request.fee_token.as_deref(), + rpc_client, + &config, ) .await?; diff --git a/crates/lib/src/tests/cache_mock.rs b/crates/lib/src/tests/cache_mock.rs index fff91145..2333a9ed 100644 --- a/crates/lib/src/tests/cache_mock.rs +++ b/crates/lib/src/tests/cache_mock.rs @@ -1,4 +1,4 @@ -use crate::error::KoraError; +use crate::{config::Config, error::KoraError}; use mockall::mock; use solana_client::nonblocking::rpc_client::RpcClient; use solana_sdk::{account::Account, pubkey::Pubkey}; diff --git a/crates/lib/src/token/token.rs b/crates/lib/src/token/token.rs index 8ed63341..7f9c7cb7 100644 --- a/crates/lib/src/token/token.rs +++ b/crates/lib/src/token/token.rs @@ -1,7 +1,7 @@ use crate::{ config::Config, error::KoraError, - oracle::{get_price_oracle, PriceSource, RetryingPriceOracle, TokenPrice}, + oracle::{get_price_oracle, RetryingPriceOracle, TokenPrice}, token::{ interface::TokenMint, spl_token::TokenProgram, @@ -90,15 +90,17 @@ impl TokenUtil { } pub async fn get_token_price_and_decimals( - config: &Config, mint: &Pubkey, - price_source: PriceSource, rpc_client: &RpcClient, + config: &Config, ) -> Result<(TokenPrice, u8), KoraError> { let decimals = Self::get_mint_decimals(config, rpc_client, mint).await?; - let oracle = - RetryingPriceOracle::new(3, Duration::from_secs(1), get_price_oracle(price_source)); + let oracle = RetryingPriceOracle::new( + 3, + Duration::from_secs(1), + get_price_oracle(config.validation.price_source.clone()), + ); // Get token price in SOL directly let token_price = oracle @@ -110,14 +112,13 @@ impl TokenUtil { } pub async fn calculate_token_value_in_lamports( - config: &Config, amount: u64, mint: &Pubkey, - price_source: PriceSource, rpc_client: &RpcClient, + config: &Config, ) -> Result { let (token_price, decimals) = - Self::get_token_price_and_decimals(config, mint, price_source, rpc_client).await?; + Self::get_token_price_and_decimals(mint, rpc_client, config).await?; // Convert amount to Decimal with proper scaling let amount_decimal = Decimal::from_u64(amount) @@ -143,15 +144,13 @@ impl TokenUtil { } pub async fn calculate_lamports_value_in_token( - config: &Config, lamports: u64, mint: &Pubkey, - price_source: &PriceSource, rpc_client: &RpcClient, + config: &Config, ) -> Result { let (token_price, decimals) = - Self::get_token_price_and_decimals(config, mint, price_source.clone(), rpc_client) - .await?; + Self::get_token_price_and_decimals(mint, rpc_client, config).await?; // Convert lamports to SOL using Decimal let lamports_decimal = Decimal::from_u64(lamports) @@ -178,11 +177,10 @@ impl TokenUtil { /// Calculate the total lamports value of SPL token transfers where the fee payer is involved /// This includes both outflow (fee payer as owner/source) and inflow (fee payer owns destination) pub async fn calculate_spl_transfers_value_in_lamports( - config: &Config, spl_transfers: &[ParsedSPLInstructionData], fee_payer: &Pubkey, - price_source: &PriceSource, rpc_client: &RpcClient, + config: &Config, ) -> Result { // Collect all unique mints that need price lookups let mut mint_to_transfers: HashMap< @@ -276,7 +274,7 @@ impl TokenUtil { let oracle = RetryingPriceOracle::new( 3, Duration::from_secs(1), - get_price_oracle(price_source.clone()), + get_price_oracle(config.validation.price_source.clone()), ); let prices = oracle.get_token_prices(&mint_addresses).await?; @@ -472,14 +470,9 @@ impl TokenUtil { continue; } - let lamport_value = TokenUtil::calculate_token_value_in_lamports( - config, - *amount, - &token_state.mint(), - config.validation.price_source.clone(), - rpc_client, - ) - .await?; + let lamport_value = + TokenUtil::calculate_token_value_in_lamports(*amount, &token_state.mint(), rpc_client, config) + .await?; total_lamport_value = total_lamport_value.checked_add(lamport_value).ok_or_else(|| { @@ -624,7 +617,7 @@ mod tests_token { let rpc_client = RpcMockBuilder::new().with_mint_account(9).build(); let (token_price, decimals) = - TokenUtil::get_token_price_and_decimals(&config, &mint, PriceSource::Mock, &rpc_client) + TokenUtil::get_token_price_and_decimals(&mint, &rpc_client, &config) .await .unwrap(); @@ -640,7 +633,7 @@ mod tests_token { let rpc_client = RpcMockBuilder::new().with_mint_account(6).build(); let (token_price, decimals) = - TokenUtil::get_token_price_and_decimals(&config, &mint, PriceSource::Mock, &rpc_client) + TokenUtil::get_token_price_and_decimals(&mint, &rpc_client, &config) .await .unwrap(); @@ -656,7 +649,7 @@ mod tests_token { let rpc_client = RpcMockBuilder::new().with_account_not_found().build(); let result = - TokenUtil::get_token_price_and_decimals(&config, &mint, PriceSource::Mock, &rpc_client) + TokenUtil::get_token_price_and_decimals(&mint, &rpc_client, &config) .await; assert!(result.is_err()); } @@ -670,11 +663,10 @@ mod tests_token { let amount = 1_000_000_000; // 1 SOL in lamports let result = TokenUtil::calculate_token_value_in_lamports( - &config, amount, &mint, - PriceSource::Mock, &rpc_client, + &config, ) .await .unwrap(); @@ -691,11 +683,10 @@ mod tests_token { let amount = 1_000_000; // 1 USDC (6 decimals) let result = TokenUtil::calculate_token_value_in_lamports( - &config, amount, &mint, - PriceSource::Mock, &rpc_client, + &config, ) .await .unwrap(); @@ -713,11 +704,10 @@ mod tests_token { let amount = 0; let result = TokenUtil::calculate_token_value_in_lamports( - &config, amount, &mint, - PriceSource::Mock, &rpc_client, + &config, ) .await .unwrap(); @@ -734,11 +724,10 @@ mod tests_token { let amount = 1; // 0.000001 USDC (smallest unit) let result = TokenUtil::calculate_token_value_in_lamports( - &config, amount, &mint, - PriceSource::Mock, &rpc_client, + &config, ) .await .unwrap(); @@ -756,11 +745,10 @@ mod tests_token { let lamports = 1_000_000_000; // 1 SOL let result = TokenUtil::calculate_lamports_value_in_token( - &config, lamports, &mint, - &PriceSource::Mock, &rpc_client, + &config, ) .await .unwrap(); @@ -777,11 +765,10 @@ mod tests_token { let lamports = 100_000; // 0.0001 SOL let result = TokenUtil::calculate_lamports_value_in_token( - &config, lamports, &mint, - &PriceSource::Mock, &rpc_client, + &config, ) .await .unwrap(); @@ -799,11 +786,10 @@ mod tests_token { let lamports = 0; let result = TokenUtil::calculate_lamports_value_in_token( - &config, lamports, &mint, - &PriceSource::Mock, &rpc_client, + &config, ) .await .unwrap(); @@ -823,11 +809,10 @@ mod tests_token { // Convert token amount to lamports let lamports_result = TokenUtil::calculate_token_value_in_lamports( - &config, original_amount, &mint, - PriceSource::Mock, &rpc_client, + &config, ) .await; @@ -840,11 +825,10 @@ mod tests_token { // Convert lamports back to token amount let recovered_amount_result = TokenUtil::calculate_lamports_value_in_token( - &config, lamports, &mint, - &PriceSource::Mock, &rpc_client, + &config, ) .await; @@ -861,11 +845,10 @@ mod tests_token { let rpc_client = RpcMockBuilder::new().with_account_not_found().build(); let result = TokenUtil::calculate_token_value_in_lamports( - &config, 1_000_000, &mint, - PriceSource::Mock, &rpc_client, + &config, ) .await; @@ -880,11 +863,10 @@ mod tests_token { let rpc_client = RpcMockBuilder::new().with_account_not_found().build(); let result = TokenUtil::calculate_lamports_value_in_token( - &config, 1_000_000, &mint, - &PriceSource::Mock, &rpc_client, + &config, ) .await; @@ -923,11 +905,10 @@ mod tests_token { for (lamports, expected, description) in test_cases { let rpc_client = RpcMockBuilder::new().with_mint_account(6).build(); let result = TokenUtil::calculate_lamports_value_in_token( - &config, lamports, &mint, - &PriceSource::Mock, &rpc_client, + &config, ) .await .unwrap(); diff --git a/crates/lib/src/transaction/versioned_transaction.rs b/crates/lib/src/transaction/versioned_transaction.rs index e5242dcd..bf994250 100644 --- a/crates/lib/src/transaction/versioned_transaction.rs +++ b/crates/lib/src/transaction/versioned_transaction.rs @@ -258,12 +258,11 @@ impl VersionedTransactionOps for VersionedTransactionResolved { // Calculate fee and validate payment if price model requires it let fee_calculation = FeeConfigUtil::estimate_kora_fee( - &config, - rpc_client, self, &fee_payer, config.validation.is_payment_required(), - config.validation.price_source.clone(), + rpc_client, + &config, ) .await?; diff --git a/crates/lib/src/validator/account_validator.rs b/crates/lib/src/validator/account_validator.rs index 9256a91e..ad1deb20 100644 --- a/crates/lib/src/validator/account_validator.rs +++ b/crates/lib/src/validator/account_validator.rs @@ -11,7 +11,7 @@ use spl_token_interface::{ ID as SPL_TOKEN_PROGRAM_ID, }; -use crate::{CacheUtil, KoraError}; +use crate::{config::Config, CacheUtil, KoraError}; #[derive(Debug, Clone, PartialEq)] pub enum AccountType { diff --git a/crates/lib/src/validator/transaction_validator.rs b/crates/lib/src/validator/transaction_validator.rs index eb2f108d..a8236b8a 100644 --- a/crates/lib/src/validator/transaction_validator.rs +++ b/crates/lib/src/validator/transaction_validator.rs @@ -334,11 +334,10 @@ impl TransactionValidator { rpc_client: &RpcClient, ) -> Result { FeeConfigUtil::calculate_fee_payer_outflow( - config, &self.fee_payer_pubkey, transaction_resolved, rpc_client, - &self._price_source, + config, ) .await } From d2bae89c01865e5fcabf2bd6af55aefa7c7d6124 Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Tue, 18 Nov 2025 12:02:47 -0500 Subject: [PATCH 8/8] chore: cargo fmt --- crates/lib/src/fee/fee.rs | 23 +++-- crates/lib/src/metrics/balance.rs | 3 +- crates/lib/src/token/token.rs | 148 ++++++++++-------------------- 3 files changed, 69 insertions(+), 105 deletions(-) diff --git a/crates/lib/src/fee/fee.rs b/crates/lib/src/fee/fee.rs index 0d5d28c4..6445c860 100644 --- a/crates/lib/src/fee/fee.rs +++ b/crates/lib/src/fee/fee.rs @@ -239,7 +239,8 @@ impl FeeConfigUtil { // Calculate fee payer outflow if fee payer is provided, to better estimate the potential fee let fee_payer_outflow = - FeeConfigUtil::calculate_fee_payer_outflow(fee_payer, transaction, rpc_client, config).await?; + FeeConfigUtil::calculate_fee_payer_outflow(fee_payer, transaction, rpc_client, config) + .await?; // Analyze payment instructions (checks if payment exists + calculates Token2022 fees) let (has_payment, transfer_fee_config_amount) = @@ -362,9 +363,13 @@ impl FeeConfigUtil { ))); } - let fee_value_in_token = - TokenUtil::calculate_lamports_value_in_token(fee_in_lamports, &token_mint, rpc_client, config) - .await?; + let fee_value_in_token = TokenUtil::calculate_lamports_value_in_token( + fee_in_lamports, + &token_mint, + rpc_client, + config, + ) + .await?; Ok(Some(fee_value_in_token)) } else { @@ -440,9 +445,13 @@ impl FeeConfigUtil { spl_instructions.get(&ParsedSPLInstructionType::SplTokenTransfer).unwrap_or(&empty_vec); if !spl_transfers.is_empty() { - let spl_outflow = - TokenUtil::calculate_spl_transfers_value_in_lamports(spl_transfers, fee_payer_pubkey, rpc_client, config) - .await?; + let spl_outflow = TokenUtil::calculate_spl_transfers_value_in_lamports( + spl_transfers, + fee_payer_pubkey, + rpc_client, + config, + ) + .await?; total = total.checked_add(spl_outflow).ok_or_else(|| { log::error!("Fee payer outflow overflow: sol={}, spl={}", total, spl_outflow); diff --git a/crates/lib/src/metrics/balance.rs b/crates/lib/src/metrics/balance.rs index 30dafa7f..2c66176a 100644 --- a/crates/lib/src/metrics/balance.rs +++ b/crates/lib/src/metrics/balance.rs @@ -134,7 +134,8 @@ impl BalanceTracker { interval.tick().await; // Track all signer balances, but don't let errors crash the loop - if let Err(e) = BalanceTracker::track_all_signer_balances(&config, &rpc_client).await + if let Err(e) = + BalanceTracker::track_all_signer_balances(&config, &rpc_client).await { log::warn!("Failed to track signer balances in background task: {e}"); } diff --git a/crates/lib/src/token/token.rs b/crates/lib/src/token/token.rs index 7f9c7cb7..18be991f 100644 --- a/crates/lib/src/token/token.rs +++ b/crates/lib/src/token/token.rs @@ -470,9 +470,13 @@ impl TokenUtil { continue; } - let lamport_value = - TokenUtil::calculate_token_value_in_lamports(*amount, &token_state.mint(), rpc_client, config) - .await?; + let lamport_value = TokenUtil::calculate_token_value_in_lamports( + *amount, + &token_state.mint(), + rpc_client, + config, + ) + .await?; total_lamport_value = total_lamport_value.checked_add(lamport_value).ok_or_else(|| { @@ -617,9 +621,7 @@ mod tests_token { let rpc_client = RpcMockBuilder::new().with_mint_account(9).build(); let (token_price, decimals) = - TokenUtil::get_token_price_and_decimals(&mint, &rpc_client, &config) - .await - .unwrap(); + TokenUtil::get_token_price_and_decimals(&mint, &rpc_client, &config).await.unwrap(); assert_eq!(decimals, 9); assert_eq!(token_price.price, Decimal::from(1)); @@ -633,9 +635,7 @@ mod tests_token { let rpc_client = RpcMockBuilder::new().with_mint_account(6).build(); let (token_price, decimals) = - TokenUtil::get_token_price_and_decimals(&mint, &rpc_client, &config) - .await - .unwrap(); + TokenUtil::get_token_price_and_decimals(&mint, &rpc_client, &config).await.unwrap(); assert_eq!(decimals, 6); assert_eq!(token_price.price, dec!(0.0001)); @@ -648,9 +648,7 @@ mod tests_token { let mint = Pubkey::from_str(WSOL_DEVNET_MINT).unwrap(); let rpc_client = RpcMockBuilder::new().with_account_not_found().build(); - let result = - TokenUtil::get_token_price_and_decimals(&mint, &rpc_client, &config) - .await; + let result = TokenUtil::get_token_price_and_decimals(&mint, &rpc_client, &config).await; assert!(result.is_err()); } @@ -662,14 +660,10 @@ mod tests_token { let rpc_client = RpcMockBuilder::new().with_mint_account(9).build(); let amount = 1_000_000_000; // 1 SOL in lamports - let result = TokenUtil::calculate_token_value_in_lamports( - amount, - &mint, - &rpc_client, - &config, - ) - .await - .unwrap(); + let result = + TokenUtil::calculate_token_value_in_lamports(amount, &mint, &rpc_client, &config) + .await + .unwrap(); assert_eq!(result, 1_000_000_000); // Should equal input since SOL price is 1.0 } @@ -682,14 +676,10 @@ mod tests_token { let rpc_client = RpcMockBuilder::new().with_mint_account(6).build(); let amount = 1_000_000; // 1 USDC (6 decimals) - let result = TokenUtil::calculate_token_value_in_lamports( - amount, - &mint, - &rpc_client, - &config, - ) - .await - .unwrap(); + let result = + TokenUtil::calculate_token_value_in_lamports(amount, &mint, &rpc_client, &config) + .await + .unwrap(); // 1 USDC * 0.0001 SOL/USDC = 0.0001 SOL = 100,000 lamports assert_eq!(result, 100_000); @@ -703,14 +693,10 @@ mod tests_token { let rpc_client = RpcMockBuilder::new().with_mint_account(9).build(); let amount = 0; - let result = TokenUtil::calculate_token_value_in_lamports( - amount, - &mint, - &rpc_client, - &config, - ) - .await - .unwrap(); + let result = + TokenUtil::calculate_token_value_in_lamports(amount, &mint, &rpc_client, &config) + .await + .unwrap(); assert_eq!(result, 0); } @@ -723,14 +709,10 @@ mod tests_token { let rpc_client = RpcMockBuilder::new().with_mint_account(6).build(); let amount = 1; // 0.000001 USDC (smallest unit) - let result = TokenUtil::calculate_token_value_in_lamports( - amount, - &mint, - &rpc_client, - &config, - ) - .await - .unwrap(); + let result = + TokenUtil::calculate_token_value_in_lamports(amount, &mint, &rpc_client, &config) + .await + .unwrap(); // 0.000001 USDC * 0.0001 SOL/USDC = very small amount, should floor to 0 assert_eq!(result, 0); @@ -744,14 +726,10 @@ mod tests_token { let rpc_client = RpcMockBuilder::new().with_mint_account(9).build(); let lamports = 1_000_000_000; // 1 SOL - let result = TokenUtil::calculate_lamports_value_in_token( - lamports, - &mint, - &rpc_client, - &config, - ) - .await - .unwrap(); + let result = + TokenUtil::calculate_lamports_value_in_token(lamports, &mint, &rpc_client, &config) + .await + .unwrap(); assert_eq!(result, 1_000_000_000); // Should equal input since SOL price is 1.0 } @@ -764,14 +742,10 @@ mod tests_token { let rpc_client = RpcMockBuilder::new().with_mint_account(6).build(); let lamports = 100_000; // 0.0001 SOL - let result = TokenUtil::calculate_lamports_value_in_token( - lamports, - &mint, - &rpc_client, - &config, - ) - .await - .unwrap(); + let result = + TokenUtil::calculate_lamports_value_in_token(lamports, &mint, &rpc_client, &config) + .await + .unwrap(); // 0.0001 SOL / 0.0001 SOL/USDC = 1 USDC = 1,000,000 base units assert_eq!(result, 1_000_000); @@ -785,14 +759,10 @@ mod tests_token { let rpc_client = RpcMockBuilder::new().with_mint_account(9).build(); let lamports = 0; - let result = TokenUtil::calculate_lamports_value_in_token( - lamports, - &mint, - &rpc_client, - &config, - ) - .await - .unwrap(); + let result = + TokenUtil::calculate_lamports_value_in_token(lamports, &mint, &rpc_client, &config) + .await + .unwrap(); assert_eq!(result, 0); } @@ -824,13 +794,9 @@ mod tests_token { let lamports = lamports_result.unwrap(); // Convert lamports back to token amount - let recovered_amount_result = TokenUtil::calculate_lamports_value_in_token( - lamports, - &mint, - &rpc_client, - &config, - ) - .await; + let recovered_amount_result = + TokenUtil::calculate_lamports_value_in_token(lamports, &mint, &rpc_client, &config) + .await; if let Ok(recovered_amount) = recovered_amount_result { assert_eq!(recovered_amount, original_amount); @@ -844,13 +810,9 @@ mod tests_token { let mint = Pubkey::new_unique(); let rpc_client = RpcMockBuilder::new().with_account_not_found().build(); - let result = TokenUtil::calculate_token_value_in_lamports( - 1_000_000, - &mint, - &rpc_client, - &config, - ) - .await; + let result = + TokenUtil::calculate_token_value_in_lamports(1_000_000, &mint, &rpc_client, &config) + .await; assert!(result.is_err()); } @@ -862,13 +824,9 @@ mod tests_token { let mint = Pubkey::new_unique(); let rpc_client = RpcMockBuilder::new().with_account_not_found().build(); - let result = TokenUtil::calculate_lamports_value_in_token( - 1_000_000, - &mint, - &rpc_client, - &config, - ) - .await; + let result = + TokenUtil::calculate_lamports_value_in_token(1_000_000, &mint, &rpc_client, &config) + .await; assert!(result.is_err()); } @@ -904,14 +862,10 @@ mod tests_token { for (lamports, expected, description) in test_cases { let rpc_client = RpcMockBuilder::new().with_mint_account(6).build(); - let result = TokenUtil::calculate_lamports_value_in_token( - lamports, - &mint, - &rpc_client, - &config, - ) - .await - .unwrap(); + let result = + TokenUtil::calculate_lamports_value_in_token(lamports, &mint, &rpc_client, &config) + .await + .unwrap(); assert_eq!( result, expected,