Skip to main content

sol_parser_sdk/logs/
pump.rs

1//! Pump.fun `Program log` → [`DexEvent`](crate::core::events::DexEvent) (SIMD / zero-copy hot path).
2#![allow(dead_code)]
3#![allow(unused_imports)]
4#![allow(unused_variables)]
5
6use crate::core::events::*;
7use solana_sdk::{pubkey::Pubkey, signature::Signature};
8
9use memchr::memmem;
10use once_cell::sync::Lazy;
11
12#[cfg(feature = "perf-stats")]
13use std::sync::atomic::{AtomicUsize, Ordering};
14
15#[cfg(feature = "perf-stats")]
16pub static PARSE_COUNT: AtomicUsize = AtomicUsize::new(0);
17#[cfg(feature = "perf-stats")]
18pub static PARSE_TIME_NS: AtomicUsize = AtomicUsize::new(0);
19
20// --- discriminators ------------------------------------------------
21
22pub const CREATE_EVENT: u64 = u64::from_le_bytes([27, 114, 169, 77, 222, 235, 99, 118]);
23pub const TRADE_EVENT: u64 = u64::from_le_bytes([189, 219, 127, 211, 78, 230, 97, 238]);
24pub const MIGRATE_EVENT: u64 = u64::from_le_bytes([189, 233, 93, 185, 92, 148, 234, 148]);
25/// `createFeeSharingConfigEvent`(pump-fees IDL)
26pub const CREATE_FEE_SHARING_CONFIG_EVENT: u64 = crate::logs::pump_fees::discriminant_u64(
27    &crate::logs::pump_fees::CREATE_FEE_SHARING_CONFIG_EVENT_DISC,
28);
29/// `migrateBondingCurveCreatorEvent`(pump.fun IDL)
30pub const MIGRATE_BONDING_CURVE_CREATOR_EVENT: u64 =
31    u64::from_le_bytes([155, 167, 104, 220, 213, 108, 243, 3]);
32
33// --- binary_read ---------------------------------------------------
34
35#[inline(always)]
36pub unsafe fn read_u64_unchecked(data: &[u8], offset: usize) -> u64 {
37    let ptr = data.as_ptr().add(offset) as *const u64;
38    u64::from_le(ptr.read_unaligned())
39}
40
41#[inline(always)]
42pub unsafe fn read_i64_unchecked(data: &[u8], offset: usize) -> i64 {
43    let ptr = data.as_ptr().add(offset) as *const i64;
44    i64::from_le(ptr.read_unaligned())
45}
46
47#[inline(always)]
48pub unsafe fn read_bool_unchecked(data: &[u8], offset: usize) -> bool {
49    *data.get_unchecked(offset) == 1
50}
51
52#[inline(always)]
53pub unsafe fn read_pubkey_unchecked(data: &[u8], offset: usize) -> Pubkey {
54    #[cfg(target_arch = "x86_64")]
55    {
56        use std::arch::x86_64::_mm_prefetch;
57        use std::arch::x86_64::_MM_HINT_T0;
58        if offset + 64 < data.len() {
59            _mm_prefetch((data.as_ptr().add(offset + 32)) as *const i8, _MM_HINT_T0);
60        }
61    }
62
63    let ptr = data.as_ptr().add(offset);
64    let mut bytes = [0u8; 32];
65    std::ptr::copy_nonoverlapping(ptr, bytes.as_mut_ptr(), 32);
66    Pubkey::new_from_array(bytes)
67}
68
69#[inline(always)]
70pub unsafe fn read_str_unchecked(data: &[u8], offset: usize) -> Option<(&str, usize)> {
71    if data.len() < offset + 4 {
72        return None;
73    }
74
75    let len = read_u32_unchecked(data, offset) as usize;
76    if data.len() < offset + 4 + len {
77        return None;
78    }
79
80    let string_bytes = &data[offset + 4..offset + 4 + len];
81    let s = std::str::from_utf8_unchecked(string_bytes);
82    Some((s, 4 + len))
83}
84
85#[inline(always)]
86pub unsafe fn read_u32_unchecked(data: &[u8], offset: usize) -> u32 {
87    let ptr = data.as_ptr().add(offset) as *const u32;
88    u32::from_le(ptr.read_unaligned())
89}
90
91#[inline(always)]
92pub unsafe fn read_u16_unchecked(data: &[u8], offset: usize) -> u16 {
93    let ptr = data.as_ptr().add(offset) as *const u16;
94    u16::from_le(ptr.read_unaligned())
95}
96
97const MAX_TRADE_SHAREHOLDERS: usize = 64;
98
99#[inline(always)]
100unsafe fn read_optional_u64(data: &[u8], offset: &mut usize) -> u64 {
101    if *offset + 8 <= data.len() {
102        let v = read_u64_unchecked(data, *offset);
103        *offset += 8;
104        v
105    } else {
106        0
107    }
108}
109
110#[inline(always)]
111unsafe fn read_optional_pubkey(data: &[u8], offset: &mut usize) -> Pubkey {
112    if *offset + 32 <= data.len() {
113        let v = read_pubkey_unchecked(data, *offset);
114        *offset += 32;
115        v
116    } else {
117        Pubkey::default()
118    }
119}
120
121#[inline(always)]
122unsafe fn read_trade_shareholders(
123    data: &[u8],
124    offset: &mut usize,
125) -> Option<Vec<PumpFeesShareholder>> {
126    if *offset + 4 > data.len() {
127        return Some(Vec::new());
128    }
129    let n = read_u32_unchecked(data, *offset) as usize;
130    if n > MAX_TRADE_SHAREHOLDERS {
131        return None;
132    }
133    let bytes = 4usize.checked_add(n.checked_mul(34)?)?;
134    if *offset + bytes > data.len() {
135        return None;
136    }
137    *offset += 4;
138    let mut out = Vec::with_capacity(n);
139    for _ in 0..n {
140        let address = read_pubkey_unchecked(data, *offset);
141        *offset += 32;
142        let share_bps = read_u16_unchecked(data, *offset);
143        *offset += 2;
144        out.push(PumpFeesShareholder { address, share_bps });
145    }
146    Some(out)
147}
148
149#[inline(always)]
150pub(crate) unsafe fn read_trade_event_extensions(
151    data: &[u8],
152    offset: &mut usize,
153) -> Option<(u64, u64, Vec<PumpFeesShareholder>, Pubkey, u64, u64, u64)> {
154    let buyback_fee_basis_points = read_optional_u64(data, offset);
155    let buyback_fee = read_optional_u64(data, offset);
156    let shareholders = read_trade_shareholders(data, offset)?;
157    let quote_mint = read_optional_pubkey(data, offset);
158    let quote_amount = read_optional_u64(data, offset);
159    let virtual_quote_reserves = read_optional_u64(data, offset);
160    let real_quote_reserves = read_optional_u64(data, offset);
161    Some((
162        buyback_fee_basis_points,
163        buyback_fee,
164        shareholders,
165        quote_mint,
166        quote_amount,
167        virtual_quote_reserves,
168        real_quote_reserves,
169    ))
170}
171
172// --- log_decode ----------------------------------------------------
173
174static BASE64_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"Program data: "));
175/// `b"Program data: "`.len() — base64 payload starts immediately after this tag.
176const PROGRAM_DATA_TAG_LEN: usize = 14;
177
178#[inline(always)]
179pub fn extract_program_data_zero_copy<'a>(
180    log: &'a str,
181    buf: &'a mut [u8; 2048],
182) -> Option<&'a [u8]> {
183    let log_bytes = log.as_bytes();
184    let pos = BASE64_FINDER.find(log_bytes)?;
185
186    let data_part = &log[pos + PROGRAM_DATA_TAG_LEN..];
187    let trimmed = data_part.trim();
188
189    if trimmed.len() > 2700 {
190        return None;
191    }
192
193    use base64_simd::AsOut;
194    let decoded_slice =
195        base64_simd::STANDARD.decode(trimmed.as_bytes(), buf.as_mut().as_out()).ok()?;
196
197    Some(decoded_slice)
198}
199
200#[inline(always)]
201pub fn extract_discriminator_simd(log: &str) -> Option<u64> {
202    let log_bytes = log.as_bytes();
203    let pos = BASE64_FINDER.find(log_bytes)?;
204
205    let data_part = &log[pos + PROGRAM_DATA_TAG_LEN..];
206    let trimmed = data_part.trim();
207
208    if trimmed.len() < 16 {
209        return None;
210    }
211
212    use base64_simd::AsOut;
213    let mut buf = [0u8; 12];
214    let prefix = trimmed.as_bytes().get(..16)?;
215    base64_simd::STANDARD.decode(prefix, buf.as_mut().as_out()).ok()?;
216
217    unsafe {
218        let ptr = buf.as_ptr() as *const u64;
219        Some(ptr.read_unaligned())
220    }
221}
222
223// --- main parser ---------------------------------------------------
224/// 主解析函数 (极限优化版本)
225///
226/// 性能目标: <100ns
227#[inline(always)]
228pub fn parse_log(
229    log: &str,
230    signature: Signature,
231    slot: u64,
232    tx_index: u64,
233    block_time_us: Option<i64>,
234    grpc_recv_us: i64,
235    is_created_buy: bool,
236) -> Option<DexEvent> {
237    #[cfg(feature = "perf-stats")]
238    let start = std::time::Instant::now();
239
240    // 使用栈分配的缓冲区 (增加到 2KB 以防止 base64-simd 缓冲区溢出)
241    let mut buf = [0u8; 2048];
242    let program_data = extract_program_data_zero_copy(log, &mut buf)?;
243
244    if program_data.len() < 8 {
245        return None;
246    }
247
248    // 使用 unsafe 读取 discriminator (SIMD 优化)
249    let discriminator = unsafe { read_u64_unchecked(program_data, 0) };
250    let data = &program_data[8..];
251
252    let result = match discriminator {
253        CREATE_EVENT => parse_create_event_optimized(
254            data,
255            signature,
256            slot,
257            tx_index,
258            block_time_us,
259            grpc_recv_us,
260        ),
261        TRADE_EVENT => parse_trade_event_optimized(
262            data,
263            signature,
264            slot,
265            tx_index,
266            block_time_us,
267            grpc_recv_us,
268            is_created_buy,
269        ),
270        MIGRATE_EVENT => parse_migrate_event_optimized(
271            data,
272            signature,
273            slot,
274            tx_index,
275            block_time_us,
276            grpc_recv_us,
277        ),
278        CREATE_FEE_SHARING_CONFIG_EVENT => parse_create_fee_sharing_config_event_optimized(
279            data,
280            signature,
281            slot,
282            tx_index,
283            block_time_us,
284            grpc_recv_us,
285        ),
286        MIGRATE_BONDING_CURVE_CREATOR_EVENT => parse_migrate_bonding_curve_creator_event_optimized(
287            data,
288            signature,
289            slot,
290            tx_index,
291            block_time_us,
292            grpc_recv_us,
293        ),
294        _ => None,
295    };
296
297    #[cfg(feature = "perf-stats")]
298    {
299        PARSE_COUNT.fetch_add(1, Ordering::Relaxed);
300        PARSE_TIME_NS.fetch_add(start.elapsed().as_nanos() as usize, Ordering::Relaxed);
301    }
302
303    result
304}
305
306/// 解析 CreateEvent (极限优化)
307///
308/// 优化:
309/// - 使用 unsafe 消除所有边界检查
310/// - 零拷贝字符串解析
311/// - 内联所有调用
312#[inline(always)]
313fn parse_create_event_optimized(
314    data: &[u8],
315    signature: Signature,
316    slot: u64,
317    tx_index: u64,
318    block_time_us: Option<i64>,
319    grpc_recv_us: i64,
320) -> Option<DexEvent> {
321    unsafe {
322        let mut offset = 0;
323
324        // 读取字符串字段 (零拷贝)
325        let (name, name_len) = read_str_unchecked(data, offset)?;
326        offset += name_len;
327
328        let (symbol, symbol_len) = read_str_unchecked(data, offset)?;
329        offset += symbol_len;
330
331        let (uri, uri_len) = read_str_unchecked(data, offset)?;
332        offset += uri_len;
333
334        // 快速边界检查
335        if data.len() < offset + 32 + 32 + 32 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 1 {
336            return None;
337        }
338
339        // 读取 Pubkey 字段
340        let mint = read_pubkey_unchecked(data, offset);
341        offset += 32;
342
343        let bonding_curve = read_pubkey_unchecked(data, offset);
344        offset += 32;
345
346        let user = read_pubkey_unchecked(data, offset);
347        offset += 32;
348
349        let creator = read_pubkey_unchecked(data, offset);
350        offset += 32;
351
352        // 读取数值字段
353        let timestamp = read_i64_unchecked(data, offset);
354        offset += 8;
355
356        let virtual_token_reserves = read_u64_unchecked(data, offset);
357        offset += 8;
358
359        let virtual_sol_reserves = read_u64_unchecked(data, offset);
360        offset += 8;
361
362        let real_token_reserves = read_u64_unchecked(data, offset);
363        offset += 8;
364
365        let token_total_supply = read_u64_unchecked(data, offset);
366        offset += 8;
367
368        let token_program = if offset + 32 <= data.len() {
369            read_pubkey_unchecked(data, offset)
370        } else {
371            Pubkey::default()
372        };
373        offset += 32;
374
375        let is_mayhem_mode =
376            if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
377        offset += 1;
378        let is_cashback_enabled =
379            if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
380
381        let metadata = EventMetadata {
382            signature,
383            slot,
384            tx_index,
385            block_time_us: block_time_us.unwrap_or(0),
386            grpc_recv_us,
387            recent_blockhash: None,
388        };
389
390        // 将 &str 转换为 String (这是唯一的堆分配)
391        // 优化: 可以考虑使用 SmallString 或 Cow<'static, str> 进一步优化
392        Some(DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
393            metadata,
394            name: name.to_string(),
395            symbol: symbol.to_string(),
396            uri: uri.to_string(),
397            mint,
398            bonding_curve,
399            user,
400            creator,
401            timestamp,
402            virtual_token_reserves,
403            virtual_sol_reserves,
404            real_token_reserves,
405            token_total_supply,
406            token_program,
407            is_mayhem_mode,
408            is_cashback_enabled,
409        }))
410    }
411}
412
413/// 解析 TradeEvent (极限优化)
414///
415/// 根据 ix_name 返回不同的事件类型:
416/// - "buy" -> DexEvent::PumpFunBuy
417/// - "sell" -> DexEvent::PumpFunSell
418/// - "buy_exact_sol_in" / "buy_exact_quote_in" -> DexEvent::PumpFunBuyExactSolIn
419/// - 其他/空 -> DexEvent::PumpFunTrade (兼容旧版本)
420#[inline(always)]
421fn parse_trade_event_optimized(
422    data: &[u8],
423    signature: Signature,
424    slot: u64,
425    tx_index: u64,
426    block_time_us: Option<i64>,
427    grpc_recv_us: i64,
428    is_created_buy: bool,
429) -> Option<DexEvent> {
430    unsafe {
431        // 快速边界检查
432        if data.len() < 32 + 8 + 8 + 1 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 8 + 8 + 32 + 8 + 8 {
433            return None;
434        }
435
436        let mut offset = 0;
437
438        let mint = read_pubkey_unchecked(data, offset);
439        offset += 32;
440
441        let sol_amount = read_u64_unchecked(data, offset);
442        offset += 8;
443
444        let token_amount = read_u64_unchecked(data, offset);
445        offset += 8;
446
447        let is_buy = read_bool_unchecked(data, offset);
448        offset += 1;
449
450        let user = read_pubkey_unchecked(data, offset);
451        offset += 32;
452
453        let timestamp = read_i64_unchecked(data, offset);
454        offset += 8;
455
456        let virtual_sol_reserves = read_u64_unchecked(data, offset);
457        offset += 8;
458
459        let virtual_token_reserves = read_u64_unchecked(data, offset);
460        offset += 8;
461
462        let real_sol_reserves = read_u64_unchecked(data, offset);
463        offset += 8;
464
465        let real_token_reserves = read_u64_unchecked(data, offset);
466        offset += 8;
467
468        let fee_recipient = read_pubkey_unchecked(data, offset);
469        offset += 32;
470
471        let fee_basis_points = read_u64_unchecked(data, offset);
472        offset += 8;
473
474        let fee = read_u64_unchecked(data, offset);
475        offset += 8;
476
477        let creator = read_pubkey_unchecked(data, offset);
478        offset += 32;
479
480        let creator_fee_basis_points = read_u64_unchecked(data, offset);
481        offset += 8;
482
483        let creator_fee = read_u64_unchecked(data, offset);
484        offset += 8;
485
486        // 可选字段
487        let track_volume =
488            if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
489        offset += 1;
490
491        let total_unclaimed_tokens =
492            if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
493        offset += 8;
494
495        let total_claimed_tokens =
496            if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
497        offset += 8;
498
499        let current_sol_volume =
500            if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
501        offset += 8;
502
503        let last_update_timestamp =
504            if offset + 8 <= data.len() { read_i64_unchecked(data, offset) } else { 0 };
505        offset += 8;
506
507        // ix_name: String (4-byte length prefix + content)
508        // Values: "buy" | "sell" | "buy_exact_sol_in" | "buy_exact_quote_in"
509        let ix_name = if offset + 4 <= data.len() {
510            if let Some((s, len)) = read_str_unchecked(data, offset) {
511                offset += len;
512                s.to_string()
513            } else {
514                String::new()
515            }
516        } else {
517            String::new()
518        };
519
520        // mayhem_mode: bool (1 byte), cashback_fee_basis_points (8), cashback (8) - PUMP_CASHBACK_README
521        let mayhem_mode =
522            if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
523        offset += 1;
524        let cashback_fee_basis_points =
525            if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
526        offset += 8;
527        let cashback = if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
528        offset += 8;
529        let (
530            buyback_fee_basis_points,
531            buyback_fee,
532            shareholders,
533            quote_mint,
534            quote_amount,
535            virtual_quote_reserves,
536            real_quote_reserves,
537        ) = read_trade_event_extensions(data, &mut offset)?;
538
539        let metadata = EventMetadata {
540            signature,
541            slot,
542            tx_index,
543            block_time_us: block_time_us.unwrap_or(0),
544            grpc_recv_us,
545            recent_blockhash: None,
546        };
547
548        let trade_event = PumpFunTradeEvent {
549            metadata,
550            mint,
551            sol_amount,
552            token_amount,
553            is_buy,
554            is_created_buy,
555            user,
556            timestamp,
557            virtual_sol_reserves,
558            virtual_token_reserves,
559            real_sol_reserves,
560            real_token_reserves,
561            fee_recipient,
562            fee_basis_points,
563            fee,
564            creator,
565            creator_fee_basis_points,
566            creator_fee,
567            track_volume,
568            total_unclaimed_tokens,
569            total_claimed_tokens,
570            current_sol_volume,
571            last_update_timestamp,
572            ix_name: ix_name.clone(),
573            mayhem_mode,
574            cashback_fee_basis_points,
575            cashback,
576            buyback_fee_basis_points,
577            buyback_fee,
578            shareholders,
579            quote_mint,
580            quote_amount,
581            virtual_quote_reserves,
582            real_quote_reserves,
583            is_cashback_coin: cashback_fee_basis_points > 0,
584            amount: 0,
585            max_sol_cost: 0,
586            min_sol_output: 0,
587            spendable_sol_in: 0,
588            spendable_quote_in: 0,
589            min_tokens_out: 0,
590            global: Pubkey::default(),
591            bonding_curve: Pubkey::default(),
592            associated_bonding_curve: Pubkey::default(),
593            associated_user: Pubkey::default(),
594            system_program: Pubkey::default(),
595            creator_vault: Pubkey::default(),
596            event_authority: Pubkey::default(),
597            program: Pubkey::default(),
598            global_volume_accumulator: Pubkey::default(),
599            user_volume_accumulator: Pubkey::default(),
600            fee_config: Pubkey::default(),
601            fee_program: Pubkey::default(),
602            token_program: Pubkey::default(),
603            account: None,
604            ..Default::default()
605        };
606
607        // 根据 ix_name 返回不同的事件类型,支持用户过滤特定交易类型
608        match ix_name.as_str() {
609            "buy" | "buy_v2" => Some(DexEvent::PumpFunBuy(trade_event)),
610            "sell" | "sell_v2" => Some(DexEvent::PumpFunSell(trade_event)),
611            "buy_exact_sol_in" | "buy_exact_quote_in" | "buy_exact_quote_in_v2" => {
612                Some(DexEvent::PumpFunBuyExactSolIn(trade_event))
613            }
614            _ => Some(DexEvent::PumpFunTrade(trade_event)), // 兼容旧版本或未知类型
615        }
616    }
617}
618
619/// 解析 MigrateEvent (极限优化)
620#[inline(always)]
621fn parse_migrate_event_optimized(
622    data: &[u8],
623    signature: Signature,
624    slot: u64,
625    tx_index: u64,
626    block_time_us: Option<i64>,
627    grpc_recv_us: i64,
628) -> Option<DexEvent> {
629    unsafe {
630        // 快速边界检查
631        if data.len() < 32 + 32 + 8 + 8 + 8 + 32 + 8 + 32 {
632            return None;
633        }
634
635        let mut offset = 0;
636
637        let user = read_pubkey_unchecked(data, offset);
638        offset += 32;
639
640        let mint = read_pubkey_unchecked(data, offset);
641        offset += 32;
642
643        let mint_amount = read_u64_unchecked(data, offset);
644        offset += 8;
645
646        let sol_amount = read_u64_unchecked(data, offset);
647        offset += 8;
648
649        let pool_migration_fee = read_u64_unchecked(data, offset);
650        offset += 8;
651
652        let bonding_curve = read_pubkey_unchecked(data, offset);
653        offset += 32;
654
655        let timestamp = read_i64_unchecked(data, offset);
656        offset += 8;
657
658        let pool = read_pubkey_unchecked(data, offset);
659
660        let metadata = EventMetadata {
661            signature,
662            slot,
663            tx_index,
664            block_time_us: block_time_us.unwrap_or(0),
665            grpc_recv_us,
666            recent_blockhash: None,
667        };
668
669        Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
670            metadata,
671            user,
672            mint,
673            mint_amount,
674            sol_amount,
675            pool_migration_fee,
676            bonding_curve,
677            timestamp,
678            pool,
679        }))
680    }
681}
682
683#[inline(always)]
684fn parse_migrate_bonding_curve_creator_event_optimized(
685    data: &[u8],
686    signature: Signature,
687    slot: u64,
688    tx_index: u64,
689    block_time_us: Option<i64>,
690    grpc_recv_us: i64,
691) -> Option<DexEvent> {
692    let metadata = EventMetadata {
693        signature,
694        slot,
695        tx_index,
696        block_time_us: block_time_us.unwrap_or(0),
697        grpc_recv_us,
698        recent_blockhash: None,
699    };
700    parse_migrate_bonding_curve_creator_from_data(data, metadata)
701}
702
703#[inline(always)]
704fn parse_create_fee_sharing_config_event_optimized(
705    data: &[u8],
706    signature: Signature,
707    slot: u64,
708    tx_index: u64,
709    block_time_us: Option<i64>,
710    grpc_recv_us: i64,
711) -> Option<DexEvent> {
712    let metadata = EventMetadata {
713        signature,
714        slot,
715        tx_index,
716        block_time_us: block_time_us.unwrap_or(0),
717        grpc_recv_us,
718        recent_blockhash: None,
719    };
720    crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(data, metadata)
721}
722
723// ============================================================================
724// 快速过滤 API (用于事件过滤场景)
725// ============================================================================
726
727/// 快速判断事件类型 (只解析 discriminator)
728///
729/// 性能: <50ns
730#[inline(always)]
731pub fn get_event_type_fast(log: &str) -> Option<u64> {
732    extract_discriminator_simd(log)
733}
734
735/// 检查是否为特定事件类型 (SIMD 优化)
736#[inline(always)]
737pub fn is_event_type(log: &str, discriminator: u64) -> bool {
738    extract_discriminator_simd(log) == Some(discriminator)
739}
740
741// ============================================================================
742// Public API for optimized parsing from pre-decoded data
743// These functions accept already-decoded data (without discriminator)
744// ============================================================================
745
746/// Parse PumpFun Trade event from pre-decoded data
747///
748/// `data` should be the decoded bytes AFTER the 8-byte discriminator
749///
750/// Returns different event types based on ix_name:
751/// - "buy" -> DexEvent::PumpFunBuy
752/// - "sell" -> DexEvent::PumpFunSell
753/// - "buy_exact_sol_in" / "buy_exact_quote_in" -> DexEvent::PumpFunBuyExactSolIn
754/// - other/empty -> DexEvent::PumpFunTrade (backward compatible)
755#[inline(always)]
756pub fn parse_trade_from_data(
757    data: &[u8],
758    metadata: EventMetadata,
759    is_created_buy: bool,
760) -> Option<DexEvent> {
761    unsafe {
762        // 快速边界检查
763        if data.len() < 32 + 8 + 8 + 1 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 8 + 8 + 32 + 8 + 8 {
764            return None;
765        }
766
767        let mut offset = 0;
768
769        let mint = read_pubkey_unchecked(data, offset);
770        offset += 32;
771
772        let sol_amount = read_u64_unchecked(data, offset);
773        offset += 8;
774
775        let token_amount = read_u64_unchecked(data, offset);
776        offset += 8;
777
778        let is_buy = read_bool_unchecked(data, offset);
779        offset += 1;
780
781        let user = read_pubkey_unchecked(data, offset);
782        offset += 32;
783
784        let timestamp = read_i64_unchecked(data, offset);
785        offset += 8;
786
787        let virtual_sol_reserves = read_u64_unchecked(data, offset);
788        offset += 8;
789
790        let virtual_token_reserves = read_u64_unchecked(data, offset);
791        offset += 8;
792
793        let real_sol_reserves = read_u64_unchecked(data, offset);
794        offset += 8;
795
796        let real_token_reserves = read_u64_unchecked(data, offset);
797        offset += 8;
798
799        let fee_recipient = read_pubkey_unchecked(data, offset);
800        offset += 32;
801
802        let fee_basis_points = read_u64_unchecked(data, offset);
803        offset += 8;
804
805        let fee = read_u64_unchecked(data, offset);
806        offset += 8;
807
808        let creator = read_pubkey_unchecked(data, offset);
809        offset += 32;
810
811        let creator_fee_basis_points = read_u64_unchecked(data, offset);
812        offset += 8;
813
814        let creator_fee = read_u64_unchecked(data, offset);
815        offset += 8;
816
817        // 可选字段
818        let track_volume =
819            if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
820        offset += 1;
821
822        let total_unclaimed_tokens =
823            if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
824        offset += 8;
825
826        let total_claimed_tokens =
827            if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
828        offset += 8;
829
830        let current_sol_volume =
831            if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
832        offset += 8;
833
834        let last_update_timestamp =
835            if offset + 8 <= data.len() { read_i64_unchecked(data, offset) } else { 0 };
836        offset += 8;
837
838        let ix_name = if offset + 4 <= data.len() {
839            if let Some((s, len)) = read_str_unchecked(data, offset) {
840                offset += len;
841                s.to_string()
842            } else {
843                String::new()
844            }
845        } else {
846            String::new()
847        };
848
849        // mayhem_mode (1), cashback_fee_basis_points (8), cashback (8) - PUMP_CASHBACK_README
850        let mayhem_mode =
851            if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
852        offset += 1;
853        let cashback_fee_basis_points =
854            if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
855        offset += 8;
856        let cashback = if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
857        offset += 8;
858        let (
859            buyback_fee_basis_points,
860            buyback_fee,
861            shareholders,
862            quote_mint,
863            quote_amount,
864            virtual_quote_reserves,
865            real_quote_reserves,
866        ) = read_trade_event_extensions(data, &mut offset)?;
867
868        let trade_event = PumpFunTradeEvent {
869            metadata,
870            mint,
871            sol_amount,
872            token_amount,
873            is_buy,
874            is_created_buy,
875            user,
876            timestamp,
877            virtual_sol_reserves,
878            virtual_token_reserves,
879            real_sol_reserves,
880            real_token_reserves,
881            fee_recipient,
882            fee_basis_points,
883            fee,
884            creator,
885            creator_fee_basis_points,
886            creator_fee,
887            track_volume,
888            total_unclaimed_tokens,
889            total_claimed_tokens,
890            current_sol_volume,
891            last_update_timestamp,
892            ix_name: ix_name.clone(),
893            mayhem_mode,
894            cashback_fee_basis_points,
895            cashback,
896            buyback_fee_basis_points,
897            buyback_fee,
898            shareholders,
899            quote_mint,
900            quote_amount,
901            virtual_quote_reserves,
902            real_quote_reserves,
903            is_cashback_coin: cashback_fee_basis_points > 0,
904            amount: 0,
905            max_sol_cost: 0,
906            min_sol_output: 0,
907            spendable_sol_in: 0,
908            spendable_quote_in: 0,
909            min_tokens_out: 0,
910            global: Pubkey::default(),
911            bonding_curve: Pubkey::default(),
912            associated_bonding_curve: Pubkey::default(),
913            associated_user: Pubkey::default(),
914            system_program: Pubkey::default(),
915            creator_vault: Pubkey::default(),
916            event_authority: Pubkey::default(),
917            program: Pubkey::default(),
918            global_volume_accumulator: Pubkey::default(),
919            user_volume_accumulator: Pubkey::default(),
920            fee_config: Pubkey::default(),
921            fee_program: Pubkey::default(),
922            token_program: Pubkey::default(),
923            account: None,
924            ..Default::default()
925        };
926
927        // 根据 ix_name 返回不同的事件类型
928        match ix_name.as_str() {
929            "buy" | "buy_v2" => Some(DexEvent::PumpFunBuy(trade_event)),
930            "sell" | "sell_v2" => Some(DexEvent::PumpFunSell(trade_event)),
931            "buy_exact_sol_in" | "buy_exact_quote_in" | "buy_exact_quote_in_v2" => {
932                Some(DexEvent::PumpFunBuyExactSolIn(trade_event))
933            }
934            _ => Some(DexEvent::PumpFunTrade(trade_event)),
935        }
936    }
937}
938
939/// Parse only PumpFun Buy events from pre-decoded data
940///
941/// Returns None if the event is not a buy event
942#[inline(always)]
943pub fn parse_buy_from_data(
944    data: &[u8],
945    metadata: EventMetadata,
946    is_created_buy: bool,
947) -> Option<DexEvent> {
948    let event = parse_trade_from_data(data, metadata, is_created_buy)?;
949    match &event {
950        DexEvent::PumpFunBuy(_) => Some(event),
951        _ => None,
952    }
953}
954
955/// Parse only PumpFun Sell events from pre-decoded data
956///
957/// Returns None if the event is not a sell event
958#[inline(always)]
959pub fn parse_sell_from_data(
960    data: &[u8],
961    metadata: EventMetadata,
962    is_created_buy: bool,
963) -> Option<DexEvent> {
964    let event = parse_trade_from_data(data, metadata, is_created_buy)?;
965    match &event {
966        DexEvent::PumpFunSell(_) => Some(event),
967        _ => None,
968    }
969}
970
971/// Parse only PumpFun BuyExactSolIn events from pre-decoded data
972///
973/// Returns None if the event is not a buy_exact_sol_in event
974#[inline(always)]
975pub fn parse_buy_exact_sol_in_from_data(
976    data: &[u8],
977    metadata: EventMetadata,
978    is_created_buy: bool,
979) -> Option<DexEvent> {
980    let event = parse_trade_from_data(data, metadata, is_created_buy)?;
981    match &event {
982        DexEvent::PumpFunBuyExactSolIn(_) => Some(event),
983        _ => None,
984    }
985}
986
987/// Parse PumpFun Create event from pre-decoded data
988#[inline(always)]
989pub fn parse_create_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
990    unsafe {
991        let mut offset = 0;
992
993        let (name, name_len) = read_str_unchecked(data, offset)?;
994        offset += name_len;
995
996        let (symbol, symbol_len) = read_str_unchecked(data, offset)?;
997        offset += symbol_len;
998
999        let (uri, uri_len) = read_str_unchecked(data, offset)?;
1000        offset += uri_len;
1001
1002        if data.len() < offset + 32 + 32 + 32 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 1 {
1003            return None;
1004        }
1005
1006        let mint = read_pubkey_unchecked(data, offset);
1007        offset += 32;
1008
1009        let bonding_curve = read_pubkey_unchecked(data, offset);
1010        offset += 32;
1011
1012        let user = read_pubkey_unchecked(data, offset);
1013        offset += 32;
1014
1015        let creator = read_pubkey_unchecked(data, offset);
1016        offset += 32;
1017
1018        let timestamp = read_i64_unchecked(data, offset);
1019        offset += 8;
1020
1021        let virtual_token_reserves = read_u64_unchecked(data, offset);
1022        offset += 8;
1023
1024        let virtual_sol_reserves = read_u64_unchecked(data, offset);
1025        offset += 8;
1026
1027        let real_token_reserves = read_u64_unchecked(data, offset);
1028        offset += 8;
1029
1030        let token_total_supply = read_u64_unchecked(data, offset);
1031        offset += 8;
1032
1033        let token_program = if offset + 32 <= data.len() {
1034            read_pubkey_unchecked(data, offset)
1035        } else {
1036            Pubkey::default()
1037        };
1038        offset += 32;
1039
1040        let is_mayhem_mode =
1041            if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
1042        offset += 1;
1043        let is_cashback_enabled =
1044            if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
1045
1046        Some(DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
1047            metadata,
1048            name: name.to_string(),
1049            symbol: symbol.to_string(),
1050            uri: uri.to_string(),
1051            mint,
1052            bonding_curve,
1053            user,
1054            creator,
1055            timestamp,
1056            virtual_token_reserves,
1057            virtual_sol_reserves,
1058            real_token_reserves,
1059            token_total_supply,
1060            token_program,
1061            is_mayhem_mode,
1062            is_cashback_enabled,
1063        }))
1064    }
1065}
1066
1067/// Parse PumpFun Migrate event from pre-decoded data
1068#[inline(always)]
1069pub fn parse_migrate_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
1070    unsafe {
1071        if data.len() < 32 + 32 + 8 + 8 + 8 + 32 + 8 + 32 {
1072            return None;
1073        }
1074
1075        let mut offset = 0;
1076
1077        let user = read_pubkey_unchecked(data, offset);
1078        offset += 32;
1079
1080        let mint = read_pubkey_unchecked(data, offset);
1081        offset += 32;
1082
1083        let mint_amount = read_u64_unchecked(data, offset);
1084        offset += 8;
1085
1086        let sol_amount = read_u64_unchecked(data, offset);
1087        offset += 8;
1088
1089        let pool_migration_fee = read_u64_unchecked(data, offset);
1090        offset += 8;
1091
1092        let bonding_curve = read_pubkey_unchecked(data, offset);
1093        offset += 32;
1094
1095        let timestamp = read_i64_unchecked(data, offset);
1096        offset += 8;
1097
1098        let pool = read_pubkey_unchecked(data, offset);
1099
1100        Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
1101            metadata,
1102            user,
1103            mint,
1104            mint_amount,
1105            sol_amount,
1106            pool_migration_fee,
1107            bonding_curve,
1108            timestamp,
1109            pool,
1110        }))
1111    }
1112}
1113
1114/// `migrateBondingCurveCreatorEvent`:`data` 为去掉 8 字节 discriminator 之后的 Borsh 体。
1115#[inline(always)]
1116pub fn parse_migrate_bonding_curve_creator_from_data(
1117    data: &[u8],
1118    metadata: EventMetadata,
1119) -> Option<DexEvent> {
1120    unsafe {
1121        const NEED: usize = 8 + 32 * 5;
1122        if data.len() < NEED {
1123            return None;
1124        }
1125
1126        let mut offset = 0usize;
1127        let timestamp = read_i64_unchecked(data, offset);
1128        offset += 8;
1129        let mint = read_pubkey_unchecked(data, offset);
1130        offset += 32;
1131        let bonding_curve = read_pubkey_unchecked(data, offset);
1132        offset += 32;
1133        let sharing_config = read_pubkey_unchecked(data, offset);
1134        offset += 32;
1135        let old_creator = read_pubkey_unchecked(data, offset);
1136        offset += 32;
1137        let new_creator = read_pubkey_unchecked(data, offset);
1138
1139        Some(DexEvent::PumpFunMigrateBondingCurveCreator(PumpFunMigrateBondingCurveCreatorEvent {
1140            metadata,
1141            timestamp,
1142            mint,
1143            bonding_curve,
1144            sharing_config,
1145            old_creator,
1146            new_creator,
1147        }))
1148    }
1149}
1150
1151/// `createFeeSharingConfigEvent`:委托 [`pump_fees::parse_create_fee_sharing_config_from_data`](crate::logs::pump_fees)。
1152#[inline]
1153pub fn parse_create_fee_sharing_config_from_data(
1154    data: &[u8],
1155    metadata: EventMetadata,
1156) -> Option<DexEvent> {
1157    crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(data, metadata)
1158}
1159
1160#[inline(always)]
1161fn read_i64_at(data: &[u8], o: &mut usize) -> Option<i64> {
1162    if data.len() < *o + 8 {
1163        return None;
1164    }
1165    let v = i64::from_le_bytes(data[*o..*o + 8].try_into().ok()?);
1166    *o += 8;
1167    Some(v)
1168}
1169
1170#[inline(always)]
1171fn read_u16_at(data: &[u8], o: &mut usize) -> Option<u16> {
1172    if data.len() < *o + 2 {
1173        return None;
1174    }
1175    let v = u16::from_le_bytes(data[*o..*o + 2].try_into().ok()?);
1176    *o += 2;
1177    Some(v)
1178}
1179
1180#[inline(always)]
1181fn read_u32_at(data: &[u8], o: &mut usize) -> Option<u32> {
1182    if data.len() < *o + 4 {
1183        return None;
1184    }
1185    let v = u32::from_le_bytes(data[*o..*o + 4].try_into().ok()?);
1186    *o += 4;
1187    Some(v)
1188}
1189
1190#[inline(always)]
1191fn read_pubkey_at(data: &[u8], o: &mut usize) -> Option<Pubkey> {
1192    if data.len() < *o + 32 {
1193        return None;
1194    }
1195    let pk = Pubkey::new_from_array(data[*o..*o + 32].try_into().ok()?);
1196    *o += 32;
1197    Some(pk)
1198}
1199
1200// ============================================================================
1201// 性能统计 API (可选)
1202// ============================================================================
1203
1204#[cfg(feature = "perf-stats")]
1205pub fn get_perf_stats() -> (usize, usize) {
1206    let count = PARSE_COUNT.load(Ordering::Relaxed);
1207    let total_ns = PARSE_TIME_NS.load(Ordering::Relaxed);
1208    (count, total_ns)
1209}
1210
1211#[cfg(feature = "perf-stats")]
1212pub fn reset_perf_stats() {
1213    PARSE_COUNT.store(0, Ordering::Relaxed);
1214    PARSE_TIME_NS.store(0, Ordering::Relaxed);
1215}
1216
1217#[cfg(test)]
1218mod tests {
1219    use super::*;
1220    use crate::core::events::{DexEvent, EventMetadata};
1221
1222    #[test]
1223    fn test_discriminator_simd() {
1224        // 测试 SIMD discriminator 提取
1225        let log = "Program data: G3Kp5Dfe605nAAAAAAAAAAA=";
1226        let disc = extract_discriminator_simd(log);
1227        assert!(disc.is_some());
1228    }
1229
1230    #[test]
1231    fn test_parse_performance() {
1232        // 性能测试
1233        let log = "Program data: G3Kp5Dfe605nAAAAAAAAAAA=";
1234        let sig = Signature::default();
1235
1236        let start = std::time::Instant::now();
1237        for _ in 0..1000 {
1238            let _ = parse_log(log, sig, 0, 0, Some(0), 0, false);
1239        }
1240        let elapsed = start.elapsed();
1241
1242        println!("Average parse time: {} ns", elapsed.as_nanos() / 1000);
1243    }
1244
1245    #[test]
1246    fn migrate_bonding_curve_creator_roundtrip_from_data() {
1247        let ts: i64 = 1_777_920_719;
1248        let mint = Pubkey::new_unique();
1249        let bonding_curve = Pubkey::new_unique();
1250        let sharing_config = Pubkey::new_unique();
1251        let old_creator = Pubkey::new_unique();
1252        let new_creator = Pubkey::new_unique();
1253
1254        let mut buf = Vec::with_capacity(200);
1255        buf.extend_from_slice(&ts.to_le_bytes());
1256        buf.extend_from_slice(mint.as_ref());
1257        buf.extend_from_slice(bonding_curve.as_ref());
1258        buf.extend_from_slice(sharing_config.as_ref());
1259        buf.extend_from_slice(old_creator.as_ref());
1260        buf.extend_from_slice(new_creator.as_ref());
1261
1262        let metadata = EventMetadata {
1263            signature: Signature::default(),
1264            slot: 0,
1265            tx_index: 0,
1266            block_time_us: 0,
1267            grpc_recv_us: 0,
1268            recent_blockhash: None,
1269        };
1270
1271        let ev = parse_migrate_bonding_curve_creator_from_data(&buf, metadata).expect("parse");
1272        match ev {
1273            DexEvent::PumpFunMigrateBondingCurveCreator(e) => {
1274                assert_eq!(e.timestamp, ts);
1275                assert_eq!(e.mint, mint);
1276                assert_eq!(e.bonding_curve, bonding_curve);
1277                assert_eq!(e.sharing_config, sharing_config);
1278                assert_eq!(e.old_creator, old_creator);
1279                assert_eq!(e.new_creator, new_creator);
1280            }
1281            _ => panic!("wrong variant"),
1282        }
1283    }
1284}