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 recent_blockhash: None,
315 };
316
317 Some(DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
320 metadata,
321 name: name.to_string(),
322 symbol: symbol.to_string(),
323 uri: uri.to_string(),
324 mint,
325 bonding_curve,
326 user,
327 creator,
328 timestamp,
329 virtual_token_reserves,
330 virtual_sol_reserves,
331 real_token_reserves,
332 token_total_supply,
333 token_program,
334 is_mayhem_mode,
335 is_cashback_enabled,
336 }))
337 }
338}
339
340#[inline(always)]
348fn parse_trade_event_optimized(
349 data: &[u8],
350 signature: Signature,
351 slot: u64,
352 tx_index: u64,
353 block_time_us: Option<i64>,
354 grpc_recv_us: i64,
355 is_created_buy: bool,
356) -> Option<DexEvent> {
357 unsafe {
358 if data.len() < 32 + 8 + 8 + 1 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 8 + 8 + 32 + 8 + 8 {
360 return None;
361 }
362
363 let mut offset = 0;
364
365 let mint = read_pubkey_unchecked(data, offset);
366 offset += 32;
367
368 let sol_amount = read_u64_unchecked(data, offset);
369 offset += 8;
370
371 let token_amount = read_u64_unchecked(data, offset);
372 offset += 8;
373
374 let is_buy = read_bool_unchecked(data, offset);
375 offset += 1;
376
377 let user = read_pubkey_unchecked(data, offset);
378 offset += 32;
379
380 let timestamp = read_i64_unchecked(data, offset);
381 offset += 8;
382
383 let virtual_sol_reserves = read_u64_unchecked(data, offset);
384 offset += 8;
385
386 let virtual_token_reserves = read_u64_unchecked(data, offset);
387 offset += 8;
388
389 let real_sol_reserves = read_u64_unchecked(data, offset);
390 offset += 8;
391
392 let real_token_reserves = read_u64_unchecked(data, offset);
393 offset += 8;
394
395 let fee_recipient = read_pubkey_unchecked(data, offset);
396 offset += 32;
397
398 let fee_basis_points = read_u64_unchecked(data, offset);
399 offset += 8;
400
401 let fee = read_u64_unchecked(data, offset);
402 offset += 8;
403
404 let creator = read_pubkey_unchecked(data, offset);
405 offset += 32;
406
407 let creator_fee_basis_points = read_u64_unchecked(data, offset);
408 offset += 8;
409
410 let creator_fee = read_u64_unchecked(data, offset);
411 offset += 8;
412
413 let track_volume = if offset < data.len() {
415 read_bool_unchecked(data, offset)
416 } else {
417 false
418 };
419 offset += 1;
420
421 let total_unclaimed_tokens = if offset + 8 <= data.len() {
422 read_u64_unchecked(data, offset)
423 } else {
424 0
425 };
426 offset += 8;
427
428 let total_claimed_tokens = if offset + 8 <= data.len() {
429 read_u64_unchecked(data, offset)
430 } else {
431 0
432 };
433 offset += 8;
434
435 let current_sol_volume = if offset + 8 <= data.len() {
436 read_u64_unchecked(data, offset)
437 } else {
438 0
439 };
440 offset += 8;
441
442 let last_update_timestamp = if offset + 8 <= data.len() {
443 read_i64_unchecked(data, offset)
444 } else {
445 0
446 };
447 offset += 8;
448
449 let ix_name = if offset + 4 <= data.len() {
452 if let Some((s, len)) = read_str_unchecked(data, offset) {
453 offset += len;
454 s.to_string()
455 } else {
456 String::new()
457 }
458 } else {
459 String::new()
460 };
461
462 let mayhem_mode = if offset < data.len() {
464 read_bool_unchecked(data, offset)
465 } else {
466 false
467 };
468 offset += 1;
469 let cashback_fee_basis_points = if offset + 8 <= data.len() {
470 read_u64_unchecked(data, offset)
471 } else {
472 0
473 };
474 offset += 8;
475 let cashback = if offset + 8 <= data.len() {
476 read_u64_unchecked(data, offset)
477 } else {
478 0
479 };
480
481 let metadata = EventMetadata {
482 signature,
483 slot,
484 tx_index,
485 block_time_us: block_time_us.unwrap_or(0),
486 grpc_recv_us,
487 recent_blockhash: None,
488 };
489
490 let trade_event = PumpFunTradeEvent {
491 metadata,
492 mint,
493 sol_amount,
494 token_amount,
495 is_buy,
496 is_created_buy,
497 user,
498 timestamp,
499 virtual_sol_reserves,
500 virtual_token_reserves,
501 real_sol_reserves,
502 real_token_reserves,
503 fee_recipient,
504 fee_basis_points,
505 fee,
506 creator,
507 creator_fee_basis_points,
508 creator_fee,
509 track_volume,
510 total_unclaimed_tokens,
511 total_claimed_tokens,
512 current_sol_volume,
513 last_update_timestamp,
514 ix_name: ix_name.clone(),
515 mayhem_mode,
516 cashback_fee_basis_points,
517 cashback,
518 is_cashback_coin: cashback_fee_basis_points > 0,
519 bonding_curve: Pubkey::default(),
520 associated_bonding_curve: Pubkey::default(),
521 creator_vault: Pubkey::default(),
522 token_program: Pubkey::default(),
523 account: None,
524 };
525
526 match ix_name.as_str() {
528 "buy" => Some(DexEvent::PumpFunBuy(trade_event)),
529 "sell" => Some(DexEvent::PumpFunSell(trade_event)),
530 "buy_exact_sol_in" => Some(DexEvent::PumpFunBuyExactSolIn(trade_event)),
531 _ => Some(DexEvent::PumpFunTrade(trade_event)), }
533 }
534}
535
536#[inline(always)]
538fn parse_migrate_event_optimized(
539 data: &[u8],
540 signature: Signature,
541 slot: u64,
542 tx_index: u64,
543 block_time_us: Option<i64>,
544 grpc_recv_us: i64,
545) -> Option<DexEvent> {
546 unsafe {
547 if data.len() < 32 + 32 + 8 + 8 + 8 + 32 + 8 + 32 {
549 return None;
550 }
551
552 let mut offset = 0;
553
554 let user = read_pubkey_unchecked(data, offset);
555 offset += 32;
556
557 let mint = read_pubkey_unchecked(data, offset);
558 offset += 32;
559
560 let mint_amount = read_u64_unchecked(data, offset);
561 offset += 8;
562
563 let sol_amount = read_u64_unchecked(data, offset);
564 offset += 8;
565
566 let pool_migration_fee = read_u64_unchecked(data, offset);
567 offset += 8;
568
569 let bonding_curve = read_pubkey_unchecked(data, offset);
570 offset += 32;
571
572 let timestamp = read_i64_unchecked(data, offset);
573 offset += 8;
574
575 let pool = read_pubkey_unchecked(data, offset);
576
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
586 Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
587 metadata,
588 user,
589 mint,
590 mint_amount,
591 sol_amount,
592 pool_migration_fee,
593 bonding_curve,
594 timestamp,
595 pool,
596 }))
597 }
598}
599
600#[inline(always)]
608pub fn get_event_type_fast(log: &str) -> Option<u64> {
609 extract_discriminator_simd(log)
610}
611
612#[inline(always)]
614pub fn is_event_type(log: &str, discriminator: u64) -> bool {
615 extract_discriminator_simd(log) == Some(discriminator)
616}
617
618#[inline(always)]
633pub fn parse_trade_from_data(data: &[u8], metadata: EventMetadata, is_created_buy: bool) -> Option<DexEvent> {
634 unsafe {
635 if data.len() < 32 + 8 + 8 + 1 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 8 + 8 + 32 + 8 + 8 {
637 return None;
638 }
639
640 let mut offset = 0;
641
642 let mint = read_pubkey_unchecked(data, offset);
643 offset += 32;
644
645 let sol_amount = read_u64_unchecked(data, offset);
646 offset += 8;
647
648 let token_amount = read_u64_unchecked(data, offset);
649 offset += 8;
650
651 let is_buy = read_bool_unchecked(data, offset);
652 offset += 1;
653
654 let user = read_pubkey_unchecked(data, offset);
655 offset += 32;
656
657 let timestamp = read_i64_unchecked(data, offset);
658 offset += 8;
659
660 let virtual_sol_reserves = read_u64_unchecked(data, offset);
661 offset += 8;
662
663 let virtual_token_reserves = read_u64_unchecked(data, offset);
664 offset += 8;
665
666 let real_sol_reserves = read_u64_unchecked(data, offset);
667 offset += 8;
668
669 let real_token_reserves = read_u64_unchecked(data, offset);
670 offset += 8;
671
672 let fee_recipient = read_pubkey_unchecked(data, offset);
673 offset += 32;
674
675 let fee_basis_points = read_u64_unchecked(data, offset);
676 offset += 8;
677
678 let fee = read_u64_unchecked(data, offset);
679 offset += 8;
680
681 let creator = read_pubkey_unchecked(data, offset);
682 offset += 32;
683
684 let creator_fee_basis_points = read_u64_unchecked(data, offset);
685 offset += 8;
686
687 let creator_fee = read_u64_unchecked(data, offset);
688 offset += 8;
689
690 let track_volume = if offset < data.len() {
692 read_bool_unchecked(data, offset)
693 } else {
694 false
695 };
696 offset += 1;
697
698 let total_unclaimed_tokens = if offset + 8 <= data.len() {
699 read_u64_unchecked(data, offset)
700 } else {
701 0
702 };
703 offset += 8;
704
705 let total_claimed_tokens = if offset + 8 <= data.len() {
706 read_u64_unchecked(data, offset)
707 } else {
708 0
709 };
710 offset += 8;
711
712 let current_sol_volume = if offset + 8 <= data.len() {
713 read_u64_unchecked(data, offset)
714 } else {
715 0
716 };
717 offset += 8;
718
719 let last_update_timestamp = if offset + 8 <= data.len() {
720 read_i64_unchecked(data, offset)
721 } else {
722 0
723 };
724 offset += 8;
725
726 let ix_name = if offset + 4 <= data.len() {
727 if let Some((s, len)) = read_str_unchecked(data, offset) {
728 offset += len;
729 s.to_string()
730 } else {
731 String::new()
732 }
733 } else {
734 String::new()
735 };
736
737 let mayhem_mode = if offset < data.len() {
739 read_bool_unchecked(data, offset)
740 } else {
741 false
742 };
743 offset += 1;
744 let cashback_fee_basis_points = if offset + 8 <= data.len() {
745 read_u64_unchecked(data, offset)
746 } else {
747 0
748 };
749 offset += 8;
750 let cashback = if offset + 8 <= data.len() {
751 read_u64_unchecked(data, offset)
752 } else {
753 0
754 };
755
756 let trade_event = PumpFunTradeEvent {
757 metadata,
758 mint,
759 sol_amount,
760 token_amount,
761 is_buy,
762 is_created_buy,
763 user,
764 timestamp,
765 virtual_sol_reserves,
766 virtual_token_reserves,
767 real_sol_reserves,
768 real_token_reserves,
769 fee_recipient,
770 fee_basis_points,
771 fee,
772 creator,
773 creator_fee_basis_points,
774 creator_fee,
775 track_volume,
776 total_unclaimed_tokens,
777 total_claimed_tokens,
778 current_sol_volume,
779 last_update_timestamp,
780 ix_name: ix_name.clone(),
781 mayhem_mode,
782 cashback_fee_basis_points,
783 cashback,
784 is_cashback_coin: cashback_fee_basis_points > 0,
785 bonding_curve: Pubkey::default(),
786 associated_bonding_curve: Pubkey::default(),
787 creator_vault: Pubkey::default(),
788 token_program: Pubkey::default(),
789 account: None,
790 };
791
792 match ix_name.as_str() {
794 "buy" => Some(DexEvent::PumpFunBuy(trade_event)),
795 "sell" => Some(DexEvent::PumpFunSell(trade_event)),
796 "buy_exact_sol_in" => Some(DexEvent::PumpFunBuyExactSolIn(trade_event)),
797 _ => Some(DexEvent::PumpFunTrade(trade_event)),
798 }
799 }
800}
801
802#[inline(always)]
806pub fn parse_buy_from_data(data: &[u8], metadata: EventMetadata, is_created_buy: bool) -> Option<DexEvent> {
807 let event = parse_trade_from_data(data, metadata, is_created_buy)?;
808 match &event {
809 DexEvent::PumpFunBuy(_) => Some(event),
810 _ => None,
811 }
812}
813
814#[inline(always)]
818pub fn parse_sell_from_data(data: &[u8], metadata: EventMetadata, is_created_buy: bool) -> Option<DexEvent> {
819 let event = parse_trade_from_data(data, metadata, is_created_buy)?;
820 match &event {
821 DexEvent::PumpFunSell(_) => Some(event),
822 _ => None,
823 }
824}
825
826#[inline(always)]
830pub fn parse_buy_exact_sol_in_from_data(data: &[u8], metadata: EventMetadata, is_created_buy: bool) -> Option<DexEvent> {
831 let event = parse_trade_from_data(data, metadata, is_created_buy)?;
832 match &event {
833 DexEvent::PumpFunBuyExactSolIn(_) => Some(event),
834 _ => None,
835 }
836}
837
838#[inline(always)]
840pub fn parse_create_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
841 unsafe {
842 let mut offset = 0;
843
844 let (name, name_len) = read_str_unchecked(data, offset)?;
845 offset += name_len;
846
847 let (symbol, symbol_len) = read_str_unchecked(data, offset)?;
848 offset += symbol_len;
849
850 let (uri, uri_len) = read_str_unchecked(data, offset)?;
851 offset += uri_len;
852
853 if data.len() < offset + 32 + 32 + 32 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 1 {
854 return None;
855 }
856
857 let mint = read_pubkey_unchecked(data, offset);
858 offset += 32;
859
860 let bonding_curve = read_pubkey_unchecked(data, offset);
861 offset += 32;
862
863 let user = read_pubkey_unchecked(data, offset);
864 offset += 32;
865
866 let creator = read_pubkey_unchecked(data, offset);
867 offset += 32;
868
869 let timestamp = read_i64_unchecked(data, offset);
870 offset += 8;
871
872 let virtual_token_reserves = read_u64_unchecked(data, offset);
873 offset += 8;
874
875 let virtual_sol_reserves = read_u64_unchecked(data, offset);
876 offset += 8;
877
878 let real_token_reserves = read_u64_unchecked(data, offset);
879 offset += 8;
880
881 let token_total_supply = read_u64_unchecked(data, offset);
882 offset += 8;
883
884 let token_program = if offset + 32 <= data.len() {
885 read_pubkey_unchecked(data, offset)
886 } else {
887 Pubkey::default()
888 };
889 offset += 32;
890
891 let is_mayhem_mode = if offset < data.len() {
892 read_bool_unchecked(data, offset)
893 } else {
894 false
895 };
896 offset += 1;
897 let is_cashback_enabled = if offset < data.len() {
898 read_bool_unchecked(data, offset)
899 } else {
900 false
901 };
902
903 Some(DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
904 metadata,
905 name: name.to_string(),
906 symbol: symbol.to_string(),
907 uri: uri.to_string(),
908 mint,
909 bonding_curve,
910 user,
911 creator,
912 timestamp,
913 virtual_token_reserves,
914 virtual_sol_reserves,
915 real_token_reserves,
916 token_total_supply,
917 token_program,
918 is_mayhem_mode,
919 is_cashback_enabled,
920 }))
921 }
922}
923
924#[inline(always)]
926pub fn parse_migrate_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
927 unsafe {
928 if data.len() < 32 + 32 + 8 + 8 + 8 + 32 + 8 + 32 {
929 return None;
930 }
931
932 let mut offset = 0;
933
934 let user = read_pubkey_unchecked(data, offset);
935 offset += 32;
936
937 let mint = read_pubkey_unchecked(data, offset);
938 offset += 32;
939
940 let mint_amount = read_u64_unchecked(data, offset);
941 offset += 8;
942
943 let sol_amount = read_u64_unchecked(data, offset);
944 offset += 8;
945
946 let pool_migration_fee = read_u64_unchecked(data, offset);
947 offset += 8;
948
949 let bonding_curve = read_pubkey_unchecked(data, offset);
950 offset += 32;
951
952 let timestamp = read_i64_unchecked(data, offset);
953 offset += 8;
954
955 let pool = read_pubkey_unchecked(data, offset);
956
957 Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
958 metadata,
959 user,
960 mint,
961 mint_amount,
962 sol_amount,
963 pool_migration_fee,
964 bonding_curve,
965 timestamp,
966 pool,
967 }))
968 }
969}
970
971#[cfg(feature = "perf-stats")]
976pub fn get_perf_stats() -> (usize, usize) {
977 let count = PARSE_COUNT.load(Ordering::Relaxed);
978 let total_ns = PARSE_TIME_NS.load(Ordering::Relaxed);
979 (count, total_ns)
980}
981
982#[cfg(feature = "perf-stats")]
983pub fn reset_perf_stats() {
984 PARSE_COUNT.store(0, Ordering::Relaxed);
985 PARSE_TIME_NS.store(0, Ordering::Relaxed);
986}
987
988#[cfg(test)]
989mod tests {
990 use super::*;
991
992 #[test]
993 fn test_discriminator_simd() {
994 let log = "Program data: G3Kp5Dfe605nAAAAAAAAAAA=";
996 let disc = extract_discriminator_simd(log);
997 assert!(disc.is_some());
998 }
999
1000 #[test]
1001 fn test_parse_performance() {
1002 let log = "Program data: G3Kp5Dfe605nAAAAAAAAAAA=";
1004 let sig = Signature::default();
1005
1006 let start = std::time::Instant::now();
1007 for _ in 0..1000 {
1008 let _ = parse_log(log, sig, 0, 0, Some(0), 0, false);
1009 }
1010 let elapsed = start.elapsed();
1011
1012 println!("Average parse time: {} ns", elapsed.as_nanos() / 1000);
1013 }
1014}