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            ..Default::default()
454        }))
455    }
456}
457
458/// 解析 TradeEvent (极限优化)
459///
460/// 根据 ix_name 返回不同的事件类型:
461/// - "buy" -> DexEvent::PumpFunBuy
462/// - "sell" -> DexEvent::PumpFunSell
463/// - "buy_exact_sol_in" -> DexEvent::PumpFunBuyExactSolIn
464/// - "buy_exact_quote_in" -> DexEvent::PumpFunBuy (exact quote args preserved on fields)
465/// - 其他/空 -> DexEvent::PumpFunTrade (兼容旧版本)
466#[inline(always)]
467fn parse_trade_event_optimized(
468    data: &[u8],
469    signature: Signature,
470    slot: u64,
471    tx_index: u64,
472    block_time_us: Option<i64>,
473    grpc_recv_us: i64,
474    is_created_buy: bool,
475) -> Option<DexEvent> {
476    unsafe {
477        // 快速边界检查
478        if data.len() < 32 + 8 + 8 + 1 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 8 + 8 + 32 + 8 + 8 {
479            return None;
480        }
481
482        let mut offset = 0;
483
484        let mint = read_pubkey_unchecked(data, offset);
485        offset += 32;
486
487        let sol_amount = read_u64_unchecked(data, offset);
488        offset += 8;
489
490        let token_amount = read_u64_unchecked(data, offset);
491        offset += 8;
492
493        let is_buy = read_bool_unchecked(data, offset);
494        offset += 1;
495
496        let user = read_pubkey_unchecked(data, offset);
497        offset += 32;
498
499        let timestamp = read_i64_unchecked(data, offset);
500        offset += 8;
501
502        let virtual_sol_reserves = read_u64_unchecked(data, offset);
503        offset += 8;
504
505        let virtual_token_reserves = read_u64_unchecked(data, offset);
506        offset += 8;
507
508        let real_sol_reserves = read_u64_unchecked(data, offset);
509        offset += 8;
510
511        let real_token_reserves = read_u64_unchecked(data, offset);
512        offset += 8;
513
514        let fee_recipient = read_pubkey_unchecked(data, offset);
515        offset += 32;
516
517        let fee_basis_points = read_u64_unchecked(data, offset);
518        offset += 8;
519
520        let fee = read_u64_unchecked(data, offset);
521        offset += 8;
522
523        let creator = read_pubkey_unchecked(data, offset);
524        offset += 32;
525
526        let creator_fee_basis_points = read_u64_unchecked(data, offset);
527        offset += 8;
528
529        let creator_fee = read_u64_unchecked(data, offset);
530        offset += 8;
531
532        // 可选字段
533        let track_volume =
534            if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
535        offset += 1;
536
537        let total_unclaimed_tokens =
538            if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
539        offset += 8;
540
541        let total_claimed_tokens =
542            if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
543        offset += 8;
544
545        let current_sol_volume =
546            if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
547        offset += 8;
548
549        let last_update_timestamp =
550            if offset + 8 <= data.len() { read_i64_unchecked(data, offset) } else { 0 };
551        offset += 8;
552
553        // ix_name: String (4-byte length prefix + content)
554        // Values: "buy" | "sell" | "buy_exact_sol_in" | "buy_exact_quote_in"
555        let ix_name = if offset + 4 <= data.len() {
556            if let Some((s, len)) = read_str_unchecked(data, offset) {
557                offset += len;
558                s.to_string()
559            } else {
560                String::new()
561            }
562        } else {
563            String::new()
564        };
565        let ix_kind = normalize_pumpfun_ix_name(&ix_name);
566
567        // mayhem_mode: bool (1 byte), cashback_fee_basis_points (8), cashback (8) - PUMP_CASHBACK_README
568        let mayhem_mode =
569            if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
570        offset += 1;
571        let cashback_fee_basis_points =
572            if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
573        offset += 8;
574        let cashback = if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
575        offset += 8;
576        let (
577            buyback_fee_basis_points,
578            buyback_fee,
579            shareholders,
580            quote_mint,
581            quote_amount,
582            virtual_quote_reserves,
583            real_quote_reserves,
584        ) = read_trade_event_extensions(data, &mut offset)?;
585
586        let metadata = EventMetadata {
587            signature,
588            slot,
589            tx_index,
590            block_time_us: block_time_us.unwrap_or(0),
591            grpc_recv_us,
592            recent_blockhash: None,
593        };
594
595        let trade_event = PumpFunTradeEvent {
596            metadata,
597            mint,
598            sol_amount,
599            token_amount,
600            is_buy,
601            is_created_buy,
602            user,
603            timestamp,
604            virtual_sol_reserves,
605            virtual_token_reserves,
606            real_sol_reserves,
607            real_token_reserves,
608            fee_recipient,
609            fee_basis_points,
610            fee,
611            creator,
612            creator_fee_basis_points,
613            creator_fee,
614            track_volume,
615            total_unclaimed_tokens,
616            total_claimed_tokens,
617            current_sol_volume,
618            last_update_timestamp,
619            ix_name: ix_name.clone(),
620            mayhem_mode,
621            cashback_fee_basis_points,
622            cashback,
623            buyback_fee_basis_points,
624            buyback_fee,
625            shareholders,
626            quote_mint,
627            quote_amount,
628            virtual_quote_reserves,
629            real_quote_reserves,
630            is_cashback_coin: cashback_fee_basis_points > 0,
631            amount: 0,
632            max_sol_cost: 0,
633            min_sol_output: 0,
634            spendable_sol_in: 0,
635            spendable_quote_in: 0,
636            min_tokens_out: 0,
637            global: Pubkey::default(),
638            bonding_curve: Pubkey::default(),
639            associated_bonding_curve: Pubkey::default(),
640            associated_user: Pubkey::default(),
641            system_program: Pubkey::default(),
642            creator_vault: Pubkey::default(),
643            event_authority: Pubkey::default(),
644            program: Pubkey::default(),
645            global_volume_accumulator: Pubkey::default(),
646            user_volume_accumulator: Pubkey::default(),
647            fee_config: Pubkey::default(),
648            fee_program: Pubkey::default(),
649            token_program: Pubkey::default(),
650            account: None,
651            ..Default::default()
652        };
653
654        // 根据 ix_name 返回不同的事件类型,支持用户过滤特定交易类型
655        match ix_kind {
656            "buy" => Some(DexEvent::PumpFunBuy(trade_event)),
657            "sell" => Some(DexEvent::PumpFunSell(trade_event)),
658            "buy_exact_sol_in" => Some(DexEvent::PumpFunBuyExactSolIn(trade_event)),
659            "buy_exact_quote_in" => Some(DexEvent::PumpFunBuy(trade_event)),
660            _ => Some(DexEvent::PumpFunTrade(trade_event)), // 兼容旧版本或未知类型
661        }
662    }
663}
664
665/// 解析 MigrateEvent (极限优化)
666#[inline(always)]
667fn parse_migrate_event_optimized(
668    data: &[u8],
669    signature: Signature,
670    slot: u64,
671    tx_index: u64,
672    block_time_us: Option<i64>,
673    grpc_recv_us: i64,
674) -> Option<DexEvent> {
675    unsafe {
676        // 快速边界检查
677        if data.len() < 32 + 32 + 8 + 8 + 8 + 32 + 8 + 32 {
678            return None;
679        }
680
681        let mut offset = 0;
682
683        let user = read_pubkey_unchecked(data, offset);
684        offset += 32;
685
686        let mint = read_pubkey_unchecked(data, offset);
687        offset += 32;
688
689        let mint_amount = read_u64_unchecked(data, offset);
690        offset += 8;
691
692        let sol_amount = read_u64_unchecked(data, offset);
693        offset += 8;
694
695        let pool_migration_fee = read_u64_unchecked(data, offset);
696        offset += 8;
697
698        let bonding_curve = read_pubkey_unchecked(data, offset);
699        offset += 32;
700
701        let timestamp = read_i64_unchecked(data, offset);
702        offset += 8;
703
704        let pool = read_pubkey_unchecked(data, offset);
705
706        let metadata = EventMetadata {
707            signature,
708            slot,
709            tx_index,
710            block_time_us: block_time_us.unwrap_or(0),
711            grpc_recv_us,
712            recent_blockhash: None,
713        };
714
715        Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
716            metadata,
717            user,
718            mint,
719            mint_amount,
720            sol_amount,
721            pool_migration_fee,
722            bonding_curve,
723            timestamp,
724            pool,
725        }))
726    }
727}
728
729#[inline(always)]
730fn parse_migrate_bonding_curve_creator_event_optimized(
731    data: &[u8],
732    signature: Signature,
733    slot: u64,
734    tx_index: u64,
735    block_time_us: Option<i64>,
736    grpc_recv_us: i64,
737) -> Option<DexEvent> {
738    let metadata = EventMetadata {
739        signature,
740        slot,
741        tx_index,
742        block_time_us: block_time_us.unwrap_or(0),
743        grpc_recv_us,
744        recent_blockhash: None,
745    };
746    parse_migrate_bonding_curve_creator_from_data(data, metadata)
747}
748
749#[inline(always)]
750fn parse_create_fee_sharing_config_event_optimized(
751    data: &[u8],
752    signature: Signature,
753    slot: u64,
754    tx_index: u64,
755    block_time_us: Option<i64>,
756    grpc_recv_us: i64,
757) -> Option<DexEvent> {
758    let metadata = EventMetadata {
759        signature,
760        slot,
761        tx_index,
762        block_time_us: block_time_us.unwrap_or(0),
763        grpc_recv_us,
764        recent_blockhash: None,
765    };
766    crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(data, metadata)
767}
768
769// ============================================================================
770// 快速过滤 API (用于事件过滤场景)
771// ============================================================================
772
773/// 快速判断事件类型 (只解析 discriminator)
774///
775/// 性能: <50ns
776#[inline(always)]
777pub fn get_event_type_fast(log: &str) -> Option<u64> {
778    extract_discriminator_simd(log)
779}
780
781/// 检查是否为特定事件类型 (SIMD 优化)
782#[inline(always)]
783pub fn is_event_type(log: &str, discriminator: u64) -> bool {
784    extract_discriminator_simd(log) == Some(discriminator)
785}
786
787// ============================================================================
788// Public API for optimized parsing from pre-decoded data
789// These functions accept already-decoded data (without discriminator)
790// ============================================================================
791
792/// Parse PumpFun Trade event from pre-decoded data
793///
794/// `data` should be the decoded bytes AFTER the 8-byte discriminator
795///
796/// Returns different event types based on ix_name:
797/// - "buy" -> DexEvent::PumpFunBuy
798/// - "sell" -> DexEvent::PumpFunSell
799/// - "buy_exact_sol_in" -> DexEvent::PumpFunBuyExactSolIn
800/// - "buy_exact_quote_in" -> DexEvent::PumpFunBuy (exact quote args preserved on fields)
801/// - other/empty -> DexEvent::PumpFunTrade (backward compatible)
802#[inline(always)]
803pub fn parse_trade_from_data(
804    data: &[u8],
805    metadata: EventMetadata,
806    is_created_buy: bool,
807) -> Option<DexEvent> {
808    unsafe {
809        // 快速边界检查
810        if data.len() < 32 + 8 + 8 + 1 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 8 + 8 + 32 + 8 + 8 {
811            return None;
812        }
813
814        let mut offset = 0;
815
816        let mint = read_pubkey_unchecked(data, offset);
817        offset += 32;
818
819        let sol_amount = read_u64_unchecked(data, offset);
820        offset += 8;
821
822        let token_amount = read_u64_unchecked(data, offset);
823        offset += 8;
824
825        let is_buy = read_bool_unchecked(data, offset);
826        offset += 1;
827
828        let user = read_pubkey_unchecked(data, offset);
829        offset += 32;
830
831        let timestamp = read_i64_unchecked(data, offset);
832        offset += 8;
833
834        let virtual_sol_reserves = read_u64_unchecked(data, offset);
835        offset += 8;
836
837        let virtual_token_reserves = read_u64_unchecked(data, offset);
838        offset += 8;
839
840        let real_sol_reserves = read_u64_unchecked(data, offset);
841        offset += 8;
842
843        let real_token_reserves = read_u64_unchecked(data, offset);
844        offset += 8;
845
846        let fee_recipient = read_pubkey_unchecked(data, offset);
847        offset += 32;
848
849        let fee_basis_points = read_u64_unchecked(data, offset);
850        offset += 8;
851
852        let fee = read_u64_unchecked(data, offset);
853        offset += 8;
854
855        let creator = read_pubkey_unchecked(data, offset);
856        offset += 32;
857
858        let creator_fee_basis_points = read_u64_unchecked(data, offset);
859        offset += 8;
860
861        let creator_fee = read_u64_unchecked(data, offset);
862        offset += 8;
863
864        // 可选字段
865        let track_volume =
866            if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
867        offset += 1;
868
869        let total_unclaimed_tokens =
870            if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
871        offset += 8;
872
873        let total_claimed_tokens =
874            if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
875        offset += 8;
876
877        let current_sol_volume =
878            if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
879        offset += 8;
880
881        let last_update_timestamp =
882            if offset + 8 <= data.len() { read_i64_unchecked(data, offset) } else { 0 };
883        offset += 8;
884
885        let ix_name = if offset + 4 <= data.len() {
886            if let Some((s, len)) = read_str_unchecked(data, offset) {
887                offset += len;
888                s.to_string()
889            } else {
890                String::new()
891            }
892        } else {
893            String::new()
894        };
895        let ix_kind = normalize_pumpfun_ix_name(&ix_name);
896
897        // mayhem_mode (1), cashback_fee_basis_points (8), cashback (8) - PUMP_CASHBACK_README
898        let mayhem_mode =
899            if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
900        offset += 1;
901        let cashback_fee_basis_points =
902            if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
903        offset += 8;
904        let cashback = if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
905        offset += 8;
906        let (
907            buyback_fee_basis_points,
908            buyback_fee,
909            shareholders,
910            quote_mint,
911            quote_amount,
912            virtual_quote_reserves,
913            real_quote_reserves,
914        ) = read_trade_event_extensions(data, &mut offset)?;
915
916        let trade_event = PumpFunTradeEvent {
917            metadata,
918            mint,
919            sol_amount,
920            token_amount,
921            is_buy,
922            is_created_buy,
923            user,
924            timestamp,
925            virtual_sol_reserves,
926            virtual_token_reserves,
927            real_sol_reserves,
928            real_token_reserves,
929            fee_recipient,
930            fee_basis_points,
931            fee,
932            creator,
933            creator_fee_basis_points,
934            creator_fee,
935            track_volume,
936            total_unclaimed_tokens,
937            total_claimed_tokens,
938            current_sol_volume,
939            last_update_timestamp,
940            ix_name: ix_name.clone(),
941            mayhem_mode,
942            cashback_fee_basis_points,
943            cashback,
944            buyback_fee_basis_points,
945            buyback_fee,
946            shareholders,
947            quote_mint,
948            quote_amount,
949            virtual_quote_reserves,
950            real_quote_reserves,
951            is_cashback_coin: cashback_fee_basis_points > 0,
952            amount: 0,
953            max_sol_cost: 0,
954            min_sol_output: 0,
955            spendable_sol_in: 0,
956            spendable_quote_in: 0,
957            min_tokens_out: 0,
958            global: Pubkey::default(),
959            bonding_curve: Pubkey::default(),
960            associated_bonding_curve: Pubkey::default(),
961            associated_user: Pubkey::default(),
962            system_program: Pubkey::default(),
963            creator_vault: Pubkey::default(),
964            event_authority: Pubkey::default(),
965            program: Pubkey::default(),
966            global_volume_accumulator: Pubkey::default(),
967            user_volume_accumulator: Pubkey::default(),
968            fee_config: Pubkey::default(),
969            fee_program: Pubkey::default(),
970            token_program: Pubkey::default(),
971            account: None,
972            ..Default::default()
973        };
974
975        // 根据 ix_name 返回不同的事件类型
976        match ix_kind {
977            "buy" => Some(DexEvent::PumpFunBuy(trade_event)),
978            "sell" => Some(DexEvent::PumpFunSell(trade_event)),
979            "buy_exact_sol_in" => Some(DexEvent::PumpFunBuyExactSolIn(trade_event)),
980            "buy_exact_quote_in" => Some(DexEvent::PumpFunBuy(trade_event)),
981            _ => Some(DexEvent::PumpFunTrade(trade_event)),
982        }
983    }
984}
985
986/// Parse only PumpFun Buy events from pre-decoded data
987///
988/// Returns None if the event is not a buy event
989#[inline(always)]
990pub fn parse_buy_from_data(
991    data: &[u8],
992    metadata: EventMetadata,
993    is_created_buy: bool,
994) -> Option<DexEvent> {
995    let event = parse_trade_from_data(data, metadata, is_created_buy)?;
996    match &event {
997        DexEvent::PumpFunBuy(_) => Some(event),
998        _ => None,
999    }
1000}
1001
1002/// Parse only PumpFun Sell events from pre-decoded data
1003///
1004/// Returns None if the event is not a sell event
1005#[inline(always)]
1006pub fn parse_sell_from_data(
1007    data: &[u8],
1008    metadata: EventMetadata,
1009    is_created_buy: bool,
1010) -> Option<DexEvent> {
1011    let event = parse_trade_from_data(data, metadata, is_created_buy)?;
1012    match &event {
1013        DexEvent::PumpFunSell(_) => Some(event),
1014        _ => None,
1015    }
1016}
1017
1018/// Parse only PumpFun BuyExactSolIn events from pre-decoded data
1019///
1020/// Returns None if the event is not a buy_exact_sol_in event
1021#[inline(always)]
1022pub fn parse_buy_exact_sol_in_from_data(
1023    data: &[u8],
1024    metadata: EventMetadata,
1025    is_created_buy: bool,
1026) -> Option<DexEvent> {
1027    let event = parse_trade_from_data(data, metadata, is_created_buy)?;
1028    match &event {
1029        DexEvent::PumpFunBuyExactSolIn(_) => Some(event),
1030        _ => None,
1031    }
1032}
1033
1034/// Parse PumpFun Create event from pre-decoded data
1035#[inline(always)]
1036pub fn parse_create_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
1037    unsafe {
1038        let mut offset = 0;
1039
1040        let (name, name_len) = read_str_unchecked(data, offset)?;
1041        offset += name_len;
1042
1043        let (symbol, symbol_len) = read_str_unchecked(data, offset)?;
1044        offset += symbol_len;
1045
1046        let (uri, uri_len) = read_str_unchecked(data, offset)?;
1047        offset += uri_len;
1048
1049        if data.len() < offset + 32 + 32 + 32 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 1 {
1050            return None;
1051        }
1052
1053        let mint = read_pubkey_unchecked(data, offset);
1054        offset += 32;
1055
1056        let bonding_curve = read_pubkey_unchecked(data, offset);
1057        offset += 32;
1058
1059        let user = read_pubkey_unchecked(data, offset);
1060        offset += 32;
1061
1062        let creator = read_pubkey_unchecked(data, offset);
1063        offset += 32;
1064
1065        let timestamp = read_i64_unchecked(data, offset);
1066        offset += 8;
1067
1068        let virtual_token_reserves = read_u64_unchecked(data, offset);
1069        offset += 8;
1070
1071        let virtual_sol_reserves = read_u64_unchecked(data, offset);
1072        offset += 8;
1073
1074        let real_token_reserves = read_u64_unchecked(data, offset);
1075        offset += 8;
1076
1077        let token_total_supply = read_u64_unchecked(data, offset);
1078        offset += 8;
1079
1080        let token_program = if offset + 32 <= data.len() {
1081            read_pubkey_unchecked(data, offset)
1082        } else {
1083            Pubkey::default()
1084        };
1085        offset += 32;
1086
1087        let is_mayhem_mode =
1088            if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
1089        offset += 1;
1090        let is_cashback_enabled =
1091            if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
1092        offset += 1;
1093        let quote_mint = normalize_pumpfun_quote_mint(if offset + 32 <= data.len() {
1094            read_pubkey_unchecked(data, offset)
1095        } else {
1096            Pubkey::default()
1097        });
1098        offset += 32;
1099        let virtual_quote_reserves =
1100            if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
1101
1102        Some(DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
1103            metadata,
1104            name: name.to_string(),
1105            symbol: symbol.to_string(),
1106            uri: uri.to_string(),
1107            mint,
1108            bonding_curve,
1109            user,
1110            creator,
1111            timestamp,
1112            virtual_token_reserves,
1113            virtual_sol_reserves,
1114            real_token_reserves,
1115            token_total_supply,
1116            token_program,
1117            is_mayhem_mode,
1118            is_cashback_enabled,
1119            quote_mint,
1120            virtual_quote_reserves,
1121            ..Default::default()
1122        }))
1123    }
1124}
1125
1126/// Parse PumpFun Migrate event from pre-decoded data
1127#[inline(always)]
1128pub fn parse_migrate_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
1129    unsafe {
1130        if data.len() < 32 + 32 + 8 + 8 + 8 + 32 + 8 + 32 {
1131            return None;
1132        }
1133
1134        let mut offset = 0;
1135
1136        let user = read_pubkey_unchecked(data, offset);
1137        offset += 32;
1138
1139        let mint = read_pubkey_unchecked(data, offset);
1140        offset += 32;
1141
1142        let mint_amount = read_u64_unchecked(data, offset);
1143        offset += 8;
1144
1145        let sol_amount = read_u64_unchecked(data, offset);
1146        offset += 8;
1147
1148        let pool_migration_fee = read_u64_unchecked(data, offset);
1149        offset += 8;
1150
1151        let bonding_curve = read_pubkey_unchecked(data, offset);
1152        offset += 32;
1153
1154        let timestamp = read_i64_unchecked(data, offset);
1155        offset += 8;
1156
1157        let pool = read_pubkey_unchecked(data, offset);
1158
1159        Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
1160            metadata,
1161            user,
1162            mint,
1163            mint_amount,
1164            sol_amount,
1165            pool_migration_fee,
1166            bonding_curve,
1167            timestamp,
1168            pool,
1169        }))
1170    }
1171}
1172
1173/// `migrateBondingCurveCreatorEvent`:`data` 为去掉 8 字节 discriminator 之后的 Borsh 体。
1174#[inline(always)]
1175pub fn parse_migrate_bonding_curve_creator_from_data(
1176    data: &[u8],
1177    metadata: EventMetadata,
1178) -> Option<DexEvent> {
1179    unsafe {
1180        const NEED: usize = 8 + 32 * 5;
1181        if data.len() < NEED {
1182            return None;
1183        }
1184
1185        let mut offset = 0usize;
1186        let timestamp = read_i64_unchecked(data, offset);
1187        offset += 8;
1188        let mint = read_pubkey_unchecked(data, offset);
1189        offset += 32;
1190        let bonding_curve = read_pubkey_unchecked(data, offset);
1191        offset += 32;
1192        let sharing_config = read_pubkey_unchecked(data, offset);
1193        offset += 32;
1194        let old_creator = read_pubkey_unchecked(data, offset);
1195        offset += 32;
1196        let new_creator = read_pubkey_unchecked(data, offset);
1197
1198        Some(DexEvent::PumpFunMigrateBondingCurveCreator(PumpFunMigrateBondingCurveCreatorEvent {
1199            metadata,
1200            timestamp,
1201            mint,
1202            bonding_curve,
1203            sharing_config,
1204            old_creator,
1205            new_creator,
1206        }))
1207    }
1208}
1209
1210/// `createFeeSharingConfigEvent`:委托 [`pump_fees::parse_create_fee_sharing_config_from_data`](crate::logs::pump_fees)。
1211#[inline]
1212pub fn parse_create_fee_sharing_config_from_data(
1213    data: &[u8],
1214    metadata: EventMetadata,
1215) -> Option<DexEvent> {
1216    crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(data, metadata)
1217}
1218
1219#[inline(always)]
1220fn read_i64_at(data: &[u8], o: &mut usize) -> Option<i64> {
1221    if data.len() < *o + 8 {
1222        return None;
1223    }
1224    let v = i64::from_le_bytes(data[*o..*o + 8].try_into().ok()?);
1225    *o += 8;
1226    Some(v)
1227}
1228
1229#[inline(always)]
1230fn read_u16_at(data: &[u8], o: &mut usize) -> Option<u16> {
1231    if data.len() < *o + 2 {
1232        return None;
1233    }
1234    let v = u16::from_le_bytes(data[*o..*o + 2].try_into().ok()?);
1235    *o += 2;
1236    Some(v)
1237}
1238
1239#[inline(always)]
1240fn read_u32_at(data: &[u8], o: &mut usize) -> Option<u32> {
1241    if data.len() < *o + 4 {
1242        return None;
1243    }
1244    let v = u32::from_le_bytes(data[*o..*o + 4].try_into().ok()?);
1245    *o += 4;
1246    Some(v)
1247}
1248
1249#[inline(always)]
1250fn read_pubkey_at(data: &[u8], o: &mut usize) -> Option<Pubkey> {
1251    if data.len() < *o + 32 {
1252        return None;
1253    }
1254    let pk = Pubkey::new_from_array(data[*o..*o + 32].try_into().ok()?);
1255    *o += 32;
1256    Some(pk)
1257}
1258
1259// ============================================================================
1260// 性能统计 API (可选)
1261// ============================================================================
1262
1263#[cfg(feature = "perf-stats")]
1264pub fn get_perf_stats() -> (usize, usize) {
1265    let count = PARSE_COUNT.load(Ordering::Relaxed);
1266    let total_ns = PARSE_TIME_NS.load(Ordering::Relaxed);
1267    (count, total_ns)
1268}
1269
1270#[cfg(feature = "perf-stats")]
1271pub fn reset_perf_stats() {
1272    PARSE_COUNT.store(0, Ordering::Relaxed);
1273    PARSE_TIME_NS.store(0, Ordering::Relaxed);
1274}
1275
1276#[cfg(test)]
1277mod tests {
1278    use super::*;
1279    use crate::core::events::{DexEvent, EventMetadata};
1280
1281    #[test]
1282    fn test_discriminator_simd() {
1283        // 测试 SIMD discriminator 提取
1284        let log = "Program data: G3Kp5Dfe605nAAAAAAAAAAA=";
1285        let disc = extract_discriminator_simd(log);
1286        assert!(disc.is_some());
1287    }
1288
1289    #[test]
1290    fn test_parse_performance() {
1291        // 性能测试
1292        let log = "Program data: G3Kp5Dfe605nAAAAAAAAAAA=";
1293        let sig = Signature::default();
1294
1295        let start = std::time::Instant::now();
1296        for _ in 0..1000 {
1297            let _ = parse_log(log, sig, 0, 0, Some(0), 0, false);
1298        }
1299        let elapsed = start.elapsed();
1300
1301        println!("Average parse time: {} ns", elapsed.as_nanos() / 1000);
1302    }
1303
1304    #[test]
1305    fn migrate_bonding_curve_creator_roundtrip_from_data() {
1306        let ts: i64 = 1_777_920_719;
1307        let mint = Pubkey::new_unique();
1308        let bonding_curve = Pubkey::new_unique();
1309        let sharing_config = Pubkey::new_unique();
1310        let old_creator = Pubkey::new_unique();
1311        let new_creator = Pubkey::new_unique();
1312
1313        let mut buf = Vec::with_capacity(200);
1314        buf.extend_from_slice(&ts.to_le_bytes());
1315        buf.extend_from_slice(mint.as_ref());
1316        buf.extend_from_slice(bonding_curve.as_ref());
1317        buf.extend_from_slice(sharing_config.as_ref());
1318        buf.extend_from_slice(old_creator.as_ref());
1319        buf.extend_from_slice(new_creator.as_ref());
1320
1321        let metadata = EventMetadata {
1322            signature: Signature::default(),
1323            slot: 0,
1324            tx_index: 0,
1325            block_time_us: 0,
1326            grpc_recv_us: 0,
1327            recent_blockhash: None,
1328        };
1329
1330        let ev = parse_migrate_bonding_curve_creator_from_data(&buf, metadata).expect("parse");
1331        match ev {
1332            DexEvent::PumpFunMigrateBondingCurveCreator(e) => {
1333                assert_eq!(e.timestamp, ts);
1334                assert_eq!(e.mint, mint);
1335                assert_eq!(e.bonding_curve, bonding_curve);
1336                assert_eq!(e.sharing_config, sharing_config);
1337                assert_eq!(e.old_creator, old_creator);
1338                assert_eq!(e.new_creator, new_creator);
1339            }
1340            _ => panic!("wrong variant"),
1341        }
1342    }
1343}