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 =
68 base64_simd::STANDARD.decode(trimmed.as_bytes(), buf.as_mut().as_out()).ok()?;
69
70 Some(decoded_slice)
71}
72
73#[inline(always)]
75fn extract_discriminator_simd(log: &str) -> Option<u64> {
76 let log_bytes = log.as_bytes();
77 let pos = BASE64_FINDER.find(log_bytes)?;
78
79 let data_part = &log[pos + 14..];
80 let trimmed = data_part.trim();
81
82 if trimmed.len() < 12 {
83 return None;
84 }
85
86 use base64_simd::AsOut;
88 let mut buf = [0u8; 12];
89 base64_simd::STANDARD.decode(&trimmed.as_bytes()[..16], buf.as_mut().as_out()).ok()?;
90
91 unsafe {
93 let ptr = buf.as_ptr() as *const u64;
94 Some(ptr.read_unaligned())
95 }
96}
97
98#[inline(always)]
104unsafe fn read_u64_unchecked(data: &[u8], offset: usize) -> u64 {
105 let ptr = data.as_ptr().add(offset) as *const u64;
106 u64::from_le(ptr.read_unaligned())
107}
108
109#[inline(always)]
111unsafe fn read_i64_unchecked(data: &[u8], offset: usize) -> i64 {
112 let ptr = data.as_ptr().add(offset) as *const i64;
113 i64::from_le(ptr.read_unaligned())
114}
115
116#[inline(always)]
118unsafe fn read_bool_unchecked(data: &[u8], offset: usize) -> bool {
119 *data.get_unchecked(offset) == 1
120}
121
122#[inline(always)]
126unsafe fn read_pubkey_unchecked(data: &[u8], offset: usize) -> Pubkey {
127 #[cfg(target_arch = "x86_64")]
130 {
131 use std::arch::x86_64::_mm_prefetch;
132 use std::arch::x86_64::_MM_HINT_T0;
133 if offset + 64 < data.len() {
134 _mm_prefetch((data.as_ptr().add(offset + 32)) as *const i8, _MM_HINT_T0);
135 }
136 }
137
138 let ptr = data.as_ptr().add(offset);
139 let mut bytes = [0u8; 32];
140 std::ptr::copy_nonoverlapping(ptr, bytes.as_mut_ptr(), 32);
141 Pubkey::new_from_array(bytes)
142}
143
144#[inline(always)]
148unsafe fn read_str_unchecked(data: &[u8], offset: usize) -> Option<(&str, usize)> {
149 if data.len() < offset + 4 {
150 return None;
151 }
152
153 let len = read_u32_unchecked(data, offset) as usize;
154 if data.len() < offset + 4 + len {
155 return None;
156 }
157
158 let string_bytes = &data[offset + 4..offset + 4 + len];
159 let s = std::str::from_utf8_unchecked(string_bytes);
160 Some((s, 4 + len))
161}
162
163#[inline(always)]
165unsafe fn read_u32_unchecked(data: &[u8], offset: usize) -> u32 {
166 let ptr = data.as_ptr().add(offset) as *const u32;
167 u32::from_le(ptr.read_unaligned())
168}
169
170#[inline(always)]
178pub fn parse_log(
179 log: &str,
180 signature: Signature,
181 slot: u64,
182 tx_index: u64,
183 block_time_us: Option<i64>,
184 grpc_recv_us: i64,
185 is_created_buy: bool,
186) -> Option<DexEvent> {
187 #[cfg(feature = "perf-stats")]
188 let start = std::time::Instant::now();
189
190 let mut buf = [0u8; 2048];
192 let program_data = extract_program_data_zero_copy(log, &mut buf)?;
193
194 if program_data.len() < 8 {
195 return None;
196 }
197
198 let discriminator = unsafe { read_u64_unchecked(program_data, 0) };
200 let data = &program_data[8..];
201
202 let result = match discriminator {
203 discriminators::CREATE_EVENT => parse_create_event_optimized(
204 data,
205 signature,
206 slot,
207 tx_index,
208 block_time_us,
209 grpc_recv_us,
210 ),
211 discriminators::TRADE_EVENT => parse_trade_event_optimized(
212 data,
213 signature,
214 slot,
215 tx_index,
216 block_time_us,
217 grpc_recv_us,
218 is_created_buy,
219 ),
220 discriminators::MIGRATE_EVENT => parse_migrate_event_optimized(
221 data,
222 signature,
223 slot,
224 tx_index,
225 block_time_us,
226 grpc_recv_us,
227 ),
228 _ => None,
229 };
230
231 #[cfg(feature = "perf-stats")]
232 {
233 PARSE_COUNT.fetch_add(1, Ordering::Relaxed);
234 PARSE_TIME_NS.fetch_add(start.elapsed().as_nanos() as usize, Ordering::Relaxed);
235 }
236
237 result
238}
239
240#[inline(always)]
247fn parse_create_event_optimized(
248 data: &[u8],
249 signature: Signature,
250 slot: u64,
251 tx_index: u64,
252 block_time_us: Option<i64>,
253 grpc_recv_us: i64,
254) -> Option<DexEvent> {
255 unsafe {
256 let mut offset = 0;
257
258 let (name, name_len) = read_str_unchecked(data, offset)?;
260 offset += name_len;
261
262 let (symbol, symbol_len) = read_str_unchecked(data, offset)?;
263 offset += symbol_len;
264
265 let (uri, uri_len) = read_str_unchecked(data, offset)?;
266 offset += uri_len;
267
268 if data.len() < offset + 32 + 32 + 32 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 1 {
270 return None;
271 }
272
273 let mint = read_pubkey_unchecked(data, offset);
275 offset += 32;
276
277 let bonding_curve = read_pubkey_unchecked(data, offset);
278 offset += 32;
279
280 let user = read_pubkey_unchecked(data, offset);
281 offset += 32;
282
283 let creator = read_pubkey_unchecked(data, offset);
284 offset += 32;
285
286 let timestamp = read_i64_unchecked(data, offset);
288 offset += 8;
289
290 let virtual_token_reserves = read_u64_unchecked(data, offset);
291 offset += 8;
292
293 let virtual_sol_reserves = read_u64_unchecked(data, offset);
294 offset += 8;
295
296 let real_token_reserves = read_u64_unchecked(data, offset);
297 offset += 8;
298
299 let token_total_supply = read_u64_unchecked(data, offset);
300 offset += 8;
301
302 let token_program = if offset + 32 <= data.len() {
303 read_pubkey_unchecked(data, offset)
304 } else {
305 Pubkey::default()
306 };
307 offset += 32;
308
309 let is_mayhem_mode =
310 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
311 offset += 1;
312 let is_cashback_enabled =
313 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
314
315 let metadata = EventMetadata {
316 signature,
317 slot,
318 tx_index,
319 block_time_us: block_time_us.unwrap_or(0),
320 grpc_recv_us,
321 recent_blockhash: None,
322 };
323
324 Some(DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
327 metadata,
328 name: name.to_string(),
329 symbol: symbol.to_string(),
330 uri: uri.to_string(),
331 mint,
332 bonding_curve,
333 user,
334 creator,
335 timestamp,
336 virtual_token_reserves,
337 virtual_sol_reserves,
338 real_token_reserves,
339 token_total_supply,
340 token_program,
341 is_mayhem_mode,
342 is_cashback_enabled,
343 }))
344 }
345}
346
347#[inline(always)]
355fn parse_trade_event_optimized(
356 data: &[u8],
357 signature: Signature,
358 slot: u64,
359 tx_index: u64,
360 block_time_us: Option<i64>,
361 grpc_recv_us: i64,
362 is_created_buy: bool,
363) -> Option<DexEvent> {
364 unsafe {
365 if data.len() < 32 + 8 + 8 + 1 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 8 + 8 + 32 + 8 + 8 {
367 return None;
368 }
369
370 let mut offset = 0;
371
372 let mint = read_pubkey_unchecked(data, offset);
373 offset += 32;
374
375 let sol_amount = read_u64_unchecked(data, offset);
376 offset += 8;
377
378 let token_amount = read_u64_unchecked(data, offset);
379 offset += 8;
380
381 let is_buy = read_bool_unchecked(data, offset);
382 offset += 1;
383
384 let user = read_pubkey_unchecked(data, offset);
385 offset += 32;
386
387 let timestamp = read_i64_unchecked(data, offset);
388 offset += 8;
389
390 let virtual_sol_reserves = read_u64_unchecked(data, offset);
391 offset += 8;
392
393 let virtual_token_reserves = read_u64_unchecked(data, offset);
394 offset += 8;
395
396 let real_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 fee_recipient = read_pubkey_unchecked(data, offset);
403 offset += 32;
404
405 let fee_basis_points = read_u64_unchecked(data, offset);
406 offset += 8;
407
408 let fee = read_u64_unchecked(data, offset);
409 offset += 8;
410
411 let creator = read_pubkey_unchecked(data, offset);
412 offset += 32;
413
414 let creator_fee_basis_points = read_u64_unchecked(data, offset);
415 offset += 8;
416
417 let creator_fee = read_u64_unchecked(data, offset);
418 offset += 8;
419
420 let track_volume =
422 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
423 offset += 1;
424
425 let total_unclaimed_tokens =
426 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
427 offset += 8;
428
429 let total_claimed_tokens =
430 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
431 offset += 8;
432
433 let current_sol_volume =
434 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
435 offset += 8;
436
437 let last_update_timestamp =
438 if offset + 8 <= data.len() { read_i64_unchecked(data, offset) } else { 0 };
439 offset += 8;
440
441 let ix_name = if offset + 4 <= data.len() {
444 if let Some((s, len)) = read_str_unchecked(data, offset) {
445 offset += len;
446 s.to_string()
447 } else {
448 String::new()
449 }
450 } else {
451 String::new()
452 };
453
454 let mayhem_mode =
456 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
457 offset += 1;
458 let cashback_fee_basis_points =
459 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
460 offset += 8;
461 let cashback = if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
462
463 let metadata = EventMetadata {
464 signature,
465 slot,
466 tx_index,
467 block_time_us: block_time_us.unwrap_or(0),
468 grpc_recv_us,
469 recent_blockhash: None,
470 };
471
472 let trade_event = PumpFunTradeEvent {
473 metadata,
474 mint,
475 sol_amount,
476 token_amount,
477 is_buy,
478 is_created_buy,
479 user,
480 timestamp,
481 virtual_sol_reserves,
482 virtual_token_reserves,
483 real_sol_reserves,
484 real_token_reserves,
485 fee_recipient,
486 fee_basis_points,
487 fee,
488 creator,
489 creator_fee_basis_points,
490 creator_fee,
491 track_volume,
492 total_unclaimed_tokens,
493 total_claimed_tokens,
494 current_sol_volume,
495 last_update_timestamp,
496 ix_name: ix_name.clone(),
497 mayhem_mode,
498 cashback_fee_basis_points,
499 cashback,
500 is_cashback_coin: cashback_fee_basis_points > 0,
501 bonding_curve: Pubkey::default(),
502 associated_bonding_curve: Pubkey::default(),
503 creator_vault: Pubkey::default(),
504 token_program: Pubkey::default(),
505 account: None,
506 };
507
508 match ix_name.as_str() {
510 "buy" => Some(DexEvent::PumpFunBuy(trade_event)),
511 "sell" => Some(DexEvent::PumpFunSell(trade_event)),
512 "buy_exact_sol_in" => Some(DexEvent::PumpFunBuyExactSolIn(trade_event)),
513 _ => Some(DexEvent::PumpFunTrade(trade_event)), }
515 }
516}
517
518#[inline(always)]
520fn parse_migrate_event_optimized(
521 data: &[u8],
522 signature: Signature,
523 slot: u64,
524 tx_index: u64,
525 block_time_us: Option<i64>,
526 grpc_recv_us: i64,
527) -> Option<DexEvent> {
528 unsafe {
529 if data.len() < 32 + 32 + 8 + 8 + 8 + 32 + 8 + 32 {
531 return None;
532 }
533
534 let mut offset = 0;
535
536 let user = read_pubkey_unchecked(data, offset);
537 offset += 32;
538
539 let mint = read_pubkey_unchecked(data, offset);
540 offset += 32;
541
542 let mint_amount = read_u64_unchecked(data, offset);
543 offset += 8;
544
545 let sol_amount = read_u64_unchecked(data, offset);
546 offset += 8;
547
548 let pool_migration_fee = read_u64_unchecked(data, offset);
549 offset += 8;
550
551 let bonding_curve = read_pubkey_unchecked(data, offset);
552 offset += 32;
553
554 let timestamp = read_i64_unchecked(data, offset);
555 offset += 8;
556
557 let pool = read_pubkey_unchecked(data, offset);
558
559 let metadata = EventMetadata {
560 signature,
561 slot,
562 tx_index,
563 block_time_us: block_time_us.unwrap_or(0),
564 grpc_recv_us,
565 recent_blockhash: None,
566 };
567
568 Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
569 metadata,
570 user,
571 mint,
572 mint_amount,
573 sol_amount,
574 pool_migration_fee,
575 bonding_curve,
576 timestamp,
577 pool,
578 }))
579 }
580}
581
582#[inline(always)]
590pub fn get_event_type_fast(log: &str) -> Option<u64> {
591 extract_discriminator_simd(log)
592}
593
594#[inline(always)]
596pub fn is_event_type(log: &str, discriminator: u64) -> bool {
597 extract_discriminator_simd(log) == Some(discriminator)
598}
599
600#[inline(always)]
615pub fn parse_trade_from_data(
616 data: &[u8],
617 metadata: EventMetadata,
618 is_created_buy: bool,
619) -> Option<DexEvent> {
620 unsafe {
621 if data.len() < 32 + 8 + 8 + 1 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 8 + 8 + 32 + 8 + 8 {
623 return None;
624 }
625
626 let mut offset = 0;
627
628 let mint = read_pubkey_unchecked(data, offset);
629 offset += 32;
630
631 let sol_amount = read_u64_unchecked(data, offset);
632 offset += 8;
633
634 let token_amount = read_u64_unchecked(data, offset);
635 offset += 8;
636
637 let is_buy = read_bool_unchecked(data, offset);
638 offset += 1;
639
640 let user = read_pubkey_unchecked(data, offset);
641 offset += 32;
642
643 let timestamp = read_i64_unchecked(data, offset);
644 offset += 8;
645
646 let virtual_sol_reserves = read_u64_unchecked(data, offset);
647 offset += 8;
648
649 let virtual_token_reserves = read_u64_unchecked(data, offset);
650 offset += 8;
651
652 let real_sol_reserves = read_u64_unchecked(data, offset);
653 offset += 8;
654
655 let real_token_reserves = read_u64_unchecked(data, offset);
656 offset += 8;
657
658 let fee_recipient = read_pubkey_unchecked(data, offset);
659 offset += 32;
660
661 let fee_basis_points = read_u64_unchecked(data, offset);
662 offset += 8;
663
664 let fee = read_u64_unchecked(data, offset);
665 offset += 8;
666
667 let creator = read_pubkey_unchecked(data, offset);
668 offset += 32;
669
670 let creator_fee_basis_points = read_u64_unchecked(data, offset);
671 offset += 8;
672
673 let creator_fee = read_u64_unchecked(data, offset);
674 offset += 8;
675
676 let track_volume =
678 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
679 offset += 1;
680
681 let total_unclaimed_tokens =
682 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
683 offset += 8;
684
685 let total_claimed_tokens =
686 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
687 offset += 8;
688
689 let current_sol_volume =
690 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
691 offset += 8;
692
693 let last_update_timestamp =
694 if offset + 8 <= data.len() { read_i64_unchecked(data, offset) } else { 0 };
695 offset += 8;
696
697 let ix_name = if offset + 4 <= data.len() {
698 if let Some((s, len)) = read_str_unchecked(data, offset) {
699 offset += len;
700 s.to_string()
701 } else {
702 String::new()
703 }
704 } else {
705 String::new()
706 };
707
708 let mayhem_mode =
710 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
711 offset += 1;
712 let cashback_fee_basis_points =
713 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
714 offset += 8;
715 let cashback = if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
716
717 let trade_event = PumpFunTradeEvent {
718 metadata,
719 mint,
720 sol_amount,
721 token_amount,
722 is_buy,
723 is_created_buy,
724 user,
725 timestamp,
726 virtual_sol_reserves,
727 virtual_token_reserves,
728 real_sol_reserves,
729 real_token_reserves,
730 fee_recipient,
731 fee_basis_points,
732 fee,
733 creator,
734 creator_fee_basis_points,
735 creator_fee,
736 track_volume,
737 total_unclaimed_tokens,
738 total_claimed_tokens,
739 current_sol_volume,
740 last_update_timestamp,
741 ix_name: ix_name.clone(),
742 mayhem_mode,
743 cashback_fee_basis_points,
744 cashback,
745 is_cashback_coin: cashback_fee_basis_points > 0,
746 bonding_curve: Pubkey::default(),
747 associated_bonding_curve: Pubkey::default(),
748 creator_vault: Pubkey::default(),
749 token_program: Pubkey::default(),
750 account: None,
751 };
752
753 match ix_name.as_str() {
755 "buy" => Some(DexEvent::PumpFunBuy(trade_event)),
756 "sell" => Some(DexEvent::PumpFunSell(trade_event)),
757 "buy_exact_sol_in" => Some(DexEvent::PumpFunBuyExactSolIn(trade_event)),
758 _ => Some(DexEvent::PumpFunTrade(trade_event)),
759 }
760 }
761}
762
763#[inline(always)]
767pub fn parse_buy_from_data(
768 data: &[u8],
769 metadata: EventMetadata,
770 is_created_buy: bool,
771) -> Option<DexEvent> {
772 let event = parse_trade_from_data(data, metadata, is_created_buy)?;
773 match &event {
774 DexEvent::PumpFunBuy(_) => Some(event),
775 _ => None,
776 }
777}
778
779#[inline(always)]
783pub fn parse_sell_from_data(
784 data: &[u8],
785 metadata: EventMetadata,
786 is_created_buy: bool,
787) -> Option<DexEvent> {
788 let event = parse_trade_from_data(data, metadata, is_created_buy)?;
789 match &event {
790 DexEvent::PumpFunSell(_) => Some(event),
791 _ => None,
792 }
793}
794
795#[inline(always)]
799pub fn parse_buy_exact_sol_in_from_data(
800 data: &[u8],
801 metadata: EventMetadata,
802 is_created_buy: bool,
803) -> Option<DexEvent> {
804 let event = parse_trade_from_data(data, metadata, is_created_buy)?;
805 match &event {
806 DexEvent::PumpFunBuyExactSolIn(_) => Some(event),
807 _ => None,
808 }
809}
810
811#[inline(always)]
813pub fn parse_create_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
814 unsafe {
815 let mut offset = 0;
816
817 let (name, name_len) = read_str_unchecked(data, offset)?;
818 offset += name_len;
819
820 let (symbol, symbol_len) = read_str_unchecked(data, offset)?;
821 offset += symbol_len;
822
823 let (uri, uri_len) = read_str_unchecked(data, offset)?;
824 offset += uri_len;
825
826 if data.len() < offset + 32 + 32 + 32 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 1 {
827 return None;
828 }
829
830 let mint = read_pubkey_unchecked(data, offset);
831 offset += 32;
832
833 let bonding_curve = read_pubkey_unchecked(data, offset);
834 offset += 32;
835
836 let user = read_pubkey_unchecked(data, offset);
837 offset += 32;
838
839 let creator = read_pubkey_unchecked(data, offset);
840 offset += 32;
841
842 let timestamp = read_i64_unchecked(data, offset);
843 offset += 8;
844
845 let virtual_token_reserves = read_u64_unchecked(data, offset);
846 offset += 8;
847
848 let virtual_sol_reserves = read_u64_unchecked(data, offset);
849 offset += 8;
850
851 let real_token_reserves = read_u64_unchecked(data, offset);
852 offset += 8;
853
854 let token_total_supply = read_u64_unchecked(data, offset);
855 offset += 8;
856
857 let token_program = if offset + 32 <= data.len() {
858 read_pubkey_unchecked(data, offset)
859 } else {
860 Pubkey::default()
861 };
862 offset += 32;
863
864 let is_mayhem_mode =
865 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
866 offset += 1;
867 let is_cashback_enabled =
868 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
869
870 Some(DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
871 metadata,
872 name: name.to_string(),
873 symbol: symbol.to_string(),
874 uri: uri.to_string(),
875 mint,
876 bonding_curve,
877 user,
878 creator,
879 timestamp,
880 virtual_token_reserves,
881 virtual_sol_reserves,
882 real_token_reserves,
883 token_total_supply,
884 token_program,
885 is_mayhem_mode,
886 is_cashback_enabled,
887 }))
888 }
889}
890
891#[inline(always)]
893pub fn parse_migrate_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
894 unsafe {
895 if data.len() < 32 + 32 + 8 + 8 + 8 + 32 + 8 + 32 {
896 return None;
897 }
898
899 let mut offset = 0;
900
901 let user = read_pubkey_unchecked(data, offset);
902 offset += 32;
903
904 let mint = read_pubkey_unchecked(data, offset);
905 offset += 32;
906
907 let mint_amount = read_u64_unchecked(data, offset);
908 offset += 8;
909
910 let sol_amount = read_u64_unchecked(data, offset);
911 offset += 8;
912
913 let pool_migration_fee = read_u64_unchecked(data, offset);
914 offset += 8;
915
916 let bonding_curve = read_pubkey_unchecked(data, offset);
917 offset += 32;
918
919 let timestamp = read_i64_unchecked(data, offset);
920 offset += 8;
921
922 let pool = read_pubkey_unchecked(data, offset);
923
924 Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
925 metadata,
926 user,
927 mint,
928 mint_amount,
929 sol_amount,
930 pool_migration_fee,
931 bonding_curve,
932 timestamp,
933 pool,
934 }))
935 }
936}
937
938#[cfg(feature = "perf-stats")]
943pub fn get_perf_stats() -> (usize, usize) {
944 let count = PARSE_COUNT.load(Ordering::Relaxed);
945 let total_ns = PARSE_TIME_NS.load(Ordering::Relaxed);
946 (count, total_ns)
947}
948
949#[cfg(feature = "perf-stats")]
950pub fn reset_perf_stats() {
951 PARSE_COUNT.store(0, Ordering::Relaxed);
952 PARSE_TIME_NS.store(0, Ordering::Relaxed);
953}
954
955#[cfg(test)]
956mod tests {
957 use super::*;
958
959 #[test]
960 fn test_discriminator_simd() {
961 let log = "Program data: G3Kp5Dfe605nAAAAAAAAAAA=";
963 let disc = extract_discriminator_simd(log);
964 assert!(disc.is_some());
965 }
966
967 #[test]
968 fn test_parse_performance() {
969 let log = "Program data: G3Kp5Dfe605nAAAAAAAAAAA=";
971 let sig = Signature::default();
972
973 let start = std::time::Instant::now();
974 for _ in 0..1000 {
975 let _ = parse_log(log, sig, 0, 0, Some(0), 0, false);
976 }
977 let elapsed = start.elapsed();
978
979 println!("Average parse time: {} ns", elapsed.as_nanos() / 1000);
980 }
981}