use {
crate::{
IndexOfAccount, MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION, MAX_ACCOUNT_DATA_LEN,
MAX_ACCOUNTS_PER_TRANSACTION,
instruction::{InstructionContext, InstructionFrame},
instruction_accounts::InstructionAccount,
transaction_accounts::{KeyedAccountSharedData, TransactionAccounts},
vm_addresses::{
GUEST_INSTRUCTION_DATA_BASE_ADDRESS, GUEST_REGION_SIZE, RETURN_DATA_SCRATCHPAD,
},
vm_slice::VmSlice,
},
solana_account::{AccountSharedData, ReadableAccount, WritableAccount},
solana_instruction::error::InstructionError,
solana_instructions_sysvar as instructions,
solana_pubkey::Pubkey,
solana_rent::Rent,
solana_sbpf::memory_region::{AccessType, AccessViolationHandler, MemoryRegion},
std::{borrow::Cow, cell::Cell, rc::Rc},
};
pub type InstructionTrace<'ix_data> = (
Vec<InstructionFrame>,
Vec<Box<[InstructionAccount]>>,
Vec<Cow<'ix_data, [u8]>>,
);
#[repr(C)]
#[derive(Debug)]
struct TransactionFrame {
return_data_pubkey: Pubkey,
return_data_scratchpad: VmSlice<u8>,
cpi_scratchpad: VmSlice<u8>,
current_executing_instruction: u16,
total_number_of_instructions_in_trace: u16,
number_of_cpis_in_trace: u16,
number_of_transaction_accounts: u16,
}
#[derive(Debug)]
#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
pub struct TransactionContext<'ix_data> {
pub(crate) accounts: Rc<TransactionAccounts>,
instruction_stack_capacity: usize,
instruction_trace_capacity: usize,
instruction_stack: Vec<usize>,
instruction_trace: Vec<InstructionFrame>,
transaction_frame: TransactionFrame,
return_data_bytes: Vec<u8>,
next_top_level_instruction_index: usize,
#[cfg(not(target_os = "solana"))]
pub(crate) rent: Rent,
deduplication_maps: Vec<Box<[u16]>>,
instruction_accounts: Vec<Box<[InstructionAccount]>>,
instruction_data: Vec<Cow<'ix_data, [u8]>>,
}
#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
impl<'ix_data> TransactionContext<'ix_data> {
pub fn new(
transaction_accounts: Vec<KeyedAccountSharedData>,
rent: Rent,
instruction_stack_capacity: usize,
instruction_trace_capacity: usize,
number_of_top_level_instructions: usize,
) -> Self {
let transaction_frame = TransactionFrame {
return_data_pubkey: Pubkey::default(),
return_data_scratchpad: VmSlice::new(RETURN_DATA_SCRATCHPAD, 0),
cpi_scratchpad: VmSlice::new(0, 0),
current_executing_instruction: 0,
total_number_of_instructions_in_trace: number_of_top_level_instructions as u16,
number_of_cpis_in_trace: 0,
number_of_transaction_accounts: transaction_accounts.len() as u16,
};
Self {
accounts: Rc::new(TransactionAccounts::new(transaction_accounts)),
instruction_stack_capacity,
instruction_trace_capacity,
instruction_stack: Vec::with_capacity(instruction_stack_capacity),
instruction_trace: vec![InstructionFrame::default()],
return_data_bytes: Vec::new(),
transaction_frame,
next_top_level_instruction_index: 0,
rent,
instruction_accounts: Vec::with_capacity(instruction_trace_capacity),
deduplication_maps: Vec::with_capacity(instruction_trace_capacity),
instruction_data: Vec::with_capacity(instruction_trace_capacity),
}
}
pub fn deconstruct_without_keys(self) -> Result<Vec<AccountSharedData>, InstructionError> {
if !self.instruction_stack.is_empty() {
return Err(InstructionError::CallDepth);
}
let accounts = Rc::try_unwrap(self.accounts)
.expect("transaction_context.accounts has unexpected outstanding refs")
.deconstruct_into_account_shared_data();
Ok(accounts)
}
pub fn accounts(&self) -> &Rc<TransactionAccounts> {
&self.accounts
}
pub fn get_number_of_accounts(&self) -> IndexOfAccount {
self.accounts.len() as IndexOfAccount
}
pub fn get_key_of_account_at_index(
&self,
index_in_transaction: IndexOfAccount,
) -> Result<&Pubkey, InstructionError> {
self.accounts
.account_key(index_in_transaction)
.ok_or(InstructionError::MissingAccount)
}
pub fn find_index_of_account(&self, pubkey: &Pubkey) -> Option<IndexOfAccount> {
self.accounts
.account_keys_iter()
.position(|key| key == pubkey)
.map(|index| index as IndexOfAccount)
}
pub fn get_instruction_trace_capacity(&self) -> usize {
self.instruction_trace_capacity
}
pub fn get_instruction_trace_length(&self) -> usize {
self.instruction_trace.len().saturating_sub(1)
}
pub fn get_instruction_context_at_index_in_trace(
&self,
index_in_trace: usize,
) -> Result<InstructionContext<'_, '_>, InstructionError> {
let instruction = self
.instruction_trace
.get(index_in_trace)
.ok_or(InstructionError::CallDepth)?;
let instruction_accounts = self
.instruction_accounts
.get(index_in_trace)
.map(|item| item.as_ref())
.unwrap_or_default();
let dedup_map = self
.deduplication_maps
.get(index_in_trace)
.map(|item| item.as_ref())
.unwrap_or_default();
let instruction_data = self
.instruction_data
.get(index_in_trace)
.map(|item| item.as_ref())
.unwrap_or_default();
Ok(InstructionContext {
transaction_context: self,
index_in_trace,
nesting_level: instruction.nesting_level as usize,
program_account_index_in_tx: instruction.program_account_index_in_tx as IndexOfAccount,
instruction_accounts,
dedup_map,
instruction_data,
index_of_caller_instruction: instruction.index_of_caller_instruction as usize,
})
}
pub fn get_instruction_context_at_nesting_level(
&self,
nesting_level: usize,
) -> Result<InstructionContext<'_, '_>, InstructionError> {
let index_in_trace = *self
.instruction_stack
.get(nesting_level)
.ok_or(InstructionError::CallDepth)?;
let instruction_context = self.get_instruction_context_at_index_in_trace(index_in_trace)?;
debug_assert_eq!(instruction_context.nesting_level, nesting_level);
Ok(instruction_context)
}
pub fn get_instruction_stack_capacity(&self) -> usize {
self.instruction_stack_capacity
}
pub fn get_instruction_stack_height(&self) -> usize {
self.instruction_stack.len()
}
pub fn get_current_instruction_index(&self) -> Result<usize, InstructionError> {
self.instruction_stack
.last()
.copied()
.ok_or(InstructionError::CallDepth)
}
pub fn get_current_instruction_context(
&self,
) -> Result<InstructionContext<'_, '_>, InstructionError> {
let index_in_trace = self.get_current_instruction_index()?;
self.get_instruction_context_at_index_in_trace(index_in_trace)
}
pub fn get_next_instruction_context(
&self,
) -> Result<InstructionContext<'_, '_>, InstructionError> {
let index_in_trace = self
.instruction_trace
.len()
.checked_sub(1)
.ok_or(InstructionError::CallDepth)?;
self.get_instruction_context_at_index_in_trace(index_in_trace)
}
pub fn configure_instruction_at_index(
&mut self,
instruction_index: usize,
program_index: IndexOfAccount,
instruction_accounts: Vec<InstructionAccount>,
deduplication_map: Vec<u16>,
instruction_data: Cow<'ix_data, [u8]>,
caller_index: Option<u16>,
) -> Result<(), InstructionError> {
debug_assert_eq!(deduplication_map.len(), MAX_ACCOUNTS_PER_TRANSACTION);
let instruction = self
.instruction_trace
.get_mut(instruction_index)
.ok_or(InstructionError::MaxInstructionTraceLengthExceeded)?;
if let Some(caller_index) = caller_index {
self.transaction_frame.total_number_of_instructions_in_trace = self
.transaction_frame
.total_number_of_instructions_in_trace
.saturating_add(1);
instruction.index_of_caller_instruction = caller_index;
}
self.transaction_frame.cpi_scratchpad = VmSlice::new(
GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(
self.transaction_frame.total_number_of_instructions_in_trace as u64,
)),
0,
);
instruction.program_account_index_in_tx = program_index;
instruction.configure_vm_slices(
instruction_index as u64,
instruction_accounts.len(),
instruction_data.len() as u64,
);
self.deduplication_maps
.push(deduplication_map.into_boxed_slice());
self.instruction_accounts
.push(instruction_accounts.into_boxed_slice());
self.instruction_data.push(instruction_data);
Ok(())
}
fn deduplicate_accounts_for_tests(instruction_accounts: &[InstructionAccount]) -> Vec<u16> {
let mut dedup_map = vec![u16::MAX; MAX_ACCOUNTS_PER_TRANSACTION];
for (idx, account) in instruction_accounts.iter().enumerate() {
let index_in_instruction = dedup_map
.get_mut(account.index_in_transaction as usize)
.unwrap();
if *index_in_instruction == u16::MAX {
*index_in_instruction = idx as u16;
}
}
dedup_map
}
pub fn configure_top_level_instruction_for_tests(
&mut self,
program_index: IndexOfAccount,
instruction_accounts: Vec<InstructionAccount>,
instruction_data: Vec<u8>,
) -> Result<(), InstructionError> {
debug_assert!(instruction_accounts.len() <= u16::MAX as usize);
let dedup_map = Self::deduplicate_accounts_for_tests(&instruction_accounts);
self.configure_instruction_at_index(
self.get_instruction_trace_length(),
program_index,
instruction_accounts,
dedup_map,
Cow::Owned(instruction_data),
None,
)?;
Ok(())
}
pub fn configure_next_cpi_for_tests(
&mut self,
program_index: IndexOfAccount,
instruction_accounts: Vec<InstructionAccount>,
instruction_data: Vec<u8>,
) -> Result<(), InstructionError> {
debug_assert!(instruction_accounts.len() <= u16::MAX as usize);
let dedup_map = Self::deduplicate_accounts_for_tests(&instruction_accounts);
let caller_index = self.get_current_instruction_index()?;
let cpi_index = self.get_instruction_trace_length();
self.configure_instruction_at_index(
cpi_index,
program_index,
instruction_accounts,
dedup_map,
Cow::Owned(instruction_data),
Some(caller_index as u16),
)?;
Ok(())
}
pub fn push(&mut self) -> Result<(), InstructionError> {
let nesting_level = self.get_instruction_stack_height();
if !self.instruction_stack.is_empty() && self.accounts.get_lamports_delta() != 0 {
return Err(InstructionError::UnbalancedInstruction);
}
{
let instruction = self
.instruction_trace
.last_mut()
.ok_or(InstructionError::CallDepth)?;
instruction.nesting_level = nesting_level as u16;
}
let index_in_trace = self.get_instruction_trace_length();
if index_in_trace >= self.instruction_trace_capacity {
return Err(InstructionError::MaxInstructionTraceLengthExceeded);
}
let current_top_level_instruction = if self.instruction_stack.is_empty() {
let index = self.next_top_level_instruction_index;
self.next_top_level_instruction_index =
self.next_top_level_instruction_index.saturating_add(1);
index
} else {
self.transaction_frame.number_of_cpis_in_trace = self
.transaction_frame
.number_of_cpis_in_trace
.saturating_add(1);
self.next_top_level_instruction_index.saturating_sub(1)
};
self.instruction_trace.push(InstructionFrame::default());
if nesting_level >= self.instruction_stack_capacity {
return Err(InstructionError::CallDepth);
}
self.transaction_frame.current_executing_instruction = index_in_trace as u16;
self.instruction_stack.push(index_in_trace);
if let Some(index_in_transaction) = self.find_index_of_account(&instructions::id()) {
let mut mut_account_ref = self.accounts.try_borrow_mut(index_in_transaction)?;
if mut_account_ref.owner() != &solana_sdk_ids::sysvar::id() {
return Err(InstructionError::InvalidAccountOwner);
}
instructions::store_current_index_checked(
mut_account_ref.data_as_mut_slice(),
current_top_level_instruction as u16,
)?;
}
Ok(())
}
pub fn pop(&mut self) -> Result<(), InstructionError> {
if self.instruction_stack.is_empty() {
return Err(InstructionError::CallDepth);
}
let detected_an_unbalanced_instruction =
self.get_current_instruction_context()
.and_then(|instruction_context| {
self.accounts
.try_borrow_mut(
instruction_context.get_index_of_program_account_in_transaction()?,
)
.map_err(|err| {
if err == InstructionError::AccountBorrowFailed {
InstructionError::AccountBorrowOutstanding
} else {
err
}
})?;
Ok(self.accounts.get_lamports_delta() != 0)
});
self.instruction_stack.pop();
if let Some(instr_idx) = self.instruction_stack.last() {
self.transaction_frame.current_executing_instruction = *instr_idx as u16;
}
if detected_an_unbalanced_instruction? {
Err(InstructionError::UnbalancedInstruction)
} else {
Ok(())
}
}
pub fn get_return_data(&self) -> (&Pubkey, &[u8]) {
(
&self.transaction_frame.return_data_pubkey,
&self.return_data_bytes,
)
}
pub fn set_return_data(
&mut self,
program_id: Pubkey,
data: Vec<u8>,
) -> Result<(), InstructionError> {
self.transaction_frame.return_data_pubkey = program_id;
unsafe {
self.transaction_frame
.return_data_scratchpad
.set_len(data.len() as u64);
}
self.return_data_bytes = data;
Ok(())
}
pub fn access_violation_handler(
&self,
virtual_address_space_adjustments: bool,
account_data_direct_mapping: bool,
) -> AccessViolationHandler {
let accounts = Rc::clone(&self.accounts);
Box::new(
move |region: &mut MemoryRegion,
address_space_reserved_for_account: u64,
access_type: AccessType,
vm_addr: u64,
len: u64| {
if access_type == AccessType::Load {
return;
}
let Some(index_in_transaction) = region.access_violation_handler_payload else {
return;
};
let requested_length =
vm_addr.saturating_add(len).saturating_sub(region.vm_addr) as usize;
if requested_length > address_space_reserved_for_account as usize {
return;
}
let Ok(mut account) = accounts.try_borrow_mut(index_in_transaction) else {
debug_assert!(false);
return;
};
if accounts.touch(index_in_transaction).is_err() {
debug_assert!(false);
return;
}
let remaining_allowed_growth = MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION
.saturating_sub(accounts.resize_delta())
.max(0) as usize;
if requested_length > region.len as usize {
let old_len = account.data().len();
let new_len = (address_space_reserved_for_account as usize)
.min(MAX_ACCOUNT_DATA_LEN as usize)
.min(old_len.saturating_add(remaining_allowed_growth));
debug_assert!(accounts.can_data_be_resized(old_len, new_len).is_ok());
if accounts
.update_accounts_resize_delta(old_len, new_len)
.is_err()
{
return;
}
account.resize(new_len, 0);
region.len = new_len as u64;
}
if virtual_address_space_adjustments && account_data_direct_mapping {
region.host_addr = account.data_as_mut_slice().as_mut_ptr() as u64;
region.writable = true;
}
},
)
}
pub fn take_instruction_trace(&mut self) -> InstructionTrace<'_> {
self.instruction_trace.pop();
(
std::mem::take(&mut self.instruction_trace),
std::mem::take(&mut self.instruction_accounts),
std::mem::take(&mut self.instruction_data),
)
}
pub fn number_of_active_instructions_in_trace(&self) -> usize {
self.next_top_level_instruction_index
.saturating_add(self.transaction_frame.number_of_cpis_in_trace as usize)
}
pub fn next_top_level_instruction_index(&self) -> usize {
self.next_top_level_instruction_index
}
pub fn number_of_cpis_in_trace(&self) -> usize {
self.transaction_frame.number_of_cpis_in_trace as usize
}
}
#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct TransactionReturnData {
pub program_id: Pubkey,
pub data: Vec<u8>,
}
#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
pub struct ExecutionRecord {
pub accounts: Vec<KeyedAccountSharedData>,
pub return_data: TransactionReturnData,
pub touched_account_count: u64,
pub accounts_resize_delta: i64,
}
#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
impl From<TransactionContext<'_>> for ExecutionRecord {
fn from(context: TransactionContext) -> Self {
let (accounts, touched_flags, resize_delta) = Rc::try_unwrap(context.accounts)
.expect("transaction_context.accounts has unexpected outstanding refs")
.take();
let touched_account_count = touched_flags
.iter()
.fold(0usize, |accumulator, was_touched| {
accumulator.saturating_add(was_touched.get() as usize)
}) as u64;
let return_data = TransactionReturnData {
program_id: context.transaction_frame.return_data_pubkey,
data: context.return_data_bytes,
};
Self {
accounts,
return_data,
touched_account_count,
accounts_resize_delta: Cell::into_inner(resize_delta),
}
}
}
#[cfg(all(test, not(target_arch = "sbf"), not(target_arch = "bpf")))]
mod tests {
use super::*;
#[test]
fn test_instructions_sysvar_store_index_checked() {
let build_transaction_context = |account: AccountSharedData| {
TransactionContext::new(
vec![
(Pubkey::new_unique(), AccountSharedData::default()),
(instructions::id(), account),
],
Rent::default(),
2,
2,
1,
)
};
let correct_space = 2;
let rent_exempt_lamports = Rent::default().minimum_balance(correct_space);
let account =
AccountSharedData::new(rent_exempt_lamports, correct_space, &Pubkey::new_unique());
assert_eq!(
build_transaction_context(account).push(),
Err(InstructionError::InvalidAccountOwner),
);
let account =
AccountSharedData::new(rent_exempt_lamports, 0, &solana_sdk_ids::sysvar::id());
assert_eq!(
build_transaction_context(account).push(),
Err(InstructionError::AccountDataTooSmall),
);
let account = AccountSharedData::new(
rent_exempt_lamports,
correct_space,
&solana_sdk_ids::sysvar::id(),
);
assert_eq!(build_transaction_context(account).push(), Ok(()),);
}
#[test]
fn test_invalid_native_loader_index() {
let mut transaction_context = TransactionContext::new(
vec![(
Pubkey::new_unique(),
AccountSharedData::new(1, 1, &Pubkey::new_unique()),
)],
Rent::default(),
20,
20,
1,
);
transaction_context
.configure_top_level_instruction_for_tests(
u16::MAX,
vec![InstructionAccount::new(0, false, false)],
vec![],
)
.unwrap();
let instruction_context = transaction_context.get_next_instruction_context().unwrap();
let result = instruction_context.get_index_of_program_account_in_transaction();
assert_eq!(result, Err(InstructionError::MissingAccount));
let result = instruction_context.get_program_key();
assert_eq!(result, Err(InstructionError::MissingAccount));
let result = instruction_context.get_program_owner();
assert_eq!(result.err(), Some(InstructionError::MissingAccount));
}
#[test]
fn test_instruction_shared_items() {
let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 10];
let mut transaction_context =
TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 3);
let instruction_accounts_1 = vec![
InstructionAccount::new(0, false, true),
InstructionAccount::new(3, true, false),
];
transaction_context
.configure_top_level_instruction_for_tests(
1,
instruction_accounts_1.clone(),
vec![1, 2, 3, 4],
)
.unwrap();
transaction_context.push().unwrap();
let instruction_accounts_2 = vec![
InstructionAccount::new(0, false, true),
InstructionAccount::new(3, true, false),
InstructionAccount::new(5, false, false),
];
transaction_context
.configure_top_level_instruction_for_tests(
1,
instruction_accounts_2.clone(),
vec![5, 6, 7, 8, 9],
)
.unwrap();
transaction_context.push().unwrap();
let instruction_accounts_3 = vec![
InstructionAccount::new(0, false, true),
InstructionAccount::new(3, true, false),
InstructionAccount::new(5, false, false),
InstructionAccount::new(3, false, false),
InstructionAccount::new(10, false, false),
];
transaction_context
.configure_top_level_instruction_for_tests(
1,
instruction_accounts_3.clone(),
vec![10, 11],
)
.unwrap();
transaction_context.push().unwrap();
let first_ix_context = transaction_context
.get_instruction_context_at_index_in_trace(0)
.unwrap();
assert_eq!(
instruction_accounts_1.as_slice(),
first_ix_context.instruction_accounts
);
assert_eq!(
*first_ix_context.instruction_data,
**transaction_context.instruction_data.first().unwrap()
);
for (idx_in_ix, acc) in instruction_accounts_1.iter().enumerate() {
assert_eq!(
*first_ix_context
.dedup_map
.get(acc.index_in_transaction as usize)
.unwrap(),
idx_in_ix as u16
);
}
let second_ix_context = transaction_context
.get_instruction_context_at_index_in_trace(1)
.unwrap();
assert_eq!(
instruction_accounts_2.as_slice(),
second_ix_context.instruction_accounts
);
assert_eq!(
*second_ix_context.instruction_data,
**transaction_context.instruction_data.get(1).unwrap()
);
for (idx_in_ix, acc) in instruction_accounts_2.iter().enumerate() {
assert_eq!(
*second_ix_context
.dedup_map
.get(acc.index_in_transaction as usize)
.unwrap(),
idx_in_ix as u16
);
}
let third_ix_context = transaction_context
.get_instruction_context_at_index_in_trace(2)
.unwrap();
assert_eq!(
instruction_accounts_3.as_slice(),
third_ix_context.instruction_accounts
);
assert_eq!(
*third_ix_context.instruction_data,
**transaction_context.instruction_data.get(2).unwrap()
);
for (idx_in_ix, acc) in instruction_accounts_3.iter().enumerate() {
if idx_in_ix == 3 {
assert_eq!(
*third_ix_context
.dedup_map
.get(acc.index_in_transaction as usize)
.unwrap(),
1
);
} else {
assert_eq!(
*third_ix_context
.dedup_map
.get(acc.index_in_transaction as usize)
.unwrap(),
idx_in_ix as u16
);
}
}
}
#[test]
fn test_number_of_instructions() {
let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 3];
let mut transaction_context =
TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 2);
assert_eq!(
transaction_context
.transaction_frame
.number_of_cpis_in_trace,
0
);
transaction_context
.configure_instruction_at_index(
0,
0,
vec![InstructionAccount::new(1, false, false)],
vec![0; MAX_ACCOUNTS_PER_TRANSACTION],
Vec::new().into(),
None,
)
.unwrap();
transaction_context.push().unwrap();
assert_eq!(
transaction_context
.transaction_frame
.current_executing_instruction,
0
);
assert_eq!(
transaction_context
.transaction_frame
.total_number_of_instructions_in_trace,
2
);
assert_eq!(
transaction_context
.transaction_frame
.number_of_cpis_in_trace,
0
);
assert_eq!(
transaction_context.transaction_frame.cpi_scratchpad.ptr(),
GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(2))
);
assert_eq!(
transaction_context.transaction_frame.cpi_scratchpad.len(),
0,
);
assert_eq!(
transaction_context.number_of_active_instructions_in_trace(),
1
);
transaction_context
.configure_next_cpi_for_tests(
0,
vec![InstructionAccount::new(2, false, true)],
Vec::new(),
)
.unwrap();
transaction_context.push().unwrap();
assert_eq!(
transaction_context
.transaction_frame
.current_executing_instruction,
1,
);
assert_eq!(
transaction_context
.transaction_frame
.total_number_of_instructions_in_trace,
3
);
assert_eq!(
transaction_context
.transaction_frame
.number_of_cpis_in_trace,
1
);
assert_eq!(
transaction_context.number_of_active_instructions_in_trace(),
2
);
assert_eq!(
transaction_context.transaction_frame.cpi_scratchpad.ptr(),
GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(3))
);
transaction_context
.configure_next_cpi_for_tests(
0,
vec![InstructionAccount::new(2, false, true)],
Vec::new(),
)
.unwrap();
transaction_context.push().unwrap();
assert_eq!(
transaction_context
.transaction_frame
.current_executing_instruction,
2
);
assert_eq!(
transaction_context
.transaction_frame
.total_number_of_instructions_in_trace,
4
);
assert_eq!(
transaction_context.transaction_frame.cpi_scratchpad.ptr(),
GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(4))
);
assert_eq!(
transaction_context
.transaction_frame
.number_of_cpis_in_trace,
2
);
assert_eq!(
transaction_context.number_of_active_instructions_in_trace(),
3
);
transaction_context.pop().unwrap();
assert_eq!(
transaction_context.number_of_active_instructions_in_trace(),
3
);
assert_eq!(
transaction_context
.transaction_frame
.total_number_of_instructions_in_trace,
4
);
assert_eq!(
transaction_context
.transaction_frame
.number_of_cpis_in_trace,
2,
);
assert_eq!(
transaction_context
.transaction_frame
.current_executing_instruction,
1
);
transaction_context
.configure_next_cpi_for_tests(
0,
vec![InstructionAccount::new(2, false, true)],
Vec::new(),
)
.unwrap();
transaction_context.push().unwrap();
assert_eq!(
transaction_context
.transaction_frame
.current_executing_instruction,
3
);
assert_eq!(
transaction_context
.transaction_frame
.total_number_of_instructions_in_trace,
5
);
assert_eq!(
transaction_context.transaction_frame.cpi_scratchpad.ptr(),
GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
);
assert_eq!(
transaction_context
.transaction_frame
.number_of_cpis_in_trace,
3
);
assert_eq!(
transaction_context.number_of_active_instructions_in_trace(),
4
);
transaction_context.pop().unwrap();
assert_eq!(
transaction_context
.transaction_frame
.current_executing_instruction,
1
);
assert_eq!(
transaction_context
.transaction_frame
.total_number_of_instructions_in_trace,
5
);
assert_eq!(
transaction_context.transaction_frame.cpi_scratchpad.ptr(),
GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
);
assert_eq!(
transaction_context
.transaction_frame
.number_of_cpis_in_trace,
3
);
transaction_context.pop().unwrap();
assert_eq!(
transaction_context.number_of_active_instructions_in_trace(),
4
);
assert_eq!(
transaction_context
.transaction_frame
.current_executing_instruction,
0
);
assert_eq!(
transaction_context
.transaction_frame
.total_number_of_instructions_in_trace,
5
);
assert_eq!(
transaction_context.transaction_frame.cpi_scratchpad.ptr(),
GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(5))
);
assert_eq!(
transaction_context
.transaction_frame
.number_of_cpis_in_trace,
3,
);
transaction_context.pop().unwrap();
transaction_context
.configure_top_level_instruction_for_tests(
0,
vec![InstructionAccount::new(1, false, false)],
Vec::new(),
)
.unwrap();
transaction_context.push().unwrap();
assert_eq!(
transaction_context
.transaction_frame
.current_executing_instruction,
4,
);
assert_eq!(
transaction_context
.transaction_frame
.number_of_cpis_in_trace,
3
);
transaction_context
.configure_next_cpi_for_tests(
0,
vec![InstructionAccount::new(2, false, true)],
Vec::new(),
)
.unwrap();
transaction_context.push().unwrap();
assert_eq!(
transaction_context
.transaction_frame
.current_executing_instruction,
5,
);
assert_eq!(
transaction_context
.transaction_frame
.total_number_of_instructions_in_trace,
6
);
assert_eq!(
transaction_context.transaction_frame.cpi_scratchpad.ptr(),
GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(GUEST_REGION_SIZE.saturating_mul(6))
);
assert_eq!(
transaction_context
.transaction_frame
.number_of_cpis_in_trace,
4
);
assert_eq!(
transaction_context.number_of_active_instructions_in_trace(),
6
);
transaction_context.pop().unwrap();
assert_eq!(
transaction_context
.transaction_frame
.number_of_cpis_in_trace,
4
);
assert_eq!(
transaction_context
.transaction_frame
.current_executing_instruction,
4,
);
transaction_context.pop().unwrap();
}
#[test]
fn test_get_current_instruction_index() {
let transaction_accounts = vec![(Pubkey::new_unique(), AccountSharedData::default()); 3];
let mut transaction_context =
TransactionContext::new(transaction_accounts, Rent::default(), 20, 20, 2);
transaction_context
.configure_instruction_at_index(
0,
1,
vec![
InstructionAccount::new(0, false, false),
InstructionAccount::new(1, false, false),
],
vec![u16::MAX; 256],
Cow::Owned(Vec::new()),
None,
)
.unwrap();
transaction_context.push().unwrap();
assert_eq!(
transaction_context.get_current_instruction_index().unwrap(),
0
);
transaction_context.pop().unwrap();
transaction_context
.configure_instruction_at_index(
1,
1,
vec![
InstructionAccount::new(0, false, false),
InstructionAccount::new(1, false, true),
],
vec![u16::MAX; 256],
Cow::Owned(Vec::new()),
None,
)
.unwrap();
transaction_context.push().unwrap();
assert_eq!(
transaction_context.get_current_instruction_index().unwrap(),
1
);
transaction_context
.configure_next_cpi_for_tests(
1,
vec![
InstructionAccount::new(0, false, true),
InstructionAccount::new(1, false, false),
],
Vec::new(),
)
.unwrap();
transaction_context.push().unwrap();
assert_eq!(
transaction_context.get_current_instruction_index().unwrap(),
2
);
transaction_context
.configure_next_cpi_for_tests(
1,
vec![
InstructionAccount::new(0, false, true),
InstructionAccount::new(1, false, false),
],
Vec::new(),
)
.unwrap();
transaction_context.push().unwrap();
assert_eq!(
transaction_context.get_current_instruction_index().unwrap(),
3
);
transaction_context.pop().unwrap();
assert_eq!(
transaction_context.get_current_instruction_index().unwrap(),
2
);
transaction_context.pop().unwrap();
assert_eq!(
transaction_context.get_current_instruction_index().unwrap(),
1
);
}
}