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) -> 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 self.number_of_called_instructions_in_trace() >= self.instruction_trace_capacity {
494            return Err(InstructionError::MaxInstructionTraceLengthExceeded);
495        }
496
497        let (index_in_trace, current_top_level_instruction) = if self.instruction_stack.is_empty() {
498            let index = self.next_top_level_instruction_index;
499            self.next_top_level_instruction_index =
500                self.next_top_level_instruction_index.saturating_add(1);
501            (index, index)
502        } else {
503            let index = self.get_instruction_trace_length();
504            self.transaction_frame.number_of_cpis_in_trace = self
505                .transaction_frame
506                .number_of_cpis_in_trace
507                .saturating_add(1);
508            self.instruction_trace.push(InstructionFrame::default());
509            (
510                index,
511                self.next_top_level_instruction_index.saturating_sub(1),
512            )
513        };
514
515        if nesting_level >= self.instruction_stack_capacity {
516            return Err(InstructionError::CallDepth);
517        }
518        self.transaction_frame.current_executing_instruction = index_in_trace as u16;
519        self.instruction_stack.push(index_in_trace);
520        if let Some(index_in_transaction) = self.find_index_of_account(&instructions::id()) {
521            let mut mut_account_ref = self.accounts.try_borrow_mut(index_in_transaction)?;
522            if mut_account_ref.owner() != &solana_sdk_ids::sysvar::id() {
523                return Err(InstructionError::InvalidAccountOwner);
524            }
525            instructions::store_current_index_checked(
526                mut_account_ref.data_as_mut_slice(),
527                current_top_level_instruction as u16,
528            )?;
529        }
530        Ok(())
531    }
532
533    /// Pops the current instruction
534    pub fn pop(&mut self) -> Result<(), InstructionError> {
535        if self.instruction_stack.is_empty() {
536            return Err(InstructionError::CallDepth);
537        }
538        // Verify (before we pop) that the total sum of all lamports in this instruction did not change
539        let detected_an_unbalanced_instruction =
540            self.get_current_instruction_context()
541                .and_then(|instruction_context| {
542                    // Verify all executable accounts have no outstanding refs
543                    self.accounts
544                        .try_borrow_mut(
545                            instruction_context.get_index_of_program_account_in_transaction()?,
546                        )
547                        .map_err(|err| {
548                            if err == InstructionError::AccountBorrowFailed {
549                                InstructionError::AccountBorrowOutstanding
550                            } else {
551                                err
552                            }
553                        })?;
554                    Ok(self.accounts.get_lamports_delta() != 0)
555                });
556        // Always pop, even if we `detected_an_unbalanced_instruction`
557        self.instruction_stack.pop();
558        if let Some(instr_idx) = self.instruction_stack.last() {
559            self.transaction_frame.current_executing_instruction = *instr_idx as u16;
560        }
561        if detected_an_unbalanced_instruction? {
562            Err(InstructionError::UnbalancedInstruction)
563        } else {
564            Ok(())
565        }
566    }
567
568    /// Gets the return data of the current instruction or any above
569    pub fn get_return_data(&self) -> (&Pubkey, &[u8]) {
570        (
571            &self.transaction_frame.return_data_pubkey,
572            &self.return_data_bytes,
573        )
574    }
575
576    /// Set the return data of the current instruction
577    pub fn set_return_data(
578        &mut self,
579        program_id: Pubkey,
580        data: Vec<u8>,
581    ) -> Result<(), InstructionError> {
582        self.transaction_frame.return_data_pubkey = program_id;
583        self.transaction_frame
584            .return_data_scratchpad
585            .set_len(data.len() as u64);
586        self.return_data_bytes = data;
587        Ok(())
588    }
589
590    /// Returns a new account data write access handler
591    pub fn access_violation_handler(
592        &self,
593        virtual_address_space_adjustments: bool,
594        account_data_direct_mapping: bool,
595    ) -> AccessViolationHandler {
596        let accounts = Rc::clone(&self.accounts);
597        Box::new(
598            move |region: &mut MemoryRegion,
599                  address_space_reserved_for_account: u64,
600                  access_type: AccessType,
601                  vm_addr: u64,
602                  len: u64| {
603                if access_type == AccessType::Load {
604                    return;
605                }
606                let Some(index_in_transaction) = region.access_violation_handler_payload else {
607                    // This region is not a writable account.
608                    return;
609                };
610                let region_vm_addr_start = region.vm_addr_range().start;
611                let requested_length = vm_addr
612                    .saturating_add(len)
613                    .saturating_sub(region_vm_addr_start)
614                    as usize;
615                if requested_length > address_space_reserved_for_account as usize {
616                    // Requested access goes further than the account region.
617                    return;
618                }
619
620                // The four calls below can't really fail. If they fail because of a bug,
621                // whatever is writing will trigger an EbpfError::AccessViolation like
622                // if the region was readonly, and the transaction will fail gracefully.
623                let Ok(mut account) = accounts.try_borrow_mut(index_in_transaction) else {
624                    debug_assert!(false);
625                    return;
626                };
627                if accounts.touch(index_in_transaction).is_err() {
628                    debug_assert!(false);
629                    return;
630                }
631
632                let remaining_allowed_growth = MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION
633                    .saturating_sub(accounts.resize_delta())
634                    .max(0) as usize;
635
636                if requested_length > region.len() {
637                    // Realloc immediately here to fit the requested access,
638                    // then later in CPI or deserialization realloc again to the
639                    // account length the program stored in AccountInfo.
640                    let old_len = account.data().len();
641                    let new_len = (address_space_reserved_for_account as usize)
642                        .min(MAX_ACCOUNT_DATA_LEN as usize)
643                        .min(old_len.saturating_add(remaining_allowed_growth));
644                    // The last two min operations ensure the following:
645                    debug_assert!(accounts.can_data_be_resized(old_len, new_len).is_ok());
646                    if accounts
647                        .update_accounts_resize_delta(old_len, new_len)
648                        .is_err()
649                    {
650                        return;
651                    }
652
653                    account.resize(new_len, 0);
654                    let data_ptr = region.host_buffer().ptr() as *mut u8;
655                    let new_buffer = std::ptr::slice_from_raw_parts_mut(data_ptr, new_len);
656                    unsafe {
657                        // SAFETY:
658                        //
659                        // Contract from `MemoryRegion::redirect`: MemoryRegion must point to a
660                        // valid object live for the duration of this `MemoryMapping`.
661                        //
662                        // Evidence: There are two distinct cases, when the account buffer is
663                        // serialized and when the account buffer is directly mapped.
664                        // * In the serialization case we continue pointing at the same buffer as
665                        // before, and the original buffer must have satisfied the liveness
666                        // condition before.
667                        // * In the direct mapping case `account.resize` invalidates the buffer this
668                        // region has been pointing at, but this is fixed up later in the "unshare"
669                        // branch later.
670                        // * In the serialization case the section of serialized buffer has the
671                        // necessary padding after the account payload proper for resize. This
672                        // padding is a part of the originally constructed `MemoryRegion` and is
673                        // only later subsliced to not expose it before the first access to the
674                        // area (which invokes this handler.)
675                        //
676                        // Contract from `MemoryRegion::redirect`: For `MemoryRegion`s marked
677                        // writable, the host buffer must accept arbitrary bytes being overwritten
678                        // without it resulting in unsoundness.
679                        //
680                        // Evidence: The account payloads dont have any internal soundness
681                        // invariants. The buffer in the serialization case starts off and remains
682                        // writable (even though the HostBuffer might have been initially created as
683                        // immutable.) In the direct mapping case we redirect the region to the
684                        // buffer stored in the account later on.
685                        region.redirect(new_buffer);
686                    }
687                }
688
689                // Potentially unshare / make the account shared data unique (CoW logic).
690                if virtual_address_space_adjustments && account_data_direct_mapping {
691                    unsafe {
692                        // SAFETY: refer to the comment above.
693                        region.redirect(account.raw_mut_data_slice());
694                    }
695                }
696            },
697        )
698    }
699
700    /// Take ownership of the instruction trace
701    pub fn take_instruction_trace(&mut self) -> InstructionTrace<'_> {
702        // The last frame is a placeholder for the next instruction to be executed, so it
703        // is empty.
704        self.instruction_trace.pop();
705        (
706            std::mem::take(&mut self.instruction_trace),
707            std::mem::take(&mut self.instruction_accounts),
708            std::mem::take(&mut self.instruction_data),
709        )
710    }
711
712    /// Called instruction are those that the program runtime has already called into. It
713    /// encompasses instructions under execution (e.g. all nested CPIs are already called) and
714    /// finished ones.
715    ///
716    /// Top level instructions that have not yet been executed aren't considered called.
717    pub fn number_of_called_instructions_in_trace(&self) -> usize {
718        self.next_top_level_instruction_index
719            .saturating_add(self.transaction_frame.number_of_cpis_in_trace as usize)
720    }
721
722    /// Return next top level instruction to execute
723    pub fn next_top_level_instruction_index(&self) -> usize {
724        self.next_top_level_instruction_index
725    }
726
727    /// Return number of CPIs in instruction trace
728    pub fn number_of_cpis_in_trace(&self) -> usize {
729        self.transaction_frame.number_of_cpis_in_trace as usize
730    }
731}
732
733/// Return data at the end of a transaction
734#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
735#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
736#[cfg_attr(feature = "wincode", derive(wincode::SchemaRead, wincode::SchemaWrite))]
737#[derive(Clone, Debug, Default, PartialEq, Eq)]
738pub struct TransactionReturnData {
739    pub program_id: Pubkey,
740    pub data: Vec<u8>,
741}
742
743/// Everything that needs to be recorded from a TransactionContext after execution
744#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
745pub struct ExecutionRecord {
746    pub accounts: Vec<KeyedAccountSharedData>,
747    pub return_data: TransactionReturnData,
748    /// Parallel to `accounts`: whether each account was modified by the VM.
749    pub touched_flags: Box<[bool]>,
750    pub accounts_resize_delta: i64,
751}
752
753/// Used by the bank in the runtime to write back the processed accounts and recorded instructions
754#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
755impl From<TransactionContext<'_>> for ExecutionRecord {
756    fn from(context: TransactionContext) -> Self {
757        let (accounts, touched_flags, resize_delta) = Rc::try_unwrap(context.accounts)
758            .expect("transaction_context.accounts has unexpected outstanding refs")
759            .take();
760
761        // The flags only needed interior mutability while the VM was running.
762        // Now that we own them, unwrap the per-element `Cell`s into a plain
763        // `Box<[bool]>`. `Vec::from` reuses the box's allocation and the mapped
764        // collect reuses that same buffer in place (`Cell<bool>` and `bool` have
765        // identical layout), so no reallocation occurs.
766        let touched_flags: Box<[bool]> = Vec::from(touched_flags)
767            .into_iter()
768            .map(|flag| flag.into_inner())
769            .collect();
770
771        let return_data = TransactionReturnData {
772            program_id: context.transaction_frame.return_data_pubkey,
773            data: context.return_data_bytes,
774        };
775
776        Self {
777            accounts,
778            return_data,
779            touched_flags,
780            accounts_resize_delta: Cell::into_inner(resize_delta),
781        }
782    }
783}
784
785#[cfg(all(test, not(target_arch = "sbf"), not(target_arch = "bpf")))]
786mod tests {
787    use super::*;
788
789    #[test]
790    fn test_instructions_sysvar_store_index_checked() {
791        let build_transaction_context = |account: AccountSharedData| {
792            TransactionContext::new(
793                vec![
794                    (Pubkey::new_unique(), AccountSharedData::default()),
795                    (instructions::id(), account),
796                ],
797                Rent::default(),
798                /* max_instruction_stack_depth */ 2,
799                /* max_instruction_trace_length */ 2,
800                /* number_of_top_level_instructions */ 1,
801            )
802        };
803
804        let correct_space = 2;
805        let rent_exempt_lamports = Rent::default().minimum_balance(correct_space);
806
807        // First try it with the wrong owner.
808        let account =
809            AccountSharedData::new(rent_exempt_lamports, correct_space, &Pubkey::new_unique());
810        assert_eq!(
811            build_transaction_context(account).push(),
812            Err(InstructionError::InvalidAccountOwner),
813        );
814
815        // Now with the wrong data length.
816        let account =
817            AccountSharedData::new(rent_exempt_lamports, 0, &solana_sdk_ids::sysvar::id());
818        assert_eq!(
819            build_transaction_context(account).push(),
820            Err(InstructionError::AccountDataTooSmall),
821        );
822
823        // Finally provide the correct account setup.
824        let account = AccountSharedData::new(
825            rent_exempt_lamports,
826            correct_space,
827            &solana_sdk_ids::sysvar::id(),
828        );
829        assert_eq!(build_transaction_context(account).push(), Ok(()),);
830    }
831
832    #[test]
833    fn test_invalid_native_loader_index() {
834        let mut transaction_context = TransactionContext::new(
835            vec![(
836                Pubkey::new_unique(),
837                AccountSharedData::new(1, 1, &Pubkey::new_unique()),
838            )],
839            Rent::default(),
840            20,
841            20,
842            1,
843        );
844
845        transaction_context
846            .configure_top_level_instruction_for_tests(
847                u16::MAX,
848                vec![InstructionAccount::new(0, false, false)],
849                vec![],
850            )
851            .unwrap();
852        let instruction_context = transaction_context.get_next_instruction_context().unwrap();
853
854        let result = instruction_context.get_index_of_program_account_in_transaction();
855        assert_eq!(result, Err(InstructionError::MissingAccount));
856
857        let result = instruction_context.get_program_key();
858        assert_eq!(result, Err(InstructionError::MissingAccount));
859
860        let result = instruction_context.get_program_owner();
861        assert_eq!(result.err(), Some(InstructionError::MissingAccount));
862    }
863
864    #[test]
865    fn test_instruction_shared_items() {
866        let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 11];
867        let mut transaction_context =
868            TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 3);
869
870        let instruction_accounts_1 = vec![
871            InstructionAccount::new(0, false, true),
872            InstructionAccount::new(3, true, false),
873        ];
874        transaction_context
875            .configure_top_level_instruction_for_tests(
876                1,
877                instruction_accounts_1.clone(),
878                vec![1, 2, 3, 4],
879            )
880            .unwrap();
881        transaction_context.push().unwrap();
882
883        let instruction_accounts_2 = vec![
884            InstructionAccount::new(0, false, true),
885            InstructionAccount::new(3, true, false),
886            InstructionAccount::new(5, false, false),
887        ];
888        transaction_context
889            .configure_top_level_instruction_for_tests(
890                1,
891                instruction_accounts_2.clone(),
892                vec![5, 6, 7, 8, 9],
893            )
894            .unwrap();
895        transaction_context.push().unwrap();
896
897        let instruction_accounts_3 = vec![
898            InstructionAccount::new(0, false, true),
899            InstructionAccount::new(3, true, false),
900            InstructionAccount::new(5, false, false),
901            InstructionAccount::new(3, false, false),
902            InstructionAccount::new(10, false, false),
903        ];
904        transaction_context
905            .configure_top_level_instruction_for_tests(
906                1,
907                instruction_accounts_3.clone(),
908                vec![10, 11],
909            )
910            .unwrap();
911        transaction_context.push().unwrap();
912
913        let first_ix_context = transaction_context
914            .get_instruction_context_at_index_in_trace(0)
915            .unwrap();
916        assert_eq!(
917            instruction_accounts_1.as_slice(),
918            first_ix_context.instruction_accounts
919        );
920        assert_eq!(
921            *first_ix_context.instruction_data,
922            **transaction_context.instruction_data.first().unwrap()
923        );
924        for (idx_in_ix, acc) in instruction_accounts_1.iter().enumerate() {
925            assert_eq!(
926                *first_ix_context
927                    .dedup_map
928                    .get(acc.index_in_transaction as usize)
929                    .unwrap(),
930                idx_in_ix as u8
931            );
932        }
933
934        let second_ix_context = transaction_context
935            .get_instruction_context_at_index_in_trace(1)
936            .unwrap();
937        assert_eq!(
938            instruction_accounts_2.as_slice(),
939            second_ix_context.instruction_accounts
940        );
941        assert_eq!(
942            *second_ix_context.instruction_data,
943            **transaction_context.instruction_data.get(1).unwrap()
944        );
945        for (idx_in_ix, acc) in instruction_accounts_2.iter().enumerate() {
946            assert_eq!(
947                *second_ix_context
948                    .dedup_map
949                    .get(acc.index_in_transaction as usize)
950                    .unwrap(),
951                idx_in_ix as u8
952            );
953        }
954
955        let third_ix_context = transaction_context
956            .get_instruction_context_at_index_in_trace(2)
957            .unwrap();
958        assert_eq!(
959            vec![
960                InstructionAccount::new(0, false, true),
961                InstructionAccount::new(3, true, false),
962                InstructionAccount::new(5, false, false),
963                InstructionAccount::new(3, true, false),
964                InstructionAccount::new(10, false, false),
965            ]
966            .as_slice(),
967            third_ix_context.instruction_accounts
968        );
969
970        assert_eq!(
971            *third_ix_context.instruction_data,
972            **transaction_context.instruction_data.get(2).unwrap()
973        );
974        for (idx_in_ix, acc) in instruction_accounts_3.iter().enumerate() {
975            if idx_in_ix == 3 {
976                assert_eq!(
977                    *third_ix_context
978                        .dedup_map
979                        .get(acc.index_in_transaction as usize)
980                        .unwrap(),
981                    1
982                );
983            } else {
984                assert_eq!(
985                    *third_ix_context
986                        .dedup_map
987                        .get(acc.index_in_transaction as usize)
988                        .unwrap(),
989                    idx_in_ix as u8
990                );
991            }
992        }
993    }
994
995    #[test]
996    fn test_number_of_instructions() {
997        let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 3];
998        let mut transaction_context =
999            TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 2);
1000        assert_eq!(
1001            transaction_context
1002                .transaction_frame
1003                .number_of_cpis_in_trace,
1004            0
1005        );
1006
1007        // Instruction #0
1008        transaction_context
1009            .configure_instruction_at_index(
1010                0,
1011                0,
1012                vec![InstructionAccount::new(1, false, false)],
1013                vec![0; 3],
1014                Vec::new().into(),
1015                None,
1016            )
1017            .unwrap();
1018
1019        // Instruction #1
1020        transaction_context
1021            .configure_instruction_at_index(
1022                1,
1023                0,
1024                vec![InstructionAccount::new(1, false, false)],
1025                vec![0; 3],
1026                Vec::new().into(),
1027                None,
1028            )
1029            .unwrap();
1030
1031        // Executing instruction #0
1032        transaction_context.push().unwrap();
1033        assert_eq!(
1034            transaction_context
1035                .transaction_frame
1036                .current_executing_instruction,
1037            0
1038        );
1039        assert_eq!(
1040            transaction_context.number_of_called_instructions_in_trace(),
1041            1
1042        );
1043
1044        assert_eq!(
1045            transaction_context
1046                .transaction_frame
1047                .total_number_of_instructions_in_trace,
1048            2
1049        );
1050
1051        assert_eq!(
1052            transaction_context
1053                .transaction_frame
1054                .number_of_cpis_in_trace,
1055            0
1056        );
1057
1058        assert_eq!(
1059            transaction_context
1060                .transaction_frame
1061                .cpi_data_scratchpad
1062                .ptr(),
1063            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(2))
1064        );
1065        assert_eq!(
1066            transaction_context
1067                .transaction_frame
1068                .cpi_data_scratchpad
1069                .len(),
1070            0,
1071        );
1072        assert_eq!(
1073            transaction_context
1074                .transaction_frame
1075                .cpi_accounts_scratchpad
1076                .ptr(),
1077            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1078                .saturating_add(GUEST_REGION_SIZE.saturating_mul(2))
1079        );
1080        assert_eq!(
1081            transaction_context
1082                .transaction_frame
1083                .cpi_data_scratchpad
1084                .len(),
1085            0,
1086        );
1087
1088        assert_eq!(
1089            transaction_context.number_of_called_instructions_in_trace(),
1090            1
1091        );
1092
1093        // Instruction #0 does a CPI.
1094        transaction_context
1095            .configure_next_cpi_for_tests(
1096                0,
1097                vec![InstructionAccount::new(2, false, true)],
1098                Vec::new(),
1099            )
1100            .unwrap();
1101
1102        transaction_context.push().unwrap();
1103        assert_eq!(
1104            transaction_context
1105                .transaction_frame
1106                .current_executing_instruction,
1107            2
1108        );
1109
1110        assert_eq!(
1111            transaction_context
1112                .transaction_frame
1113                .total_number_of_instructions_in_trace,
1114            3
1115        );
1116        assert_eq!(
1117            transaction_context
1118                .transaction_frame
1119                .number_of_cpis_in_trace,
1120            1
1121        );
1122        assert_eq!(
1123            transaction_context.number_of_called_instructions_in_trace(),
1124            2
1125        );
1126
1127        assert_eq!(
1128            transaction_context
1129                .transaction_frame
1130                .cpi_data_scratchpad
1131                .ptr(),
1132            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(3))
1133        );
1134        assert_eq!(
1135            transaction_context
1136                .transaction_frame
1137                .cpi_accounts_scratchpad
1138                .ptr(),
1139            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1140                .saturating_add(GUEST_REGION_SIZE.saturating_mul(3))
1141        );
1142
1143        // A nested CPI
1144        transaction_context
1145            .configure_next_cpi_for_tests(
1146                0,
1147                vec![InstructionAccount::new(2, false, true)],
1148                Vec::new(),
1149            )
1150            .unwrap();
1151
1152        transaction_context.push().unwrap();
1153        assert_eq!(
1154            transaction_context
1155                .transaction_frame
1156                .current_executing_instruction,
1157            3
1158        );
1159
1160        assert_eq!(
1161            transaction_context
1162                .transaction_frame
1163                .total_number_of_instructions_in_trace,
1164            4
1165        );
1166
1167        assert_eq!(
1168            transaction_context
1169                .transaction_frame
1170                .cpi_data_scratchpad
1171                .ptr(),
1172            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(4))
1173        );
1174        assert_eq!(
1175            transaction_context
1176                .transaction_frame
1177                .cpi_accounts_scratchpad
1178                .ptr(),
1179            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1180                .saturating_add(GUEST_REGION_SIZE.saturating_mul(4))
1181        );
1182
1183        assert_eq!(
1184            transaction_context
1185                .transaction_frame
1186                .number_of_cpis_in_trace,
1187            2
1188        );
1189
1190        assert_eq!(
1191            transaction_context.number_of_called_instructions_in_trace(),
1192            3
1193        );
1194        // Return from nested CPI
1195        transaction_context.pop().unwrap();
1196        assert_eq!(
1197            transaction_context.number_of_called_instructions_in_trace(),
1198            3
1199        );
1200
1201        assert_eq!(
1202            transaction_context
1203                .transaction_frame
1204                .total_number_of_instructions_in_trace,
1205            4
1206        );
1207        assert_eq!(
1208            transaction_context
1209                .transaction_frame
1210                .number_of_cpis_in_trace,
1211            2,
1212        );
1213        assert_eq!(
1214            transaction_context
1215                .transaction_frame
1216                .current_executing_instruction,
1217            2
1218        );
1219
1220        // A second nested CPI
1221        transaction_context
1222            .configure_next_cpi_for_tests(
1223                0,
1224                vec![InstructionAccount::new(2, false, true)],
1225                Vec::new(),
1226            )
1227            .unwrap();
1228
1229        transaction_context.push().unwrap();
1230        assert_eq!(
1231            transaction_context
1232                .transaction_frame
1233                .current_executing_instruction,
1234            4
1235        );
1236
1237        assert_eq!(
1238            transaction_context
1239                .transaction_frame
1240                .total_number_of_instructions_in_trace,
1241            5
1242        );
1243
1244        assert_eq!(
1245            transaction_context
1246                .transaction_frame
1247                .cpi_data_scratchpad
1248                .ptr(),
1249            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1250        );
1251        assert_eq!(
1252            transaction_context
1253                .transaction_frame
1254                .cpi_accounts_scratchpad
1255                .ptr(),
1256            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1257                .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1258        );
1259
1260        assert_eq!(
1261            transaction_context
1262                .transaction_frame
1263                .number_of_cpis_in_trace,
1264            3
1265        );
1266        assert_eq!(
1267            transaction_context.number_of_called_instructions_in_trace(),
1268            4
1269        );
1270
1271        // Return from second nested CPI
1272        transaction_context.pop().unwrap();
1273
1274        assert_eq!(
1275            transaction_context
1276                .transaction_frame
1277                .current_executing_instruction,
1278            2
1279        );
1280
1281        assert_eq!(
1282            transaction_context
1283                .transaction_frame
1284                .total_number_of_instructions_in_trace,
1285            5
1286        );
1287
1288        assert_eq!(
1289            transaction_context
1290                .transaction_frame
1291                .cpi_data_scratchpad
1292                .ptr(),
1293            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1294        );
1295        assert_eq!(
1296            transaction_context
1297                .transaction_frame
1298                .cpi_accounts_scratchpad
1299                .ptr(),
1300            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1301                .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1302        );
1303
1304        assert_eq!(
1305            transaction_context
1306                .transaction_frame
1307                .number_of_cpis_in_trace,
1308            3
1309        );
1310
1311        // Return from first CPI
1312        transaction_context.pop().unwrap();
1313        assert_eq!(
1314            transaction_context.number_of_called_instructions_in_trace(),
1315            4
1316        );
1317
1318        assert_eq!(
1319            transaction_context
1320                .transaction_frame
1321                .current_executing_instruction,
1322            0
1323        );
1324
1325        assert_eq!(
1326            transaction_context
1327                .transaction_frame
1328                .total_number_of_instructions_in_trace,
1329            5
1330        );
1331
1332        assert_eq!(
1333            transaction_context
1334                .transaction_frame
1335                .cpi_data_scratchpad
1336                .ptr(),
1337            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1338        );
1339        assert_eq!(
1340            transaction_context
1341                .transaction_frame
1342                .cpi_accounts_scratchpad
1343                .ptr(),
1344            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1345                .saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
1346        );
1347
1348        assert_eq!(
1349            transaction_context
1350                .transaction_frame
1351                .number_of_cpis_in_trace,
1352            3,
1353        );
1354
1355        // Let's go to Instruction #1 (top level)
1356        transaction_context.pop().unwrap();
1357        transaction_context.push().unwrap();
1358        assert_eq!(
1359            transaction_context
1360                .transaction_frame
1361                .current_executing_instruction,
1362            1,
1363        );
1364        assert_eq!(
1365            transaction_context
1366                .transaction_frame
1367                .number_of_cpis_in_trace,
1368            3
1369        );
1370
1371        // Instruction #1 will do a CPI.
1372        transaction_context
1373            .configure_next_cpi_for_tests(
1374                0,
1375                vec![InstructionAccount::new(2, false, true)],
1376                Vec::new(),
1377            )
1378            .unwrap();
1379
1380        transaction_context.push().unwrap();
1381
1382        assert_eq!(
1383            transaction_context
1384                .transaction_frame
1385                .current_executing_instruction,
1386            5,
1387        );
1388
1389        assert_eq!(
1390            transaction_context
1391                .transaction_frame
1392                .total_number_of_instructions_in_trace,
1393            6
1394        );
1395
1396        assert_eq!(
1397            transaction_context
1398                .transaction_frame
1399                .cpi_data_scratchpad
1400                .ptr(),
1401            GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(6))
1402        );
1403        assert_eq!(
1404            transaction_context
1405                .transaction_frame
1406                .cpi_accounts_scratchpad
1407                .ptr(),
1408            GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS
1409                .saturating_add(GUEST_REGION_SIZE.saturating_mul(6))
1410        );
1411
1412        assert_eq!(
1413            transaction_context
1414                .transaction_frame
1415                .number_of_cpis_in_trace,
1416            4
1417        );
1418        assert_eq!(
1419            transaction_context.number_of_called_instructions_in_trace(),
1420            6
1421        );
1422
1423        // Return from CPI
1424        transaction_context.pop().unwrap();
1425        assert_eq!(
1426            transaction_context
1427                .transaction_frame
1428                .number_of_cpis_in_trace,
1429            4
1430        );
1431        assert_eq!(
1432            transaction_context
1433                .transaction_frame
1434                .current_executing_instruction,
1435            1,
1436        );
1437
1438        transaction_context.pop().unwrap();
1439    }
1440
1441    #[test]
1442    fn test_get_current_instruction_index() {
1443        let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 3];
1444        let mut transaction_context =
1445            TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 2);
1446
1447        // First top level instruction
1448        transaction_context
1449            .configure_instruction_at_index(
1450                0,
1451                1,
1452                vec![
1453                    InstructionAccount::new(0, false, false),
1454                    InstructionAccount::new(1, false, false),
1455                ],
1456                vec![u8::MAX; 3],
1457                Cow::Owned(Vec::new()),
1458                None,
1459            )
1460            .unwrap();
1461
1462        // Second top-level instruction
1463        transaction_context
1464            .configure_instruction_at_index(
1465                1,
1466                1,
1467                vec![
1468                    InstructionAccount::new(0, false, false),
1469                    InstructionAccount::new(1, false, true),
1470                ],
1471                vec![u8::MAX; 3],
1472                Cow::Owned(Vec::new()),
1473                None,
1474            )
1475            .unwrap();
1476
1477        transaction_context.push().unwrap();
1478        assert_eq!(
1479            transaction_context.get_current_instruction_index().unwrap(),
1480            0
1481        );
1482
1483        transaction_context.pop().unwrap();
1484
1485        transaction_context.push().unwrap();
1486        assert_eq!(
1487            transaction_context.get_current_instruction_index().unwrap(),
1488            1
1489        );
1490
1491        // Simulating a CPI
1492        transaction_context
1493            .configure_next_cpi_for_tests(
1494                1,
1495                vec![
1496                    InstructionAccount::new(0, false, true),
1497                    InstructionAccount::new(1, false, false),
1498                ],
1499                Vec::new(),
1500            )
1501            .unwrap();
1502        transaction_context.push().unwrap();
1503        assert_eq!(
1504            transaction_context.get_current_instruction_index().unwrap(),
1505            2
1506        );
1507
1508        // Yet another CPI
1509        transaction_context
1510            .configure_next_cpi_for_tests(
1511                1,
1512                vec![
1513                    InstructionAccount::new(0, false, true),
1514                    InstructionAccount::new(1, false, false),
1515                ],
1516                Vec::new(),
1517            )
1518            .unwrap();
1519        transaction_context.push().unwrap();
1520        assert_eq!(
1521            transaction_context.get_current_instruction_index().unwrap(),
1522            3
1523        );
1524
1525        // CPI return
1526        transaction_context.pop().unwrap();
1527        assert_eq!(
1528            transaction_context.get_current_instruction_index().unwrap(),
1529            2
1530        );
1531
1532        // CPI return 2
1533        transaction_context.pop().unwrap();
1534        assert_eq!(
1535            transaction_context.get_current_instruction_index().unwrap(),
1536            1
1537        );
1538    }
1539
1540    #[test]
1541    fn test_deduplicate_accounts() {
1542        let mut instruction_accounts = vec![
1543            InstructionAccount::new(0, false, true), // Account 0, writable
1544            InstructionAccount::new(1, true, false), // Account 1, signer
1545            InstructionAccount::new(0, false, false), // Account 0 again, not writable
1546            InstructionAccount::new(2, false, true), // Account 2, writable
1547            InstructionAccount::new(1, true, false), // Account 1 again, signer
1548        ];
1549
1550        let dedup_map = TransactionContext::deduplicate_accounts(
1551            instruction_accounts.len(),
1552            &mut instruction_accounts,
1553        );
1554
1555        // Check that the dedup_map correctly maps duplicate accounts
1556        assert_eq!(
1557            *dedup_map.first().unwrap(),
1558            0,
1559            "account must be a duplicate of itself"
1560        );
1561        assert_eq!(
1562            *dedup_map.get(1).unwrap(),
1563            1,
1564            "account must be a duplicate of itself"
1565        );
1566        assert_eq!(
1567            *dedup_map.get(2).unwrap(),
1568            3,
1569            "account must be a duplicate of itself"
1570        );
1571
1572        // Check that duplicate accounts are properly merged
1573        let acc = instruction_accounts.first().unwrap();
1574        assert_eq!(acc.index_in_transaction, 0);
1575        assert!(
1576            !acc.is_signer(),
1577            "Must not be a signer because account 1 is not signer"
1578        );
1579        assert!(
1580            acc.is_writable(),
1581            "Must be writable because account 0 is writable"
1582        );
1583
1584        let acc = instruction_accounts.get(1).unwrap();
1585        assert_eq!(acc.index_in_transaction, 1);
1586        assert!(
1587            acc.is_signer(),
1588            "Must be signer because account 1 is signer"
1589        );
1590        assert!(
1591            !acc.is_writable(),
1592            "Must not be writable because account 1 is not writable"
1593        );
1594
1595        let acc = instruction_accounts.get(2).unwrap();
1596        assert_eq!(acc.index_in_transaction, 0);
1597        assert!(!acc.is_signer(), "Should be merged from account 1");
1598        assert!(acc.is_writable(), "Should be merged from account 0");
1599
1600        let acc = instruction_accounts.get(3).unwrap();
1601        assert_eq!(acc.index_in_transaction, 2);
1602        assert!(!acc.is_signer());
1603        assert!(acc.is_writable());
1604
1605        let acc = instruction_accounts.get(4).unwrap();
1606        assert_eq!(acc.index_in_transaction, 1);
1607        assert!(
1608            acc.is_signer(),
1609            "Must be signer because account 1 is signer"
1610        );
1611        assert!(
1612            !acc.is_writable(),
1613            "Must not be writable because account 1 is not writable"
1614        );
1615
1616        // Verify that the deduplication map correctly identifies duplicates
1617        assert_eq!(
1618            *dedup_map.first().unwrap(),
1619            0,
1620            "account must be a duplicate of itself"
1621        );
1622        assert_eq!(
1623            *dedup_map.get(1).unwrap(),
1624            1,
1625            "account must be a duplicate of itself"
1626        );
1627        assert_eq!(
1628            *dedup_map.get(2).unwrap(),
1629            3,
1630            "account must be a duplicate of itself"
1631        );
1632    }
1633
1634    #[test]
1635    fn test_deduplicate_accounts_no_duplicates() {
1636        let mut instruction_accounts = vec![
1637            InstructionAccount::new(0, false, true),
1638            InstructionAccount::new(1, true, false),
1639            InstructionAccount::new(2, false, false),
1640        ];
1641
1642        let dedup_map = TransactionContext::deduplicate_accounts(
1643            instruction_accounts.len(),
1644            &mut instruction_accounts,
1645        );
1646
1647        // Check that the dedup_map correctly maps each account to itself
1648        assert_eq!(
1649            *dedup_map.first().unwrap(),
1650            0,
1651            "account must be a duplicate of itself"
1652        );
1653        assert_eq!(
1654            *dedup_map.get(1).unwrap(),
1655            1,
1656            "account must be a duplicate of itself"
1657        );
1658        assert_eq!(
1659            *dedup_map.get(2).unwrap(),
1660            2,
1661            "account must be a duplicate of itself"
1662        );
1663
1664        // Check that accounts are not modified
1665        let acc = instruction_accounts.first().unwrap();
1666        assert_eq!(acc.index_in_transaction, 0);
1667        assert!(!acc.is_signer());
1668        assert!(acc.is_writable());
1669
1670        let acc = instruction_accounts.get(1).unwrap();
1671        assert_eq!(acc.index_in_transaction, 1);
1672        assert!(acc.is_signer());
1673        assert!(!acc.is_writable());
1674
1675        let acc = instruction_accounts.get(2).unwrap();
1676        assert_eq!(acc.index_in_transaction, 2);
1677        assert!(!acc.is_signer());
1678        assert!(!acc.is_writable());
1679    }
1680
1681    #[test]
1682    fn test_deduplicate_accounts_all_duplicates() {
1683        let mut instruction_accounts = vec![
1684            InstructionAccount::new(0, false, true),
1685            InstructionAccount::new(0, true, false),
1686            InstructionAccount::new(0, false, false),
1687        ];
1688
1689        let dedup_map = TransactionContext::deduplicate_accounts(
1690            instruction_accounts.len(),
1691            &mut instruction_accounts,
1692        );
1693
1694        // Check that all accounts map to the first occurrence (index 0)
1695        assert_eq!(
1696            *dedup_map.first().unwrap(),
1697            0,
1698            "account must be a duplicate of itself"
1699        );
1700        for idx in dedup_map.iter().skip(1) {
1701            assert_eq!(*idx, u8::MAX);
1702        }
1703
1704        // Check that the first account has combined flags
1705        let acc = instruction_accounts.first().unwrap();
1706        assert_eq!(acc.index_in_transaction, 0);
1707        assert!(
1708            acc.is_signer(),
1709            "Should be signer because of second account"
1710        );
1711        assert!(
1712            acc.is_writable(),
1713            "Should be writable because of first account"
1714        );
1715
1716        // Check that the other accounts have the same flags as the first
1717        let acc = instruction_accounts.get(1).unwrap();
1718        assert_eq!(acc.index_in_transaction, 0);
1719        assert!(acc.is_signer());
1720        assert!(acc.is_writable());
1721
1722        let acc = instruction_accounts.get(2).unwrap();
1723        assert_eq!(acc.index_in_transaction, 0);
1724        assert!(acc.is_signer());
1725        assert!(acc.is_writable());
1726    }
1727}