1use crate::core::events::*;
13use memchr::memmem;
14use once_cell::sync::Lazy;
15use solana_sdk::{pubkey::Pubkey, signature::Signature};
16
17#[cfg(feature = "perf-stats")]
18use std::sync::atomic::{AtomicUsize, Ordering};
19
20#[cfg(feature = "perf-stats")]
25pub static PARSE_COUNT: AtomicUsize = AtomicUsize::new(0);
26#[cfg(feature = "perf-stats")]
27pub static PARSE_TIME_NS: AtomicUsize = AtomicUsize::new(0);
28
29pub mod discriminators {
35 pub const CREATE_EVENT: u64 = u64::from_le_bytes([27, 114, 169, 77, 222, 235, 99, 118]);
36 pub const TRADE_EVENT: u64 = u64::from_le_bytes([189, 219, 127, 211, 78, 230, 97, 238]);
37 pub const MIGRATE_EVENT: u64 = u64::from_le_bytes([189, 233, 93, 185, 92, 148, 234, 148]);
38}
39
40static BASE64_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"Program data: "));
42
43#[inline(always)]
52fn extract_program_data_zero_copy<'a>(log: &'a str, buf: &'a mut [u8; 2048]) -> Option<&'a [u8]> {
53 let log_bytes = log.as_bytes();
54 let pos = BASE64_FINDER.find(log_bytes)?;
55
56 let data_part = &log[pos + 14..];
57 let trimmed = data_part.trim();
58
59 if trimmed.len() > 2700 {
62 return None;
63 }
64
65 use base64_simd::AsOut;
67 let decoded_slice = base64_simd::STANDARD
68 .decode(trimmed.as_bytes(), buf.as_mut().as_out())
69 .ok()?;
70
71 Some(decoded_slice)
72}
73
74#[inline(always)]
76fn extract_discriminator_simd(log: &str) -> Option<u64> {
77 let log_bytes = log.as_bytes();
78 let pos = BASE64_FINDER.find(log_bytes)?;
79
80 let data_part = &log[pos + 14..];
81 let trimmed = data_part.trim();
82
83 if trimmed.len() < 12 {
84 return None;
85 }
86
87 use base64_simd::AsOut;
89 let mut buf = [0u8; 12];
90 base64_simd::STANDARD
91 .decode(&trimmed.as_bytes()[..16], buf.as_mut().as_out())
92 .ok()?;
93
94 unsafe {
96 let ptr = buf.as_ptr() as *const u64;
97 Some(ptr.read_unaligned())
98 }
99}
100
101#[inline(always)]
107unsafe fn read_u64_unchecked(data: &[u8], offset: usize) -> u64 {
108 let ptr = data.as_ptr().add(offset) as *const u64;
109 u64::from_le(ptr.read_unaligned())
110}
111
112#[inline(always)]
114unsafe fn read_i64_unchecked(data: &[u8], offset: usize) -> i64 {
115 let ptr = data.as_ptr().add(offset) as *const i64;
116 i64::from_le(ptr.read_unaligned())
117}
118
119#[inline(always)]
121unsafe fn read_bool_unchecked(data: &[u8], offset: usize) -> bool {
122 *data.get_unchecked(offset) == 1
123}
124
125#[inline(always)]
129unsafe fn read_pubkey_unchecked(data: &[u8], offset: usize) -> Pubkey {
130 #[cfg(target_arch = "x86_64")]
133 {
134 use std::arch::x86_64::_mm_prefetch;
135 use std::arch::x86_64::_MM_HINT_T0;
136 if offset + 64 < data.len() {
137 _mm_prefetch((data.as_ptr().add(offset + 32)) as *const i8, _MM_HINT_T0);
138 }
139 }
140
141 let ptr = data.as_ptr().add(offset);
142 let mut bytes = [0u8; 32];
143 std::ptr::copy_nonoverlapping(ptr, bytes.as_mut_ptr(), 32);
144 Pubkey::new_from_array(bytes)
145}
146
147#[inline(always)]
151unsafe fn read_str_unchecked(data: &[u8], offset: usize) -> Option<(&str, usize)> {
152 if data.len() < offset + 4 {
153 return None;
154 }
155
156 let len = read_u32_unchecked(data, offset) as usize;
157 if data.len() < offset + 4 + len {
158 return None;
159 }
160
161 let string_bytes = &data[offset + 4..offset + 4 + len];
162 let s = std::str::from_utf8_unchecked(string_bytes);
163 Some((s, 4 + len))
164}
165
166#[inline(always)]
168unsafe fn read_u32_unchecked(data: &[u8], offset: usize) -> u32 {
169 let ptr = data.as_ptr().add(offset) as *const u32;
170 u32::from_le(ptr.read_unaligned())
171}
172
173#[inline(always)]
181pub fn parse_log(
182 log: &str,
183 signature: Signature,
184 slot: u64,
185 tx_index: u64,
186 block_time_us: Option<i64>,
187 grpc_recv_us: i64,
188 is_created_buy: bool,
189) -> Option<DexEvent> {
190 #[cfg(feature = "perf-stats")]
191 let start = std::time::Instant::now();
192
193 let mut buf = [0u8; 2048];
195 let program_data = extract_program_data_zero_copy(log, &mut buf)?;
196
197 if program_data.len() < 8 {
198 return None;
199 }
200
201 let discriminator = unsafe { read_u64_unchecked(program_data, 0) };
203 let data = &program_data[8..];
204
205 let result = match discriminator {
206 discriminators::CREATE_EVENT => {
207 parse_create_event_optimized(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
208 }
209 discriminators::TRADE_EVENT => {
210 parse_trade_event_optimized(data, signature, slot, tx_index, block_time_us, grpc_recv_us, is_created_buy)
211 }
212 discriminators::MIGRATE_EVENT => {
213 parse_migrate_event_optimized(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
214 }
215 _ => None,
216 };
217
218 #[cfg(feature = "perf-stats")]
219 {
220 PARSE_COUNT.fetch_add(1, Ordering::Relaxed);
221 PARSE_TIME_NS.fetch_add(start.elapsed().as_nanos() as usize, Ordering::Relaxed);
222 }
223
224 result
225}
226
227#[inline(always)]
234fn parse_create_event_optimized(
235 data: &[u8],
236 signature: Signature,
237 slot: u64,
238 tx_index: u64,
239 block_time_us: Option<i64>,
240 grpc_recv_us: i64,
241) -> Option<DexEvent> {
242 unsafe {
243 let mut offset = 0;
244
245 let (name, name_len) = read_str_unchecked(data, offset)?;
247 offset += name_len;
248
249 let (symbol, symbol_len) = read_str_unchecked(data, offset)?;
250 offset += symbol_len;
251
252 let (uri, uri_len) = read_str_unchecked(data, offset)?;
253 offset += uri_len;
254
255 if data.len() < offset + 32 + 32 + 32 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 1 {
257 return None;
258 }
259
260 let mint = read_pubkey_unchecked(data, offset);
262 offset += 32;
263
264 let bonding_curve = read_pubkey_unchecked(data, offset);
265 offset += 32;
266
267 let user = read_pubkey_unchecked(data, offset);
268 offset += 32;
269
270 let creator = read_pubkey_unchecked(data, offset);
271 offset += 32;
272
273 let timestamp = read_i64_unchecked(data, offset);
275 offset += 8;
276
277 let virtual_token_reserves = read_u64_unchecked(data, offset);
278 offset += 8;
279
280 let virtual_sol_reserves = read_u64_unchecked(data, offset);
281 offset += 8;
282
283 let real_token_reserves = read_u64_unchecked(data, offset);
284 offset += 8;
285
286 let token_total_supply = read_u64_unchecked(data, offset);
287 offset += 8;
288
289 let token_program = if offset + 32 <= data.len() {
290 read_pubkey_unchecked(data, offset)
291 } else {
292 Pubkey::default()
293 };
294 offset += 32;
295
296 let is_mayhem_mode = if offset < data.len() {
297 read_bool_unchecked(data, offset)
298 } else {
299 false
300 };
301 offset += 1;
302 let is_cashback_enabled = if offset < data.len() {
303 read_bool_unchecked(data, offset)
304 } else {
305 false
306 };
307
308 let metadata = EventMetadata {
309 signature,
310 slot,
311 tx_index,
312 block_time_us: block_time_us.unwrap_or(0),
313 grpc_recv_us,
314 };
315
316 Some(DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
319 metadata,
320 name: name.to_string(),
321 symbol: symbol.to_string(),
322 uri: uri.to_string(),
323 mint,
324 bonding_curve,
325 user,
326 creator,
327 timestamp,
328 virtual_token_reserves,
329 virtual_sol_reserves,
330 real_token_reserves,
331 token_total_supply,
332 token_program,
333 is_mayhem_mode,
334 is_cashback_enabled,
335 }))
336 }
337}
338
339#[inline(always)]
347fn parse_trade_event_optimized(
348 data: &[u8],
349 signature: Signature,
350 slot: u64,
351 tx_index: u64,
352 block_time_us: Option<i64>,
353 grpc_recv_us: i64,
354 is_created_buy: bool,
355) -> Option<DexEvent> {
356 unsafe {
357 if data.len() < 32 + 8 + 8 + 1 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 8 + 8 + 32 + 8 + 8 {
359 return None;
360 }
361
362 let mut offset = 0;
363
364 let mint = read_pubkey_unchecked(data, offset);
365 offset += 32;
366
367 let sol_amount = read_u64_unchecked(data, offset);
368 offset += 8;
369
370 let token_amount = read_u64_unchecked(data, offset);
371 offset += 8;
372
373 let is_buy = read_bool_unchecked(data, offset);
374 offset += 1;
375
376 let user = read_pubkey_unchecked(data, offset);
377 offset += 32;
378
379 let timestamp = read_i64_unchecked(data, offset);
380 offset += 8;
381
382 let virtual_sol_reserves = read_u64_unchecked(data, offset);
383 offset += 8;
384
385 let virtual_token_reserves = read_u64_unchecked(data, offset);
386 offset += 8;
387
388 let real_sol_reserves = read_u64_unchecked(data, offset);
389 offset += 8;
390
391 let real_token_reserves = read_u64_unchecked(data, offset);
392 offset += 8;
393
394 let fee_recipient = read_pubkey_unchecked(data, offset);
395 offset += 32;
396
397 let fee_basis_points = read_u64_unchecked(data, offset);
398 offset += 8;
399
400 let fee = read_u64_unchecked(data, offset);
401 offset += 8;
402
403 let creator = read_pubkey_unchecked(data, offset);
404 offset += 32;
405
406 let creator_fee_basis_points = read_u64_unchecked(data, offset);
407 offset += 8;
408
409 let creator_fee = read_u64_unchecked(data, offset);
410 offset += 8;
411
412 let track_volume = if offset < data.len() {
414 read_bool_unchecked(data, offset)
415 } else {
416 false
417 };
418 offset += 1;
419
420 let total_unclaimed_tokens = if offset + 8 <= data.len() {
421 read_u64_unchecked(data, offset)
422 } else {
423 0
424 };
425 offset += 8;
426
427 let total_claimed_tokens = if offset + 8 <= data.len() {
428 read_u64_unchecked(data, offset)
429 } else {
430 0
431 };
432 offset += 8;
433
434 let current_sol_volume = if offset + 8 <= data.len() {
435 read_u64_unchecked(data, offset)
436 } else {
437 0
438 };
439 offset += 8;
440
441 let last_update_timestamp = if offset + 8 <= data.len() {
442 read_i64_unchecked(data, offset)
443 } else {
444 0
445 };
446 offset += 8;
447
448 let ix_name = if offset + 4 <= data.len() {
451 if let Some((s, len)) = read_str_unchecked(data, offset) {
452 offset += len;
453 s.to_string()
454 } else {
455 String::new()
456 }
457 } else {
458 String::new()
459 };
460
461 let mayhem_mode = if offset < data.len() {
463 read_bool_unchecked(data, offset)
464 } else {
465 false
466 };
467 offset += 1;
468 let cashback_fee_basis_points = if offset + 8 <= data.len() {
469 read_u64_unchecked(data, offset)
470 } else {
471 0
472 };
473 offset += 8;
474 let cashback = if offset + 8 <= data.len() {
475 read_u64_unchecked(data, offset)
476 } else {
477 0
478 };
479
480 let metadata = EventMetadata {
481 signature,
482 slot,
483 tx_index,
484 block_time_us: block_time_us.unwrap_or(0),
485 grpc_recv_us,
486 };
487
488 let trade_event = PumpFunTradeEvent {
489 metadata,
490 mint,
491 sol_amount,
492 token_amount,
493 is_buy,
494 is_created_buy,
495 user,
496 timestamp,
497 virtual_sol_reserves,
498 virtual_token_reserves,
499 real_sol_reserves,
500 real_token_reserves,
501 fee_recipient,
502 fee_basis_points,
503 fee,
504 creator,
505 creator_fee_basis_points,
506 creator_fee,
507 track_volume,
508 total_unclaimed_tokens,
509 total_claimed_tokens,
510 current_sol_volume,
511 last_update_timestamp,
512 ix_name: ix_name.clone(),
513 mayhem_mode,
514 cashback_fee_basis_points,
515 cashback,
516 is_cashback_coin: cashback_fee_basis_points > 0,
517 bonding_curve: Pubkey::default(),
518 associated_bonding_curve: Pubkey::default(),
519 creator_vault: Pubkey::default(),
520 token_program: Pubkey::default(),
521 };
522
523 match ix_name.as_str() {
525 "buy" => Some(DexEvent::PumpFunBuy(trade_event)),
526 "sell" => Some(DexEvent::PumpFunSell(trade_event)),
527 "buy_exact_sol_in" => Some(DexEvent::PumpFunBuyExactSolIn(trade_event)),
528 _ => Some(DexEvent::PumpFunTrade(trade_event)), }
530 }
531}
532
533#[inline(always)]
535fn parse_migrate_event_optimized(
536 data: &[u8],
537 signature: Signature,
538 slot: u64,
539 tx_index: u64,
540 block_time_us: Option<i64>,
541 grpc_recv_us: i64,
542) -> Option<DexEvent> {
543 unsafe {
544 if data.len() < 32 + 32 + 8 + 8 + 8 + 32 + 8 + 32 {
546 return None;
547 }
548
549 let mut offset = 0;
550
551 let user = read_pubkey_unchecked(data, offset);
552 offset += 32;
553
554 let mint = read_pubkey_unchecked(data, offset);
555 offset += 32;
556
557 let mint_amount = read_u64_unchecked(data, offset);
558 offset += 8;
559
560 let sol_amount = read_u64_unchecked(data, offset);
561 offset += 8;
562
563 let pool_migration_fee = read_u64_unchecked(data, offset);
564 offset += 8;
565
566 let bonding_curve = read_pubkey_unchecked(data, offset);
567 offset += 32;
568
569 let timestamp = read_i64_unchecked(data, offset);
570 offset += 8;
571
572 let pool = read_pubkey_unchecked(data, offset);
573
574 let metadata = EventMetadata {
575 signature,
576 slot,
577 tx_index,
578 block_time_us: block_time_us.unwrap_or(0),
579 grpc_recv_us,
580 };
581
582 Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
583 metadata,
584 user,
585 mint,
586 mint_amount,
587 sol_amount,
588 pool_migration_fee,
589 bonding_curve,
590 timestamp,
591 pool,
592 }))
593 }
594}
595
596#[inline(always)]
604pub fn get_event_type_fast(log: &str) -> Option<u64> {
605 extract_discriminator_simd(log)
606}
607
608#[inline(always)]
610pub fn is_event_type(log: &str, discriminator: u64) -> bool {
611 extract_discriminator_simd(log) == Some(discriminator)
612}
613
614#[inline(always)]
629pub fn parse_trade_from_data(data: &[u8], metadata: EventMetadata, is_created_buy: bool) -> Option<DexEvent> {
630 unsafe {
631 if data.len() < 32 + 8 + 8 + 1 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 8 + 8 + 32 + 8 + 8 {
633 return None;
634 }
635
636 let mut offset = 0;
637
638 let mint = read_pubkey_unchecked(data, offset);
639 offset += 32;
640
641 let sol_amount = read_u64_unchecked(data, offset);
642 offset += 8;
643
644 let token_amount = read_u64_unchecked(data, offset);
645 offset += 8;
646
647 let is_buy = read_bool_unchecked(data, offset);
648 offset += 1;
649
650 let user = read_pubkey_unchecked(data, offset);
651 offset += 32;
652
653 let timestamp = read_i64_unchecked(data, offset);
654 offset += 8;
655
656 let virtual_sol_reserves = read_u64_unchecked(data, offset);
657 offset += 8;
658
659 let virtual_token_reserves = read_u64_unchecked(data, offset);
660 offset += 8;
661
662 let real_sol_reserves = read_u64_unchecked(data, offset);
663 offset += 8;
664
665 let real_token_reserves = read_u64_unchecked(data, offset);
666 offset += 8;
667
668 let fee_recipient = read_pubkey_unchecked(data, offset);
669 offset += 32;
670
671 let fee_basis_points = read_u64_unchecked(data, offset);
672 offset += 8;
673
674 let fee = read_u64_unchecked(data, offset);
675 offset += 8;
676
677 let creator = read_pubkey_unchecked(data, offset);
678 offset += 32;
679
680 let creator_fee_basis_points = read_u64_unchecked(data, offset);
681 offset += 8;
682
683 let creator_fee = read_u64_unchecked(data, offset);
684 offset += 8;
685
686 let track_volume = if offset < data.len() {
688 read_bool_unchecked(data, offset)
689 } else {
690 false
691 };
692 offset += 1;
693
694 let total_unclaimed_tokens = if offset + 8 <= data.len() {
695 read_u64_unchecked(data, offset)
696 } else {
697 0
698 };
699 offset += 8;
700
701 let total_claimed_tokens = if offset + 8 <= data.len() {
702 read_u64_unchecked(data, offset)
703 } else {
704 0
705 };
706 offset += 8;
707
708 let current_sol_volume = if offset + 8 <= data.len() {
709 read_u64_unchecked(data, offset)
710 } else {
711 0
712 };
713 offset += 8;
714
715 let last_update_timestamp = if offset + 8 <= data.len() {
716 read_i64_unchecked(data, offset)
717 } else {
718 0
719 };
720 offset += 8;
721
722 let ix_name = if offset + 4 <= data.len() {
723 if let Some((s, len)) = read_str_unchecked(data, offset) {
724 offset += len;
725 s.to_string()
726 } else {
727 String::new()
728 }
729 } else {
730 String::new()
731 };
732
733 let mayhem_mode = if offset < data.len() {
735 read_bool_unchecked(data, offset)
736 } else {
737 false
738 };
739 offset += 1;
740 let cashback_fee_basis_points = if offset + 8 <= data.len() {
741 read_u64_unchecked(data, offset)
742 } else {
743 0
744 };
745 offset += 8;
746 let cashback = if offset + 8 <= data.len() {
747 read_u64_unchecked(data, offset)
748 } else {
749 0
750 };
751
752 let trade_event = PumpFunTradeEvent {
753 metadata,
754 mint,
755 sol_amount,
756 token_amount,
757 is_buy,
758 is_created_buy,
759 user,
760 timestamp,
761 virtual_sol_reserves,
762 virtual_token_reserves,
763 real_sol_reserves,
764 real_token_reserves,
765 fee_recipient,
766 fee_basis_points,
767 fee,
768 creator,
769 creator_fee_basis_points,
770 creator_fee,
771 track_volume,
772 total_unclaimed_tokens,
773 total_claimed_tokens,
774 current_sol_volume,
775 last_update_timestamp,
776 ix_name: ix_name.clone(),
777 mayhem_mode,
778 cashback_fee_basis_points,
779 cashback,
780 is_cashback_coin: cashback_fee_basis_points > 0,
781 bonding_curve: Pubkey::default(),
782 associated_bonding_curve: Pubkey::default(),
783 creator_vault: Pubkey::default(),
784 token_program: Pubkey::default(),
785 };
786
787 match ix_name.as_str() {
789 "buy" => Some(DexEvent::PumpFunBuy(trade_event)),
790 "sell" => Some(DexEvent::PumpFunSell(trade_event)),
791 "buy_exact_sol_in" => Some(DexEvent::PumpFunBuyExactSolIn(trade_event)),
792 _ => Some(DexEvent::PumpFunTrade(trade_event)),
793 }
794 }
795}
796
797#[inline(always)]
801pub fn parse_buy_from_data(data: &[u8], metadata: EventMetadata, is_created_buy: bool) -> Option<DexEvent> {
802 let event = parse_trade_from_data(data, metadata, is_created_buy)?;
803 match &event {
804 DexEvent::PumpFunBuy(_) => Some(event),
805 _ => None,
806 }
807}
808
809#[inline(always)]
813pub fn parse_sell_from_data(data: &[u8], metadata: EventMetadata, is_created_buy: bool) -> Option<DexEvent> {
814 let event = parse_trade_from_data(data, metadata, is_created_buy)?;
815 match &event {
816 DexEvent::PumpFunSell(_) => Some(event),
817 _ => None,
818 }
819}
820
821#[inline(always)]
825pub fn parse_buy_exact_sol_in_from_data(data: &[u8], metadata: EventMetadata, is_created_buy: bool) -> Option<DexEvent> {
826 let event = parse_trade_from_data(data, metadata, is_created_buy)?;
827 match &event {
828 DexEvent::PumpFunBuyExactSolIn(_) => Some(event),
829 _ => None,
830 }
831}
832
833#[inline(always)]
835pub fn parse_create_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
836 unsafe {
837 let mut offset = 0;
838
839 let (name, name_len) = read_str_unchecked(data, offset)?;
840 offset += name_len;
841
842 let (symbol, symbol_len) = read_str_unchecked(data, offset)?;
843 offset += symbol_len;
844
845 let (uri, uri_len) = read_str_unchecked(data, offset)?;
846 offset += uri_len;
847
848 if data.len() < offset + 32 + 32 + 32 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 1 {
849 return None;
850 }
851
852 let mint = read_pubkey_unchecked(data, offset);
853 offset += 32;
854
855 let bonding_curve = read_pubkey_unchecked(data, offset);
856 offset += 32;
857
858 let user = read_pubkey_unchecked(data, offset);
859 offset += 32;
860
861 let creator = read_pubkey_unchecked(data, offset);
862 offset += 32;
863
864 let timestamp = read_i64_unchecked(data, offset);
865 offset += 8;
866
867 let virtual_token_reserves = read_u64_unchecked(data, offset);
868 offset += 8;
869
870 let virtual_sol_reserves = read_u64_unchecked(data, offset);
871 offset += 8;
872
873 let real_token_reserves = read_u64_unchecked(data, offset);
874 offset += 8;
875
876 let token_total_supply = read_u64_unchecked(data, offset);
877 offset += 8;
878
879 let token_program = if offset + 32 <= data.len() {
880 read_pubkey_unchecked(data, offset)
881 } else {
882 Pubkey::default()
883 };
884 offset += 32;
885
886 let is_mayhem_mode = if offset < data.len() {
887 read_bool_unchecked(data, offset)
888 } else {
889 false
890 };
891 offset += 1;
892 let is_cashback_enabled = if offset < data.len() {
893 read_bool_unchecked(data, offset)
894 } else {
895 false
896 };
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#[cfg(feature = "perf-stats")]
971pub fn get_perf_stats() -> (usize, usize) {
972 let count = PARSE_COUNT.load(Ordering::Relaxed);
973 let total_ns = PARSE_TIME_NS.load(Ordering::Relaxed);
974 (count, total_ns)
975}
976
977#[cfg(feature = "perf-stats")]
978pub fn reset_perf_stats() {
979 PARSE_COUNT.store(0, Ordering::Relaxed);
980 PARSE_TIME_NS.store(0, Ordering::Relaxed);
981}
982
983#[cfg(test)]
984mod tests {
985 use super::*;
986
987 #[test]
988 fn test_discriminator_simd() {
989 let log = "Program data: G3Kp5Dfe605nAAAAAAAAAAA=";
991 let disc = extract_discriminator_simd(log);
992 assert!(disc.is_some());
993 }
994
995 #[test]
996 fn test_parse_performance() {
997 let log = "Program data: G3Kp5Dfe605nAAAAAAAAAAA=";
999 let sig = Signature::default();
1000
1001 let start = std::time::Instant::now();
1002 for _ in 0..1000 {
1003 let _ = parse_log(log, sig, 0, 0, Some(0), 0, false);
1004 }
1005 let elapsed = start.elapsed();
1006
1007 println!("Average parse time: {} ns", elapsed.as_nanos() / 1000);
1008 }
1009}