Skip to main content

sol_parser_sdk/logs/
pump_amm.rs

1//! PumpSwap (Pump AMM) 极限优化解析器 - 纳秒/微秒级性能
2//!
3//! 优化策略:
4//! - 零拷贝解析 (zero-copy)
5//! - 栈分配替代堆分配
6//! - unsafe 消除边界检查
7//! - 编译器自动向量化 (target-cpu=native)
8//! - 内联所有热路径
9//! - 编译时计算
10//! - 预计算查找表
11//! - L1 cache 优化 (1KB 栈缓冲区)
12
13use crate::core::events::*;
14use memchr::memmem;
15use once_cell::sync::Lazy;
16use solana_sdk::{pubkey::Pubkey, signature::Signature};
17
18#[cfg(feature = "perf-stats")]
19use std::sync::atomic::{AtomicUsize, Ordering};
20
21// ============================================================================
22// 性能计数器 (可选,用于性能分析)
23// ============================================================================
24
25#[cfg(feature = "perf-stats")]
26pub static PARSE_COUNT: AtomicUsize = AtomicUsize::new(0);
27#[cfg(feature = "perf-stats")]
28pub static PARSE_TIME_NS: AtomicUsize = AtomicUsize::new(0);
29
30// ============================================================================
31// 编译时常量和查找表
32// ============================================================================
33
34/// PumpSwap discriminator constants (compile-time computed)
35pub mod discriminators {
36    // Use u64 direct comparison to avoid array comparison
37    // Event discriminators from pump_amm.json
38    pub const BUY: u64 = u64::from_le_bytes([103, 244, 82, 31, 44, 245, 119, 119]); // BuyEvent
39    pub const SELL: u64 = u64::from_le_bytes([62, 47, 55, 10, 165, 3, 220, 42]); // SellEvent
40    pub const CREATE_POOL: u64 = u64::from_le_bytes([177, 49, 12, 210, 160, 118, 167, 116]); // CreatePoolEvent
41    pub const ADD_LIQUIDITY: u64 = u64::from_le_bytes([120, 248, 61, 83, 31, 142, 107, 144]); // DepositEvent
42    pub const REMOVE_LIQUIDITY: u64 = u64::from_le_bytes([22, 9, 133, 26, 160, 44, 71, 192]);
43    // WithdrawEvent
44}
45
46/// Base64 查找器预计算 (用于快速定位)
47static BASE64_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"Program data: "));
48
49/// 跳过 ASCII 空白后拷贝 base64 前缀(Explorer / 部分日志会在 base64 中插空格)
50#[inline(always)]
51fn copy_b64_skip_ws_prefix(src: &[u8], out: &mut [u8], max_copy: usize) -> Option<usize> {
52    let cap = max_copy.min(out.len());
53    let mut j = 0usize;
54    for &b in src {
55        if b.is_ascii_whitespace() {
56            continue;
57        }
58        if j >= cap {
59            break;
60        }
61        out[j] = b;
62        j += 1;
63    }
64    if j < 12 {
65        return None;
66    }
67    Some(j)
68}
69
70// ============================================================================
71// 零拷贝解析核心 - 使用栈分配
72// ============================================================================
73
74/// 零拷贝提取 program data (栈分配,无堆分配)
75///
76/// 优化: 使用固定大小栈缓冲区,避免 Vec 分配
77/// 缓冲区大小增加到 2KB 以防止 base64-simd 缓冲区溢出panic
78#[inline(always)]
79fn extract_program_data_zero_copy<'a>(log: &'a str, buf: &'a mut [u8; 2048]) -> Option<&'a [u8]> {
80    let log_bytes = log.as_bytes();
81    let pos = BASE64_FINDER.find(log_bytes)?;
82
83    let data_part = &log[pos + 14..];
84    let trimmed = data_part.trim();
85    let body = trimmed.as_bytes();
86
87    if body.len() > 2700 {
88        return None;
89    }
90
91    use base64_simd::AsOut;
92    const COMPACT_CAP: usize = 2730;
93    let decoded_slice = if body.iter().any(|&b| b.is_ascii_whitespace()) {
94        let mut compact = [0u8; COMPACT_CAP];
95        let n = copy_b64_skip_ws_prefix(body, &mut compact, COMPACT_CAP)?;
96        base64_simd::STANDARD.decode(&compact[..n], buf.as_mut().as_out()).ok()?
97    } else {
98        base64_simd::STANDARD.decode(body, buf.as_mut().as_out()).ok()?
99    };
100
101    Some(decoded_slice)
102}
103
104/// 快速 discriminator 提取 (SIMD 优化)
105#[inline(always)]
106fn extract_discriminator_simd(log: &str) -> Option<u64> {
107    let log_bytes = log.as_bytes();
108    let pos = BASE64_FINDER.find(log_bytes)?;
109
110    let data_part = &log[pos + 14..];
111    let body = data_part.trim().as_bytes();
112
113    use base64_simd::AsOut;
114    let mut compact = [0u8; 24];
115    let n = copy_b64_skip_ws_prefix(body, &mut compact, 16)?;
116    let mut dec = [0u8; 12];
117    base64_simd::STANDARD.decode(&compact[..n], dec.as_mut().as_out()).ok()?;
118
119    unsafe {
120        let ptr = dec.as_ptr() as *const u64;
121        Some(ptr.read_unaligned())
122    }
123}
124
125// ============================================================================
126// Unsafe 读取函数 - 消除边界检查
127// ============================================================================
128
129/// 读取 u64 (unsafe, 无边界检查)
130#[inline(always)]
131unsafe fn read_u64_unchecked(data: &[u8], offset: usize) -> u64 {
132    let ptr = data.as_ptr().add(offset) as *const u64;
133    u64::from_le(ptr.read_unaligned())
134}
135
136/// 读取 i64 (unsafe, 无边界检查)
137#[inline(always)]
138unsafe fn read_i64_unchecked(data: &[u8], offset: usize) -> i64 {
139    let ptr = data.as_ptr().add(offset) as *const i64;
140    i64::from_le(ptr.read_unaligned())
141}
142
143#[derive(Default)]
144struct PumpSwapTradeTail {
145    cashback_fee_basis_points: u64,
146    cashback: u64,
147    buyback_fee_basis_points: u64,
148    buyback_fee: u64,
149    virtual_quote_reserves: i128,
150    can_boost: bool,
151    base_supply: u64,
152    holder_rewards_bps: u64,
153    holder_rewards: u64,
154}
155
156#[inline(always)]
157fn read_u64_le_at(data: &[u8], offset: usize) -> Option<u64> {
158    let bytes = data.get(offset..offset.checked_add(8)?)?;
159    Some(u64::from_le_bytes(bytes.try_into().ok()?))
160}
161
162#[inline(always)]
163fn read_i128_le_at(data: &[u8], offset: usize) -> Option<i128> {
164    let bytes = data.get(offset..offset.checked_add(16)?)?;
165    Some(i128::from_le_bytes(bytes.try_into().ok()?))
166}
167
168#[inline(always)]
169fn read_borsh_string(data: &[u8], offset: usize) -> Option<(String, usize)> {
170    let content_offset = offset.checked_add(4)?;
171    let len = u32::from_le_bytes(data.get(offset..content_offset)?.try_into().ok()?) as usize;
172    let end = content_offset.checked_add(len)?;
173    let value = std::str::from_utf8(data.get(content_offset..end)?).ok()?.to_owned();
174    Some((value, end))
175}
176
177/// Decode the append-only PumpSwap trade-event tail across released layouts.
178#[inline(always)]
179fn parse_trade_tail(data: &[u8]) -> Option<PumpSwapTradeTail> {
180    const CASHBACK_LEN: usize = 16;
181    const BUYBACK_LEN: usize = 32;
182    const BOOST_LEN: usize = 57;
183    const HOLDER_REWARDS_LEN: usize = 73;
184
185    if data.is_empty() {
186        return Some(PumpSwapTradeTail::default());
187    }
188    if data.len() < CASHBACK_LEN {
189        return None;
190    }
191
192    let mut tail = PumpSwapTradeTail {
193        cashback_fee_basis_points: read_u64_le_at(data, 0)?,
194        cashback: read_u64_le_at(data, 8)?,
195        ..Default::default()
196    };
197    if data.len() == CASHBACK_LEN {
198        return Some(tail);
199    }
200    if data.len() < BUYBACK_LEN {
201        return None;
202    }
203
204    tail.buyback_fee_basis_points = read_u64_le_at(data, 16)?;
205    tail.buyback_fee = read_u64_le_at(data, 24)?;
206    if data.len() == BUYBACK_LEN {
207        return Some(tail);
208    }
209    if data.len() < BOOST_LEN {
210        return None;
211    }
212
213    tail.virtual_quote_reserves = read_i128_le_at(data, 32)?;
214    tail.can_boost = match data[48] {
215        0 => false,
216        1 => true,
217        _ => return None,
218    };
219    tail.base_supply = read_u64_le_at(data, 49)?;
220    if data.len() != BOOST_LEN && data.len() < HOLDER_REWARDS_LEN {
221        return None;
222    }
223    if data.len() >= HOLDER_REWARDS_LEN {
224        tail.holder_rewards_bps = read_u64_le_at(data, 57)?;
225        tail.holder_rewards = read_u64_le_at(data, 65)?;
226    }
227    Some(tail)
228}
229
230/// Read u16 (unsafe, no bounds check)
231#[inline(always)]
232unsafe fn read_u16_unchecked(data: &[u8], offset: usize) -> u16 {
233    let ptr = data.as_ptr().add(offset) as *const u16;
234    u16::from_le(ptr.read_unaligned())
235}
236
237/// Read u8 (unsafe, no bounds check)
238#[inline(always)]
239unsafe fn read_u8_unchecked(data: &[u8], offset: usize) -> u8 {
240    *data.get_unchecked(offset)
241}
242
243/// 读取 bool (unsafe, 无边界检查)
244#[inline(always)]
245unsafe fn read_bool_unchecked(data: &[u8], offset: usize) -> bool {
246    *data.get_unchecked(offset) == 1
247}
248
249/// 读取 Pubkey (unsafe, 无边界检查)
250///
251/// 优化: 添加内存预取,假设连续读取多个 Pubkey
252#[inline(always)]
253unsafe fn read_pubkey_unchecked(data: &[u8], offset: usize) -> Pubkey {
254    // 预取下一个可能的 Pubkey 位置 (假设连续读取)
255    // 使用 T0 提示 (最高优先级) 将数据预取到 L1 cache
256    #[cfg(target_arch = "x86_64")]
257    {
258        use std::arch::x86_64::_mm_prefetch;
259        use std::arch::x86_64::_MM_HINT_T0;
260        if offset + 64 < data.len() {
261            _mm_prefetch((data.as_ptr().add(offset + 32)) as *const i8, _MM_HINT_T0);
262        }
263    }
264
265    let ptr = data.as_ptr().add(offset);
266    let mut bytes = [0u8; 32];
267    std::ptr::copy_nonoverlapping(ptr, bytes.as_mut_ptr(), 32);
268    Pubkey::new_from_array(bytes)
269}
270
271// ============================================================================
272// Optimized event parsing functions
273// ============================================================================
274
275/// Main parse function (optimized)
276///
277/// Performance target: <100ns
278#[inline(always)]
279pub fn parse_log(
280    log: &str,
281    signature: Signature,
282    slot: u64,
283    tx_index: u64,
284    block_time_us: Option<i64>,
285    grpc_recv_us: i64,
286) -> Option<DexEvent> {
287    #[cfg(feature = "perf-stats")]
288    let start = std::time::Instant::now();
289
290    // Stack-allocated buffer (增加到 2KB 以防止 base64-simd 缓冲区溢出)
291    let mut buf = [0u8; 2048];
292    let program_data = extract_program_data_zero_copy(log, &mut buf)?;
293
294    if program_data.len() < 8 {
295        return None;
296    }
297
298    // Read discriminator using unsafe (SIMD optimized)
299    let discriminator = unsafe { read_u64_unchecked(program_data, 0) };
300    let data = &program_data[8..];
301
302    let result = match discriminator {
303        discriminators::BUY => parse_buy_from_data(
304            data,
305            EventMetadata {
306                signature,
307                slot,
308                tx_index,
309                block_time_us: block_time_us.unwrap_or(0),
310                grpc_recv_us,
311                recent_blockhash: None,
312            },
313        ),
314        discriminators::SELL => parse_sell_from_data(
315            data,
316            EventMetadata {
317                signature,
318                slot,
319                tx_index,
320                block_time_us: block_time_us.unwrap_or(0),
321                grpc_recv_us,
322                recent_blockhash: None,
323            },
324        ),
325        discriminators::CREATE_POOL => parse_create_pool_event_optimized(
326            data,
327            signature,
328            slot,
329            tx_index,
330            block_time_us,
331            grpc_recv_us,
332        ),
333        discriminators::ADD_LIQUIDITY => parse_add_liquidity_event_optimized(
334            data,
335            signature,
336            slot,
337            tx_index,
338            block_time_us,
339            grpc_recv_us,
340        ),
341        discriminators::REMOVE_LIQUIDITY => parse_remove_liquidity_event_optimized(
342            data,
343            signature,
344            slot,
345            tx_index,
346            block_time_us,
347            grpc_recv_us,
348        ),
349        _ => None,
350    };
351
352    #[cfg(feature = "perf-stats")]
353    {
354        PARSE_COUNT.fetch_add(1, Ordering::Relaxed);
355        PARSE_TIME_NS.fetch_add(start.elapsed().as_nanos() as usize, Ordering::Relaxed);
356    }
357
358    result
359}
360
361/// 解析池创建事件 (极限优化)
362#[inline(always)]
363fn parse_create_pool_event_optimized(
364    data: &[u8],
365    signature: Signature,
366    slot: u64,
367    tx_index: u64,
368    block_time_us: Option<i64>,
369    grpc_recv_us: i64,
370) -> Option<DexEvent> {
371    // 一次性边界检查 (含 IDL 最后一列 is_mayhem_mode: bool)
372    const CREATE_POOL_EVENT_LEN: usize = 326;
373    const CREATOR_FEE_EVENT_LEN: usize = 335;
374    const REQUIRED_LEN: usize = CREATE_POOL_EVENT_LEN;
375    if data.len() < REQUIRED_LEN
376        || (data.len() != CREATE_POOL_EVENT_LEN && data.len() < CREATOR_FEE_EVENT_LEN)
377    {
378        return None;
379    }
380
381    unsafe {
382        let timestamp = read_i64_unchecked(data, 0);
383        let index = read_u16_unchecked(data, 8);
384
385        let creator = read_pubkey_unchecked(data, 10);
386        let base_mint = read_pubkey_unchecked(data, 42);
387        let quote_mint = read_pubkey_unchecked(data, 74);
388
389        let base_mint_decimals = read_u8_unchecked(data, 106);
390        let quote_mint_decimals = read_u8_unchecked(data, 107);
391
392        let base_amount_in = read_u64_unchecked(data, 108);
393        let quote_amount_in = read_u64_unchecked(data, 116);
394        let pool_base_amount = read_u64_unchecked(data, 124);
395        let pool_quote_amount = read_u64_unchecked(data, 132);
396        let minimum_liquidity = read_u64_unchecked(data, 140);
397        let initial_liquidity = read_u64_unchecked(data, 148);
398        let lp_token_amount_out = read_u64_unchecked(data, 156);
399
400        let pool_bump = read_u8_unchecked(data, 164);
401
402        let pool = read_pubkey_unchecked(data, 165);
403        let lp_mint = read_pubkey_unchecked(data, 197);
404        let user_base_token_account = read_pubkey_unchecked(data, 229);
405        let user_quote_token_account = read_pubkey_unchecked(data, 261);
406        let coin_creator = read_pubkey_unchecked(data, 293);
407        let is_mayhem_mode = read_bool_unchecked(data, 325);
408        let creator_fee_bps = if data.len() >= 334 { read_u64_unchecked(data, 326) } else { 0 };
409        let can_edit_creator_fee = data.len() > 334 && read_bool_unchecked(data, 334);
410        let is_holder_reward = data.len() > 335 && read_bool_unchecked(data, 335);
411
412        let metadata = EventMetadata {
413            signature,
414            slot,
415            tx_index,
416            block_time_us: block_time_us.unwrap_or(0),
417            grpc_recv_us,
418            recent_blockhash: None,
419        };
420
421        Some(DexEvent::PumpSwapCreatePool(PumpSwapCreatePoolEvent {
422            metadata,
423            timestamp,
424            index,
425            creator,
426            base_mint,
427            quote_mint,
428            base_mint_decimals,
429            quote_mint_decimals,
430            base_amount_in,
431            quote_amount_in,
432            pool_base_amount,
433            pool_quote_amount,
434            minimum_liquidity,
435            initial_liquidity,
436            lp_token_amount_out,
437            pool_bump,
438            pool,
439            lp_mint,
440            user_base_token_account,
441            user_quote_token_account,
442            coin_creator,
443            is_mayhem_mode,
444            is_cashback_coin: false,
445            creator_fee_bps,
446            can_edit_creator_fee,
447            is_holder_reward,
448        }))
449    }
450}
451
452/// 解析添加流动性事件 (极限优化)
453#[inline(always)]
454fn parse_add_liquidity_event_optimized(
455    data: &[u8],
456    signature: Signature,
457    slot: u64,
458    tx_index: u64,
459    block_time_us: Option<i64>,
460    grpc_recv_us: i64,
461) -> Option<DexEvent> {
462    const REQUIRED_LEN: usize = 10 * 8 + 5 * 32;
463    if data.len() < REQUIRED_LEN {
464        return None;
465    }
466
467    unsafe {
468        let timestamp = read_i64_unchecked(data, 0);
469        let lp_token_amount_out = read_u64_unchecked(data, 8);
470        let max_base_amount_in = read_u64_unchecked(data, 16);
471        let max_quote_amount_in = read_u64_unchecked(data, 24);
472        let user_base_token_reserves = read_u64_unchecked(data, 32);
473        let user_quote_token_reserves = read_u64_unchecked(data, 40);
474        let pool_base_token_reserves = read_u64_unchecked(data, 48);
475        let pool_quote_token_reserves = read_u64_unchecked(data, 56);
476        let base_amount_in = read_u64_unchecked(data, 64);
477        let quote_amount_in = read_u64_unchecked(data, 72);
478        let lp_mint_supply = read_u64_unchecked(data, 80);
479
480        let pool = read_pubkey_unchecked(data, 88);
481        let user = read_pubkey_unchecked(data, 120);
482        let user_base_token_account = read_pubkey_unchecked(data, 152);
483        let user_quote_token_account = read_pubkey_unchecked(data, 184);
484        let user_pool_token_account = read_pubkey_unchecked(data, 216);
485
486        let metadata = EventMetadata {
487            signature,
488            slot,
489            tx_index,
490            block_time_us: block_time_us.unwrap_or(0),
491            grpc_recv_us,
492            recent_blockhash: None,
493        };
494
495        Some(DexEvent::PumpSwapLiquidityAdded(PumpSwapLiquidityAdded {
496            metadata,
497            timestamp,
498            lp_token_amount_out,
499            max_base_amount_in,
500            max_quote_amount_in,
501            user_base_token_reserves,
502            user_quote_token_reserves,
503            pool_base_token_reserves,
504            pool_quote_token_reserves,
505            base_amount_in,
506            quote_amount_in,
507            lp_mint_supply,
508            pool,
509            user,
510            user_base_token_account,
511            user_quote_token_account,
512            user_pool_token_account,
513        }))
514    }
515}
516
517/// 解析移除流动性事件 (极限优化)
518#[inline(always)]
519fn parse_remove_liquidity_event_optimized(
520    data: &[u8],
521    signature: Signature,
522    slot: u64,
523    tx_index: u64,
524    block_time_us: Option<i64>,
525    grpc_recv_us: i64,
526) -> Option<DexEvent> {
527    const REQUIRED_LEN: usize = 10 * 8 + 5 * 32;
528    if data.len() < REQUIRED_LEN {
529        return None;
530    }
531
532    unsafe {
533        let timestamp = read_i64_unchecked(data, 0);
534        let lp_token_amount_in = read_u64_unchecked(data, 8);
535        let min_base_amount_out = read_u64_unchecked(data, 16);
536        let min_quote_amount_out = read_u64_unchecked(data, 24);
537        let user_base_token_reserves = read_u64_unchecked(data, 32);
538        let user_quote_token_reserves = read_u64_unchecked(data, 40);
539        let pool_base_token_reserves = read_u64_unchecked(data, 48);
540        let pool_quote_token_reserves = read_u64_unchecked(data, 56);
541        let base_amount_out = read_u64_unchecked(data, 64);
542        let quote_amount_out = read_u64_unchecked(data, 72);
543        let lp_mint_supply = read_u64_unchecked(data, 80);
544
545        let pool = read_pubkey_unchecked(data, 88);
546        let user = read_pubkey_unchecked(data, 120);
547        let user_base_token_account = read_pubkey_unchecked(data, 152);
548        let user_quote_token_account = read_pubkey_unchecked(data, 184);
549        let user_pool_token_account = read_pubkey_unchecked(data, 216);
550
551        let metadata = EventMetadata {
552            signature,
553            slot,
554            tx_index,
555            block_time_us: block_time_us.unwrap_or(0),
556            grpc_recv_us,
557            recent_blockhash: None,
558        };
559
560        Some(DexEvent::PumpSwapLiquidityRemoved(PumpSwapLiquidityRemoved {
561            metadata,
562            timestamp,
563            lp_token_amount_in,
564            min_base_amount_out,
565            min_quote_amount_out,
566            user_base_token_reserves,
567            user_quote_token_reserves,
568            pool_base_token_reserves,
569            pool_quote_token_reserves,
570            base_amount_out,
571            quote_amount_out,
572            lp_mint_supply,
573            pool,
574            user,
575            user_base_token_account,
576            user_quote_token_account,
577            user_pool_token_account,
578        }))
579    }
580}
581
582// ============================================================================
583// 快速过滤 API (用于事件过滤场景)
584// ============================================================================
585
586/// 快速判断事件类型 (只解析 discriminator)
587///
588/// 性能: <50ns
589#[inline(always)]
590pub fn get_event_type_fast(log: &str) -> Option<u64> {
591    extract_discriminator_simd(log)
592}
593
594/// 检查是否为特定事件类型 (SIMD 优化)
595#[inline(always)]
596pub fn is_event_type(log: &str, discriminator: u64) -> bool {
597    extract_discriminator_simd(log) == Some(discriminator)
598}
599
600// ============================================================================
601// Public API for optimized parsing from pre-decoded data
602// These functions accept already-decoded data (without discriminator)
603// ============================================================================
604
605/// Parse PumpSwap Buy event from pre-decoded data
606#[inline(always)]
607pub fn parse_buy_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
608    // Historical events end after last_update_timestamp. Newer layouts append
609    // min_base_amount_out, ix_name, and complete trade-tail schema versions.
610    const LEGACY_LEN: usize = 16 * 8 + 7 * 32 + 1 + 4 * 8;
611    const MIN_REQUIRED_LEN: usize = LEGACY_LEN + 8 + 4;
612    if data.len() != LEGACY_LEN && data.len() < MIN_REQUIRED_LEN {
613        return None;
614    }
615    let track_volume = match data[352] {
616        0 => false,
617        1 => true,
618        _ => return None,
619    };
620
621    unsafe {
622        let timestamp = read_i64_unchecked(data, 0);
623        let base_amount_out = read_u64_unchecked(data, 8);
624        let max_quote_amount_in = read_u64_unchecked(data, 16);
625        let user_base_token_reserves = read_u64_unchecked(data, 24);
626        let user_quote_token_reserves = read_u64_unchecked(data, 32);
627        let pool_base_token_reserves = read_u64_unchecked(data, 40);
628        let pool_quote_token_reserves = read_u64_unchecked(data, 48);
629        let quote_amount_in = read_u64_unchecked(data, 56);
630        let lp_fee_basis_points = read_u64_unchecked(data, 64);
631        let lp_fee = read_u64_unchecked(data, 72);
632        let protocol_fee_basis_points = read_u64_unchecked(data, 80);
633        let protocol_fee = read_u64_unchecked(data, 88);
634        let quote_amount_in_with_lp_fee = read_u64_unchecked(data, 96);
635        let user_quote_amount_in = read_u64_unchecked(data, 104);
636
637        let pool = read_pubkey_unchecked(data, 112);
638        let user = read_pubkey_unchecked(data, 144);
639        let user_base_token_account = read_pubkey_unchecked(data, 176);
640        let user_quote_token_account = read_pubkey_unchecked(data, 208);
641        let protocol_fee_recipient = read_pubkey_unchecked(data, 240);
642        let protocol_fee_recipient_token_account = read_pubkey_unchecked(data, 272);
643        let coin_creator = read_pubkey_unchecked(data, 304);
644
645        let coin_creator_fee_basis_points = read_u64_unchecked(data, 336);
646        let coin_creator_fee = read_u64_unchecked(data, 344);
647        let total_unclaimed_tokens = read_u64_unchecked(data, 353);
648        let total_claimed_tokens = read_u64_unchecked(data, 361);
649        let current_sol_volume = read_u64_unchecked(data, 369);
650        let last_update_timestamp = read_i64_unchecked(data, 377);
651
652        let (min_base_amount_out, ix_name, tail) = if data.len() == LEGACY_LEN {
653            (0, String::new(), PumpSwapTradeTail::default())
654        } else {
655            let min_base_amount_out = read_u64_unchecked(data, LEGACY_LEN);
656            let (ix_name, tail_offset) = read_borsh_string(data, LEGACY_LEN + 8)?;
657            let tail = parse_trade_tail(&data[tail_offset..])?;
658            (min_base_amount_out, ix_name, tail)
659        };
660
661        Some(DexEvent::PumpSwapBuy(PumpSwapBuyEvent {
662            metadata,
663            timestamp,
664            base_amount_out,
665            max_quote_amount_in,
666            user_base_token_reserves,
667            user_quote_token_reserves,
668            pool_base_token_reserves,
669            pool_quote_token_reserves,
670            quote_amount_in,
671            lp_fee_basis_points,
672            lp_fee,
673            protocol_fee_basis_points,
674            protocol_fee,
675            quote_amount_in_with_lp_fee,
676            user_quote_amount_in,
677            pool,
678            user,
679            user_base_token_account,
680            user_quote_token_account,
681            protocol_fee_recipient,
682            protocol_fee_recipient_token_account,
683            coin_creator,
684            coin_creator_fee_basis_points,
685            coin_creator_fee,
686            track_volume,
687            total_unclaimed_tokens,
688            total_claimed_tokens,
689            current_sol_volume,
690            last_update_timestamp,
691            min_base_amount_out,
692            ix_name,
693            cashback_fee_basis_points: tail.cashback_fee_basis_points,
694            cashback: tail.cashback,
695            buyback_fee_basis_points: tail.buyback_fee_basis_points,
696            buyback_fee: tail.buyback_fee,
697            virtual_quote_reserves: tail.virtual_quote_reserves,
698            can_boost: tail.can_boost,
699            base_supply: tail.base_supply,
700            holder_rewards_bps: tail.holder_rewards_bps,
701            holder_rewards: tail.holder_rewards,
702            ..Default::default()
703        }))
704    }
705}
706
707/// Parse PumpSwap Sell event from pre-decoded data
708#[inline(always)]
709pub fn parse_sell_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
710    // 14 numeric fields, 7 pubkeys, and the two coin-creator fee fields.
711    const REQUIRED_LEN: usize = 14 * 8 + 7 * 32 + 2 * 8;
712    if data.len() < REQUIRED_LEN {
713        return None;
714    }
715
716    let tail = parse_trade_tail(&data[REQUIRED_LEN..])?;
717
718    unsafe {
719        let timestamp = read_i64_unchecked(data, 0);
720        let base_amount_in = read_u64_unchecked(data, 8);
721        let min_quote_amount_out = read_u64_unchecked(data, 16);
722        let user_base_token_reserves = read_u64_unchecked(data, 24);
723        let user_quote_token_reserves = read_u64_unchecked(data, 32);
724        let pool_base_token_reserves = read_u64_unchecked(data, 40);
725        let pool_quote_token_reserves = read_u64_unchecked(data, 48);
726        let quote_amount_out = read_u64_unchecked(data, 56);
727        let lp_fee_basis_points = read_u64_unchecked(data, 64);
728        let lp_fee = read_u64_unchecked(data, 72);
729        let protocol_fee_basis_points = read_u64_unchecked(data, 80);
730        let protocol_fee = read_u64_unchecked(data, 88);
731        let quote_amount_out_without_lp_fee = read_u64_unchecked(data, 96);
732        let user_quote_amount_out = read_u64_unchecked(data, 104);
733
734        let pool = read_pubkey_unchecked(data, 112);
735        let user = read_pubkey_unchecked(data, 144);
736        let user_base_token_account = read_pubkey_unchecked(data, 176);
737        let user_quote_token_account = read_pubkey_unchecked(data, 208);
738        let protocol_fee_recipient = read_pubkey_unchecked(data, 240);
739        let protocol_fee_recipient_token_account = read_pubkey_unchecked(data, 272);
740        let coin_creator = read_pubkey_unchecked(data, 304);
741
742        let coin_creator_fee_basis_points = read_u64_unchecked(data, 336);
743        let coin_creator_fee = read_u64_unchecked(data, 344);
744
745        Some(DexEvent::PumpSwapSell(PumpSwapSellEvent {
746            metadata,
747            timestamp,
748            base_amount_in,
749            min_quote_amount_out,
750            user_base_token_reserves,
751            user_quote_token_reserves,
752            pool_base_token_reserves,
753            pool_quote_token_reserves,
754            quote_amount_out,
755            lp_fee_basis_points,
756            lp_fee,
757            protocol_fee_basis_points,
758            protocol_fee,
759            quote_amount_out_without_lp_fee,
760            user_quote_amount_out,
761            pool,
762            user,
763            user_base_token_account,
764            user_quote_token_account,
765            protocol_fee_recipient,
766            protocol_fee_recipient_token_account,
767            coin_creator,
768            coin_creator_fee_basis_points,
769            coin_creator_fee,
770            cashback_fee_basis_points: tail.cashback_fee_basis_points,
771            cashback: tail.cashback,
772            buyback_fee_basis_points: tail.buyback_fee_basis_points,
773            buyback_fee: tail.buyback_fee,
774            virtual_quote_reserves: tail.virtual_quote_reserves,
775            can_boost: tail.can_boost,
776            base_supply: tail.base_supply,
777            holder_rewards_bps: tail.holder_rewards_bps,
778            holder_rewards: tail.holder_rewards,
779            ..Default::default()
780        }))
781    }
782}
783
784/// Parse PumpSwap CreatePool event from pre-decoded data
785#[inline(always)]
786pub fn parse_create_pool_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
787    const CREATE_POOL_EVENT_LEN: usize = 326;
788    const CREATOR_FEE_EVENT_LEN: usize = 335;
789    const REQUIRED_LEN: usize = CREATE_POOL_EVENT_LEN;
790    if data.len() < REQUIRED_LEN
791        || (data.len() != CREATE_POOL_EVENT_LEN && data.len() < CREATOR_FEE_EVENT_LEN)
792    {
793        return None;
794    }
795
796    unsafe {
797        let timestamp = read_i64_unchecked(data, 0);
798        let index = read_u16_unchecked(data, 8);
799
800        let creator = read_pubkey_unchecked(data, 10);
801        let base_mint = read_pubkey_unchecked(data, 42);
802        let quote_mint = read_pubkey_unchecked(data, 74);
803
804        let base_mint_decimals = read_u8_unchecked(data, 106);
805        let quote_mint_decimals = read_u8_unchecked(data, 107);
806
807        let base_amount_in = read_u64_unchecked(data, 108);
808        let quote_amount_in = read_u64_unchecked(data, 116);
809        let pool_base_amount = read_u64_unchecked(data, 124);
810        let pool_quote_amount = read_u64_unchecked(data, 132);
811        let minimum_liquidity = read_u64_unchecked(data, 140);
812        let initial_liquidity = read_u64_unchecked(data, 148);
813        let lp_token_amount_out = read_u64_unchecked(data, 156);
814
815        let pool_bump = read_u8_unchecked(data, 164);
816
817        let pool = read_pubkey_unchecked(data, 165);
818        let lp_mint = read_pubkey_unchecked(data, 197);
819        let user_base_token_account = read_pubkey_unchecked(data, 229);
820        let user_quote_token_account = read_pubkey_unchecked(data, 261);
821        let coin_creator = read_pubkey_unchecked(data, 293);
822        let is_mayhem_mode = data.len() > 325 && read_bool_unchecked(data, 325);
823        let creator_fee_bps = if data.len() >= 334 { read_u64_unchecked(data, 326) } else { 0 };
824        let can_edit_creator_fee = data.len() > 334 && read_bool_unchecked(data, 334);
825        let is_holder_reward = data.len() > 335 && read_bool_unchecked(data, 335);
826
827        Some(DexEvent::PumpSwapCreatePool(PumpSwapCreatePoolEvent {
828            metadata,
829            timestamp,
830            index,
831            creator,
832            base_mint,
833            quote_mint,
834            base_mint_decimals,
835            quote_mint_decimals,
836            base_amount_in,
837            quote_amount_in,
838            pool_base_amount,
839            pool_quote_amount,
840            minimum_liquidity,
841            initial_liquidity,
842            lp_token_amount_out,
843            pool_bump,
844            pool,
845            lp_mint,
846            user_base_token_account,
847            user_quote_token_account,
848            coin_creator,
849            is_mayhem_mode,
850            is_cashback_coin: false,
851            creator_fee_bps,
852            can_edit_creator_fee,
853            is_holder_reward,
854        }))
855    }
856}
857
858/// Parse PumpSwap AddLiquidity event from pre-decoded data
859#[inline(always)]
860pub fn parse_add_liquidity_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
861    const REQUIRED_LEN: usize = 10 * 8 + 5 * 32;
862    if data.len() < REQUIRED_LEN {
863        return None;
864    }
865
866    unsafe {
867        let timestamp = read_i64_unchecked(data, 0);
868        let lp_token_amount_out = read_u64_unchecked(data, 8);
869        let max_base_amount_in = read_u64_unchecked(data, 16);
870        let max_quote_amount_in = read_u64_unchecked(data, 24);
871        let user_base_token_reserves = read_u64_unchecked(data, 32);
872        let user_quote_token_reserves = read_u64_unchecked(data, 40);
873        let pool_base_token_reserves = read_u64_unchecked(data, 48);
874        let pool_quote_token_reserves = read_u64_unchecked(data, 56);
875        let base_amount_in = read_u64_unchecked(data, 64);
876        let quote_amount_in = read_u64_unchecked(data, 72);
877        let lp_mint_supply = read_u64_unchecked(data, 80);
878
879        let pool = read_pubkey_unchecked(data, 88);
880        let user = read_pubkey_unchecked(data, 120);
881        let user_base_token_account = read_pubkey_unchecked(data, 152);
882        let user_quote_token_account = read_pubkey_unchecked(data, 184);
883        let user_pool_token_account = read_pubkey_unchecked(data, 216);
884
885        Some(DexEvent::PumpSwapLiquidityAdded(PumpSwapLiquidityAdded {
886            metadata,
887            timestamp,
888            lp_token_amount_out,
889            max_base_amount_in,
890            max_quote_amount_in,
891            user_base_token_reserves,
892            user_quote_token_reserves,
893            pool_base_token_reserves,
894            pool_quote_token_reserves,
895            base_amount_in,
896            quote_amount_in,
897            lp_mint_supply,
898            pool,
899            user,
900            user_base_token_account,
901            user_quote_token_account,
902            user_pool_token_account,
903        }))
904    }
905}
906
907/// Parse PumpSwap RemoveLiquidity event from pre-decoded data
908#[inline(always)]
909pub fn parse_remove_liquidity_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
910    const REQUIRED_LEN: usize = 10 * 8 + 5 * 32;
911    if data.len() < REQUIRED_LEN {
912        return None;
913    }
914
915    unsafe {
916        let timestamp = read_i64_unchecked(data, 0);
917        let lp_token_amount_in = read_u64_unchecked(data, 8);
918        let min_base_amount_out = read_u64_unchecked(data, 16);
919        let min_quote_amount_out = read_u64_unchecked(data, 24);
920        let user_base_token_reserves = read_u64_unchecked(data, 32);
921        let user_quote_token_reserves = read_u64_unchecked(data, 40);
922        let pool_base_token_reserves = read_u64_unchecked(data, 48);
923        let pool_quote_token_reserves = read_u64_unchecked(data, 56);
924        let base_amount_out = read_u64_unchecked(data, 64);
925        let quote_amount_out = read_u64_unchecked(data, 72);
926        let lp_mint_supply = read_u64_unchecked(data, 80);
927
928        let pool = read_pubkey_unchecked(data, 88);
929        let user = read_pubkey_unchecked(data, 120);
930        let user_base_token_account = read_pubkey_unchecked(data, 152);
931        let user_quote_token_account = read_pubkey_unchecked(data, 184);
932        let user_pool_token_account = read_pubkey_unchecked(data, 216);
933
934        Some(DexEvent::PumpSwapLiquidityRemoved(PumpSwapLiquidityRemoved {
935            metadata,
936            timestamp,
937            lp_token_amount_in,
938            min_base_amount_out,
939            min_quote_amount_out,
940            user_base_token_reserves,
941            user_quote_token_reserves,
942            pool_base_token_reserves,
943            pool_quote_token_reserves,
944            base_amount_out,
945            quote_amount_out,
946            lp_mint_supply,
947            pool,
948            user,
949            user_base_token_account,
950            user_quote_token_account,
951            user_pool_token_account,
952        }))
953    }
954}
955
956// ============================================================================
957// 性能统计 API (可选)
958// ============================================================================
959
960#[cfg(feature = "perf-stats")]
961pub fn get_perf_stats() -> (usize, usize) {
962    let count = PARSE_COUNT.load(Ordering::Relaxed);
963    let total_ns = PARSE_TIME_NS.load(Ordering::Relaxed);
964    (count, total_ns)
965}
966
967#[cfg(feature = "perf-stats")]
968pub fn reset_perf_stats() {
969    PARSE_COUNT.store(0, Ordering::Relaxed);
970    PARSE_TIME_NS.store(0, Ordering::Relaxed);
971}
972
973#[cfg(test)]
974mod tests {
975    use super::*;
976    use base64::{engine::general_purpose::STANDARD, Engine as _};
977    use solana_sdk::{pubkey::Pubkey, signature::Signature};
978
979    fn metadata() -> EventMetadata {
980        EventMetadata {
981            signature: Signature::default(),
982            slot: 0,
983            tx_index: 0,
984            block_time_us: 0,
985            grpc_recv_us: 0,
986            recent_blockhash: None,
987        }
988    }
989
990    fn write_u64(buf: &mut [u8], offset: usize, value: u64) {
991        buf[offset..offset + 8].copy_from_slice(&value.to_le_bytes());
992    }
993
994    fn write_i64(buf: &mut [u8], offset: usize, value: i64) {
995        buf[offset..offset + 8].copy_from_slice(&value.to_le_bytes());
996    }
997
998    fn write_pubkey(buf: &mut [u8], offset: usize, value: Pubkey) {
999        buf[offset..offset + 32].copy_from_slice(value.as_ref());
1000    }
1001
1002    fn append_current_trade_tail(data: &mut Vec<u8>) {
1003        data.extend_from_slice(&177u64.to_le_bytes()); // cashback_fee_basis_points
1004        data.extend_from_slice(&188u64.to_le_bytes()); // cashback
1005        data.extend_from_slice(&199u64.to_le_bytes()); // buyback_fee_basis_points
1006        data.extend_from_slice(&211u64.to_le_bytes()); // buyback_fee
1007        data.extend_from_slice(&(-987_654_321i128).to_le_bytes());
1008        data.push(1); // can_boost
1009        data.extend_from_slice(&222u64.to_le_bytes()); // base_supply
1010        data.extend_from_slice(&233u64.to_le_bytes()); // holder_rewards_bps
1011        data.extend_from_slice(&244u64.to_le_bytes()); // holder_rewards
1012    }
1013
1014    fn append_buyback_trade_tail(data: &mut Vec<u8>) {
1015        data.extend_from_slice(&177u64.to_le_bytes()); // cashback_fee_basis_points
1016        data.extend_from_slice(&188u64.to_le_bytes()); // cashback
1017        data.extend_from_slice(&199u64.to_le_bytes()); // buyback_fee_basis_points
1018        data.extend_from_slice(&211u64.to_le_bytes()); // buyback_fee
1019    }
1020
1021    fn build_buy_payload(include_current_tail: bool) -> Vec<u8> {
1022        let mut data = vec![0u8; 393];
1023        write_i64(&mut data, 0, 1_713_498_953);
1024        write_u64(&mut data, 8, 11);
1025        write_u64(&mut data, 385, 22);
1026        data.extend_from_slice(&3u32.to_le_bytes());
1027        data.extend_from_slice(b"buy");
1028        if include_current_tail {
1029            append_current_trade_tail(&mut data);
1030        }
1031        data
1032    }
1033
1034    fn build_sell_payload(include_cashback: bool) -> Vec<u8> {
1035        let len = if include_cashback { 368 } else { 352 };
1036        let mut data = vec![0u8; len];
1037
1038        write_i64(&mut data, 0, 1_713_498_953);
1039        write_u64(&mut data, 8, 11);
1040        write_u64(&mut data, 16, 22);
1041        write_u64(&mut data, 24, 33);
1042        write_u64(&mut data, 32, 44);
1043        write_u64(&mut data, 40, 55);
1044        write_u64(&mut data, 48, 66);
1045        write_u64(&mut data, 56, 77);
1046        write_u64(&mut data, 64, 88);
1047        write_u64(&mut data, 72, 99);
1048        write_u64(&mut data, 80, 111);
1049        write_u64(&mut data, 88, 122);
1050        write_u64(&mut data, 96, 133);
1051        write_u64(&mut data, 104, 144);
1052
1053        write_pubkey(&mut data, 112, Pubkey::new_from_array([1; 32]));
1054        write_pubkey(&mut data, 144, Pubkey::new_from_array([2; 32]));
1055        write_pubkey(&mut data, 176, Pubkey::new_from_array([3; 32]));
1056        write_pubkey(&mut data, 208, Pubkey::new_from_array([4; 32]));
1057        write_pubkey(&mut data, 240, Pubkey::new_from_array([5; 32]));
1058        write_pubkey(&mut data, 272, Pubkey::new_from_array([6; 32]));
1059        write_pubkey(&mut data, 304, Pubkey::new_from_array([7; 32]));
1060
1061        write_u64(&mut data, 336, 155);
1062        write_u64(&mut data, 344, 166);
1063
1064        if include_cashback {
1065            write_u64(&mut data, 352, 177);
1066            write_u64(&mut data, 360, 188);
1067        }
1068
1069        data
1070    }
1071
1072    fn build_create_pool_payload(is_mayhem_mode: bool) -> Vec<u8> {
1073        let mut data = vec![0u8; 326];
1074
1075        write_i64(&mut data, 0, 1_713_498_953);
1076        data[8..10].copy_from_slice(&42u16.to_le_bytes());
1077        write_pubkey(&mut data, 10, Pubkey::new_from_array([1; 32]));
1078        write_pubkey(&mut data, 42, Pubkey::new_from_array([2; 32]));
1079        write_pubkey(&mut data, 74, Pubkey::new_from_array([3; 32]));
1080        data[106] = 6;
1081        data[107] = 9;
1082        write_u64(&mut data, 108, 11);
1083        write_u64(&mut data, 116, 22);
1084        write_u64(&mut data, 124, 33);
1085        write_u64(&mut data, 132, 44);
1086        write_u64(&mut data, 140, 55);
1087        write_u64(&mut data, 148, 66);
1088        write_u64(&mut data, 156, 77);
1089        data[164] = 8;
1090        write_pubkey(&mut data, 165, Pubkey::new_from_array([4; 32]));
1091        write_pubkey(&mut data, 197, Pubkey::new_from_array([5; 32]));
1092        write_pubkey(&mut data, 229, Pubkey::new_from_array([6; 32]));
1093        write_pubkey(&mut data, 261, Pubkey::new_from_array([7; 32]));
1094        write_pubkey(&mut data, 293, Pubkey::new_from_array([8; 32]));
1095        data[325] = u8::from(is_mayhem_mode);
1096        data.extend_from_slice(&250u64.to_le_bytes());
1097        data.push(1);
1098        data.push(1);
1099
1100        data
1101    }
1102
1103    #[test]
1104    fn test_discriminator_simd() {
1105        // 测试 SIMD discriminator 提取
1106        let log = "Program data: Z/RS H8v1d3cAAAAAAAAAAA=";
1107        let disc = extract_discriminator_simd(log);
1108        assert!(disc.is_some());
1109    }
1110
1111    #[test]
1112    fn test_parse_performance() {
1113        // 性能测试
1114        let log = "Program data: Z/RS H8v1d3cAAAAAAAAAAA=";
1115        let sig = Signature::default();
1116
1117        let start = std::time::Instant::now();
1118        for _ in 0..1000 {
1119            let _ = parse_log(log, sig, 0, 0, Some(0), 0);
1120        }
1121        let elapsed = start.elapsed();
1122
1123        println!("Average parse time: {} ns", elapsed.as_nanos() / 1000);
1124    }
1125
1126    #[test]
1127    fn parse_sell_from_data_preserves_cashback_fields() {
1128        let event = parse_sell_from_data(&build_sell_payload(true), metadata())
1129            .expect("expected pumpswap sell event");
1130
1131        let DexEvent::PumpSwapSell(event) = event else {
1132            panic!("expected PumpSwapSell event");
1133        };
1134
1135        assert_eq!(event.cashback_fee_basis_points, 177);
1136        assert_eq!(event.cashback, 188);
1137        assert_eq!(event.coin_creator_fee_basis_points, 155);
1138        assert_eq!(event.coin_creator_fee, 166);
1139    }
1140
1141    #[test]
1142    fn parse_current_buy_from_data_reads_virtual_reserves() {
1143        let event = parse_buy_from_data(&build_buy_payload(true), metadata())
1144            .expect("expected current pumpswap buy event");
1145
1146        let DexEvent::PumpSwapBuy(event) = event else {
1147            panic!("expected PumpSwapBuy event");
1148        };
1149
1150        assert_eq!(event.min_base_amount_out, 22);
1151        assert_eq!(event.ix_name, "buy");
1152        assert_eq!(event.cashback_fee_basis_points, 177);
1153        assert_eq!(event.cashback, 188);
1154        assert_eq!(event.buyback_fee_basis_points, 199);
1155        assert_eq!(event.buyback_fee, 211);
1156        assert_eq!(event.virtual_quote_reserves, -987_654_321);
1157        assert!(event.can_boost);
1158        assert_eq!(event.holder_rewards_bps, 233);
1159        assert_eq!(event.holder_rewards, 244);
1160        assert_eq!(event.base_supply, 222);
1161        assert_eq!(event.holder_rewards_bps, 233);
1162        assert_eq!(event.holder_rewards, 244);
1163    }
1164
1165    #[test]
1166    fn parse_log_uses_current_buy_layout() {
1167        let mut program_data = discriminators::BUY.to_le_bytes().to_vec();
1168        program_data.extend_from_slice(&build_buy_payload(true));
1169        let log = format!("Program data: {}", STANDARD.encode(program_data));
1170
1171        let event = parse_log(&log, Signature::default(), 7, 8, Some(9), 10)
1172            .expect("expected current pumpswap buy log");
1173        let DexEvent::PumpSwapBuy(event) = event else {
1174            panic!("expected PumpSwapBuy event");
1175        };
1176
1177        assert_eq!(event.metadata.slot, 7);
1178        assert_eq!(event.virtual_quote_reserves, -987_654_321);
1179        assert!(event.can_boost);
1180    }
1181
1182    #[test]
1183    fn parse_log_uses_current_sell_layout() {
1184        let mut payload = build_sell_payload(false);
1185        append_current_trade_tail(&mut payload);
1186        let mut program_data = discriminators::SELL.to_le_bytes().to_vec();
1187        program_data.extend_from_slice(&payload);
1188        let log = format!("Program data: {}", STANDARD.encode(program_data));
1189
1190        let event = parse_log(&log, Signature::default(), 17, 18, Some(19), 20)
1191            .expect("expected current pumpswap sell log");
1192        let DexEvent::PumpSwapSell(event) = event else {
1193            panic!("expected PumpSwapSell event");
1194        };
1195
1196        assert_eq!(event.metadata.slot, 17);
1197        assert_eq!(event.virtual_quote_reserves, -987_654_321);
1198        assert!(event.can_boost);
1199        assert_eq!(event.base_supply, 222);
1200    }
1201
1202    #[test]
1203    fn parse_current_sell_from_data_reads_virtual_reserves() {
1204        let mut data = build_sell_payload(false);
1205        append_current_trade_tail(&mut data);
1206        let event =
1207            parse_sell_from_data(&data, metadata()).expect("expected current pumpswap sell event");
1208
1209        let DexEvent::PumpSwapSell(event) = event else {
1210            panic!("expected PumpSwapSell event");
1211        };
1212
1213        assert_eq!(event.cashback_fee_basis_points, 177);
1214        assert_eq!(event.cashback, 188);
1215        assert_eq!(event.buyback_fee_basis_points, 199);
1216        assert_eq!(event.buyback_fee, 211);
1217        assert_eq!(event.virtual_quote_reserves, -987_654_321);
1218        assert!(event.can_boost);
1219        assert_eq!(event.base_supply, 222);
1220    }
1221
1222    #[test]
1223    fn trade_parsers_preserve_i128_extremes() {
1224        let mut buy = build_buy_payload(true);
1225        buy[432..448].copy_from_slice(&i128::MIN.to_le_bytes());
1226        let DexEvent::PumpSwapBuy(buy) =
1227            parse_buy_from_data(&buy, metadata()).expect("expected current buy")
1228        else {
1229            panic!("expected PumpSwapBuy event");
1230        };
1231        assert_eq!(buy.virtual_quote_reserves, i128::MIN);
1232
1233        let mut sell = build_sell_payload(false);
1234        append_current_trade_tail(&mut sell);
1235        sell[384..400].copy_from_slice(&i128::MAX.to_le_bytes());
1236        let DexEvent::PumpSwapSell(sell) =
1237            parse_sell_from_data(&sell, metadata()).expect("expected current sell")
1238        else {
1239            panic!("expected PumpSwapSell event");
1240        };
1241        assert_eq!(sell.virtual_quote_reserves, i128::MAX);
1242    }
1243
1244    #[test]
1245    fn parse_sell_from_data_keeps_legacy_payload_compatible() {
1246        let event = parse_sell_from_data(&build_sell_payload(false), metadata())
1247            .expect("expected legacy pumpswap sell event");
1248
1249        let DexEvent::PumpSwapSell(event) = event else {
1250            panic!("expected PumpSwapSell event");
1251        };
1252
1253        assert_eq!(event.cashback_fee_basis_points, 0);
1254        assert_eq!(event.cashback, 0);
1255        assert_eq!(event.coin_creator_fee_basis_points, 155);
1256        assert_eq!(event.coin_creator_fee, 166);
1257        assert_eq!(event.virtual_quote_reserves, 0);
1258        assert!(!event.can_boost);
1259        assert_eq!(event.base_supply, 0);
1260    }
1261
1262    #[test]
1263    fn parse_buyback_layouts_without_boost_fields() {
1264        let mut buy = build_buy_payload(false);
1265        append_buyback_trade_tail(&mut buy);
1266        let DexEvent::PumpSwapBuy(buy) =
1267            parse_buy_from_data(&buy, metadata()).expect("expected buyback buy event")
1268        else {
1269            panic!("expected PumpSwapBuy event");
1270        };
1271        assert_eq!(buy.cashback_fee_basis_points, 177);
1272        assert_eq!(buy.buyback_fee_basis_points, 199);
1273        assert_eq!(buy.buyback_fee, 211);
1274        assert_eq!(buy.virtual_quote_reserves, 0);
1275
1276        let mut sell = build_sell_payload(false);
1277        append_buyback_trade_tail(&mut sell);
1278        let DexEvent::PumpSwapSell(sell) =
1279            parse_sell_from_data(&sell, metadata()).expect("expected buyback sell event")
1280        else {
1281            panic!("expected PumpSwapSell event");
1282        };
1283        assert_eq!(sell.cashback_fee_basis_points, 177);
1284        assert_eq!(sell.buyback_fee_basis_points, 199);
1285        assert_eq!(sell.buyback_fee, 211);
1286        assert_eq!(sell.virtual_quote_reserves, 0);
1287    }
1288
1289    #[test]
1290    fn trade_tail_accepts_only_complete_layouts() {
1291        for len in 0..=80 {
1292            let tail = vec![0u8; len];
1293            let expected = matches!(len, 0 | 16 | 32 | 57 | 73..=80);
1294            assert_eq!(parse_trade_tail(&tail).is_some(), expected, "tail length {len}");
1295        }
1296
1297        for invalid_bool in 2..=u8::MAX {
1298            let mut tail = vec![0u8; 57];
1299            tail[48] = invalid_bool;
1300            assert!(parse_trade_tail(&tail).is_none(), "bool value {invalid_bool}");
1301        }
1302    }
1303
1304    #[test]
1305    fn parse_buy_from_data_rejects_truncated_min_base_payload() {
1306        assert!(parse_buy_from_data(&vec![0u8; 385], metadata()).is_some());
1307        for len in 386..397 {
1308            assert!(parse_buy_from_data(&vec![0u8; len], metadata()).is_none());
1309        }
1310        assert!(parse_buy_from_data(&vec![0u8; 397], metadata()).is_some());
1311    }
1312
1313    #[test]
1314    fn parse_buy_from_data_rejects_malformed_string_and_partial_tails() {
1315        let mut invalid_track_volume = build_buy_payload(false);
1316        invalid_track_volume[352] = 2;
1317        assert!(parse_buy_from_data(&invalid_track_volume, metadata()).is_none());
1318
1319        let mut invalid_utf8 = build_buy_payload(false);
1320        invalid_utf8[397] = 0xff;
1321        assert!(parse_buy_from_data(&invalid_utf8, metadata()).is_none());
1322
1323        let legacy = build_buy_payload(false);
1324        for partial_len in [1, 15, 17, 31, 33, 56] {
1325            let mut partial = legacy.clone();
1326            partial.resize(legacy.len() + partial_len, 0);
1327            assert!(parse_buy_from_data(&partial, metadata()).is_none());
1328        }
1329
1330        let mut oversized_name = vec![0u8; 397];
1331        oversized_name[393..397].copy_from_slice(&u32::MAX.to_le_bytes());
1332        assert!(parse_buy_from_data(&oversized_name, metadata()).is_none());
1333    }
1334
1335    #[test]
1336    fn parse_sell_from_data_rejects_partial_or_invalid_boost_tails() {
1337        for truncated_len in 336..352 {
1338            assert!(parse_sell_from_data(&vec![0u8; truncated_len], metadata()).is_none());
1339        }
1340
1341        let legacy = build_sell_payload(false);
1342        for partial_len in [1, 15, 17, 31, 33, 56] {
1343            let mut partial = legacy.clone();
1344            partial.resize(legacy.len() + partial_len, 0);
1345            assert!(parse_sell_from_data(&partial, metadata()).is_none());
1346        }
1347
1348        let mut invalid_bool = legacy;
1349        append_current_trade_tail(&mut invalid_bool);
1350        invalid_bool[352 + 48] = 2;
1351        assert!(parse_sell_from_data(&invalid_bool, metadata()).is_none());
1352    }
1353
1354    #[test]
1355    fn parse_create_pool_from_data_reads_current_fields() {
1356        let event = parse_create_pool_from_data(&build_create_pool_payload(true), metadata())
1357            .expect("expected pumpswap create pool event");
1358
1359        let DexEvent::PumpSwapCreatePool(event) = event else {
1360            panic!("expected PumpSwapCreatePool event");
1361        };
1362
1363        assert_eq!(event.index, 42);
1364        assert!(event.is_mayhem_mode);
1365        assert!(!event.is_cashback_coin);
1366        assert_eq!(event.creator_fee_bps, 250);
1367        assert!(event.can_edit_creator_fee);
1368        assert!(event.is_holder_reward);
1369    }
1370
1371    #[test]
1372    fn parse_create_pool_accepts_only_complete_layouts() {
1373        for len in 326..=336 {
1374            let expected = len == 326 || len >= 335;
1375            assert_eq!(
1376                parse_create_pool_from_data(&vec![0u8; len], metadata()).is_some(),
1377                expected,
1378                "create pool length {len}"
1379            );
1380        }
1381    }
1382}