1use borsh::BorshDeserialize;
2use light_compressed_account::{
3 compressed_account::{
4 CompressedAccount, CompressedAccountData, PackedCompressedAccountWithMerkleContext,
5 },
6 constants::{
7 ACCOUNT_COMPRESSION_PROGRAM_ID, CREATE_CPI_CONTEXT_ACCOUNT, REGISTERED_PROGRAM_PDA,
8 SYSTEM_PROGRAM_ID,
9 },
10 discriminators::*,
11 instruction_data::{
12 data::{InstructionDataInvoke, OutputCompressedAccountWithPackedContext},
13 insert_into_queues::InsertIntoQueuesInstructionData,
14 with_account_info::InstructionDataInvokeCpiWithAccountInfo,
15 with_readonly::InstructionDataInvokeCpiWithReadOnly,
16 },
17 nullifier::create_nullifier,
18 Pubkey,
19};
20use light_zero_copy::traits::ZeroCopyAt;
21
22use super::{
23 error::ParseIndexerEventError,
24 event::{
25 BatchNullifyContext, BatchPublicTransactionEvent, MerkleTreeSequenceNumber,
26 MerkleTreeSequenceNumberV1, NewAddress, PublicTransactionEvent,
27 },
28};
29
30#[derive(Debug, Clone, PartialEq)]
31struct ExecutingSystemInstruction<'a> {
32 output_compressed_accounts: Vec<OutputCompressedAccountWithPackedContext>,
33 input_compressed_accounts: Vec<PackedCompressedAccountWithMerkleContext>,
34 is_compress: bool,
35 relay_fee: Option<u64>,
36 compress_or_decompress_lamports: Option<u64>,
37 execute_cpi_context: bool,
38 accounts: &'a [Pubkey],
39}
40
41#[derive(Debug, Clone, PartialEq, Default)]
42pub(crate) struct Indices {
43 pub system: usize,
44 pub cpi: Vec<usize>,
45 pub insert_into_queues: usize,
46 pub found_solana_system_program_instruction: bool,
47 pub found_system: bool,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq)]
51pub(crate) enum ProgramId {
52 LightSystem,
53 AccountCompression,
54 SolanaSystem,
55 Unknown,
56}
57
58#[derive(Debug, Clone, PartialEq)]
59struct AssociatedInstructions<'a> {
60 pub executing_system_instruction: ExecutingSystemInstruction<'a>,
61 pub cpi_context_outputs: Vec<OutputCompressedAccountWithPackedContext>,
62 pub insert_into_queues_instruction: InsertIntoQueuesInstructionData<'a>,
63 pub accounts: &'a [Pubkey],
64}
65
66pub fn event_from_light_transaction(
84 program_ids: &[Pubkey],
85 instructions: &[Vec<u8>],
86 accounts: Vec<Vec<Pubkey>>,
87) -> Result<Option<Vec<BatchPublicTransactionEvent>>, ParseIndexerEventError> {
88 let program_ids = wrap_program_ids(program_ids, instructions, &accounts);
90 let mut patterns = find_cpi_patterns(&program_ids);
92 if patterns.is_empty() {
93 return Ok(None);
94 }
95 patterns.reverse();
98 let associated_instructions = patterns
100 .iter()
101 .map(|pattern| deserialize_associated_instructions(pattern, instructions, &accounts))
102 .collect::<Result<Vec<_>, _>>()?;
103 let batched_transaction_events = associated_instructions
105 .iter()
106 .map(|associated_instruction| create_batched_transaction_event(associated_instruction))
107 .collect::<Result<Vec<_>, _>>()?;
108
109 Ok(Some(batched_transaction_events))
134}
135
136fn deserialize_associated_instructions<'a>(
137 indices: &Indices,
138 instructions: &'a [Vec<u8>],
139 accounts: &'a [Vec<Pubkey>],
140) -> Result<AssociatedInstructions<'a>, ParseIndexerEventError> {
141 let (insert_queues_instruction, cpi_context_outputs) = {
142 let ix = &instructions[indices.insert_into_queues];
143 if ix.len() < 12 {
144 return Err(ParseIndexerEventError::InstructionDataTooSmall(
145 ix.len(),
146 12,
147 ));
148 }
149 let discriminator: [u8; 8] = ix[0..8].try_into().unwrap();
150 if discriminator == DISCRIMINATOR_INSERT_INTO_QUEUES {
151 let (data, bytes) = InsertIntoQueuesInstructionData::zero_copy_at(&ix[12..])?;
152 let cpi_context_outputs =
153 Vec::<OutputCompressedAccountWithPackedContext>::deserialize(&mut &bytes[..])?;
154 Ok((data, cpi_context_outputs))
155 } else {
156 Err(ParseIndexerEventError::DeserializeAccountLightSystemCpiInputsError)
157 }
158 }?;
159 let exec_instruction =
160 deserialize_instruction(&instructions[indices.system], &accounts[indices.system])?;
161 Ok(AssociatedInstructions {
162 executing_system_instruction: exec_instruction,
163 cpi_context_outputs,
164 insert_into_queues_instruction: insert_queues_instruction,
165 accounts: &accounts[indices.insert_into_queues][2..],
167 })
168}
169
170fn find_cpi_patterns(program_ids: &[ProgramId]) -> Vec<Indices> {
177 let mut vec = Vec::new();
178 let mut next_index = usize::MAX;
179 for (last_index, program_id) in (0..program_ids.len()).rev().zip(program_ids.iter().rev()) {
180 if last_index > next_index {
182 continue;
183 }
184 if let ProgramId::AccountCompression = program_id {
187 let (res, last_index) = find_cpi_pattern(last_index, program_ids);
188 next_index = last_index;
189 if let Some(res) = res {
190 vec.push(res);
191 };
192 }
193 }
194 vec
195}
196
197fn find_cpi_pattern(start_index: usize, program_ids: &[ProgramId]) -> (Option<Indices>, usize) {
202 let mut index_account = Indices {
203 insert_into_queues: start_index,
204 ..Default::default()
205 };
206 for (index, program_id) in (0..start_index)
207 .rev()
208 .zip(program_ids[..start_index].iter().rev())
209 {
210 if let ProgramId::SolanaSystem = program_id {
211 index_account.found_solana_system_program_instruction = true;
212 continue;
213 } else if matches!(program_id, ProgramId::LightSystem)
214 && index_account.found_solana_system_program_instruction
215 && !index_account.found_system
216 {
217 index_account.system = index;
218 index_account.found_system = true;
219 } else if index_account.found_system && matches!(program_id, ProgramId::LightSystem) {
220 index_account.cpi.push(index);
221 } else if matches!(program_id, ProgramId::AccountCompression) && index_account.found_system
222 {
223 return (Some(index_account), index);
225 } else if !index_account.found_system {
226 return (None, index);
229 }
230 }
231 if index_account.found_system {
232 (Some(index_account), 0)
233 } else {
234 (None, 0)
235 }
236}
237
238fn wrap_program_ids(
239 program_ids: &[Pubkey],
240 instructions: &[Vec<u8>],
241 accounts: &[Vec<Pubkey>],
242) -> Vec<ProgramId> {
243 let mut vec = Vec::new();
244 for ((instruction, program_id), accounts) in instructions
245 .iter()
246 .zip(program_ids.iter())
247 .zip(accounts.iter())
248 {
249 if instruction.len() < 12 {
250 vec.push(ProgramId::Unknown);
251 continue;
252 }
253 let discriminator: [u8; 8] = instruction[0..8].try_into().unwrap();
254 if program_id == &Pubkey::default() {
255 vec.push(ProgramId::SolanaSystem);
256 } else if program_id == &SYSTEM_PROGRAM_ID {
257 if discriminator == CREATE_CPI_CONTEXT_ACCOUNT {
258 vec.push(ProgramId::Unknown);
259 } else {
260 vec.push(ProgramId::LightSystem);
261 }
262 } else if program_id == &ACCOUNT_COMPRESSION_PROGRAM_ID {
263 if discriminator == DISCRIMINATOR_INSERT_INTO_QUEUES
264 && accounts.len() > 2
265 && accounts[1] == REGISTERED_PROGRAM_PDA
266 {
267 vec.push(ProgramId::AccountCompression);
268 } else {
269 vec.push(ProgramId::Unknown);
270 }
271 } else {
272 vec.push(ProgramId::Unknown);
273 }
274 }
275 vec
276}
277
278fn deserialize_instruction<'a>(
279 instruction: &'a [u8],
280 accounts: &'a [Pubkey],
281) -> Result<ExecutingSystemInstruction<'a>, ParseIndexerEventError> {
282 if instruction.len() < 12 {
283 return Err(ParseIndexerEventError::InstructionDataTooSmall(
284 instruction.len(),
285 12,
286 ));
287 }
288 let instruction_discriminator = instruction[0..8].try_into().unwrap();
289 let instruction = instruction.split_at(8).1;
290 match instruction_discriminator {
291 DISCRIMINATOR_INVOKE => {
293 if accounts.len() < 9 {
294 return Err(ParseIndexerEventError::DeserializeSystemInstructionError);
295 }
296 let accounts = accounts.split_at(9).1;
297 let data = InstructionDataInvoke::deserialize(&mut &instruction[4..])?;
299 Ok(ExecutingSystemInstruction {
300 output_compressed_accounts: data.output_compressed_accounts,
301 input_compressed_accounts: data.input_compressed_accounts_with_merkle_context,
302 is_compress: data.is_compress,
303 relay_fee: data.relay_fee,
304 compress_or_decompress_lamports: data.compress_or_decompress_lamports,
305 execute_cpi_context: false,
306 accounts,
307 })
308 }
309 DISCRIMINATOR_INVOKE_CPI => {
310 if accounts.len() < 11 {
311 return Err(ParseIndexerEventError::DeserializeSystemInstructionError);
312 }
313 let accounts = accounts.split_at(11).1;
314 let data = light_compressed_account::instruction_data::invoke_cpi::InstructionDataInvokeCpi::deserialize(
315 &mut &instruction[4..],
316 )?;
317 Ok(ExecutingSystemInstruction {
318 output_compressed_accounts: data.output_compressed_accounts,
319 input_compressed_accounts: data.input_compressed_accounts_with_merkle_context,
320 is_compress: data.is_compress,
321 relay_fee: data.relay_fee,
322 compress_or_decompress_lamports: data.compress_or_decompress_lamports,
323 execute_cpi_context: data.cpi_context.is_some(),
324 accounts,
325 })
326 }
327 DISCRIMINATOR_INVOKE_CPI_WITH_READ_ONLY => {
328 if accounts.len() < 5 {
331 return Err(ParseIndexerEventError::DeserializeSystemInstructionError);
332 }
333 let data: InstructionDataInvokeCpiWithReadOnly =
334 InstructionDataInvokeCpiWithReadOnly::deserialize(&mut &instruction[..])?;
335 let system_accounts_len = if data.mode == 0 {
336 11
337 } else {
338 let mut len = 6; if data.compress_or_decompress_lamports > 0 {
340 len += 1;
341 }
342 if !data.is_compress && data.compress_or_decompress_lamports > 0 {
343 len += 1;
344 }
345 if data.with_cpi_context {
346 len += 1;
347 }
348 len
349 };
350
351 let accounts = accounts.split_at(system_accounts_len).1;
352 Ok(ExecutingSystemInstruction {
353 output_compressed_accounts: data.output_compressed_accounts,
354 input_compressed_accounts: data
355 .input_compressed_accounts
356 .iter()
357 .map(|x| {
358 x.into_packed_compressed_account_with_merkle_context(
359 data.invoking_program_id,
360 )
361 })
362 .collect::<Vec<_>>(),
363 is_compress: data.is_compress && data.compress_or_decompress_lamports > 0,
364 relay_fee: None,
365 compress_or_decompress_lamports: if data.compress_or_decompress_lamports == 0 {
366 None
367 } else {
368 Some(data.compress_or_decompress_lamports)
369 },
370 execute_cpi_context: data.with_cpi_context,
371 accounts,
372 })
373 }
374 INVOKE_CPI_WITH_ACCOUNT_INFO_INSTRUCTION => {
375 if accounts.len() < 5 {
378 return Err(ParseIndexerEventError::DeserializeSystemInstructionError);
379 }
380 let data: InstructionDataInvokeCpiWithAccountInfo =
381 InstructionDataInvokeCpiWithAccountInfo::deserialize(&mut &instruction[..])?;
382 let system_accounts_len = if data.mode == 0 {
383 11
384 } else {
385 let mut len = 6; if data.compress_or_decompress_lamports > 0 {
387 len += 1;
388 }
389 if !data.is_compress && data.compress_or_decompress_lamports > 0 {
390 len += 1;
391 }
392 if data.with_cpi_context {
393 len += 1;
394 }
395 len
396 };
397 let accounts = accounts.split_at(system_accounts_len).1;
398
399 let instruction = ExecutingSystemInstruction {
400 output_compressed_accounts: data
401 .account_infos
402 .iter()
403 .filter(|x| x.output.is_some())
404 .map(|x| {
405 let account = x.output.as_ref().unwrap();
406 OutputCompressedAccountWithPackedContext {
407 compressed_account: CompressedAccount {
408 address: x.address,
409 owner: data.invoking_program_id,
410 lamports: account.lamports,
411 data: Some(CompressedAccountData {
412 discriminator: account.discriminator,
413 data: account.data.clone(),
414 data_hash: account.data_hash,
415 }),
416 },
417 merkle_tree_index: account.output_merkle_tree_index,
418 }
419 })
420 .collect::<Vec<_>>(),
421 input_compressed_accounts: data
422 .account_infos
423 .iter()
424 .filter(|x| x.input.is_some())
425 .map(|x| {
426 let account = x.input.as_ref().unwrap();
427 PackedCompressedAccountWithMerkleContext {
428 compressed_account: CompressedAccount {
429 address: x.address,
430 owner: data.invoking_program_id,
431 lamports: account.lamports,
432 data: Some(CompressedAccountData {
433 discriminator: account.discriminator,
434 data: vec![],
435 data_hash: account.data_hash,
436 }),
437 },
438 read_only: false,
439 root_index: account.root_index,
440 merkle_context: account.merkle_context,
441 }
442 })
443 .collect::<Vec<_>>(),
444 is_compress: data.is_compress && data.compress_or_decompress_lamports > 0,
445 relay_fee: None,
446 compress_or_decompress_lamports: if data.compress_or_decompress_lamports == 0 {
447 None
448 } else {
449 Some(data.compress_or_decompress_lamports)
450 },
451 execute_cpi_context: data.with_cpi_context,
452 accounts,
453 };
454
455 Ok(instruction)
456 }
457 _ => Err(ParseIndexerEventError::DeserializeSystemInstructionError),
458 }
459}
460
461fn create_batched_transaction_event(
462 associated_instructions: &AssociatedInstructions,
463) -> Result<BatchPublicTransactionEvent, ParseIndexerEventError> {
464 let input_sequence_numbers = associated_instructions
465 .insert_into_queues_instruction
466 .input_sequence_numbers
467 .iter()
468 .map(From::from)
469 .filter(|x: &MerkleTreeSequenceNumber| !(*x).is_empty())
470 .collect::<Vec<MerkleTreeSequenceNumber>>();
471 let mut batched_transaction_event = BatchPublicTransactionEvent {
472 event: PublicTransactionEvent {
473 input_compressed_account_hashes: associated_instructions
474 .insert_into_queues_instruction
475 .nullifiers
476 .iter()
477 .map(|x| x.account_hash)
478 .collect(),
479 output_compressed_account_hashes: associated_instructions
480 .insert_into_queues_instruction
481 .leaves
482 .iter()
483 .map(|x| x.leaf)
484 .collect(),
485 output_compressed_accounts: [
486 associated_instructions.cpi_context_outputs.clone(),
487 associated_instructions
488 .executing_system_instruction
489 .output_compressed_accounts
490 .clone(),
491 ]
492 .concat(),
493 output_leaf_indices: associated_instructions
494 .insert_into_queues_instruction
495 .output_leaf_indices
496 .iter()
497 .map(|x| u32::from(*x))
498 .collect(),
499 sequence_numbers: associated_instructions
500 .insert_into_queues_instruction
501 .output_sequence_numbers
502 .iter()
503 .map(From::from)
504 .filter(|x: &MerkleTreeSequenceNumber| !(*x).is_empty())
505 .map(|x| MerkleTreeSequenceNumberV1 {
506 seq: x.seq,
507 tree_pubkey: x.tree_pubkey,
508 })
509 .collect(),
510 relay_fee: associated_instructions
511 .executing_system_instruction
512 .relay_fee,
513 is_compress: associated_instructions
514 .executing_system_instruction
515 .is_compress,
516 compress_or_decompress_lamports: associated_instructions
517 .executing_system_instruction
518 .compress_or_decompress_lamports,
519 pubkey_array: associated_instructions
520 .executing_system_instruction
521 .accounts
522 .to_vec(),
523 message: None,
524 },
525 tx_hash: associated_instructions
526 .insert_into_queues_instruction
527 .tx_hash,
528 new_addresses: associated_instructions
529 .insert_into_queues_instruction
530 .addresses
531 .iter()
532 .map(|x| NewAddress {
533 address: x.address,
534 mt_pubkey: associated_instructions.accounts[x.tree_index as usize],
535 queue_index: u64::MAX,
536 })
537 .collect::<Vec<_>>(),
538 address_sequence_numbers: associated_instructions
539 .insert_into_queues_instruction
540 .address_sequence_numbers
541 .iter()
542 .map(From::from)
543 .filter(|x: &MerkleTreeSequenceNumber| !(*x).is_empty())
544 .collect::<Vec<MerkleTreeSequenceNumber>>(),
545 batch_input_accounts: associated_instructions
546 .insert_into_queues_instruction
547 .nullifiers
548 .iter()
549 .filter(|x| {
550 input_sequence_numbers.iter().any(|y| {
551 y.tree_pubkey == associated_instructions.accounts[x.tree_index as usize]
552 })
553 })
554 .map(|n| {
555 Ok(BatchNullifyContext {
556 tx_hash: associated_instructions
557 .insert_into_queues_instruction
558 .tx_hash,
559 account_hash: n.account_hash,
560 nullifier: {
561 create_nullifier(
565 &n.account_hash,
566 n.leaf_index.into(),
567 &associated_instructions
568 .insert_into_queues_instruction
569 .tx_hash,
570 )?
571 },
572 nullifier_queue_index: u64::MAX,
573 })
574 })
575 .collect::<Result<Vec<_>, ParseIndexerEventError>>()?,
576 input_sequence_numbers,
577 };
578
579 let nullifier_queue_indices = create_nullifier_queue_indices(
580 associated_instructions,
581 batched_transaction_event.batch_input_accounts.len(),
582 );
583
584 batched_transaction_event
585 .batch_input_accounts
586 .iter_mut()
587 .zip(nullifier_queue_indices.iter())
588 .for_each(|(context, index)| {
589 context.nullifier_queue_index = *index;
590 });
591
592 let address_queue_indices = create_address_queue_indices(
593 associated_instructions,
594 batched_transaction_event.new_addresses.len(),
595 );
596
597 batched_transaction_event
598 .new_addresses
599 .iter_mut()
600 .zip(address_queue_indices.iter())
601 .for_each(|(context, index)| {
602 context.queue_index = *index;
603 });
604
605 Ok(batched_transaction_event)
606}
607
608fn create_nullifier_queue_indices(
609 associated_instructions: &AssociatedInstructions,
610 len: usize,
611) -> Vec<u64> {
612 let input_merkle_tree_pubkeys = associated_instructions
613 .executing_system_instruction
614 .input_compressed_accounts
615 .iter()
616 .map(|x| {
617 associated_instructions
618 .executing_system_instruction
619 .accounts[x.merkle_context.merkle_tree_pubkey_index as usize]
620 })
621 .collect::<Vec<_>>();
622 let mut nullifier_queue_indices = vec![u64::MAX; len];
623 let mut internal_input_sequence_numbers = associated_instructions
624 .insert_into_queues_instruction
625 .input_sequence_numbers
626 .to_vec();
627 internal_input_sequence_numbers.iter_mut().for_each(|seq| {
632 for (i, merkle_tree_pubkey) in input_merkle_tree_pubkeys.iter().enumerate() {
633 if *merkle_tree_pubkey == seq.tree_pubkey {
634 nullifier_queue_indices[i] = seq.seq.into();
635 seq.seq += 1;
636 }
637 }
638 });
639 nullifier_queue_indices
640}
641
642fn create_address_queue_indices(
643 associated_instructions: &AssociatedInstructions,
644 len: usize,
645) -> Vec<u64> {
646 let address_merkle_tree_pubkeys = associated_instructions
647 .insert_into_queues_instruction
648 .addresses
649 .iter()
650 .map(|x| associated_instructions.accounts[x.tree_index as usize])
651 .collect::<Vec<_>>();
652 let mut address_queue_indices = vec![u64::MAX; len];
653 let mut internal_address_sequence_numbers = associated_instructions
654 .insert_into_queues_instruction
655 .address_sequence_numbers
656 .to_vec();
657 internal_address_sequence_numbers
658 .iter_mut()
659 .for_each(|seq| {
660 for (i, merkle_tree_pubkey) in address_merkle_tree_pubkeys.iter().enumerate() {
661 if *merkle_tree_pubkey == seq.tree_pubkey {
662 address_queue_indices[i] = seq.seq.into();
663 seq.seq += 1;
664 }
665 }
666 });
667 address_queue_indices
668}
669
670#[cfg(test)]
671mod test {
672 use rand::{
673 rngs::{StdRng, ThreadRng},
674 Rng, RngCore, SeedableRng,
675 };
676
677 use super::*;
678 fn get_rnd_program_id<R: Rng>(rng: &mut R, with_system_program: bool) -> ProgramId {
679 let vec = [
680 ProgramId::Unknown,
681 ProgramId::AccountCompression,
682 ProgramId::LightSystem,
683 ];
684 let len = if with_system_program { 3 } else { 2 };
685 let index = rng.gen_range(0..len);
686 vec[index]
687 }
688 fn get_rnd_program_ids<R: Rng>(
689 rng: &mut R,
690 len: usize,
691 with_system_program: bool,
692 ) -> Vec<ProgramId> {
693 (0..len)
694 .map(|_| get_rnd_program_id(rng, with_system_program))
695 .collect()
696 }
697
698 #[test]
699 fn test_rnd_functional() {
700 let mut thread_rng = ThreadRng::default();
701 let seed = thread_rng.next_u64();
702 println!("\n\ntest seed {}\n\n", seed);
705 let mut rng = StdRng::seed_from_u64(seed);
706 let num_iters = 100000;
707 for _ in 0..num_iters {
708 let len_pre = rng.gen_range(0..6);
709 let rnd_vec_pre = get_rnd_program_ids(&mut rng, len_pre, false);
710 let len_post = rng.gen_range(0..6);
711 let rnd_vec_post = get_rnd_program_ids(&mut rng, len_post, false);
712 let num_mid = rng.gen_range(1..6);
713
714 let program_ids = [
715 rnd_vec_pre.as_slice(),
716 [ProgramId::LightSystem].as_slice(),
717 vec![ProgramId::SolanaSystem; num_mid].as_slice(),
718 [ProgramId::AccountCompression].as_slice(),
719 rnd_vec_post.as_slice(),
720 ]
721 .concat();
722 let start_index = program_ids.len() - 1 - len_post;
723 let system_index = program_ids.len() - 1 - len_post - num_mid - 1;
724 let vec = find_cpi_patterns(&program_ids);
725 let expected = Indices {
726 system: system_index,
727 cpi: vec![],
728 insert_into_queues: start_index,
729 found_solana_system_program_instruction: true,
730 found_system: true,
731 };
732 assert!(
733 vec.contains(&expected),
734 "program ids {:?} parsed events {:?} expected {:?} ",
735 program_ids,
736 vec,
737 expected,
738 );
739 }
740
741 for _ in 0..num_iters {
742 let len_pre = rng.gen_range(0..6);
743 let rnd_vec_pre = get_rnd_program_ids(&mut rng, len_pre, true);
744 let len_post = rng.gen_range(0..6);
745 let rnd_vec_post = get_rnd_program_ids(&mut rng, len_post, true);
746 let num_mid = rng.gen_range(1..6);
747
748 let program_ids = [
749 rnd_vec_pre.as_slice(),
750 [ProgramId::LightSystem].as_slice(),
751 vec![ProgramId::SolanaSystem; num_mid].as_slice(),
752 [ProgramId::AccountCompression].as_slice(),
753 rnd_vec_post.as_slice(),
754 ]
755 .concat();
756 let start_index = program_ids.len() - 1 - len_post;
757 let system_index = program_ids.len() - 1 - len_post - num_mid - 1;
758 let vec = find_cpi_patterns(&program_ids);
759 let expected = Indices {
760 system: system_index,
761 cpi: vec![],
762 insert_into_queues: start_index,
763 found_solana_system_program_instruction: true,
764 found_system: true,
765 };
766 assert!(
767 vec.iter().any(|x| x.system == expected.system
768 && x.insert_into_queues == expected.insert_into_queues),
769 "program ids {:?} parsed events {:?} expected {:?} ",
770 program_ids,
771 vec,
772 expected,
773 );
774 }
775 }
776
777 #[test]
778 fn test_rnd_failing() {
779 let mut thread_rng = ThreadRng::default();
780 let seed = thread_rng.next_u64();
781 println!("\n\ntest seed {}\n\n", seed);
784 let mut rng = StdRng::seed_from_u64(seed);
785 let num_iters = 100000;
786 for _ in 0..num_iters {
787 let len = rng.gen_range(0..20);
788 let mut program_ids = get_rnd_program_ids(&mut rng, len, true);
789 for i in 0..program_ids.len().saturating_sub(1) {
791 if matches!(program_ids[i], ProgramId::LightSystem)
792 && matches!(program_ids[i + 1], ProgramId::SolanaSystem)
793 {
794 program_ids[i + 1] = ProgramId::Unknown;
795 }
796 }
797
798 let vec = find_cpi_patterns(&program_ids);
799
800 assert!(
801 vec.is_empty(),
802 "program_ids {:?} result {:?}",
803 program_ids,
804 vec
805 );
806 }
807 }
808
809 #[test]
810 fn test_find_two_patterns() {
811 {
813 let program_ids = vec![
814 ProgramId::Unknown,
815 ProgramId::LightSystem,
816 ProgramId::SolanaSystem,
817 ProgramId::AccountCompression,
818 ProgramId::Unknown,
819 ProgramId::LightSystem,
820 ProgramId::SolanaSystem,
821 ProgramId::AccountCompression,
822 ];
823 let vec = find_cpi_patterns(&program_ids);
824 assert_eq!(vec.len(), 2);
825 assert_eq!(
826 vec[0],
827 Indices {
828 system: 5,
829 cpi: vec![],
830 insert_into_queues: 7,
831 found_solana_system_program_instruction: true,
832 found_system: true,
833 }
834 );
835 assert_eq!(
836 vec[1],
837 Indices {
838 system: 1,
839 cpi: vec![],
840 insert_into_queues: 3,
841 found_solana_system_program_instruction: true,
842 found_system: true,
843 }
844 );
845 {
847 let mut program_ids = program_ids.clone();
848 program_ids[2] = ProgramId::Unknown;
849 let vec = find_cpi_patterns(&program_ids);
850 assert_eq!(vec.len(), 1);
851 assert_eq!(
852 vec[0],
853 Indices {
854 system: 5,
855 cpi: vec![],
856 insert_into_queues: 7,
857 found_solana_system_program_instruction: true,
858 found_system: true,
859 }
860 );
861 }
862 {
864 let mut program_ids = program_ids;
865 program_ids[6] = ProgramId::Unknown;
866 let vec = find_cpi_patterns(&program_ids);
867 assert_eq!(vec.len(), 1);
868 assert_eq!(
869 vec[0],
870 Indices {
871 system: 1,
872 cpi: vec![],
873 insert_into_queues: 3,
874 found_solana_system_program_instruction: true,
875 found_system: true,
876 }
877 );
878 }
879 }
880 }
881
882 #[test]
883 fn test_find_pattern() {
884 {
886 let program_ids = vec![
887 ProgramId::Unknown,
888 ProgramId::LightSystem,
889 ProgramId::SolanaSystem,
890 ProgramId::AccountCompression,
891 ];
892 let (res, last_index) = find_cpi_pattern(3, &program_ids);
893 assert_eq!(last_index, 0);
894 assert_eq!(
895 res,
896 Some(Indices {
897 system: 1,
898 cpi: vec![],
899 insert_into_queues: 3,
900 found_solana_system_program_instruction: true,
901 found_system: true,
902 })
903 );
904 }
905 {
906 let program_ids = vec![
907 ProgramId::Unknown,
908 ProgramId::LightSystem,
909 ProgramId::SolanaSystem,
910 ProgramId::SolanaSystem,
911 ProgramId::SolanaSystem,
912 ProgramId::AccountCompression,
913 ];
914 let start_index = program_ids.len() - 1;
915 let (res, last_index) = find_cpi_pattern(start_index, &program_ids);
916 assert_eq!(last_index, 0);
917 assert_eq!(
918 res,
919 Some(Indices {
920 system: 1,
921 cpi: vec![],
922 insert_into_queues: start_index,
923 found_solana_system_program_instruction: true,
924 found_system: true,
925 })
926 );
927 }
928 {
929 let program_ids = vec![
930 ProgramId::Unknown,
931 ProgramId::LightSystem,
932 ProgramId::SolanaSystem,
933 ProgramId::Unknown,
934 ProgramId::SolanaSystem,
935 ProgramId::AccountCompression,
936 ];
937 let start_index = program_ids.len() - 1;
938 let (res, last_index) = find_cpi_pattern(start_index, &program_ids);
939 assert_eq!(last_index, 3);
940 assert_eq!(res, None);
941 }
942 {
944 let program_ids = vec![
945 ProgramId::Unknown,
946 ProgramId::LightSystem,
947 ProgramId::Unknown,
948 ProgramId::LightSystem,
949 ProgramId::SolanaSystem,
950 ProgramId::SolanaSystem,
951 ProgramId::SolanaSystem,
952 ProgramId::AccountCompression,
953 ];
954 let start_index = program_ids.len() - 1;
955 let (res, last_index) = find_cpi_pattern(start_index, &program_ids);
956 assert_eq!(last_index, 0);
957 assert_eq!(
958 res,
959 Some(Indices {
960 system: 3,
961 cpi: vec![1],
962 insert_into_queues: start_index,
963 found_solana_system_program_instruction: true,
964 found_system: true,
965 })
966 );
967 {
969 let mut program_ids = program_ids;
970 program_ids[5] = ProgramId::Unknown;
971 let (res, last_index) = find_cpi_pattern(start_index, &program_ids);
972 assert_eq!(last_index, 5);
973 assert_eq!(res, None);
974 }
975 }
976 {
978 let program_ids = vec![
979 ProgramId::Unknown,
980 ProgramId::LightSystem,
981 ProgramId::LightSystem,
982 ProgramId::SolanaSystem,
983 ProgramId::SolanaSystem,
984 ProgramId::SolanaSystem,
985 ProgramId::AccountCompression,
986 ];
987 let start_index = program_ids.len() - 1;
988 let (res, last_index) = find_cpi_pattern(start_index, &program_ids);
989 assert_eq!(last_index, 0);
990 assert_eq!(
991 res,
992 Some(Indices {
993 system: 2,
994 cpi: vec![1],
995 insert_into_queues: start_index,
996 found_solana_system_program_instruction: true,
997 found_system: true,
998 })
999 );
1000 {
1002 let mut program_ids = program_ids;
1003 program_ids[4] = ProgramId::Unknown;
1004 let (res, last_index) = find_cpi_pattern(start_index, &program_ids);
1005 assert_eq!(last_index, 4);
1006 assert_eq!(res, None);
1007 }
1008 }
1009 }
1010}