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