Skip to main content

solana_transaction_context/
transaction.rs

1#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
2use {
3    crate::{
4        DropOnBailOut, IndexOfAccount, MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION,
5        MAX_ACCOUNT_DATA_LEN, MAX_ACCOUNTS_PER_TRANSACTION,
6        instruction::{InstructionContext, InstructionFrame},
7        transaction_accounts::{KeyedAccountSharedData, TransactionAccounts},
8        vm_addresses::{
9            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS, GUEST_INSTRUCTION_DATA_BASE_ADDRESS,
10            GUEST_REGION_SIZE, RETURN_DATA_SCRATCHPAD,
11        },
12    },
13    solana_account::{AccountSharedData, ReadableAccount, WritableAccount},
14    solana_instruction_error::InstructionError,
15    solana_instructions_sysvar as instructions,
16    solana_rent::Rent,
17    solana_sbpf::memory_region::{AccessType, AccessViolationHandler, MemoryRegion},
18    std::{borrow::Cow, cell::Cell, rc::Rc},
19};
20use {
21    crate::{instruction_accounts::InstructionAccount, vm_slice::VmSlice},
22    solana_pubkey::Pubkey,
23};
24
25/// Used only in fn `take_instruction_trace` for deconstructing TransactionContext
26pub type InstructionTrace<'ix_data> = (
27    Vec<InstructionFrame>,
28    Vec<Box<[InstructionAccount]>>,
29    Vec<Cow<'ix_data, [u8]>>,
30);
31
32/// This data structure is shared with programs in ABIv2, providing information about the
33/// transaction metadata.
34///
35/// Modifications without a feature gate and proper versioning might break programs.
36#[repr(C)]
37#[derive(Debug)]
38struct TransactionFrame {
39    /// Pubkey of the last program to write to the return data scratchpad
40    return_data_pubkey: Pubkey,
41    return_data_scratchpad: VmSlice<u8>,
42    /// Scratchpad for programs to write CPI instruction data
43    pub cpi_data_scratchpad: VmSlice<u8>,
44    /// Scratchpad for programs to write CPI accounts
45    pub cpi_accounts_scratchpad: VmSlice<InstructionAccount>,
46    /// Index of current executing instruction
47    current_executing_instruction: u16,
48    /// Number of instructions in the instruction trace (including top level and CPIs)
49    total_number_of_instructions_in_trace: u16,
50    /// Number of CPIs in the instruction trace
51    number_of_cpis_in_trace: u16,
52    /// Number of transaction accounts
53    number_of_transaction_accounts: u16,
54}
55
56/// Loaded transaction shared between runtime and programs.
57///
58/// This context is valid for the entire duration of a transaction being processed.
59#[derive(Debug)]
60#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
61pub struct TransactionContext<'ix_data> {
62    pub(crate) accounts: Rc<TransactionAccounts>,
63    instruction_stack_capacity: usize,
64    instruction_trace_capacity: usize,
65    instruction_stack: Vec<usize>,
66    instruction_trace: Vec<InstructionFrame>,
67    transaction_frame: TransactionFrame,
68    return_data_bytes: Vec<u8>,
69    next_top_level_instruction_index: usize,
70    #[cfg(not(target_os = "solana"))]
71    pub(crate) rent: Rent,
72    /// This is an account deduplication map that maps index_in_transaction to index_in_instruction
73    /// Usage: dedup_map[index_in_transaction] = index_in_instruction
74    /// Each entry in `deduplication_maps` represents the deduplication map for each instruction.
75    deduplication_maps: Vec<Box<[u8]>>,
76    /// Each entry in `instruction_accounts` represents the array of accounts for each instruction.
77    instruction_accounts: Vec<Box<[InstructionAccount]>>,
78    /// Each entry in `instruction_data` represents the data for instruction at the corresponding
79    /// index.
80    instruction_data: Vec<Cow<'ix_data, [u8]>>,
81}
82
83#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
84impl<'ix_data> TransactionContext<'ix_data> {
85    /// Constructs a new TransactionContext
86    pub fn new_with_feature_flags(
87        transaction_accounts: Vec<KeyedAccountSharedData>,
88        rent: Rent,
89        instruction_stack_capacity: usize,
90        instruction_trace_capacity: usize,
91        number_of_top_level_instructions: usize,
92        drop_on_bail_out: DropOnBailOut,
93    ) -> Self {
94        let transaction_frame = TransactionFrame {
95            return_data_pubkey: Pubkey::default(),
96            return_data_scratchpad: VmSlice::new(RETURN_DATA_SCRATCHPAD, 0),
97            cpi_data_scratchpad: VmSlice::new(
98                GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(
99                    GUEST_REGION_SIZE.saturating_mul(number_of_top_level_instructions as u64),
100                ),
101                0,
102            ),
103            cpi_accounts_scratchpad: VmSlice::new(
104                GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS.saturating_add(
105                    GUEST_REGION_SIZE.saturating_mul(number_of_top_level_instructions as u64),
106                ),
107                0,
108            ),
109            current_executing_instruction: 0,
110            total_number_of_instructions_in_trace: number_of_top_level_instructions as u16,
111            number_of_cpis_in_trace: 0,
112            number_of_transaction_accounts: transaction_accounts.len() as u16,
113        };
114
115        // We need an extra space for the placeholder, so we avoid relocations.
116        let mut instruction_trace =
117            Vec::with_capacity(instruction_trace_capacity.saturating_add(1));
118        instruction_trace.resize_with(
119            number_of_top_level_instructions.saturating_add(1),
120            InstructionFrame::default,
121        );
122
123        Self {
124            accounts: Rc::new(TransactionAccounts::new_with_feature_flags(
125                transaction_accounts,
126                drop_on_bail_out,
127            )),
128            instruction_stack_capacity,
129            instruction_trace_capacity,
130            instruction_stack: Vec::with_capacity(instruction_stack_capacity),
131            instruction_trace,
132            return_data_bytes: Vec::new(),
133            transaction_frame,
134            next_top_level_instruction_index: 0,
135            rent,
136            instruction_accounts: Vec::with_capacity(instruction_trace_capacity),
137            deduplication_maps: Vec::with_capacity(instruction_trace_capacity),
138            instruction_data: Vec::with_capacity(instruction_trace_capacity),
139        }
140    }
141
142    /// Constructs a new TransactionContext with all features active
143    #[cfg(feature = "dev-context-only-utils")]
144    pub fn new(
145        transaction_accounts: Vec<KeyedAccountSharedData>,
146        rent: Rent,
147        instruction_stack_capacity: usize,
148        instruction_trace_capacity: usize,
149        number_of_top_level_instructions: usize,
150    ) -> Self {
151        Self::new_with_feature_flags(
152            transaction_accounts,
153            rent,
154            instruction_stack_capacity,
155            instruction_trace_capacity,
156            number_of_top_level_instructions,
157            DropOnBailOut::Disabled,
158        )
159    }
160
161    /// Used in mock_process_instruction
162    pub fn deconstruct_without_keys(self) -> Result<Vec<AccountSharedData>, InstructionError> {
163        if !self.instruction_stack.is_empty() {
164            return Err(InstructionError::CallDepth);
165        }
166
167        let accounts = Rc::try_unwrap(self.accounts)
168            .expect("transaction_context.accounts has unexpected outstanding refs")
169            .deconstruct_into_account_shared_data();
170
171        Ok(accounts)
172    }
173
174    pub fn accounts(&self) -> &Rc<TransactionAccounts> {
175        &self.accounts
176    }
177
178    /// Returns the total number of accounts loaded in this Transaction
179    pub fn get_number_of_accounts(&self) -> IndexOfAccount {
180        self.accounts.len() as IndexOfAccount
181    }
182
183    /// Searches for an account by its key
184    pub fn get_key_of_account_at_index(
185        &self,
186        index_in_transaction: IndexOfAccount,
187    ) -> Result<&Pubkey, InstructionError> {
188        self.accounts
189            .account_key(index_in_transaction)
190            .ok_or(InstructionError::MissingAccount)
191    }
192
193    /// Searches for an account by its key
194    pub fn find_index_of_account(&self, pubkey: &Pubkey) -> Option<IndexOfAccount> {
195        self.accounts
196            .account_keys_iter()
197            .position(|key| key == pubkey)
198            .map(|index| index as IndexOfAccount)
199    }
200
201    /// Gets the max length of the instruction trace
202    pub fn get_instruction_trace_capacity(&self) -> usize {
203        self.instruction_trace_capacity
204    }
205
206    /// Returns the instruction trace length.
207    ///
208    /// Not counting the last empty instruction which is always pre-reserved for the next instruction.
209    pub fn get_instruction_trace_length(&self) -> usize {
210        self.instruction_trace.len().saturating_sub(1)
211    }
212
213    /// Gets a view on an instruction by its index in the trace
214    pub fn get_instruction_context_at_index_in_trace(
215        &self,
216        index_in_trace: usize,
217    ) -> Result<InstructionContext<'_, '_>, InstructionError> {
218        let instruction = self
219            .instruction_trace
220            .get(index_in_trace)
221            .ok_or(InstructionError::CallDepth)?;
222
223        // These commands will return a default empty slice if we are retrieving an instruction
224        // that hasn't been configured yet.
225        let instruction_accounts = self
226            .instruction_accounts
227            .get(index_in_trace)
228            .map(|item| item.as_ref())
229            .unwrap_or_default();
230        let dedup_map = self
231            .deduplication_maps
232            .get(index_in_trace)
233            .map(|item| item.as_ref())
234            .unwrap_or_default();
235        let instruction_data = self
236            .instruction_data
237            .get(index_in_trace)
238            .map(|item| item.as_ref())
239            .unwrap_or_default();
240        Ok(InstructionContext {
241            transaction_context: self,
242            index_in_trace,
243            nesting_level: instruction.nesting_level as usize,
244            program_account_index_in_tx: instruction.program_account_index_in_tx as IndexOfAccount,
245            instruction_accounts,
246            dedup_map,
247            instruction_data,
248            index_of_caller_instruction: instruction.index_of_caller_instruction as usize,
249        })
250    }
251
252    /// Gets a view on the instruction by its nesting level in the stack
253    pub fn get_instruction_context_at_nesting_level(
254        &self,
255        nesting_level: usize,
256    ) -> Result<InstructionContext<'_, '_>, InstructionError> {
257        let index_in_trace = *self
258            .instruction_stack
259            .get(nesting_level)
260            .ok_or(InstructionError::CallDepth)?;
261        let instruction_context = self.get_instruction_context_at_index_in_trace(index_in_trace)?;
262        debug_assert_eq!(instruction_context.nesting_level, nesting_level);
263        Ok(instruction_context)
264    }
265
266    /// Gets the max height of the instruction stack
267    pub fn get_instruction_stack_capacity(&self) -> usize {
268        self.instruction_stack_capacity
269    }
270
271    /// Gets instruction stack height, top-level instructions are height
272    /// `solana_instruction::TRANSACTION_LEVEL_STACK_HEIGHT`
273    pub fn get_instruction_stack_height(&self) -> usize {
274        self.instruction_stack.len()
275    }
276
277    /// Returns the index in the instruction trace of the current executing instruction
278    pub fn get_current_instruction_index(&self) -> Result<usize, InstructionError> {
279        self.instruction_stack
280            .last()
281            .copied()
282            .ok_or(InstructionError::CallDepth)
283    }
284
285    /// Returns a view on the current instruction
286    pub fn get_current_instruction_context(
287        &self,
288    ) -> Result<InstructionContext<'_, '_>, InstructionError> {
289        let index_in_trace = self.get_current_instruction_index()?;
290        self.get_instruction_context_at_index_in_trace(index_in_trace)
291    }
292
293    /// Returns a view on the next instruction. This function assumes it has already been
294    /// configured with the correct values in `prepare_next_instruction` or
295    /// `prepare_next_top_level_instruction`
296    pub fn get_next_instruction_context(
297        &self,
298    ) -> Result<InstructionContext<'_, '_>, InstructionError> {
299        let index_in_trace = if self.instruction_stack.is_empty() {
300            self.next_top_level_instruction_index
301        } else {
302            self.instruction_trace
303                .len()
304                .checked_sub(1)
305                .ok_or(InstructionError::CallDepth)?
306        };
307        self.get_instruction_context_at_index_in_trace(index_in_trace)
308    }
309
310    /// Configures an instruction at a specific index in trace.
311    pub fn configure_instruction_at_index(
312        &mut self,
313        instruction_index: usize,
314        program_index: IndexOfAccount,
315        instruction_accounts: Vec<InstructionAccount>,
316        deduplication_map: Vec<u8>,
317        instruction_data: Cow<'ix_data, [u8]>,
318        caller_index: Option<u16>,
319    ) -> Result<(), InstructionError> {
320        debug_assert_eq!(
321            deduplication_map.len(),
322            usize::from(self.get_number_of_accounts()).min(MAX_ACCOUNTS_PER_TRANSACTION)
323        );
324
325        let instruction = self
326            .instruction_trace
327            .get_mut(instruction_index)
328            .ok_or(InstructionError::MaxInstructionTraceLengthExceeded)?;
329
330        // If we have a parent index, then we are dealing with a CPI.
331        if let Some(caller_index) = caller_index {
332            self.transaction_frame.total_number_of_instructions_in_trace = self
333                .transaction_frame
334                .total_number_of_instructions_in_trace
335                .saturating_add(1);
336            instruction.index_of_caller_instruction = caller_index;
337            let next_data_ptr = self
338                .transaction_frame
339                .cpi_data_scratchpad
340                .ptr()
341                .saturating_add(GUEST_REGION_SIZE);
342            self.transaction_frame.cpi_data_scratchpad = VmSlice::new(next_data_ptr, 0);
343            let next_accounts_ptr = self
344                .transaction_frame
345                .cpi_accounts_scratchpad
346                .ptr()
347                .saturating_add(GUEST_REGION_SIZE);
348            self.transaction_frame.cpi_accounts_scratchpad = VmSlice::new(next_accounts_ptr, 0);
349        }
350
351        instruction.program_account_index_in_tx = program_index;
352        instruction.configure_vm_slices(
353            instruction_index as u64,
354            instruction_accounts.len(),
355            instruction_data.len() as u64,
356        );
357        self.deduplication_maps
358            .push(deduplication_map.into_boxed_slice());
359        self.instruction_accounts
360            .push(instruction_accounts.into_boxed_slice());
361        self.instruction_data.push(instruction_data);
362        Ok(())
363    }
364
365    /// For tests only
366    fn deduplicate_accounts_for_tests(
367        &self,
368        instruction_accounts: &[InstructionAccount],
369    ) -> Vec<u8> {
370        let mut dedup_map = vec![
371            u8::MAX;
372            usize::from(self.get_number_of_accounts())
373                .min(MAX_ACCOUNTS_PER_TRANSACTION)
374        ];
375        for (idx, account) in instruction_accounts.iter().enumerate() {
376            let index_in_instruction = dedup_map
377                .get_mut(account.index_in_transaction as usize)
378                .unwrap();
379            if *index_in_instruction == u8::MAX {
380                *index_in_instruction = idx as u8;
381            }
382        }
383        dedup_map
384    }
385
386    /// A version of `configure_top_level_instruction` to help creating the deduplication map in tests
387    pub fn configure_top_level_instruction_for_tests(
388        &mut self,
389        program_index: IndexOfAccount,
390        instruction_accounts: Vec<InstructionAccount>,
391        instruction_data: Vec<u8>,
392    ) -> Result<(), InstructionError> {
393        debug_assert!(instruction_accounts.len() <= u8::MAX as usize);
394        let dedup_map = self.deduplicate_accounts_for_tests(&instruction_accounts);
395
396        self.configure_instruction_at_index(
397            self.next_top_level_instruction_index,
398            program_index,
399            instruction_accounts,
400            dedup_map,
401            Cow::Owned(instruction_data),
402            None,
403        )?;
404        Ok(())
405    }
406
407    /// A helper function to facilitate creating a CPI in tests
408    pub fn configure_next_cpi_for_tests(
409        &mut self,
410        program_index: IndexOfAccount,
411        instruction_accounts: Vec<InstructionAccount>,
412        instruction_data: Vec<u8>,
413    ) -> Result<(), InstructionError> {
414        debug_assert!(instruction_accounts.len() <= u8::MAX as usize);
415        let dedup_map = self.deduplicate_accounts_for_tests(&instruction_accounts);
416        let caller_index = self.get_current_instruction_index()?;
417        let cpi_index = self.get_instruction_trace_length();
418        self.configure_instruction_at_index(
419            cpi_index,
420            program_index,
421            instruction_accounts,
422            dedup_map,
423            Cow::Owned(instruction_data),
424            Some(caller_index as u16),
425        )?;
426        Ok(())
427    }
428
429    /// Pushes the next instruction
430    pub fn push(&mut self) -> Result<(), InstructionError> {
431        let nesting_level = self.get_instruction_stack_height();
432        if !self.instruction_stack.is_empty() && self.accounts.get_lamports_delta() != 0 {
433            return Err(InstructionError::UnbalancedInstruction);
434        }
435        {
436            let instruction = self
437                .instruction_trace
438                .last_mut()
439                .ok_or(InstructionError::CallDepth)?;
440            instruction.nesting_level = nesting_level as u16;
441        }
442
443        if self.number_of_called_instructions_in_trace() >= self.instruction_trace_capacity {
444            return Err(InstructionError::MaxInstructionTraceLengthExceeded);
445        }
446
447        let (index_in_trace, current_top_level_instruction) = if self.instruction_stack.is_empty() {
448            let index = self.next_top_level_instruction_index;
449            self.next_top_level_instruction_index =
450                self.next_top_level_instruction_index.saturating_add(1);
451            (index, index)
452        } else {
453            let index = self.get_instruction_trace_length();
454            self.transaction_frame.number_of_cpis_in_trace = self
455                .transaction_frame
456                .number_of_cpis_in_trace
457                .saturating_add(1);
458            self.instruction_trace.push(InstructionFrame::default());
459            (
460                index,
461                self.next_top_level_instruction_index.saturating_sub(1),
462            )
463        };
464
465        if nesting_level >= self.instruction_stack_capacity {
466            return Err(InstructionError::CallDepth);
467        }
468        self.transaction_frame.current_executing_instruction = index_in_trace as u16;
469        self.instruction_stack.push(index_in_trace);
470        if let Some(index_in_transaction) = self.find_index_of_account(&instructions::id()) {
471            let mut mut_account_ref = self.accounts.try_borrow_mut(index_in_transaction)?;
472            if mut_account_ref.owner() != &solana_sdk_ids::sysvar::id() {
473                return Err(InstructionError::InvalidAccountOwner);
474            }
475            instructions::store_current_index_checked(
476                mut_account_ref.data_as_mut_slice(),
477                current_top_level_instruction as u16,
478            )?;
479        }
480        Ok(())
481    }
482
483    /// Pops the current instruction
484    pub fn pop(&mut self) -> Result<(), InstructionError> {
485        if self.instruction_stack.is_empty() {
486            return Err(InstructionError::CallDepth);
487        }
488        // Verify (before we pop) that the total sum of all lamports in this instruction did not change
489        let detected_an_unbalanced_instruction =
490            self.get_current_instruction_context()
491                .and_then(|instruction_context| {
492                    // Verify all executable accounts have no outstanding refs
493                    self.accounts
494                        .try_borrow_mut(
495                            instruction_context.get_index_of_program_account_in_transaction()?,
496                        )
497                        .map_err(|err| {
498                            if err == InstructionError::AccountBorrowFailed {
499                                InstructionError::AccountBorrowOutstanding
500                            } else {
501                                err
502                            }
503                        })?;
504                    Ok(self.accounts.get_lamports_delta() != 0)
505                });
506        // Always pop, even if we `detected_an_unbalanced_instruction`
507        self.instruction_stack.pop();
508        if let Some(instr_idx) = self.instruction_stack.last() {
509            self.transaction_frame.current_executing_instruction = *instr_idx as u16;
510        }
511        if detected_an_unbalanced_instruction? {
512            Err(InstructionError::UnbalancedInstruction)
513        } else {
514            Ok(())
515        }
516    }
517
518    /// Gets the return data of the current instruction or any above
519    pub fn get_return_data(&self) -> (&Pubkey, &[u8]) {
520        (
521            &self.transaction_frame.return_data_pubkey,
522            &self.return_data_bytes,
523        )
524    }
525
526    /// Set the return data of the current instruction
527    pub fn set_return_data(
528        &mut self,
529        program_id: Pubkey,
530        data: Vec<u8>,
531    ) -> Result<(), InstructionError> {
532        self.transaction_frame.return_data_pubkey = program_id;
533        self.transaction_frame
534            .return_data_scratchpad
535            .set_len(data.len() as u64);
536        self.return_data_bytes = data;
537        Ok(())
538    }
539
540    /// Returns a new account data write access handler
541    pub fn access_violation_handler(
542        &self,
543        virtual_address_space_adjustments: bool,
544        account_data_direct_mapping: bool,
545    ) -> AccessViolationHandler {
546        let accounts = Rc::clone(&self.accounts);
547        Box::new(
548            move |region: &mut MemoryRegion,
549                  address_space_reserved_for_account: u64,
550                  access_type: AccessType,
551                  vm_addr: u64,
552                  len: u64| {
553                if access_type == AccessType::Load {
554                    return;
555                }
556                let Some(index_in_transaction) = region.access_violation_handler_payload else {
557                    // This region is not a writable account.
558                    return;
559                };
560                let region_vm_addr_start = region.vm_addr_range().start;
561                let requested_length = vm_addr
562                    .saturating_add(len)
563                    .saturating_sub(region_vm_addr_start)
564                    as usize;
565                if requested_length > address_space_reserved_for_account as usize {
566                    // Requested access goes further than the account region.
567                    return;
568                }
569
570                // The four calls below can't really fail. If they fail because of a bug,
571                // whatever is writing will trigger an EbpfError::AccessViolation like
572                // if the region was readonly, and the transaction will fail gracefully.
573                let Ok(mut account) = accounts.try_borrow_mut(index_in_transaction) else {
574                    debug_assert!(false);
575                    return;
576                };
577                if accounts.touch(index_in_transaction).is_err() {
578                    debug_assert!(false);
579                    return;
580                }
581
582                let remaining_allowed_growth = MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION
583                    .saturating_sub(accounts.resize_delta())
584                    .max(0) as usize;
585
586                if requested_length > region.len() {
587                    // Realloc immediately here to fit the requested access,
588                    // then later in CPI or deserialization realloc again to the
589                    // account length the program stored in AccountInfo.
590                    let old_len = account.data().len();
591                    let new_len = (address_space_reserved_for_account as usize)
592                        .min(MAX_ACCOUNT_DATA_LEN as usize)
593                        .min(old_len.saturating_add(remaining_allowed_growth));
594                    // The last two min operations ensure the following:
595                    debug_assert!(accounts.can_data_be_resized(old_len, new_len).is_ok());
596                    if accounts
597                        .update_accounts_resize_delta(old_len, new_len)
598                        .is_err()
599                    {
600                        return;
601                    }
602
603                    account.resize(new_len, 0);
604                    let data_ptr = region.host_buffer().ptr() as *mut u8;
605                    let new_buffer = std::ptr::slice_from_raw_parts_mut(data_ptr, new_len);
606                    unsafe {
607                        // SAFETY:
608                        //
609                        // Contract from `MemoryRegion::redirect`: MemoryRegion must point to a
610                        // valid object live for the duration of this `MemoryMapping`.
611                        //
612                        // Evidence: There are two distinct cases, when the account buffer is
613                        // serialized and when the account buffer is directly mapped.
614                        // * In the serialization case we continue pointing at the same buffer as
615                        // before, and the original buffer must have satisfied the liveness
616                        // condition before.
617                        // * In the direct mapping case `account.resize` invalidates the buffer this
618                        // region has been pointing at, but this is fixed up later in the "unshare"
619                        // branch later.
620                        // * In the serialization case the section of serialized buffer has the
621                        // necessary padding after the account payload proper for resize. This
622                        // padding is a part of the originally constructed `MemoryRegion` and is
623                        // only later subsliced to not expose it before the first access to the
624                        // area (which invokes this handler.)
625                        //
626                        // Contract from `MemoryRegion::redirect`: For `MemoryRegion`s marked
627                        // writable, the host buffer must accept arbitrary bytes being overwritten
628                        // without it resulting in unsoundness.
629                        //
630                        // Evidence: The account payloads dont have any internal soundness
631                        // invariants. The buffer in the serialization case starts off and remains
632                        // writable (even though the HostBuffer might have been initially created as
633                        // immutable.) In the direct mapping case we redirect the region to the
634                        // buffer stored in the account later on.
635                        region.redirect(new_buffer);
636                    }
637                }
638
639                // Potentially unshare / make the account shared data unique (CoW logic).
640                if virtual_address_space_adjustments && account_data_direct_mapping {
641                    unsafe {
642                        // SAFETY: refer to the comment above.
643                        region.redirect(account.raw_mut_data_slice());
644                    }
645                }
646            },
647        )
648    }
649
650    /// Take ownership of the instruction trace
651    pub fn take_instruction_trace(&mut self) -> InstructionTrace<'_> {
652        // The last frame is a placeholder for the next instruction to be executed, so it
653        // is empty.
654        self.instruction_trace.pop();
655        (
656            std::mem::take(&mut self.instruction_trace),
657            std::mem::take(&mut self.instruction_accounts),
658            std::mem::take(&mut self.instruction_data),
659        )
660    }
661
662    /// Called instruction are those that the program runtime has already called into. It
663    /// encompasses instructions under execution (e.g. all nested CPIs are already called) and
664    /// finished ones.
665    ///
666    /// Top level instructions that have not yet been executed aren't considered called.
667    pub fn number_of_called_instructions_in_trace(&self) -> usize {
668        self.next_top_level_instruction_index
669            .saturating_add(self.transaction_frame.number_of_cpis_in_trace as usize)
670    }
671
672    /// Return next top level instruction to execute
673    pub fn next_top_level_instruction_index(&self) -> usize {
674        self.next_top_level_instruction_index
675    }
676
677    /// Return number of CPIs in instruction trace
678    pub fn number_of_cpis_in_trace(&self) -> usize {
679        self.transaction_frame.number_of_cpis_in_trace as usize
680    }
681}
682
683/// Return data at the end of a transaction
684#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
685#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
686#[cfg_attr(feature = "wincode", derive(wincode::SchemaRead, wincode::SchemaWrite))]
687#[derive(Clone, Debug, Default, PartialEq, Eq)]
688pub struct TransactionReturnData {
689    pub program_id: Pubkey,
690    pub data: Vec<u8>,
691}
692
693/// Everything that needs to be recorded from a TransactionContext after execution
694#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
695pub struct ExecutionRecord {
696    pub accounts: Vec<KeyedAccountSharedData>,
697    pub return_data: TransactionReturnData,
698    /// Parallel to `accounts`: whether each account was modified by the VM.
699    pub touched_flags: Box<[bool]>,
700    pub accounts_resize_delta: i64,
701}
702
703/// Used by the bank in the runtime to write back the processed accounts and recorded instructions
704#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
705impl From<TransactionContext<'_>> for ExecutionRecord {
706    fn from(context: TransactionContext) -> Self {
707        let (accounts, touched_flags, resize_delta) = Rc::try_unwrap(context.accounts)
708            .expect("transaction_context.accounts has unexpected outstanding refs")
709            .take();
710
711        // The flags only needed interior mutability while the VM was running.
712        // Now that we own them, unwrap the per-element `Cell`s into a plain
713        // `Box<[bool]>`. `Vec::from` reuses the box's allocation and the mapped
714        // collect reuses that same buffer in place (`Cell<bool>` and `bool` have
715        // identical layout), so no reallocation occurs.
716        let touched_flags: Box<[bool]> = Vec::from(touched_flags)
717            .into_iter()
718            .map(|flag| flag.into_inner())
719            .collect();
720
721        let return_data = TransactionReturnData {
722            program_id: context.transaction_frame.return_data_pubkey,
723            data: context.return_data_bytes,
724        };
725
726        Self {
727            accounts,
728            return_data,
729            touched_flags,
730            accounts_resize_delta: Cell::into_inner(resize_delta),
731        }
732    }
733}
734
735#[cfg(all(test, not(target_arch = "sbf"), not(target_arch = "bpf")))]
736mod tests {
737    use super::*;
738
739    #[test]
740    fn test_instructions_sysvar_store_index_checked() {
741        let build_transaction_context = |account: AccountSharedData| {
742            TransactionContext::new(
743                vec![
744                    (Pubkey::new_unique(), AccountSharedData::default()),
745                    (instructions::id(), account),
746                ],
747                Rent::default(),
748                /* max_instruction_stack_depth */ 2,
749                /* max_instruction_trace_length */ 2,
750                /* number_of_top_level_instructions */ 1,
751            )
752        };
753
754        let correct_space = 2;
755        let rent_exempt_lamports = Rent::default().minimum_balance(correct_space);
756
757        // First try it with the wrong owner.
758        let account =
759            AccountSharedData::new(rent_exempt_lamports, correct_space, &Pubkey::new_unique());
760        assert_eq!(
761            build_transaction_context(account).push(),
762            Err(InstructionError::InvalidAccountOwner),
763        );
764
765        // Now with the wrong data length.
766        let account =
767            AccountSharedData::new(rent_exempt_lamports, 0, &solana_sdk_ids::sysvar::id());
768        assert_eq!(
769            build_transaction_context(account).push(),
770            Err(InstructionError::AccountDataTooSmall),
771        );
772
773        // Finally provide the correct account setup.
774        let account = AccountSharedData::new(
775            rent_exempt_lamports,
776            correct_space,
777            &solana_sdk_ids::sysvar::id(),
778        );
779        assert_eq!(build_transaction_context(account).push(), Ok(()),);
780    }
781
782    #[test]
783    fn test_invalid_native_loader_index() {
784        let mut transaction_context = TransactionContext::new(
785            vec![(
786                Pubkey::new_unique(),
787                AccountSharedData::new(1, 1, &Pubkey::new_unique()),
788            )],
789            Rent::default(),
790            20,
791            20,
792            1,
793        );
794
795        transaction_context
796            .configure_top_level_instruction_for_tests(
797                u16::MAX,
798                vec![InstructionAccount::new(0, false, false)],
799                vec![],
800            )
801            .unwrap();
802        let instruction_context = transaction_context.get_next_instruction_context().unwrap();
803
804        let result = instruction_context.get_index_of_program_account_in_transaction();
805        assert_eq!(result, Err(InstructionError::MissingAccount));
806
807        let result = instruction_context.get_program_key();
808        assert_eq!(result, Err(InstructionError::MissingAccount));
809
810        let result = instruction_context.get_program_owner();
811        assert_eq!(result.err(), Some(InstructionError::MissingAccount));
812    }
813
814    #[test]
815    fn test_instruction_shared_items() {
816        let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 11];
817        let mut transaction_context =
818            TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 3);
819
820        let instruction_accounts_1 = vec![
821            InstructionAccount::new(0, false, true),
822            InstructionAccount::new(3, true, false),
823        ];
824        transaction_context
825            .configure_top_level_instruction_for_tests(
826                1,
827                instruction_accounts_1.clone(),
828                vec![1, 2, 3, 4],
829            )
830            .unwrap();
831        transaction_context.push().unwrap();
832
833        let instruction_accounts_2 = vec![
834            InstructionAccount::new(0, false, true),
835            InstructionAccount::new(3, true, false),
836            InstructionAccount::new(5, false, false),
837        ];
838        transaction_context
839            .configure_top_level_instruction_for_tests(
840                1,
841                instruction_accounts_2.clone(),
842                vec![5, 6, 7, 8, 9],
843            )
844            .unwrap();
845        transaction_context.push().unwrap();
846
847        let instruction_accounts_3 = vec![
848            InstructionAccount::new(0, false, true),
849            InstructionAccount::new(3, true, false),
850            InstructionAccount::new(5, false, false),
851            InstructionAccount::new(3, false, false),
852            InstructionAccount::new(10, false, false),
853        ];
854        transaction_context
855            .configure_top_level_instruction_for_tests(
856                1,
857                instruction_accounts_3.clone(),
858                vec![10, 11],
859            )
860            .unwrap();
861        transaction_context.push().unwrap();
862
863        let first_ix_context = transaction_context
864            .get_instruction_context_at_index_in_trace(0)
865            .unwrap();
866        assert_eq!(
867            instruction_accounts_1.as_slice(),
868            first_ix_context.instruction_accounts
869        );
870        assert_eq!(
871            *first_ix_context.instruction_data,
872            **transaction_context.instruction_data.first().unwrap()
873        );
874        for (idx_in_ix, acc) in instruction_accounts_1.iter().enumerate() {
875            assert_eq!(
876                *first_ix_context
877                    .dedup_map
878                    .get(acc.index_in_transaction as usize)
879                    .unwrap(),
880                idx_in_ix as u8
881            );
882        }
883
884        let second_ix_context = transaction_context
885            .get_instruction_context_at_index_in_trace(1)
886            .unwrap();
887        assert_eq!(
888            instruction_accounts_2.as_slice(),
889            second_ix_context.instruction_accounts
890        );
891        assert_eq!(
892            *second_ix_context.instruction_data,
893            **transaction_context.instruction_data.get(1).unwrap()
894        );
895        for (idx_in_ix, acc) in instruction_accounts_2.iter().enumerate() {
896            assert_eq!(
897                *second_ix_context
898                    .dedup_map
899                    .get(acc.index_in_transaction as usize)
900                    .unwrap(),
901                idx_in_ix as u8
902            );
903        }
904
905        let third_ix_context = transaction_context
906            .get_instruction_context_at_index_in_trace(2)
907            .unwrap();
908        assert_eq!(
909            instruction_accounts_3.as_slice(),
910            third_ix_context.instruction_accounts
911        );
912        assert_eq!(
913            *third_ix_context.instruction_data,
914            **transaction_context.instruction_data.get(2).unwrap()
915        );
916        for (idx_in_ix, acc) in instruction_accounts_3.iter().enumerate() {
917            if idx_in_ix == 3 {
918                assert_eq!(
919                    *third_ix_context
920                        .dedup_map
921                        .get(acc.index_in_transaction as usize)
922                        .unwrap(),
923                    1
924                );
925            } else {
926                assert_eq!(
927                    *third_ix_context
928                        .dedup_map
929                        .get(acc.index_in_transaction as usize)
930                        .unwrap(),
931                    idx_in_ix as u8
932                );
933            }
934        }
935    }
936
937    #[test]
938    fn test_number_of_instructions() {
939        let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 3];
940        let mut transaction_context =
941            TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 2);
942        assert_eq!(
943            transaction_context
944                .transaction_frame
945                .number_of_cpis_in_trace,
946            0
947        );
948
949        // Instruction #0
950        transaction_context
951            .configure_instruction_at_index(
952                0,
953                0,
954                vec![InstructionAccount::new(1, false, false)],
955                vec![0; 3],
956                Vec::new().into(),
957                None,
958            )
959            .unwrap();
960
961        // Instruction #1
962        transaction_context
963            .configure_instruction_at_index(
964                1,
965                0,
966                vec![InstructionAccount::new(1, false, false)],
967                vec![0; 3],
968                Vec::new().into(),
969                None,
970            )
971            .unwrap();
972
973        // Executing instruction #0
974        transaction_context.push().unwrap();
975        assert_eq!(
976            transaction_context
977                .transaction_frame
978                .current_executing_instruction,
979            0
980        );
981        assert_eq!(
982            transaction_context.number_of_called_instructions_in_trace(),
983            1
984        );
985
986        assert_eq!(
987            transaction_context
988                .transaction_frame
989                .total_number_of_instructions_in_trace,
990            2
991        );
992
993        assert_eq!(
994            transaction_context
995                .transaction_frame
996                .number_of_cpis_in_trace,
997            0
998        );
999
1000        assert_eq!(
1001            transaction_context
1002                .transaction_frame
1003                .cpi_data_scratchpad
1004                .ptr(),
1005            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(2))
1006        );
1007        assert_eq!(
1008            transaction_context
1009                .transaction_frame
1010                .cpi_data_scratchpad
1011                .len(),
1012            0,
1013        );
1014        assert_eq!(
1015            transaction_context
1016                .transaction_frame
1017                .cpi_accounts_scratchpad
1018                .ptr(),
1019            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1020                .saturating_add(GUEST_REGION_SIZE.saturating_mul(2))
1021        );
1022        assert_eq!(
1023            transaction_context
1024                .transaction_frame
1025                .cpi_data_scratchpad
1026                .len(),
1027            0,
1028        );
1029
1030        assert_eq!(
1031            transaction_context.number_of_called_instructions_in_trace(),
1032            1
1033        );
1034
1035        // Instruction #0 does a CPI.
1036        transaction_context
1037            .configure_next_cpi_for_tests(
1038                0,
1039                vec![InstructionAccount::new(2, false, true)],
1040                Vec::new(),
1041            )
1042            .unwrap();
1043
1044        transaction_context.push().unwrap();
1045        assert_eq!(
1046            transaction_context
1047                .transaction_frame
1048                .current_executing_instruction,
1049            2
1050        );
1051
1052        assert_eq!(
1053            transaction_context
1054                .transaction_frame
1055                .total_number_of_instructions_in_trace,
1056            3
1057        );
1058        assert_eq!(
1059            transaction_context
1060                .transaction_frame
1061                .number_of_cpis_in_trace,
1062            1
1063        );
1064        assert_eq!(
1065            transaction_context.number_of_called_instructions_in_trace(),
1066            2
1067        );
1068
1069        assert_eq!(
1070            transaction_context
1071                .transaction_frame
1072                .cpi_data_scratchpad
1073                .ptr(),
1074            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(3))
1075        );
1076        assert_eq!(
1077            transaction_context
1078                .transaction_frame
1079                .cpi_accounts_scratchpad
1080                .ptr(),
1081            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1082                .saturating_add(GUEST_REGION_SIZE.saturating_mul(3))
1083        );
1084
1085        // A nested CPI
1086        transaction_context
1087            .configure_next_cpi_for_tests(
1088                0,
1089                vec![InstructionAccount::new(2, false, true)],
1090                Vec::new(),
1091            )
1092            .unwrap();
1093
1094        transaction_context.push().unwrap();
1095        assert_eq!(
1096            transaction_context
1097                .transaction_frame
1098                .current_executing_instruction,
1099            3
1100        );
1101
1102        assert_eq!(
1103            transaction_context
1104                .transaction_frame
1105                .total_number_of_instructions_in_trace,
1106            4
1107        );
1108
1109        assert_eq!(
1110            transaction_context
1111                .transaction_frame
1112                .cpi_data_scratchpad
1113                .ptr(),
1114            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(4))
1115        );
1116        assert_eq!(
1117            transaction_context
1118                .transaction_frame
1119                .cpi_accounts_scratchpad
1120                .ptr(),
1121            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1122                .saturating_add(GUEST_REGION_SIZE.saturating_mul(4))
1123        );
1124
1125        assert_eq!(
1126            transaction_context
1127                .transaction_frame
1128                .number_of_cpis_in_trace,
1129            2
1130        );
1131
1132        assert_eq!(
1133            transaction_context.number_of_called_instructions_in_trace(),
1134            3
1135        );
1136        // Return from nested CPI
1137        transaction_context.pop().unwrap();
1138        assert_eq!(
1139            transaction_context.number_of_called_instructions_in_trace(),
1140            3
1141        );
1142
1143        assert_eq!(
1144            transaction_context
1145                .transaction_frame
1146                .total_number_of_instructions_in_trace,
1147            4
1148        );
1149        assert_eq!(
1150            transaction_context
1151                .transaction_frame
1152                .number_of_cpis_in_trace,
1153            2,
1154        );
1155        assert_eq!(
1156            transaction_context
1157                .transaction_frame
1158                .current_executing_instruction,
1159            2
1160        );
1161
1162        // A second nested CPI
1163        transaction_context
1164            .configure_next_cpi_for_tests(
1165                0,
1166                vec![InstructionAccount::new(2, false, true)],
1167                Vec::new(),
1168            )
1169            .unwrap();
1170
1171        transaction_context.push().unwrap();
1172        assert_eq!(
1173            transaction_context
1174                .transaction_frame
1175                .current_executing_instruction,
1176            4
1177        );
1178
1179        assert_eq!(
1180            transaction_context
1181                .transaction_frame
1182                .total_number_of_instructions_in_trace,
1183            5
1184        );
1185
1186        assert_eq!(
1187            transaction_context
1188                .transaction_frame
1189                .cpi_data_scratchpad
1190                .ptr(),
1191            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1192        );
1193        assert_eq!(
1194            transaction_context
1195                .transaction_frame
1196                .cpi_accounts_scratchpad
1197                .ptr(),
1198            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1199                .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1200        );
1201
1202        assert_eq!(
1203            transaction_context
1204                .transaction_frame
1205                .number_of_cpis_in_trace,
1206            3
1207        );
1208        assert_eq!(
1209            transaction_context.number_of_called_instructions_in_trace(),
1210            4
1211        );
1212
1213        // Return from second nested CPI
1214        transaction_context.pop().unwrap();
1215
1216        assert_eq!(
1217            transaction_context
1218                .transaction_frame
1219                .current_executing_instruction,
1220            2
1221        );
1222
1223        assert_eq!(
1224            transaction_context
1225                .transaction_frame
1226                .total_number_of_instructions_in_trace,
1227            5
1228        );
1229
1230        assert_eq!(
1231            transaction_context
1232                .transaction_frame
1233                .cpi_data_scratchpad
1234                .ptr(),
1235            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1236        );
1237        assert_eq!(
1238            transaction_context
1239                .transaction_frame
1240                .cpi_accounts_scratchpad
1241                .ptr(),
1242            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1243                .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1244        );
1245
1246        assert_eq!(
1247            transaction_context
1248                .transaction_frame
1249                .number_of_cpis_in_trace,
1250            3
1251        );
1252
1253        // Return from first CPI
1254        transaction_context.pop().unwrap();
1255        assert_eq!(
1256            transaction_context.number_of_called_instructions_in_trace(),
1257            4
1258        );
1259
1260        assert_eq!(
1261            transaction_context
1262                .transaction_frame
1263                .current_executing_instruction,
1264            0
1265        );
1266
1267        assert_eq!(
1268            transaction_context
1269                .transaction_frame
1270                .total_number_of_instructions_in_trace,
1271            5
1272        );
1273
1274        assert_eq!(
1275            transaction_context
1276                .transaction_frame
1277                .cpi_data_scratchpad
1278                .ptr(),
1279            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1280        );
1281        assert_eq!(
1282            transaction_context
1283                .transaction_frame
1284                .cpi_accounts_scratchpad
1285                .ptr(),
1286            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1287                .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1288        );
1289
1290        assert_eq!(
1291            transaction_context
1292                .transaction_frame
1293                .number_of_cpis_in_trace,
1294            3,
1295        );
1296
1297        // Let's go to Instruction #1 (top level)
1298        transaction_context.pop().unwrap();
1299        transaction_context.push().unwrap();
1300        assert_eq!(
1301            transaction_context
1302                .transaction_frame
1303                .current_executing_instruction,
1304            1,
1305        );
1306        assert_eq!(
1307            transaction_context
1308                .transaction_frame
1309                .number_of_cpis_in_trace,
1310            3
1311        );
1312
1313        // Instruction #1 will do a CPI.
1314        transaction_context
1315            .configure_next_cpi_for_tests(
1316                0,
1317                vec![InstructionAccount::new(2, false, true)],
1318                Vec::new(),
1319            )
1320            .unwrap();
1321
1322        transaction_context.push().unwrap();
1323
1324        assert_eq!(
1325            transaction_context
1326                .transaction_frame
1327                .current_executing_instruction,
1328            5,
1329        );
1330
1331        assert_eq!(
1332            transaction_context
1333                .transaction_frame
1334                .total_number_of_instructions_in_trace,
1335            6
1336        );
1337
1338        assert_eq!(
1339            transaction_context
1340                .transaction_frame
1341                .cpi_data_scratchpad
1342                .ptr(),
1343            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(6))
1344        );
1345        assert_eq!(
1346            transaction_context
1347                .transaction_frame
1348                .cpi_accounts_scratchpad
1349                .ptr(),
1350            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1351                .saturating_add(GUEST_REGION_SIZE.saturating_mul(6))
1352        );
1353
1354        assert_eq!(
1355            transaction_context
1356                .transaction_frame
1357                .number_of_cpis_in_trace,
1358            4
1359        );
1360        assert_eq!(
1361            transaction_context.number_of_called_instructions_in_trace(),
1362            6
1363        );
1364
1365        // Return from CPI
1366        transaction_context.pop().unwrap();
1367        assert_eq!(
1368            transaction_context
1369                .transaction_frame
1370                .number_of_cpis_in_trace,
1371            4
1372        );
1373        assert_eq!(
1374            transaction_context
1375                .transaction_frame
1376                .current_executing_instruction,
1377            1,
1378        );
1379
1380        transaction_context.pop().unwrap();
1381    }
1382
1383    #[test]
1384    fn test_get_current_instruction_index() {
1385        let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 3];
1386        let mut transaction_context =
1387            TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 2);
1388
1389        // First top level instruction
1390        transaction_context
1391            .configure_instruction_at_index(
1392                0,
1393                1,
1394                vec![
1395                    InstructionAccount::new(0, false, false),
1396                    InstructionAccount::new(1, false, false),
1397                ],
1398                vec![u8::MAX; 3],
1399                Cow::Owned(Vec::new()),
1400                None,
1401            )
1402            .unwrap();
1403
1404        // Second top-level instruction
1405        transaction_context
1406            .configure_instruction_at_index(
1407                1,
1408                1,
1409                vec![
1410                    InstructionAccount::new(0, false, false),
1411                    InstructionAccount::new(1, false, true),
1412                ],
1413                vec![u8::MAX; 3],
1414                Cow::Owned(Vec::new()),
1415                None,
1416            )
1417            .unwrap();
1418
1419        transaction_context.push().unwrap();
1420        assert_eq!(
1421            transaction_context.get_current_instruction_index().unwrap(),
1422            0
1423        );
1424
1425        transaction_context.pop().unwrap();
1426
1427        transaction_context.push().unwrap();
1428        assert_eq!(
1429            transaction_context.get_current_instruction_index().unwrap(),
1430            1
1431        );
1432
1433        // Simulating a CPI
1434        transaction_context
1435            .configure_next_cpi_for_tests(
1436                1,
1437                vec![
1438                    InstructionAccount::new(0, false, true),
1439                    InstructionAccount::new(1, false, false),
1440                ],
1441                Vec::new(),
1442            )
1443            .unwrap();
1444        transaction_context.push().unwrap();
1445        assert_eq!(
1446            transaction_context.get_current_instruction_index().unwrap(),
1447            2
1448        );
1449
1450        // Yet another CPI
1451        transaction_context
1452            .configure_next_cpi_for_tests(
1453                1,
1454                vec![
1455                    InstructionAccount::new(0, false, true),
1456                    InstructionAccount::new(1, false, false),
1457                ],
1458                Vec::new(),
1459            )
1460            .unwrap();
1461        transaction_context.push().unwrap();
1462        assert_eq!(
1463            transaction_context.get_current_instruction_index().unwrap(),
1464            3
1465        );
1466
1467        // CPI return
1468        transaction_context.pop().unwrap();
1469        assert_eq!(
1470            transaction_context.get_current_instruction_index().unwrap(),
1471            2
1472        );
1473
1474        // CPI return 2
1475        transaction_context.pop().unwrap();
1476        assert_eq!(
1477            transaction_context.get_current_instruction_index().unwrap(),
1478            1
1479        );
1480    }
1481}