1use {
24 crate::{
25 collection::InstructionDecoderCollection,
26 datasource::TransactionUpdate,
27 error::{CarbonResult, Error},
28 instruction::{DecodedInstruction, InstructionMetadata, MAX_INSTRUCTION_STACK_DEPTH},
29 schema::ParsedInstruction,
30 transaction::TransactionMetadata,
31 },
32 solana_instruction::AccountMeta,
33 solana_program::{
34 instruction::CompiledInstruction,
35 message::{v0::LoadedAddresses, VersionedMessage},
36 },
37 solana_pubkey::Pubkey,
38 solana_transaction_context::TransactionReturnData,
39 solana_transaction_status::{
40 option_serializer::OptionSerializer, InnerInstruction, InnerInstructions, Reward,
41 TransactionStatusMeta, TransactionTokenBalance, UiInstruction, UiLoadedAddresses,
42 UiTransactionStatusMeta,
43 },
44 std::{collections::HashSet, str::FromStr, sync::Arc},
45};
46
47pub fn extract_instructions_with_metadata(
71 transaction_metadata: &Arc<TransactionMetadata>,
72 transaction_update: &TransactionUpdate,
73) -> CarbonResult<Vec<(InstructionMetadata, solana_instruction::Instruction)>> {
74 log::trace!(
75 "extract_instructions_with_metadata(transaction_metadata: {:?}, transaction_update: {:?})",
76 transaction_metadata,
77 transaction_update
78 );
79
80 let message = &transaction_update.transaction.message;
81 let meta = &transaction_update.meta;
82 let mut instructions_with_metadata = Vec::with_capacity(32);
83
84 match message {
85 VersionedMessage::Legacy(legacy) => {
86 process_instructions(
87 &legacy.account_keys,
88 &legacy.instructions,
89 &meta.inner_instructions,
90 transaction_metadata,
91 &mut instructions_with_metadata,
92 |_, idx| legacy.is_maybe_writable(idx, None),
93 |_, idx| legacy.is_signer(idx),
94 );
95 }
96 VersionedMessage::V0(v0) => {
97 let mut account_keys: Vec<Pubkey> = Vec::with_capacity(
98 v0.account_keys.len()
99 + meta.loaded_addresses.writable.len()
100 + meta.loaded_addresses.readonly.len(),
101 );
102
103 account_keys.extend_from_slice(&v0.account_keys);
104 account_keys.extend_from_slice(&meta.loaded_addresses.writable);
105 account_keys.extend_from_slice(&meta.loaded_addresses.readonly);
106
107 process_instructions(
108 &account_keys,
109 &v0.instructions,
110 &meta.inner_instructions,
111 transaction_metadata,
112 &mut instructions_with_metadata,
113 |key, _| meta.loaded_addresses.writable.contains(key),
114 |_, idx| idx < v0.header.num_required_signatures as usize,
115 );
116 }
117 }
118
119 Ok(instructions_with_metadata)
120}
121
122fn process_instructions<F1, F2>(
123 account_keys: &[Pubkey],
124 instructions: &[CompiledInstruction],
125 inner: &Option<Vec<InnerInstructions>>,
126 transaction_metadata: &Arc<TransactionMetadata>,
127 result: &mut Vec<(InstructionMetadata, solana_instruction::Instruction)>,
128 is_writable: F1,
129 is_signer: F2,
130) where
131 F1: Fn(&Pubkey, usize) -> bool,
132 F2: Fn(&Pubkey, usize) -> bool,
133{
134 for (i, compiled_instruction) in instructions.iter().enumerate() {
135 result.push((
136 InstructionMetadata {
137 transaction_metadata: transaction_metadata.clone(),
138 stack_height: 1,
139 index: i as u32,
140 absolute_path: vec![i as u8],
141 },
142 build_instruction(account_keys, compiled_instruction, &is_writable, &is_signer),
143 ));
144
145 if let Some(inner_instructions) = inner {
146 for inner_tx in inner_instructions {
147 if inner_tx.index as usize == i {
148 let mut path_stack = [0; MAX_INSTRUCTION_STACK_DEPTH];
149 path_stack[0] = inner_tx.index;
150 let mut prev_height = 0;
151
152 for inner_inst in &inner_tx.instructions {
153 let stack_height = inner_inst.stack_height.unwrap_or(1) as usize;
154 if stack_height > prev_height {
155 path_stack[stack_height - 1] = 0;
156 } else {
157 path_stack[stack_height - 1] += 1;
158 }
159
160 result.push((
161 InstructionMetadata {
162 transaction_metadata: transaction_metadata.clone(),
163 stack_height: stack_height as u32,
164 index: inner_tx.index as u32,
165 absolute_path: path_stack[..stack_height].into(),
166 },
167 build_instruction(
168 account_keys,
169 &inner_inst.instruction,
170 &is_writable,
171 &is_signer,
172 ),
173 ));
174
175 prev_height = stack_height;
176 }
177 }
178 }
179 }
180 }
181}
182
183fn build_instruction<F1, F2>(
184 account_keys: &[Pubkey],
185 instruction: &CompiledInstruction,
186 is_writable: &F1,
187 is_signer: &F2,
188) -> solana_instruction::Instruction
189where
190 F1: Fn(&Pubkey, usize) -> bool,
191 F2: Fn(&Pubkey, usize) -> bool,
192{
193 let program_id = *account_keys
194 .get(instruction.program_id_index as usize)
195 .unwrap_or(&Pubkey::default());
196
197 let accounts = instruction
198 .accounts
199 .iter()
200 .filter_map(|account_idx| {
201 account_keys
202 .get(*account_idx as usize)
203 .map(|key| AccountMeta {
204 pubkey: *key,
205 is_writable: is_writable(key, *account_idx as usize),
206 is_signer: is_signer(key, *account_idx as usize),
207 })
208 })
209 .collect();
210
211 solana_instruction::Instruction {
212 program_id,
213 accounts,
214 data: instruction.data.clone(),
215 }
216}
217
218pub fn extract_account_metas(
240 compiled_instruction: &CompiledInstruction,
241 message: &VersionedMessage,
242) -> CarbonResult<Vec<AccountMeta>> {
243 log::trace!(
244 "extract_account_metas(compiled_instruction: {:?}, message: {:?})",
245 compiled_instruction,
246 message
247 );
248 let mut accounts = Vec::<AccountMeta>::with_capacity(compiled_instruction.accounts.len());
249
250 for account_index in compiled_instruction.accounts.iter() {
251 accounts.push(AccountMeta {
252 pubkey: *message
253 .static_account_keys()
254 .get(*account_index as usize)
255 .ok_or(Error::MissingAccountInTransaction)?,
256 is_signer: message.is_signer(*account_index as usize),
257 is_writable: message.is_maybe_writable(
258 *account_index as usize,
259 Some(
260 &message
261 .static_account_keys()
262 .iter()
263 .copied()
264 .collect::<HashSet<_>>(),
265 ),
266 ),
267 });
268 }
269
270 Ok(accounts)
271}
272
273pub fn unnest_parsed_instructions<T: InstructionDecoderCollection>(
293 transaction_metadata: Arc<TransactionMetadata>,
294 instructions: Vec<ParsedInstruction<T>>,
295 stack_height: u32,
296) -> Vec<(InstructionMetadata, DecodedInstruction<T>)> {
297 log::trace!(
298 "unnest_parsed_instructions(instructions: {:?})",
299 instructions
300 );
301
302 let mut result = Vec::new();
303
304 for (ix_idx, parsed_instruction) in instructions.into_iter().enumerate() {
305 result.push((
306 InstructionMetadata {
307 transaction_metadata: transaction_metadata.clone(),
308 stack_height,
309 index: ix_idx as u32 + 1,
310 absolute_path: vec![],
311 },
312 parsed_instruction.instruction,
313 ));
314 result.extend(unnest_parsed_instructions(
315 transaction_metadata.clone(),
316 parsed_instruction.inner_instructions,
317 stack_height + 1,
318 ));
319 }
320
321 result
322}
323
324pub fn transaction_metadata_from_original_meta(
345 meta_original: UiTransactionStatusMeta,
346) -> CarbonResult<TransactionStatusMeta> {
347 log::trace!(
348 "transaction_metadata_from_original_meta(meta_original: {:?})",
349 meta_original
350 );
351 Ok(TransactionStatusMeta {
352 status: meta_original.status,
353 fee: meta_original.fee,
354 pre_balances: meta_original.pre_balances,
355 post_balances: meta_original.post_balances,
356 inner_instructions: Some(
357 meta_original
358 .inner_instructions
359 .unwrap_or_else(std::vec::Vec::new)
360 .iter()
361 .map(|inner_instruction_group| InnerInstructions {
362 index: inner_instruction_group.index,
363 instructions: inner_instruction_group
364 .instructions
365 .iter()
366 .map(|ui_instruction| match ui_instruction {
367 UiInstruction::Compiled(compiled_ui_instruction) => {
368 let decoded_data =
369 bs58::decode(compiled_ui_instruction.data.clone())
370 .into_vec()
371 .unwrap_or_else(|_| vec![]);
372 InnerInstruction {
373 instruction: CompiledInstruction {
374 program_id_index: compiled_ui_instruction.program_id_index,
375 accounts: compiled_ui_instruction.accounts.clone(),
376 data: decoded_data,
377 },
378 stack_height: compiled_ui_instruction.stack_height,
379 }
380 }
381 _ => {
382 log::error!("Unsupported instruction type encountered");
383 InnerInstruction {
384 instruction: CompiledInstruction {
385 program_id_index: 0,
386 accounts: vec![],
387 data: vec![],
388 },
389 stack_height: None,
390 }
391 }
392 })
393 .collect::<Vec<InnerInstruction>>(),
394 })
395 .collect::<Vec<InnerInstructions>>(),
396 ),
397 log_messages: Some(
398 meta_original
399 .log_messages
400 .unwrap_or_else(std::vec::Vec::new),
401 ),
402 pre_token_balances: Some(
403 meta_original
404 .pre_token_balances
405 .unwrap_or_else(std::vec::Vec::new)
406 .iter()
407 .filter_map(|transaction_token_balance| {
408 if let (OptionSerializer::Some(owner), OptionSerializer::Some(program_id)) = (
409 transaction_token_balance.owner.as_ref(),
410 transaction_token_balance.program_id.as_ref(),
411 ) {
412 Some(TransactionTokenBalance {
413 account_index: transaction_token_balance.account_index,
414 mint: transaction_token_balance.mint.clone(),
415 ui_token_amount: transaction_token_balance.ui_token_amount.clone(),
416 owner: owner.to_string(),
417 program_id: program_id.to_string(),
418 })
419 } else {
420 None
421 }
422 })
423 .collect::<Vec<TransactionTokenBalance>>(),
424 ),
425 post_token_balances: Some(
426 meta_original
427 .post_token_balances
428 .unwrap_or_else(std::vec::Vec::new)
429 .iter()
430 .filter_map(|transaction_token_balance| {
431 if let (OptionSerializer::Some(owner), OptionSerializer::Some(program_id)) = (
432 transaction_token_balance.owner.as_ref(),
433 transaction_token_balance.program_id.as_ref(),
434 ) {
435 Some(TransactionTokenBalance {
436 account_index: transaction_token_balance.account_index,
437 mint: transaction_token_balance.mint.clone(),
438 ui_token_amount: transaction_token_balance.ui_token_amount.clone(),
439 owner: owner.to_string(),
440 program_id: program_id.to_string(),
441 })
442 } else {
443 None
444 }
445 })
446 .collect::<Vec<TransactionTokenBalance>>(),
447 ),
448 rewards: Some(
449 meta_original
450 .rewards
451 .unwrap_or_else(std::vec::Vec::new)
452 .iter()
453 .map(|rewards| Reward {
454 pubkey: rewards.pubkey.clone(),
455 lamports: rewards.lamports,
456 post_balance: rewards.post_balance,
457 reward_type: rewards.reward_type,
458 commission: rewards.commission,
459 })
460 .collect::<Vec<Reward>>(),
461 ),
462 loaded_addresses: {
463 let loaded = meta_original
464 .loaded_addresses
465 .unwrap_or_else(|| UiLoadedAddresses {
466 writable: vec![],
467 readonly: vec![],
468 });
469 LoadedAddresses {
470 writable: loaded
471 .writable
472 .iter()
473 .map(|w| Pubkey::from_str(w).unwrap_or_default())
474 .collect::<Vec<Pubkey>>(),
475 readonly: loaded
476 .readonly
477 .iter()
478 .map(|r| Pubkey::from_str(r).unwrap_or_default())
479 .collect::<Vec<Pubkey>>(),
480 }
481 },
482 return_data: meta_original
483 .return_data
484 .map(|return_data| TransactionReturnData {
485 program_id: return_data.program_id.parse().unwrap_or_default(),
486 data: return_data.data.0.as_bytes().to_vec(),
487 }),
488 compute_units_consumed: meta_original
489 .compute_units_consumed
490 .map(|compute_unit_consumed| compute_unit_consumed)
491 .or(None),
492 })
493}
494
495#[cfg(test)]
496mod tests {
497 use {
498 super::*,
499 crate::instruction::{InstructionsWithMetadata, NestedInstructions},
500 carbon_test_utils::base58_deserialize,
501 solana_account_decoder_client_types::token::UiTokenAmount,
502 solana_hash::Hash,
503 solana_message::{
504 legacy::Message,
505 v0::{self, MessageAddressTableLookup},
506 MessageHeader,
507 },
508 solana_signature::Signature,
509 solana_transaction::versioned::VersionedTransaction,
510 std::vec,
511 };
512
513 #[test]
514 fn test_transaction_metadata_from_original_meta_simple() {
515 let expected_tx_meta = TransactionStatusMeta {
517 status: Ok(()),
518 fee: 9000,
519 pre_balances: vec![
520 323078186,
521 2039280,
522 3320301592555,
523 68596164896,
524 446975391774,
525 1019603,
526 2039280,
527 1,
528 1141440,
529 1,
530 731913600,
531 934087680,
532 3695760,
533 1461600
534 ],
535 post_balances: vec![
536 321402879,
537 2039280,
538 3320301602394,
539 68597804804,
540 446975398334,
541 1029603,
542 2039280,
543 1,
544 1141440,
545 1,
546 731913600,
547 934087680,
548 3695760,
549 1461600,
550 ],
551 inner_instructions: Some(vec![
552 InnerInstructions{
553 index: 1,
554 instructions: vec![
555 InnerInstruction{
556 instruction: CompiledInstruction{
557 program_id_index: 11,
558 accounts: vec![
559 1,
560 13,
561 6,
562 3
563 ],
564 data: base58_deserialize::ix_data("hDDqy4KAEGx3J")
565 },
566 stack_height: Some(2),
567 },
568 InnerInstruction{
569 instruction: CompiledInstruction{
570 program_id_index: 7,
571 accounts: vec![
572 0,
573 3
574 ],
575 data: base58_deserialize::ix_data("3Bxs4ezjpW22kuoV")
576 },
577 stack_height: Some(2),
578 },
579 InnerInstruction{
580 instruction: CompiledInstruction{
581 program_id_index: 7,
582 accounts: vec![
583 0,
584 2
585 ],
586 data: base58_deserialize::ix_data("3Bxs4KSwSHEiNiN3")
587 },
588 stack_height: Some(2),
589 },
590 InnerInstruction{
591 instruction: CompiledInstruction{
592 program_id_index: 7,
593 accounts: vec![
594 0,
595 4
596 ],
597 data: base58_deserialize::ix_data("3Bxs4TdopiUbobUj")
598 },
599 stack_height: Some(2),
600 },
601 ],
602 }
603 ]),
604 log_messages: Some(vec![
605 "Program ComputeBudget111111111111111111111111111111 invoke [1]",
606 "Program ComputeBudget111111111111111111111111111111 success",
607 "Program MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG invoke [1]",
608 "Program log: Instruction: Buy",
609 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]",
610 "Program log: Instruction: TransferChecked",
611 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 6147 of 370747 compute units",
612 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
613 "Program 11111111111111111111111111111111 invoke [2]",
614 "Program 11111111111111111111111111111111 success",
615 "Program 11111111111111111111111111111111 invoke [2]",
616 "Program 11111111111111111111111111111111 success",
617 "Program 11111111111111111111111111111111 invoke [2]",
618 "Program 11111111111111111111111111111111 success",
619 "Program log: Transfering collateral from buyer to curve account: 1639908, Helio fee: 6560, Dex fee: 9839",
620 "Program data: vdt/007mYe5XD5Rn8AQAAOQFGQAAAAAAbyYAAAAAAACgGQAAAAAAAAAGZYutIlwKL4hMgKVUfMrwNkmY1Lx+bGF8yTqY+mFm7CM5km+SaKcGm4hX/quBhPtof2NGGMA12sQ53BrrO1WYoPAAAAAAAc/9l+YGhwgMeWNsZs3HFBi8RjvPXd5tjX5Jv9YfHhgWAAUAAAB0cmFkZQ==",
621 "Program MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG consumed 44550 of 399850 compute units",
622 "Program MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG success",
623 "Program 11111111111111111111111111111111 invoke [1]",
624 "Program 11111111111111111111111111111111 success"
625 ].into_iter().map(|s| s.to_string()).collect()),
626 pre_token_balances: Some(vec![
627 TransactionTokenBalance {
628 account_index:1,
629 mint:"3cBFsM1wosTJi9yun6kcHhYHyJcut1MNQY28zjC4moon".to_owned(),
630 ui_token_amount: UiTokenAmount {
631 ui_amount: Some(253495663.57641867),
632 decimals: 9,
633 amount: "253495663576418647".to_owned(),
634 ui_amount_string: "253495663.576418647".to_owned(),
635 },
636 owner: "4CYhuDhT4c9ATZpJceoQG8Du4vCjf5ZKvxsyXpJoVub4".to_owned(),
637 program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_owned(),
638 },
639 TransactionTokenBalance {
640 account_index:6,
641 mint:"3cBFsM1wosTJi9yun6kcHhYHyJcut1MNQY28zjC4moon".to_owned(),
642 ui_token_amount: UiTokenAmount {
643 ui_amount: Some(2266244.543486133),
644 decimals: 9,
645 amount: "2266244543486133".to_owned(),
646 ui_amount_string: "2266244.543486133".to_owned(),
647 },
648 owner: "Ezug1uk7oTEULvBcXCngdZuJDmZ8Ed2TKY4oov4GmLLm".to_owned(),
649 program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_owned(),
650 },
651 ]),
652 post_token_balances: Some(vec![
653 TransactionTokenBalance {
654 account_index:1,
655 mint:"3cBFsM1wosTJi9yun6kcHhYHyJcut1MNQY28zjC4moon".to_owned(),
656 ui_token_amount: UiTokenAmount {
657 ui_amount: Some(253490233.0),
658 decimals: 9,
659 amount: "253490233000000000".to_owned(),
660 ui_amount_string: "253490233".to_owned(),
661 },
662 owner: "4CYhuDhT4c9ATZpJceoQG8Du4vCjf5ZKvxsyXpJoVub4".to_owned(),
663 program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_owned(),
664 },
665 TransactionTokenBalance {
666 account_index:6,
667 mint:"3cBFsM1wosTJi9yun6kcHhYHyJcut1MNQY28zjC4moon".to_owned(),
668 ui_token_amount: UiTokenAmount {
669 ui_amount: Some(2271675.11990478),
670 decimals: 9,
671 amount: "2271675119904780".to_owned(),
672 ui_amount_string: "2271675.11990478".to_owned(),
673 },
674 owner: "Ezug1uk7oTEULvBcXCngdZuJDmZ8Ed2TKY4oov4GmLLm".to_owned(),
675 program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_owned(),
676 },
677 ]),
678 rewards: Some(vec![]),
679 loaded_addresses: LoadedAddresses {
680 writable: vec![],
681 readonly: vec![],
682 },
683 return_data: None,
684 compute_units_consumed: Some(44850),
685 };
686 let tx_meta_status =
688 carbon_test_utils::read_transaction_meta("tests/fixtures/simple_tx.json")
689 .expect("read fixture");
690
691 let original_tx_meta = transaction_metadata_from_original_meta(tx_meta_status)
692 .expect("transaction metadata from original meta");
693 let transaction_update = TransactionUpdate {
694 signature: Signature::default(),
695 transaction: VersionedTransaction {
696 signatures: vec![Signature::default()],
697 message: VersionedMessage::Legacy(Message {
698 header: MessageHeader::default(),
699 account_keys: vec![
700 Pubkey::from_str_const("Ezug1uk7oTEULvBcXCngdZuJDmZ8Ed2TKY4oov4GmLLm"),
701 Pubkey::from_str_const("5Zg9kJdzYFKwS4hLzF1QvvNBYyUNpn9YWxYp6HVMknJt"),
702 Pubkey::from_str_const("3udvfL24waJcLhskRAsStNMoNUvtyXdxrWQz4hgi953N"),
703 Pubkey::from_str_const("4CYhuDhT4c9ATZpJceoQG8Du4vCjf5ZKvxsyXpJoVub4"),
704 Pubkey::from_str_const("5K5RtTWzzLp4P8Npi84ocf7F1vBsAu29N1irG4iiUnzt"),
705 Pubkey::from_str_const("ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49"),
706 Pubkey::from_str_const("6FqNPPA4W1nuvL1BHGhusSHjdNa4qJBoXyRKggAh9pb9"),
707 Pubkey::from_str_const("11111111111111111111111111111111"),
708 Pubkey::from_str_const("MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG"),
709 Pubkey::from_str_const("ComputeBudget111111111111111111111111111111"),
710 Pubkey::from_str_const("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"),
711 Pubkey::from_str_const("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"),
712 Pubkey::from_str_const("36Eru7v11oU5Pfrojyn5oY3nETA1a1iqsw2WUu6afkM9"),
713 Pubkey::from_str_const("3cBFsM1wosTJi9yun6kcHhYHyJcut1MNQY28zjC4moon"),
714 ],
715 recent_blockhash: Hash::default(),
716 instructions: vec![
717 CompiledInstruction {
718 program_id_index: 9,
719 accounts: vec![],
720 data: base58_deserialize::ix_data("3GAG5eogvTjV"),
721 },
722 CompiledInstruction {
723 program_id_index: 8,
724 accounts: vec![0, 6, 3, 1, 2, 4, 5, 12, 11, 10, 7],
725 data: base58_deserialize::ix_data(
726 "XJqfG9ATWCDptdf7vx8UxGEDKxSPzetbnXg1wZsUpasa7",
727 ),
728 },
729 CompiledInstruction {
730 program_id_index: 7,
731 accounts: vec![],
732 data: base58_deserialize::ix_data("3GAG5eogvTjV"),
733 },
734 ],
735 }),
736 },
737 meta: original_tx_meta.clone(),
738 is_vote: false,
739 slot: 123,
740 block_time: Some(123),
741 block_hash: Hash::from_str("9bit9vXNX9HyHwL89aGDNmk3vbyAM96nvb6F4SaoM1CU").ok(),
742 };
743 let transaction_metadata = transaction_update
744 .clone()
745 .try_into()
746 .expect("transaction metadata");
747 let instructions_with_metadata: InstructionsWithMetadata =
748 extract_instructions_with_metadata(
749 &Arc::new(transaction_metadata),
750 &transaction_update,
751 )
752 .expect("extract instructions with metadata");
753
754 let nested_instructions: NestedInstructions = instructions_with_metadata.into();
755
756 assert_eq!(original_tx_meta, expected_tx_meta);
758 assert_eq!(nested_instructions.len(), 3);
759 assert_eq!(nested_instructions[0].inner_instructions.len(), 0);
760 assert_eq!(nested_instructions[1].inner_instructions.len(), 4);
761 assert_eq!(nested_instructions[2].inner_instructions.len(), 0);
762 }
763
764 #[test]
765 fn test_transaction_metadata_from_original_meta_cpi() {
766 let expected_tx_meta = TransactionStatusMeta {
768 status: Ok(()),
769 fee: 80000,
770 pre_balances: vec![
771 64472129,
772 2039280,
773 2039280,
774 71437440,
775 2039280,
776 1,
777 1141440,
778 7775404600,
779 117416465239,
780 731913600,
781 71437440,
782 23385600,
783 71437440,
784 7182750,
785 2039280,
786 2039280,
787 1141440,
788 1,
789 934087680,
790 4000000
791 ],
792 post_balances: vec![
793 64392129,
794 2039280,
795 2039280,
796 71437440,
797 2039280,
798 1,
799 1141440,
800 7775404600,
801 117416465239,
802 731913600,
803 71437440,
804 23385600,
805 71437440,
806 7182750,
807 2039280,
808 2039280,
809 1141440,
810 1,
811 934087680,
812 4000000
813 ],
814 inner_instructions: Some(vec![
815 InnerInstructions {
816 index: 3,
817 instructions: vec![
818 InnerInstruction{
819 instruction: CompiledInstruction{
820 program_id_index: 16,
821 accounts: vec![
822 13,
823 16,
824 14,
825 15,
826 1,
827 2,
828 7,
829 8,
830 11,
831 16,
832 0,
833 18,
834 18,
835 19,
836 16,
837 3,
838 10,
839 12,
840 ],
841 data: base58_deserialize::ix_data("PgQWtn8oziwqoZL8sWNwT7LtzLzAUp8MM")
842 },
843 stack_height: Some(2),
844 },
845 InnerInstruction{
846 instruction: CompiledInstruction{
847 program_id_index: 18,
848 accounts: vec![
849 1,
850 8,
851 15,
852 0,
853 ],
854 data: base58_deserialize::ix_data("gD28Qcm8qkpHv")
855 },
856 stack_height: Some(3),
857 },
858 InnerInstruction{
859 instruction: CompiledInstruction{
860 program_id_index: 18,
861 accounts: vec![
862 14,
863 7,
864 2,
865 13,
866 ],
867 data: base58_deserialize::ix_data("hLZYKissEeFUU")
868 },
869 stack_height: Some(3),
870 },
871 InnerInstruction{
872 instruction: CompiledInstruction{
873 program_id_index: 16,
874 accounts: vec![
875 19,
876 ],
877 data: base58_deserialize::ix_data("yCGxBopjnVNQkNP5usq1PonMQAFjN4WpP7MXQHZjf7XRFvuZeLCkVHy966UtS1VyTsN9u6oGPC5aaYqLj5UXxLj8FaCJccaibatRPgkX95PDrzwLBhZE43gcsTwwccBuEd67YuWJsM1j7tXo5ntSaTWRsfuaqkkoaCDDeidPunPSTBRUY68Hw5oFnYhUcG5CUEPWmM")
878 },
879 stack_height: Some(3),
880 },
881 InnerInstruction{
882 instruction: CompiledInstruction{
883 program_id_index: 18,
884 accounts: vec![
885 1,
886 8,
887 4,
888 0,
889 ],
890 data: base58_deserialize::ix_data("heASn5ozzjZrp")
891 },
892 stack_height: Some(2),
893 },
894 ],
895 }
896 ]),
897 log_messages: Some(vec![
898 "Program ComputeBudget111111111111111111111111111111 invoke [1]",
899 "Program ComputeBudget111111111111111111111111111111 success",
900 "Program ComputeBudget111111111111111111111111111111 invoke [1]",
901 "Program ComputeBudget111111111111111111111111111111 success",
902 "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL invoke [1]",
903 "Program log: CreateIdempotent",
904 "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL consumed 4338 of 191700 compute units",
905 "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL success",
906 "Program 6m2CDdhRgxpH4WjvdzxAYbGxwdGUz5MziiL5jek2kBma invoke [1]",
907 "Program log: Instruction: CommissionSplSwap2",
908 "Program log: order_id: 105213",
909 "Program log: AFbX8oGjGpmVFywbVouvhQSRmiW2aR1mohfahi4Y2AdB",
910 "Program log: 7i5KKsX2weiTkry7jA4ZwSuXGhs5eJBEjY8vVxR4pfRx",
911 "Program log: 7Z2QzVa3q7r7m84nuez9eRn2u3oCUeg9D1bzdRvNFdxN",
912 "Program log: 7Z2QzVa3q7r7m84nuez9eRn2u3oCUeg9D1bzdRvNFdxN",
913 "Program log: before_source_balance: 9452950000000, before_destination_balance: 573930799366, amount_in: 9372599925000, expect_amount_out: 1700985700449, min_return: 1680573872044",
914 "Program log: Dex::MeteoraDlmm amount_in: 9372599925000, offset: 0",
915 "Program log: BMCheVSdKZ6rxsoJ1MChA5HQRtk5pz4QkCLs7MXFkvZJ",
916 "Program LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo invoke [2]",
917 "Program log: Instruction: Swap",
918 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]",
919 "Program log: Instruction: TransferChecked",
920 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 6173 of 112532 compute units",
921 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
922 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]",
923 "Program log: Instruction: TransferChecked",
924 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 6147 of 102925 compute units",
925 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
926 "Program LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo invoke [3]",
927 "Program LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo consumed 2134 of 93344 compute units",
928 "Program LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo success",
929 "Program LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo consumed 63904 of 153546 compute units",
930 "Program LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo success",
931 "Program data: QMbN6CYIceINCDl9OoYIAABhAKYKjAEAAA==",
932 "Program log: SwapEvent { dex: MeteoraDlmm, amount_in: 9372599925000, amount_out: 1700985700449 }",
933 "Program log: 6VRWsRGxnJFg7y4ck3NBsBPQ5SLkCtkH3tTioJkEby3b",
934 "Program log: AADrz4o64xxynMr8tochpqAYevmySvsEhjAgKEXNcjnd",
935 "Program log: after_source_balance: 80350075000, after_destination_balance: 2274916499815, source_token_change: 9372599925000, destination_token_change: 1700985700449",
936 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]",
937 "Program log: Instruction: TransferChecked",
938 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 6173 of 76109 compute units",
939 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
940 "Program log: commission_direction: true, commission_amount: 80350075000",
941 "Program 6m2CDdhRgxpH4WjvdzxAYbGxwdGUz5MziiL5jek2kBma consumed 118873 of 187362 compute units",
942 "Program 6m2CDdhRgxpH4WjvdzxAYbGxwdGUz5MziiL5jek2kBma success"
943 ].into_iter().map(|s| s.to_string()).collect()),
944 pre_token_balances: Some(vec![
945 TransactionTokenBalance {
946 account_index:1,
947 mint:"AFbX8oGjGpmVFywbVouvhQSRmiW2aR1mohfahi4Y2AdB".to_owned(),
948 ui_token_amount: UiTokenAmount {
949 ui_amount: Some(9452.95),
950 decimals: 9,
951 amount: "9452950000000".to_owned(),
952 ui_amount_string: "9452.95".to_owned(),
953 },
954 owner: "7Z2QzVa3q7r7m84nuez9eRn2u3oCUeg9D1bzdRvNFdxN".to_owned(),
955 program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_owned(),
956 },
957 TransactionTokenBalance {
958 account_index:2,
959 mint:"7i5KKsX2weiTkry7jA4ZwSuXGhs5eJBEjY8vVxR4pfRx".to_owned(),
960 ui_token_amount: UiTokenAmount {
961 ui_amount: Some(573.930799366),
962 decimals: 9,
963 amount: "573930799366".to_owned(),
964 ui_amount_string: "573.930799366".to_owned(),
965 },
966 owner: "7Z2QzVa3q7r7m84nuez9eRn2u3oCUeg9D1bzdRvNFdxN".to_owned(),
967 program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_owned(),
968 },
969 TransactionTokenBalance {
970 account_index:4,
971 mint:"AFbX8oGjGpmVFywbVouvhQSRmiW2aR1mohfahi4Y2AdB".to_owned(),
972 ui_token_amount: UiTokenAmount {
973 ui_amount: Some(357.387320573),
974 decimals: 9,
975 amount: "357387320573".to_owned(),
976 ui_amount_string: "357.387320573".to_owned(),
977 },
978 owner: "8psNvWTrdNTiVRNzAgsou9kETXNJm2SXZyaKuJraVRtf".to_owned(),
979 program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_owned(),
980 },
981 TransactionTokenBalance {
982 account_index:14,
983 mint:"7i5KKsX2weiTkry7jA4ZwSuXGhs5eJBEjY8vVxR4pfRx".to_owned(),
984 ui_token_amount: UiTokenAmount {
985 ui_amount: Some(108469.853556668),
986 decimals: 9,
987 amount: "108469853556668".to_owned(),
988 ui_amount_string: "108469.853556668".to_owned(),
989 },
990 owner: "BMCheVSdKZ6rxsoJ1MChA5HQRtk5pz4QkCLs7MXFkvZJ".to_owned(),
991 program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_owned(),
992 },
993 TransactionTokenBalance {
994 account_index:15,
995 mint:"AFbX8oGjGpmVFywbVouvhQSRmiW2aR1mohfahi4Y2AdB".to_owned(),
996 ui_token_amount: UiTokenAmount {
997 ui_amount: Some(176698.438078034),
998 decimals: 9,
999 amount: "176698438078034".to_owned(),
1000 ui_amount_string: "176698.438078034".to_owned(),
1001 },
1002 owner: "BMCheVSdKZ6rxsoJ1MChA5HQRtk5pz4QkCLs7MXFkvZJ".to_owned(),
1003 program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_owned(),
1004 },
1005 ]),
1006 post_token_balances: Some(vec![
1007 TransactionTokenBalance {
1008 account_index:1,
1009 mint:"AFbX8oGjGpmVFywbVouvhQSRmiW2aR1mohfahi4Y2AdB".to_owned(),
1010 ui_token_amount: UiTokenAmount {
1011 ui_amount: None,
1012 decimals: 9,
1013 amount: "0".to_owned(),
1014 ui_amount_string: "0".to_owned(),
1015 },
1016 owner: "7Z2QzVa3q7r7m84nuez9eRn2u3oCUeg9D1bzdRvNFdxN".to_owned(),
1017 program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_owned(),
1018 },
1019 TransactionTokenBalance {
1020 account_index:2,
1021 mint:"7i5KKsX2weiTkry7jA4ZwSuXGhs5eJBEjY8vVxR4pfRx".to_owned(),
1022 ui_token_amount: UiTokenAmount {
1023 ui_amount: Some( 2274.916499815),
1024 decimals: 9,
1025 amount: "2274916499815".to_owned(),
1026 ui_amount_string: "2274.916499815".to_owned(),
1027 },
1028 owner: "7Z2QzVa3q7r7m84nuez9eRn2u3oCUeg9D1bzdRvNFdxN".to_owned(),
1029 program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_owned(),
1030 },
1031 TransactionTokenBalance {
1032 account_index:4,
1033 mint:"AFbX8oGjGpmVFywbVouvhQSRmiW2aR1mohfahi4Y2AdB".to_owned(),
1034 ui_token_amount: UiTokenAmount {
1035 ui_amount: Some(437.737395573),
1036 decimals: 9,
1037 amount: "437737395573".to_owned(),
1038 ui_amount_string: "437.737395573".to_owned(),
1039 },
1040 owner: "8psNvWTrdNTiVRNzAgsou9kETXNJm2SXZyaKuJraVRtf".to_owned(),
1041 program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_owned(),
1042 },
1043 TransactionTokenBalance {
1044 account_index:14,
1045 mint:"7i5KKsX2weiTkry7jA4ZwSuXGhs5eJBEjY8vVxR4pfRx".to_owned(),
1046 ui_token_amount: UiTokenAmount {
1047 ui_amount: Some(106768.867856219),
1048 decimals: 9,
1049 amount: "106768867856219".to_owned(),
1050 ui_amount_string: "106768.867856219".to_owned(),
1051 },
1052 owner: "BMCheVSdKZ6rxsoJ1MChA5HQRtk5pz4QkCLs7MXFkvZJ".to_owned(),
1053 program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_owned(),
1054 },
1055 TransactionTokenBalance {
1056 account_index:15,
1057 mint:"AFbX8oGjGpmVFywbVouvhQSRmiW2aR1mohfahi4Y2AdB".to_owned(),
1058 ui_token_amount: UiTokenAmount {
1059 ui_amount: Some(186071.038003034),
1060 decimals: 9,
1061 amount: "186071038003034".to_owned(),
1062 ui_amount_string: "186071.038003034".to_owned(),
1063 },
1064 owner: "BMCheVSdKZ6rxsoJ1MChA5HQRtk5pz4QkCLs7MXFkvZJ".to_owned(),
1065 program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_owned(),
1066 },
1067 ]),
1068 rewards: Some(vec![]),
1069 loaded_addresses: LoadedAddresses {
1070 writable: vec![
1071 Pubkey::from_str_const("12QvTU4Z7XdxT16mSYU8rE2n9CpXpvunHiZrfySaf7h8"),
1072 Pubkey::from_str_const("76TaSYC4LuopNGf5apJUXMG2MDfcQMHiw6SMX93VYQGp"),
1073 Pubkey::from_str_const("7q4JPakWqK7UrjRsTNoYXMNeBYKP3WKawNcHYzidTaav"),
1074 Pubkey::from_str_const("BMCheVSdKZ6rxsoJ1MChA5HQRtk5pz4QkCLs7MXFkvZJ"),
1075 Pubkey::from_str_const("GqXFYwijuNaKRQgtrVrJkDGXorPcZwX7Vyd4jDsuxW9J"),
1076 Pubkey::from_str_const("JBbKXBC4yBco9BfChDaf5GHd8hyLbjASeLxFCwGCH99a"),
1077 Pubkey::from_str_const("LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo")
1078 ],
1079 readonly: vec![
1080 Pubkey::from_str_const("11111111111111111111111111111111"),
1081 Pubkey::from_str_const("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"),
1082 Pubkey::from_str_const("D1ZN9Wj1fRSUQfCjhvnu1hqDMT7hzjzBBpi12nVniYD6"),
1083 ],
1084 },
1085 return_data: None,
1086 compute_units_consumed: Some(123511),
1087 };
1088
1089 let tx_meta_status = carbon_test_utils::read_transaction_meta("tests/fixtures/cpi_tx.json")
1091 .expect("read fixture");
1092 let original_tx_meta = transaction_metadata_from_original_meta(tx_meta_status)
1093 .expect("transaction metadata from original meta");
1094 let transaction_update = TransactionUpdate {
1095 signature: Signature::default(),
1096 transaction: VersionedTransaction {
1097 signatures: vec![Signature::default()],
1098 message: VersionedMessage::V0(v0::Message {
1099 header: MessageHeader::default(),
1100 account_keys: vec![
1101 Pubkey::from_str_const("7Z2QzVa3q7r7m84nuez9eRn2u3oCUeg9D1bzdRvNFdxN"),
1102 Pubkey::from_str_const("6VRWsRGxnJFg7y4ck3NBsBPQ5SLkCtkH3tTioJkEby3b"),
1103 Pubkey::from_str_const("AADrz4o64xxynMr8tochpqAYevmySvsEhjAgKEXNcjnd"),
1104 Pubkey::from_str_const("EuGVLjHv1K1YVxmcukLYMBjGB7YXy5hxbJ2z4LeDLUfQ"),
1105 Pubkey::from_str_const("FDxb6WnUHrSsz9zwKqneC5JXVmrRgPySBjf6WfNfFyrM"),
1106 Pubkey::from_str_const("ComputeBudget111111111111111111111111111111"),
1107 Pubkey::from_str_const("6m2CDdhRgxpH4WjvdzxAYbGxwdGUz5MziiL5jek2kBma"),
1108 Pubkey::from_str_const("7i5KKsX2weiTkry7jA4ZwSuXGhs5eJBEjY8vVxR4pfRx"),
1109 Pubkey::from_str_const("AFbX8oGjGpmVFywbVouvhQSRmiW2aR1mohfahi4Y2AdB"),
1110 Pubkey::from_str_const("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"),
1111 Pubkey::from_str_const("12QvTU4Z7XdxT16mSYU8rE2n9CpXpvunHiZrfySaf7h8"),
1112 Pubkey::from_str_const("76TaSYC4LuopNGf5apJUXMG2MDfcQMHiw6SMX93VYQGp"),
1113 Pubkey::from_str_const("7q4JPakWqK7UrjRsTNoYXMNeBYKP3WKawNcHYzidTaav"),
1114 Pubkey::from_str_const("BMCheVSdKZ6rxsoJ1MChA5HQRtk5pz4QkCLs7MXFkvZJ"),
1115 Pubkey::from_str_const("GqXFYwijuNaKRQgtrVrJkDGXorPcZwX7Vyd4jDsuxW9J"),
1116 Pubkey::from_str_const("JBbKXBC4yBco9BfChDaf5GHd8hyLbjASeLxFCwGCH99a"),
1117 Pubkey::from_str_const("LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo"),
1118 Pubkey::from_str_const("11111111111111111111111111111111"),
1119 Pubkey::from_str_const("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"),
1120 Pubkey::from_str_const("D1ZN9Wj1fRSUQfCjhvnu1hqDMT7hzjzBBpi12nVniYD6"),
1121 ],
1122 recent_blockhash: Hash::default(),
1123 instructions: vec![
1124 CompiledInstruction {
1125 program_id_index: 5,
1126 accounts: vec![],
1127 data: base58_deserialize::ix_data("3GAG5eogvTjV"),
1128 },
1129 CompiledInstruction {
1130 program_id_index: 5,
1131 accounts: vec![],
1132 data: base58_deserialize::ix_data("3GAG5eogvTjV"),
1133 },
1134 CompiledInstruction {
1135 program_id_index: 9,
1136 accounts: vec![],
1137 data: base58_deserialize::ix_data("3GAG5eogvTjV"),
1138 },
1139 CompiledInstruction {
1140 program_id_index: 8,
1141 accounts: vec![],
1142 data: base58_deserialize::ix_data(
1143 "XJqfG9ATWCDptdf7vx8UxGEDKxSPzetbnXg1wZsUpasa7",
1144 ),
1145 },
1146 ],
1147 address_table_lookups: vec![
1148 MessageAddressTableLookup {
1149 account_key: Pubkey::from_str_const(
1150 "FfMiwAdZeeSZyuApu5fsCuPzvyAyKdEbNcmEVEEhgJAW",
1151 ),
1152 writable_indexes: vec![0, 1, 2, 3, 4, 5],
1153 readonly_indexes: vec![],
1154 },
1155 MessageAddressTableLookup {
1156 account_key: Pubkey::from_str_const(
1157 "EDDSpjZHrsFKYTMJDcBqXAjkLcu9EKdvrQR4XnqsXErH",
1158 ),
1159 writable_indexes: vec![0],
1160 readonly_indexes: vec![3, 4, 5],
1161 },
1162 ],
1163 }),
1164 },
1165 meta: original_tx_meta.clone(),
1166 is_vote: false,
1167 slot: 123,
1168 block_time: Some(123),
1169 block_hash: None,
1170 };
1171 let transaction_metadata = transaction_update
1172 .clone()
1173 .try_into()
1174 .expect("transaction metadata");
1175 let instructions_with_metadata: InstructionsWithMetadata =
1176 extract_instructions_with_metadata(
1177 &Arc::new(transaction_metadata),
1178 &transaction_update,
1179 )
1180 .expect("extract instructions with metadata");
1181 let nested_instructions: NestedInstructions = instructions_with_metadata.into();
1182
1183 assert_eq!(original_tx_meta, expected_tx_meta);
1185 assert_eq!(nested_instructions.len(), 4);
1186 assert_eq!(nested_instructions[0].inner_instructions.len(), 0);
1187 assert_eq!(nested_instructions[1].inner_instructions.len(), 0);
1188 assert_eq!(nested_instructions[2].inner_instructions.len(), 0);
1189 assert_eq!(nested_instructions[3].inner_instructions.len(), 2);
1190 }
1191}