From 76a47e87b1bd4abf0accc7ad3e01cba0fbeede89 Mon Sep 17 00:00:00 2001 From: ysq Date: Mon, 8 Sep 2025 22:33:05 +0800 Subject: [PATCH] feat(raydium-cpmm): optimize instruction builder performance and add vault address caching - Replace dynamic PDA calculations with cached vault addresses for better performance - Add support for pre-calculated pool state and observation state addresses - Optimize instruction data creation using fixed-size arrays instead of Vec - Refactor vault account resolution with fallback to PDA calculation - Use fast ATA address calculation to reduce computation overhead - Extract common wSOL handling logic to reduce code duplication - Add RaydiumCpmmParams::from_trade() constructor for event-driven trading - Improve memory allocation efficiency with Vec::with_capacity() --- src/instruction/raydium_cpmm.rs | 249 +++++++++++++------------- src/instruction/utils/raydium_cpmm.rs | 47 ++++- src/trading/core/params.rs | 46 ++++- 3 files changed, 208 insertions(+), 134 deletions(-) diff --git a/src/instruction/raydium_cpmm.rs b/src/instruction/raydium_cpmm.rs index 37ae419..2583e11 100755 --- a/src/instruction/raydium_cpmm.rs +++ b/src/instruction/raydium_cpmm.rs @@ -1,13 +1,8 @@ -use anyhow::{anyhow, Result}; -use solana_sdk::{instruction::Instruction, signer::Signer}; -use solana_system_interface::instruction::transfer; -use spl_associated_token_account::instruction::create_associated_token_account_idempotent; -use spl_token::instruction::close_account; - use crate::{ + common::fast_fn::get_associated_token_address_with_program_id_fast, constants::trade::trade::DEFAULT_SLIPPAGE, instruction::utils::raydium_cpmm::{ - accounts, get_observation_state_pda, get_pool_pda, get_vault_pda, + accounts, get_observation_state_pda, get_pool_pda, get_vault_account, SWAP_BASE_IN_DISCRIMINATOR, }, trading::core::{ @@ -16,6 +11,12 @@ use crate::{ }, utils::calc::raydium_cpmm::compute_swap_amount, }; +use anyhow::{anyhow, Result}; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + signer::Signer, +}; /// Instruction builder for RaydiumCpmm protocol pub struct RaydiumCpmmInstructionBuilder; @@ -32,12 +33,16 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder { .downcast_ref::() .ok_or_else(|| anyhow!("Invalid protocol params for RaydiumCpmm"))?; - let pool_state = get_pool_pda( - &accounts::AMM_CONFIG, - &protocol_params.base_mint, - &protocol_params.quote_mint, - ) - .unwrap(); + let pool_state = if protocol_params.pool_state == Pubkey::default() { + get_pool_pda( + &accounts::AMM_CONFIG, + &protocol_params.base_mint, + &protocol_params.quote_mint, + ) + .unwrap() + } else { + protocol_params.pool_state + }; let is_base_in = protocol_params.base_mint == crate::constants::WSOL_TOKEN_ACCOUNT; let mint_token_program = if is_base_in { @@ -46,23 +51,32 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder { protocol_params.base_token_program }; - let wsol_token_account = spl_associated_token_account::get_associated_token_address( + let wsol_token_account = get_associated_token_address_with_program_id_fast( ¶ms.payer.pubkey(), &crate::constants::WSOL_TOKEN_ACCOUNT, + &crate::constants::TOKEN_PROGRAM, + ); + let mint_token_account = get_associated_token_address_with_program_id_fast( + ¶ms.payer.pubkey(), + ¶ms.mint, + &mint_token_program, ); - let mint_token_account = - spl_associated_token_account::get_associated_token_address_with_program_id( - ¶ms.payer.pubkey(), - ¶ms.mint, - &mint_token_program, - ); // Get pool token accounts - let wsol_vault_account = - get_vault_pda(&pool_state, &crate::constants::WSOL_TOKEN_ACCOUNT).unwrap(); - let mint_vault_account = get_vault_pda(&pool_state, ¶ms.mint).unwrap(); + let wsol_vault_account = get_vault_account( + &pool_state, + &crate::constants::WSOL_TOKEN_ACCOUNT, + protocol_params, + true, + ); + let mint_vault_account = + get_vault_account(&pool_state, ¶ms.mint, protocol_params, false); - let observation_state_account = get_observation_state_pda(&pool_state).unwrap(); + let observation_state_account = if protocol_params.observation_state == Pubkey::default() { + get_observation_state_pda(&pool_state).unwrap() + } else { + protocol_params.observation_state + }; let amount_in: u64 = params.sol_amount; let result = compute_swap_amount( @@ -74,35 +88,14 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder { ); let minimum_amount_out = result.min_amount_out; - let mut instructions = vec![]; + let mut instructions = Vec::with_capacity(6); if protocol_params.auto_handle_wsol { - // Handle wSOL - instructions.push( - // Create wSOL ATA account if it doesn't exist - create_associated_token_account_idempotent( - ¶ms.payer.pubkey(), - ¶ms.payer.pubkey(), - &crate::constants::WSOL_TOKEN_ACCOUNT, - &crate::constants::TOKEN_PROGRAM, - ), - ); - instructions.push( - // Transfer SOL to wSOL ATA account - transfer(¶ms.payer.pubkey(), &wsol_token_account, amount_in), - ); - - // Sync wSOL balance - instructions.push( - spl_token::instruction::sync_native( - &crate::constants::TOKEN_PROGRAM, - &wsol_token_account, - ) - .unwrap(), - ); + instructions + .extend(crate::trading::common::handle_wsol(¶ms.payer.pubkey(), amount_in)); } - instructions.push(create_associated_token_account_idempotent( + instructions.push(crate::common::fast_fn::create_associated_token_account_idempotent_fast( ¶ms.payer.pubkey(), ¶ms.payer.pubkey(), ¶ms.mint, @@ -110,41 +103,36 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder { )); // Create buy instruction - let accounts = vec![ - solana_sdk::instruction::AccountMeta::new(params.payer.pubkey(), true), // Payer (signer) - accounts::AUTHORITY_META, // Authority (readonly) - accounts::AMM_CONFIG_META, // Amm Config (readonly) - solana_sdk::instruction::AccountMeta::new(pool_state, false), // Pool State - solana_sdk::instruction::AccountMeta::new(wsol_token_account, false), // Input Token Account - solana_sdk::instruction::AccountMeta::new(mint_token_account, false), // Output Token Account - solana_sdk::instruction::AccountMeta::new(wsol_vault_account, false), // Input Vault Account - solana_sdk::instruction::AccountMeta::new(mint_vault_account, false), // Output Vault Account - crate::constants::TOKEN_PROGRAM_META, // Input Token Program (readonly) - solana_sdk::instruction::AccountMeta::new_readonly(mint_token_program, false), // Output Token Program (readonly) - crate::constants::WSOL_TOKEN_ACCOUNT_META, // Input token mint (readonly) - solana_sdk::instruction::AccountMeta::new_readonly(params.mint, false), // Output token mint (readonly) - solana_sdk::instruction::AccountMeta::new(observation_state_account, false), // Observation State Account + let accounts: [AccountMeta; 13] = [ + AccountMeta::new(params.payer.pubkey(), true), // Payer (signer) + accounts::AUTHORITY_META, // Authority (readonly) + accounts::AMM_CONFIG_META, // Amm Config (readonly) + AccountMeta::new(pool_state, false), // Pool State + AccountMeta::new(wsol_token_account, false), // Input Token Account + AccountMeta::new(mint_token_account, false), // Output Token Account + AccountMeta::new(wsol_vault_account, false), // Input Vault Account + AccountMeta::new(mint_vault_account, false), // Output Vault Account + crate::constants::TOKEN_PROGRAM_META, // Input Token Program (readonly) + AccountMeta::new_readonly(mint_token_program, false), // Output Token Program (readonly) + crate::constants::WSOL_TOKEN_ACCOUNT_META, // Input token mint (readonly) + AccountMeta::new_readonly(params.mint, false), // Output token mint (readonly) + AccountMeta::new(observation_state_account, false), // Observation State Account ]; // Create instruction data - let mut data = vec![]; - data.extend_from_slice(&SWAP_BASE_IN_DISCRIMINATOR); - data.extend_from_slice(&amount_in.to_le_bytes()); - data.extend_from_slice(&minimum_amount_out.to_le_bytes()); + let mut data = [0u8; 24]; + data[..8].copy_from_slice(&SWAP_BASE_IN_DISCRIMINATOR); + data[8..16].copy_from_slice(&amount_in.to_le_bytes()); + data[16..24].copy_from_slice(&minimum_amount_out.to_le_bytes()); - instructions.push(Instruction { program_id: accounts::RAYDIUM_CPMM, accounts, data }); + instructions.push(Instruction::new_with_bytes( + accounts::RAYDIUM_CPMM, + &data, + accounts.to_vec(), + )); if protocol_params.auto_handle_wsol { // Close wSOL ATA account, reclaim rent - instructions.push( - spl_token::instruction::close_account( - &crate::constants::TOKEN_PROGRAM, - &wsol_token_account, - ¶ms.payer.pubkey(), - ¶ms.payer.pubkey(), - &[], - ) - .unwrap(), - ); + instructions.push(crate::trading::common::close_wsol(¶ms.payer.pubkey())); } Ok(instructions) @@ -177,37 +165,50 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder { ) .min_amount_out; - let pool_state = get_pool_pda( - &accounts::AMM_CONFIG, - &protocol_params.base_mint, - &protocol_params.quote_mint, - ) - .unwrap(); + let pool_state = if protocol_params.pool_state == Pubkey::default() { + get_pool_pda( + &accounts::AMM_CONFIG, + &protocol_params.base_mint, + &protocol_params.quote_mint, + ) + .unwrap() + } else { + protocol_params.pool_state + }; - let wsol_token_account = spl_associated_token_account::get_associated_token_address( + let wsol_token_account = get_associated_token_address_with_program_id_fast( ¶ms.payer.pubkey(), &crate::constants::WSOL_TOKEN_ACCOUNT, + &crate::constants::TOKEN_PROGRAM, + ); + let mint_token_account = get_associated_token_address_with_program_id_fast( + ¶ms.payer.pubkey(), + ¶ms.mint, + &mint_token_program, ); - let mint_token_account = - spl_associated_token_account::get_associated_token_address_with_program_id( - ¶ms.payer.pubkey(), - ¶ms.mint, - &mint_token_program, - ); // Get pool token accounts - let wsol_vault_account = - get_vault_pda(&pool_state, &crate::constants::WSOL_TOKEN_ACCOUNT).unwrap(); - let mint_vault_account = get_vault_pda(&pool_state, ¶ms.mint).unwrap(); + let wsol_vault_account = get_vault_account( + &pool_state, + &crate::constants::WSOL_TOKEN_ACCOUNT, + protocol_params, + true, + ); + let mint_vault_account = + get_vault_account(&pool_state, ¶ms.mint, protocol_params, false); - let observation_state_account = get_observation_state_pda(&pool_state).unwrap(); + let observation_state_account = if protocol_params.observation_state == Pubkey::default() { + get_observation_state_pda(&pool_state).unwrap() + } else { + protocol_params.observation_state + }; - let mut instructions = vec![]; + let mut instructions = Vec::with_capacity(3); // Handle wSOL instructions.push( // Create wSOL ATA account if it doesn't exist - create_associated_token_account_idempotent( + crate::common::fast_fn::create_associated_token_account_idempotent_fast( ¶ms.payer.pubkey(), ¶ms.payer.pubkey(), &crate::constants::WSOL_TOKEN_ACCOUNT, @@ -216,40 +217,36 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder { ); // Create sell instruction - let accounts = vec![ - solana_sdk::instruction::AccountMeta::new(params.payer.pubkey(), true), // Payer (signer) - accounts::AUTHORITY_META, // Authority (readonly) - accounts::AMM_CONFIG_META, // Amm Config (readonly) - solana_sdk::instruction::AccountMeta::new(pool_state, false), // Pool State - solana_sdk::instruction::AccountMeta::new(mint_token_account, false), // Input Token Account - solana_sdk::instruction::AccountMeta::new(wsol_token_account, false), // Output Token Account - solana_sdk::instruction::AccountMeta::new(mint_vault_account, false), // Input Vault Account - solana_sdk::instruction::AccountMeta::new(wsol_vault_account, false), // Output Vault Account - solana_sdk::instruction::AccountMeta::new_readonly(mint_token_program, false), // Input Token Program (readonly) - crate::constants::TOKEN_PROGRAM_META, // Output Token Program (readonly) - solana_sdk::instruction::AccountMeta::new_readonly(params.mint, false), // Input token mint (readonly) - crate::constants::WSOL_TOKEN_ACCOUNT_META, // Output token mint (readonly) - solana_sdk::instruction::AccountMeta::new(observation_state_account, false), // Observation State Account + let accounts: [AccountMeta; 13] = [ + AccountMeta::new(params.payer.pubkey(), true), // Payer (signer) + accounts::AUTHORITY_META, // Authority (readonly) + accounts::AMM_CONFIG_META, // Amm Config (readonly) + AccountMeta::new(pool_state, false), // Pool State + AccountMeta::new(mint_token_account, false), // Input Token Account + AccountMeta::new(wsol_token_account, false), // Output Token Account + AccountMeta::new(mint_vault_account, false), // Input Vault Account + AccountMeta::new(wsol_vault_account, false), // Output Vault Account + AccountMeta::new_readonly(mint_token_program, false), // Input Token Program (readonly) + crate::constants::TOKEN_PROGRAM_META, // Output Token Program (readonly) + AccountMeta::new_readonly(params.mint, false), // Input token mint (readonly) + crate::constants::WSOL_TOKEN_ACCOUNT_META, // Output token mint (readonly) + AccountMeta::new(observation_state_account, false), // Observation State Account ]; // Create instruction data - let mut data = vec![]; - data.extend_from_slice(&SWAP_BASE_IN_DISCRIMINATOR); - data.extend_from_slice(¶ms.token_amount.unwrap_or(0).to_le_bytes()); - data.extend_from_slice(&minimum_amount_out.to_le_bytes()); + let mut data = [0u8; 24]; + data[..8].copy_from_slice(&SWAP_BASE_IN_DISCRIMINATOR); + data[8..16].copy_from_slice(¶ms.token_amount.unwrap_or(0).to_le_bytes()); + data[16..24].copy_from_slice(&minimum_amount_out.to_le_bytes()); - instructions.push(Instruction { program_id: accounts::RAYDIUM_CPMM, accounts, data }); + instructions.push(Instruction::new_with_bytes( + accounts::RAYDIUM_CPMM, + &data, + accounts.to_vec(), + )); if protocol_params.auto_handle_wsol { - instructions.push( - close_account( - &crate::constants::TOKEN_PROGRAM, - &wsol_token_account, - ¶ms.payer.pubkey(), - ¶ms.payer.pubkey(), - &[¶ms.payer.pubkey()], - ) - .unwrap(), - ); + // Close wSOL ATA account, reclaim rent + instructions.push(crate::trading::common::close_wsol(¶ms.payer.pubkey())); } Ok(instructions) diff --git a/src/instruction/utils/raydium_cpmm.rs b/src/instruction/utils/raydium_cpmm.rs index 1c8316e..5dbc8b7 100644 --- a/src/instruction/utils/raydium_cpmm.rs +++ b/src/instruction/utils/raydium_cpmm.rs @@ -1,4 +1,4 @@ -use crate::common::SolanaRpcClient; +use crate::{common::SolanaRpcClient, trading::core::params::RaydiumCpmmParams}; use anyhow::anyhow; use solana_sdk::pubkey::Pubkey; use solana_streamer_sdk::streaming::event_parser::protocols::raydium_cpmm::types::{ @@ -121,3 +121,48 @@ pub async fn calculate_price( let price = token1_adjusted / token0_adjusted; Ok(price) } + +/// 获取 vault 账户地址的辅助函数 +/// +/// # 参数 +/// - `pool_state`: 池子状态账户地址 +/// - `token_mint`: 代币 mint 地址 +/// - `protocol_params`: 协议参数 +/// - `is_wsol`: 是否为 wSOL 代币 +/// +/// # 返回值 +/// 返回对应的 vault 账户地址 +pub fn get_vault_account( + pool_state: &Pubkey, + token_mint: &Pubkey, + protocol_params: &RaydiumCpmmParams, + is_wsol: bool, +) -> Pubkey { + if is_wsol { + // 如果是 wSOL,检查是否为 base mint + if protocol_params.base_mint == crate::constants::WSOL_TOKEN_ACCOUNT + && protocol_params.base_vault != Pubkey::default() + { + protocol_params.base_vault + } else if protocol_params.quote_mint == crate::constants::WSOL_TOKEN_ACCOUNT + && protocol_params.quote_vault != Pubkey::default() + { + protocol_params.quote_vault + } else { + get_vault_pda(pool_state, &crate::constants::WSOL_TOKEN_ACCOUNT).unwrap() + } + } else { + // 对于其他代币,检查是否为 base 或 quote mint + if *token_mint == protocol_params.base_mint + && protocol_params.base_vault != Pubkey::default() + { + protocol_params.base_vault + } else if *token_mint == protocol_params.quote_mint + && protocol_params.quote_vault != Pubkey::default() + { + protocol_params.quote_vault + } else { + get_vault_pda(pool_state, token_mint).unwrap() + } + } +} diff --git a/src/trading/core/params.rs b/src/trading/core/params.rs index 584546c..45adbde 100755 --- a/src/trading/core/params.rs +++ b/src/trading/core/params.rs @@ -1,3 +1,9 @@ +use super::traits::ProtocolParams; +use crate::common::bonding_curve::BondingCurveAccount; +use crate::common::{PriorityFee, SolanaRpcClient}; +use crate::solana_streamer_sdk::streaming::event_parser::common::EventType; +use crate::solana_streamer_sdk::streaming::event_parser::protocols::bonk::BonkTradeEvent; +use crate::trading::common::get_multi_token_balances; use solana_hash::Hash; use solana_sdk::{pubkey::Pubkey, signature::Keypair}; use solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent; @@ -5,14 +11,8 @@ use solana_streamer_sdk::streaming::event_parser::protocols::pumpswap::{ PumpSwapBuyEvent, PumpSwapSellEvent, }; use solana_streamer_sdk::streaming::event_parser::protocols::raydium_amm_v4::types::AmmInfo; +use solana_streamer_sdk::streaming::event_parser::protocols::raydium_cpmm::RaydiumCpmmSwapEvent; use std::sync::Arc; - -use super::traits::ProtocolParams; -use crate::common::bonding_curve::BondingCurveAccount; -use crate::common::{PriorityFee, SolanaRpcClient}; -use crate::solana_streamer_sdk::streaming::event_parser::common::EventType; -use crate::solana_streamer_sdk::streaming::event_parser::protocols::bonk::BonkTradeEvent; -use crate::trading::common::get_multi_token_balances; /// Buy parameters #[derive(Clone)] pub struct BuyParams { @@ -405,6 +405,8 @@ impl ProtocolParams for BonkParams { /// Configuration parameters specific to Raydium CPMM trading protocol #[derive(Clone)] pub struct RaydiumCpmmParams { + /// Pool address + pub pool_state: Pubkey, /// Base token mint address pub base_mint: Pubkey, /// Quote token mint address @@ -413,15 +415,41 @@ pub struct RaydiumCpmmParams { pub base_reserve: u64, /// Quote token reserve amount in the pool pub quote_reserve: u64, + /// Base token vault address + pub base_vault: Pubkey, + /// Quote token vault address + pub quote_vault: Pubkey, /// Base token program ID (usually spl_token::ID or spl_token_2022::ID) pub base_token_program: Pubkey, /// Quote token program ID (usually spl_token::ID or spl_token_2022::ID) pub quote_token_program: Pubkey, + /// Observation state account + pub observation_state: Pubkey, /// Whether to automatically handle wSOL wrapping and unwrapping pub auto_handle_wsol: bool, } impl RaydiumCpmmParams { + pub fn from_trade( + trade_info: RaydiumCpmmSwapEvent, + base_reserve: u64, + quote_reserve: u64, + ) -> Self { + Self { + pool_state: trade_info.pool_state, + base_mint: trade_info.input_token_mint, + quote_mint: trade_info.output_token_mint, + base_reserve: base_reserve, + quote_reserve: quote_reserve, + base_vault: trade_info.input_vault, + quote_vault: trade_info.output_vault, + base_token_program: trade_info.input_token_program, + quote_token_program: trade_info.output_token_program, + observation_state: trade_info.observation_state, + auto_handle_wsol: true, + } + } + pub async fn from_pool_address_by_rpc( rpc: &SolanaRpcClient, pool_address: &Pubkey, @@ -437,12 +465,16 @@ impl RaydiumCpmmParams { ) .await?; Ok(Self { + pool_state: pool_address.clone(), base_mint: pool.token0_mint, quote_mint: pool.token1_mint, base_reserve: token0_balance, quote_reserve: token1_balance, + base_vault: pool.token0_vault, + quote_vault: pool.token1_vault, base_token_program: pool.token0_program, quote_token_program: pool.token1_program, + observation_state: pool.observation_key, auto_handle_wsol: true, }) }