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