Fix trade execution correctness and release 4.0.11
This commit is contained in:
+64
-25
@@ -4,7 +4,7 @@ use solana_sdk::{
|
||||
instruction::{AccountMeta, Instruction},
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use std::{hash::Hash, sync::Arc};
|
||||
|
||||
use crate::common::{
|
||||
spl_associated_token_account::get_associated_token_address_with_program_id,
|
||||
@@ -21,6 +21,22 @@ const MAX_PDA_CACHE_SIZE: usize = 100_000;
|
||||
const MAX_ATA_CACHE_SIZE: usize = 100_000;
|
||||
const MAX_INSTRUCTION_CACHE_SIZE: usize = 100_000;
|
||||
|
||||
#[inline]
|
||||
fn prune_cache<K, V>(cache: &DashMap<K, V>, max_size: usize)
|
||||
where
|
||||
K: Eq + Hash + Clone,
|
||||
{
|
||||
let len = cache.len();
|
||||
if len <= max_size {
|
||||
return;
|
||||
}
|
||||
let remove_count = (len - max_size).max(max_size / 16).min(len);
|
||||
let keys: Vec<K> = cache.iter().take(remove_count).map(|entry| entry.key().clone()).collect();
|
||||
for key in keys {
|
||||
cache.remove(&key);
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------- Instruction Cache ---------------------
|
||||
|
||||
/// Instruction cache key for uniquely identifying instruction types and parameters
|
||||
@@ -66,7 +82,10 @@ where
|
||||
};
|
||||
|
||||
// Lock-free cache lookup with entry API
|
||||
INSTRUCTION_CACHE.entry(cache_key).or_insert_with(|| Arc::new(compute_fn())).clone()
|
||||
let instructions =
|
||||
INSTRUCTION_CACHE.entry(cache_key).or_insert_with(|| Arc::new(compute_fn())).clone();
|
||||
prune_cache(&INSTRUCTION_CACHE, MAX_INSTRUCTION_CACHE_SIZE);
|
||||
instructions
|
||||
}
|
||||
|
||||
// --------------------- Associated Token Account ---------------------
|
||||
@@ -106,8 +125,26 @@ pub fn _create_associated_token_account_idempotent_fast(
|
||||
use_seed,
|
||||
};
|
||||
|
||||
// Only use seed if the mint address is not wSOL or SOL
|
||||
// 🔧 修复:Token-2022 也支持 seed 方式(白名单方式更安全)
|
||||
let build_standard_create = || {
|
||||
let associated_token_address =
|
||||
get_associated_token_address_with_program_id_fast(owner, mint, token_program);
|
||||
vec![Instruction {
|
||||
program_id: crate::constants::ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
accounts: vec![
|
||||
AccountMeta::new(*payer, true), // Payer (signer, writable)
|
||||
AccountMeta::new(associated_token_address, false), // ATA address (writable, non-signer)
|
||||
AccountMeta::new_readonly(*owner, false), // Token account owner (readonly, non-signer)
|
||||
AccountMeta::new_readonly(*mint, false), // Token mint address (readonly, non-signer)
|
||||
crate::constants::SYSTEM_PROGRAM_META,
|
||||
AccountMeta::new_readonly(*token_program, false), // Token program (readonly, non-signer)
|
||||
],
|
||||
data: vec![1],
|
||||
}]
|
||||
};
|
||||
|
||||
// Only use seed if the mint address is not wSOL or SOL.
|
||||
// Seed accounts are deterministic token accounts, not ATAs; callers must ensure they are not
|
||||
// recreating an existing seeded account. Fall back to idempotent ATA if seed assembly fails.
|
||||
let arc_instructions = if use_seed
|
||||
&& !mint.eq(&crate::constants::WSOL_TOKEN_ACCOUNT)
|
||||
&& !mint.eq(&crate::constants::SOL_TOKEN_ACCOUNT)
|
||||
@@ -117,29 +154,18 @@ pub fn _create_associated_token_account_idempotent_fast(
|
||||
// Use cache to get instruction
|
||||
get_cached_instructions(cache_key, || {
|
||||
super::seed::create_associated_token_account_use_seed(payer, owner, mint, token_program)
|
||||
.unwrap()
|
||||
.unwrap_or_else(|err| {
|
||||
tracing::warn!(
|
||||
"seed token account setup failed for mint {}: {}; fallback to idempotent ATA",
|
||||
mint,
|
||||
err
|
||||
);
|
||||
build_standard_create()
|
||||
})
|
||||
})
|
||||
} else {
|
||||
// Use cache to get instruction
|
||||
get_cached_instructions(cache_key, || {
|
||||
// Get Associated Token Address using cache
|
||||
let associated_token_address =
|
||||
get_associated_token_address_with_program_id_fast(owner, mint, token_program);
|
||||
// Create Associated Token Account instruction
|
||||
// Reference implementation of spl_associated_token_account::instruction::create_associated_token_account
|
||||
vec![Instruction {
|
||||
program_id: crate::constants::ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
accounts: vec![
|
||||
AccountMeta::new(*payer, true), // Payer (signer, writable)
|
||||
AccountMeta::new(associated_token_address, false), // ATA address (writable, non-signer)
|
||||
AccountMeta::new_readonly(*owner, false), // Token account owner (readonly, non-signer)
|
||||
AccountMeta::new_readonly(*mint, false), // Token mint address (readonly, non-signer)
|
||||
crate::constants::SYSTEM_PROGRAM_META,
|
||||
AccountMeta::new_readonly(*token_program, false), // Token program (readonly, non-signer)
|
||||
],
|
||||
data: vec![1],
|
||||
}]
|
||||
})
|
||||
get_cached_instructions(cache_key, build_standard_create)
|
||||
};
|
||||
|
||||
// 🚀 性能优化:尝试零开销解包 Arc,如果引用计数=1则直接移出,否则克隆
|
||||
@@ -183,6 +209,7 @@ where
|
||||
|
||||
if let Some(pda) = pda_result {
|
||||
PDA_CACHE.insert(cache_key, pda);
|
||||
prune_cache(&PDA_CACHE, MAX_PDA_CACHE_SIZE);
|
||||
}
|
||||
|
||||
pda_result
|
||||
@@ -265,7 +292,18 @@ fn _get_associated_token_address_with_program_id_fast(
|
||||
token_mint_address,
|
||||
token_program_id,
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap_or_else(|err| {
|
||||
tracing::warn!(
|
||||
"seed token account address failed for mint {}: {}; fallback to ATA",
|
||||
token_mint_address,
|
||||
err
|
||||
);
|
||||
get_associated_token_address_with_program_id(
|
||||
wallet_address,
|
||||
token_mint_address,
|
||||
token_program_id,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
get_associated_token_address_with_program_id(
|
||||
wallet_address,
|
||||
@@ -276,6 +314,7 @@ fn _get_associated_token_address_with_program_id_fast(
|
||||
|
||||
// Store computation result in cache (lock-free)
|
||||
ATA_CACHE.insert(cache_key, ata);
|
||||
prune_cache(&ATA_CACHE, MAX_ATA_CACHE_SIZE);
|
||||
|
||||
ata
|
||||
}
|
||||
|
||||
+31
-34
@@ -1,5 +1,4 @@
|
||||
use crate::common::SolanaRpcClient;
|
||||
use anyhow::anyhow;
|
||||
use fnv::FnvHasher;
|
||||
use once_cell::sync::Lazy;
|
||||
use solana_sdk::{instruction::Instruction, pubkey::Pubkey};
|
||||
@@ -50,6 +49,26 @@ async fn fetch_rent_for_token_account(
|
||||
Ok(client.get_minimum_balance_for_rent_exemption(165).await?)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn derive_seed_from_mint(mint: &Pubkey) -> String {
|
||||
// Keep the legacy 8-hex seed stable. Changing this derivation changes the token account
|
||||
// address and can strand balances created by earlier buys.
|
||||
let mut hasher = FnvHasher::default();
|
||||
hasher.write(mint.as_ref());
|
||||
let hash = hasher.finish();
|
||||
let v = (hash & 0xFFFF_FFFF) as u32;
|
||||
let mut seed = String::with_capacity(8);
|
||||
for i in 0..8 {
|
||||
let nibble = ((v >> (28 - i * 4)) & 0xF) as u8;
|
||||
let byte = match nibble {
|
||||
0..=9 => b'0' + nibble,
|
||||
_ => b'a' + (nibble - 10),
|
||||
};
|
||||
seed.push(byte as char);
|
||||
}
|
||||
seed
|
||||
}
|
||||
|
||||
pub fn create_associated_token_account_use_seed(
|
||||
payer: &Pubkey,
|
||||
owner: &Pubkey,
|
||||
@@ -63,33 +82,23 @@ pub fn create_associated_token_account_use_seed(
|
||||
let rent = if is_2022_token {
|
||||
let v = SPL_TOKEN_2022_RENT.load(Ordering::Relaxed);
|
||||
if v == u64::MAX {
|
||||
return Err(anyhow!("Rent not initialized"));
|
||||
DEFAULT_TOKEN_ACCOUNT_RENT
|
||||
} else {
|
||||
v
|
||||
}
|
||||
v
|
||||
} else {
|
||||
let v = SPL_TOKEN_RENT.load(Ordering::Relaxed);
|
||||
if v == u64::MAX {
|
||||
return Err(anyhow!("Rent not initialized"));
|
||||
DEFAULT_TOKEN_ACCOUNT_RENT
|
||||
} else {
|
||||
v
|
||||
}
|
||||
v
|
||||
};
|
||||
|
||||
let mut buf = [0u8; 8];
|
||||
let mut hasher = FnvHasher::default();
|
||||
hasher.write(mint.as_ref());
|
||||
let hash = hasher.finish();
|
||||
let v = (hash & 0xFFFF_FFFF) as u32;
|
||||
for i in 0..8 {
|
||||
let nibble = ((v >> (28 - i * 4)) & 0xF) as u8;
|
||||
buf[i] = match nibble {
|
||||
0..=9 => b'0' + nibble,
|
||||
_ => b'a' + (nibble - 10),
|
||||
};
|
||||
}
|
||||
let seed = unsafe { std::str::from_utf8_unchecked(&buf) };
|
||||
let seed = derive_seed_from_mint(mint);
|
||||
// 🔧 修复:使用传入的 token_program 生成地址(支持 Token 和 Token-2022)
|
||||
// 买入和卖出只要都使用事件中的 token_program,地址自然一致
|
||||
let ata_like = Pubkey::create_with_seed(payer, seed, token_program)?;
|
||||
let ata_like = Pubkey::create_with_seed(payer, &seed, token_program)?;
|
||||
|
||||
let len = 165;
|
||||
// 🔧 修复:create_account_with_seed 的第3个参数必须是 payer(与第92行生成地址时使用的 base 一致)
|
||||
@@ -98,7 +107,7 @@ pub fn create_associated_token_account_use_seed(
|
||||
payer,
|
||||
&ata_like,
|
||||
payer,
|
||||
seed,
|
||||
&seed,
|
||||
rent,
|
||||
len,
|
||||
token_program,
|
||||
@@ -118,21 +127,9 @@ pub fn get_associated_token_address_with_program_id_use_seed(
|
||||
token_mint_address: &Pubkey,
|
||||
token_program_id: &Pubkey,
|
||||
) -> Result<Pubkey, anyhow::Error> {
|
||||
let mut buf = [0u8; 8];
|
||||
let mut hasher = FnvHasher::default();
|
||||
hasher.write(token_mint_address.as_ref());
|
||||
let hash = hasher.finish();
|
||||
let v = (hash & 0xFFFF_FFFF) as u32;
|
||||
for i in 0..8 {
|
||||
let nibble = ((v >> (28 - i * 4)) & 0xF) as u8;
|
||||
buf[i] = match nibble {
|
||||
0..=9 => b'0' + nibble,
|
||||
_ => b'a' + (nibble - 10),
|
||||
};
|
||||
}
|
||||
let seed = unsafe { std::str::from_utf8_unchecked(&buf) };
|
||||
let seed = derive_seed_from_mint(token_mint_address);
|
||||
// 🔧 修复:使用传入的 token_program_id 生成地址(支持 Token 和 Token-2022)
|
||||
// 买入和卖出只要都使用事件中的 token_program_id,地址自然一致
|
||||
let ata_like = Pubkey::create_with_seed(wallet_address, seed, token_program_id)?;
|
||||
let ata_like = Pubkey::create_with_seed(wallet_address, &seed, token_program_id)?;
|
||||
Ok(ata_like)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user