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 amount: 0,
486 max_sol_cost: 0,
487 min_sol_output: 0,
488 bonding_curve: Pubkey::default(),
489 associated_bonding_curve: Pubkey::default(),
490 creator_vault: Pubkey::default(),
491 token_program: Pubkey::default(),
492 account: None,
493 };
494
495 match ix_name.as_str() {
497 "buy" | "buy_v2" => Some(DexEvent::PumpFunBuy(trade_event)),
498 "sell" | "sell_v2" => Some(DexEvent::PumpFunSell(trade_event)),
499 "buy_exact_sol_in" | "buy_exact_quote_in_v2" => {
500 Some(DexEvent::PumpFunBuyExactSolIn(trade_event))
501 }
502 _ => Some(DexEvent::PumpFunTrade(trade_event)), }
504 }
505}
506
507#[inline(always)]
509fn parse_migrate_event_optimized(
510 data: &[u8],
511 signature: Signature,
512 slot: u64,
513 tx_index: u64,
514 block_time_us: Option<i64>,
515 grpc_recv_us: i64,
516) -> Option<DexEvent> {
517 unsafe {
518 if data.len() < 32 + 32 + 8 + 8 + 8 + 32 + 8 + 32 {
520 return None;
521 }
522
523 let mut offset = 0;
524
525 let user = read_pubkey_unchecked(data, offset);
526 offset += 32;
527
528 let mint = read_pubkey_unchecked(data, offset);
529 offset += 32;
530
531 let mint_amount = read_u64_unchecked(data, offset);
532 offset += 8;
533
534 let sol_amount = read_u64_unchecked(data, offset);
535 offset += 8;
536
537 let pool_migration_fee = read_u64_unchecked(data, offset);
538 offset += 8;
539
540 let bonding_curve = read_pubkey_unchecked(data, offset);
541 offset += 32;
542
543 let timestamp = read_i64_unchecked(data, offset);
544 offset += 8;
545
546 let pool = read_pubkey_unchecked(data, offset);
547
548 let metadata = EventMetadata {
549 signature,
550 slot,
551 tx_index,
552 block_time_us: block_time_us.unwrap_or(0),
553 grpc_recv_us,
554 recent_blockhash: None,
555 };
556
557 Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
558 metadata,
559 user,
560 mint,
561 mint_amount,
562 sol_amount,
563 pool_migration_fee,
564 bonding_curve,
565 timestamp,
566 pool,
567 }))
568 }
569}
570
571#[inline(always)]
572fn parse_migrate_bonding_curve_creator_event_optimized(
573 data: &[u8],
574 signature: Signature,
575 slot: u64,
576 tx_index: u64,
577 block_time_us: Option<i64>,
578 grpc_recv_us: i64,
579) -> Option<DexEvent> {
580 let metadata = EventMetadata {
581 signature,
582 slot,
583 tx_index,
584 block_time_us: block_time_us.unwrap_or(0),
585 grpc_recv_us,
586 recent_blockhash: None,
587 };
588 parse_migrate_bonding_curve_creator_from_data(data, metadata)
589}
590
591#[inline(always)]
592fn parse_create_fee_sharing_config_event_optimized(
593 data: &[u8],
594 signature: Signature,
595 slot: u64,
596 tx_index: u64,
597 block_time_us: Option<i64>,
598 grpc_recv_us: i64,
599) -> Option<DexEvent> {
600 let metadata = EventMetadata {
601 signature,
602 slot,
603 tx_index,
604 block_time_us: block_time_us.unwrap_or(0),
605 grpc_recv_us,
606 recent_blockhash: None,
607 };
608 crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(data, metadata)
609}
610
611#[inline(always)]
619pub fn get_event_type_fast(log: &str) -> Option<u64> {
620 extract_discriminator_simd(log)
621}
622
623#[inline(always)]
625pub fn is_event_type(log: &str, discriminator: u64) -> bool {
626 extract_discriminator_simd(log) == Some(discriminator)
627}
628
629#[inline(always)]
644pub fn parse_trade_from_data(
645 data: &[u8],
646 metadata: EventMetadata,
647 is_created_buy: bool,
648) -> Option<DexEvent> {
649 unsafe {
650 if data.len() < 32 + 8 + 8 + 1 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 8 + 8 + 32 + 8 + 8 {
652 return None;
653 }
654
655 let mut offset = 0;
656
657 let mint = read_pubkey_unchecked(data, offset);
658 offset += 32;
659
660 let sol_amount = read_u64_unchecked(data, offset);
661 offset += 8;
662
663 let token_amount = read_u64_unchecked(data, offset);
664 offset += 8;
665
666 let is_buy = read_bool_unchecked(data, offset);
667 offset += 1;
668
669 let user = read_pubkey_unchecked(data, offset);
670 offset += 32;
671
672 let timestamp = read_i64_unchecked(data, offset);
673 offset += 8;
674
675 let virtual_sol_reserves = read_u64_unchecked(data, offset);
676 offset += 8;
677
678 let virtual_token_reserves = read_u64_unchecked(data, offset);
679 offset += 8;
680
681 let real_sol_reserves = read_u64_unchecked(data, offset);
682 offset += 8;
683
684 let real_token_reserves = read_u64_unchecked(data, offset);
685 offset += 8;
686
687 let fee_recipient = read_pubkey_unchecked(data, offset);
688 offset += 32;
689
690 let fee_basis_points = read_u64_unchecked(data, offset);
691 offset += 8;
692
693 let fee = read_u64_unchecked(data, offset);
694 offset += 8;
695
696 let creator = read_pubkey_unchecked(data, offset);
697 offset += 32;
698
699 let creator_fee_basis_points = read_u64_unchecked(data, offset);
700 offset += 8;
701
702 let creator_fee = read_u64_unchecked(data, offset);
703 offset += 8;
704
705 let track_volume =
707 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
708 offset += 1;
709
710 let total_unclaimed_tokens =
711 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
712 offset += 8;
713
714 let total_claimed_tokens =
715 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
716 offset += 8;
717
718 let current_sol_volume =
719 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
720 offset += 8;
721
722 let last_update_timestamp =
723 if offset + 8 <= data.len() { read_i64_unchecked(data, offset) } else { 0 };
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 =
739 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
740 offset += 1;
741 let cashback_fee_basis_points =
742 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
743 offset += 8;
744 let cashback = if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
745
746 let trade_event = PumpFunTradeEvent {
747 metadata,
748 mint,
749 sol_amount,
750 token_amount,
751 is_buy,
752 is_created_buy,
753 user,
754 timestamp,
755 virtual_sol_reserves,
756 virtual_token_reserves,
757 real_sol_reserves,
758 real_token_reserves,
759 fee_recipient,
760 fee_basis_points,
761 fee,
762 creator,
763 creator_fee_basis_points,
764 creator_fee,
765 track_volume,
766 total_unclaimed_tokens,
767 total_claimed_tokens,
768 current_sol_volume,
769 last_update_timestamp,
770 ix_name: ix_name.clone(),
771 mayhem_mode,
772 cashback_fee_basis_points,
773 cashback,
774 is_cashback_coin: cashback_fee_basis_points > 0,
775 amount: 0,
776 max_sol_cost: 0,
777 min_sol_output: 0,
778 bonding_curve: Pubkey::default(),
779 associated_bonding_curve: Pubkey::default(),
780 creator_vault: Pubkey::default(),
781 token_program: Pubkey::default(),
782 account: None,
783 };
784
785 match ix_name.as_str() {
787 "buy" | "buy_v2" => Some(DexEvent::PumpFunBuy(trade_event)),
788 "sell" | "sell_v2" => Some(DexEvent::PumpFunSell(trade_event)),
789 "buy_exact_sol_in" | "buy_exact_quote_in_v2" => {
790 Some(DexEvent::PumpFunBuyExactSolIn(trade_event))
791 }
792 _ => Some(DexEvent::PumpFunTrade(trade_event)),
793 }
794 }
795}
796
797#[inline(always)]
801pub fn parse_buy_from_data(
802 data: &[u8],
803 metadata: EventMetadata,
804 is_created_buy: bool,
805) -> Option<DexEvent> {
806 let event = parse_trade_from_data(data, metadata, is_created_buy)?;
807 match &event {
808 DexEvent::PumpFunBuy(_) => Some(event),
809 _ => None,
810 }
811}
812
813#[inline(always)]
817pub fn parse_sell_from_data(
818 data: &[u8],
819 metadata: EventMetadata,
820 is_created_buy: bool,
821) -> Option<DexEvent> {
822 let event = parse_trade_from_data(data, metadata, is_created_buy)?;
823 match &event {
824 DexEvent::PumpFunSell(_) => Some(event),
825 _ => None,
826 }
827}
828
829#[inline(always)]
833pub fn parse_buy_exact_sol_in_from_data(
834 data: &[u8],
835 metadata: EventMetadata,
836 is_created_buy: bool,
837) -> Option<DexEvent> {
838 let event = parse_trade_from_data(data, metadata, is_created_buy)?;
839 match &event {
840 DexEvent::PumpFunBuyExactSolIn(_) => Some(event),
841 _ => None,
842 }
843}
844
845#[inline(always)]
847pub fn parse_create_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
848 unsafe {
849 let mut offset = 0;
850
851 let (name, name_len) = read_str_unchecked(data, offset)?;
852 offset += name_len;
853
854 let (symbol, symbol_len) = read_str_unchecked(data, offset)?;
855 offset += symbol_len;
856
857 let (uri, uri_len) = read_str_unchecked(data, offset)?;
858 offset += uri_len;
859
860 if data.len() < offset + 32 + 32 + 32 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 1 {
861 return None;
862 }
863
864 let mint = read_pubkey_unchecked(data, offset);
865 offset += 32;
866
867 let bonding_curve = read_pubkey_unchecked(data, offset);
868 offset += 32;
869
870 let user = read_pubkey_unchecked(data, offset);
871 offset += 32;
872
873 let creator = read_pubkey_unchecked(data, offset);
874 offset += 32;
875
876 let timestamp = read_i64_unchecked(data, offset);
877 offset += 8;
878
879 let virtual_token_reserves = read_u64_unchecked(data, offset);
880 offset += 8;
881
882 let virtual_sol_reserves = read_u64_unchecked(data, offset);
883 offset += 8;
884
885 let real_token_reserves = read_u64_unchecked(data, offset);
886 offset += 8;
887
888 let token_total_supply = read_u64_unchecked(data, offset);
889 offset += 8;
890
891 let token_program = if offset + 32 <= data.len() {
892 read_pubkey_unchecked(data, offset)
893 } else {
894 Pubkey::default()
895 };
896 offset += 32;
897
898 let is_mayhem_mode =
899 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
900 offset += 1;
901 let is_cashback_enabled =
902 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
903
904 Some(DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
905 metadata,
906 name: name.to_string(),
907 symbol: symbol.to_string(),
908 uri: uri.to_string(),
909 mint,
910 bonding_curve,
911 user,
912 creator,
913 timestamp,
914 virtual_token_reserves,
915 virtual_sol_reserves,
916 real_token_reserves,
917 token_total_supply,
918 token_program,
919 is_mayhem_mode,
920 is_cashback_enabled,
921 }))
922 }
923}
924
925#[inline(always)]
927pub fn parse_migrate_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
928 unsafe {
929 if data.len() < 32 + 32 + 8 + 8 + 8 + 32 + 8 + 32 {
930 return None;
931 }
932
933 let mut offset = 0;
934
935 let user = read_pubkey_unchecked(data, offset);
936 offset += 32;
937
938 let mint = read_pubkey_unchecked(data, offset);
939 offset += 32;
940
941 let mint_amount = read_u64_unchecked(data, offset);
942 offset += 8;
943
944 let sol_amount = read_u64_unchecked(data, offset);
945 offset += 8;
946
947 let pool_migration_fee = read_u64_unchecked(data, offset);
948 offset += 8;
949
950 let bonding_curve = read_pubkey_unchecked(data, offset);
951 offset += 32;
952
953 let timestamp = read_i64_unchecked(data, offset);
954 offset += 8;
955
956 let pool = read_pubkey_unchecked(data, offset);
957
958 Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
959 metadata,
960 user,
961 mint,
962 mint_amount,
963 sol_amount,
964 pool_migration_fee,
965 bonding_curve,
966 timestamp,
967 pool,
968 }))
969 }
970}
971
972#[inline(always)]
974pub fn parse_migrate_bonding_curve_creator_from_data(
975 data: &[u8],
976 metadata: EventMetadata,
977) -> Option<DexEvent> {
978 unsafe {
979 const NEED: usize = 8 + 32 * 5;
980 if data.len() < NEED {
981 return None;
982 }
983
984 let mut offset = 0usize;
985 let timestamp = read_i64_unchecked(data, offset);
986 offset += 8;
987 let mint = read_pubkey_unchecked(data, offset);
988 offset += 32;
989 let bonding_curve = read_pubkey_unchecked(data, offset);
990 offset += 32;
991 let sharing_config = read_pubkey_unchecked(data, offset);
992 offset += 32;
993 let old_creator = read_pubkey_unchecked(data, offset);
994 offset += 32;
995 let new_creator = read_pubkey_unchecked(data, offset);
996
997 Some(DexEvent::PumpFunMigrateBondingCurveCreator(PumpFunMigrateBondingCurveCreatorEvent {
998 metadata,
999 timestamp,
1000 mint,
1001 bonding_curve,
1002 sharing_config,
1003 old_creator,
1004 new_creator,
1005 }))
1006 }
1007}
1008
1009#[inline]
1011pub fn parse_create_fee_sharing_config_from_data(
1012 data: &[u8],
1013 metadata: EventMetadata,
1014) -> Option<DexEvent> {
1015 crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(data, metadata)
1016}
1017
1018#[inline(always)]
1019fn read_i64_at(data: &[u8], o: &mut usize) -> Option<i64> {
1020 if data.len() < *o + 8 {
1021 return None;
1022 }
1023 let v = i64::from_le_bytes(data[*o..*o + 8].try_into().ok()?);
1024 *o += 8;
1025 Some(v)
1026}
1027
1028#[inline(always)]
1029fn read_u16_at(data: &[u8], o: &mut usize) -> Option<u16> {
1030 if data.len() < *o + 2 {
1031 return None;
1032 }
1033 let v = u16::from_le_bytes(data[*o..*o + 2].try_into().ok()?);
1034 *o += 2;
1035 Some(v)
1036}
1037
1038#[inline(always)]
1039fn read_u32_at(data: &[u8], o: &mut usize) -> Option<u32> {
1040 if data.len() < *o + 4 {
1041 return None;
1042 }
1043 let v = u32::from_le_bytes(data[*o..*o + 4].try_into().ok()?);
1044 *o += 4;
1045 Some(v)
1046}
1047
1048#[inline(always)]
1049fn read_pubkey_at(data: &[u8], o: &mut usize) -> Option<Pubkey> {
1050 if data.len() < *o + 32 {
1051 return None;
1052 }
1053 let pk = Pubkey::new_from_array(data[*o..*o + 32].try_into().ok()?);
1054 *o += 32;
1055 Some(pk)
1056}
1057
1058#[cfg(feature = "perf-stats")]
1063pub fn get_perf_stats() -> (usize, usize) {
1064 let count = PARSE_COUNT.load(Ordering::Relaxed);
1065 let total_ns = PARSE_TIME_NS.load(Ordering::Relaxed);
1066 (count, total_ns)
1067}
1068
1069#[cfg(feature = "perf-stats")]
1070pub fn reset_perf_stats() {
1071 PARSE_COUNT.store(0, Ordering::Relaxed);
1072 PARSE_TIME_NS.store(0, Ordering::Relaxed);
1073}
1074
1075#[cfg(test)]
1076mod tests {
1077 use super::*;
1078 use crate::core::events::{DexEvent, EventMetadata};
1079
1080 #[test]
1081 fn test_discriminator_simd() {
1082 let log = "Program data: G3Kp5Dfe605nAAAAAAAAAAA=";
1084 let disc = extract_discriminator_simd(log);
1085 assert!(disc.is_some());
1086 }
1087
1088 #[test]
1089 fn test_parse_performance() {
1090 let log = "Program data: G3Kp5Dfe605nAAAAAAAAAAA=";
1092 let sig = Signature::default();
1093
1094 let start = std::time::Instant::now();
1095 for _ in 0..1000 {
1096 let _ = parse_log(log, sig, 0, 0, Some(0), 0, false);
1097 }
1098 let elapsed = start.elapsed();
1099
1100 println!("Average parse time: {} ns", elapsed.as_nanos() / 1000);
1101 }
1102
1103 #[test]
1104 fn migrate_bonding_curve_creator_roundtrip_from_data() {
1105 let ts: i64 = 1_777_920_719;
1106 let mint = Pubkey::new_unique();
1107 let bonding_curve = Pubkey::new_unique();
1108 let sharing_config = Pubkey::new_unique();
1109 let old_creator = Pubkey::new_unique();
1110 let new_creator = Pubkey::new_unique();
1111
1112 let mut buf = Vec::with_capacity(200);
1113 buf.extend_from_slice(&ts.to_le_bytes());
1114 buf.extend_from_slice(mint.as_ref());
1115 buf.extend_from_slice(bonding_curve.as_ref());
1116 buf.extend_from_slice(sharing_config.as_ref());
1117 buf.extend_from_slice(old_creator.as_ref());
1118 buf.extend_from_slice(new_creator.as_ref());
1119
1120 let metadata = EventMetadata {
1121 signature: Signature::default(),
1122 slot: 0,
1123 tx_index: 0,
1124 block_time_us: 0,
1125 grpc_recv_us: 0,
1126 recent_blockhash: None,
1127 };
1128
1129 let ev = parse_migrate_bonding_curve_creator_from_data(&buf, metadata).expect("parse");
1130 match ev {
1131 DexEvent::PumpFunMigrateBondingCurveCreator(e) => {
1132 assert_eq!(e.timestamp, ts);
1133 assert_eq!(e.mint, mint);
1134 assert_eq!(e.bonding_curve, bonding_curve);
1135 assert_eq!(e.sharing_config, sharing_config);
1136 assert_eq!(e.old_creator, old_creator);
1137 assert_eq!(e.new_creator, new_creator);
1138 }
1139 _ => panic!("wrong variant"),
1140 }
1141 }
1142}