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_INSTRUCTION, 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#[cfg(not(any(target_arch = "sbf", target_arch = "bpf")))]
99impl TransactionFrame {
100    fn configure_cpi(&mut self) {
101        self.total_number_of_instructions_in_trace =
102            self.total_number_of_instructions_in_trace.saturating_add(1);
103        let next_data_ptr = self
104            .cpi_data_scratchpad
105            .ptr()
106            .saturating_add(GUEST_REGION_SIZE);
107        self.cpi_data_scratchpad = VmSlice::new(next_data_ptr, 0);
108        let next_accounts_ptr = self
109            .cpi_accounts_scratchpad
110            .ptr()
111            .saturating_add(GUEST_REGION_SIZE);
112        self.cpi_accounts_scratchpad = VmSlice::new(next_accounts_ptr, 0);
113    }
114}
115
116/// Loaded transaction shared between runtime and programs.
117///
118/// This context is valid for the entire duration of a transaction being processed.
119#[derive(Debug)]
120#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
121pub struct TransactionContext<'ix_data> {
122    pub(crate) accounts: Rc<TransactionAccounts>,
123    instruction_stack_capacity: usize,
124    instruction_trace_capacity: usize,
125    instruction_stack: Vec<usize>,
126    instruction_trace: Vec<InstructionFrame>,
127    transaction_frame: TransactionFrame,
128    return_data_bytes: Vec<u8>,
129    next_top_level_instruction_index: usize,
130    /// Instruction compute meter, for tracking compute units consumed against
131    /// the designated compute budget during program execution.
132    #[cfg(not(target_os = "solana"))]
133    pub compute_meter: ComputeMeter,
134    #[cfg(not(target_os = "solana"))]
135    pub(crate) lamports_delta: Cell<i128>,
136    #[cfg(not(target_os = "solana"))]
137    pub(crate) rent: Rent,
138    /// This is an account deduplication map that maps index_in_transaction to index_in_instruction
139    /// Usage: dedup_map[index_in_transaction] = index_in_instruction
140    /// Each entry in `deduplication_maps` represents the deduplication map for each instruction.
141    deduplication_maps: Vec<Box<[u8]>>,
142    /// Each entry in `instruction_accounts` represents the array of accounts for each instruction.
143    instruction_accounts: Vec<Box<[InstructionAccount]>>,
144    /// Each entry in `instruction_data` represents the data for instruction at the corresponding
145    /// index.
146    instruction_data: Vec<Cow<'ix_data, [u8]>>,
147}
148
149#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
150impl<'ix_data> TransactionContext<'ix_data> {
151    /// Constructs a new TransactionContext
152    pub fn new_with_feature_flags(
153        transaction_accounts: Vec<KeyedAccountSharedData>,
154        rent: Rent,
155        instruction_stack_capacity: usize,
156        instruction_trace_capacity: usize,
157        number_of_top_level_instructions: usize,
158        compute_meter: ComputeMeter,
159        drop_on_bail_out: DropOnBailOut,
160    ) -> Self {
161        let transaction_frame = TransactionFrame {
162            return_data_pubkey: Pubkey::default(),
163            return_data_scratchpad: VmSlice::new(RETURN_DATA_SCRATCHPAD, 0),
164            cpi_data_scratchpad: VmSlice::new(
165                GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(
166                    GUEST_REGION_SIZE.saturating_mul(number_of_top_level_instructions as u64),
167                ),
168                0,
169            ),
170            cpi_accounts_scratchpad: VmSlice::new(
171                GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS.saturating_add(
172                    GUEST_REGION_SIZE.saturating_mul(number_of_top_level_instructions as u64),
173                ),
174                0,
175            ),
176            current_executing_instruction: 0,
177            total_number_of_instructions_in_trace: number_of_top_level_instructions as u16,
178            number_of_cpis_in_trace: 0,
179            number_of_transaction_accounts: transaction_accounts.len() as u16,
180        };
181
182        // We need an extra space for the placeholder, so we avoid relocations.
183        let mut instruction_trace =
184            Vec::with_capacity(instruction_trace_capacity.saturating_add(1));
185        instruction_trace.resize_with(
186            number_of_top_level_instructions.saturating_add(1),
187            InstructionFrame::default,
188        );
189
190        Self {
191            accounts: Rc::new(TransactionAccounts::new_with_feature_flags(
192                transaction_accounts,
193                drop_on_bail_out,
194            )),
195            instruction_stack_capacity,
196            instruction_trace_capacity,
197            instruction_stack: Vec::with_capacity(instruction_stack_capacity),
198            instruction_trace,
199            return_data_bytes: Vec::new(),
200            transaction_frame,
201            next_top_level_instruction_index: 0,
202            compute_meter,
203            lamports_delta: Cell::new(0),
204            rent,
205            instruction_accounts: Vec::with_capacity(instruction_trace_capacity),
206            deduplication_maps: Vec::with_capacity(instruction_trace_capacity),
207            instruction_data: Vec::with_capacity(instruction_trace_capacity),
208        }
209    }
210
211    /// Constructs a new TransactionContext with all features active
212    #[cfg(feature = "dev-context-only-utils")]
213    pub fn new(
214        transaction_accounts: Vec<KeyedAccountSharedData>,
215        rent: Rent,
216        instruction_stack_capacity: usize,
217        instruction_trace_capacity: usize,
218        number_of_top_level_instructions: usize,
219    ) -> Self {
220        Self::new_with_feature_flags(
221            transaction_accounts,
222            rent,
223            instruction_stack_capacity,
224            instruction_trace_capacity,
225            number_of_top_level_instructions,
226            ComputeMeter::new(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64),
227            DropOnBailOut::Disabled,
228        )
229    }
230
231    /// Used in mock_process_instruction
232    pub fn deconstruct_without_keys(self) -> Result<Vec<AccountSharedData>, InstructionError> {
233        if !self.instruction_stack.is_empty() {
234            return Err(InstructionError::CallDepth);
235        }
236
237        let accounts = Rc::try_unwrap(self.accounts)
238            .expect("transaction_context.accounts has unexpected outstanding refs")
239            .deconstruct_into_account_shared_data();
240
241        Ok(accounts)
242    }
243
244    pub(crate) fn add_lamports_delta(&self, balance: i128) -> Result<(), InstructionError> {
245        let delta = self.lamports_delta.get();
246        self.lamports_delta.set(
247            delta
248                .checked_add(balance)
249                .ok_or(InstructionError::ArithmeticOverflow)?,
250        );
251        Ok(())
252    }
253
254    pub fn accounts(&self) -> &Rc<TransactionAccounts> {
255        &self.accounts
256    }
257
258    /// Returns the total number of accounts loaded in this Transaction
259    pub fn get_number_of_accounts(&self) -> IndexOfAccount {
260        self.accounts.len() as IndexOfAccount
261    }
262
263    /// Searches for an account by its key
264    pub fn get_key_of_account_at_index(
265        &self,
266        index_in_transaction: IndexOfAccount,
267    ) -> Result<&Pubkey, InstructionError> {
268        self.accounts
269            .account_key(index_in_transaction)
270            .ok_or(InstructionError::MissingAccount)
271    }
272
273    /// Searches for an account by its key
274    pub fn find_index_of_account(&self, pubkey: &Pubkey) -> Option<IndexOfAccount> {
275        self.accounts
276            .account_keys_iter()
277            .position(|key| key == pubkey)
278            .map(|index| index as IndexOfAccount)
279    }
280
281    /// Gets the max length of the instruction trace
282    pub fn get_instruction_trace_capacity(&self) -> usize {
283        self.instruction_trace_capacity
284    }
285
286    /// Returns the instruction trace length.
287    ///
288    /// Not counting the last empty instruction which is always pre-reserved for the next instruction.
289    pub fn get_instruction_trace_length(&self) -> usize {
290        self.instruction_trace.len().saturating_sub(1)
291    }
292
293    /// Gets a view on an instruction by its index in the trace
294    pub fn get_instruction_context_at_index_in_trace(
295        &self,
296        index_in_trace: usize,
297    ) -> Result<InstructionContext<'_, '_>, InstructionError> {
298        let instruction = self
299            .instruction_trace
300            .get(index_in_trace)
301            .ok_or(InstructionError::CallDepth)?;
302
303        // These commands will return a default empty slice if we are retrieving an instruction
304        // that hasn't been configured yet.
305        let instruction_accounts = self
306            .instruction_accounts
307            .get(index_in_trace)
308            .map(|item| item.as_ref())
309            .unwrap_or_default();
310        let dedup_map = self
311            .deduplication_maps
312            .get(index_in_trace)
313            .map(|item| item.as_ref())
314            .unwrap_or_default();
315        let instruction_data = self
316            .instruction_data
317            .get(index_in_trace)
318            .map(|item| item.as_ref())
319            .unwrap_or_default();
320        Ok(InstructionContext {
321            transaction_context: self,
322            index_in_trace,
323            nesting_level: instruction.nesting_level as usize,
324            program_account_index_in_tx: instruction.program_account_index_in_tx as IndexOfAccount,
325            instruction_accounts,
326            dedup_map,
327            instruction_data,
328            index_of_caller_instruction: instruction.index_of_caller_instruction as usize,
329        })
330    }
331
332    /// Gets a view on the instruction by its nesting level in the stack
333    pub fn get_instruction_context_at_nesting_level(
334        &self,
335        nesting_level: usize,
336    ) -> Result<InstructionContext<'_, '_>, InstructionError> {
337        let index_in_trace = *self
338            .instruction_stack
339            .get(nesting_level)
340            .ok_or(InstructionError::CallDepth)?;
341        let instruction_context = self.get_instruction_context_at_index_in_trace(index_in_trace)?;
342        debug_assert_eq!(instruction_context.nesting_level, nesting_level);
343        Ok(instruction_context)
344    }
345
346    /// Gets the max height of the instruction stack
347    pub fn get_instruction_stack_capacity(&self) -> usize {
348        self.instruction_stack_capacity
349    }
350
351    /// Gets instruction stack height, top-level instructions are height
352    /// `solana_instruction::TRANSACTION_LEVEL_STACK_HEIGHT`
353    pub fn get_instruction_stack_height(&self) -> usize {
354        self.instruction_stack.len()
355    }
356
357    /// Returns the index in the instruction trace of the current executing instruction
358    pub fn get_current_instruction_index(&self) -> Result<usize, InstructionError> {
359        self.instruction_stack
360            .last()
361            .copied()
362            .ok_or(InstructionError::CallDepth)
363    }
364
365    /// Returns a view on the current instruction
366    pub fn get_current_instruction_context(
367        &self,
368    ) -> Result<InstructionContext<'_, '_>, InstructionError> {
369        let index_in_trace = self.get_current_instruction_index()?;
370        self.get_instruction_context_at_index_in_trace(index_in_trace)
371    }
372
373    /// Returns a view on the next instruction. This function assumes it has already been
374    /// configured with the correct values in `prepare_next_instruction` or
375    /// `prepare_next_top_level_instruction`
376    pub fn get_next_instruction_context(
377        &self,
378    ) -> Result<InstructionContext<'_, '_>, InstructionError> {
379        let index_in_trace = if self.instruction_stack.is_empty() {
380            self.next_top_level_instruction_index
381        } else {
382            self.instruction_trace
383                .len()
384                .checked_sub(1)
385                .ok_or(InstructionError::CallDepth)?
386        };
387        self.get_instruction_context_at_index_in_trace(index_in_trace)
388    }
389
390    /// Configures an instruction at a specific index in trace.
391    pub fn configure_instruction_at_index(
392        &mut self,
393        instruction_index: usize,
394        program_index: IndexOfAccount,
395        instruction_accounts: Vec<InstructionAccount>,
396        deduplication_map: Vec<u8>,
397        instruction_data: Cow<'ix_data, [u8]>,
398        caller_index: Option<u16>,
399    ) -> Result<(), InstructionError> {
400        // The deduplication map reserves u8::MAX for an absent account, and
401        // the SBF parameter ABI reserves it as the non-duplicate marker.
402        if instruction_accounts.len() > MAX_ACCOUNTS_PER_INSTRUCTION {
403            return Err(InstructionError::MaxAccountsExceeded);
404        }
405
406        debug_assert_eq!(
407            deduplication_map.len(),
408            usize::from(self.get_number_of_accounts()).min(MAX_ACCOUNTS_PER_TRANSACTION)
409        );
410
411        let instruction = self
412            .instruction_trace
413            .get_mut(instruction_index)
414            .ok_or(InstructionError::MaxInstructionTraceLengthExceeded)?;
415
416        // If we have a parent index, then we are dealing with a CPI.
417        if let Some(caller_index) = caller_index {
418            instruction.index_of_caller_instruction = caller_index;
419            self.transaction_frame.configure_cpi();
420        }
421
422        instruction.program_account_index_in_tx = program_index;
423        instruction.configure_vm_slices(
424            instruction_index as u64,
425            instruction_accounts.len(),
426            instruction_data.len() as u64,
427        );
428        self.deduplication_maps
429            .push(deduplication_map.into_boxed_slice());
430        self.instruction_accounts
431            .push(instruction_accounts.into_boxed_slice());
432        self.instruction_data.push(instruction_data);
433        Ok(())
434    }
435
436    fn deduplicate_accounts(
437        num_accounts: usize,
438        instruction_accounts: &mut [InstructionAccount],
439    ) -> Vec<u8> {
440        let mut dedup_map = vec![u8::MAX; num_accounts];
441        for idx_in_ix in 0..instruction_accounts.len() {
442            let first_occurrence_in_ix = dedup_map
443                .get_mut(
444                    instruction_accounts
445                        .get(idx_in_ix)
446                        .unwrap()
447                        .index_in_transaction as usize,
448                )
449                .unwrap();
450            if *first_occurrence_in_ix == u8::MAX {
451                *first_occurrence_in_ix = idx_in_ix as u8;
452            } else {
453                // Let's update the signer and writable flags for the first appearance of this
454                // account.
455                let [this_account, other_account] = instruction_accounts
456                    .get_disjoint_mut([idx_in_ix, *first_occurrence_in_ix as usize])
457                    .expect("Accounts indices must exist in array");
458
459                other_account.set_is_signer(other_account.is_signer() || this_account.is_signer());
460                other_account
461                    .set_is_writable(other_account.is_writable() || this_account.is_writable());
462            }
463        }
464
465        Self::replicate_account_flags(instruction_accounts, &dedup_map);
466        dedup_map
467    }
468
469    /// Replicate account flags to duplicated accounts.
470    /// This function only works if the accounts had been previously deduplicated, like in
471    /// `deduplicate_accounts` and `build_instruction_frame`.
472    pub fn replicate_account_flags(
473        instruction_accounts: &mut [InstructionAccount],
474        dedup_map: &[u8],
475    ) {
476        for current_index in 0..instruction_accounts.len() {
477            let instruction_account = instruction_accounts.get(current_index).unwrap();
478            let other_account_index = *dedup_map
479                .get(instruction_account.index_in_transaction as usize)
480                .expect("Deduplication map must contain this account")
481                as usize;
482
483            let Ok([current_account, reference_account]) =
484                instruction_accounts.get_disjoint_mut([current_index, other_account_index])
485            else {
486                continue;
487            };
488
489            // The deduplication procedure must have used the first occurrence of the account
490            // as the source of truths for the flags.
491            current_account
492                .set_is_signer(current_account.is_signer() || reference_account.is_signer());
493            current_account
494                .set_is_writable(current_account.is_writable() || reference_account.is_writable());
495        }
496    }
497
498    /// A version of `configure_top_level_instruction` to help creating the deduplication map in tests
499    pub fn configure_top_level_instruction_for_tests(
500        &mut self,
501        program_index: IndexOfAccount,
502        mut instruction_accounts: Vec<InstructionAccount>,
503        instruction_data: Vec<u8>,
504    ) -> Result<(), InstructionError> {
505        debug_assert!(instruction_accounts.len() <= u8::MAX as usize);
506        let dedup_map = Self::deduplicate_accounts(
507            self.get_number_of_accounts() as usize,
508            &mut instruction_accounts,
509        );
510
511        self.configure_instruction_at_index(
512            self.next_top_level_instruction_index,
513            program_index,
514            instruction_accounts,
515            dedup_map,
516            Cow::Owned(instruction_data),
517            None,
518        )?;
519        Ok(())
520    }
521
522    /// A helper function to facilitate creating a CPI in tests
523    pub fn configure_next_cpi_for_tests(
524        &mut self,
525        program_index: IndexOfAccount,
526        mut instruction_accounts: Vec<InstructionAccount>,
527        instruction_data: Vec<u8>,
528    ) -> Result<(), InstructionError> {
529        debug_assert!(instruction_accounts.len() <= u8::MAX as usize);
530        let dedup_map = Self::deduplicate_accounts(
531            self.get_number_of_accounts() as usize,
532            &mut instruction_accounts,
533        );
534        let caller_index = self.get_current_instruction_index()?;
535        let cpi_index = self.get_instruction_trace_length();
536        self.configure_instruction_at_index(
537            cpi_index,
538            program_index,
539            instruction_accounts,
540            dedup_map,
541            Cow::Owned(instruction_data),
542            Some(caller_index as u16),
543        )?;
544        Ok(())
545    }
546
547    /// Pushes the next instruction
548    pub fn push(&mut self, detect_overflow_early: bool) -> Result<(), InstructionError> {
549        let nesting_level = self.get_instruction_stack_height();
550        if !self.instruction_stack.is_empty() && self.lamports_delta.get() != 0 {
551            return Err(InstructionError::UnbalancedInstruction);
552        }
553        {
554            let instruction = self
555                .instruction_trace
556                .last_mut()
557                .ok_or(InstructionError::CallDepth)?;
558            instruction.nesting_level = nesting_level as u16;
559        }
560
561        if !detect_overflow_early
562            && self.number_of_called_instructions_in_trace() >= self.instruction_trace_capacity
563        {
564            return Err(InstructionError::MaxInstructionTraceLengthExceeded);
565        }
566
567        if detect_overflow_early
568            && ((self.transaction_frame.total_number_of_instructions_in_trace as usize
569                > self.instruction_trace_capacity)
570                || (self.number_of_called_instructions_in_trace()
571                    >= self.instruction_trace_capacity))
572        {
573            // The condition after the OR is necessary if in any case we execute more top
574            // level instructions than expected, since `total_number_of_instructions_in_trace` is
575            // with the number of top level instructions when TransactionContext is created
576            // and only updated for CPIs afterward.
577            //
578            // Having more top-level instructions than TransactionContext was created with
579            // should be impossible to happen with the current code configuration, so the extra
580            // check serves as a failsafe guard.
581            return Err(InstructionError::MaxInstructionTraceLengthExceeded);
582        }
583
584        let (index_in_trace, current_top_level_instruction) = if self.instruction_stack.is_empty() {
585            let index = self.next_top_level_instruction_index;
586            self.next_top_level_instruction_index =
587                self.next_top_level_instruction_index.saturating_add(1);
588            (index, index)
589        } else {
590            let index = self.get_instruction_trace_length();
591            self.transaction_frame.number_of_cpis_in_trace = self
592                .transaction_frame
593                .number_of_cpis_in_trace
594                .saturating_add(1);
595            self.instruction_trace.push(InstructionFrame::default());
596            (
597                index,
598                self.next_top_level_instruction_index.saturating_sub(1),
599            )
600        };
601
602        if nesting_level >= self.instruction_stack_capacity {
603            return Err(InstructionError::CallDepth);
604        }
605        self.transaction_frame.current_executing_instruction = index_in_trace as u16;
606        self.instruction_stack.push(index_in_trace);
607        if let Some(index_in_transaction) = self.find_index_of_account(&instructions::id()) {
608            let mut mut_account_ref = self.accounts.try_borrow_mut(index_in_transaction)?;
609            if mut_account_ref.owner() != &solana_sdk_ids::sysvar::id() {
610                return Err(InstructionError::InvalidAccountOwner);
611            }
612            instructions::store_current_index_checked(
613                mut_account_ref.data_as_mut_slice(),
614                current_top_level_instruction as u16,
615            )?;
616        }
617        Ok(())
618    }
619
620    /// Pops the current instruction
621    pub fn pop(&mut self) -> Result<(), InstructionError> {
622        if self.instruction_stack.is_empty() {
623            return Err(InstructionError::CallDepth);
624        }
625        // Verify (before we pop) that the total sum of all lamports in this instruction did not change
626        let detected_an_unbalanced_instruction =
627            self.get_current_instruction_context()
628                .and_then(|instruction_context| {
629                    // Verify all executable accounts have no outstanding refs
630                    self.accounts
631                        .try_borrow_mut(
632                            instruction_context.get_index_of_program_account_in_transaction()?,
633                        )
634                        .map_err(|err| {
635                            if err == InstructionError::AccountBorrowFailed {
636                                InstructionError::AccountBorrowOutstanding
637                            } else {
638                                err
639                            }
640                        })?;
641                    Ok(self.lamports_delta.get() != 0)
642                });
643        // Always pop, even if we `detected_an_unbalanced_instruction`
644        self.instruction_stack.pop();
645        if let Some(instr_idx) = self.instruction_stack.last() {
646            self.transaction_frame.current_executing_instruction = *instr_idx as u16;
647        }
648        if detected_an_unbalanced_instruction? {
649            Err(InstructionError::UnbalancedInstruction)
650        } else {
651            Ok(())
652        }
653    }
654
655    /// Gets the return data of the current instruction or any above
656    pub fn get_return_data(&self) -> (&Pubkey, &[u8]) {
657        (
658            &self.transaction_frame.return_data_pubkey,
659            &self.return_data_bytes,
660        )
661    }
662
663    /// Set the return data of the current instruction
664    pub fn set_return_data(
665        &mut self,
666        program_id: Pubkey,
667        data: Vec<u8>,
668    ) -> Result<(), InstructionError> {
669        self.transaction_frame.return_data_pubkey = program_id;
670        self.transaction_frame
671            .return_data_scratchpad
672            .set_len(data.len() as u64);
673        self.return_data_bytes = data;
674        Ok(())
675    }
676
677    /// Returns a new account data write access handler
678    pub fn access_violation_handler(
679        &self,
680        virtual_address_space_adjustments: bool,
681        account_data_direct_mapping: bool,
682    ) -> AccessViolationHandler {
683        let accounts = Rc::clone(&self.accounts);
684        Box::new(
685            move |region: &mut MemoryRegion,
686                  address_space_reserved_for_account: u64,
687                  access_type: AccessType,
688                  vm_addr: u64,
689                  len: u64| {
690                if access_type == AccessType::Load {
691                    return;
692                }
693                let Some(index_in_transaction) = region.access_violation_handler_payload else {
694                    // This region is not a writable account.
695                    return;
696                };
697                let region_vm_addr_start = region.vm_addr_range().start;
698                let requested_length = vm_addr
699                    .saturating_add(len)
700                    .saturating_sub(region_vm_addr_start)
701                    as usize;
702                if requested_length > address_space_reserved_for_account as usize {
703                    // Requested access goes further than the account region.
704                    return;
705                }
706
707                // The four calls below can't really fail. If they fail because of a bug,
708                // whatever is writing will trigger an EbpfError::AccessViolation like
709                // if the region was readonly, and the transaction will fail gracefully.
710                let Ok(mut account) = accounts.try_borrow_mut(index_in_transaction) else {
711                    debug_assert!(false);
712                    return;
713                };
714                if accounts.touch(index_in_transaction).is_err() {
715                    debug_assert!(false);
716                    return;
717                }
718
719                let remaining_allowed_growth = MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION
720                    .saturating_sub(accounts.resize_delta())
721                    .max(0) as usize;
722
723                if requested_length > region.len() {
724                    // Realloc immediately here to fit the requested access,
725                    // then later in CPI or deserialization realloc again to the
726                    // account length the program stored in AccountInfo.
727                    let old_len = account.data().len();
728                    let new_len = (address_space_reserved_for_account as usize)
729                        .min(MAX_ACCOUNT_DATA_LEN as usize)
730                        .min(old_len.saturating_add(remaining_allowed_growth));
731                    // The last two min operations ensure the following:
732                    debug_assert!(accounts.can_data_be_resized(old_len, new_len).is_ok());
733                    if accounts
734                        .update_accounts_resize_delta(old_len, new_len)
735                        .is_err()
736                    {
737                        return;
738                    }
739
740                    account.resize(new_len, 0);
741                    let data_ptr = region.host_buffer().ptr() as *mut u8;
742                    let new_buffer = std::ptr::slice_from_raw_parts_mut(data_ptr, new_len);
743                    unsafe {
744                        // SAFETY:
745                        //
746                        // Contract from `MemoryRegion::redirect`: MemoryRegion must point to a
747                        // valid object live for the duration of this `MemoryMapping`.
748                        //
749                        // Evidence: There are two distinct cases, when the account buffer is
750                        // serialized and when the account buffer is directly mapped.
751                        // * In the serialization case we continue pointing at the same buffer as
752                        // before, and the original buffer must have satisfied the liveness
753                        // condition before.
754                        // * In the direct mapping case `account.resize` invalidates the buffer this
755                        // region has been pointing at, but this is fixed up later in the "unshare"
756                        // branch later.
757                        // * In the serialization case the section of serialized buffer has the
758                        // necessary padding after the account payload proper for resize. This
759                        // padding is a part of the originally constructed `MemoryRegion` and is
760                        // only later subsliced to not expose it before the first access to the
761                        // area (which invokes this handler.)
762                        //
763                        // Contract from `MemoryRegion::redirect`: For `MemoryRegion`s marked
764                        // writable, the host buffer must accept arbitrary bytes being overwritten
765                        // without it resulting in unsoundness.
766                        //
767                        // Evidence: The account payloads dont have any internal soundness
768                        // invariants. The buffer in the serialization case starts off and remains
769                        // writable (even though the HostBuffer might have been initially created as
770                        // immutable.) In the direct mapping case we redirect the region to the
771                        // buffer stored in the account later on.
772                        region.redirect(new_buffer);
773                    }
774                }
775
776                // Potentially unshare / make the account shared data unique (CoW logic).
777                if virtual_address_space_adjustments && account_data_direct_mapping {
778                    unsafe {
779                        // SAFETY: refer to the comment above.
780                        region.redirect(account.raw_mut_data_slice());
781                    }
782                }
783            },
784        )
785    }
786
787    /// Take ownership of the instruction trace
788    pub fn take_instruction_trace(&mut self) -> InstructionTrace<'_> {
789        // The last frame is a placeholder for the next instruction to be executed, so it
790        // is empty.
791        self.instruction_trace.pop();
792        (
793            std::mem::take(&mut self.instruction_trace),
794            std::mem::take(&mut self.instruction_accounts),
795            std::mem::take(&mut self.instruction_data),
796        )
797    }
798
799    /// Called instruction are those that the program runtime has already called into. It
800    /// encompasses instructions under execution (e.g. all nested CPIs are already called) and
801    /// finished ones.
802    ///
803    /// Top level instructions that have not yet been executed aren't considered called.
804    pub fn number_of_called_instructions_in_trace(&self) -> usize {
805        self.next_top_level_instruction_index
806            .saturating_add(self.transaction_frame.number_of_cpis_in_trace as usize)
807    }
808
809    /// Return next top level instruction to execute
810    pub fn next_top_level_instruction_index(&self) -> usize {
811        self.next_top_level_instruction_index
812    }
813
814    /// Return number of CPIs in instruction trace
815    pub fn number_of_cpis_in_trace(&self) -> usize {
816        self.transaction_frame.number_of_cpis_in_trace as usize
817    }
818}
819
820/// Return data at the end of a transaction
821#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
822#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
823#[cfg_attr(feature = "wincode", derive(wincode::SchemaRead, wincode::SchemaWrite))]
824#[derive(Clone, Debug, Default, PartialEq, Eq)]
825pub struct TransactionReturnData {
826    pub program_id: Pubkey,
827    pub data: Vec<u8>,
828}
829
830/// Everything that needs to be recorded from a TransactionContext after execution
831#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
832pub struct ExecutionRecord {
833    pub accounts: Vec<KeyedAccountSharedData>,
834    pub return_data: TransactionReturnData,
835    /// Parallel to `accounts`: whether each account was modified by the VM.
836    pub touched_flags: Box<[bool]>,
837    pub accounts_resize_delta: i64,
838}
839
840/// Used by the bank in the runtime to write back the processed accounts and recorded instructions
841#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
842impl From<TransactionContext<'_>> for ExecutionRecord {
843    fn from(context: TransactionContext) -> Self {
844        let (accounts, touched_flags, resize_delta) = Rc::try_unwrap(context.accounts)
845            .expect("transaction_context.accounts has unexpected outstanding refs")
846            .take();
847
848        // The flags only needed interior mutability while the VM was running.
849        // Now that we own them, unwrap the per-element `Cell`s into a plain
850        // `Box<[bool]>`. `Vec::from` reuses the box's allocation and the mapped
851        // collect reuses that same buffer in place (`Cell<bool>` and `bool` have
852        // identical layout), so no reallocation occurs.
853        let touched_flags: Box<[bool]> = Vec::from(touched_flags)
854            .into_iter()
855            .map(|flag| flag.into_inner())
856            .collect();
857
858        let return_data = TransactionReturnData {
859            program_id: context.transaction_frame.return_data_pubkey,
860            data: context.return_data_bytes,
861        };
862
863        Self {
864            accounts,
865            return_data,
866            touched_flags,
867            accounts_resize_delta: Cell::into_inner(resize_delta),
868        }
869    }
870}
871
872#[cfg(all(test, not(target_arch = "sbf"), not(target_arch = "bpf")))]
873mod tests {
874    use {super::*, crate::MAX_INSTRUCTION_TRACE_LENGTH, test_case::test_case};
875
876    #[test]
877    fn test_instructions_sysvar_store_index_checked() {
878        let build_transaction_context = |account: AccountSharedData| {
879            TransactionContext::new(
880                vec![
881                    (Pubkey::new_unique(), AccountSharedData::default()),
882                    (instructions::id(), account),
883                ],
884                Rent::default(),
885                /* max_instruction_stack_depth */ 2,
886                /* max_instruction_trace_length */ 2,
887                /* number_of_top_level_instructions */ 1,
888            )
889        };
890
891        let correct_space = 2;
892        let rent_exempt_lamports = Rent::default().minimum_balance(correct_space);
893
894        // First try it with the wrong owner.
895        let account =
896            AccountSharedData::new(rent_exempt_lamports, correct_space, &Pubkey::new_unique());
897        assert_eq!(
898            build_transaction_context(account).push(true),
899            Err(InstructionError::InvalidAccountOwner),
900        );
901
902        // Now with the wrong data length.
903        let account =
904            AccountSharedData::new(rent_exempt_lamports, 0, &solana_sdk_ids::sysvar::id());
905        assert_eq!(
906            build_transaction_context(account).push(true),
907            Err(InstructionError::AccountDataTooSmall),
908        );
909
910        // Finally provide the correct account setup.
911        let account = AccountSharedData::new(
912            rent_exempt_lamports,
913            correct_space,
914            &solana_sdk_ids::sysvar::id(),
915        );
916        assert_eq!(build_transaction_context(account).push(true), Ok(()),);
917    }
918
919    #[test]
920    fn test_invalid_native_loader_index() {
921        let mut transaction_context = TransactionContext::new(
922            vec![(
923                Pubkey::new_unique(),
924                AccountSharedData::new(1, 1, &Pubkey::new_unique()),
925            )],
926            Rent::default(),
927            20,
928            20,
929            1,
930        );
931
932        transaction_context
933            .configure_top_level_instruction_for_tests(
934                u16::MAX,
935                vec![InstructionAccount::new(0, false, false)],
936                vec![],
937            )
938            .unwrap();
939        let instruction_context = transaction_context.get_next_instruction_context().unwrap();
940
941        let result = instruction_context.get_index_of_program_account_in_transaction();
942        assert_eq!(result, Err(InstructionError::MissingAccount));
943
944        let result = instruction_context.get_program_key();
945        assert_eq!(result, Err(InstructionError::MissingAccount));
946
947        let result = instruction_context.get_program_owner();
948        assert_eq!(result.err(), Some(InstructionError::MissingAccount));
949    }
950
951    #[test]
952    fn test_instruction_shared_items() {
953        let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 11];
954        let mut transaction_context =
955            TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 3);
956
957        let instruction_accounts_1 = vec![
958            InstructionAccount::new(0, false, true),
959            InstructionAccount::new(3, true, false),
960        ];
961        transaction_context
962            .configure_top_level_instruction_for_tests(
963                1,
964                instruction_accounts_1.clone(),
965                vec![1, 2, 3, 4],
966            )
967            .unwrap();
968        transaction_context.push(true).unwrap();
969
970        let instruction_accounts_2 = vec![
971            InstructionAccount::new(0, false, true),
972            InstructionAccount::new(3, true, false),
973            InstructionAccount::new(5, false, false),
974        ];
975        transaction_context
976            .configure_top_level_instruction_for_tests(
977                1,
978                instruction_accounts_2.clone(),
979                vec![5, 6, 7, 8, 9],
980            )
981            .unwrap();
982        transaction_context.push(true).unwrap();
983
984        let instruction_accounts_3 = vec![
985            InstructionAccount::new(0, false, true),
986            InstructionAccount::new(3, true, false),
987            InstructionAccount::new(5, false, false),
988            InstructionAccount::new(3, false, false),
989            InstructionAccount::new(10, false, false),
990        ];
991        transaction_context
992            .configure_top_level_instruction_for_tests(
993                1,
994                instruction_accounts_3.clone(),
995                vec![10, 11],
996            )
997            .unwrap();
998        transaction_context.push(true).unwrap();
999
1000        let first_ix_context = transaction_context
1001            .get_instruction_context_at_index_in_trace(0)
1002            .unwrap();
1003        assert_eq!(
1004            instruction_accounts_1.as_slice(),
1005            first_ix_context.instruction_accounts
1006        );
1007        assert_eq!(
1008            *first_ix_context.instruction_data,
1009            **transaction_context.instruction_data.first().unwrap()
1010        );
1011        for (idx_in_ix, acc) in instruction_accounts_1.iter().enumerate() {
1012            assert_eq!(
1013                *first_ix_context
1014                    .dedup_map
1015                    .get(acc.index_in_transaction as usize)
1016                    .unwrap(),
1017                idx_in_ix as u8
1018            );
1019        }
1020
1021        let second_ix_context = transaction_context
1022            .get_instruction_context_at_index_in_trace(1)
1023            .unwrap();
1024        assert_eq!(
1025            instruction_accounts_2.as_slice(),
1026            second_ix_context.instruction_accounts
1027        );
1028        assert_eq!(
1029            *second_ix_context.instruction_data,
1030            **transaction_context.instruction_data.get(1).unwrap()
1031        );
1032        for (idx_in_ix, acc) in instruction_accounts_2.iter().enumerate() {
1033            assert_eq!(
1034                *second_ix_context
1035                    .dedup_map
1036                    .get(acc.index_in_transaction as usize)
1037                    .unwrap(),
1038                idx_in_ix as u8
1039            );
1040        }
1041
1042        let third_ix_context = transaction_context
1043            .get_instruction_context_at_index_in_trace(2)
1044            .unwrap();
1045        assert_eq!(
1046            vec![
1047                InstructionAccount::new(0, false, true),
1048                InstructionAccount::new(3, true, false),
1049                InstructionAccount::new(5, false, false),
1050                InstructionAccount::new(3, true, false),
1051                InstructionAccount::new(10, false, false),
1052            ]
1053            .as_slice(),
1054            third_ix_context.instruction_accounts
1055        );
1056
1057        assert_eq!(
1058            *third_ix_context.instruction_data,
1059            **transaction_context.instruction_data.get(2).unwrap()
1060        );
1061        for (idx_in_ix, acc) in instruction_accounts_3.iter().enumerate() {
1062            if idx_in_ix == 3 {
1063                assert_eq!(
1064                    *third_ix_context
1065                        .dedup_map
1066                        .get(acc.index_in_transaction as usize)
1067                        .unwrap(),
1068                    1
1069                );
1070            } else {
1071                assert_eq!(
1072                    *third_ix_context
1073                        .dedup_map
1074                        .get(acc.index_in_transaction as usize)
1075                        .unwrap(),
1076                    idx_in_ix as u8
1077                );
1078            }
1079        }
1080    }
1081
1082    #[test]
1083    fn test_number_of_instructions() {
1084        let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 3];
1085        let mut transaction_context =
1086            TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 2);
1087        assert_eq!(
1088            transaction_context
1089                .transaction_frame
1090                .number_of_cpis_in_trace,
1091            0
1092        );
1093
1094        // Instruction #0
1095        transaction_context
1096            .configure_instruction_at_index(
1097                0,
1098                0,
1099                vec![InstructionAccount::new(1, false, false)],
1100                vec![0; 3],
1101                Vec::new().into(),
1102                None,
1103            )
1104            .unwrap();
1105
1106        // Instruction #1
1107        transaction_context
1108            .configure_instruction_at_index(
1109                1,
1110                0,
1111                vec![InstructionAccount::new(1, false, false)],
1112                vec![0; 3],
1113                Vec::new().into(),
1114                None,
1115            )
1116            .unwrap();
1117
1118        // Executing instruction #0
1119        transaction_context.push(true).unwrap();
1120        assert_eq!(
1121            transaction_context
1122                .transaction_frame
1123                .current_executing_instruction,
1124            0
1125        );
1126        assert_eq!(
1127            transaction_context.number_of_called_instructions_in_trace(),
1128            1
1129        );
1130
1131        assert_eq!(
1132            transaction_context
1133                .transaction_frame
1134                .total_number_of_instructions_in_trace,
1135            2
1136        );
1137
1138        assert_eq!(
1139            transaction_context
1140                .transaction_frame
1141                .number_of_cpis_in_trace,
1142            0
1143        );
1144
1145        assert_eq!(
1146            transaction_context
1147                .transaction_frame
1148                .cpi_data_scratchpad
1149                .ptr(),
1150            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(2))
1151        );
1152        assert_eq!(
1153            transaction_context
1154                .transaction_frame
1155                .cpi_data_scratchpad
1156                .len(),
1157            0,
1158        );
1159        assert_eq!(
1160            transaction_context
1161                .transaction_frame
1162                .cpi_accounts_scratchpad
1163                .ptr(),
1164            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1165                .saturating_add(GUEST_REGION_SIZE.saturating_mul(2))
1166        );
1167        assert_eq!(
1168            transaction_context
1169                .transaction_frame
1170                .cpi_data_scratchpad
1171                .len(),
1172            0,
1173        );
1174
1175        assert_eq!(
1176            transaction_context.number_of_called_instructions_in_trace(),
1177            1
1178        );
1179
1180        // Instruction #0 does a CPI.
1181        transaction_context
1182            .configure_next_cpi_for_tests(
1183                0,
1184                vec![InstructionAccount::new(2, false, true)],
1185                Vec::new(),
1186            )
1187            .unwrap();
1188
1189        transaction_context.push(true).unwrap();
1190        assert_eq!(
1191            transaction_context
1192                .transaction_frame
1193                .current_executing_instruction,
1194            2
1195        );
1196
1197        assert_eq!(
1198            transaction_context
1199                .transaction_frame
1200                .total_number_of_instructions_in_trace,
1201            3
1202        );
1203        assert_eq!(
1204            transaction_context
1205                .transaction_frame
1206                .number_of_cpis_in_trace,
1207            1
1208        );
1209        assert_eq!(
1210            transaction_context.number_of_called_instructions_in_trace(),
1211            2
1212        );
1213
1214        assert_eq!(
1215            transaction_context
1216                .transaction_frame
1217                .cpi_data_scratchpad
1218                .ptr(),
1219            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(3))
1220        );
1221        assert_eq!(
1222            transaction_context
1223                .transaction_frame
1224                .cpi_accounts_scratchpad
1225                .ptr(),
1226            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1227                .saturating_add(GUEST_REGION_SIZE.saturating_mul(3))
1228        );
1229
1230        // A nested CPI
1231        transaction_context
1232            .configure_next_cpi_for_tests(
1233                0,
1234                vec![InstructionAccount::new(2, false, true)],
1235                Vec::new(),
1236            )
1237            .unwrap();
1238
1239        transaction_context.push(true).unwrap();
1240        assert_eq!(
1241            transaction_context
1242                .transaction_frame
1243                .current_executing_instruction,
1244            3
1245        );
1246
1247        assert_eq!(
1248            transaction_context
1249                .transaction_frame
1250                .total_number_of_instructions_in_trace,
1251            4
1252        );
1253
1254        assert_eq!(
1255            transaction_context
1256                .transaction_frame
1257                .cpi_data_scratchpad
1258                .ptr(),
1259            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(4))
1260        );
1261        assert_eq!(
1262            transaction_context
1263                .transaction_frame
1264                .cpi_accounts_scratchpad
1265                .ptr(),
1266            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1267                .saturating_add(GUEST_REGION_SIZE.saturating_mul(4))
1268        );
1269
1270        assert_eq!(
1271            transaction_context
1272                .transaction_frame
1273                .number_of_cpis_in_trace,
1274            2
1275        );
1276
1277        assert_eq!(
1278            transaction_context.number_of_called_instructions_in_trace(),
1279            3
1280        );
1281        // Return from nested CPI
1282        transaction_context.pop().unwrap();
1283        assert_eq!(
1284            transaction_context.number_of_called_instructions_in_trace(),
1285            3
1286        );
1287
1288        assert_eq!(
1289            transaction_context
1290                .transaction_frame
1291                .total_number_of_instructions_in_trace,
1292            4
1293        );
1294        assert_eq!(
1295            transaction_context
1296                .transaction_frame
1297                .number_of_cpis_in_trace,
1298            2,
1299        );
1300        assert_eq!(
1301            transaction_context
1302                .transaction_frame
1303                .current_executing_instruction,
1304            2
1305        );
1306
1307        // A second nested CPI
1308        transaction_context
1309            .configure_next_cpi_for_tests(
1310                0,
1311                vec![InstructionAccount::new(2, false, true)],
1312                Vec::new(),
1313            )
1314            .unwrap();
1315
1316        transaction_context.push(true).unwrap();
1317        assert_eq!(
1318            transaction_context
1319                .transaction_frame
1320                .current_executing_instruction,
1321            4
1322        );
1323
1324        assert_eq!(
1325            transaction_context
1326                .transaction_frame
1327                .total_number_of_instructions_in_trace,
1328            5
1329        );
1330
1331        assert_eq!(
1332            transaction_context
1333                .transaction_frame
1334                .cpi_data_scratchpad
1335                .ptr(),
1336            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1337        );
1338        assert_eq!(
1339            transaction_context
1340                .transaction_frame
1341                .cpi_accounts_scratchpad
1342                .ptr(),
1343            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1344                .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1345        );
1346
1347        assert_eq!(
1348            transaction_context
1349                .transaction_frame
1350                .number_of_cpis_in_trace,
1351            3
1352        );
1353        assert_eq!(
1354            transaction_context.number_of_called_instructions_in_trace(),
1355            4
1356        );
1357
1358        // Return from second nested CPI
1359        transaction_context.pop().unwrap();
1360
1361        assert_eq!(
1362            transaction_context
1363                .transaction_frame
1364                .current_executing_instruction,
1365            2
1366        );
1367
1368        assert_eq!(
1369            transaction_context
1370                .transaction_frame
1371                .total_number_of_instructions_in_trace,
1372            5
1373        );
1374
1375        assert_eq!(
1376            transaction_context
1377                .transaction_frame
1378                .cpi_data_scratchpad
1379                .ptr(),
1380            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1381        );
1382        assert_eq!(
1383            transaction_context
1384                .transaction_frame
1385                .cpi_accounts_scratchpad
1386                .ptr(),
1387            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1388                .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1389        );
1390
1391        assert_eq!(
1392            transaction_context
1393                .transaction_frame
1394                .number_of_cpis_in_trace,
1395            3
1396        );
1397
1398        // Return from first CPI
1399        transaction_context.pop().unwrap();
1400        assert_eq!(
1401            transaction_context.number_of_called_instructions_in_trace(),
1402            4
1403        );
1404
1405        assert_eq!(
1406            transaction_context
1407                .transaction_frame
1408                .current_executing_instruction,
1409            0
1410        );
1411
1412        assert_eq!(
1413            transaction_context
1414                .transaction_frame
1415                .total_number_of_instructions_in_trace,
1416            5
1417        );
1418
1419        assert_eq!(
1420            transaction_context
1421                .transaction_frame
1422                .cpi_data_scratchpad
1423                .ptr(),
1424            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1425        );
1426        assert_eq!(
1427            transaction_context
1428                .transaction_frame
1429                .cpi_accounts_scratchpad
1430                .ptr(),
1431            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1432                .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1433        );
1434
1435        assert_eq!(
1436            transaction_context
1437                .transaction_frame
1438                .number_of_cpis_in_trace,
1439            3,
1440        );
1441
1442        // Let's go to Instruction #1 (top level)
1443        transaction_context.pop().unwrap();
1444        transaction_context.push(true).unwrap();
1445        assert_eq!(
1446            transaction_context
1447                .transaction_frame
1448                .current_executing_instruction,
1449            1,
1450        );
1451        assert_eq!(
1452            transaction_context
1453                .transaction_frame
1454                .number_of_cpis_in_trace,
1455            3
1456        );
1457
1458        // Instruction #1 will do a CPI.
1459        transaction_context
1460            .configure_next_cpi_for_tests(
1461                0,
1462                vec![InstructionAccount::new(2, false, true)],
1463                Vec::new(),
1464            )
1465            .unwrap();
1466
1467        transaction_context.push(true).unwrap();
1468
1469        assert_eq!(
1470            transaction_context
1471                .transaction_frame
1472                .current_executing_instruction,
1473            5,
1474        );
1475
1476        assert_eq!(
1477            transaction_context
1478                .transaction_frame
1479                .total_number_of_instructions_in_trace,
1480            6
1481        );
1482
1483        assert_eq!(
1484            transaction_context
1485                .transaction_frame
1486                .cpi_data_scratchpad
1487                .ptr(),
1488            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(6))
1489        );
1490        assert_eq!(
1491            transaction_context
1492                .transaction_frame
1493                .cpi_accounts_scratchpad
1494                .ptr(),
1495            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1496                .saturating_add(GUEST_REGION_SIZE.saturating_mul(6))
1497        );
1498
1499        assert_eq!(
1500            transaction_context
1501                .transaction_frame
1502                .number_of_cpis_in_trace,
1503            4
1504        );
1505        assert_eq!(
1506            transaction_context.number_of_called_instructions_in_trace(),
1507            6
1508        );
1509
1510        // Return from CPI
1511        transaction_context.pop().unwrap();
1512        assert_eq!(
1513            transaction_context
1514                .transaction_frame
1515                .number_of_cpis_in_trace,
1516            4
1517        );
1518        assert_eq!(
1519            transaction_context
1520                .transaction_frame
1521                .current_executing_instruction,
1522            1,
1523        );
1524
1525        transaction_context.pop().unwrap();
1526    }
1527
1528    #[test]
1529    fn test_get_current_instruction_index() {
1530        let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 3];
1531        let mut transaction_context =
1532            TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 2);
1533
1534        // First top level instruction
1535        transaction_context
1536            .configure_instruction_at_index(
1537                0,
1538                1,
1539                vec![
1540                    InstructionAccount::new(0, false, false),
1541                    InstructionAccount::new(1, false, false),
1542                ],
1543                vec![u8::MAX; 3],
1544                Cow::Owned(Vec::new()),
1545                None,
1546            )
1547            .unwrap();
1548
1549        // Second top-level instruction
1550        transaction_context
1551            .configure_instruction_at_index(
1552                1,
1553                1,
1554                vec![
1555                    InstructionAccount::new(0, false, false),
1556                    InstructionAccount::new(1, false, true),
1557                ],
1558                vec![u8::MAX; 3],
1559                Cow::Owned(Vec::new()),
1560                None,
1561            )
1562            .unwrap();
1563
1564        transaction_context.push(true).unwrap();
1565        assert_eq!(
1566            transaction_context.get_current_instruction_index().unwrap(),
1567            0
1568        );
1569
1570        transaction_context.pop().unwrap();
1571
1572        transaction_context.push(true).unwrap();
1573        assert_eq!(
1574            transaction_context.get_current_instruction_index().unwrap(),
1575            1
1576        );
1577
1578        // Simulating a CPI
1579        transaction_context
1580            .configure_next_cpi_for_tests(
1581                1,
1582                vec![
1583                    InstructionAccount::new(0, false, true),
1584                    InstructionAccount::new(1, false, false),
1585                ],
1586                Vec::new(),
1587            )
1588            .unwrap();
1589        transaction_context.push(true).unwrap();
1590        assert_eq!(
1591            transaction_context.get_current_instruction_index().unwrap(),
1592            2
1593        );
1594
1595        // Yet another CPI
1596        transaction_context
1597            .configure_next_cpi_for_tests(
1598                1,
1599                vec![
1600                    InstructionAccount::new(0, false, true),
1601                    InstructionAccount::new(1, false, false),
1602                ],
1603                Vec::new(),
1604            )
1605            .unwrap();
1606        transaction_context.push(true).unwrap();
1607        assert_eq!(
1608            transaction_context.get_current_instruction_index().unwrap(),
1609            3
1610        );
1611
1612        // CPI return
1613        transaction_context.pop().unwrap();
1614        assert_eq!(
1615            transaction_context.get_current_instruction_index().unwrap(),
1616            2
1617        );
1618
1619        // CPI return 2
1620        transaction_context.pop().unwrap();
1621        assert_eq!(
1622            transaction_context.get_current_instruction_index().unwrap(),
1623            1
1624        );
1625    }
1626
1627    #[test]
1628    fn test_deduplicate_accounts() {
1629        let mut instruction_accounts = vec![
1630            InstructionAccount::new(0, false, true), // Account 0, writable
1631            InstructionAccount::new(1, true, false), // Account 1, signer
1632            InstructionAccount::new(0, false, false), // Account 0 again, not writable
1633            InstructionAccount::new(2, false, true), // Account 2, writable
1634            InstructionAccount::new(1, true, false), // Account 1 again, signer
1635        ];
1636
1637        let dedup_map = TransactionContext::deduplicate_accounts(
1638            instruction_accounts.len(),
1639            &mut instruction_accounts,
1640        );
1641
1642        // Check that the dedup_map correctly maps duplicate accounts
1643        assert_eq!(
1644            *dedup_map.first().unwrap(),
1645            0,
1646            "account must be a duplicate of itself"
1647        );
1648        assert_eq!(
1649            *dedup_map.get(1).unwrap(),
1650            1,
1651            "account must be a duplicate of itself"
1652        );
1653        assert_eq!(
1654            *dedup_map.get(2).unwrap(),
1655            3,
1656            "account must be a duplicate of itself"
1657        );
1658
1659        // Check that duplicate accounts are properly merged
1660        let acc = instruction_accounts.first().unwrap();
1661        assert_eq!(acc.index_in_transaction, 0);
1662        assert!(
1663            !acc.is_signer(),
1664            "Must not be a signer because account 1 is not signer"
1665        );
1666        assert!(
1667            acc.is_writable(),
1668            "Must be writable because account 0 is writable"
1669        );
1670
1671        let acc = instruction_accounts.get(1).unwrap();
1672        assert_eq!(acc.index_in_transaction, 1);
1673        assert!(
1674            acc.is_signer(),
1675            "Must be signer because account 1 is signer"
1676        );
1677        assert!(
1678            !acc.is_writable(),
1679            "Must not be writable because account 1 is not writable"
1680        );
1681
1682        let acc = instruction_accounts.get(2).unwrap();
1683        assert_eq!(acc.index_in_transaction, 0);
1684        assert!(!acc.is_signer(), "Should be merged from account 1");
1685        assert!(acc.is_writable(), "Should be merged from account 0");
1686
1687        let acc = instruction_accounts.get(3).unwrap();
1688        assert_eq!(acc.index_in_transaction, 2);
1689        assert!(!acc.is_signer());
1690        assert!(acc.is_writable());
1691
1692        let acc = instruction_accounts.get(4).unwrap();
1693        assert_eq!(acc.index_in_transaction, 1);
1694        assert!(
1695            acc.is_signer(),
1696            "Must be signer because account 1 is signer"
1697        );
1698        assert!(
1699            !acc.is_writable(),
1700            "Must not be writable because account 1 is not writable"
1701        );
1702
1703        // Verify that the deduplication map correctly identifies duplicates
1704        assert_eq!(
1705            *dedup_map.first().unwrap(),
1706            0,
1707            "account must be a duplicate of itself"
1708        );
1709        assert_eq!(
1710            *dedup_map.get(1).unwrap(),
1711            1,
1712            "account must be a duplicate of itself"
1713        );
1714        assert_eq!(
1715            *dedup_map.get(2).unwrap(),
1716            3,
1717            "account must be a duplicate of itself"
1718        );
1719    }
1720
1721    #[test]
1722    fn test_deduplicate_accounts_no_duplicates() {
1723        let mut instruction_accounts = vec![
1724            InstructionAccount::new(0, false, true),
1725            InstructionAccount::new(1, true, false),
1726            InstructionAccount::new(2, false, false),
1727        ];
1728
1729        let dedup_map = TransactionContext::deduplicate_accounts(
1730            instruction_accounts.len(),
1731            &mut instruction_accounts,
1732        );
1733
1734        // Check that the dedup_map correctly maps each account to itself
1735        assert_eq!(
1736            *dedup_map.first().unwrap(),
1737            0,
1738            "account must be a duplicate of itself"
1739        );
1740        assert_eq!(
1741            *dedup_map.get(1).unwrap(),
1742            1,
1743            "account must be a duplicate of itself"
1744        );
1745        assert_eq!(
1746            *dedup_map.get(2).unwrap(),
1747            2,
1748            "account must be a duplicate of itself"
1749        );
1750
1751        // Check that accounts are not modified
1752        let acc = instruction_accounts.first().unwrap();
1753        assert_eq!(acc.index_in_transaction, 0);
1754        assert!(!acc.is_signer());
1755        assert!(acc.is_writable());
1756
1757        let acc = instruction_accounts.get(1).unwrap();
1758        assert_eq!(acc.index_in_transaction, 1);
1759        assert!(acc.is_signer());
1760        assert!(!acc.is_writable());
1761
1762        let acc = instruction_accounts.get(2).unwrap();
1763        assert_eq!(acc.index_in_transaction, 2);
1764        assert!(!acc.is_signer());
1765        assert!(!acc.is_writable());
1766    }
1767
1768    #[test]
1769    fn test_deduplicate_accounts_all_duplicates() {
1770        let mut instruction_accounts = vec![
1771            InstructionAccount::new(0, false, true),
1772            InstructionAccount::new(0, true, false),
1773            InstructionAccount::new(0, false, false),
1774        ];
1775
1776        let dedup_map = TransactionContext::deduplicate_accounts(
1777            instruction_accounts.len(),
1778            &mut instruction_accounts,
1779        );
1780
1781        // Check that all accounts map to the first occurrence (index 0)
1782        assert_eq!(
1783            *dedup_map.first().unwrap(),
1784            0,
1785            "account must be a duplicate of itself"
1786        );
1787        for idx in dedup_map.iter().skip(1) {
1788            assert_eq!(*idx, u8::MAX);
1789        }
1790
1791        // Check that the first account has combined flags
1792        let acc = instruction_accounts.first().unwrap();
1793        assert_eq!(acc.index_in_transaction, 0);
1794        assert!(
1795            acc.is_signer(),
1796            "Should be signer because of second account"
1797        );
1798        assert!(
1799            acc.is_writable(),
1800            "Should be writable because of first account"
1801        );
1802
1803        // Check that the other accounts have the same flags as the first
1804        let acc = instruction_accounts.get(1).unwrap();
1805        assert_eq!(acc.index_in_transaction, 0);
1806        assert!(acc.is_signer());
1807        assert!(acc.is_writable());
1808
1809        let acc = instruction_accounts.get(2).unwrap();
1810        assert_eq!(acc.index_in_transaction, 0);
1811        assert!(acc.is_signer());
1812        assert!(acc.is_writable());
1813    }
1814
1815    #[test_case(false; "simd_582_disabled")]
1816    #[test_case(true; "simd_582_enabled")]
1817    fn test_max_instruction_trace_len_exceeded(fail_early: bool) {
1818        const IX_TRACE_CAPACITY: usize = 5;
1819        let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 3];
1820
1821        for number_of_top_level_instructions in 4..=5 {
1822            // Simulate a trace capacity of 5
1823            let mut transaction_context = TransactionContext::new(
1824                transaction_accounts.clone(),
1825                Rent::default(),
1826                6,
1827                IX_TRACE_CAPACITY,
1828                number_of_top_level_instructions,
1829            );
1830
1831            // Configure top-level instructions
1832            for idx_to_configure in 0..number_of_top_level_instructions {
1833                transaction_context
1834                    .configure_instruction_at_index(
1835                        idx_to_configure,
1836                        0,
1837                        vec![InstructionAccount::new(1, false, false)],
1838                        vec![0; 3],
1839                        Vec::new().into(),
1840                        None,
1841                    )
1842                    .unwrap();
1843            }
1844
1845            // Execute instructions
1846            let mut pushed_ixs: usize = 0;
1847            let mut ix_in_trace = number_of_top_level_instructions;
1848            for ix_num in 0..number_of_top_level_instructions {
1849                let result = transaction_context.push(fail_early);
1850                pushed_ixs = pushed_ixs.saturating_add(1);
1851                if !fail_early && pushed_ixs > IX_TRACE_CAPACITY {
1852                    assert_eq!(pushed_ixs, IX_TRACE_CAPACITY.saturating_add(1));
1853                    assert_eq!(
1854                        transaction_context.number_of_called_instructions_in_trace(),
1855                        IX_TRACE_CAPACITY
1856                    );
1857                    assert_eq!(
1858                        result,
1859                        Err(InstructionError::MaxInstructionTraceLengthExceeded)
1860                    );
1861                    break;
1862                } else {
1863                    assert!(result.is_ok());
1864                }
1865
1866                // Two first ixs perform a CPI
1867                if ix_num < 2 {
1868                    transaction_context
1869                        .configure_next_cpi_for_tests(
1870                            0,
1871                            vec![InstructionAccount::new(2, false, false)],
1872                            Vec::new(),
1873                        )
1874                        .unwrap();
1875
1876                    ix_in_trace = ix_in_trace.saturating_add(1);
1877                    pushed_ixs = pushed_ixs.saturating_add(1);
1878                    let result = transaction_context.push(fail_early);
1879                    if fail_early && ix_in_trace > IX_TRACE_CAPACITY {
1880                        assert_eq!(ix_in_trace, IX_TRACE_CAPACITY.saturating_add(1));
1881                        assert_eq!(
1882                            transaction_context
1883                                .transaction_frame
1884                                .total_number_of_instructions_in_trace
1885                                as usize,
1886                            IX_TRACE_CAPACITY.saturating_add(1)
1887                        );
1888                        assert_eq!(
1889                            result,
1890                            Err(InstructionError::MaxInstructionTraceLengthExceeded)
1891                        );
1892                        break;
1893                    } else {
1894                        assert!(result.is_ok());
1895                    }
1896                    transaction_context.pop().unwrap();
1897                }
1898                transaction_context.pop().unwrap();
1899            }
1900        }
1901    }
1902
1903    #[test_case(false; "simd_582_disabled")]
1904    #[test_case(true; "simd_582_enabled")]
1905    fn test_max_instruction_trace_len_exceeded_64_ixs(fail_early: bool) {
1906        let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 3];
1907
1908        // Simulate a trace capacity of 5
1909        let mut transaction_context = TransactionContext::new(
1910            transaction_accounts.clone(),
1911            Rent::default(),
1912            6,
1913            MAX_INSTRUCTION_TRACE_LENGTH,
1914            MAX_INSTRUCTION_TRACE_LENGTH,
1915        );
1916
1917        // Configure top-level instructions
1918        for idx_to_configure in 0..MAX_INSTRUCTION_TRACE_LENGTH {
1919            transaction_context
1920                .configure_instruction_at_index(
1921                    idx_to_configure,
1922                    0,
1923                    vec![InstructionAccount::new(1, false, false)],
1924                    vec![0; 3],
1925                    Vec::new().into(),
1926                    None,
1927                )
1928                .unwrap();
1929        }
1930
1931        // Execute first top level instruction
1932        transaction_context.push(fail_early).unwrap();
1933        // It invokes a program
1934        transaction_context
1935            .configure_next_cpi_for_tests(
1936                0,
1937                vec![InstructionAccount::new(2, false, false)],
1938                Vec::new(),
1939            )
1940            .unwrap();
1941
1942        let result = transaction_context.push(fail_early);
1943        if fail_early {
1944            assert_eq!(
1945                result,
1946                Err(InstructionError::MaxInstructionTraceLengthExceeded)
1947            );
1948        } else {
1949            assert!(result.is_ok());
1950        }
1951    }
1952}