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
6
7use crate::core::events::*;
8use solana_sdk::{pubkey::Pubkey, signature::Signature};
9
10use memchr::memmem;
11use once_cell::sync::Lazy;
12
13#[cfg(feature = "perf-stats")]
14use std::sync::atomic::{AtomicUsize, Ordering};
15
16
17#[cfg(feature = "perf-stats")]
18pub static PARSE_COUNT: AtomicUsize = AtomicUsize::new(0);
19#[cfg(feature = "perf-stats")]
20pub static PARSE_TIME_NS: AtomicUsize = AtomicUsize::new(0);
21
22// --- discriminators ------------------------------------------------
23
24pub const CREATE_EVENT: u64 = u64::from_le_bytes([27, 114, 169, 77, 222, 235, 99, 118]);
25pub const TRADE_EVENT: u64 = u64::from_le_bytes([189, 219, 127, 211, 78, 230, 97, 238]);
26pub const MIGRATE_EVENT: u64 = u64::from_le_bytes([189, 233, 93, 185, 92, 148, 234, 148]);
27/// `createFeeSharingConfigEvent`(pump-fees IDL)
28pub const CREATE_FEE_SHARING_CONFIG_EVENT: u64 =
29    crate::logs::pump_fees::discriminant_u64(&crate::logs::pump_fees::CREATE_FEE_SHARING_CONFIG_EVENT_DISC);
30/// `migrateBondingCurveCreatorEvent`(pump.fun IDL)
31pub const MIGRATE_BONDING_CURVE_CREATOR_EVENT: u64 =
32    u64::from_le_bytes([155, 167, 104, 220, 213, 108, 243, 3]);
33
34// --- binary_read ---------------------------------------------------
35
36#[inline(always)]
37pub unsafe fn read_u64_unchecked(data: &[u8], offset: usize) -> u64 {
38    let ptr = data.as_ptr().add(offset) as *const u64;
39    u64::from_le(ptr.read_unaligned())
40}
41
42#[inline(always)]
43pub unsafe fn read_i64_unchecked(data: &[u8], offset: usize) -> i64 {
44    let ptr = data.as_ptr().add(offset) as *const i64;
45    i64::from_le(ptr.read_unaligned())
46}
47
48#[inline(always)]
49pub unsafe fn read_bool_unchecked(data: &[u8], offset: usize) -> bool {
50    *data.get_unchecked(offset) == 1
51}
52
53#[inline(always)]
54pub unsafe fn read_pubkey_unchecked(data: &[u8], offset: usize) -> Pubkey {
55    #[cfg(target_arch = "x86_64")]
56    {
57        use std::arch::x86_64::_mm_prefetch;
58        use std::arch::x86_64::_MM_HINT_T0;
59        if offset + 64 < data.len() {
60            _mm_prefetch((data.as_ptr().add(offset + 32)) as *const i8, _MM_HINT_T0);
61        }
62    }
63
64    let ptr = data.as_ptr().add(offset);
65    let mut bytes = [0u8; 32];
66    std::ptr::copy_nonoverlapping(ptr, bytes.as_mut_ptr(), 32);
67    Pubkey::new_from_array(bytes)
68}
69
70#[inline(always)]
71pub unsafe fn read_str_unchecked(data: &[u8], offset: usize) -> Option<(&str, usize)> {
72    if data.len() < offset + 4 {
73        return None;
74    }
75
76    let len = read_u32_unchecked(data, offset) as usize;
77    if data.len() < offset + 4 + len {
78        return None;
79    }
80
81    let string_bytes = &data[offset + 4..offset + 4 + len];
82    let s = std::str::from_utf8_unchecked(string_bytes);
83    Some((s, 4 + len))
84}
85
86#[inline(always)]
87pub unsafe fn read_u32_unchecked(data: &[u8], offset: usize) -> u32 {
88    let ptr = data.as_ptr().add(offset) as *const u32;
89    u32::from_le(ptr.read_unaligned())
90}
91
92// --- log_decode ----------------------------------------------------
93
94static BASE64_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"Program data: "));
95/// `b"Program data: "`.len() — base64 payload starts immediately after this tag.
96const PROGRAM_DATA_TAG_LEN: usize = 14;
97
98#[inline(always)]
99pub fn extract_program_data_zero_copy<'a>(log: &'a str, buf: &'a mut [u8; 2048]) -> Option<&'a [u8]> {
100    let log_bytes = log.as_bytes();
101    let pos = BASE64_FINDER.find(log_bytes)?;
102
103    let data_part = &log[pos + PROGRAM_DATA_TAG_LEN..];
104    let trimmed = data_part.trim();
105
106    if trimmed.len() > 2700 {
107        return None;
108    }
109
110    use base64_simd::AsOut;
111    let decoded_slice =
112        base64_simd::STANDARD.decode(trimmed.as_bytes(), buf.as_mut().as_out()).ok()?;
113
114    Some(decoded_slice)
115}
116
117#[inline(always)]
118pub fn extract_discriminator_simd(log: &str) -> Option<u64> {
119    let log_bytes = log.as_bytes();
120    let pos = BASE64_FINDER.find(log_bytes)?;
121
122    let data_part = &log[pos + PROGRAM_DATA_TAG_LEN..];
123    let trimmed = data_part.trim();
124
125    if trimmed.len() < 12 {
126        return None;
127    }
128
129    use base64_simd::AsOut;
130    let mut buf = [0u8; 12];
131    base64_simd::STANDARD.decode(&trimmed.as_bytes()[..16], buf.as_mut().as_out()).ok()?;
132
133    unsafe {
134        let ptr = buf.as_ptr() as *const u64;
135        Some(ptr.read_unaligned())
136    }
137}
138
139// --- main parser ---------------------------------------------------
140/// 主解析函数 (极限优化版本)
141///
142/// 性能目标: <100ns
143#[inline(always)]
144pub fn parse_log(
145    log: &str,
146    signature: Signature,
147    slot: u64,
148    tx_index: u64,
149    block_time_us: Option<i64>,
150    grpc_recv_us: i64,
151    is_created_buy: bool,
152) -> Option<DexEvent> {
153    #[cfg(feature = "perf-stats")]
154    let start = std::time::Instant::now();
155
156    // 使用栈分配的缓冲区 (增加到 2KB 以防止 base64-simd 缓冲区溢出)
157    let mut buf = [0u8; 2048];
158    let program_data = extract_program_data_zero_copy(log, &mut buf)?;
159
160    if program_data.len() < 8 {
161        return None;
162    }
163
164    // 使用 unsafe 读取 discriminator (SIMD 优化)
165    let discriminator = unsafe { read_u64_unchecked(program_data, 0) };
166    let data = &program_data[8..];
167
168    let result = match discriminator {
169        CREATE_EVENT => parse_create_event_optimized(
170            data,
171            signature,
172            slot,
173            tx_index,
174            block_time_us,
175            grpc_recv_us,
176        ),
177        TRADE_EVENT => parse_trade_event_optimized(
178            data,
179            signature,
180            slot,
181            tx_index,
182            block_time_us,
183            grpc_recv_us,
184            is_created_buy,
185        ),
186        MIGRATE_EVENT => parse_migrate_event_optimized(
187            data,
188            signature,
189            slot,
190            tx_index,
191            block_time_us,
192            grpc_recv_us,
193        ),
194        CREATE_FEE_SHARING_CONFIG_EVENT => parse_create_fee_sharing_config_event_optimized(
195            data,
196            signature,
197            slot,
198            tx_index,
199            block_time_us,
200            grpc_recv_us,
201        ),
202        MIGRATE_BONDING_CURVE_CREATOR_EVENT => {
203            parse_migrate_bonding_curve_creator_event_optimized(
204                data,
205                signature,
206                slot,
207                tx_index,
208                block_time_us,
209                grpc_recv_us,
210            )
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" => Some(DexEvent::PumpFunBuy(trade_event)),
495            "sell" => Some(DexEvent::PumpFunSell(trade_event)),
496            "buy_exact_sol_in" => Some(DexEvent::PumpFunBuyExactSolIn(trade_event)),
497            _ => Some(DexEvent::PumpFunTrade(trade_event)), // 兼容旧版本或未知类型
498        }
499    }
500}
501
502/// 解析 MigrateEvent (极限优化)
503#[inline(always)]
504fn parse_migrate_event_optimized(
505    data: &[u8],
506    signature: Signature,
507    slot: u64,
508    tx_index: u64,
509    block_time_us: Option<i64>,
510    grpc_recv_us: i64,
511) -> Option<DexEvent> {
512    unsafe {
513        // 快速边界检查
514        if data.len() < 32 + 32 + 8 + 8 + 8 + 32 + 8 + 32 {
515            return None;
516        }
517
518        let mut offset = 0;
519
520        let user = read_pubkey_unchecked(data, offset);
521        offset += 32;
522
523        let mint = read_pubkey_unchecked(data, offset);
524        offset += 32;
525
526        let mint_amount = read_u64_unchecked(data, offset);
527        offset += 8;
528
529        let sol_amount = read_u64_unchecked(data, offset);
530        offset += 8;
531
532        let pool_migration_fee = read_u64_unchecked(data, offset);
533        offset += 8;
534
535        let bonding_curve = read_pubkey_unchecked(data, offset);
536        offset += 32;
537
538        let timestamp = read_i64_unchecked(data, offset);
539        offset += 8;
540
541        let pool = read_pubkey_unchecked(data, offset);
542
543        let metadata = EventMetadata {
544            signature,
545            slot,
546            tx_index,
547            block_time_us: block_time_us.unwrap_or(0),
548            grpc_recv_us,
549            recent_blockhash: None,
550        };
551
552        Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
553            metadata,
554            user,
555            mint,
556            mint_amount,
557            sol_amount,
558            pool_migration_fee,
559            bonding_curve,
560            timestamp,
561            pool,
562        }))
563    }
564}
565
566#[inline(always)]
567fn parse_migrate_bonding_curve_creator_event_optimized(
568    data: &[u8],
569    signature: Signature,
570    slot: u64,
571    tx_index: u64,
572    block_time_us: Option<i64>,
573    grpc_recv_us: i64,
574) -> Option<DexEvent> {
575    let metadata = EventMetadata {
576        signature,
577        slot,
578        tx_index,
579        block_time_us: block_time_us.unwrap_or(0),
580        grpc_recv_us,
581        recent_blockhash: None,
582    };
583    parse_migrate_bonding_curve_creator_from_data(data, metadata)
584}
585
586#[inline(always)]
587fn parse_create_fee_sharing_config_event_optimized(
588    data: &[u8],
589    signature: Signature,
590    slot: u64,
591    tx_index: u64,
592    block_time_us: Option<i64>,
593    grpc_recv_us: i64,
594) -> Option<DexEvent> {
595    let metadata = EventMetadata {
596        signature,
597        slot,
598        tx_index,
599        block_time_us: block_time_us.unwrap_or(0),
600        grpc_recv_us,
601        recent_blockhash: None,
602    };
603    crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(data, metadata)
604}
605
606// ============================================================================
607// 快速过滤 API (用于事件过滤场景)
608// ============================================================================
609
610/// 快速判断事件类型 (只解析 discriminator)
611///
612/// 性能: <50ns
613#[inline(always)]
614pub fn get_event_type_fast(log: &str) -> Option<u64> {
615    extract_discriminator_simd(log)
616}
617
618/// 检查是否为特定事件类型 (SIMD 优化)
619#[inline(always)]
620pub fn is_event_type(log: &str, discriminator: u64) -> bool {
621    extract_discriminator_simd(log) == Some(discriminator)
622}
623
624// ============================================================================
625// Public API for optimized parsing from pre-decoded data
626// These functions accept already-decoded data (without discriminator)
627// ============================================================================
628
629/// Parse PumpFun Trade event from pre-decoded data
630///
631/// `data` should be the decoded bytes AFTER the 8-byte discriminator
632///
633/// Returns different event types based on ix_name:
634/// - "buy" -> DexEvent::PumpFunBuy
635/// - "sell" -> DexEvent::PumpFunSell
636/// - "buy_exact_sol_in" -> DexEvent::PumpFunBuyExactSolIn
637/// - other/empty -> DexEvent::PumpFunTrade (backward compatible)
638#[inline(always)]
639pub fn parse_trade_from_data(
640    data: &[u8],
641    metadata: EventMetadata,
642    is_created_buy: bool,
643) -> Option<DexEvent> {
644    unsafe {
645        // 快速边界检查
646        if data.len() < 32 + 8 + 8 + 1 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 8 + 8 + 32 + 8 + 8 {
647            return None;
648        }
649
650        let mut offset = 0;
651
652        let mint = read_pubkey_unchecked(data, offset);
653        offset += 32;
654
655        let sol_amount = read_u64_unchecked(data, offset);
656        offset += 8;
657
658        let token_amount = read_u64_unchecked(data, offset);
659        offset += 8;
660
661        let is_buy = read_bool_unchecked(data, offset);
662        offset += 1;
663
664        let user = read_pubkey_unchecked(data, offset);
665        offset += 32;
666
667        let timestamp = read_i64_unchecked(data, offset);
668        offset += 8;
669
670        let virtual_sol_reserves = read_u64_unchecked(data, offset);
671        offset += 8;
672
673        let virtual_token_reserves = read_u64_unchecked(data, offset);
674        offset += 8;
675
676        let real_sol_reserves = read_u64_unchecked(data, offset);
677        offset += 8;
678
679        let real_token_reserves = read_u64_unchecked(data, offset);
680        offset += 8;
681
682        let fee_recipient = read_pubkey_unchecked(data, offset);
683        offset += 32;
684
685        let fee_basis_points = read_u64_unchecked(data, offset);
686        offset += 8;
687
688        let fee = read_u64_unchecked(data, offset);
689        offset += 8;
690
691        let creator = read_pubkey_unchecked(data, offset);
692        offset += 32;
693
694        let creator_fee_basis_points = read_u64_unchecked(data, offset);
695        offset += 8;
696
697        let creator_fee = read_u64_unchecked(data, offset);
698        offset += 8;
699
700        // 可选字段
701        let track_volume =
702            if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
703        offset += 1;
704
705        let total_unclaimed_tokens =
706            if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
707        offset += 8;
708
709        let total_claimed_tokens =
710            if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
711        offset += 8;
712
713        let current_sol_volume =
714            if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
715        offset += 8;
716
717        let last_update_timestamp =
718            if offset + 8 <= data.len() { read_i64_unchecked(data, offset) } else { 0 };
719        offset += 8;
720
721        let ix_name = if offset + 4 <= data.len() {
722            if let Some((s, len)) = read_str_unchecked(data, offset) {
723                offset += len;
724                s.to_string()
725            } else {
726                String::new()
727            }
728        } else {
729            String::new()
730        };
731
732        // mayhem_mode (1), cashback_fee_basis_points (8), cashback (8) - PUMP_CASHBACK_README
733        let mayhem_mode =
734            if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
735        offset += 1;
736        let cashback_fee_basis_points =
737            if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
738        offset += 8;
739        let cashback = if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
740
741        let trade_event = PumpFunTradeEvent {
742            metadata,
743            mint,
744            sol_amount,
745            token_amount,
746            is_buy,
747            is_created_buy,
748            user,
749            timestamp,
750            virtual_sol_reserves,
751            virtual_token_reserves,
752            real_sol_reserves,
753            real_token_reserves,
754            fee_recipient,
755            fee_basis_points,
756            fee,
757            creator,
758            creator_fee_basis_points,
759            creator_fee,
760            track_volume,
761            total_unclaimed_tokens,
762            total_claimed_tokens,
763            current_sol_volume,
764            last_update_timestamp,
765            ix_name: ix_name.clone(),
766            mayhem_mode,
767            cashback_fee_basis_points,
768            cashback,
769            is_cashback_coin: cashback_fee_basis_points > 0,
770            bonding_curve: Pubkey::default(),
771            associated_bonding_curve: Pubkey::default(),
772            creator_vault: Pubkey::default(),
773            token_program: Pubkey::default(),
774            account: None,
775        };
776
777        // 根据 ix_name 返回不同的事件类型
778        match ix_name.as_str() {
779            "buy" => Some(DexEvent::PumpFunBuy(trade_event)),
780            "sell" => Some(DexEvent::PumpFunSell(trade_event)),
781            "buy_exact_sol_in" => Some(DexEvent::PumpFunBuyExactSolIn(trade_event)),
782            _ => Some(DexEvent::PumpFunTrade(trade_event)),
783        }
784    }
785}
786
787/// Parse only PumpFun Buy events from pre-decoded data
788///
789/// Returns None if the event is not a buy event
790#[inline(always)]
791pub fn parse_buy_from_data(
792    data: &[u8],
793    metadata: EventMetadata,
794    is_created_buy: bool,
795) -> Option<DexEvent> {
796    let event = parse_trade_from_data(data, metadata, is_created_buy)?;
797    match &event {
798        DexEvent::PumpFunBuy(_) => Some(event),
799        _ => None,
800    }
801}
802
803/// Parse only PumpFun Sell events from pre-decoded data
804///
805/// Returns None if the event is not a sell event
806#[inline(always)]
807pub fn parse_sell_from_data(
808    data: &[u8],
809    metadata: EventMetadata,
810    is_created_buy: bool,
811) -> Option<DexEvent> {
812    let event = parse_trade_from_data(data, metadata, is_created_buy)?;
813    match &event {
814        DexEvent::PumpFunSell(_) => Some(event),
815        _ => None,
816    }
817}
818
819/// Parse only PumpFun BuyExactSolIn events from pre-decoded data
820///
821/// Returns None if the event is not a buy_exact_sol_in event
822#[inline(always)]
823pub fn parse_buy_exact_sol_in_from_data(
824    data: &[u8],
825    metadata: EventMetadata,
826    is_created_buy: bool,
827) -> Option<DexEvent> {
828    let event = parse_trade_from_data(data, metadata, is_created_buy)?;
829    match &event {
830        DexEvent::PumpFunBuyExactSolIn(_) => Some(event),
831        _ => None,
832    }
833}
834
835/// Parse PumpFun Create event from pre-decoded data
836#[inline(always)]
837pub fn parse_create_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
838    unsafe {
839        let mut offset = 0;
840
841        let (name, name_len) = read_str_unchecked(data, offset)?;
842        offset += name_len;
843
844        let (symbol, symbol_len) = read_str_unchecked(data, offset)?;
845        offset += symbol_len;
846
847        let (uri, uri_len) = read_str_unchecked(data, offset)?;
848        offset += uri_len;
849
850        if data.len() < offset + 32 + 32 + 32 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 1 {
851            return None;
852        }
853
854        let mint = read_pubkey_unchecked(data, offset);
855        offset += 32;
856
857        let bonding_curve = read_pubkey_unchecked(data, offset);
858        offset += 32;
859
860        let user = read_pubkey_unchecked(data, offset);
861        offset += 32;
862
863        let creator = read_pubkey_unchecked(data, offset);
864        offset += 32;
865
866        let timestamp = read_i64_unchecked(data, offset);
867        offset += 8;
868
869        let virtual_token_reserves = read_u64_unchecked(data, offset);
870        offset += 8;
871
872        let virtual_sol_reserves = read_u64_unchecked(data, offset);
873        offset += 8;
874
875        let real_token_reserves = read_u64_unchecked(data, offset);
876        offset += 8;
877
878        let token_total_supply = read_u64_unchecked(data, offset);
879        offset += 8;
880
881        let token_program = if offset + 32 <= data.len() {
882            read_pubkey_unchecked(data, offset)
883        } else {
884            Pubkey::default()
885        };
886        offset += 32;
887
888        let is_mayhem_mode =
889            if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
890        offset += 1;
891        let is_cashback_enabled =
892            if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
893
894        Some(DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
895            metadata,
896            name: name.to_string(),
897            symbol: symbol.to_string(),
898            uri: uri.to_string(),
899            mint,
900            bonding_curve,
901            user,
902            creator,
903            timestamp,
904            virtual_token_reserves,
905            virtual_sol_reserves,
906            real_token_reserves,
907            token_total_supply,
908            token_program,
909            is_mayhem_mode,
910            is_cashback_enabled,
911        }))
912    }
913}
914
915/// Parse PumpFun Migrate event from pre-decoded data
916#[inline(always)]
917pub fn parse_migrate_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
918    unsafe {
919        if data.len() < 32 + 32 + 8 + 8 + 8 + 32 + 8 + 32 {
920            return None;
921        }
922
923        let mut offset = 0;
924
925        let user = read_pubkey_unchecked(data, offset);
926        offset += 32;
927
928        let mint = read_pubkey_unchecked(data, offset);
929        offset += 32;
930
931        let mint_amount = read_u64_unchecked(data, offset);
932        offset += 8;
933
934        let sol_amount = read_u64_unchecked(data, offset);
935        offset += 8;
936
937        let pool_migration_fee = read_u64_unchecked(data, offset);
938        offset += 8;
939
940        let bonding_curve = read_pubkey_unchecked(data, offset);
941        offset += 32;
942
943        let timestamp = read_i64_unchecked(data, offset);
944        offset += 8;
945
946        let pool = read_pubkey_unchecked(data, offset);
947
948        Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
949            metadata,
950            user,
951            mint,
952            mint_amount,
953            sol_amount,
954            pool_migration_fee,
955            bonding_curve,
956            timestamp,
957            pool,
958        }))
959    }
960}
961
962/// `migrateBondingCurveCreatorEvent`:`data` 为去掉 8 字节 discriminator 之后的 Borsh 体。
963#[inline(always)]
964pub fn parse_migrate_bonding_curve_creator_from_data(
965    data: &[u8],
966    metadata: EventMetadata,
967) -> Option<DexEvent> {
968    unsafe {
969        const NEED: usize = 8 + 32 * 5;
970        if data.len() < NEED {
971            return None;
972        }
973
974        let mut offset = 0usize;
975        let timestamp = read_i64_unchecked(data, offset);
976        offset += 8;
977        let mint = read_pubkey_unchecked(data, offset);
978        offset += 32;
979        let bonding_curve = read_pubkey_unchecked(data, offset);
980        offset += 32;
981        let sharing_config = read_pubkey_unchecked(data, offset);
982        offset += 32;
983        let old_creator = read_pubkey_unchecked(data, offset);
984        offset += 32;
985        let new_creator = read_pubkey_unchecked(data, offset);
986
987        Some(DexEvent::PumpFunMigrateBondingCurveCreator(
988            PumpFunMigrateBondingCurveCreatorEvent {
989                metadata,
990                timestamp,
991                mint,
992                bonding_curve,
993                sharing_config,
994                old_creator,
995                new_creator,
996            },
997        ))
998    }
999}
1000
1001/// `createFeeSharingConfigEvent`:委托 [`pump_fees::parse_create_fee_sharing_config_from_data`](crate::logs::pump_fees)。
1002#[inline]
1003pub fn parse_create_fee_sharing_config_from_data(
1004    data: &[u8],
1005    metadata: EventMetadata,
1006) -> Option<DexEvent> {
1007    crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(data, metadata)
1008}
1009
1010#[inline(always)]
1011fn read_i64_at(data: &[u8], o: &mut usize) -> Option<i64> {
1012    if data.len() < *o + 8 {
1013        return None;
1014    }
1015    let v =
1016        i64::from_le_bytes(data[*o..*o + 8].try_into().ok()?);
1017    *o += 8;
1018    Some(v)
1019}
1020
1021#[inline(always)]
1022fn read_u16_at(data: &[u8], o: &mut usize) -> Option<u16> {
1023    if data.len() < *o + 2 {
1024        return None;
1025    }
1026    let v =
1027        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 =
1038        u32::from_le_bytes(data[*o..*o + 4].try_into().ok()?);
1039    *o += 4;
1040    Some(v)
1041}
1042
1043#[inline(always)]
1044fn read_pubkey_at(data: &[u8], o: &mut usize) -> Option<Pubkey> {
1045    if data.len() < *o + 32 {
1046        return None;
1047    }
1048    let pk = Pubkey::new_from_array(data[*o..*o + 32].try_into().ok()?);
1049    *o += 32;
1050    Some(pk)
1051}
1052
1053// ============================================================================
1054// 性能统计 API (可选)
1055// ============================================================================
1056
1057#[cfg(feature = "perf-stats")]
1058pub fn get_perf_stats() -> (usize, usize) {
1059    let count = PARSE_COUNT.load(Ordering::Relaxed);
1060    let total_ns = PARSE_TIME_NS.load(Ordering::Relaxed);
1061    (count, total_ns)
1062}
1063
1064#[cfg(feature = "perf-stats")]
1065pub fn reset_perf_stats() {
1066    PARSE_COUNT.store(0, Ordering::Relaxed);
1067    PARSE_TIME_NS.store(0, Ordering::Relaxed);
1068}
1069
1070#[cfg(test)]
1071mod tests {
1072    use super::*;
1073    use crate::core::events::{DexEvent, EventMetadata};
1074
1075    #[test]
1076    fn test_discriminator_simd() {
1077        // 测试 SIMD discriminator 提取
1078        let log = "Program data: G3Kp5Dfe605nAAAAAAAAAAA=";
1079        let disc = extract_discriminator_simd(log);
1080        assert!(disc.is_some());
1081    }
1082
1083    #[test]
1084    fn test_parse_performance() {
1085        // 性能测试
1086        let log = "Program data: G3Kp5Dfe605nAAAAAAAAAAA=";
1087        let sig = Signature::default();
1088
1089        let start = std::time::Instant::now();
1090        for _ in 0..1000 {
1091            let _ = parse_log(log, sig, 0, 0, Some(0), 0, false);
1092        }
1093        let elapsed = start.elapsed();
1094
1095        println!("Average parse time: {} ns", elapsed.as_nanos() / 1000);
1096    }
1097
1098    #[test]
1099    fn migrate_bonding_curve_creator_roundtrip_from_data() {
1100        let ts: i64 = 1_777_920_719;
1101        let mint = Pubkey::new_unique();
1102        let bonding_curve = Pubkey::new_unique();
1103        let sharing_config = Pubkey::new_unique();
1104        let old_creator = Pubkey::new_unique();
1105        let new_creator = Pubkey::new_unique();
1106
1107        let mut buf = Vec::with_capacity(200);
1108        buf.extend_from_slice(&ts.to_le_bytes());
1109        buf.extend_from_slice(mint.as_ref());
1110        buf.extend_from_slice(bonding_curve.as_ref());
1111        buf.extend_from_slice(sharing_config.as_ref());
1112        buf.extend_from_slice(old_creator.as_ref());
1113        buf.extend_from_slice(new_creator.as_ref());
1114
1115        let metadata = EventMetadata {
1116            signature: Signature::default(),
1117            slot: 0,
1118            tx_index: 0,
1119            block_time_us: 0,
1120            grpc_recv_us: 0,
1121            recent_blockhash: None,
1122        };
1123
1124        let ev = parse_migrate_bonding_curve_creator_from_data(&buf, metadata).expect("parse");
1125        match ev {
1126            DexEvent::PumpFunMigrateBondingCurveCreator(e) => {
1127                assert_eq!(e.timestamp, ts);
1128                assert_eq!(e.mint, mint);
1129                assert_eq!(e.bonding_curve, bonding_curve);
1130                assert_eq!(e.sharing_config, sharing_config);
1131                assert_eq!(e.old_creator, old_creator);
1132                assert_eq!(e.new_creator, new_creator);
1133            }
1134            _ => panic!("wrong variant"),
1135        }
1136    }
1137
1138}