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