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            amount: 0,
486            max_sol_cost: 0,
487            min_sol_output: 0,
488            bonding_curve: Pubkey::default(),
489            associated_bonding_curve: Pubkey::default(),
490            creator_vault: Pubkey::default(),
491            token_program: Pubkey::default(),
492            account: None,
493        };
494
495        // 根据 ix_name 返回不同的事件类型,支持用户过滤特定交易类型
496        match ix_name.as_str() {
497            "buy" | "buy_v2" => Some(DexEvent::PumpFunBuy(trade_event)),
498            "sell" | "sell_v2" => Some(DexEvent::PumpFunSell(trade_event)),
499            "buy_exact_sol_in" | "buy_exact_quote_in_v2" => {
500                Some(DexEvent::PumpFunBuyExactSolIn(trade_event))
501            }
502            _ => Some(DexEvent::PumpFunTrade(trade_event)), // 兼容旧版本或未知类型
503        }
504    }
505}
506
507/// 解析 MigrateEvent (极限优化)
508#[inline(always)]
509fn parse_migrate_event_optimized(
510    data: &[u8],
511    signature: Signature,
512    slot: u64,
513    tx_index: u64,
514    block_time_us: Option<i64>,
515    grpc_recv_us: i64,
516) -> Option<DexEvent> {
517    unsafe {
518        // 快速边界检查
519        if data.len() < 32 + 32 + 8 + 8 + 8 + 32 + 8 + 32 {
520            return None;
521        }
522
523        let mut offset = 0;
524
525        let user = read_pubkey_unchecked(data, offset);
526        offset += 32;
527
528        let mint = read_pubkey_unchecked(data, offset);
529        offset += 32;
530
531        let mint_amount = read_u64_unchecked(data, offset);
532        offset += 8;
533
534        let sol_amount = read_u64_unchecked(data, offset);
535        offset += 8;
536
537        let pool_migration_fee = read_u64_unchecked(data, offset);
538        offset += 8;
539
540        let bonding_curve = read_pubkey_unchecked(data, offset);
541        offset += 32;
542
543        let timestamp = read_i64_unchecked(data, offset);
544        offset += 8;
545
546        let pool = read_pubkey_unchecked(data, offset);
547
548        let metadata = EventMetadata {
549            signature,
550            slot,
551            tx_index,
552            block_time_us: block_time_us.unwrap_or(0),
553            grpc_recv_us,
554            recent_blockhash: None,
555        };
556
557        Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
558            metadata,
559            user,
560            mint,
561            mint_amount,
562            sol_amount,
563            pool_migration_fee,
564            bonding_curve,
565            timestamp,
566            pool,
567        }))
568    }
569}
570
571#[inline(always)]
572fn parse_migrate_bonding_curve_creator_event_optimized(
573    data: &[u8],
574    signature: Signature,
575    slot: u64,
576    tx_index: u64,
577    block_time_us: Option<i64>,
578    grpc_recv_us: i64,
579) -> Option<DexEvent> {
580    let metadata = EventMetadata {
581        signature,
582        slot,
583        tx_index,
584        block_time_us: block_time_us.unwrap_or(0),
585        grpc_recv_us,
586        recent_blockhash: None,
587    };
588    parse_migrate_bonding_curve_creator_from_data(data, metadata)
589}
590
591#[inline(always)]
592fn parse_create_fee_sharing_config_event_optimized(
593    data: &[u8],
594    signature: Signature,
595    slot: u64,
596    tx_index: u64,
597    block_time_us: Option<i64>,
598    grpc_recv_us: i64,
599) -> Option<DexEvent> {
600    let metadata = EventMetadata {
601        signature,
602        slot,
603        tx_index,
604        block_time_us: block_time_us.unwrap_or(0),
605        grpc_recv_us,
606        recent_blockhash: None,
607    };
608    crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(data, metadata)
609}
610
611// ============================================================================
612// 快速过滤 API (用于事件过滤场景)
613// ============================================================================
614
615/// 快速判断事件类型 (只解析 discriminator)
616///
617/// 性能: <50ns
618#[inline(always)]
619pub fn get_event_type_fast(log: &str) -> Option<u64> {
620    extract_discriminator_simd(log)
621}
622
623/// 检查是否为特定事件类型 (SIMD 优化)
624#[inline(always)]
625pub fn is_event_type(log: &str, discriminator: u64) -> bool {
626    extract_discriminator_simd(log) == Some(discriminator)
627}
628
629// ============================================================================
630// Public API for optimized parsing from pre-decoded data
631// These functions accept already-decoded data (without discriminator)
632// ============================================================================
633
634/// Parse PumpFun Trade event from pre-decoded data
635///
636/// `data` should be the decoded bytes AFTER the 8-byte discriminator
637///
638/// Returns different event types based on ix_name:
639/// - "buy" -> DexEvent::PumpFunBuy
640/// - "sell" -> DexEvent::PumpFunSell
641/// - "buy_exact_sol_in" -> DexEvent::PumpFunBuyExactSolIn
642/// - other/empty -> DexEvent::PumpFunTrade (backward compatible)
643#[inline(always)]
644pub fn parse_trade_from_data(
645    data: &[u8],
646    metadata: EventMetadata,
647    is_created_buy: bool,
648) -> Option<DexEvent> {
649    unsafe {
650        // 快速边界检查
651        if data.len() < 32 + 8 + 8 + 1 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 8 + 8 + 32 + 8 + 8 {
652            return None;
653        }
654
655        let mut offset = 0;
656
657        let mint = read_pubkey_unchecked(data, offset);
658        offset += 32;
659
660        let sol_amount = read_u64_unchecked(data, offset);
661        offset += 8;
662
663        let token_amount = read_u64_unchecked(data, offset);
664        offset += 8;
665
666        let is_buy = read_bool_unchecked(data, offset);
667        offset += 1;
668
669        let user = read_pubkey_unchecked(data, offset);
670        offset += 32;
671
672        let timestamp = read_i64_unchecked(data, offset);
673        offset += 8;
674
675        let virtual_sol_reserves = read_u64_unchecked(data, offset);
676        offset += 8;
677
678        let virtual_token_reserves = read_u64_unchecked(data, offset);
679        offset += 8;
680
681        let real_sol_reserves = read_u64_unchecked(data, offset);
682        offset += 8;
683
684        let real_token_reserves = read_u64_unchecked(data, offset);
685        offset += 8;
686
687        let fee_recipient = read_pubkey_unchecked(data, offset);
688        offset += 32;
689
690        let fee_basis_points = read_u64_unchecked(data, offset);
691        offset += 8;
692
693        let fee = read_u64_unchecked(data, offset);
694        offset += 8;
695
696        let creator = read_pubkey_unchecked(data, offset);
697        offset += 32;
698
699        let creator_fee_basis_points = read_u64_unchecked(data, offset);
700        offset += 8;
701
702        let creator_fee = read_u64_unchecked(data, offset);
703        offset += 8;
704
705        // 可选字段
706        let track_volume =
707            if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
708        offset += 1;
709
710        let total_unclaimed_tokens =
711            if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
712        offset += 8;
713
714        let total_claimed_tokens =
715            if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
716        offset += 8;
717
718        let current_sol_volume =
719            if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
720        offset += 8;
721
722        let last_update_timestamp =
723            if offset + 8 <= data.len() { read_i64_unchecked(data, offset) } else { 0 };
724        offset += 8;
725
726        let ix_name = if offset + 4 <= data.len() {
727            if let Some((s, len)) = read_str_unchecked(data, offset) {
728                offset += len;
729                s.to_string()
730            } else {
731                String::new()
732            }
733        } else {
734            String::new()
735        };
736
737        // mayhem_mode (1), cashback_fee_basis_points (8), cashback (8) - PUMP_CASHBACK_README
738        let mayhem_mode =
739            if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
740        offset += 1;
741        let cashback_fee_basis_points =
742            if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
743        offset += 8;
744        let cashback = if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
745
746        let trade_event = PumpFunTradeEvent {
747            metadata,
748            mint,
749            sol_amount,
750            token_amount,
751            is_buy,
752            is_created_buy,
753            user,
754            timestamp,
755            virtual_sol_reserves,
756            virtual_token_reserves,
757            real_sol_reserves,
758            real_token_reserves,
759            fee_recipient,
760            fee_basis_points,
761            fee,
762            creator,
763            creator_fee_basis_points,
764            creator_fee,
765            track_volume,
766            total_unclaimed_tokens,
767            total_claimed_tokens,
768            current_sol_volume,
769            last_update_timestamp,
770            ix_name: ix_name.clone(),
771            mayhem_mode,
772            cashback_fee_basis_points,
773            cashback,
774            is_cashback_coin: cashback_fee_basis_points > 0,
775            amount: 0,
776            max_sol_cost: 0,
777            min_sol_output: 0,
778            bonding_curve: Pubkey::default(),
779            associated_bonding_curve: Pubkey::default(),
780            creator_vault: Pubkey::default(),
781            token_program: Pubkey::default(),
782            account: None,
783        };
784
785        // 根据 ix_name 返回不同的事件类型
786        match ix_name.as_str() {
787            "buy" | "buy_v2" => Some(DexEvent::PumpFunBuy(trade_event)),
788            "sell" | "sell_v2" => Some(DexEvent::PumpFunSell(trade_event)),
789            "buy_exact_sol_in" | "buy_exact_quote_in_v2" => {
790                Some(DexEvent::PumpFunBuyExactSolIn(trade_event))
791            }
792            _ => Some(DexEvent::PumpFunTrade(trade_event)),
793        }
794    }
795}
796
797/// Parse only PumpFun Buy events from pre-decoded data
798///
799/// Returns None if the event is not a buy event
800#[inline(always)]
801pub fn parse_buy_from_data(
802    data: &[u8],
803    metadata: EventMetadata,
804    is_created_buy: bool,
805) -> Option<DexEvent> {
806    let event = parse_trade_from_data(data, metadata, is_created_buy)?;
807    match &event {
808        DexEvent::PumpFunBuy(_) => Some(event),
809        _ => None,
810    }
811}
812
813/// Parse only PumpFun Sell events from pre-decoded data
814///
815/// Returns None if the event is not a sell event
816#[inline(always)]
817pub fn parse_sell_from_data(
818    data: &[u8],
819    metadata: EventMetadata,
820    is_created_buy: bool,
821) -> Option<DexEvent> {
822    let event = parse_trade_from_data(data, metadata, is_created_buy)?;
823    match &event {
824        DexEvent::PumpFunSell(_) => Some(event),
825        _ => None,
826    }
827}
828
829/// Parse only PumpFun BuyExactSolIn events from pre-decoded data
830///
831/// Returns None if the event is not a buy_exact_sol_in event
832#[inline(always)]
833pub fn parse_buy_exact_sol_in_from_data(
834    data: &[u8],
835    metadata: EventMetadata,
836    is_created_buy: bool,
837) -> Option<DexEvent> {
838    let event = parse_trade_from_data(data, metadata, is_created_buy)?;
839    match &event {
840        DexEvent::PumpFunBuyExactSolIn(_) => Some(event),
841        _ => None,
842    }
843}
844
845/// Parse PumpFun Create event from pre-decoded data
846#[inline(always)]
847pub fn parse_create_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
848    unsafe {
849        let mut offset = 0;
850
851        let (name, name_len) = read_str_unchecked(data, offset)?;
852        offset += name_len;
853
854        let (symbol, symbol_len) = read_str_unchecked(data, offset)?;
855        offset += symbol_len;
856
857        let (uri, uri_len) = read_str_unchecked(data, offset)?;
858        offset += uri_len;
859
860        if data.len() < offset + 32 + 32 + 32 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 1 {
861            return None;
862        }
863
864        let mint = read_pubkey_unchecked(data, offset);
865        offset += 32;
866
867        let bonding_curve = read_pubkey_unchecked(data, offset);
868        offset += 32;
869
870        let user = read_pubkey_unchecked(data, offset);
871        offset += 32;
872
873        let creator = read_pubkey_unchecked(data, offset);
874        offset += 32;
875
876        let timestamp = read_i64_unchecked(data, offset);
877        offset += 8;
878
879        let virtual_token_reserves = read_u64_unchecked(data, offset);
880        offset += 8;
881
882        let virtual_sol_reserves = read_u64_unchecked(data, offset);
883        offset += 8;
884
885        let real_token_reserves = read_u64_unchecked(data, offset);
886        offset += 8;
887
888        let token_total_supply = read_u64_unchecked(data, offset);
889        offset += 8;
890
891        let token_program = if offset + 32 <= data.len() {
892            read_pubkey_unchecked(data, offset)
893        } else {
894            Pubkey::default()
895        };
896        offset += 32;
897
898        let is_mayhem_mode =
899            if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
900        offset += 1;
901        let is_cashback_enabled =
902            if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
903
904        Some(DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
905            metadata,
906            name: name.to_string(),
907            symbol: symbol.to_string(),
908            uri: uri.to_string(),
909            mint,
910            bonding_curve,
911            user,
912            creator,
913            timestamp,
914            virtual_token_reserves,
915            virtual_sol_reserves,
916            real_token_reserves,
917            token_total_supply,
918            token_program,
919            is_mayhem_mode,
920            is_cashback_enabled,
921        }))
922    }
923}
924
925/// Parse PumpFun Migrate event from pre-decoded data
926#[inline(always)]
927pub fn parse_migrate_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
928    unsafe {
929        if data.len() < 32 + 32 + 8 + 8 + 8 + 32 + 8 + 32 {
930            return None;
931        }
932
933        let mut offset = 0;
934
935        let user = read_pubkey_unchecked(data, offset);
936        offset += 32;
937
938        let mint = read_pubkey_unchecked(data, offset);
939        offset += 32;
940
941        let mint_amount = read_u64_unchecked(data, offset);
942        offset += 8;
943
944        let sol_amount = read_u64_unchecked(data, offset);
945        offset += 8;
946
947        let pool_migration_fee = read_u64_unchecked(data, offset);
948        offset += 8;
949
950        let bonding_curve = read_pubkey_unchecked(data, offset);
951        offset += 32;
952
953        let timestamp = read_i64_unchecked(data, offset);
954        offset += 8;
955
956        let pool = read_pubkey_unchecked(data, offset);
957
958        Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
959            metadata,
960            user,
961            mint,
962            mint_amount,
963            sol_amount,
964            pool_migration_fee,
965            bonding_curve,
966            timestamp,
967            pool,
968        }))
969    }
970}
971
972/// `migrateBondingCurveCreatorEvent`:`data` 为去掉 8 字节 discriminator 之后的 Borsh 体。
973#[inline(always)]
974pub fn parse_migrate_bonding_curve_creator_from_data(
975    data: &[u8],
976    metadata: EventMetadata,
977) -> Option<DexEvent> {
978    unsafe {
979        const NEED: usize = 8 + 32 * 5;
980        if data.len() < NEED {
981            return None;
982        }
983
984        let mut offset = 0usize;
985        let timestamp = read_i64_unchecked(data, offset);
986        offset += 8;
987        let mint = read_pubkey_unchecked(data, offset);
988        offset += 32;
989        let bonding_curve = read_pubkey_unchecked(data, offset);
990        offset += 32;
991        let sharing_config = read_pubkey_unchecked(data, offset);
992        offset += 32;
993        let old_creator = read_pubkey_unchecked(data, offset);
994        offset += 32;
995        let new_creator = read_pubkey_unchecked(data, offset);
996
997        Some(DexEvent::PumpFunMigrateBondingCurveCreator(PumpFunMigrateBondingCurveCreatorEvent {
998            metadata,
999            timestamp,
1000            mint,
1001            bonding_curve,
1002            sharing_config,
1003            old_creator,
1004            new_creator,
1005        }))
1006    }
1007}
1008
1009/// `createFeeSharingConfigEvent`:委托 [`pump_fees::parse_create_fee_sharing_config_from_data`](crate::logs::pump_fees)。
1010#[inline]
1011pub fn parse_create_fee_sharing_config_from_data(
1012    data: &[u8],
1013    metadata: EventMetadata,
1014) -> Option<DexEvent> {
1015    crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(data, metadata)
1016}
1017
1018#[inline(always)]
1019fn read_i64_at(data: &[u8], o: &mut usize) -> Option<i64> {
1020    if data.len() < *o + 8 {
1021        return None;
1022    }
1023    let v = i64::from_le_bytes(data[*o..*o + 8].try_into().ok()?);
1024    *o += 8;
1025    Some(v)
1026}
1027
1028#[inline(always)]
1029fn read_u16_at(data: &[u8], o: &mut usize) -> Option<u16> {
1030    if data.len() < *o + 2 {
1031        return None;
1032    }
1033    let v = u16::from_le_bytes(data[*o..*o + 2].try_into().ok()?);
1034    *o += 2;
1035    Some(v)
1036}
1037
1038#[inline(always)]
1039fn read_u32_at(data: &[u8], o: &mut usize) -> Option<u32> {
1040    if data.len() < *o + 4 {
1041        return None;
1042    }
1043    let v = u32::from_le_bytes(data[*o..*o + 4].try_into().ok()?);
1044    *o += 4;
1045    Some(v)
1046}
1047
1048#[inline(always)]
1049fn read_pubkey_at(data: &[u8], o: &mut usize) -> Option<Pubkey> {
1050    if data.len() < *o + 32 {
1051        return None;
1052    }
1053    let pk = Pubkey::new_from_array(data[*o..*o + 32].try_into().ok()?);
1054    *o += 32;
1055    Some(pk)
1056}
1057
1058// ============================================================================
1059// 性能统计 API (可选)
1060// ============================================================================
1061
1062#[cfg(feature = "perf-stats")]
1063pub fn get_perf_stats() -> (usize, usize) {
1064    let count = PARSE_COUNT.load(Ordering::Relaxed);
1065    let total_ns = PARSE_TIME_NS.load(Ordering::Relaxed);
1066    (count, total_ns)
1067}
1068
1069#[cfg(feature = "perf-stats")]
1070pub fn reset_perf_stats() {
1071    PARSE_COUNT.store(0, Ordering::Relaxed);
1072    PARSE_TIME_NS.store(0, Ordering::Relaxed);
1073}
1074
1075#[cfg(test)]
1076mod tests {
1077    use super::*;
1078    use crate::core::events::{DexEvent, EventMetadata};
1079
1080    #[test]
1081    fn test_discriminator_simd() {
1082        // 测试 SIMD discriminator 提取
1083        let log = "Program data: G3Kp5Dfe605nAAAAAAAAAAA=";
1084        let disc = extract_discriminator_simd(log);
1085        assert!(disc.is_some());
1086    }
1087
1088    #[test]
1089    fn test_parse_performance() {
1090        // 性能测试
1091        let log = "Program data: G3Kp5Dfe605nAAAAAAAAAAA=";
1092        let sig = Signature::default();
1093
1094        let start = std::time::Instant::now();
1095        for _ in 0..1000 {
1096            let _ = parse_log(log, sig, 0, 0, Some(0), 0, false);
1097        }
1098        let elapsed = start.elapsed();
1099
1100        println!("Average parse time: {} ns", elapsed.as_nanos() / 1000);
1101    }
1102
1103    #[test]
1104    fn migrate_bonding_curve_creator_roundtrip_from_data() {
1105        let ts: i64 = 1_777_920_719;
1106        let mint = Pubkey::new_unique();
1107        let bonding_curve = Pubkey::new_unique();
1108        let sharing_config = Pubkey::new_unique();
1109        let old_creator = Pubkey::new_unique();
1110        let new_creator = Pubkey::new_unique();
1111
1112        let mut buf = Vec::with_capacity(200);
1113        buf.extend_from_slice(&ts.to_le_bytes());
1114        buf.extend_from_slice(mint.as_ref());
1115        buf.extend_from_slice(bonding_curve.as_ref());
1116        buf.extend_from_slice(sharing_config.as_ref());
1117        buf.extend_from_slice(old_creator.as_ref());
1118        buf.extend_from_slice(new_creator.as_ref());
1119
1120        let metadata = EventMetadata {
1121            signature: Signature::default(),
1122            slot: 0,
1123            tx_index: 0,
1124            block_time_us: 0,
1125            grpc_recv_us: 0,
1126            recent_blockhash: None,
1127        };
1128
1129        let ev = parse_migrate_bonding_curve_creator_from_data(&buf, metadata).expect("parse");
1130        match ev {
1131            DexEvent::PumpFunMigrateBondingCurveCreator(e) => {
1132                assert_eq!(e.timestamp, ts);
1133                assert_eq!(e.mint, mint);
1134                assert_eq!(e.bonding_curve, bonding_curve);
1135                assert_eq!(e.sharing_config, sharing_config);
1136                assert_eq!(e.old_creator, old_creator);
1137                assert_eq!(e.new_creator, new_creator);
1138            }
1139            _ => panic!("wrong variant"),
1140        }
1141    }
1142}