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