Skip to main content

solana_transaction_context/
transaction.rs

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