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 cost_units: meta_original.cost_units.into(),
493 })
494}
495
496#[cfg(test)]
497mod tests {
498 use {
499 super::*,
500 crate::instruction::{InstructionsWithMetadata, NestedInstructions},
501 carbon_test_utils::base58_deserialize,
502 solana_account_decoder_client_types::token::UiTokenAmount,
503 solana_hash::Hash,
504 solana_message::{
505 legacy::Message,
506 v0::{self, MessageAddressTableLookup},
507 MessageHeader,
508 },
509 solana_signature::Signature,
510 solana_transaction::versioned::VersionedTransaction,
511 std::vec,
512 };
513
514 #[test]
515 fn test_transaction_metadata_from_original_meta_simple() {
516 let expected_tx_meta = TransactionStatusMeta {
518 status: Ok(()),
519 fee: 9000,
520 pre_balances: vec![
521 323078186,
522 2039280,
523 3320301592555,
524 68596164896,
525 446975391774,
526 1019603,
527 2039280,
528 1,
529 1141440,
530 1,
531 731913600,
532 934087680,
533 3695760,
534 1461600
535 ],
536 post_balances: vec![
537 321402879,
538 2039280,
539 3320301602394,
540 68597804804,
541 446975398334,
542 1029603,
543 2039280,
544 1,
545 1141440,
546 1,
547 731913600,
548 934087680,
549 3695760,
550 1461600,
551 ],
552 cost_units: None,
553 inner_instructions: Some(vec![
554 InnerInstructions{
555 index: 1,
556 instructions: vec![
557 InnerInstruction{
558 instruction: CompiledInstruction{
559 program_id_index: 11,
560 accounts: vec![
561 1,
562 13,
563 6,
564 3
565 ],
566 data: base58_deserialize::ix_data("hDDqy4KAEGx3J")
567 },
568 stack_height: Some(2),
569 },
570 InnerInstruction{
571 instruction: CompiledInstruction{
572 program_id_index: 7,
573 accounts: vec![
574 0,
575 3
576 ],
577 data: base58_deserialize::ix_data("3Bxs4ezjpW22kuoV")
578 },
579 stack_height: Some(2),
580 },
581 InnerInstruction{
582 instruction: CompiledInstruction{
583 program_id_index: 7,
584 accounts: vec![
585 0,
586 2
587 ],
588 data: base58_deserialize::ix_data("3Bxs4KSwSHEiNiN3")
589 },
590 stack_height: Some(2),
591 },
592 InnerInstruction{
593 instruction: CompiledInstruction{
594 program_id_index: 7,
595 accounts: vec![
596 0,
597 4
598 ],
599 data: base58_deserialize::ix_data("3Bxs4TdopiUbobUj")
600 },
601 stack_height: Some(2),
602 },
603 ],
604 }
605 ]),
606 log_messages: Some(vec![
607 "Program ComputeBudget111111111111111111111111111111 invoke [1]",
608 "Program ComputeBudget111111111111111111111111111111 success",
609 "Program MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG invoke [1]",
610 "Program log: Instruction: Buy",
611 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]",
612 "Program log: Instruction: TransferChecked",
613 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 6147 of 370747 compute units",
614 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
615 "Program 11111111111111111111111111111111 invoke [2]",
616 "Program 11111111111111111111111111111111 success",
617 "Program 11111111111111111111111111111111 invoke [2]",
618 "Program 11111111111111111111111111111111 success",
619 "Program 11111111111111111111111111111111 invoke [2]",
620 "Program 11111111111111111111111111111111 success",
621 "Program log: Transfering collateral from buyer to curve account: 1639908, Helio fee: 6560, Dex fee: 9839",
622 "Program data: vdt/007mYe5XD5Rn8AQAAOQFGQAAAAAAbyYAAAAAAACgGQAAAAAAAAAGZYutIlwKL4hMgKVUfMrwNkmY1Lx+bGF8yTqY+mFm7CM5km+SaKcGm4hX/quBhPtof2NGGMA12sQ53BrrO1WYoPAAAAAAAc/9l+YGhwgMeWNsZs3HFBi8RjvPXd5tjX5Jv9YfHhgWAAUAAAB0cmFkZQ==",
623 "Program MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG consumed 44550 of 399850 compute units",
624 "Program MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG success",
625 "Program 11111111111111111111111111111111 invoke [1]",
626 "Program 11111111111111111111111111111111 success"
627 ].into_iter().map(|s| s.to_string()).collect()),
628 pre_token_balances: Some(vec![
629 TransactionTokenBalance {
630 account_index:1,
631 mint:"3cBFsM1wosTJi9yun6kcHhYHyJcut1MNQY28zjC4moon".to_owned(),
632 ui_token_amount: UiTokenAmount {
633 ui_amount: Some(253495663.57641867),
634 decimals: 9,
635 amount: "253495663576418647".to_owned(),
636 ui_amount_string: "253495663.576418647".to_owned(),
637 },
638 owner: "4CYhuDhT4c9ATZpJceoQG8Du4vCjf5ZKvxsyXpJoVub4".to_owned(),
639 program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_owned(),
640 },
641 TransactionTokenBalance {
642 account_index:6,
643 mint:"3cBFsM1wosTJi9yun6kcHhYHyJcut1MNQY28zjC4moon".to_owned(),
644 ui_token_amount: UiTokenAmount {
645 ui_amount: Some(2266244.543486133),
646 decimals: 9,
647 amount: "2266244543486133".to_owned(),
648 ui_amount_string: "2266244.543486133".to_owned(),
649 },
650 owner: "Ezug1uk7oTEULvBcXCngdZuJDmZ8Ed2TKY4oov4GmLLm".to_owned(),
651 program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_owned(),
652 },
653 ]),
654 post_token_balances: Some(vec![
655 TransactionTokenBalance {
656 account_index:1,
657 mint:"3cBFsM1wosTJi9yun6kcHhYHyJcut1MNQY28zjC4moon".to_owned(),
658 ui_token_amount: UiTokenAmount {
659 ui_amount: Some(253490233.0),
660 decimals: 9,
661 amount: "253490233000000000".to_owned(),
662 ui_amount_string: "253490233".to_owned(),
663 },
664 owner: "4CYhuDhT4c9ATZpJceoQG8Du4vCjf5ZKvxsyXpJoVub4".to_owned(),
665 program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_owned(),
666 },
667 TransactionTokenBalance {
668 account_index:6,
669 mint:"3cBFsM1wosTJi9yun6kcHhYHyJcut1MNQY28zjC4moon".to_owned(),
670 ui_token_amount: UiTokenAmount {
671 ui_amount: Some(2271675.11990478),
672 decimals: 9,
673 amount: "2271675119904780".to_owned(),
674 ui_amount_string: "2271675.11990478".to_owned(),
675 },
676 owner: "Ezug1uk7oTEULvBcXCngdZuJDmZ8Ed2TKY4oov4GmLLm".to_owned(),
677 program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_owned(),
678 },
679 ]),
680 rewards: Some(vec![]),
681 loaded_addresses: LoadedAddresses {
682 writable: vec![],
683 readonly: vec![],
684 },
685 return_data: None,
686 compute_units_consumed: Some(44850),
687 };
688 let tx_meta_status =
690 carbon_test_utils::read_transaction_meta("tests/fixtures/simple_tx.json")
691 .expect("read fixture");
692
693 let original_tx_meta = transaction_metadata_from_original_meta(tx_meta_status)
694 .expect("transaction metadata from original meta");
695 let transaction_update = TransactionUpdate {
696 signature: Signature::default(),
697 transaction: VersionedTransaction {
698 signatures: vec![Signature::default()],
699 message: VersionedMessage::Legacy(Message {
700 header: MessageHeader::default(),
701 account_keys: vec![
702 Pubkey::from_str_const("Ezug1uk7oTEULvBcXCngdZuJDmZ8Ed2TKY4oov4GmLLm"),
703 Pubkey::from_str_const("5Zg9kJdzYFKwS4hLzF1QvvNBYyUNpn9YWxYp6HVMknJt"),
704 Pubkey::from_str_const("3udvfL24waJcLhskRAsStNMoNUvtyXdxrWQz4hgi953N"),
705 Pubkey::from_str_const("4CYhuDhT4c9ATZpJceoQG8Du4vCjf5ZKvxsyXpJoVub4"),
706 Pubkey::from_str_const("5K5RtTWzzLp4P8Npi84ocf7F1vBsAu29N1irG4iiUnzt"),
707 Pubkey::from_str_const("ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49"),
708 Pubkey::from_str_const("6FqNPPA4W1nuvL1BHGhusSHjdNa4qJBoXyRKggAh9pb9"),
709 Pubkey::from_str_const("11111111111111111111111111111111"),
710 Pubkey::from_str_const("MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG"),
711 Pubkey::from_str_const("ComputeBudget111111111111111111111111111111"),
712 Pubkey::from_str_const("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"),
713 Pubkey::from_str_const("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"),
714 Pubkey::from_str_const("36Eru7v11oU5Pfrojyn5oY3nETA1a1iqsw2WUu6afkM9"),
715 Pubkey::from_str_const("3cBFsM1wosTJi9yun6kcHhYHyJcut1MNQY28zjC4moon"),
716 ],
717 recent_blockhash: Hash::default(),
718 instructions: vec![
719 CompiledInstruction {
720 program_id_index: 9,
721 accounts: vec![],
722 data: base58_deserialize::ix_data("3GAG5eogvTjV"),
723 },
724 CompiledInstruction {
725 program_id_index: 8,
726 accounts: vec![0, 6, 3, 1, 2, 4, 5, 12, 11, 10, 7],
727 data: base58_deserialize::ix_data(
728 "XJqfG9ATWCDptdf7vx8UxGEDKxSPzetbnXg1wZsUpasa7",
729 ),
730 },
731 CompiledInstruction {
732 program_id_index: 7,
733 accounts: vec![],
734 data: base58_deserialize::ix_data("3GAG5eogvTjV"),
735 },
736 ],
737 }),
738 },
739 meta: original_tx_meta.clone(),
740 is_vote: false,
741 slot: 123,
742 block_time: Some(123),
743 block_hash: Hash::from_str("9bit9vXNX9HyHwL89aGDNmk3vbyAM96nvb6F4SaoM1CU").ok(),
744 };
745 let transaction_metadata = transaction_update
746 .clone()
747 .try_into()
748 .expect("transaction metadata");
749 let instructions_with_metadata: InstructionsWithMetadata =
750 extract_instructions_with_metadata(
751 &Arc::new(transaction_metadata),
752 &transaction_update,
753 )
754 .expect("extract instructions with metadata");
755
756 let nested_instructions: NestedInstructions = instructions_with_metadata.into();
757
758 assert_eq!(original_tx_meta, expected_tx_meta);
760 assert_eq!(nested_instructions.len(), 3);
761 assert_eq!(nested_instructions[0].inner_instructions.len(), 0);
762 assert_eq!(nested_instructions[1].inner_instructions.len(), 4);
763 assert_eq!(nested_instructions[2].inner_instructions.len(), 0);
764 }
765
766 #[test]
767 fn test_transaction_metadata_from_original_meta_cpi() {
768 let expected_tx_meta = TransactionStatusMeta {
770 status: Ok(()),
771 fee: 80000,
772 pre_balances: vec![
773 64472129,
774 2039280,
775 2039280,
776 71437440,
777 2039280,
778 1,
779 1141440,
780 7775404600,
781 117416465239,
782 731913600,
783 71437440,
784 23385600,
785 71437440,
786 7182750,
787 2039280,
788 2039280,
789 1141440,
790 1,
791 934087680,
792 4000000
793 ],
794 post_balances: vec![
795 64392129,
796 2039280,
797 2039280,
798 71437440,
799 2039280,
800 1,
801 1141440,
802 7775404600,
803 117416465239,
804 731913600,
805 71437440,
806 23385600,
807 71437440,
808 7182750,
809 2039280,
810 2039280,
811 1141440,
812 1,
813 934087680,
814 4000000
815 ],
816 cost_units: None,
817 inner_instructions: Some(vec![
818 InnerInstructions {
819 index: 3,
820 instructions: vec![
821 InnerInstruction{
822 instruction: CompiledInstruction{
823 program_id_index: 16,
824 accounts: vec![
825 13,
826 16,
827 14,
828 15,
829 1,
830 2,
831 7,
832 8,
833 11,
834 16,
835 0,
836 18,
837 18,
838 19,
839 16,
840 3,
841 10,
842 12,
843 ],
844 data: base58_deserialize::ix_data("PgQWtn8oziwqoZL8sWNwT7LtzLzAUp8MM")
845 },
846 stack_height: Some(2),
847 },
848 InnerInstruction{
849 instruction: CompiledInstruction{
850 program_id_index: 18,
851 accounts: vec![
852 1,
853 8,
854 15,
855 0,
856 ],
857 data: base58_deserialize::ix_data("gD28Qcm8qkpHv")
858 },
859 stack_height: Some(3),
860 },
861 InnerInstruction{
862 instruction: CompiledInstruction{
863 program_id_index: 18,
864 accounts: vec![
865 14,
866 7,
867 2,
868 13,
869 ],
870 data: base58_deserialize::ix_data("hLZYKissEeFUU")
871 },
872 stack_height: Some(3),
873 },
874 InnerInstruction{
875 instruction: CompiledInstruction{
876 program_id_index: 16,
877 accounts: vec![
878 19,
879 ],
880 data: base58_deserialize::ix_data("yCGxBopjnVNQkNP5usq1PonMQAFjN4WpP7MXQHZjf7XRFvuZeLCkVHy966UtS1VyTsN9u6oGPC5aaYqLj5UXxLj8FaCJccaibatRPgkX95PDrzwLBhZE43gcsTwwccBuEd67YuWJsM1j7tXo5ntSaTWRsfuaqkkoaCDDeidPunPSTBRUY68Hw5oFnYhUcG5CUEPWmM")
881 },
882 stack_height: Some(3),
883 },
884 InnerInstruction{
885 instruction: CompiledInstruction{
886 program_id_index: 18,
887 accounts: vec![
888 1,
889 8,
890 4,
891 0,
892 ],
893 data: base58_deserialize::ix_data("heASn5ozzjZrp")
894 },
895 stack_height: Some(2),
896 },
897 ],
898 }
899 ]),
900 log_messages: Some(vec![
901 "Program ComputeBudget111111111111111111111111111111 invoke [1]",
902 "Program ComputeBudget111111111111111111111111111111 success",
903 "Program ComputeBudget111111111111111111111111111111 invoke [1]",
904 "Program ComputeBudget111111111111111111111111111111 success",
905 "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL invoke [1]",
906 "Program log: CreateIdempotent",
907 "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL consumed 4338 of 191700 compute units",
908 "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL success",
909 "Program 6m2CDdhRgxpH4WjvdzxAYbGxwdGUz5MziiL5jek2kBma invoke [1]",
910 "Program log: Instruction: CommissionSplSwap2",
911 "Program log: order_id: 105213",
912 "Program log: AFbX8oGjGpmVFywbVouvhQSRmiW2aR1mohfahi4Y2AdB",
913 "Program log: 7i5KKsX2weiTkry7jA4ZwSuXGhs5eJBEjY8vVxR4pfRx",
914 "Program log: 7Z2QzVa3q7r7m84nuez9eRn2u3oCUeg9D1bzdRvNFdxN",
915 "Program log: 7Z2QzVa3q7r7m84nuez9eRn2u3oCUeg9D1bzdRvNFdxN",
916 "Program log: before_source_balance: 9452950000000, before_destination_balance: 573930799366, amount_in: 9372599925000, expect_amount_out: 1700985700449, min_return: 1680573872044",
917 "Program log: Dex::MeteoraDlmm amount_in: 9372599925000, offset: 0",
918 "Program log: BMCheVSdKZ6rxsoJ1MChA5HQRtk5pz4QkCLs7MXFkvZJ",
919 "Program LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo invoke [2]",
920 "Program log: Instruction: Swap",
921 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]",
922 "Program log: Instruction: TransferChecked",
923 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 6173 of 112532 compute units",
924 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
925 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]",
926 "Program log: Instruction: TransferChecked",
927 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 6147 of 102925 compute units",
928 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
929 "Program LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo invoke [3]",
930 "Program LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo consumed 2134 of 93344 compute units",
931 "Program LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo success",
932 "Program LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo consumed 63904 of 153546 compute units",
933 "Program LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo success",
934 "Program data: QMbN6CYIceINCDl9OoYIAABhAKYKjAEAAA==",
935 "Program log: SwapEvent { dex: MeteoraDlmm, amount_in: 9372599925000, amount_out: 1700985700449 }",
936 "Program log: 6VRWsRGxnJFg7y4ck3NBsBPQ5SLkCtkH3tTioJkEby3b",
937 "Program log: AADrz4o64xxynMr8tochpqAYevmySvsEhjAgKEXNcjnd",
938 "Program log: after_source_balance: 80350075000, after_destination_balance: 2274916499815, source_token_change: 9372599925000, destination_token_change: 1700985700449",
939 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]",
940 "Program log: Instruction: TransferChecked",
941 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 6173 of 76109 compute units",
942 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
943 "Program log: commission_direction: true, commission_amount: 80350075000",
944 "Program 6m2CDdhRgxpH4WjvdzxAYbGxwdGUz5MziiL5jek2kBma consumed 118873 of 187362 compute units",
945 "Program 6m2CDdhRgxpH4WjvdzxAYbGxwdGUz5MziiL5jek2kBma success"
946 ].into_iter().map(|s| s.to_string()).collect()),
947 pre_token_balances: Some(vec![
948 TransactionTokenBalance {
949 account_index:1,
950 mint:"AFbX8oGjGpmVFywbVouvhQSRmiW2aR1mohfahi4Y2AdB".to_owned(),
951 ui_token_amount: UiTokenAmount {
952 ui_amount: Some(9452.95),
953 decimals: 9,
954 amount: "9452950000000".to_owned(),
955 ui_amount_string: "9452.95".to_owned(),
956 },
957 owner: "7Z2QzVa3q7r7m84nuez9eRn2u3oCUeg9D1bzdRvNFdxN".to_owned(),
958 program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_owned(),
959 },
960 TransactionTokenBalance {
961 account_index:2,
962 mint:"7i5KKsX2weiTkry7jA4ZwSuXGhs5eJBEjY8vVxR4pfRx".to_owned(),
963 ui_token_amount: UiTokenAmount {
964 ui_amount: Some(573.930799366),
965 decimals: 9,
966 amount: "573930799366".to_owned(),
967 ui_amount_string: "573.930799366".to_owned(),
968 },
969 owner: "7Z2QzVa3q7r7m84nuez9eRn2u3oCUeg9D1bzdRvNFdxN".to_owned(),
970 program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_owned(),
971 },
972 TransactionTokenBalance {
973 account_index:4,
974 mint:"AFbX8oGjGpmVFywbVouvhQSRmiW2aR1mohfahi4Y2AdB".to_owned(),
975 ui_token_amount: UiTokenAmount {
976 ui_amount: Some(357.387320573),
977 decimals: 9,
978 amount: "357387320573".to_owned(),
979 ui_amount_string: "357.387320573".to_owned(),
980 },
981 owner: "8psNvWTrdNTiVRNzAgsou9kETXNJm2SXZyaKuJraVRtf".to_owned(),
982 program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_owned(),
983 },
984 TransactionTokenBalance {
985 account_index:14,
986 mint:"7i5KKsX2weiTkry7jA4ZwSuXGhs5eJBEjY8vVxR4pfRx".to_owned(),
987 ui_token_amount: UiTokenAmount {
988 ui_amount: Some(108469.853556668),
989 decimals: 9,
990 amount: "108469853556668".to_owned(),
991 ui_amount_string: "108469.853556668".to_owned(),
992 },
993 owner: "BMCheVSdKZ6rxsoJ1MChA5HQRtk5pz4QkCLs7MXFkvZJ".to_owned(),
994 program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_owned(),
995 },
996 TransactionTokenBalance {
997 account_index:15,
998 mint:"AFbX8oGjGpmVFywbVouvhQSRmiW2aR1mohfahi4Y2AdB".to_owned(),
999 ui_token_amount: UiTokenAmount {
1000 ui_amount: Some(176698.438078034),
1001 decimals: 9,
1002 amount: "176698438078034".to_owned(),
1003 ui_amount_string: "176698.438078034".to_owned(),
1004 },
1005 owner: "BMCheVSdKZ6rxsoJ1MChA5HQRtk5pz4QkCLs7MXFkvZJ".to_owned(),
1006 program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_owned(),
1007 },
1008 ]),
1009 post_token_balances: Some(vec![
1010 TransactionTokenBalance {
1011 account_index:1,
1012 mint:"AFbX8oGjGpmVFywbVouvhQSRmiW2aR1mohfahi4Y2AdB".to_owned(),
1013 ui_token_amount: UiTokenAmount {
1014 ui_amount: None,
1015 decimals: 9,
1016 amount: "0".to_owned(),
1017 ui_amount_string: "0".to_owned(),
1018 },
1019 owner: "7Z2QzVa3q7r7m84nuez9eRn2u3oCUeg9D1bzdRvNFdxN".to_owned(),
1020 program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_owned(),
1021 },
1022 TransactionTokenBalance {
1023 account_index:2,
1024 mint:"7i5KKsX2weiTkry7jA4ZwSuXGhs5eJBEjY8vVxR4pfRx".to_owned(),
1025 ui_token_amount: UiTokenAmount {
1026 ui_amount: Some( 2274.916499815),
1027 decimals: 9,
1028 amount: "2274916499815".to_owned(),
1029 ui_amount_string: "2274.916499815".to_owned(),
1030 },
1031 owner: "7Z2QzVa3q7r7m84nuez9eRn2u3oCUeg9D1bzdRvNFdxN".to_owned(),
1032 program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_owned(),
1033 },
1034 TransactionTokenBalance {
1035 account_index:4,
1036 mint:"AFbX8oGjGpmVFywbVouvhQSRmiW2aR1mohfahi4Y2AdB".to_owned(),
1037 ui_token_amount: UiTokenAmount {
1038 ui_amount: Some(437.737395573),
1039 decimals: 9,
1040 amount: "437737395573".to_owned(),
1041 ui_amount_string: "437.737395573".to_owned(),
1042 },
1043 owner: "8psNvWTrdNTiVRNzAgsou9kETXNJm2SXZyaKuJraVRtf".to_owned(),
1044 program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_owned(),
1045 },
1046 TransactionTokenBalance {
1047 account_index:14,
1048 mint:"7i5KKsX2weiTkry7jA4ZwSuXGhs5eJBEjY8vVxR4pfRx".to_owned(),
1049 ui_token_amount: UiTokenAmount {
1050 ui_amount: Some(106768.867856219),
1051 decimals: 9,
1052 amount: "106768867856219".to_owned(),
1053 ui_amount_string: "106768.867856219".to_owned(),
1054 },
1055 owner: "BMCheVSdKZ6rxsoJ1MChA5HQRtk5pz4QkCLs7MXFkvZJ".to_owned(),
1056 program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_owned(),
1057 },
1058 TransactionTokenBalance {
1059 account_index:15,
1060 mint:"AFbX8oGjGpmVFywbVouvhQSRmiW2aR1mohfahi4Y2AdB".to_owned(),
1061 ui_token_amount: UiTokenAmount {
1062 ui_amount: Some(186071.038003034),
1063 decimals: 9,
1064 amount: "186071038003034".to_owned(),
1065 ui_amount_string: "186071.038003034".to_owned(),
1066 },
1067 owner: "BMCheVSdKZ6rxsoJ1MChA5HQRtk5pz4QkCLs7MXFkvZJ".to_owned(),
1068 program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_owned(),
1069 },
1070 ]),
1071 rewards: Some(vec![]),
1072 loaded_addresses: LoadedAddresses {
1073 writable: vec![
1074 Pubkey::from_str_const("12QvTU4Z7XdxT16mSYU8rE2n9CpXpvunHiZrfySaf7h8"),
1075 Pubkey::from_str_const("76TaSYC4LuopNGf5apJUXMG2MDfcQMHiw6SMX93VYQGp"),
1076 Pubkey::from_str_const("7q4JPakWqK7UrjRsTNoYXMNeBYKP3WKawNcHYzidTaav"),
1077 Pubkey::from_str_const("BMCheVSdKZ6rxsoJ1MChA5HQRtk5pz4QkCLs7MXFkvZJ"),
1078 Pubkey::from_str_const("GqXFYwijuNaKRQgtrVrJkDGXorPcZwX7Vyd4jDsuxW9J"),
1079 Pubkey::from_str_const("JBbKXBC4yBco9BfChDaf5GHd8hyLbjASeLxFCwGCH99a"),
1080 Pubkey::from_str_const("LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo")
1081 ],
1082 readonly: vec![
1083 Pubkey::from_str_const("11111111111111111111111111111111"),
1084 Pubkey::from_str_const("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"),
1085 Pubkey::from_str_const("D1ZN9Wj1fRSUQfCjhvnu1hqDMT7hzjzBBpi12nVniYD6"),
1086 ],
1087 },
1088 return_data: None,
1089 compute_units_consumed: Some(123511),
1090 };
1091
1092 let tx_meta_status = carbon_test_utils::read_transaction_meta("tests/fixtures/cpi_tx.json")
1094 .expect("read fixture");
1095 let original_tx_meta = transaction_metadata_from_original_meta(tx_meta_status)
1096 .expect("transaction metadata from original meta");
1097 let transaction_update = TransactionUpdate {
1098 signature: Signature::default(),
1099 transaction: VersionedTransaction {
1100 signatures: vec![Signature::default()],
1101 message: VersionedMessage::V0(v0::Message {
1102 header: MessageHeader::default(),
1103 account_keys: vec![
1104 Pubkey::from_str_const("7Z2QzVa3q7r7m84nuez9eRn2u3oCUeg9D1bzdRvNFdxN"),
1105 Pubkey::from_str_const("6VRWsRGxnJFg7y4ck3NBsBPQ5SLkCtkH3tTioJkEby3b"),
1106 Pubkey::from_str_const("AADrz4o64xxynMr8tochpqAYevmySvsEhjAgKEXNcjnd"),
1107 Pubkey::from_str_const("EuGVLjHv1K1YVxmcukLYMBjGB7YXy5hxbJ2z4LeDLUfQ"),
1108 Pubkey::from_str_const("FDxb6WnUHrSsz9zwKqneC5JXVmrRgPySBjf6WfNfFyrM"),
1109 Pubkey::from_str_const("ComputeBudget111111111111111111111111111111"),
1110 Pubkey::from_str_const("6m2CDdhRgxpH4WjvdzxAYbGxwdGUz5MziiL5jek2kBma"),
1111 Pubkey::from_str_const("7i5KKsX2weiTkry7jA4ZwSuXGhs5eJBEjY8vVxR4pfRx"),
1112 Pubkey::from_str_const("AFbX8oGjGpmVFywbVouvhQSRmiW2aR1mohfahi4Y2AdB"),
1113 Pubkey::from_str_const("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"),
1114 Pubkey::from_str_const("12QvTU4Z7XdxT16mSYU8rE2n9CpXpvunHiZrfySaf7h8"),
1115 Pubkey::from_str_const("76TaSYC4LuopNGf5apJUXMG2MDfcQMHiw6SMX93VYQGp"),
1116 Pubkey::from_str_const("7q4JPakWqK7UrjRsTNoYXMNeBYKP3WKawNcHYzidTaav"),
1117 Pubkey::from_str_const("BMCheVSdKZ6rxsoJ1MChA5HQRtk5pz4QkCLs7MXFkvZJ"),
1118 Pubkey::from_str_const("GqXFYwijuNaKRQgtrVrJkDGXorPcZwX7Vyd4jDsuxW9J"),
1119 Pubkey::from_str_const("JBbKXBC4yBco9BfChDaf5GHd8hyLbjASeLxFCwGCH99a"),
1120 Pubkey::from_str_const("LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo"),
1121 Pubkey::from_str_const("11111111111111111111111111111111"),
1122 Pubkey::from_str_const("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"),
1123 Pubkey::from_str_const("D1ZN9Wj1fRSUQfCjhvnu1hqDMT7hzjzBBpi12nVniYD6"),
1124 ],
1125 recent_blockhash: Hash::default(),
1126 instructions: vec![
1127 CompiledInstruction {
1128 program_id_index: 5,
1129 accounts: vec![],
1130 data: base58_deserialize::ix_data("3GAG5eogvTjV"),
1131 },
1132 CompiledInstruction {
1133 program_id_index: 5,
1134 accounts: vec![],
1135 data: base58_deserialize::ix_data("3GAG5eogvTjV"),
1136 },
1137 CompiledInstruction {
1138 program_id_index: 9,
1139 accounts: vec![],
1140 data: base58_deserialize::ix_data("3GAG5eogvTjV"),
1141 },
1142 CompiledInstruction {
1143 program_id_index: 8,
1144 accounts: vec![],
1145 data: base58_deserialize::ix_data(
1146 "XJqfG9ATWCDptdf7vx8UxGEDKxSPzetbnXg1wZsUpasa7",
1147 ),
1148 },
1149 ],
1150 address_table_lookups: vec![
1151 MessageAddressTableLookup {
1152 account_key: Pubkey::from_str_const(
1153 "FfMiwAdZeeSZyuApu5fsCuPzvyAyKdEbNcmEVEEhgJAW",
1154 ),
1155 writable_indexes: vec![0, 1, 2, 3, 4, 5],
1156 readonly_indexes: vec![],
1157 },
1158 MessageAddressTableLookup {
1159 account_key: Pubkey::from_str_const(
1160 "EDDSpjZHrsFKYTMJDcBqXAjkLcu9EKdvrQR4XnqsXErH",
1161 ),
1162 writable_indexes: vec![0],
1163 readonly_indexes: vec![3, 4, 5],
1164 },
1165 ],
1166 }),
1167 },
1168 meta: original_tx_meta.clone(),
1169 is_vote: false,
1170 slot: 123,
1171 block_time: Some(123),
1172 block_hash: None,
1173 };
1174 let transaction_metadata = transaction_update
1175 .clone()
1176 .try_into()
1177 .expect("transaction metadata");
1178 let instructions_with_metadata: InstructionsWithMetadata =
1179 extract_instructions_with_metadata(
1180 &Arc::new(transaction_metadata),
1181 &transaction_update,
1182 )
1183 .expect("extract instructions with metadata");
1184 let nested_instructions: NestedInstructions = instructions_with_metadata.into();
1185
1186 assert_eq!(original_tx_meta, expected_tx_meta);
1188 assert_eq!(nested_instructions.len(), 4);
1189 assert_eq!(nested_instructions[0].inner_instructions.len(), 0);
1190 assert_eq!(nested_instructions[1].inner_instructions.len(), 0);
1191 assert_eq!(nested_instructions[2].inner_instructions.len(), 0);
1192 assert_eq!(nested_instructions[3].inner_instructions.len(), 2);
1193 }
1194}