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