Skip to main content

sol_parser_sdk/logs/
zero_copy_parser.rs

1//! 零拷贝解析器 - 极致性能优化
2
3use crate::core::events::*;
4use base64::{engine::general_purpose, Engine as _};
5use memchr::memmem;
6use solana_sdk::signature::Signature;
7
8/// 零分配 PumpFun Trade 事件解析(栈缓冲区)
9#[inline(always)]
10pub fn parse_pumpfun_trade(
11    log: &str,
12    signature: Signature,
13    slot: u64,
14    tx_index: u64,
15    block_time_us: Option<i64>,
16    grpc_recv_us: i64,
17    is_created_buy: bool,
18) -> Option<DexEvent> {
19    // 使用栈缓冲区,避免堆分配。当前 TradeEvent tail 含 shareholders Vec,
20    // 需要比旧固定布局更大的缓冲区。
21    const MAX_DECODE_SIZE: usize = 4096;
22    let mut decode_buf: [u8; MAX_DECODE_SIZE] = [0u8; MAX_DECODE_SIZE];
23
24    // SIMD 快速查找 "Program data: "
25    let log_bytes = log.as_bytes();
26    let pos = memmem::find(log_bytes, b"Program data: ")?;
27    let data_part = log[pos + 14..].trim();
28
29    // 快速检查 discriminator(需要至少12个base64字符才能解码出8字节)
30    // base64: 每4个字符 = 3个字节,所以12个字符 = 9个字节
31    if data_part.len() < 12 {
32        return None;
33    }
34
35    // 解码 discriminator 到栈缓冲区(12个字符解码为9字节,包含完整8字节discriminator)
36    let disc_decoded_len = general_purpose::STANDARD
37        .decode_slice(&data_part.as_bytes()[..12], &mut decode_buf[..9])
38        .ok()?;
39
40    if disc_decoded_len < 8 {
41        return None;
42    }
43
44    // 检查是否为 Trade 事件 discriminator
45    const TRADE_DISCRIMINATOR: [u8; 8] = [189, 219, 127, 211, 78, 230, 97, 238];
46
47    if decode_buf[..8] != TRADE_DISCRIMINATOR {
48        return None;
49    }
50
51    // 完整解码事件数据到栈缓冲区
52    let decoded_len =
53        general_purpose::STANDARD.decode_slice(data_part.as_bytes(), &mut decode_buf).ok()?;
54
55    if decoded_len < 8 {
56        return None;
57    }
58
59    let metadata = EventMetadata {
60        signature,
61        slot,
62        tx_index,
63        block_time_us: block_time_us.unwrap_or(0),
64        grpc_recv_us,
65        recent_blockhash: None,
66    };
67
68    crate::logs::pump::parse_trade_from_data(&decode_buf[8..decoded_len], metadata, is_created_buy)
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use solana_sdk::pubkey::Pubkey;
75
76    fn push_u64(out: &mut Vec<u8>, value: u64) {
77        out.extend_from_slice(&value.to_le_bytes());
78    }
79
80    fn push_i64(out: &mut Vec<u8>, value: i64) {
81        out.extend_from_slice(&value.to_le_bytes());
82    }
83
84    fn push_pubkey(out: &mut Vec<u8>, value: Pubkey) {
85        out.extend_from_slice(value.as_ref());
86    }
87
88    fn trade_log_with_latest_tail(quote_mint: Pubkey, shareholder: Pubkey) -> String {
89        let mut data = Vec::new();
90        data.extend_from_slice(&[189, 219, 127, 211, 78, 230, 97, 238]);
91        push_pubkey(&mut data, Pubkey::new_unique()); // mint
92        push_u64(&mut data, 1_000); // sol_amount
93        push_u64(&mut data, 2_000); // token_amount
94        data.push(1); // is_buy
95        push_pubkey(&mut data, Pubkey::new_unique()); // user
96        push_i64(&mut data, 123); // timestamp
97        push_u64(&mut data, 10); // virtual_sol_reserves
98        push_u64(&mut data, 20); // virtual_token_reserves
99        push_u64(&mut data, 30); // real_sol_reserves
100        push_u64(&mut data, 40); // real_token_reserves
101        push_pubkey(&mut data, Pubkey::new_unique()); // fee_recipient
102        push_u64(&mut data, 50); // fee_basis_points
103        push_u64(&mut data, 60); // fee
104        push_pubkey(&mut data, Pubkey::new_unique()); // creator
105        push_u64(&mut data, 70); // creator_fee_basis_points
106        push_u64(&mut data, 80); // creator_fee
107        data.push(1); // track_volume
108        push_u64(&mut data, 90); // total_unclaimed_tokens
109        push_u64(&mut data, 100); // total_claimed_tokens
110        push_u64(&mut data, 110); // current_sol_volume
111        push_i64(&mut data, 120); // last_update_timestamp
112        data.extend_from_slice(&(6u32).to_le_bytes());
113        data.extend_from_slice(b"buy_v2");
114        data.push(1); // mayhem_mode
115        push_u64(&mut data, 130); // cashback_fee_basis_points
116        push_u64(&mut data, 140); // cashback
117        push_u64(&mut data, 150); // buyback_fee_basis_points
118        push_u64(&mut data, 160); // buyback_fee
119        data.extend_from_slice(&(1u32).to_le_bytes()); // shareholders len
120        push_pubkey(&mut data, shareholder);
121        data.extend_from_slice(&(250u16).to_le_bytes());
122        push_pubkey(&mut data, quote_mint);
123        push_u64(&mut data, 170); // quote_amount
124        push_u64(&mut data, 180); // virtual_quote_reserves
125        push_u64(&mut data, 190); // real_quote_reserves
126
127        format!("Program data: {}", general_purpose::STANDARD.encode(data))
128    }
129
130    #[test]
131    fn public_zero_copy_trade_parser_keeps_latest_tail_fields() {
132        let quote_mint = Pubkey::new_unique();
133        let shareholder = Pubkey::new_unique();
134        let log = trade_log_with_latest_tail(quote_mint, shareholder);
135        let event = parse_pumpfun_trade(&log, Signature::default(), 1, 0, Some(2), 3, true)
136            .expect("trade log");
137
138        match event {
139            DexEvent::PumpFunBuy(t) => {
140                assert_eq!(t.sol_amount, 1_000);
141                assert_eq!(t.token_amount, 2_000);
142                assert_eq!(t.ix_name, "buy_v2");
143                assert_eq!(t.buyback_fee_basis_points, 150);
144                assert_eq!(t.buyback_fee, 160);
145                assert_eq!(t.shareholders.len(), 1);
146                assert_eq!(t.shareholders[0].address, shareholder);
147                assert_eq!(t.shareholders[0].share_bps, 250);
148                assert_eq!(t.quote_mint, quote_mint);
149                assert_eq!(t.quote_amount, 170);
150                assert_eq!(t.virtual_quote_reserves, 180);
151                assert_eq!(t.real_quote_reserves, 190);
152                assert!(t.is_created_buy);
153            }
154            other => panic!("expected buy event, got {other:?}"),
155        }
156    }
157}