diff --git a/crates/lib/src/admin/token_util.rs b/crates/lib/src/admin/token_util.rs index 33c72b65..329fbad7 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, @@ -91,10 +92,12 @@ 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 atas_to_create = find_missing_atas(rpc_client, address).await?; + 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 +248,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,16 +275,17 @@ 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| { - 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)?; @@ -328,7 +331,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 +370,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..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; @@ -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: &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: &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 e3f0f915..6445c860 100644 --- a/crates/lib/src/fee/fee.rs +++ b/crates/lib/src/fee/fee.rs @@ -1,10 +1,10 @@ use std::str::FromStr; use crate::{ + config::Config, constant::{ESTIMATED_LAMPORTS_FOR_PAYMENT_INSTRUCTION, LAMPORTS_PER_SIGNATURE}, error::KoraError, fee::price::PriceModel, - oracle::PriceSource, token::{ spl_token_2022::Token2022Mint, token::{TokenType, TokenUtil}, @@ -18,10 +18,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; @@ -106,6 +106,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, @@ -113,7 +114,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); @@ -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; @@ -162,6 +163,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, @@ -176,7 +178,8 @@ 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)?; @@ -215,10 +218,11 @@ impl FeeConfigUtil { } async fn estimate_transaction_fee( - 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 = @@ -234,18 +238,14 @@ 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, - 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) = - 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,29 +277,28 @@ impl FeeConfigUtil { /// Main entry point for fee calculation with Kora's price model applied pub async fn estimate_kora_fee( - rpc_client: &RpcClient, transaction: &mut VersionedTransactionResolved, fee_payer: &Pubkey, is_payment_required: bool, - price_source: PriceSource, + rpc_client: &RpcClient, + config: &Config, ) -> Result { - let config = get_config()?; - match &config.validation.price.model { PriceModel::Free => Ok(TotalFeeCalculation::new_fixed(0)), PriceModel::Fixed { strict, .. } => { let fixed_fee_lamports = config .validation .price - .get_required_lamports_with_fixed(rpc_client, price_source) + .get_required_lamports_with_fixed(rpc_client, config) .await?; if *strict { let fee_calculation = Self::estimate_transaction_fee( - rpc_client, transaction, fee_payer, is_payment_required, + rpc_client, + config, ) .await?; @@ -318,10 +317,11 @@ impl FeeConfigUtil { PriceModel::Margin { .. } => { // Get the raw transaction let fee_calculation = Self::estimate_transaction_fee( - rpc_client, transaction, fee_payer, is_payment_required, + rpc_client, + config, ) .await?; @@ -345,16 +345,16 @@ impl FeeConfigUtil { /// Calculate the fee in a specific token if provided pub async fn calculate_fee_in_token( - 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(|_| { 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) { @@ -366,8 +366,8 @@ impl FeeConfigUtil { let fee_value_in_token = TokenUtil::calculate_lamports_value_in_token( fee_in_lamports, &token_mint, - &validation_config.price_source, rpc_client, + config, ) .await?; @@ -383,7 +383,7 @@ impl FeeConfigUtil { fee_payer_pubkey: &Pubkey, transaction: &mut VersionedTransactionResolved, rpc_client: &RpcClient, - price_source: &PriceSource, + config: &Config, ) -> Result { let mut total = 0u64; @@ -448,8 +448,8 @@ impl FeeConfigUtil { let spl_outflow = TokenUtil::calculate_spl_transfers_value_in_lamports( spl_transfers, fee_payer_pubkey, - price_source, rpc_client, + config, ) .await?; @@ -520,7 +520,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}, @@ -634,11 +634,12 @@ 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( &fee_payer, &mut resolved_transaction, &mocked_rpc_client, - &crate::oracle::PriceSource::Mock, + &config, ) .await .unwrap(); @@ -651,11 +652,12 @@ 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( &fee_payer, &mut resolved_transaction, &mocked_rpc_client, - &crate::oracle::PriceSource::Mock, + &config, ) .await .unwrap(); @@ -668,11 +670,12 @@ 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( &fee_payer, &mut resolved_transaction, &mocked_rpc_client, - &crate::oracle::PriceSource::Mock, + &config, ) .await .unwrap(); @@ -699,11 +702,12 @@ 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( &fee_payer, &mut resolved_transaction, &mocked_rpc_client, - &crate::oracle::PriceSource::Mock, + &config, ) .await .unwrap(); @@ -723,11 +727,12 @@ 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( &fee_payer, &mut resolved_transaction, &mocked_rpc_client, - &crate::oracle::PriceSource::Mock, + &config, ) .await .unwrap(); @@ -751,11 +756,12 @@ 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( &fee_payer, &mut resolved_transaction, &mocked_rpc_client, - &crate::oracle::PriceSource::Mock, + &config, ) .await .unwrap(); @@ -769,11 +775,12 @@ 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( &fee_payer, &mut resolved_transaction, &mocked_rpc_client, - &crate::oracle::PriceSource::Mock, + &config, ) .await .unwrap(); @@ -801,11 +808,12 @@ 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( &fee_payer, &mut resolved_transaction, &mocked_rpc_client, - &crate::oracle::PriceSource::Mock, + &config, ) .await .unwrap(); @@ -830,11 +838,12 @@ 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( &fee_payer, &mut resolved_transaction, &mocked_rpc_client, - &crate::oracle::PriceSource::Mock, + &config, ) .await .unwrap(); @@ -851,11 +860,12 @@ 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( &fee_payer, &mut resolved_transaction, &mocked_rpc_client, - &crate::oracle::PriceSource::Mock, + &config, ) .await .unwrap(); @@ -883,11 +893,12 @@ 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( &fee_payer, &mut resolved_transaction, &mocked_rpc_client, - &crate::oracle::PriceSource::Mock, + &config, ) .await .unwrap(); @@ -913,11 +924,12 @@ 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( &fee_payer, &mut resolved_transaction, &mocked_rpc_client, - &crate::oracle::PriceSource::Mock, + &config, ) .await .unwrap(); @@ -936,7 +948,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(); @@ -957,7 +969,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 +1000,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, @@ -1011,7 +1027,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); @@ -1032,7 +1048,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,11 +1081,13 @@ 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( - &mocked_rpc_client, &mut resolved_transaction, &fee_payer.pubkey(), false, + &mocked_rpc_client, + &config, ) .await .unwrap(); @@ -1093,11 +1113,13 @@ 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( - &mocked_rpc_client, &mut resolved_transaction, &kora_fee_payer.pubkey(), false, + &mocked_rpc_client, + &config, ) .await .unwrap(); @@ -1130,11 +1152,13 @@ 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( - &mocked_rpc_client, &mut resolved_transaction, &fee_payer.pubkey(), true, // payment required + &mocked_rpc_client, + &config, ) .await .unwrap(); @@ -1158,7 +1182,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); @@ -1186,7 +1210,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/fee/price.rs b/crates/lib/src/fee/price.rs index 6981f107..08c907f4 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, token::token::TokenUtil}; use serde::{Deserialize, Serialize}; use solana_client::nonblocking::rpc_client::RpcClient; use solana_sdk::pubkey::Pubkey; @@ -29,7 +29,7 @@ impl PriceConfig { pub async fn get_required_lamports_with_fixed( &self, rpc_client: &RpcClient, - price_source: PriceSource, + config: &Config, ) -> Result { if let PriceModel::Fixed { amount, token, .. } = &self.model { return TokenUtil::calculate_token_value_in_lamports( @@ -39,8 +39,8 @@ impl PriceConfig { KoraError::ConfigError })?, - price_source, rpc_client, + config, ) .await; } @@ -78,7 +78,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 +113,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"; @@ -122,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(&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 @@ -137,6 +140,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 @@ -149,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(&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 @@ -164,6 +167,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"; @@ -175,10 +179,8 @@ 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(&rpc_client, &config).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 2d6db7ee..2c66176a 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(()); } @@ -64,7 +67,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)); } @@ -120,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)); @@ -128,7 +134,9 @@ 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}"); } } @@ -295,8 +303,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()); } @@ -317,10 +326,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()); } @@ -341,9 +351,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/rpc_server/method/estimate_transaction_fee.rs b/crates/lib/src/rpc_server/method/estimate_transaction_fee.rs index b2b046d0..4c7271c3 100644 --- a/crates/lib/src/rpc_server/method/estimate_transaction_fee.rs +++ b/crates/lib/src/rpc_server/method/estimate_transaction_fee.rs @@ -57,17 +57,18 @@ pub async fn estimate_transaction_fee( let mut resolved_transaction = VersionedTransactionResolved::from_transaction( &transaction, + &config, rpc_client, request.sig_verify, ) .await?; let fee_calculation = FeeConfigUtil::estimate_kora_fee( - rpc_client, &mut resolved_transaction, &fee_payer, validation_config.is_payment_required(), - validation_config.price_source.clone(), + rpc_client, + &config, ) .await?; @@ -75,9 +76,10 @@ pub async fn estimate_transaction_fee( // Calculate fee in token if requested let fee_in_token = FeeConfigUtil::calculate_fee_in_token( - rpc_client, fee_in_lamports, request.fee_token.as_deref(), + rpc_client, + &config, ) .await?; 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..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 @@ -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, }; @@ -35,20 +35,23 @@ 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 mut resolved_transaction = VersionedTransactionResolved::from_transaction( &transaction, + &config, rpc_client, request.sig_verify, ) .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..31e2f0ea 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, @@ -35,20 +35,23 @@ 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 mut resolved_transaction = VersionedTransactionResolved::from_transaction( &transaction, + &config, rpc_client, request.sig_verify, ) .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..caffc5b9 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}")))?; @@ -74,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, 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(); @@ -82,11 +84,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, @@ -126,7 +128,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..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}; @@ -7,6 +7,7 @@ mock! { pub CacheUtil { pub async fn init() -> Result<(), KoraError>; pub async fn get_account( + 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..18be991f 100644 --- a/crates/lib/src/token/token.rs +++ b/crates/lib/src/token/token.rs @@ -1,6 +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, @@ -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,10 +67,11 @@ impl TokenUtil { } pub async fn get_mint( + 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,22 +81,26 @@ impl TokenUtil { } pub async fn get_mint_decimals( + 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()) } pub async fn get_token_price_and_decimals( mint: &Pubkey, - price_source: PriceSource, rpc_client: &RpcClient, + config: &Config, ) -> Result<(TokenPrice, u8), KoraError> { - let decimals = Self::get_mint_decimals(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)); + 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 @@ -111,11 +114,11 @@ impl TokenUtil { pub async fn calculate_token_value_in_lamports( amount: u64, mint: &Pubkey, - price_source: PriceSource, rpc_client: &RpcClient, + config: &Config, ) -> Result { let (token_price, decimals) = - Self::get_token_price_and_decimals(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,11 +146,11 @@ impl TokenUtil { pub async fn calculate_lamports_value_in_token( lamports: u64, mint: &Pubkey, - price_source: &PriceSource, rpc_client: &RpcClient, + config: &Config, ) -> Result { let (token_price, decimals) = - Self::get_token_price_and_decimals(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) @@ -176,8 +179,8 @@ impl TokenUtil { pub async fn calculate_spl_transfers_value_in_lamports( 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< @@ -204,7 +207,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(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)?; @@ -269,14 +274,14 @@ 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?; 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 +334,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: &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 +358,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 +366,8 @@ 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 +378,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 +387,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 +398,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 +409,13 @@ impl TokenUtil { } pub async fn verify_token_payment( + 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 +440,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 +452,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, @@ -465,8 +473,8 @@ impl TokenUtil { let lamport_value = TokenUtil::calculate_token_value_in_lamports( *amount, &token_state.mint(), - config.validation.price_source.clone(), rpc_client, + config, ) .await?; @@ -575,7 +583,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 +596,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 +607,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); } @@ -605,13 +616,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) - .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)); @@ -620,13 +630,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) - .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)); @@ -635,29 +644,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; + let result = TokenUtil::get_token_price_and_decimals(&mint, &rpc_client, &config).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( - amount, - &mint, - PriceSource::Mock, - &rpc_client, - ) - .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 } @@ -665,18 +671,15 @@ 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( - amount, - &mint, - PriceSource::Mock, - &rpc_client, - ) - .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); @@ -685,18 +688,15 @@ 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( - amount, - &mint, - PriceSource::Mock, - &rpc_client, - ) - .await - .unwrap(); + let result = + TokenUtil::calculate_token_value_in_lamports(amount, &mint, &rpc_client, &config) + .await + .unwrap(); assert_eq!(result, 0); } @@ -704,18 +704,15 @@ 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( - amount, - &mint, - PriceSource::Mock, - &rpc_client, - ) - .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); @@ -724,18 +721,15 @@ 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( - lamports, - &mint, - &PriceSource::Mock, - &rpc_client, - ) - .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 } @@ -743,18 +737,15 @@ 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( - lamports, - &mint, - &PriceSource::Mock, - &rpc_client, - ) - .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); @@ -763,18 +754,15 @@ 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( - lamports, - &mint, - &PriceSource::Mock, - &rpc_client, - ) - .await - .unwrap(); + let result = + TokenUtil::calculate_lamports_value_in_token(lamports, &mint, &rpc_client, &config) + .await + .unwrap(); assert_eq!(result, 0); } @@ -782,6 +770,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(); @@ -792,8 +781,8 @@ mod tests_token { let lamports_result = TokenUtil::calculate_token_value_in_lamports( original_amount, &mint, - PriceSource::Mock, &rpc_client, + &config, ) .await; @@ -805,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, - &PriceSource::Mock, - &rpc_client, - ) - .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); @@ -821,16 +806,13 @@ 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( - 1_000_000, - &mint, - PriceSource::Mock, - &rpc_client, - ) - .await; + let result = + TokenUtil::calculate_token_value_in_lamports(1_000_000, &mint, &rpc_client, &config) + .await; assert!(result.is_err()); } @@ -838,16 +820,13 @@ 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( - 1_000_000, - &mint, - &PriceSource::Mock, - &rpc_client, - ) - .await; + let result = + TokenUtil::calculate_lamports_value_in_token(1_000_000, &mint, &rpc_client, &config) + .await; assert!(result.is_err()); } @@ -855,6 +834,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) @@ -882,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, - &PriceSource::Mock, - &rpc_client, - ) - .await - .unwrap(); + let result = + TokenUtil::calculate_lamports_value_in_token(lamports, &mint, &rpc_client, &config) + .await + .unwrap(); assert_eq!( result, expected, @@ -908,7 +884,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 +911,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 33b4e7a6..bf994250 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>; @@ -71,6 +73,7 @@ pub trait VersionedTransactionOps { impl VersionedTransactionResolved { pub async fn from_transaction( transaction: &VersionedTransaction, + config: &Config, rpc_client: &RpcClient, sig_verify: bool, ) -> Result { @@ -91,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, ) @@ -242,23 +246,23 @@ 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?; + 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( - rpc_client, self, &fee_payer, config.validation.is_payment_required(), - config.validation.price_source.clone(), + rpc_client, + &config, ) .await?; @@ -272,6 +276,7 @@ impl VersionedTransactionOps for VersionedTransactionResolved { // Validate token payment using the resolved transaction TransactionValidator::validate_token_payment( + config, self, required_lamports, rpc_client, @@ -280,7 +285,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 +322,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 @@ -338,6 +344,7 @@ 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> { @@ -346,9 +353,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(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) @@ -662,7 +671,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( @@ -693,10 +702,14 @@ mod tests { ); let rpc_client = RpcMockBuilder::new().with_custom_mocks(mocks).build(); - let resolved = - VersionedTransactionResolved::from_transaction(&transaction, &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()); @@ -718,7 +731,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(); @@ -795,10 +808,14 @@ mod tests { let rpc_client = RpcMockBuilder::new().with_custom_mocks(mocks).build(); - let resolved = - VersionedTransactionResolved::from_transaction(&transaction, &rpc_client, true) - .await - .unwrap(); + let resolved = VersionedTransactionResolved::from_transaction( + &transaction, + &config, + &rpc_client, + true, + ) + .await + .unwrap(); assert_eq!(resolved.transaction, transaction); @@ -812,7 +829,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( @@ -840,8 +857,13 @@ mod tests { ); let rpc_client = RpcMockBuilder::new().with_custom_mocks(mocks).build(); - let result = - VersionedTransactionResolved::from_transaction(&transaction, &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) @@ -1002,7 +1024,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(); @@ -1039,7 +1061,9 @@ 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); @@ -1049,17 +1073,25 @@ 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(), @@ -1067,7 +1099,8 @@ 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 { @@ -1078,7 +1111,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(); @@ -1112,7 +1145,8 @@ 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 { @@ -1124,7 +1158,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(); @@ -1157,7 +1191,8 @@ 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 { diff --git a/crates/lib/src/usage_limit/usage_tracker.rs b/crates/lib/src/usage_limit/usage_tracker.rs index 8bc711b9..faaedf4c 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,9 @@ 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 +327,9 @@ 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 +344,9 @@ 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..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 { @@ -132,11 +132,12 @@ impl AccountType { } pub async fn validate_account( + 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,9 @@ 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 +380,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 +392,10 @@ 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 +409,10 @@ 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..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(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(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(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 +520,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 5303ffad..a8236b8a 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 @@ -75,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 @@ -84,7 +84,7 @@ impl TransactionValidator { ))); } - let mint = TokenUtil::get_mint(rpc_client, mint).await?; + let mint = TokenUtil::get_mint(config, rpc_client, mint).await?; Ok(mint) } @@ -94,6 +94,7 @@ impl TransactionValidator { */ pub async fn validate_transaction( &self, + config: &Config, transaction_resolved: &mut VersionedTransactionResolved, rpc_client: &RpcClient, ) -> Result<(), KoraError> { @@ -112,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)?; @@ -281,10 +282,12 @@ 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!( @@ -326,26 +329,28 @@ impl TransactionValidator { async fn calculate_total_outflow( &self, + config: &Config, 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, + config, ) .await } 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, @@ -362,10 +367,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 +398,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 +462,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(); @@ -467,7 +472,10 @@ 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] @@ -477,7 +485,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(); @@ -487,7 +496,10 @@ 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 = @@ -495,7 +507,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(&mut transaction, &rpc_client).await.is_ok()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_ok()); } #[tokio::test] @@ -505,7 +520,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(); @@ -514,7 +530,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(&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 +546,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(&mut transaction, &rpc_client).await.is_err()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_err()); } #[tokio::test] @@ -544,7 +566,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(); @@ -558,7 +581,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(&mut transaction, &rpc_client).await.is_err()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_err()); } #[tokio::test] @@ -568,7 +594,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(); @@ -577,14 +604,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(&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] @@ -594,13 +627,17 @@ 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))); 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] @@ -619,7 +656,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, @@ -628,7 +666,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(&mut transaction, &rpc_client).await.is_err()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_err()); } #[tokio::test] @@ -643,14 +684,18 @@ 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); 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(); @@ -658,13 +703,17 @@ 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))); 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] @@ -681,13 +730,17 @@ 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))); 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 @@ -697,13 +750,17 @@ 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))); 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] @@ -721,7 +778,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(), @@ -736,7 +794,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(&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(); @@ -745,7 +806,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(), @@ -760,7 +822,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(&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(); @@ -777,7 +842,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(&mut transaction, &rpc_client).await.is_ok()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_ok()); } #[tokio::test] @@ -798,7 +866,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(), @@ -815,7 +884,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(&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() @@ -825,7 +897,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(), @@ -844,7 +917,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(&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(); @@ -865,7 +941,10 @@ 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] @@ -881,7 +960,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(); @@ -890,8 +970,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(&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) @@ -902,8 +984,10 @@ 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(); + 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 @@ -919,8 +1003,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(&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 @@ -939,8 +1025,10 @@ 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(); + 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" @@ -961,8 +1049,10 @@ 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(); + 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 @@ -974,8 +1064,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(&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" @@ -988,8 +1080,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(&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 @@ -1000,8 +1094,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(&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"); } @@ -1019,7 +1115,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(), @@ -1035,7 +1132,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(&mut transaction, &rpc_client).await.is_ok()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_ok()); // Test with allow_burn = false @@ -1044,7 +1144,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(), @@ -1061,7 +1162,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(&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( @@ -1080,7 +1184,10 @@ 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] @@ -1097,7 +1204,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(), @@ -1112,7 +1220,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(&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(); @@ -1120,7 +1231,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(), @@ -1136,7 +1248,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(&mut transaction, &rpc_client).await.is_err()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_err()); } #[tokio::test] @@ -1153,7 +1268,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(), @@ -1169,7 +1285,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(&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(); @@ -1177,7 +1296,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(), @@ -1194,7 +1314,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(&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(); @@ -1216,7 +1339,10 @@ 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] @@ -1233,7 +1359,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(), @@ -1249,7 +1376,10 @@ 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] @@ -1266,7 +1396,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(), @@ -1281,7 +1412,10 @@ 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] @@ -1298,7 +1432,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(), @@ -1314,7 +1449,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(&mut transaction, &rpc_client).await.is_ok()); + assert!(validator + .validate_transaction(&config, &mut transaction, &rpc_client) + .await + .is_ok()); // Test with allow_approve = false @@ -1323,7 +1461,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(), @@ -1340,7 +1479,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(&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(); @@ -1362,7 +1504,10 @@ 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] @@ -1380,12 +1525,16 @@ 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 = 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(); @@ -1393,12 +1542,16 @@ 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 = 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] @@ -1414,12 +1567,16 @@ 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 = 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(); @@ -1427,12 +1584,16 @@ 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 = 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] @@ -1449,14 +1610,18 @@ 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 = 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(); @@ -1464,13 +1629,17 @@ 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))); 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] @@ -1487,12 +1656,16 @@ 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 = 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(); @@ -1500,12 +1673,16 @@ 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 = 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] @@ -1523,12 +1700,16 @@ 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 = 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(); @@ -1536,12 +1717,16 @@ 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 = 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] @@ -1559,12 +1744,16 @@ 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 = 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(); @@ -1572,12 +1761,16 @@ 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 = 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] @@ -1594,7 +1787,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 +1813,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 +1832,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 +1851,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 +1875,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"); }