Skip to main content

solana_transaction_context/
transaction.rs

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