carbon_core/
transformers.rs

1//! Provides utility functions to transform transaction data into various
2//! representations within the `carbon-core` framework.
3//!
4//! This module includes functions for extracting transaction metadata, parsing
5//! instructions, and nesting instructions based on stack depth. It also offers
6//! transformations for Solana transaction components into suitable formats for
7//! the framework, enabling flexible processing of transaction data.
8//!
9//! ## Key Components
10//!
11//! - **Metadata Extraction**: Extracts essential transaction metadata for
12//!   processing.
13//! - **Instruction Parsing**: Parses both top-level and nested instructions
14//!   from transactions.
15//! - **Account Metadata**: Converts account data into a standardized format for
16//!   transactions.
17//!
18//! ## Notes
19//!
20//! - The module supports both legacy and v0 transactions, including handling of
21//!   loaded addresses and inner instructions.
22
23use {
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
47/// Extracts instructions with metadata from a transaction update.
48///
49/// This function parses both top-level and inner instructions, associating them
50/// with metadata such as stack height and account information. It provides a
51/// detailed breakdown of each instruction, useful for further processing.
52///
53/// # Parameters
54///
55/// - `transaction_metadata`: Metadata about the transaction from which
56///   instructions are extracted.
57/// - `transaction_update`: The `TransactionUpdate` containing the transaction's
58///   data and message.
59///
60/// # Returns
61///
62/// A `CarbonResult<Vec<(InstructionMetadata,
63/// solana_instruction::Instruction)>>` containing instructions along with
64/// their associated metadata.
65///
66/// # Errors
67///
68/// Returns an error if any account metadata required for instruction processing
69/// is missing.
70pub 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
218/// Extracts account metadata from a compiled instruction and transaction
219/// message.
220///
221/// This function converts each account index within the instruction into an
222/// `AccountMeta` struct, providing details on account keys, signer status, and
223/// write permissions.
224///
225/// # Parameters
226///
227/// - `compiled_instruction`: The compiled instruction to extract accounts from.
228/// - `message`: The transaction message containing the account keys.
229///
230/// # Returns
231///
232/// A `CarbonResult<&[AccountMeta]>` containing
233/// metadata for each account involved in the instruction.
234///
235/// # Errors
236///
237/// Returns an error if any referenced account key is missing from the
238/// transaction.
239pub 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
273/// Unnests parsed instructions, producing an array of `(InstructionMetadata,
274/// DecodedInstruction<T>)` tuple
275///
276/// This function takes a vector of `ParsedInstruction` and unnests them into a
277/// vector of `(InstructionMetadata, DecodedInstruction<T>)` tuples.
278/// It recursively processes nested instructions, increasing the stack height
279/// for each level of nesting.
280///
281/// # Parameters
282///
283/// - `transaction_metadata`: The metadata of the transaction containing the
284///   instructions.
285/// - `instructions`: The vector of `ParsedInstruction` to be unnested.
286/// - `stack_height`: The current stack height.
287///
288/// # Returns
289///
290/// A vector of `(InstructionMetadata, DecodedInstruction<T>)` tuples
291/// representing the unnested instructions.
292pub 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
324/// Converts UI transaction metadata into `TransactionStatusMeta`.
325///
326/// This function transforms the user interface format of transaction metadata
327/// into a more comprehensive `TransactionStatusMeta` structure suitable for
328/// backend processing.
329///
330/// # Parameters
331///
332/// - `meta_original`: The original UI format of transaction status metadata.
333///
334/// # Returns
335///
336/// A `CarbonResult<TransactionStatusMeta>` representing the full transaction
337/// status with nested instructions, token balances, and rewards.
338///
339/// # Notes
340///
341/// This function handles various metadata fields, including inner instructions,
342/// token balances, and rewards, providing a complete view of the transaction's
343/// effects.
344pub 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        // Arrange
517        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        // Act
689        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
759        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        // Arrange
769        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        // Act
1093        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
1187        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}