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