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