1#![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
20pub 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]);
25pub 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);
29pub const MIGRATE_BONDING_CURVE_CREATOR_EVENT: u64 =
31 u64::from_le_bytes([155, 167, 104, 220, 213, 108, 243, 3]);
32
33#[inline(always)]
36pub unsafe fn read_u64_unchecked(data: &[u8], offset: usize) -> u64 {
37 let ptr = data.as_ptr().add(offset) as *const u64;
38 u64::from_le(ptr.read_unaligned())
39}
40
41#[inline(always)]
42pub unsafe fn read_i64_unchecked(data: &[u8], offset: usize) -> i64 {
43 let ptr = data.as_ptr().add(offset) as *const i64;
44 i64::from_le(ptr.read_unaligned())
45}
46
47#[inline(always)]
48pub unsafe fn read_bool_unchecked(data: &[u8], offset: usize) -> bool {
49 *data.get_unchecked(offset) == 1
50}
51
52#[inline(always)]
53pub unsafe fn read_pubkey_unchecked(data: &[u8], offset: usize) -> Pubkey {
54 #[cfg(target_arch = "x86_64")]
55 {
56 use std::arch::x86_64::_mm_prefetch;
57 use std::arch::x86_64::_MM_HINT_T0;
58 if offset + 64 < data.len() {
59 _mm_prefetch((data.as_ptr().add(offset + 32)) as *const i8, _MM_HINT_T0);
60 }
61 }
62
63 let ptr = data.as_ptr().add(offset);
64 let mut bytes = [0u8; 32];
65 std::ptr::copy_nonoverlapping(ptr, bytes.as_mut_ptr(), 32);
66 Pubkey::new_from_array(bytes)
67}
68
69#[inline(always)]
70pub unsafe fn read_str_unchecked(data: &[u8], offset: usize) -> Option<(&str, usize)> {
71 if data.len() < offset + 4 {
72 return None;
73 }
74
75 let len = read_u32_unchecked(data, offset) as usize;
76 if data.len() < offset + 4 + len {
77 return None;
78 }
79
80 let string_bytes = &data[offset + 4..offset + 4 + len];
81 let s = std::str::from_utf8_unchecked(string_bytes);
82 Some((s, 4 + len))
83}
84
85#[inline(always)]
86pub unsafe fn read_u32_unchecked(data: &[u8], offset: usize) -> u32 {
87 let ptr = data.as_ptr().add(offset) as *const u32;
88 u32::from_le(ptr.read_unaligned())
89}
90
91static BASE64_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"Program data: "));
94const PROGRAM_DATA_TAG_LEN: usize = 14;
96
97#[inline(always)]
98pub fn extract_program_data_zero_copy<'a>(
99 log: &'a str,
100 buf: &'a mut [u8; 2048],
101) -> Option<&'a [u8]> {
102 let log_bytes = log.as_bytes();
103 let pos = BASE64_FINDER.find(log_bytes)?;
104
105 let data_part = &log[pos + PROGRAM_DATA_TAG_LEN..];
106 let trimmed = data_part.trim();
107
108 if trimmed.len() > 2700 {
109 return None;
110 }
111
112 use base64_simd::AsOut;
113 let decoded_slice =
114 base64_simd::STANDARD.decode(trimmed.as_bytes(), buf.as_mut().as_out()).ok()?;
115
116 Some(decoded_slice)
117}
118
119#[inline(always)]
120pub fn extract_discriminator_simd(log: &str) -> Option<u64> {
121 let log_bytes = log.as_bytes();
122 let pos = BASE64_FINDER.find(log_bytes)?;
123
124 let data_part = &log[pos + PROGRAM_DATA_TAG_LEN..];
125 let trimmed = data_part.trim();
126
127 if trimmed.len() < 12 {
128 return None;
129 }
130
131 use base64_simd::AsOut;
132 let mut buf = [0u8; 12];
133 base64_simd::STANDARD.decode(&trimmed.as_bytes()[..16], buf.as_mut().as_out()).ok()?;
134
135 unsafe {
136 let ptr = buf.as_ptr() as *const u64;
137 Some(ptr.read_unaligned())
138 }
139}
140
141#[inline(always)]
146pub fn parse_log(
147 log: &str,
148 signature: Signature,
149 slot: u64,
150 tx_index: u64,
151 block_time_us: Option<i64>,
152 grpc_recv_us: i64,
153 is_created_buy: bool,
154) -> Option<DexEvent> {
155 #[cfg(feature = "perf-stats")]
156 let start = std::time::Instant::now();
157
158 let mut buf = [0u8; 2048];
160 let program_data = extract_program_data_zero_copy(log, &mut buf)?;
161
162 if program_data.len() < 8 {
163 return None;
164 }
165
166 let discriminator = unsafe { read_u64_unchecked(program_data, 0) };
168 let data = &program_data[8..];
169
170 let result = match discriminator {
171 CREATE_EVENT => parse_create_event_optimized(
172 data,
173 signature,
174 slot,
175 tx_index,
176 block_time_us,
177 grpc_recv_us,
178 ),
179 TRADE_EVENT => parse_trade_event_optimized(
180 data,
181 signature,
182 slot,
183 tx_index,
184 block_time_us,
185 grpc_recv_us,
186 is_created_buy,
187 ),
188 MIGRATE_EVENT => parse_migrate_event_optimized(
189 data,
190 signature,
191 slot,
192 tx_index,
193 block_time_us,
194 grpc_recv_us,
195 ),
196 CREATE_FEE_SHARING_CONFIG_EVENT => parse_create_fee_sharing_config_event_optimized(
197 data,
198 signature,
199 slot,
200 tx_index,
201 block_time_us,
202 grpc_recv_us,
203 ),
204 MIGRATE_BONDING_CURVE_CREATOR_EVENT => parse_migrate_bonding_curve_creator_event_optimized(
205 data,
206 signature,
207 slot,
208 tx_index,
209 block_time_us,
210 grpc_recv_us,
211 ),
212 _ => None,
213 };
214
215 #[cfg(feature = "perf-stats")]
216 {
217 PARSE_COUNT.fetch_add(1, Ordering::Relaxed);
218 PARSE_TIME_NS.fetch_add(start.elapsed().as_nanos() as usize, Ordering::Relaxed);
219 }
220
221 result
222}
223
224#[inline(always)]
231fn parse_create_event_optimized(
232 data: &[u8],
233 signature: Signature,
234 slot: u64,
235 tx_index: u64,
236 block_time_us: Option<i64>,
237 grpc_recv_us: i64,
238) -> Option<DexEvent> {
239 unsafe {
240 let mut offset = 0;
241
242 let (name, name_len) = read_str_unchecked(data, offset)?;
244 offset += name_len;
245
246 let (symbol, symbol_len) = read_str_unchecked(data, offset)?;
247 offset += symbol_len;
248
249 let (uri, uri_len) = read_str_unchecked(data, offset)?;
250 offset += uri_len;
251
252 if data.len() < offset + 32 + 32 + 32 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 1 {
254 return None;
255 }
256
257 let mint = read_pubkey_unchecked(data, offset);
259 offset += 32;
260
261 let bonding_curve = read_pubkey_unchecked(data, offset);
262 offset += 32;
263
264 let user = read_pubkey_unchecked(data, offset);
265 offset += 32;
266
267 let creator = read_pubkey_unchecked(data, offset);
268 offset += 32;
269
270 let timestamp = read_i64_unchecked(data, offset);
272 offset += 8;
273
274 let virtual_token_reserves = read_u64_unchecked(data, offset);
275 offset += 8;
276
277 let virtual_sol_reserves = read_u64_unchecked(data, offset);
278 offset += 8;
279
280 let real_token_reserves = read_u64_unchecked(data, offset);
281 offset += 8;
282
283 let token_total_supply = read_u64_unchecked(data, offset);
284 offset += 8;
285
286 let token_program = if offset + 32 <= data.len() {
287 read_pubkey_unchecked(data, offset)
288 } else {
289 Pubkey::default()
290 };
291 offset += 32;
292
293 let is_mayhem_mode =
294 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
295 offset += 1;
296 let is_cashback_enabled =
297 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
298
299 let metadata = EventMetadata {
300 signature,
301 slot,
302 tx_index,
303 block_time_us: block_time_us.unwrap_or(0),
304 grpc_recv_us,
305 recent_blockhash: None,
306 };
307
308 Some(DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
311 metadata,
312 name: name.to_string(),
313 symbol: symbol.to_string(),
314 uri: uri.to_string(),
315 mint,
316 bonding_curve,
317 user,
318 creator,
319 timestamp,
320 virtual_token_reserves,
321 virtual_sol_reserves,
322 real_token_reserves,
323 token_total_supply,
324 token_program,
325 is_mayhem_mode,
326 is_cashback_enabled,
327 }))
328 }
329}
330
331#[inline(always)]
339fn parse_trade_event_optimized(
340 data: &[u8],
341 signature: Signature,
342 slot: u64,
343 tx_index: u64,
344 block_time_us: Option<i64>,
345 grpc_recv_us: i64,
346 is_created_buy: bool,
347) -> Option<DexEvent> {
348 unsafe {
349 if data.len() < 32 + 8 + 8 + 1 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 8 + 8 + 32 + 8 + 8 {
351 return None;
352 }
353
354 let mut offset = 0;
355
356 let mint = read_pubkey_unchecked(data, offset);
357 offset += 32;
358
359 let sol_amount = read_u64_unchecked(data, offset);
360 offset += 8;
361
362 let token_amount = read_u64_unchecked(data, offset);
363 offset += 8;
364
365 let is_buy = read_bool_unchecked(data, offset);
366 offset += 1;
367
368 let user = read_pubkey_unchecked(data, offset);
369 offset += 32;
370
371 let timestamp = read_i64_unchecked(data, offset);
372 offset += 8;
373
374 let virtual_sol_reserves = read_u64_unchecked(data, offset);
375 offset += 8;
376
377 let virtual_token_reserves = read_u64_unchecked(data, offset);
378 offset += 8;
379
380 let real_sol_reserves = read_u64_unchecked(data, offset);
381 offset += 8;
382
383 let real_token_reserves = read_u64_unchecked(data, offset);
384 offset += 8;
385
386 let fee_recipient = read_pubkey_unchecked(data, offset);
387 offset += 32;
388
389 let fee_basis_points = read_u64_unchecked(data, offset);
390 offset += 8;
391
392 let fee = read_u64_unchecked(data, offset);
393 offset += 8;
394
395 let creator = read_pubkey_unchecked(data, offset);
396 offset += 32;
397
398 let creator_fee_basis_points = read_u64_unchecked(data, offset);
399 offset += 8;
400
401 let creator_fee = read_u64_unchecked(data, offset);
402 offset += 8;
403
404 let track_volume =
406 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
407 offset += 1;
408
409 let total_unclaimed_tokens =
410 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
411 offset += 8;
412
413 let total_claimed_tokens =
414 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
415 offset += 8;
416
417 let current_sol_volume =
418 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
419 offset += 8;
420
421 let last_update_timestamp =
422 if offset + 8 <= data.len() { read_i64_unchecked(data, offset) } else { 0 };
423 offset += 8;
424
425 let ix_name = if offset + 4 <= data.len() {
428 if let Some((s, len)) = read_str_unchecked(data, offset) {
429 offset += len;
430 s.to_string()
431 } else {
432 String::new()
433 }
434 } else {
435 String::new()
436 };
437
438 let mayhem_mode =
440 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
441 offset += 1;
442 let cashback_fee_basis_points =
443 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
444 offset += 8;
445 let cashback = if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
446
447 let metadata = EventMetadata {
448 signature,
449 slot,
450 tx_index,
451 block_time_us: block_time_us.unwrap_or(0),
452 grpc_recv_us,
453 recent_blockhash: None,
454 };
455
456 let trade_event = PumpFunTradeEvent {
457 metadata,
458 mint,
459 sol_amount,
460 token_amount,
461 is_buy,
462 is_created_buy,
463 user,
464 timestamp,
465 virtual_sol_reserves,
466 virtual_token_reserves,
467 real_sol_reserves,
468 real_token_reserves,
469 fee_recipient,
470 fee_basis_points,
471 fee,
472 creator,
473 creator_fee_basis_points,
474 creator_fee,
475 track_volume,
476 total_unclaimed_tokens,
477 total_claimed_tokens,
478 current_sol_volume,
479 last_update_timestamp,
480 ix_name: ix_name.clone(),
481 mayhem_mode,
482 cashback_fee_basis_points,
483 cashback,
484 is_cashback_coin: cashback_fee_basis_points > 0,
485 bonding_curve: Pubkey::default(),
486 associated_bonding_curve: Pubkey::default(),
487 creator_vault: Pubkey::default(),
488 token_program: Pubkey::default(),
489 account: None,
490 };
491
492 match ix_name.as_str() {
494 "buy" | "buy_v2" => Some(DexEvent::PumpFunBuy(trade_event)),
495 "sell" | "sell_v2" => Some(DexEvent::PumpFunSell(trade_event)),
496 "buy_exact_sol_in" | "buy_exact_quote_in_v2" => {
497 Some(DexEvent::PumpFunBuyExactSolIn(trade_event))
498 }
499 _ => Some(DexEvent::PumpFunTrade(trade_event)), }
501 }
502}
503
504#[inline(always)]
506fn parse_migrate_event_optimized(
507 data: &[u8],
508 signature: Signature,
509 slot: u64,
510 tx_index: u64,
511 block_time_us: Option<i64>,
512 grpc_recv_us: i64,
513) -> Option<DexEvent> {
514 unsafe {
515 if data.len() < 32 + 32 + 8 + 8 + 8 + 32 + 8 + 32 {
517 return None;
518 }
519
520 let mut offset = 0;
521
522 let user = read_pubkey_unchecked(data, offset);
523 offset += 32;
524
525 let mint = read_pubkey_unchecked(data, offset);
526 offset += 32;
527
528 let mint_amount = read_u64_unchecked(data, offset);
529 offset += 8;
530
531 let sol_amount = read_u64_unchecked(data, offset);
532 offset += 8;
533
534 let pool_migration_fee = read_u64_unchecked(data, offset);
535 offset += 8;
536
537 let bonding_curve = read_pubkey_unchecked(data, offset);
538 offset += 32;
539
540 let timestamp = read_i64_unchecked(data, offset);
541 offset += 8;
542
543 let pool = read_pubkey_unchecked(data, offset);
544
545 let metadata = EventMetadata {
546 signature,
547 slot,
548 tx_index,
549 block_time_us: block_time_us.unwrap_or(0),
550 grpc_recv_us,
551 recent_blockhash: None,
552 };
553
554 Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
555 metadata,
556 user,
557 mint,
558 mint_amount,
559 sol_amount,
560 pool_migration_fee,
561 bonding_curve,
562 timestamp,
563 pool,
564 }))
565 }
566}
567
568#[inline(always)]
569fn parse_migrate_bonding_curve_creator_event_optimized(
570 data: &[u8],
571 signature: Signature,
572 slot: u64,
573 tx_index: u64,
574 block_time_us: Option<i64>,
575 grpc_recv_us: i64,
576) -> Option<DexEvent> {
577 let metadata = EventMetadata {
578 signature,
579 slot,
580 tx_index,
581 block_time_us: block_time_us.unwrap_or(0),
582 grpc_recv_us,
583 recent_blockhash: None,
584 };
585 parse_migrate_bonding_curve_creator_from_data(data, metadata)
586}
587
588#[inline(always)]
589fn parse_create_fee_sharing_config_event_optimized(
590 data: &[u8],
591 signature: Signature,
592 slot: u64,
593 tx_index: u64,
594 block_time_us: Option<i64>,
595 grpc_recv_us: i64,
596) -> Option<DexEvent> {
597 let metadata = EventMetadata {
598 signature,
599 slot,
600 tx_index,
601 block_time_us: block_time_us.unwrap_or(0),
602 grpc_recv_us,
603 recent_blockhash: None,
604 };
605 crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(data, metadata)
606}
607
608#[inline(always)]
616pub fn get_event_type_fast(log: &str) -> Option<u64> {
617 extract_discriminator_simd(log)
618}
619
620#[inline(always)]
622pub fn is_event_type(log: &str, discriminator: u64) -> bool {
623 extract_discriminator_simd(log) == Some(discriminator)
624}
625
626#[inline(always)]
641pub fn parse_trade_from_data(
642 data: &[u8],
643 metadata: EventMetadata,
644 is_created_buy: bool,
645) -> Option<DexEvent> {
646 unsafe {
647 if data.len() < 32 + 8 + 8 + 1 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 8 + 8 + 32 + 8 + 8 {
649 return None;
650 }
651
652 let mut offset = 0;
653
654 let mint = read_pubkey_unchecked(data, offset);
655 offset += 32;
656
657 let sol_amount = read_u64_unchecked(data, offset);
658 offset += 8;
659
660 let token_amount = read_u64_unchecked(data, offset);
661 offset += 8;
662
663 let is_buy = read_bool_unchecked(data, offset);
664 offset += 1;
665
666 let user = read_pubkey_unchecked(data, offset);
667 offset += 32;
668
669 let timestamp = read_i64_unchecked(data, offset);
670 offset += 8;
671
672 let virtual_sol_reserves = read_u64_unchecked(data, offset);
673 offset += 8;
674
675 let virtual_token_reserves = read_u64_unchecked(data, offset);
676 offset += 8;
677
678 let real_sol_reserves = read_u64_unchecked(data, offset);
679 offset += 8;
680
681 let real_token_reserves = read_u64_unchecked(data, offset);
682 offset += 8;
683
684 let fee_recipient = read_pubkey_unchecked(data, offset);
685 offset += 32;
686
687 let fee_basis_points = read_u64_unchecked(data, offset);
688 offset += 8;
689
690 let fee = read_u64_unchecked(data, offset);
691 offset += 8;
692
693 let creator = read_pubkey_unchecked(data, offset);
694 offset += 32;
695
696 let creator_fee_basis_points = read_u64_unchecked(data, offset);
697 offset += 8;
698
699 let creator_fee = read_u64_unchecked(data, offset);
700 offset += 8;
701
702 let track_volume =
704 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
705 offset += 1;
706
707 let total_unclaimed_tokens =
708 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
709 offset += 8;
710
711 let total_claimed_tokens =
712 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
713 offset += 8;
714
715 let current_sol_volume =
716 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
717 offset += 8;
718
719 let last_update_timestamp =
720 if offset + 8 <= data.len() { read_i64_unchecked(data, offset) } else { 0 };
721 offset += 8;
722
723 let ix_name = if offset + 4 <= data.len() {
724 if let Some((s, len)) = read_str_unchecked(data, offset) {
725 offset += len;
726 s.to_string()
727 } else {
728 String::new()
729 }
730 } else {
731 String::new()
732 };
733
734 let mayhem_mode =
736 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
737 offset += 1;
738 let cashback_fee_basis_points =
739 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
740 offset += 8;
741 let cashback = if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
742
743 let trade_event = PumpFunTradeEvent {
744 metadata,
745 mint,
746 sol_amount,
747 token_amount,
748 is_buy,
749 is_created_buy,
750 user,
751 timestamp,
752 virtual_sol_reserves,
753 virtual_token_reserves,
754 real_sol_reserves,
755 real_token_reserves,
756 fee_recipient,
757 fee_basis_points,
758 fee,
759 creator,
760 creator_fee_basis_points,
761 creator_fee,
762 track_volume,
763 total_unclaimed_tokens,
764 total_claimed_tokens,
765 current_sol_volume,
766 last_update_timestamp,
767 ix_name: ix_name.clone(),
768 mayhem_mode,
769 cashback_fee_basis_points,
770 cashback,
771 is_cashback_coin: cashback_fee_basis_points > 0,
772 bonding_curve: Pubkey::default(),
773 associated_bonding_curve: Pubkey::default(),
774 creator_vault: Pubkey::default(),
775 token_program: Pubkey::default(),
776 account: None,
777 };
778
779 match ix_name.as_str() {
781 "buy" | "buy_v2" => Some(DexEvent::PumpFunBuy(trade_event)),
782 "sell" | "sell_v2" => Some(DexEvent::PumpFunSell(trade_event)),
783 "buy_exact_sol_in" | "buy_exact_quote_in_v2" => {
784 Some(DexEvent::PumpFunBuyExactSolIn(trade_event))
785 }
786 _ => Some(DexEvent::PumpFunTrade(trade_event)),
787 }
788 }
789}
790
791#[inline(always)]
795pub fn parse_buy_from_data(
796 data: &[u8],
797 metadata: EventMetadata,
798 is_created_buy: bool,
799) -> Option<DexEvent> {
800 let event = parse_trade_from_data(data, metadata, is_created_buy)?;
801 match &event {
802 DexEvent::PumpFunBuy(_) => Some(event),
803 _ => None,
804 }
805}
806
807#[inline(always)]
811pub fn parse_sell_from_data(
812 data: &[u8],
813 metadata: EventMetadata,
814 is_created_buy: bool,
815) -> Option<DexEvent> {
816 let event = parse_trade_from_data(data, metadata, is_created_buy)?;
817 match &event {
818 DexEvent::PumpFunSell(_) => Some(event),
819 _ => None,
820 }
821}
822
823#[inline(always)]
827pub fn parse_buy_exact_sol_in_from_data(
828 data: &[u8],
829 metadata: EventMetadata,
830 is_created_buy: bool,
831) -> Option<DexEvent> {
832 let event = parse_trade_from_data(data, metadata, is_created_buy)?;
833 match &event {
834 DexEvent::PumpFunBuyExactSolIn(_) => Some(event),
835 _ => None,
836 }
837}
838
839#[inline(always)]
841pub fn parse_create_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
842 unsafe {
843 let mut offset = 0;
844
845 let (name, name_len) = read_str_unchecked(data, offset)?;
846 offset += name_len;
847
848 let (symbol, symbol_len) = read_str_unchecked(data, offset)?;
849 offset += symbol_len;
850
851 let (uri, uri_len) = read_str_unchecked(data, offset)?;
852 offset += uri_len;
853
854 if data.len() < offset + 32 + 32 + 32 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 1 {
855 return None;
856 }
857
858 let mint = read_pubkey_unchecked(data, offset);
859 offset += 32;
860
861 let bonding_curve = read_pubkey_unchecked(data, offset);
862 offset += 32;
863
864 let user = read_pubkey_unchecked(data, offset);
865 offset += 32;
866
867 let creator = read_pubkey_unchecked(data, offset);
868 offset += 32;
869
870 let timestamp = read_i64_unchecked(data, offset);
871 offset += 8;
872
873 let virtual_token_reserves = read_u64_unchecked(data, offset);
874 offset += 8;
875
876 let virtual_sol_reserves = read_u64_unchecked(data, offset);
877 offset += 8;
878
879 let real_token_reserves = read_u64_unchecked(data, offset);
880 offset += 8;
881
882 let token_total_supply = read_u64_unchecked(data, offset);
883 offset += 8;
884
885 let token_program = if offset + 32 <= data.len() {
886 read_pubkey_unchecked(data, offset)
887 } else {
888 Pubkey::default()
889 };
890 offset += 32;
891
892 let is_mayhem_mode =
893 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
894 offset += 1;
895 let is_cashback_enabled =
896 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
897
898 Some(DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
899 metadata,
900 name: name.to_string(),
901 symbol: symbol.to_string(),
902 uri: uri.to_string(),
903 mint,
904 bonding_curve,
905 user,
906 creator,
907 timestamp,
908 virtual_token_reserves,
909 virtual_sol_reserves,
910 real_token_reserves,
911 token_total_supply,
912 token_program,
913 is_mayhem_mode,
914 is_cashback_enabled,
915 }))
916 }
917}
918
919#[inline(always)]
921pub fn parse_migrate_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
922 unsafe {
923 if data.len() < 32 + 32 + 8 + 8 + 8 + 32 + 8 + 32 {
924 return None;
925 }
926
927 let mut offset = 0;
928
929 let user = read_pubkey_unchecked(data, offset);
930 offset += 32;
931
932 let mint = read_pubkey_unchecked(data, offset);
933 offset += 32;
934
935 let mint_amount = read_u64_unchecked(data, offset);
936 offset += 8;
937
938 let sol_amount = read_u64_unchecked(data, offset);
939 offset += 8;
940
941 let pool_migration_fee = read_u64_unchecked(data, offset);
942 offset += 8;
943
944 let bonding_curve = read_pubkey_unchecked(data, offset);
945 offset += 32;
946
947 let timestamp = read_i64_unchecked(data, offset);
948 offset += 8;
949
950 let pool = read_pubkey_unchecked(data, offset);
951
952 Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
953 metadata,
954 user,
955 mint,
956 mint_amount,
957 sol_amount,
958 pool_migration_fee,
959 bonding_curve,
960 timestamp,
961 pool,
962 }))
963 }
964}
965
966#[inline(always)]
968pub fn parse_migrate_bonding_curve_creator_from_data(
969 data: &[u8],
970 metadata: EventMetadata,
971) -> Option<DexEvent> {
972 unsafe {
973 const NEED: usize = 8 + 32 * 5;
974 if data.len() < NEED {
975 return None;
976 }
977
978 let mut offset = 0usize;
979 let timestamp = read_i64_unchecked(data, offset);
980 offset += 8;
981 let mint = read_pubkey_unchecked(data, offset);
982 offset += 32;
983 let bonding_curve = read_pubkey_unchecked(data, offset);
984 offset += 32;
985 let sharing_config = read_pubkey_unchecked(data, offset);
986 offset += 32;
987 let old_creator = read_pubkey_unchecked(data, offset);
988 offset += 32;
989 let new_creator = read_pubkey_unchecked(data, offset);
990
991 Some(DexEvent::PumpFunMigrateBondingCurveCreator(PumpFunMigrateBondingCurveCreatorEvent {
992 metadata,
993 timestamp,
994 mint,
995 bonding_curve,
996 sharing_config,
997 old_creator,
998 new_creator,
999 }))
1000 }
1001}
1002
1003#[inline]
1005pub fn parse_create_fee_sharing_config_from_data(
1006 data: &[u8],
1007 metadata: EventMetadata,
1008) -> Option<DexEvent> {
1009 crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(data, metadata)
1010}
1011
1012#[inline(always)]
1013fn read_i64_at(data: &[u8], o: &mut usize) -> Option<i64> {
1014 if data.len() < *o + 8 {
1015 return None;
1016 }
1017 let v = i64::from_le_bytes(data[*o..*o + 8].try_into().ok()?);
1018 *o += 8;
1019 Some(v)
1020}
1021
1022#[inline(always)]
1023fn read_u16_at(data: &[u8], o: &mut usize) -> Option<u16> {
1024 if data.len() < *o + 2 {
1025 return None;
1026 }
1027 let v = u16::from_le_bytes(data[*o..*o + 2].try_into().ok()?);
1028 *o += 2;
1029 Some(v)
1030}
1031
1032#[inline(always)]
1033fn read_u32_at(data: &[u8], o: &mut usize) -> Option<u32> {
1034 if data.len() < *o + 4 {
1035 return None;
1036 }
1037 let v = u32::from_le_bytes(data[*o..*o + 4].try_into().ok()?);
1038 *o += 4;
1039 Some(v)
1040}
1041
1042#[inline(always)]
1043fn read_pubkey_at(data: &[u8], o: &mut usize) -> Option<Pubkey> {
1044 if data.len() < *o + 32 {
1045 return None;
1046 }
1047 let pk = Pubkey::new_from_array(data[*o..*o + 32].try_into().ok()?);
1048 *o += 32;
1049 Some(pk)
1050}
1051
1052#[cfg(feature = "perf-stats")]
1057pub fn get_perf_stats() -> (usize, usize) {
1058 let count = PARSE_COUNT.load(Ordering::Relaxed);
1059 let total_ns = PARSE_TIME_NS.load(Ordering::Relaxed);
1060 (count, total_ns)
1061}
1062
1063#[cfg(feature = "perf-stats")]
1064pub fn reset_perf_stats() {
1065 PARSE_COUNT.store(0, Ordering::Relaxed);
1066 PARSE_TIME_NS.store(0, Ordering::Relaxed);
1067}
1068
1069#[cfg(test)]
1070mod tests {
1071 use super::*;
1072 use crate::core::events::{DexEvent, EventMetadata};
1073
1074 #[test]
1075 fn test_discriminator_simd() {
1076 let log = "Program data: G3Kp5Dfe605nAAAAAAAAAAA=";
1078 let disc = extract_discriminator_simd(log);
1079 assert!(disc.is_some());
1080 }
1081
1082 #[test]
1083 fn test_parse_performance() {
1084 let log = "Program data: G3Kp5Dfe605nAAAAAAAAAAA=";
1086 let sig = Signature::default();
1087
1088 let start = std::time::Instant::now();
1089 for _ in 0..1000 {
1090 let _ = parse_log(log, sig, 0, 0, Some(0), 0, false);
1091 }
1092 let elapsed = start.elapsed();
1093
1094 println!("Average parse time: {} ns", elapsed.as_nanos() / 1000);
1095 }
1096
1097 #[test]
1098 fn migrate_bonding_curve_creator_roundtrip_from_data() {
1099 let ts: i64 = 1_777_920_719;
1100 let mint = Pubkey::new_unique();
1101 let bonding_curve = Pubkey::new_unique();
1102 let sharing_config = Pubkey::new_unique();
1103 let old_creator = Pubkey::new_unique();
1104 let new_creator = Pubkey::new_unique();
1105
1106 let mut buf = Vec::with_capacity(200);
1107 buf.extend_from_slice(&ts.to_le_bytes());
1108 buf.extend_from_slice(mint.as_ref());
1109 buf.extend_from_slice(bonding_curve.as_ref());
1110 buf.extend_from_slice(sharing_config.as_ref());
1111 buf.extend_from_slice(old_creator.as_ref());
1112 buf.extend_from_slice(new_creator.as_ref());
1113
1114 let metadata = EventMetadata {
1115 signature: Signature::default(),
1116 slot: 0,
1117 tx_index: 0,
1118 block_time_us: 0,
1119 grpc_recv_us: 0,
1120 recent_blockhash: None,
1121 };
1122
1123 let ev = parse_migrate_bonding_curve_creator_from_data(&buf, metadata).expect("parse");
1124 match ev {
1125 DexEvent::PumpFunMigrateBondingCurveCreator(e) => {
1126 assert_eq!(e.timestamp, ts);
1127 assert_eq!(e.mint, mint);
1128 assert_eq!(e.bonding_curve, bonding_curve);
1129 assert_eq!(e.sharing_config, sharing_config);
1130 assert_eq!(e.old_creator, old_creator);
1131 assert_eq!(e.new_creator, new_creator);
1132 }
1133 _ => panic!("wrong variant"),
1134 }
1135 }
1136}