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