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