1use {
22 crate::{
23 deserialize::CarbonDeserialize, error::CarbonResult, filter::Filter, processor::Processor,
24 transaction::TransactionMetadata,
25 },
26 async_trait::async_trait,
27 std::{
28 ops::{Deref, DerefMut},
29 sync::Arc,
30 },
31};
32
33#[derive(Debug, Clone)]
36pub struct InstructionMetadata {
37 pub transaction_metadata: Arc<TransactionMetadata>,
38 pub stack_height: u32,
39 pub index: u32,
40 pub absolute_path: Vec<u8>,
41}
42
43#[derive(Debug)]
44enum LogType {
45 Start(usize), Data,
47 CU,
48 Finish,
49}
50
51const PRECOMPILE_PROGRAMS: &[&str] = &[
52 "Ed25519SigVerify111111111111111111111111111",
53 "KeccakSecp256k11111111111111111111111111111",
54 "Secp256r1SigVerify1111111111111111111111111",
55];
56
57pub const MAX_INSTRUCTION_STACK_DEPTH: usize = 5;
59
60impl InstructionMetadata {
61 pub fn decode_log_events<T: CarbonDeserialize>(&self) -> Vec<T> {
62 self.extract_event_log_data()
63 .into_iter()
64 .filter_map(|log| <T as CarbonDeserialize>::deserialize(&log))
65 .collect()
66 }
67
68 fn extract_event_log_data(&self) -> Vec<Vec<u8>> {
69 let logs = match &self.transaction_metadata.meta.log_messages {
70 Some(logs) => logs,
71 None => return Vec::new(),
72 };
73
74 let precompile_offset = self.count_precompiles_before_index();
75 let adjusted_absolute_path: Vec<u8> = if !self.absolute_path.is_empty() {
76 let mut adjusted = self.absolute_path.clone();
77 adjusted[0] = adjusted[0].saturating_sub(precompile_offset as u8);
78 adjusted
79 } else {
80 self.absolute_path.clone()
81 };
82
83 let mut extracted_logs = Vec::new();
84 let mut current_stack_height = 0usize;
85 let mut last_stack_height = 0usize;
86
87 let mut position_at_level: std::collections::HashMap<usize, u8> =
88 std::collections::HashMap::new();
89
90 for log in logs {
91 let parsed_log = self.parse_log(log);
92
93 match parsed_log {
94 LogType::Start(stack_height) => {
95 current_stack_height = stack_height;
96
97 let current_pos = if stack_height > last_stack_height {
98 0
99 } else {
100 position_at_level
101 .get(&stack_height)
102 .map(|&pos| pos + 1)
103 .unwrap_or(0)
104 };
105
106 position_at_level.insert(stack_height, current_pos);
107 last_stack_height = stack_height;
108 }
109 LogType::Finish => {
110 current_stack_height = current_stack_height.saturating_sub(1);
111 }
112 _ => {}
113 }
114
115 let current_path: Vec<u8> = (1..=current_stack_height)
116 .map(|level| position_at_level.get(&level).copied().unwrap_or(0))
117 .collect();
118
119 if current_path == adjusted_absolute_path && matches!(parsed_log, LogType::Data) {
120 if let Some(data) = log.split_whitespace().last() {
121 if let Ok(buf) =
122 base64::Engine::decode(&base64::engine::general_purpose::STANDARD, data)
123 {
124 extracted_logs.push(buf);
125 }
126 }
127 }
128 }
129
130 extracted_logs
131 }
132
133 fn count_precompiles_before_index(&self) -> usize {
134 if self.absolute_path.is_empty() {
135 return 0;
136 }
137
138 let outer_index = self.absolute_path[0] as usize;
139 let account_keys = self.transaction_metadata.message.static_account_keys();
140 let instructions = self.transaction_metadata.message.instructions();
141
142 let mut precompile_count = 0;
143 for (idx, ix) in instructions.iter().enumerate() {
144 if idx >= outer_index {
145 break;
146 }
147 if let Some(program_id) = account_keys.get(ix.program_id_index as usize) {
148 let program_id_str = program_id.to_string();
149 if PRECOMPILE_PROGRAMS.contains(&program_id_str.as_str()) {
150 precompile_count += 1;
151 }
152 }
153 }
154
155 precompile_count
156 }
157
158 fn parse_log(&self, log: &str) -> LogType {
159 if log.starts_with("Program ") && log.contains(" invoke [") {
160 let parts: Vec<&str> = log.split_whitespace().collect();
161 if parts.len() >= 4 && parts[0] == "Program" && parts[2] == "invoke" {
162 let level_str = parts[3].trim_start_matches('[').trim_end_matches(']');
163 if let Ok(level) = level_str.parse::<usize>() {
164 return LogType::Start(level);
165 }
166 }
167 } else if log.starts_with("Program ")
168 && (log.ends_with(" success") || log.contains(" failed"))
169 {
170 let parts: Vec<&str> = log.split_whitespace().collect();
171 if parts.len() >= 3 && parts[0] == "Program" {
172 return LogType::Finish;
173 }
174 } else if log.contains("consumed") && log.contains("compute units") {
175 return LogType::CU;
176 }
177
178 LogType::Data
179 }
180}
181
182pub type InstructionsWithMetadata = Vec<(InstructionMetadata, solana_instruction::Instruction)>;
183
184pub trait InstructionDecoder<'a> {
188 type InstructionType;
189
190 fn decode_instruction(
191 &self,
192 instruction: &'a solana_instruction::Instruction,
193 ) -> Option<Self::InstructionType>;
194}
195
196#[derive(Debug)]
200pub struct InstructionProcessorInputType<'a, T> {
201 pub metadata: &'a InstructionMetadata,
202 pub decoded_instruction: &'a T,
203 pub nested_instructions: &'a NestedInstructions,
204 pub raw_instruction: &'a solana_instruction::Instruction,
205}
206
207pub struct InstructionPipe<T: Send, P> {
208 decoder: Box<dyn for<'a> InstructionDecoder<'a, InstructionType = T> + Send + Sync + 'static>,
209 processor: P,
210 filters: Vec<Box<dyn Filter + 'static>>,
211}
212
213impl<T: Send, P> InstructionPipe<T, P> {
214 pub fn new(
215 decoder: Box<
216 dyn for<'a> InstructionDecoder<'a, InstructionType = T> + Send + Sync + 'static,
217 >,
218 processor: P,
219 filters: Vec<Box<dyn Filter + 'static>>,
220 ) -> Self {
221 Self {
222 decoder,
223 processor,
224 filters,
225 }
226 }
227}
228
229#[async_trait]
230pub trait InstructionPipes<'a>: Send + Sync {
231 async fn run(&mut self, nested_instruction: &NestedInstruction) -> CarbonResult<()>;
232
233 fn filters(&self) -> &[Box<dyn Filter + 'static>];
234}
235
236#[async_trait]
237impl<T, P> InstructionPipes<'_> for InstructionPipe<T, P>
238where
239 T: Send + Sync + 'static,
240 P: for<'a> Processor<InstructionProcessorInputType<'a, T>> + Send + Sync + 'static,
241{
242 async fn run(&mut self, nested_instruction: &NestedInstruction) -> CarbonResult<()> {
243 if let Some(decoded_instruction) = self
244 .decoder
245 .decode_instruction(&nested_instruction.instruction)
246 {
247 let data = InstructionProcessorInputType {
248 metadata: &nested_instruction.metadata,
249 decoded_instruction: &decoded_instruction,
250 nested_instructions: &nested_instruction.inner_instructions,
251 raw_instruction: &nested_instruction.instruction,
252 };
253
254 self.processor.process(&data).await?;
255 }
256
257 Ok(())
258 }
259
260 fn filters(&self) -> &[Box<dyn Filter + 'static>] {
261 &self.filters
262 }
263}
264
265#[derive(Debug, Clone)]
268pub struct NestedInstruction {
269 pub metadata: InstructionMetadata,
270 pub instruction: solana_instruction::Instruction,
271 pub inner_instructions: NestedInstructions,
272}
273
274#[derive(Debug, Default)]
278pub struct NestedInstructions(pub Vec<NestedInstruction>);
279
280impl NestedInstructions {
281 pub fn len(&self) -> usize {
282 self.0.len()
283 }
284
285 pub fn is_empty(&self) -> bool {
286 self.len() == 0
287 }
288
289 pub fn push(&mut self, nested_instruction: NestedInstruction) {
290 self.0.push(nested_instruction);
291 }
292}
293
294impl Deref for NestedInstructions {
295 type Target = [NestedInstruction];
296
297 fn deref(&self) -> &[NestedInstruction] {
298 &self.0[..]
299 }
300}
301
302impl DerefMut for NestedInstructions {
303 fn deref_mut(&mut self) -> &mut [NestedInstruction] {
304 &mut self.0[..]
305 }
306}
307
308impl Clone for NestedInstructions {
309 fn clone(&self) -> Self {
310 NestedInstructions(self.0.clone())
311 }
312}
313
314impl IntoIterator for NestedInstructions {
315 type Item = NestedInstruction;
316 type IntoIter = std::vec::IntoIter<NestedInstruction>;
317
318 fn into_iter(self) -> Self::IntoIter {
319 self.0.into_iter()
320 }
321}
322
323impl From<InstructionsWithMetadata> for NestedInstructions {
324 fn from(instructions: InstructionsWithMetadata) -> Self {
325 let estimated_capacity = instructions
328 .iter()
329 .filter(|(meta, _)| meta.stack_height == 1)
330 .count();
331
332 UnsafeNestedBuilder::new(estimated_capacity).build(instructions)
333 }
334}
335
336pub struct UnsafeNestedBuilder {
337 nested_ixs: Vec<NestedInstruction>,
338 level_ptrs: [Option<*mut NestedInstruction>; MAX_INSTRUCTION_STACK_DEPTH],
339}
340
341impl UnsafeNestedBuilder {
342 pub fn new(capacity: usize) -> Self {
343 Self {
344 nested_ixs: Vec::with_capacity(capacity),
345 level_ptrs: [None; MAX_INSTRUCTION_STACK_DEPTH],
346 }
347 }
348
349 pub fn build(mut self, instructions: InstructionsWithMetadata) -> NestedInstructions {
350 for (metadata, instruction) in instructions {
351 let stack_height = metadata.stack_height as usize;
352
353 assert!(stack_height > 0);
354 assert!(stack_height <= MAX_INSTRUCTION_STACK_DEPTH);
355
356 for ptr in &mut self.level_ptrs[stack_height..] {
357 *ptr = None;
358 }
359
360 let new_instruction = NestedInstruction {
361 metadata,
362 instruction,
363 inner_instructions: NestedInstructions::default(),
364 };
365
366 unsafe {
372 if stack_height == 1 {
373 self.nested_ixs.push(new_instruction);
374 let ptr = self.nested_ixs.last_mut().unwrap_unchecked() as *mut _;
375 self.level_ptrs[0] = Some(ptr);
376 } else if let Some(parent_ptr) = self.level_ptrs[stack_height - 2] {
377 (*parent_ptr).inner_instructions.push(new_instruction);
378 let ptr = (*parent_ptr)
379 .inner_instructions
380 .last_mut()
381 .unwrap_unchecked() as *mut _;
382 self.level_ptrs[stack_height - 1] = Some(ptr);
383 }
384 }
385 }
386
387 NestedInstructions(self.nested_ixs)
388 }
389}
390
391#[cfg(test)]
392mod tests {
393
394 use {
395 super::*,
396 solana_instruction::{AccountMeta, Instruction},
397 solana_pubkey::Pubkey,
398 solana_transaction_status::TransactionStatusMeta,
399 std::str::FromStr,
400 };
401
402 fn create_instruction_with_metadata(
403 index: u32,
404 stack_height: u32,
405 absolute_path: Vec<u8>,
406 ) -> (InstructionMetadata, Instruction) {
407 let metadata = InstructionMetadata {
408 transaction_metadata: Arc::new(TransactionMetadata {
409 meta: TransactionStatusMeta {
410 log_messages: Some(vec!["Program CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK invoke [1]".to_string(), "Program data: QMbN6CYIceLh9Vdh3ndmrpChVVDCYAykCoHLEYdQWNcAxLJNu7nWNHiJzugda0JT2xgyBCWGtm7/oWjb/wT2kcbwA0JRUuwSV88ABSiDPpXudmLYK2jIBhqh3sTXxnR7WMgtjWsyqjga53NruXU9Dj/hyRRE/RQ9xCEh3052KbW6tbtNksNK4HIr+0wAAAAAAAAAAAAAAACz/t2FxQIAAAAAAAAAAAAAACdJpynsFrOoMAAAAAAAAAD4JhBoAxAAAAAAAAAAAAAAhC8BAA==".to_string(), "Program CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK consumed 91799 of 185765 compute units".to_string(), "Program CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK success".to_string()]),
411 ..Default::default()
412 },
413 ..Default::default()
414 }),
415 stack_height,
416 index,
417 absolute_path,
418 };
419 let instruction = Instruction {
420 program_id: Pubkey::new_unique(),
421 accounts: vec![AccountMeta::new(Pubkey::new_unique(), false)],
422 data: vec![],
423 };
424 (metadata, instruction)
425 }
426
427 fn create_metadata_with_message(
428 absolute_path: Vec<u8>,
429 stack_height: u32,
430 logs: Vec<String>,
431 account_keys: Vec<Pubkey>,
432 instructions: Vec<solana_message::compiled_instruction::CompiledInstruction>,
433 ) -> InstructionMetadata {
434 use solana_message::{legacy::Message as LegacyMessage, VersionedMessage};
435
436 let message = VersionedMessage::Legacy(LegacyMessage {
437 header: solana_message::MessageHeader {
438 num_required_signatures: 1,
439 num_readonly_signed_accounts: 0,
440 num_readonly_unsigned_accounts: 0,
441 },
442 account_keys,
443 recent_blockhash: solana_hash::Hash::default(),
444 instructions,
445 });
446
447 InstructionMetadata {
448 transaction_metadata: Arc::new(TransactionMetadata {
449 meta: TransactionStatusMeta {
450 log_messages: Some(logs),
451 ..Default::default()
452 },
453 message,
454 ..Default::default()
455 }),
456 stack_height,
457 index: absolute_path.first().copied().unwrap_or(0) as u32,
458 absolute_path,
459 }
460 }
461
462 #[test]
463 fn test_nested_instructions_single_level() {
464 let instructions = vec![
465 create_instruction_with_metadata(1, 1, vec![1]),
466 create_instruction_with_metadata(2, 1, vec![2]),
467 ];
468 let nested_instructions: NestedInstructions = instructions.into();
469 assert_eq!(nested_instructions.len(), 2);
470 assert!(nested_instructions[0].inner_instructions.is_empty());
471 assert!(nested_instructions[1].inner_instructions.is_empty());
472 }
473
474 #[test]
475 fn test_nested_instructions_empty() {
476 let instructions: InstructionsWithMetadata = vec![];
477 let nested_instructions: NestedInstructions = instructions.into();
478 assert!(nested_instructions.is_empty());
479 }
480
481 #[test]
482 fn test_deep_nested_instructions() {
483 let instructions = vec![
484 create_instruction_with_metadata(0, 1, vec![0]),
485 create_instruction_with_metadata(0, 1, vec![0]),
486 create_instruction_with_metadata(1, 2, vec![0, 1]),
487 create_instruction_with_metadata(1, 3, vec![0, 1, 1]),
488 create_instruction_with_metadata(1, 3, vec![0, 1, 1]),
489 create_instruction_with_metadata(1, 3, vec![0, 1, 1]),
490 create_instruction_with_metadata(1, 3, vec![0, 1, 1]),
491 ];
492
493 let nested_instructions: NestedInstructions = instructions.into();
494 assert_eq!(nested_instructions.len(), 2);
495 assert_eq!(nested_instructions.0[1].inner_instructions.len(), 1);
496 }
497
498 #[test]
499 fn test_decode_log_events_accepts_payloads_under_eight_bytes() {
500 #[derive(crate::borsh::BorshDeserialize)]
501 struct TinyEvent {
502 value: u8,
503 }
504
505 impl CarbonDeserialize for TinyEvent {
506 const DISCRIMINATOR: &'static [u8] = &[9];
507
508 fn deserialize(data: &[u8]) -> Option<Self> {
509 let payload = data.strip_prefix(Self::DISCRIMINATOR)?;
510 crate::borsh::BorshDeserialize::try_from_slice(payload).ok()
511 }
512 }
513
514 let metadata = create_metadata_with_message(
515 vec![0],
516 1,
517 vec![
518 "Program CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK invoke [1]".to_string(),
519 "Program data: CSo=".to_string(),
520 "Program CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK success".to_string(),
521 ],
522 vec![],
523 vec![],
524 );
525
526 let events = metadata.decode_log_events::<TinyEvent>();
527 assert_eq!(events.len(), 1);
528 assert_eq!(events[0].value, 42);
529 }
530
531 #[test]
532 fn test_extract_event_log_data() {
533 let logs = create_instruction_with_metadata(0, 1, vec![0])
534 .0
535 .extract_event_log_data();
536 assert_eq!(logs.len(), 1);
537 assert_eq!(
538 logs[0],
539 base64::Engine::decode(
540 &base64::engine::general_purpose::STANDARD,
541 "QMbN6CYIceLh9Vdh3ndmrpChVVDCYAykCoHLEYdQWNcAxLJNu7nWNHiJzugda0JT2xgyBCWGtm7/oWjb/wT2kcbwA0JRUuwSV88ABSiDPpXudmLYK2jIBhqh3sTXxnR7WMgtjWsyqjga53NruXU9Dj/hyRRE/RQ9xCEh3052KbW6tbtNksNK4HIr+0wAAAAAAAAAAAAAAACz/t2FxQIAAAAAAAAAAAAAACdJpynsFrOoMAAAAAAAAAD4JhBoAxAAAAAAAAAAAAAAhC8BAA=="
542 )
543 .expect("decode base64")
544 );
545 }
546
547 #[test]
548 fn test_count_precompiles_before_index_no_precompiles() {
549 use solana_message::compiled_instruction::CompiledInstruction;
550
551 let program1 = Pubkey::new_unique();
558 let program2 = Pubkey::new_unique();
559
560 let metadata = create_metadata_with_message(
561 vec![1],
562 1,
563 vec![],
564 vec![Pubkey::new_unique(), program1, program2],
565 vec![
566 CompiledInstruction {
567 program_id_index: 1,
568 accounts: vec![],
569 data: vec![],
570 },
571 CompiledInstruction {
572 program_id_index: 2,
573 accounts: vec![],
574 data: vec![],
575 },
576 ],
577 );
578
579 assert_eq!(metadata.count_precompiles_before_index(), 0);
580 }
581
582 #[test]
583 fn test_count_precompiles_before_index_with_ed25519() {
584 use solana_message::compiled_instruction::CompiledInstruction;
585
586 let ed25519 = Pubkey::from_str("Ed25519SigVerify111111111111111111111111111").unwrap();
594 let program1 = Pubkey::new_unique();
595 let program2 = Pubkey::new_unique();
596
597 let metadata = create_metadata_with_message(
598 vec![2],
599 1,
600 vec![],
601 vec![Pubkey::new_unique(), ed25519, program1, program2],
602 vec![
603 CompiledInstruction {
604 program_id_index: 1,
605 accounts: vec![],
606 data: vec![],
607 },
608 CompiledInstruction {
609 program_id_index: 2,
610 accounts: vec![],
611 data: vec![],
612 },
613 CompiledInstruction {
614 program_id_index: 3,
615 accounts: vec![],
616 data: vec![],
617 },
618 ],
619 );
620
621 assert_eq!(metadata.count_precompiles_before_index(), 1);
622 }
623
624 #[test]
625 fn test_count_precompiles_before_index_with_multiple_precompiles() {
626 use solana_message::compiled_instruction::CompiledInstruction;
627
628 let ed25519 = Pubkey::from_str("Ed25519SigVerify111111111111111111111111111").unwrap();
637 let secp256k1 = Pubkey::from_str("KeccakSecp256k11111111111111111111111111111").unwrap();
638 let program1 = Pubkey::new_unique();
639 let program2 = Pubkey::new_unique();
640
641 let metadata = create_metadata_with_message(
642 vec![3],
643 1,
644 vec![],
645 vec![Pubkey::new_unique(), ed25519, secp256k1, program1, program2],
646 vec![
647 CompiledInstruction {
648 program_id_index: 1,
649 accounts: vec![],
650 data: vec![],
651 },
652 CompiledInstruction {
653 program_id_index: 2,
654 accounts: vec![],
655 data: vec![],
656 },
657 CompiledInstruction {
658 program_id_index: 3,
659 accounts: vec![],
660 data: vec![],
661 },
662 CompiledInstruction {
663 program_id_index: 4,
664 accounts: vec![],
665 data: vec![],
666 },
667 ],
668 );
669
670 assert_eq!(metadata.count_precompiles_before_index(), 2);
671 }
672
673 #[test]
674 fn test_extract_event_log_data_with_precompile_offset() {
675 use solana_message::compiled_instruction::CompiledInstruction;
676
677 let compute_budget = Pubkey::new_unique();
688 let ed25519 = Pubkey::from_str("Ed25519SigVerify111111111111111111111111111").unwrap();
689 let target_program = Pubkey::new_unique();
690
691 let logs = vec![
692 "Program ComputeBudget111111111111111111111111111 invoke [1]".to_string(),
693 "Program ComputeBudget111111111111111111111111111 success".to_string(),
694 format!("Program {} invoke [1]", target_program),
695 "Program data: dGVzdF9kYXRh".to_string(), format!("Program {} success", target_program),
697 ];
698
699 let metadata = create_metadata_with_message(
700 vec![2],
701 1,
702 logs,
703 vec![
704 Pubkey::new_unique(),
705 compute_budget,
706 ed25519,
707 target_program,
708 ],
709 vec![
710 CompiledInstruction {
711 program_id_index: 1,
712 accounts: vec![],
713 data: vec![],
714 },
715 CompiledInstruction {
716 program_id_index: 2,
717 accounts: vec![],
718 data: vec![],
719 },
720 CompiledInstruction {
721 program_id_index: 3,
722 accounts: vec![],
723 data: vec![],
724 },
725 ],
726 );
727
728 let extracted = metadata.extract_event_log_data();
729 assert_eq!(extracted.len(), 1);
730 assert_eq!(extracted[0], b"test_data");
731 }
732
733 #[test]
734 fn test_extract_event_log_data_cpi_with_precompile_offset() {
735 use solana_message::compiled_instruction::CompiledInstruction;
736
737 let compute_budget = Pubkey::new_unique();
749 let ed25519 = Pubkey::from_str("Ed25519SigVerify111111111111111111111111111").unwrap();
750 let router_program = Pubkey::new_unique();
751 let target_program = Pubkey::new_unique();
752
753 let logs = vec![
754 "Program ComputeBudget111111111111111111111111111 invoke [1]".to_string(),
755 "Program ComputeBudget111111111111111111111111111 success".to_string(),
756 format!("Program {} invoke [1]", router_program),
757 format!("Program {} invoke [2]", target_program),
758 "Program data: Y3BpX2RhdGE=".to_string(), format!("Program {} success", target_program),
760 format!("Program {} success", router_program),
761 ];
762
763 let metadata = create_metadata_with_message(
764 vec![2, 0],
765 2,
766 logs,
767 vec![
768 Pubkey::new_unique(),
769 compute_budget,
770 ed25519,
771 router_program,
772 target_program,
773 ],
774 vec![
775 CompiledInstruction {
776 program_id_index: 1,
777 accounts: vec![],
778 data: vec![],
779 },
780 CompiledInstruction {
781 program_id_index: 2,
782 accounts: vec![],
783 data: vec![],
784 },
785 CompiledInstruction {
786 program_id_index: 3,
787 accounts: vec![],
788 data: vec![],
789 },
790 ],
791 );
792
793 let extracted = metadata.extract_event_log_data();
794 assert_eq!(extracted.len(), 1);
795 assert_eq!(extracted[0], b"cpi_data");
796 }
797
798 #[test]
799 fn test_extract_event_log_data_cpi_with_multiple_precompiles_and_instructions() {
800 use solana_message::compiled_instruction::CompiledInstruction;
801
802 let compute_budget = Pubkey::new_unique();
803 let ed25519 = Pubkey::from_str("Ed25519SigVerify111111111111111111111111111").unwrap();
804 let secp256k1 = Pubkey::from_str("KeccakSecp256k11111111111111111111111111111").unwrap();
805 let router_program = Pubkey::new_unique();
806 let swap_program = Pubkey::new_unique();
807 let token_program = Pubkey::new_unique();
808
809 let logs = vec![
826 "Program ComputeBudget111111111111111111111111111 invoke [1]".to_string(),
827 "Program ComputeBudget111111111111111111111111111 success".to_string(),
828 format!("Program {} invoke [1]", router_program),
829 format!("Program {} invoke [2]", swap_program),
830 format!("Program {} invoke [3]", token_program),
831 "Program data: dG9rZW5fZGF0YQ==".to_string(), format!("Program {} success", token_program),
833 "Program data: c3dhcF9kYXRhXzE=".to_string(), format!("Program {} success", swap_program),
835 format!("Program {} invoke [2]", swap_program),
836 "Program data: c3dhcF9kYXRhXzI=".to_string(), format!("Program {} success", swap_program),
838 "Program data: cm91dGVyX2RhdGE=".to_string(), format!("Program {} success", router_program),
840 ];
841
842 let account_keys = vec![
843 Pubkey::new_unique(),
844 compute_budget,
845 ed25519,
846 secp256k1,
847 router_program,
848 swap_program,
849 token_program,
850 ];
851
852 let instructions = vec![
853 CompiledInstruction {
854 program_id_index: 1,
855 accounts: vec![],
856 data: vec![],
857 },
858 CompiledInstruction {
859 program_id_index: 2,
860 accounts: vec![],
861 data: vec![],
862 },
863 CompiledInstruction {
864 program_id_index: 3,
865 accounts: vec![],
866 data: vec![],
867 },
868 CompiledInstruction {
869 program_id_index: 4,
870 accounts: vec![],
871 data: vec![],
872 },
873 ];
874
875 let router_metadata = create_metadata_with_message(
876 vec![3],
877 1,
878 logs.clone(),
879 account_keys.clone(),
880 instructions.clone(),
881 );
882 let router_extracted = router_metadata.extract_event_log_data();
883 assert_eq!(router_extracted.len(), 1);
884 assert_eq!(router_extracted[0], b"router_data");
885
886 let first_swap_metadata = create_metadata_with_message(
887 vec![3, 0],
888 2,
889 logs.clone(),
890 account_keys.clone(),
891 instructions.clone(),
892 );
893 let first_swap_extracted = first_swap_metadata.extract_event_log_data();
894 assert_eq!(first_swap_extracted.len(), 1);
895 assert_eq!(first_swap_extracted[0], b"swap_data_1");
896
897 let token_metadata = create_metadata_with_message(
898 vec![3, 0, 0],
899 3,
900 logs.clone(),
901 account_keys.clone(),
902 instructions.clone(),
903 );
904 let token_extracted = token_metadata.extract_event_log_data();
905 assert_eq!(token_extracted.len(), 1);
906 assert_eq!(token_extracted[0], b"token_data");
907
908 let second_swap_metadata = create_metadata_with_message(
909 vec![3, 1],
910 2,
911 logs.clone(),
912 account_keys.clone(),
913 instructions.clone(),
914 );
915 let second_swap_extracted = second_swap_metadata.extract_event_log_data();
916 assert_eq!(second_swap_extracted.len(), 1);
917 assert_eq!(second_swap_extracted[0], b"swap_data_2");
918
919 assert_eq!(router_metadata.count_precompiles_before_index(), 2);
920 assert_eq!(first_swap_metadata.count_precompiles_before_index(), 2);
921 }
922}