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