Skip to main content

sol_parser_sdk/instr/
mod.rs

1//! 指令解析器模块
2//!
3//! 包含所有 DEX 协议的指令解析器实现
4
5pub mod meteora_amm;
6pub mod meteora_damm;
7pub mod meteora_dlmm;
8pub mod orca_whirlpool;
9pub mod program_ids;
10pub mod pump;
11pub mod pump_amm;
12pub mod pump_fees;
13pub mod raydium_amm;
14pub mod raydium_clmm;
15pub mod raydium_cpmm;
16pub mod raydium_launchlab;
17pub mod utils;
18
19// Inner instruction 解析器(16字节 discriminator)
20pub mod all_inner;
21pub mod inner_common; // 通用零拷贝读取函数
22pub mod pump_amm_inner; // PumpSwap inner instruction
23pub mod pump_inner; // PumpFun inner instruction
24pub mod raydium_clmm_inner; // Raydium CLMM inner instruction // 其他所有协议的 inner instruction(统一文件)
25use crate::grpc::types::EventTypeFilter;
26// 重新导出主要解析函数
27pub use meteora_damm::parse_instruction as parse_meteora_damm_instruction;
28pub use pump::parse_instruction as parse_pumpfun_instruction;
29pub use pump_amm::parse_instruction as parse_pumpswap_instruction;
30pub use raydium_launchlab::parse_instruction as parse_raydium_launchlab_instruction;
31
32// 重新导出工具函数
33pub use utils::*;
34
35use crate::core::events::DexEvent;
36use program_ids::*;
37use solana_sdk::{pubkey::Pubkey, signature::Signature};
38
39#[inline(always)]
40fn disc8(instruction_data: &[u8]) -> Option<[u8; 8]> {
41    instruction_data.get(..8)?.try_into().ok()
42}
43
44#[inline(always)]
45fn supports_pumpfun_instruction(disc: [u8; 8]) -> bool {
46    matches!(
47        disc,
48        pump::discriminators::CREATE
49            | pump::discriminators::BUY_V3
50            | pump::discriminators::BUY_EXACT_QUOTE_IN_V3
51            | pump::discriminators::SELL_V3
52            | pump::discriminators::CREATE_V2
53            | pump::discriminators::BUY
54            | pump::discriminators::SELL
55            | pump::discriminators::BUY_EXACT_SOL_IN
56            | pump::discriminators::BUY_V2
57            | pump::discriminators::BUY_EXACT_QUOTE_IN_V2
58            | pump::discriminators::SELL_V2
59            | pump::discriminators::MIGRATE_BONDING_CURVE_CREATOR
60    )
61}
62
63#[inline(always)]
64fn supports_pumpswap_instruction(disc: [u8; 8]) -> bool {
65    matches!(
66        disc,
67        pump_amm::discriminators::BUY
68            | pump_amm::discriminators::BUY_V2
69            | pump_amm::discriminators::BUY_EXACT_QUOTE_IN_V2
70            | pump_amm::discriminators::SELL_V2
71            | pump_amm::discriminators::SELL
72            | pump_amm::discriminators::CREATE_POOL
73            | pump_amm::discriminators::BUY_EXACT_QUOTE_IN
74            | pump_amm::discriminators::DEPOSIT
75            | pump_amm::discriminators::WITHDRAW
76    )
77}
78
79#[inline(always)]
80fn supports_pump_fees_instruction(disc: [u8; 8]) -> bool {
81    matches!(
82        disc,
83        pump_fees::CREATE_FEE_SHARING_IX
84            | pump_fees::INITIALIZE_FEE_CONFIG_IX
85            | pump_fees::RESET_FEE_SHARING_IX
86            | pump_fees::RESET_FEE_SHARING_V2_IX
87            | pump_fees::REVOKE_FEE_SHARING_IX
88            | pump_fees::TRANSFER_FEE_SHARING_IX
89            | pump_fees::UPDATE_ADMIN_IX
90            | pump_fees::UPDATE_FEE_CONFIG_IX
91            | pump_fees::UPDATE_FEE_SHARES_IX
92            | pump_fees::UPDATE_FEE_SHARES_V2_IX
93            | pump_fees::UPSERT_FEE_TIERS_IX
94    )
95}
96
97#[inline(always)]
98fn supports_launchlab_instruction(disc: [u8; 8]) -> bool {
99    matches!(
100        disc,
101        raydium_launchlab::discriminators::BUY_EXACT_IN
102            | raydium_launchlab::discriminators::BUY_EXACT_OUT
103            | raydium_launchlab::discriminators::SELL_EXACT_IN
104            | raydium_launchlab::discriminators::SELL_EXACT_OUT
105            | raydium_launchlab::discriminators::INITIALIZE
106            | raydium_launchlab::discriminators::INITIALIZE_V2
107            | raydium_launchlab::discriminators::INITIALIZE_WITH_TOKEN_2022
108            | raydium_launchlab::discriminators::MIGRATE_TO_AMM
109            | raydium_launchlab::discriminators::MIGRATE_TO_CPSWAP
110    )
111}
112
113#[inline(always)]
114fn supports_cpmm_instruction(disc: [u8; 8]) -> bool {
115    matches!(
116        disc,
117        raydium_cpmm::discriminators::SWAP_BASE_IN
118            | raydium_cpmm::discriminators::SWAP_BASE_OUT
119            | raydium_cpmm::discriminators::INITIALIZE
120            | raydium_cpmm::discriminators::INITIALIZE_PERMISSION
121            | raydium_cpmm::discriminators::DEPOSIT
122            | raydium_cpmm::discriminators::WITHDRAW
123            | raydium_cpmm::discriminators::COLLECT_CREATOR_FEE
124            | raydium_cpmm::discriminators::COLLECT_CREATOR_FEE_PERMISSIONLESS
125    )
126}
127
128#[inline(always)]
129fn supports_clmm_instruction(disc: [u8; 8]) -> bool {
130    matches!(
131        disc,
132        raydium_clmm::discriminators::SWAP
133            | raydium_clmm::discriminators::SWAP_V2
134            | raydium_clmm::discriminators::INCREASE_LIQUIDITY_V2
135            | raydium_clmm::discriminators::DECREASE_LIQUIDITY_V2
136            | raydium_clmm::discriminators::CREATE_POOL
137            | raydium_clmm::discriminators::CREATE_CUSTOMIZABLE_POOL
138            | raydium_clmm::discriminators::OPEN_POSITION
139            | raydium_clmm::discriminators::OPEN_POSITION_V2
140            | raydium_clmm::discriminators::OPEN_POSITION_WITH_TOKEN_22_NFT
141            | raydium_clmm::discriminators::CLOSE_POSITION
142    )
143}
144
145#[inline(always)]
146fn supports_raydium_amm_v4_instruction(instruction_data: &[u8]) -> bool {
147    matches!(
148        instruction_data.first().copied(),
149        Some(raydium_amm::discriminators::SWAP_BASE_IN)
150            | Some(raydium_amm::discriminators::SWAP_BASE_OUT)
151            | Some(raydium_amm::discriminators::DEPOSIT)
152            | Some(raydium_amm::discriminators::WITHDRAW)
153            | Some(raydium_amm::discriminators::INITIALIZE2)
154            | Some(raydium_amm::discriminators::WITHDRAW_PNL)
155            | Some(raydium_amm::discriminators::SWAP_BASE_IN_V2)
156            | Some(raydium_amm::discriminators::SWAP_BASE_OUT_V2)
157    )
158}
159
160#[inline(always)]
161fn supports_orca_instruction(disc: [u8; 8]) -> bool {
162    matches!(
163        disc,
164        orca_whirlpool::discriminators::SWAP
165            | orca_whirlpool::discriminators::SWAP_V2
166            | orca_whirlpool::discriminators::INCREASE_LIQUIDITY
167            | orca_whirlpool::discriminators::DECREASE_LIQUIDITY
168            | orca_whirlpool::discriminators::INITIALIZE_POOL
169            | orca_whirlpool::discriminators::INITIALIZE_POOL_V2
170            | orca_whirlpool::discriminators::INCREASE_LIQUIDITY_V2
171            | orca_whirlpool::discriminators::DECREASE_LIQUIDITY_V2
172    )
173}
174
175#[inline(always)]
176fn supports_meteora_pools_instruction(disc: [u8; 8]) -> bool {
177    matches!(
178        disc,
179        meteora_amm::discriminators::SWAP
180            | meteora_amm::discriminators::ADD_LIQUIDITY
181            | meteora_amm::discriminators::ADD_IMBALANCE_LIQUIDITY
182            | meteora_amm::discriminators::REMOVE_LIQUIDITY
183            | meteora_amm::discriminators::REMOVE_LIQUIDITY_SINGLE_SIDE
184            | meteora_amm::discriminators::BOOTSTRAP_LIQUIDITY
185            | meteora_amm::discriminators::INITIALIZE_PERMISSIONED_POOL
186            | meteora_amm::discriminators::INITIALIZE_PERMISSIONLESS_POOL
187            | meteora_amm::discriminators::INITIALIZE_PERMISSIONLESS_POOL_WITH_FEE_TIER
188            | meteora_amm::discriminators::INITIALIZE_CUSTOMIZABLE_POOL
189            | meteora_amm::discriminators::CREATE_POOL
190            | meteora_amm::discriminators::CREATE_POOL_WITH_CONFIG2
191            | meteora_amm::discriminators::SET_POOL_FEES
192    )
193}
194
195#[inline(always)]
196fn supports_meteora_damm_v2_instruction(instruction_data: &[u8]) -> bool {
197    let Some(disc) = disc8(instruction_data) else {
198        return false;
199    };
200    if disc == meteora_damm::discriminators::INITIALIZE_POOL {
201        return true;
202    }
203    let Some(cpi_disc) = instruction_data.get(8..16).and_then(|bytes| bytes.try_into().ok()) else {
204        return false;
205    };
206    matches!(
207        cpi_disc,
208        meteora_damm::discriminators::SWAP_LOG
209            | meteora_damm::discriminators::SWAP2_LOG
210            | meteora_damm::discriminators::CREATE_POSITION_LOG
211            | meteora_damm::discriminators::CLOSE_POSITION_LOG
212            | meteora_damm::discriminators::ADD_LIQUIDITY_LOG
213            | meteora_damm::discriminators::REMOVE_LIQUIDITY_LOG
214    )
215}
216
217#[inline(always)]
218fn supports_meteora_dlmm_instruction(instruction_data: &[u8]) -> bool {
219    let Some(disc) = disc8(instruction_data) else {
220        return false;
221    };
222    matches!(
223        disc,
224        meteora_dlmm::discriminators::INITIALIZE_LB_PAIR
225            | meteora_dlmm::discriminators::INITIALIZE_LB_PAIR2
226            | meteora_dlmm::discriminators::INITIALIZE_BIN_ARRAY
227            | meteora_dlmm::discriminators::ADD_LIQUIDITY
228            | meteora_dlmm::discriminators::ADD_LIQUIDITY2
229            | meteora_dlmm::discriminators::REMOVE_LIQUIDITY
230            | meteora_dlmm::discriminators::REMOVE_LIQUIDITY2
231            | meteora_dlmm::discriminators::INITIALIZE_POSITION
232            | meteora_dlmm::discriminators::INITIALIZE_POSITION2
233            | meteora_dlmm::discriminators::INITIALIZE_POSITION_PDA
234            | meteora_dlmm::discriminators::SWAP
235            | meteora_dlmm::discriminators::SWAP2
236            | meteora_dlmm::discriminators::SWAP_EXACT_OUT
237            | meteora_dlmm::discriminators::SWAP_EXACT_OUT2
238            | meteora_dlmm::discriminators::SWAP_WITH_PRICE_IMPACT
239            | meteora_dlmm::discriminators::SWAP_WITH_PRICE_IMPACT2
240            | meteora_dlmm::discriminators::CLAIM_FEE
241            | meteora_dlmm::discriminators::CLAIM_FEE2
242            | meteora_dlmm::discriminators::CLOSE_POSITION
243            | meteora_dlmm::discriminators::CLOSE_POSITION2
244    )
245}
246
247#[inline(always)]
248pub(crate) fn instruction_data_may_parse(program_id: &Pubkey, instruction_data: &[u8]) -> bool {
249    if instruction_data.is_empty() {
250        return false;
251    }
252    if *program_id == RAYDIUM_AMM_V4_PROGRAM_ID {
253        return supports_raydium_amm_v4_instruction(instruction_data);
254    }
255    if *program_id == METEORA_DLMM_PROGRAM_ID {
256        return supports_meteora_dlmm_instruction(instruction_data);
257    }
258    if *program_id == METEORA_DBC_PROGRAM_ID {
259        return instruction_data.get(..8) == Some(&[228, 69, 165, 46, 81, 203, 154, 29])
260            && instruction_data.len() >= 16;
261    }
262    if *program_id == METEORA_DAMM_V2_PROGRAM_ID {
263        return supports_meteora_damm_v2_instruction(instruction_data);
264    }
265
266    let Some(disc) = disc8(instruction_data) else {
267        return false;
268    };
269    if *program_id == PUMPFUN_PROGRAM_ID {
270        supports_pumpfun_instruction(disc)
271    } else if *program_id == PUMPSWAP_PROGRAM_ID {
272        supports_pumpswap_instruction(disc)
273    } else if *program_id == PUMP_FEES_PROGRAM_ID {
274        supports_pump_fees_instruction(disc)
275    } else if *program_id == RAYDIUM_LAUNCHLAB_PROGRAM_ID {
276        supports_launchlab_instruction(disc)
277    } else if *program_id == RAYDIUM_CPMM_PROGRAM_ID {
278        supports_cpmm_instruction(disc)
279    } else if *program_id == RAYDIUM_CLMM_PROGRAM_ID {
280        supports_clmm_instruction(disc)
281    } else if *program_id == ORCA_WHIRLPOOL_PROGRAM_ID {
282        supports_orca_instruction(disc)
283    } else if *program_id == METEORA_POOLS_PROGRAM_ID {
284        supports_meteora_pools_instruction(disc)
285    } else {
286        false
287    }
288}
289
290#[inline(always)]
291pub(crate) fn normal_instruction_data_may_parse(
292    program_id: &Pubkey,
293    instruction_data: &[u8],
294) -> bool {
295    if *program_id == METEORA_DBC_PROGRAM_ID {
296        return false;
297    }
298    if *program_id == METEORA_DAMM_V2_PROGRAM_ID {
299        return disc8(instruction_data)
300            .is_some_and(|disc| disc == meteora_damm::discriminators::INITIALIZE_POOL);
301    }
302    instruction_data_may_parse(program_id, instruction_data)
303}
304
305#[inline(always)]
306fn filter_parsed_event(
307    event: Option<DexEvent>,
308    event_type_filter: Option<&EventTypeFilter>,
309) -> Option<DexEvent> {
310    let event = event?;
311    if event_type_filter.map(|f| f.should_include_dex_event(&event)).unwrap_or(true) {
312        Some(event)
313    } else {
314        None
315    }
316}
317
318/// 统一的指令解析入口函数
319#[inline]
320pub fn parse_instruction_unified(
321    instruction_data: &[u8],
322    accounts: &[Pubkey],
323    signature: Signature,
324    slot: u64,
325    tx_index: u64,
326    block_time_us: Option<i64>,
327    grpc_recv_us: i64,
328    event_type_filter: Option<&EventTypeFilter>,
329    program_id: &Pubkey,
330) -> Option<DexEvent> {
331    // 快速检查指令数据长度,避免无效解析
332    if instruction_data.is_empty() {
333        return None;
334    }
335
336    if *program_id == METEORA_DBC_PROGRAM_ID {
337        if event_type_filter.is_some_and(|f| !f.includes_meteora_dbc()) {
338            return None;
339        }
340        let metadata = crate::logs::utils::create_metadata_simple(
341            signature,
342            slot,
343            tx_index,
344            block_time_us,
345            Pubkey::default(),
346            grpc_recv_us,
347        );
348        return filter_parsed_event(
349            crate::logs::meteora_dbc::parse_event_cpi(instruction_data, metadata),
350            event_type_filter,
351        );
352    }
353
354    // 根据程序 ID 路由到相应的解析器,按使用频率排序
355
356    // Pumpfun
357    if *program_id == PUMPFUN_PROGRAM_ID {
358        if event_type_filter.is_some() && !event_type_filter.unwrap().includes_pumpfun() {
359            return None;
360        }
361        return filter_parsed_event(
362            parse_pumpfun_instruction(
363                instruction_data,
364                accounts,
365                signature,
366                slot,
367                tx_index,
368                block_time_us,
369                grpc_recv_us,
370            ),
371            event_type_filter,
372        );
373    }
374    // PumpSwap (Pump AMM)
375    else if *program_id == PUMPSWAP_PROGRAM_ID {
376        if event_type_filter.is_some() && !event_type_filter.unwrap().includes_pumpswap() {
377            return None;
378        }
379        return filter_parsed_event(
380            parse_pumpswap_instruction(
381                instruction_data,
382                accounts,
383                signature,
384                slot,
385                tx_index,
386                block_time_us,
387            ),
388            event_type_filter,
389        );
390    }
391    // Meteora DAMM
392    else if *program_id == METEORA_DAMM_V2_PROGRAM_ID {
393        if event_type_filter.is_some() && !event_type_filter.unwrap().includes_meteora_damm_v2() {
394            return None;
395        }
396        return filter_parsed_event(
397            parse_meteora_damm_instruction(
398                instruction_data,
399                accounts,
400                signature,
401                slot,
402                tx_index,
403                block_time_us,
404                grpc_recv_us,
405            ),
406            event_type_filter,
407        );
408    }
409    // Pump fees (`pfeeUx...`)
410    else if *program_id == PUMP_FEES_PROGRAM_ID {
411        if event_type_filter.is_some() && !event_type_filter.unwrap().includes_pump_fees() {
412            return None;
413        }
414        return filter_parsed_event(
415            crate::instr::pump_fees::parse_instruction(
416                instruction_data,
417                accounts,
418                signature,
419                slot,
420                tx_index,
421                block_time_us,
422                grpc_recv_us,
423            ),
424            event_type_filter,
425        );
426    }
427    // RaydiumLaunchlab / LaunchLab
428    else if *program_id == RAYDIUM_LAUNCHLAB_PROGRAM_ID {
429        if event_type_filter.is_some() && !event_type_filter.unwrap().includes_raydium_launchlab() {
430            return None;
431        }
432        return filter_parsed_event(
433            parse_raydium_launchlab_instruction(
434                instruction_data,
435                accounts,
436                signature,
437                slot,
438                tx_index,
439                block_time_us,
440            ),
441            event_type_filter,
442        );
443    }
444    // Raydium CPMM
445    else if *program_id == RAYDIUM_CPMM_PROGRAM_ID {
446        if event_type_filter.is_some() && !event_type_filter.unwrap().includes_raydium_cpmm() {
447            return None;
448        }
449        return filter_parsed_event(
450            crate::instr::raydium_cpmm::parse_instruction(
451                instruction_data,
452                accounts,
453                signature,
454                slot,
455                tx_index,
456                block_time_us,
457            ),
458            event_type_filter,
459        );
460    }
461    // Raydium CLMM
462    else if *program_id == RAYDIUM_CLMM_PROGRAM_ID {
463        if event_type_filter.is_some() && !event_type_filter.unwrap().includes_raydium_clmm() {
464            return None;
465        }
466        return filter_parsed_event(
467            crate::instr::raydium_clmm::parse_instruction(
468                instruction_data,
469                accounts,
470                signature,
471                slot,
472                tx_index,
473                block_time_us,
474            ),
475            event_type_filter,
476        );
477    }
478    // Raydium AMM V4
479    else if *program_id == RAYDIUM_AMM_V4_PROGRAM_ID {
480        if event_type_filter.is_some() && !event_type_filter.unwrap().includes_raydium_amm_v4() {
481            return None;
482        }
483        return filter_parsed_event(
484            crate::instr::raydium_amm::parse_instruction(
485                instruction_data,
486                accounts,
487                signature,
488                slot,
489                tx_index,
490                block_time_us,
491            ),
492            event_type_filter,
493        );
494    }
495    // Orca Whirlpool
496    else if *program_id == ORCA_WHIRLPOOL_PROGRAM_ID {
497        if event_type_filter.is_some() && !event_type_filter.unwrap().includes_orca_whirlpool() {
498            return None;
499        }
500        return filter_parsed_event(
501            crate::instr::orca_whirlpool::parse_instruction(
502                instruction_data,
503                accounts,
504                signature,
505                slot,
506                tx_index,
507                block_time_us,
508            ),
509            event_type_filter,
510        );
511    }
512    // Meteora Pools / AMM
513    else if *program_id == METEORA_POOLS_PROGRAM_ID {
514        if event_type_filter.is_some() && !event_type_filter.unwrap().includes_meteora_pools() {
515            return None;
516        }
517        return filter_parsed_event(
518            crate::instr::meteora_amm::parse_instruction(
519                instruction_data,
520                accounts,
521                signature,
522                slot,
523                tx_index,
524                block_time_us,
525            ),
526            event_type_filter,
527        );
528    }
529    // Meteora DLMM
530    else if *program_id == METEORA_DLMM_PROGRAM_ID {
531        if event_type_filter.is_some() && !event_type_filter.unwrap().includes_meteora_dlmm() {
532            return None;
533        }
534        return filter_parsed_event(
535            crate::instr::meteora_dlmm::parse_instruction(
536                instruction_data,
537                accounts,
538                signature,
539                slot,
540                tx_index,
541                block_time_us,
542            ),
543            event_type_filter,
544        );
545    }
546
547    None
548}
549
550#[cfg(test)]
551mod tests {
552    use super::*;
553
554    fn data8(disc: [u8; 8]) -> Vec<u8> {
555        let mut data = Vec::from(disc);
556        data.extend_from_slice(&1u64.to_le_bytes());
557        data.extend_from_slice(&2u64.to_le_bytes());
558        data
559    }
560
561    #[test]
562    fn instruction_data_gate_covers_supported_normal_instruction_protocols() {
563        assert!(instruction_data_may_parse(
564            &PUMPSWAP_PROGRAM_ID,
565            &data8(pump_amm::discriminators::CREATE_POOL)
566        ));
567        assert!(instruction_data_may_parse(
568            &PUMP_FEES_PROGRAM_ID,
569            &data8(pump_fees::UPDATE_FEE_SHARES_IX)
570        ));
571        assert!(instruction_data_may_parse(
572            &RAYDIUM_LAUNCHLAB_PROGRAM_ID,
573            &data8(raydium_launchlab::discriminators::BUY_EXACT_IN)
574        ));
575        assert!(instruction_data_may_parse(
576            &RAYDIUM_CPMM_PROGRAM_ID,
577            &data8(raydium_cpmm::discriminators::SWAP_BASE_IN)
578        ));
579        assert!(instruction_data_may_parse(
580            &RAYDIUM_CLMM_PROGRAM_ID,
581            &data8(raydium_clmm::discriminators::SWAP_V2)
582        ));
583        assert!(instruction_data_may_parse(
584            &RAYDIUM_AMM_V4_PROGRAM_ID,
585            &[raydium_amm::discriminators::SWAP_BASE_IN, 1, 0, 0, 0, 0, 0, 0, 0]
586        ));
587        assert!(instruction_data_may_parse(
588            &ORCA_WHIRLPOOL_PROGRAM_ID,
589            &data8(orca_whirlpool::discriminators::SWAP)
590        ));
591        assert!(instruction_data_may_parse(
592            &METEORA_POOLS_PROGRAM_ID,
593            &data8(meteora_amm::discriminators::CREATE_POOL)
594        ));
595        assert!(instruction_data_may_parse(
596            &METEORA_DAMM_V2_PROGRAM_ID,
597            &data8(meteora_damm::discriminators::INITIALIZE_POOL)
598        ));
599        assert!(instruction_data_may_parse(
600            &METEORA_DLMM_PROGRAM_ID,
601            &data8(meteora_dlmm::discriminators::SWAP2)
602        ));
603        assert!(!instruction_data_may_parse(&METEORA_DLMM_PROGRAM_ID, &[11, 1, 2, 3]));
604    }
605
606    #[test]
607    fn instruction_data_gate_rejects_unknown_program_and_event_cpi_layouts() {
608        assert!(!instruction_data_may_parse(&Pubkey::new_unique(), &data8([1; 8])));
609        assert!(!instruction_data_may_parse(&PUMPSWAP_PROGRAM_ID, &data8([0xff; 8])));
610        assert!(instruction_data_may_parse(
611            &PUMPFUN_PROGRAM_ID,
612            &data8(pump::discriminators::MIGRATE_BONDING_CURVE_CREATOR)
613        ));
614
615        let mut pumpswap_event_cpi = Vec::from(pump_amm_inner::discriminators::CREATE_POOL);
616        pumpswap_event_cpi.extend_from_slice(&[0; 64]);
617        assert!(!instruction_data_may_parse(&PUMPSWAP_PROGRAM_ID, &pumpswap_event_cpi));
618    }
619
620    #[test]
621    fn normal_instruction_gate_keeps_meteora_damm_event_cpi_on_event_path() {
622        let mut event_cpi = Vec::new();
623        event_cpi.extend_from_slice(&[228, 69, 165, 46, 81, 203, 154, 29]);
624        event_cpi.extend_from_slice(&meteora_damm::discriminators::SWAP_LOG);
625        event_cpi.extend_from_slice(&[0; 64]);
626
627        assert!(instruction_data_may_parse(&METEORA_DAMM_V2_PROGRAM_ID, &event_cpi));
628        assert!(!normal_instruction_data_may_parse(&METEORA_DAMM_V2_PROGRAM_ID, &event_cpi));
629        assert!(normal_instruction_data_may_parse(
630            &METEORA_DAMM_V2_PROGRAM_ID,
631            &data8(meteora_damm::discriminators::INITIALIZE_POOL)
632        ));
633    }
634}