Skip to main content

sol_parser_sdk/logs/
pump.rs

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