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