1use crate::core::events::*;
14use memchr::memmem;
15use once_cell::sync::Lazy;
16use solana_sdk::{pubkey::Pubkey, signature::Signature};
17
18#[cfg(feature = "perf-stats")]
19use std::sync::atomic::{AtomicUsize, Ordering};
20
21#[cfg(feature = "perf-stats")]
26pub static PARSE_COUNT: AtomicUsize = AtomicUsize::new(0);
27#[cfg(feature = "perf-stats")]
28pub static PARSE_TIME_NS: AtomicUsize = AtomicUsize::new(0);
29
30pub mod discriminators {
36 pub const BUY: u64 = u64::from_le_bytes([103, 244, 82, 31, 44, 245, 119, 119]); pub const SELL: u64 = u64::from_le_bytes([62, 47, 55, 10, 165, 3, 220, 42]); pub const CREATE_POOL: u64 = u64::from_le_bytes([177, 49, 12, 210, 160, 118, 167, 116]); pub const ADD_LIQUIDITY: u64 = u64::from_le_bytes([120, 248, 61, 83, 31, 142, 107, 144]); pub const REMOVE_LIQUIDITY: u64 = u64::from_le_bytes([22, 9, 133, 26, 160, 44, 71, 192]);
43 }
45
46static BASE64_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"Program data: "));
48
49#[inline(always)]
51fn copy_b64_skip_ws_prefix(src: &[u8], out: &mut [u8], max_copy: usize) -> Option<usize> {
52 let cap = max_copy.min(out.len());
53 let mut j = 0usize;
54 for &b in src {
55 if b.is_ascii_whitespace() {
56 continue;
57 }
58 if j >= cap {
59 break;
60 }
61 out[j] = b;
62 j += 1;
63 }
64 if j < 12 {
65 return None;
66 }
67 Some(j)
68}
69
70#[inline(always)]
79fn extract_program_data_zero_copy<'a>(log: &'a str, buf: &'a mut [u8; 2048]) -> Option<&'a [u8]> {
80 let log_bytes = log.as_bytes();
81 let pos = BASE64_FINDER.find(log_bytes)?;
82
83 let data_part = &log[pos + 14..];
84 let trimmed = data_part.trim();
85 let body = trimmed.as_bytes();
86
87 if body.len() > 2700 {
88 return None;
89 }
90
91 use base64_simd::AsOut;
92 const COMPACT_CAP: usize = 2730;
93 let decoded_slice = if body.iter().any(|&b| b.is_ascii_whitespace()) {
94 let mut compact = [0u8; COMPACT_CAP];
95 let n = copy_b64_skip_ws_prefix(body, &mut compact, COMPACT_CAP)?;
96 base64_simd::STANDARD.decode(&compact[..n], buf.as_mut().as_out()).ok()?
97 } else {
98 base64_simd::STANDARD.decode(body, buf.as_mut().as_out()).ok()?
99 };
100
101 Some(decoded_slice)
102}
103
104#[inline(always)]
106fn extract_discriminator_simd(log: &str) -> Option<u64> {
107 let log_bytes = log.as_bytes();
108 let pos = BASE64_FINDER.find(log_bytes)?;
109
110 let data_part = &log[pos + 14..];
111 let body = data_part.trim().as_bytes();
112
113 use base64_simd::AsOut;
114 let mut compact = [0u8; 24];
115 let n = copy_b64_skip_ws_prefix(body, &mut compact, 16)?;
116 let mut dec = [0u8; 12];
117 base64_simd::STANDARD.decode(&compact[..n], dec.as_mut().as_out()).ok()?;
118
119 unsafe {
120 let ptr = dec.as_ptr() as *const u64;
121 Some(ptr.read_unaligned())
122 }
123}
124
125#[inline(always)]
131unsafe fn read_u64_unchecked(data: &[u8], offset: usize) -> u64 {
132 let ptr = data.as_ptr().add(offset) as *const u64;
133 u64::from_le(ptr.read_unaligned())
134}
135
136#[inline(always)]
138unsafe fn read_i64_unchecked(data: &[u8], offset: usize) -> i64 {
139 let ptr = data.as_ptr().add(offset) as *const i64;
140 i64::from_le(ptr.read_unaligned())
141}
142
143#[derive(Default)]
144struct PumpSwapTradeTail {
145 cashback_fee_basis_points: u64,
146 cashback: u64,
147 buyback_fee_basis_points: u64,
148 buyback_fee: u64,
149 virtual_quote_reserves: i128,
150 can_boost: bool,
151 base_supply: u64,
152}
153
154#[inline(always)]
155fn read_u64_le_at(data: &[u8], offset: usize) -> Option<u64> {
156 let bytes = data.get(offset..offset.checked_add(8)?)?;
157 Some(u64::from_le_bytes(bytes.try_into().ok()?))
158}
159
160#[inline(always)]
161fn read_i128_le_at(data: &[u8], offset: usize) -> Option<i128> {
162 let bytes = data.get(offset..offset.checked_add(16)?)?;
163 Some(i128::from_le_bytes(bytes.try_into().ok()?))
164}
165
166#[inline(always)]
167fn read_borsh_string(data: &[u8], offset: usize) -> Option<(String, usize)> {
168 let content_offset = offset.checked_add(4)?;
169 let len = u32::from_le_bytes(data.get(offset..content_offset)?.try_into().ok()?) as usize;
170 let end = content_offset.checked_add(len)?;
171 let value = std::str::from_utf8(data.get(content_offset..end)?).ok()?.to_owned();
172 Some((value, end))
173}
174
175#[inline(always)]
177fn parse_trade_tail(data: &[u8]) -> Option<PumpSwapTradeTail> {
178 const CASHBACK_LEN: usize = 16;
179 const BUYBACK_LEN: usize = 32;
180 const BOOST_LEN: usize = 57;
181
182 if data.is_empty() {
183 return Some(PumpSwapTradeTail::default());
184 }
185 if data.len() < CASHBACK_LEN {
186 return None;
187 }
188
189 let mut tail = PumpSwapTradeTail {
190 cashback_fee_basis_points: read_u64_le_at(data, 0)?,
191 cashback: read_u64_le_at(data, 8)?,
192 ..Default::default()
193 };
194 if data.len() == CASHBACK_LEN {
195 return Some(tail);
196 }
197 if data.len() < BUYBACK_LEN {
198 return None;
199 }
200
201 tail.buyback_fee_basis_points = read_u64_le_at(data, 16)?;
202 tail.buyback_fee = read_u64_le_at(data, 24)?;
203 if data.len() == BUYBACK_LEN {
204 return Some(tail);
205 }
206 if data.len() < BOOST_LEN {
207 return None;
208 }
209
210 tail.virtual_quote_reserves = read_i128_le_at(data, 32)?;
211 tail.can_boost = match data[48] {
212 0 => false,
213 1 => true,
214 _ => return None,
215 };
216 tail.base_supply = read_u64_le_at(data, 49)?;
217 Some(tail)
218}
219
220#[inline(always)]
222unsafe fn read_u16_unchecked(data: &[u8], offset: usize) -> u16 {
223 let ptr = data.as_ptr().add(offset) as *const u16;
224 u16::from_le(ptr.read_unaligned())
225}
226
227#[inline(always)]
229unsafe fn read_u8_unchecked(data: &[u8], offset: usize) -> u8 {
230 *data.get_unchecked(offset)
231}
232
233#[inline(always)]
235unsafe fn read_bool_unchecked(data: &[u8], offset: usize) -> bool {
236 *data.get_unchecked(offset) == 1
237}
238
239#[inline(always)]
243unsafe fn read_pubkey_unchecked(data: &[u8], offset: usize) -> Pubkey {
244 #[cfg(target_arch = "x86_64")]
247 {
248 use std::arch::x86_64::_mm_prefetch;
249 use std::arch::x86_64::_MM_HINT_T0;
250 if offset + 64 < data.len() {
251 _mm_prefetch((data.as_ptr().add(offset + 32)) as *const i8, _MM_HINT_T0);
252 }
253 }
254
255 let ptr = data.as_ptr().add(offset);
256 let mut bytes = [0u8; 32];
257 std::ptr::copy_nonoverlapping(ptr, bytes.as_mut_ptr(), 32);
258 Pubkey::new_from_array(bytes)
259}
260
261#[inline(always)]
269pub fn parse_log(
270 log: &str,
271 signature: Signature,
272 slot: u64,
273 tx_index: u64,
274 block_time_us: Option<i64>,
275 grpc_recv_us: i64,
276) -> Option<DexEvent> {
277 #[cfg(feature = "perf-stats")]
278 let start = std::time::Instant::now();
279
280 let mut buf = [0u8; 2048];
282 let program_data = extract_program_data_zero_copy(log, &mut buf)?;
283
284 if program_data.len() < 8 {
285 return None;
286 }
287
288 let discriminator = unsafe { read_u64_unchecked(program_data, 0) };
290 let data = &program_data[8..];
291
292 let result = match discriminator {
293 discriminators::BUY => parse_buy_from_data(
294 data,
295 EventMetadata {
296 signature,
297 slot,
298 tx_index,
299 block_time_us: block_time_us.unwrap_or(0),
300 grpc_recv_us,
301 recent_blockhash: None,
302 },
303 ),
304 discriminators::SELL => parse_sell_from_data(
305 data,
306 EventMetadata {
307 signature,
308 slot,
309 tx_index,
310 block_time_us: block_time_us.unwrap_or(0),
311 grpc_recv_us,
312 recent_blockhash: None,
313 },
314 ),
315 discriminators::CREATE_POOL => parse_create_pool_event_optimized(
316 data,
317 signature,
318 slot,
319 tx_index,
320 block_time_us,
321 grpc_recv_us,
322 ),
323 discriminators::ADD_LIQUIDITY => parse_add_liquidity_event_optimized(
324 data,
325 signature,
326 slot,
327 tx_index,
328 block_time_us,
329 grpc_recv_us,
330 ),
331 discriminators::REMOVE_LIQUIDITY => parse_remove_liquidity_event_optimized(
332 data,
333 signature,
334 slot,
335 tx_index,
336 block_time_us,
337 grpc_recv_us,
338 ),
339 _ => None,
340 };
341
342 #[cfg(feature = "perf-stats")]
343 {
344 PARSE_COUNT.fetch_add(1, Ordering::Relaxed);
345 PARSE_TIME_NS.fetch_add(start.elapsed().as_nanos() as usize, Ordering::Relaxed);
346 }
347
348 result
349}
350
351#[inline(always)]
353fn parse_create_pool_event_optimized(
354 data: &[u8],
355 signature: Signature,
356 slot: u64,
357 tx_index: u64,
358 block_time_us: Option<i64>,
359 grpc_recv_us: i64,
360) -> Option<DexEvent> {
361 const CREATE_POOL_EVENT_LEN: usize = 326;
363 const REQUIRED_LEN: usize = CREATE_POOL_EVENT_LEN;
364 if data.len() < REQUIRED_LEN {
365 return None;
366 }
367
368 unsafe {
369 let timestamp = read_i64_unchecked(data, 0);
370 let index = read_u16_unchecked(data, 8);
371
372 let creator = read_pubkey_unchecked(data, 10);
373 let base_mint = read_pubkey_unchecked(data, 42);
374 let quote_mint = read_pubkey_unchecked(data, 74);
375
376 let base_mint_decimals = read_u8_unchecked(data, 106);
377 let quote_mint_decimals = read_u8_unchecked(data, 107);
378
379 let base_amount_in = read_u64_unchecked(data, 108);
380 let quote_amount_in = read_u64_unchecked(data, 116);
381 let pool_base_amount = read_u64_unchecked(data, 124);
382 let pool_quote_amount = read_u64_unchecked(data, 132);
383 let minimum_liquidity = read_u64_unchecked(data, 140);
384 let initial_liquidity = read_u64_unchecked(data, 148);
385 let lp_token_amount_out = read_u64_unchecked(data, 156);
386
387 let pool_bump = read_u8_unchecked(data, 164);
388
389 let pool = read_pubkey_unchecked(data, 165);
390 let lp_mint = read_pubkey_unchecked(data, 197);
391 let user_base_token_account = read_pubkey_unchecked(data, 229);
392 let user_quote_token_account = read_pubkey_unchecked(data, 261);
393 let coin_creator = read_pubkey_unchecked(data, 293);
394 let is_mayhem_mode = read_bool_unchecked(data, 325);
395
396 let metadata = EventMetadata {
397 signature,
398 slot,
399 tx_index,
400 block_time_us: block_time_us.unwrap_or(0),
401 grpc_recv_us,
402 recent_blockhash: None,
403 };
404
405 Some(DexEvent::PumpSwapCreatePool(PumpSwapCreatePoolEvent {
406 metadata,
407 timestamp,
408 index,
409 creator,
410 base_mint,
411 quote_mint,
412 base_mint_decimals,
413 quote_mint_decimals,
414 base_amount_in,
415 quote_amount_in,
416 pool_base_amount,
417 pool_quote_amount,
418 minimum_liquidity,
419 initial_liquidity,
420 lp_token_amount_out,
421 pool_bump,
422 pool,
423 lp_mint,
424 user_base_token_account,
425 user_quote_token_account,
426 coin_creator,
427 is_mayhem_mode,
428 is_cashback_coin: false,
429 }))
430 }
431}
432
433#[inline(always)]
435fn parse_add_liquidity_event_optimized(
436 data: &[u8],
437 signature: Signature,
438 slot: u64,
439 tx_index: u64,
440 block_time_us: Option<i64>,
441 grpc_recv_us: i64,
442) -> Option<DexEvent> {
443 const REQUIRED_LEN: usize = 10 * 8 + 5 * 32;
444 if data.len() < REQUIRED_LEN {
445 return None;
446 }
447
448 unsafe {
449 let timestamp = read_i64_unchecked(data, 0);
450 let lp_token_amount_out = read_u64_unchecked(data, 8);
451 let max_base_amount_in = read_u64_unchecked(data, 16);
452 let max_quote_amount_in = read_u64_unchecked(data, 24);
453 let user_base_token_reserves = read_u64_unchecked(data, 32);
454 let user_quote_token_reserves = read_u64_unchecked(data, 40);
455 let pool_base_token_reserves = read_u64_unchecked(data, 48);
456 let pool_quote_token_reserves = read_u64_unchecked(data, 56);
457 let base_amount_in = read_u64_unchecked(data, 64);
458 let quote_amount_in = read_u64_unchecked(data, 72);
459 let lp_mint_supply = read_u64_unchecked(data, 80);
460
461 let pool = read_pubkey_unchecked(data, 88);
462 let user = read_pubkey_unchecked(data, 120);
463 let user_base_token_account = read_pubkey_unchecked(data, 152);
464 let user_quote_token_account = read_pubkey_unchecked(data, 184);
465 let user_pool_token_account = read_pubkey_unchecked(data, 216);
466
467 let metadata = EventMetadata {
468 signature,
469 slot,
470 tx_index,
471 block_time_us: block_time_us.unwrap_or(0),
472 grpc_recv_us,
473 recent_blockhash: None,
474 };
475
476 Some(DexEvent::PumpSwapLiquidityAdded(PumpSwapLiquidityAdded {
477 metadata,
478 timestamp,
479 lp_token_amount_out,
480 max_base_amount_in,
481 max_quote_amount_in,
482 user_base_token_reserves,
483 user_quote_token_reserves,
484 pool_base_token_reserves,
485 pool_quote_token_reserves,
486 base_amount_in,
487 quote_amount_in,
488 lp_mint_supply,
489 pool,
490 user,
491 user_base_token_account,
492 user_quote_token_account,
493 user_pool_token_account,
494 }))
495 }
496}
497
498#[inline(always)]
500fn parse_remove_liquidity_event_optimized(
501 data: &[u8],
502 signature: Signature,
503 slot: u64,
504 tx_index: u64,
505 block_time_us: Option<i64>,
506 grpc_recv_us: i64,
507) -> Option<DexEvent> {
508 const REQUIRED_LEN: usize = 10 * 8 + 5 * 32;
509 if data.len() < REQUIRED_LEN {
510 return None;
511 }
512
513 unsafe {
514 let timestamp = read_i64_unchecked(data, 0);
515 let lp_token_amount_in = read_u64_unchecked(data, 8);
516 let min_base_amount_out = read_u64_unchecked(data, 16);
517 let min_quote_amount_out = read_u64_unchecked(data, 24);
518 let user_base_token_reserves = read_u64_unchecked(data, 32);
519 let user_quote_token_reserves = read_u64_unchecked(data, 40);
520 let pool_base_token_reserves = read_u64_unchecked(data, 48);
521 let pool_quote_token_reserves = read_u64_unchecked(data, 56);
522 let base_amount_out = read_u64_unchecked(data, 64);
523 let quote_amount_out = read_u64_unchecked(data, 72);
524 let lp_mint_supply = read_u64_unchecked(data, 80);
525
526 let pool = read_pubkey_unchecked(data, 88);
527 let user = read_pubkey_unchecked(data, 120);
528 let user_base_token_account = read_pubkey_unchecked(data, 152);
529 let user_quote_token_account = read_pubkey_unchecked(data, 184);
530 let user_pool_token_account = read_pubkey_unchecked(data, 216);
531
532 let metadata = EventMetadata {
533 signature,
534 slot,
535 tx_index,
536 block_time_us: block_time_us.unwrap_or(0),
537 grpc_recv_us,
538 recent_blockhash: None,
539 };
540
541 Some(DexEvent::PumpSwapLiquidityRemoved(PumpSwapLiquidityRemoved {
542 metadata,
543 timestamp,
544 lp_token_amount_in,
545 min_base_amount_out,
546 min_quote_amount_out,
547 user_base_token_reserves,
548 user_quote_token_reserves,
549 pool_base_token_reserves,
550 pool_quote_token_reserves,
551 base_amount_out,
552 quote_amount_out,
553 lp_mint_supply,
554 pool,
555 user,
556 user_base_token_account,
557 user_quote_token_account,
558 user_pool_token_account,
559 }))
560 }
561}
562
563#[inline(always)]
571pub fn get_event_type_fast(log: &str) -> Option<u64> {
572 extract_discriminator_simd(log)
573}
574
575#[inline(always)]
577pub fn is_event_type(log: &str, discriminator: u64) -> bool {
578 extract_discriminator_simd(log) == Some(discriminator)
579}
580
581#[inline(always)]
588pub fn parse_buy_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
589 const LEGACY_LEN: usize = 16 * 8 + 7 * 32 + 1 + 4 * 8;
592 const MIN_REQUIRED_LEN: usize = LEGACY_LEN + 8 + 4;
593 if data.len() != LEGACY_LEN && data.len() < MIN_REQUIRED_LEN {
594 return None;
595 }
596 let track_volume = match data[352] {
597 0 => false,
598 1 => true,
599 _ => return None,
600 };
601
602 unsafe {
603 let timestamp = read_i64_unchecked(data, 0);
604 let base_amount_out = read_u64_unchecked(data, 8);
605 let max_quote_amount_in = read_u64_unchecked(data, 16);
606 let user_base_token_reserves = read_u64_unchecked(data, 24);
607 let user_quote_token_reserves = read_u64_unchecked(data, 32);
608 let pool_base_token_reserves = read_u64_unchecked(data, 40);
609 let pool_quote_token_reserves = read_u64_unchecked(data, 48);
610 let quote_amount_in = read_u64_unchecked(data, 56);
611 let lp_fee_basis_points = read_u64_unchecked(data, 64);
612 let lp_fee = read_u64_unchecked(data, 72);
613 let protocol_fee_basis_points = read_u64_unchecked(data, 80);
614 let protocol_fee = read_u64_unchecked(data, 88);
615 let quote_amount_in_with_lp_fee = read_u64_unchecked(data, 96);
616 let user_quote_amount_in = read_u64_unchecked(data, 104);
617
618 let pool = read_pubkey_unchecked(data, 112);
619 let user = read_pubkey_unchecked(data, 144);
620 let user_base_token_account = read_pubkey_unchecked(data, 176);
621 let user_quote_token_account = read_pubkey_unchecked(data, 208);
622 let protocol_fee_recipient = read_pubkey_unchecked(data, 240);
623 let protocol_fee_recipient_token_account = read_pubkey_unchecked(data, 272);
624 let coin_creator = read_pubkey_unchecked(data, 304);
625
626 let coin_creator_fee_basis_points = read_u64_unchecked(data, 336);
627 let coin_creator_fee = read_u64_unchecked(data, 344);
628 let total_unclaimed_tokens = read_u64_unchecked(data, 353);
629 let total_claimed_tokens = read_u64_unchecked(data, 361);
630 let current_sol_volume = read_u64_unchecked(data, 369);
631 let last_update_timestamp = read_i64_unchecked(data, 377);
632
633 let (min_base_amount_out, ix_name, tail) = if data.len() == LEGACY_LEN {
634 (0, String::new(), PumpSwapTradeTail::default())
635 } else {
636 let min_base_amount_out = read_u64_unchecked(data, LEGACY_LEN);
637 let (ix_name, tail_offset) = read_borsh_string(data, LEGACY_LEN + 8)?;
638 let tail = parse_trade_tail(&data[tail_offset..])?;
639 (min_base_amount_out, ix_name, tail)
640 };
641
642 Some(DexEvent::PumpSwapBuy(PumpSwapBuyEvent {
643 metadata,
644 timestamp,
645 base_amount_out,
646 max_quote_amount_in,
647 user_base_token_reserves,
648 user_quote_token_reserves,
649 pool_base_token_reserves,
650 pool_quote_token_reserves,
651 quote_amount_in,
652 lp_fee_basis_points,
653 lp_fee,
654 protocol_fee_basis_points,
655 protocol_fee,
656 quote_amount_in_with_lp_fee,
657 user_quote_amount_in,
658 pool,
659 user,
660 user_base_token_account,
661 user_quote_token_account,
662 protocol_fee_recipient,
663 protocol_fee_recipient_token_account,
664 coin_creator,
665 coin_creator_fee_basis_points,
666 coin_creator_fee,
667 track_volume,
668 total_unclaimed_tokens,
669 total_claimed_tokens,
670 current_sol_volume,
671 last_update_timestamp,
672 min_base_amount_out,
673 ix_name,
674 cashback_fee_basis_points: tail.cashback_fee_basis_points,
675 cashback: tail.cashback,
676 buyback_fee_basis_points: tail.buyback_fee_basis_points,
677 buyback_fee: tail.buyback_fee,
678 virtual_quote_reserves: tail.virtual_quote_reserves,
679 can_boost: tail.can_boost,
680 base_supply: tail.base_supply,
681 ..Default::default()
682 }))
683 }
684}
685
686#[inline(always)]
688pub fn parse_sell_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
689 const REQUIRED_LEN: usize = 14 * 8 + 7 * 32 + 2 * 8;
691 if data.len() < REQUIRED_LEN {
692 return None;
693 }
694
695 let tail = parse_trade_tail(&data[REQUIRED_LEN..])?;
696
697 unsafe {
698 let timestamp = read_i64_unchecked(data, 0);
699 let base_amount_in = read_u64_unchecked(data, 8);
700 let min_quote_amount_out = read_u64_unchecked(data, 16);
701 let user_base_token_reserves = read_u64_unchecked(data, 24);
702 let user_quote_token_reserves = read_u64_unchecked(data, 32);
703 let pool_base_token_reserves = read_u64_unchecked(data, 40);
704 let pool_quote_token_reserves = read_u64_unchecked(data, 48);
705 let quote_amount_out = read_u64_unchecked(data, 56);
706 let lp_fee_basis_points = read_u64_unchecked(data, 64);
707 let lp_fee = read_u64_unchecked(data, 72);
708 let protocol_fee_basis_points = read_u64_unchecked(data, 80);
709 let protocol_fee = read_u64_unchecked(data, 88);
710 let quote_amount_out_without_lp_fee = read_u64_unchecked(data, 96);
711 let user_quote_amount_out = read_u64_unchecked(data, 104);
712
713 let pool = read_pubkey_unchecked(data, 112);
714 let user = read_pubkey_unchecked(data, 144);
715 let user_base_token_account = read_pubkey_unchecked(data, 176);
716 let user_quote_token_account = read_pubkey_unchecked(data, 208);
717 let protocol_fee_recipient = read_pubkey_unchecked(data, 240);
718 let protocol_fee_recipient_token_account = read_pubkey_unchecked(data, 272);
719 let coin_creator = read_pubkey_unchecked(data, 304);
720
721 let coin_creator_fee_basis_points = read_u64_unchecked(data, 336);
722 let coin_creator_fee = read_u64_unchecked(data, 344);
723
724 Some(DexEvent::PumpSwapSell(PumpSwapSellEvent {
725 metadata,
726 timestamp,
727 base_amount_in,
728 min_quote_amount_out,
729 user_base_token_reserves,
730 user_quote_token_reserves,
731 pool_base_token_reserves,
732 pool_quote_token_reserves,
733 quote_amount_out,
734 lp_fee_basis_points,
735 lp_fee,
736 protocol_fee_basis_points,
737 protocol_fee,
738 quote_amount_out_without_lp_fee,
739 user_quote_amount_out,
740 pool,
741 user,
742 user_base_token_account,
743 user_quote_token_account,
744 protocol_fee_recipient,
745 protocol_fee_recipient_token_account,
746 coin_creator,
747 coin_creator_fee_basis_points,
748 coin_creator_fee,
749 cashback_fee_basis_points: tail.cashback_fee_basis_points,
750 cashback: tail.cashback,
751 buyback_fee_basis_points: tail.buyback_fee_basis_points,
752 buyback_fee: tail.buyback_fee,
753 virtual_quote_reserves: tail.virtual_quote_reserves,
754 can_boost: tail.can_boost,
755 base_supply: tail.base_supply,
756 ..Default::default()
757 }))
758 }
759}
760
761#[inline(always)]
763pub fn parse_create_pool_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
764 const CREATE_POOL_EVENT_LEN: usize = 326;
765 const REQUIRED_LEN: usize = CREATE_POOL_EVENT_LEN;
766 if data.len() < REQUIRED_LEN {
767 return None;
768 }
769
770 unsafe {
771 let timestamp = read_i64_unchecked(data, 0);
772 let index = read_u16_unchecked(data, 8);
773
774 let creator = read_pubkey_unchecked(data, 10);
775 let base_mint = read_pubkey_unchecked(data, 42);
776 let quote_mint = read_pubkey_unchecked(data, 74);
777
778 let base_mint_decimals = read_u8_unchecked(data, 106);
779 let quote_mint_decimals = read_u8_unchecked(data, 107);
780
781 let base_amount_in = read_u64_unchecked(data, 108);
782 let quote_amount_in = read_u64_unchecked(data, 116);
783 let pool_base_amount = read_u64_unchecked(data, 124);
784 let pool_quote_amount = read_u64_unchecked(data, 132);
785 let minimum_liquidity = read_u64_unchecked(data, 140);
786 let initial_liquidity = read_u64_unchecked(data, 148);
787 let lp_token_amount_out = read_u64_unchecked(data, 156);
788
789 let pool_bump = read_u8_unchecked(data, 164);
790
791 let pool = read_pubkey_unchecked(data, 165);
792 let lp_mint = read_pubkey_unchecked(data, 197);
793 let user_base_token_account = read_pubkey_unchecked(data, 229);
794 let user_quote_token_account = read_pubkey_unchecked(data, 261);
795 let coin_creator = read_pubkey_unchecked(data, 293);
796 let is_mayhem_mode = data.len() > 325 && read_bool_unchecked(data, 325);
797
798 Some(DexEvent::PumpSwapCreatePool(PumpSwapCreatePoolEvent {
799 metadata,
800 timestamp,
801 index,
802 creator,
803 base_mint,
804 quote_mint,
805 base_mint_decimals,
806 quote_mint_decimals,
807 base_amount_in,
808 quote_amount_in,
809 pool_base_amount,
810 pool_quote_amount,
811 minimum_liquidity,
812 initial_liquidity,
813 lp_token_amount_out,
814 pool_bump,
815 pool,
816 lp_mint,
817 user_base_token_account,
818 user_quote_token_account,
819 coin_creator,
820 is_mayhem_mode,
821 is_cashback_coin: false,
822 }))
823 }
824}
825
826#[inline(always)]
828pub fn parse_add_liquidity_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
829 const REQUIRED_LEN: usize = 10 * 8 + 5 * 32;
830 if data.len() < REQUIRED_LEN {
831 return None;
832 }
833
834 unsafe {
835 let timestamp = read_i64_unchecked(data, 0);
836 let lp_token_amount_out = read_u64_unchecked(data, 8);
837 let max_base_amount_in = read_u64_unchecked(data, 16);
838 let max_quote_amount_in = read_u64_unchecked(data, 24);
839 let user_base_token_reserves = read_u64_unchecked(data, 32);
840 let user_quote_token_reserves = read_u64_unchecked(data, 40);
841 let pool_base_token_reserves = read_u64_unchecked(data, 48);
842 let pool_quote_token_reserves = read_u64_unchecked(data, 56);
843 let base_amount_in = read_u64_unchecked(data, 64);
844 let quote_amount_in = read_u64_unchecked(data, 72);
845 let lp_mint_supply = read_u64_unchecked(data, 80);
846
847 let pool = read_pubkey_unchecked(data, 88);
848 let user = read_pubkey_unchecked(data, 120);
849 let user_base_token_account = read_pubkey_unchecked(data, 152);
850 let user_quote_token_account = read_pubkey_unchecked(data, 184);
851 let user_pool_token_account = read_pubkey_unchecked(data, 216);
852
853 Some(DexEvent::PumpSwapLiquidityAdded(PumpSwapLiquidityAdded {
854 metadata,
855 timestamp,
856 lp_token_amount_out,
857 max_base_amount_in,
858 max_quote_amount_in,
859 user_base_token_reserves,
860 user_quote_token_reserves,
861 pool_base_token_reserves,
862 pool_quote_token_reserves,
863 base_amount_in,
864 quote_amount_in,
865 lp_mint_supply,
866 pool,
867 user,
868 user_base_token_account,
869 user_quote_token_account,
870 user_pool_token_account,
871 }))
872 }
873}
874
875#[inline(always)]
877pub fn parse_remove_liquidity_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
878 const REQUIRED_LEN: usize = 10 * 8 + 5 * 32;
879 if data.len() < REQUIRED_LEN {
880 return None;
881 }
882
883 unsafe {
884 let timestamp = read_i64_unchecked(data, 0);
885 let lp_token_amount_in = read_u64_unchecked(data, 8);
886 let min_base_amount_out = read_u64_unchecked(data, 16);
887 let min_quote_amount_out = read_u64_unchecked(data, 24);
888 let user_base_token_reserves = read_u64_unchecked(data, 32);
889 let user_quote_token_reserves = read_u64_unchecked(data, 40);
890 let pool_base_token_reserves = read_u64_unchecked(data, 48);
891 let pool_quote_token_reserves = read_u64_unchecked(data, 56);
892 let base_amount_out = read_u64_unchecked(data, 64);
893 let quote_amount_out = read_u64_unchecked(data, 72);
894 let lp_mint_supply = read_u64_unchecked(data, 80);
895
896 let pool = read_pubkey_unchecked(data, 88);
897 let user = read_pubkey_unchecked(data, 120);
898 let user_base_token_account = read_pubkey_unchecked(data, 152);
899 let user_quote_token_account = read_pubkey_unchecked(data, 184);
900 let user_pool_token_account = read_pubkey_unchecked(data, 216);
901
902 Some(DexEvent::PumpSwapLiquidityRemoved(PumpSwapLiquidityRemoved {
903 metadata,
904 timestamp,
905 lp_token_amount_in,
906 min_base_amount_out,
907 min_quote_amount_out,
908 user_base_token_reserves,
909 user_quote_token_reserves,
910 pool_base_token_reserves,
911 pool_quote_token_reserves,
912 base_amount_out,
913 quote_amount_out,
914 lp_mint_supply,
915 pool,
916 user,
917 user_base_token_account,
918 user_quote_token_account,
919 user_pool_token_account,
920 }))
921 }
922}
923
924#[cfg(feature = "perf-stats")]
929pub fn get_perf_stats() -> (usize, usize) {
930 let count = PARSE_COUNT.load(Ordering::Relaxed);
931 let total_ns = PARSE_TIME_NS.load(Ordering::Relaxed);
932 (count, total_ns)
933}
934
935#[cfg(feature = "perf-stats")]
936pub fn reset_perf_stats() {
937 PARSE_COUNT.store(0, Ordering::Relaxed);
938 PARSE_TIME_NS.store(0, Ordering::Relaxed);
939}
940
941#[cfg(test)]
942mod tests {
943 use super::*;
944 use base64::{engine::general_purpose::STANDARD, Engine as _};
945 use solana_sdk::{pubkey::Pubkey, signature::Signature};
946
947 fn metadata() -> EventMetadata {
948 EventMetadata {
949 signature: Signature::default(),
950 slot: 0,
951 tx_index: 0,
952 block_time_us: 0,
953 grpc_recv_us: 0,
954 recent_blockhash: None,
955 }
956 }
957
958 fn write_u64(buf: &mut [u8], offset: usize, value: u64) {
959 buf[offset..offset + 8].copy_from_slice(&value.to_le_bytes());
960 }
961
962 fn write_i64(buf: &mut [u8], offset: usize, value: i64) {
963 buf[offset..offset + 8].copy_from_slice(&value.to_le_bytes());
964 }
965
966 fn write_pubkey(buf: &mut [u8], offset: usize, value: Pubkey) {
967 buf[offset..offset + 32].copy_from_slice(value.as_ref());
968 }
969
970 fn append_current_trade_tail(data: &mut Vec<u8>) {
971 data.extend_from_slice(&177u64.to_le_bytes()); data.extend_from_slice(&188u64.to_le_bytes()); data.extend_from_slice(&199u64.to_le_bytes()); data.extend_from_slice(&211u64.to_le_bytes()); data.extend_from_slice(&(-987_654_321i128).to_le_bytes());
976 data.push(1); data.extend_from_slice(&222u64.to_le_bytes()); }
979
980 fn append_buyback_trade_tail(data: &mut Vec<u8>) {
981 data.extend_from_slice(&177u64.to_le_bytes()); data.extend_from_slice(&188u64.to_le_bytes()); data.extend_from_slice(&199u64.to_le_bytes()); data.extend_from_slice(&211u64.to_le_bytes()); }
986
987 fn build_buy_payload(include_current_tail: bool) -> Vec<u8> {
988 let mut data = vec![0u8; 393];
989 write_i64(&mut data, 0, 1_713_498_953);
990 write_u64(&mut data, 8, 11);
991 write_u64(&mut data, 385, 22);
992 data.extend_from_slice(&3u32.to_le_bytes());
993 data.extend_from_slice(b"buy");
994 if include_current_tail {
995 append_current_trade_tail(&mut data);
996 }
997 data
998 }
999
1000 fn build_sell_payload(include_cashback: bool) -> Vec<u8> {
1001 let len = if include_cashback { 368 } else { 352 };
1002 let mut data = vec![0u8; len];
1003
1004 write_i64(&mut data, 0, 1_713_498_953);
1005 write_u64(&mut data, 8, 11);
1006 write_u64(&mut data, 16, 22);
1007 write_u64(&mut data, 24, 33);
1008 write_u64(&mut data, 32, 44);
1009 write_u64(&mut data, 40, 55);
1010 write_u64(&mut data, 48, 66);
1011 write_u64(&mut data, 56, 77);
1012 write_u64(&mut data, 64, 88);
1013 write_u64(&mut data, 72, 99);
1014 write_u64(&mut data, 80, 111);
1015 write_u64(&mut data, 88, 122);
1016 write_u64(&mut data, 96, 133);
1017 write_u64(&mut data, 104, 144);
1018
1019 write_pubkey(&mut data, 112, Pubkey::new_from_array([1; 32]));
1020 write_pubkey(&mut data, 144, Pubkey::new_from_array([2; 32]));
1021 write_pubkey(&mut data, 176, Pubkey::new_from_array([3; 32]));
1022 write_pubkey(&mut data, 208, Pubkey::new_from_array([4; 32]));
1023 write_pubkey(&mut data, 240, Pubkey::new_from_array([5; 32]));
1024 write_pubkey(&mut data, 272, Pubkey::new_from_array([6; 32]));
1025 write_pubkey(&mut data, 304, Pubkey::new_from_array([7; 32]));
1026
1027 write_u64(&mut data, 336, 155);
1028 write_u64(&mut data, 344, 166);
1029
1030 if include_cashback {
1031 write_u64(&mut data, 352, 177);
1032 write_u64(&mut data, 360, 188);
1033 }
1034
1035 data
1036 }
1037
1038 fn build_create_pool_payload(is_mayhem_mode: bool) -> Vec<u8> {
1039 let mut data = vec![0u8; 326];
1040
1041 write_i64(&mut data, 0, 1_713_498_953);
1042 data[8..10].copy_from_slice(&42u16.to_le_bytes());
1043 write_pubkey(&mut data, 10, Pubkey::new_from_array([1; 32]));
1044 write_pubkey(&mut data, 42, Pubkey::new_from_array([2; 32]));
1045 write_pubkey(&mut data, 74, Pubkey::new_from_array([3; 32]));
1046 data[106] = 6;
1047 data[107] = 9;
1048 write_u64(&mut data, 108, 11);
1049 write_u64(&mut data, 116, 22);
1050 write_u64(&mut data, 124, 33);
1051 write_u64(&mut data, 132, 44);
1052 write_u64(&mut data, 140, 55);
1053 write_u64(&mut data, 148, 66);
1054 write_u64(&mut data, 156, 77);
1055 data[164] = 8;
1056 write_pubkey(&mut data, 165, Pubkey::new_from_array([4; 32]));
1057 write_pubkey(&mut data, 197, Pubkey::new_from_array([5; 32]));
1058 write_pubkey(&mut data, 229, Pubkey::new_from_array([6; 32]));
1059 write_pubkey(&mut data, 261, Pubkey::new_from_array([7; 32]));
1060 write_pubkey(&mut data, 293, Pubkey::new_from_array([8; 32]));
1061 data[325] = u8::from(is_mayhem_mode);
1062
1063 data
1064 }
1065
1066 #[test]
1067 fn test_discriminator_simd() {
1068 let log = "Program data: Z/RS H8v1d3cAAAAAAAAAAA=";
1070 let disc = extract_discriminator_simd(log);
1071 assert!(disc.is_some());
1072 }
1073
1074 #[test]
1075 fn test_parse_performance() {
1076 let log = "Program data: Z/RS H8v1d3cAAAAAAAAAAA=";
1078 let sig = Signature::default();
1079
1080 let start = std::time::Instant::now();
1081 for _ in 0..1000 {
1082 let _ = parse_log(log, sig, 0, 0, Some(0), 0);
1083 }
1084 let elapsed = start.elapsed();
1085
1086 println!("Average parse time: {} ns", elapsed.as_nanos() / 1000);
1087 }
1088
1089 #[test]
1090 fn parse_sell_from_data_preserves_cashback_fields() {
1091 let event = parse_sell_from_data(&build_sell_payload(true), metadata())
1092 .expect("expected pumpswap sell event");
1093
1094 let DexEvent::PumpSwapSell(event) = event else {
1095 panic!("expected PumpSwapSell event");
1096 };
1097
1098 assert_eq!(event.cashback_fee_basis_points, 177);
1099 assert_eq!(event.cashback, 188);
1100 assert_eq!(event.coin_creator_fee_basis_points, 155);
1101 assert_eq!(event.coin_creator_fee, 166);
1102 }
1103
1104 #[test]
1105 fn parse_current_buy_from_data_reads_virtual_reserves() {
1106 let event = parse_buy_from_data(&build_buy_payload(true), metadata())
1107 .expect("expected current pumpswap buy event");
1108
1109 let DexEvent::PumpSwapBuy(event) = event else {
1110 panic!("expected PumpSwapBuy event");
1111 };
1112
1113 assert_eq!(event.min_base_amount_out, 22);
1114 assert_eq!(event.ix_name, "buy");
1115 assert_eq!(event.cashback_fee_basis_points, 177);
1116 assert_eq!(event.cashback, 188);
1117 assert_eq!(event.buyback_fee_basis_points, 199);
1118 assert_eq!(event.buyback_fee, 211);
1119 assert_eq!(event.virtual_quote_reserves, -987_654_321);
1120 assert!(event.can_boost);
1121 assert_eq!(event.base_supply, 222);
1122 }
1123
1124 #[test]
1125 fn parse_log_uses_current_buy_layout() {
1126 let mut program_data = discriminators::BUY.to_le_bytes().to_vec();
1127 program_data.extend_from_slice(&build_buy_payload(true));
1128 let log = format!("Program data: {}", STANDARD.encode(program_data));
1129
1130 let event = parse_log(&log, Signature::default(), 7, 8, Some(9), 10)
1131 .expect("expected current pumpswap buy log");
1132 let DexEvent::PumpSwapBuy(event) = event else {
1133 panic!("expected PumpSwapBuy event");
1134 };
1135
1136 assert_eq!(event.metadata.slot, 7);
1137 assert_eq!(event.virtual_quote_reserves, -987_654_321);
1138 assert!(event.can_boost);
1139 }
1140
1141 #[test]
1142 fn parse_log_uses_current_sell_layout() {
1143 let mut payload = build_sell_payload(false);
1144 append_current_trade_tail(&mut payload);
1145 let mut program_data = discriminators::SELL.to_le_bytes().to_vec();
1146 program_data.extend_from_slice(&payload);
1147 let log = format!("Program data: {}", STANDARD.encode(program_data));
1148
1149 let event = parse_log(&log, Signature::default(), 17, 18, Some(19), 20)
1150 .expect("expected current pumpswap sell log");
1151 let DexEvent::PumpSwapSell(event) = event else {
1152 panic!("expected PumpSwapSell event");
1153 };
1154
1155 assert_eq!(event.metadata.slot, 17);
1156 assert_eq!(event.virtual_quote_reserves, -987_654_321);
1157 assert!(event.can_boost);
1158 assert_eq!(event.base_supply, 222);
1159 }
1160
1161 #[test]
1162 fn parse_current_sell_from_data_reads_virtual_reserves() {
1163 let mut data = build_sell_payload(false);
1164 append_current_trade_tail(&mut data);
1165 let event =
1166 parse_sell_from_data(&data, metadata()).expect("expected current pumpswap sell event");
1167
1168 let DexEvent::PumpSwapSell(event) = event else {
1169 panic!("expected PumpSwapSell event");
1170 };
1171
1172 assert_eq!(event.cashback_fee_basis_points, 177);
1173 assert_eq!(event.cashback, 188);
1174 assert_eq!(event.buyback_fee_basis_points, 199);
1175 assert_eq!(event.buyback_fee, 211);
1176 assert_eq!(event.virtual_quote_reserves, -987_654_321);
1177 assert!(event.can_boost);
1178 assert_eq!(event.base_supply, 222);
1179 }
1180
1181 #[test]
1182 fn trade_parsers_preserve_i128_extremes() {
1183 let mut buy = build_buy_payload(true);
1184 buy[432..448].copy_from_slice(&i128::MIN.to_le_bytes());
1185 let DexEvent::PumpSwapBuy(buy) =
1186 parse_buy_from_data(&buy, metadata()).expect("expected current buy")
1187 else {
1188 panic!("expected PumpSwapBuy event");
1189 };
1190 assert_eq!(buy.virtual_quote_reserves, i128::MIN);
1191
1192 let mut sell = build_sell_payload(false);
1193 append_current_trade_tail(&mut sell);
1194 sell[384..400].copy_from_slice(&i128::MAX.to_le_bytes());
1195 let DexEvent::PumpSwapSell(sell) =
1196 parse_sell_from_data(&sell, metadata()).expect("expected current sell")
1197 else {
1198 panic!("expected PumpSwapSell event");
1199 };
1200 assert_eq!(sell.virtual_quote_reserves, i128::MAX);
1201 }
1202
1203 #[test]
1204 fn parse_sell_from_data_keeps_legacy_payload_compatible() {
1205 let event = parse_sell_from_data(&build_sell_payload(false), metadata())
1206 .expect("expected legacy pumpswap sell event");
1207
1208 let DexEvent::PumpSwapSell(event) = event else {
1209 panic!("expected PumpSwapSell event");
1210 };
1211
1212 assert_eq!(event.cashback_fee_basis_points, 0);
1213 assert_eq!(event.cashback, 0);
1214 assert_eq!(event.coin_creator_fee_basis_points, 155);
1215 assert_eq!(event.coin_creator_fee, 166);
1216 assert_eq!(event.virtual_quote_reserves, 0);
1217 assert!(!event.can_boost);
1218 assert_eq!(event.base_supply, 0);
1219 }
1220
1221 #[test]
1222 fn parse_buyback_layouts_without_boost_fields() {
1223 let mut buy = build_buy_payload(false);
1224 append_buyback_trade_tail(&mut buy);
1225 let DexEvent::PumpSwapBuy(buy) =
1226 parse_buy_from_data(&buy, metadata()).expect("expected buyback buy event")
1227 else {
1228 panic!("expected PumpSwapBuy event");
1229 };
1230 assert_eq!(buy.cashback_fee_basis_points, 177);
1231 assert_eq!(buy.buyback_fee_basis_points, 199);
1232 assert_eq!(buy.buyback_fee, 211);
1233 assert_eq!(buy.virtual_quote_reserves, 0);
1234
1235 let mut sell = build_sell_payload(false);
1236 append_buyback_trade_tail(&mut sell);
1237 let DexEvent::PumpSwapSell(sell) =
1238 parse_sell_from_data(&sell, metadata()).expect("expected buyback sell event")
1239 else {
1240 panic!("expected PumpSwapSell event");
1241 };
1242 assert_eq!(sell.cashback_fee_basis_points, 177);
1243 assert_eq!(sell.buyback_fee_basis_points, 199);
1244 assert_eq!(sell.buyback_fee, 211);
1245 assert_eq!(sell.virtual_quote_reserves, 0);
1246 }
1247
1248 #[test]
1249 fn trade_tail_accepts_only_complete_layouts() {
1250 for len in 0..=64 {
1251 let tail = vec![0u8; len];
1252 let expected = matches!(len, 0 | 16 | 32 | 57..=64);
1253 assert_eq!(parse_trade_tail(&tail).is_some(), expected, "tail length {len}");
1254 }
1255
1256 for invalid_bool in 2..=u8::MAX {
1257 let mut tail = vec![0u8; 57];
1258 tail[48] = invalid_bool;
1259 assert!(parse_trade_tail(&tail).is_none(), "bool value {invalid_bool}");
1260 }
1261 }
1262
1263 #[test]
1264 fn parse_buy_from_data_rejects_truncated_min_base_payload() {
1265 assert!(parse_buy_from_data(&vec![0u8; 385], metadata()).is_some());
1266 for len in 386..397 {
1267 assert!(parse_buy_from_data(&vec![0u8; len], metadata()).is_none());
1268 }
1269 assert!(parse_buy_from_data(&vec![0u8; 397], metadata()).is_some());
1270 }
1271
1272 #[test]
1273 fn parse_buy_from_data_rejects_malformed_string_and_partial_tails() {
1274 let mut invalid_track_volume = build_buy_payload(false);
1275 invalid_track_volume[352] = 2;
1276 assert!(parse_buy_from_data(&invalid_track_volume, metadata()).is_none());
1277
1278 let mut invalid_utf8 = build_buy_payload(false);
1279 invalid_utf8[397] = 0xff;
1280 assert!(parse_buy_from_data(&invalid_utf8, metadata()).is_none());
1281
1282 let legacy = build_buy_payload(false);
1283 for partial_len in [1, 15, 17, 31, 33, 56] {
1284 let mut partial = legacy.clone();
1285 partial.resize(legacy.len() + partial_len, 0);
1286 assert!(parse_buy_from_data(&partial, metadata()).is_none());
1287 }
1288
1289 let mut oversized_name = vec![0u8; 397];
1290 oversized_name[393..397].copy_from_slice(&u32::MAX.to_le_bytes());
1291 assert!(parse_buy_from_data(&oversized_name, metadata()).is_none());
1292 }
1293
1294 #[test]
1295 fn parse_sell_from_data_rejects_partial_or_invalid_boost_tails() {
1296 for truncated_len in 336..352 {
1297 assert!(parse_sell_from_data(&vec![0u8; truncated_len], metadata()).is_none());
1298 }
1299
1300 let legacy = build_sell_payload(false);
1301 for partial_len in [1, 15, 17, 31, 33, 56] {
1302 let mut partial = legacy.clone();
1303 partial.resize(legacy.len() + partial_len, 0);
1304 assert!(parse_sell_from_data(&partial, metadata()).is_none());
1305 }
1306
1307 let mut invalid_bool = legacy;
1308 append_current_trade_tail(&mut invalid_bool);
1309 invalid_bool[352 + 48] = 2;
1310 assert!(parse_sell_from_data(&invalid_bool, metadata()).is_none());
1311 }
1312
1313 #[test]
1314 fn parse_create_pool_from_data_reads_mayhem_mode() {
1315 let event = parse_create_pool_from_data(&build_create_pool_payload(true), metadata())
1316 .expect("expected pumpswap create pool event");
1317
1318 let DexEvent::PumpSwapCreatePool(event) = event else {
1319 panic!("expected PumpSwapCreatePool event");
1320 };
1321
1322 assert_eq!(event.index, 42);
1323 assert!(event.is_mayhem_mode);
1324 assert!(!event.is_cashback_coin);
1325 }
1326}