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