solana/program-runtime/src/invoke_context.rs

1765 lines
66 KiB
Rust
Raw Normal View History

#[allow(deprecated)]
use solana_sdk::keyed_account::{create_keyed_accounts_unified, KeyedAccount};
use {
crate::{
2021-12-20 20:03:20 -08:00
accounts_data_meter::AccountsDataMeter,
compute_budget::ComputeBudget,
2021-12-20 20:03:20 -08:00
ic_logger_msg, ic_msg,
log_collector::LogCollector,
pre_account::PreAccount,
stable_log,
sysvar_cache::SysvarCache,
2021-12-20 20:03:20 -08:00
timings::{ExecuteDetailsTimings, ExecuteTimings},
},
2021-12-20 20:03:20 -08:00
solana_measure::measure::Measure,
solana_sdk::{
account::{AccountSharedData, ReadableAccount},
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
feature_set::{
cap_accounts_data_len, record_instruction_in_transaction_context_push, FeatureSet,
},
hash::Hash,
instruction::{AccountMeta, Instruction, InstructionError},
native_loader,
pubkey::Pubkey,
rent::Rent,
2022-01-05 16:08:35 -08:00
saturating_add_assign,
transaction_context::{
InstructionAccount, InstructionContext, TransactionAccount, TransactionContext,
},
},
2022-04-11 16:05:09 -07:00
std::{
alloc::Layout,
borrow::Cow,
cell::RefCell,
collections::HashMap,
fmt::{self, Debug},
rc::Rc,
sync::Arc,
},
};
pub type ProcessInstructionWithContext =
fn(usize, &mut InvokeContext) -> Result<(), InstructionError>;
#[derive(Clone)]
pub struct BuiltinProgram {
pub program_id: Pubkey,
pub process_instruction: ProcessInstructionWithContext,
}
impl std::fmt::Debug for BuiltinProgram {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
// These are just type aliases for work around of Debug-ing above pointers
type ErasedProcessInstructionWithContext =
fn(usize, &'static mut InvokeContext<'static>) -> Result<(), InstructionError>;
// rustc doesn't compile due to bug without this work around
// https://github.com/rust-lang/rust/issues/50280
// https://users.rust-lang.org/t/display-function-pointer/17073/2
let erased_instruction: ErasedProcessInstructionWithContext = self.process_instruction;
write!(f, "{}: {:p}", self.program_id, erased_instruction)
}
}
/// Program executor
pub trait Executor: Debug + Send + Sync {
/// Execute the program
fn execute(
&self,
first_instruction_account: usize,
invoke_context: &mut InvokeContext,
) -> Result<(), InstructionError>;
}
pub type Executors = HashMap<Pubkey, TransactionExecutor>;
#[repr(u8)]
#[derive(PartialEq, Debug)]
enum TransactionExecutorStatus {
/// Executor was already in the cache, no update needed
Cached,
/// Executor was missing from the cache, but not updated
Missing,
/// Executor is for an updated program
Updated,
}
/// Tracks whether a given executor is "dirty" and needs to updated in the
/// executors cache
#[derive(Debug)]
pub struct TransactionExecutor {
executor: Arc<dyn Executor>,
status: TransactionExecutorStatus,
}
impl TransactionExecutor {
/// Wraps an executor and tracks that it doesn't need to be updated in the
/// executors cache.
pub fn new_cached(executor: Arc<dyn Executor>) -> Self {
Self {
executor,
status: TransactionExecutorStatus::Cached,
}
}
/// Wraps an executor and tracks that it needs to be updated in the
/// executors cache.
pub fn new_miss(executor: Arc<dyn Executor>) -> Self {
Self {
executor,
status: TransactionExecutorStatus::Missing,
}
}
/// Wraps an executor and tracks that it needs to be updated in the
/// executors cache only if the transaction succeeded.
pub fn new_updated(executor: Arc<dyn Executor>) -> Self {
Self {
executor,
status: TransactionExecutorStatus::Updated,
}
}
pub fn is_missing(&self) -> bool {
self.status == TransactionExecutorStatus::Missing
}
pub fn is_updated(&self) -> bool {
self.status == TransactionExecutorStatus::Updated
}
pub fn get(&self) -> Arc<dyn Executor> {
self.executor.clone()
}
}
/// Compute meter
pub struct ComputeMeter {
remaining: u64,
}
impl ComputeMeter {
/// Consume compute units
pub fn consume(&mut self, amount: u64) -> Result<(), InstructionError> {
let exceeded = self.remaining < amount;
self.remaining = self.remaining.saturating_sub(amount);
if exceeded {
return Err(InstructionError::ComputationalBudgetExceeded);
}
Ok(())
}
/// Get the number of remaining compute units
pub fn get_remaining(&self) -> u64 {
self.remaining
}
/// Set compute units
///
/// Only use for tests and benchmarks
pub fn mock_set_remaining(&mut self, remaining: u64) {
self.remaining = remaining;
}
/// Construct a new one with the given remaining units
pub fn new_ref(remaining: u64) -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(Self { remaining }))
}
}
2022-04-11 16:05:09 -07:00
/// Based loosely on the unstable std::alloc::Alloc trait
pub trait Alloc {
fn alloc(&mut self, layout: Layout) -> Result<u64, AllocErr>;
fn dealloc(&mut self, addr: u64, layout: Layout);
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct AllocErr;
impl fmt::Display for AllocErr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("Error: Memory allocation failed")
}
}
#[deprecated(
since = "1.11.0",
note = "Please use InstructionContext instead of StackFrame"
)]
#[allow(deprecated)]
pub struct StackFrame<'a> {
pub number_of_program_accounts: usize,
pub keyed_accounts: Vec<KeyedAccount<'a>>,
pub keyed_accounts_range: std::ops::Range<usize>,
}
#[allow(deprecated)]
impl<'a> StackFrame<'a> {
pub fn new(number_of_program_accounts: usize, keyed_accounts: Vec<KeyedAccount<'a>>) -> Self {
let keyed_accounts_range = std::ops::Range {
start: 0,
end: keyed_accounts.len(),
};
Self {
number_of_program_accounts,
keyed_accounts,
keyed_accounts_range,
}
}
pub fn program_id(&self) -> Option<&Pubkey> {
self.keyed_accounts
.get(self.number_of_program_accounts.saturating_sub(1))
.map(|keyed_account| keyed_account.unsigned_key())
}
}
2022-05-19 15:14:28 -07:00
struct SyscallContext {
check_aligned: bool,
check_size: bool,
orig_account_lengths: Vec<usize>,
allocator: Rc<RefCell<dyn Alloc>>,
}
pub struct InvokeContext<'a> {
pub transaction_context: &'a mut TransactionContext,
#[allow(deprecated)]
invoke_stack: Vec<StackFrame<'a>>,
rent: Rent,
pre_accounts: Vec<PreAccount>,
builtin_programs: &'a [BuiltinProgram],
pub sysvar_cache: Cow<'a, SysvarCache>,
log_collector: Option<Rc<RefCell<LogCollector>>>,
compute_budget: ComputeBudget,
2021-11-11 14:09:28 -08:00
current_compute_budget: ComputeBudget,
compute_meter: Rc<RefCell<ComputeMeter>>,
accounts_data_meter: AccountsDataMeter,
executors: Rc<RefCell<Executors>>,
pub feature_set: Arc<FeatureSet>,
pub timings: ExecuteDetailsTimings,
pub blockhash: Hash,
pub lamports_per_signature: u64,
2022-05-19 15:14:28 -07:00
syscall_context: Vec<Option<SyscallContext>>,
}
impl<'a> InvokeContext<'a> {
#[allow(clippy::too_many_arguments)]
pub fn new(
transaction_context: &'a mut TransactionContext,
rent: Rent,
builtin_programs: &'a [BuiltinProgram],
sysvar_cache: Cow<'a, SysvarCache>,
log_collector: Option<Rc<RefCell<LogCollector>>>,
compute_budget: ComputeBudget,
executors: Rc<RefCell<Executors>>,
feature_set: Arc<FeatureSet>,
blockhash: Hash,
lamports_per_signature: u64,
initial_accounts_data_len: u64,
) -> Self {
Self {
transaction_context,
invoke_stack: Vec::with_capacity(compute_budget.max_invoke_depth),
rent,
pre_accounts: Vec::new(),
builtin_programs,
sysvar_cache,
log_collector,
2021-11-11 14:09:28 -08:00
current_compute_budget: compute_budget,
compute_budget,
compute_meter: ComputeMeter::new_ref(compute_budget.compute_unit_limit),
accounts_data_meter: AccountsDataMeter::new(initial_accounts_data_len),
executors,
feature_set,
timings: ExecuteDetailsTimings::default(),
blockhash,
lamports_per_signature,
2022-05-19 15:14:28 -07:00
syscall_context: Vec::new(),
}
}
pub fn new_mock(
transaction_context: &'a mut TransactionContext,
builtin_programs: &'a [BuiltinProgram],
) -> Self {
let mut sysvar_cache = SysvarCache::default();
sysvar_cache.fill_missing_entries(|pubkey| {
(0..transaction_context.get_number_of_accounts()).find_map(|index| {
if transaction_context
.get_key_of_account_at_index(index)
.unwrap()
== pubkey
{
Some(
transaction_context
.get_account_at_index(index)
.unwrap()
.borrow()
.clone(),
)
} else {
None
}
})
});
Self::new(
transaction_context,
Rent::default(),
builtin_programs,
Cow::Owned(sysvar_cache),
Some(LogCollector::new_ref()),
ComputeBudget::default(),
Rc::new(RefCell::new(Executors::default())),
Arc::new(FeatureSet::all_enabled()),
Hash::default(),
0,
0,
)
}
/// Push a stack frame onto the invocation stack
pub fn push(
&mut self,
instruction_accounts: &[InstructionAccount],
program_indices: &[usize],
instruction_data: &[u8],
) -> Result<(), InstructionError> {
if !self
.feature_set
.is_active(&record_instruction_in_transaction_context_push::id())
&& self
.transaction_context
.get_instruction_context_stack_height()
> self.compute_budget.max_invoke_depth
{
return Err(InstructionError::CallDepth);
}
let program_id = self.transaction_context.get_key_of_account_at_index(
*program_indices
.last()
.ok_or(InstructionError::UnsupportedProgramId)?,
)?;
if self
.transaction_context
.get_instruction_context_stack_height()
== 0
{
self.current_compute_budget = self.compute_budget;
2021-11-11 14:09:28 -08:00
self.pre_accounts = Vec::with_capacity(instruction_accounts.len());
for (instruction_account_index, instruction_account) in
instruction_accounts.iter().enumerate()
{
if instruction_account_index != instruction_account.index_in_callee {
continue; // Skip duplicate account
}
if instruction_account.index_in_transaction
>= self.transaction_context.get_number_of_accounts()
{
return Err(InstructionError::MissingAccount);
}
let account = self
.transaction_context
.get_account_at_index(instruction_account.index_in_transaction)?
.borrow()
.clone();
self.pre_accounts.push(PreAccount::new(
self.transaction_context
.get_key_of_account_at_index(instruction_account.index_in_transaction)?,
account,
));
}
} else {
let contains = (0..self
.transaction_context
.get_instruction_context_stack_height())
.any(|level| {
self.transaction_context
.get_instruction_context_at(level)
.and_then(|instruction_context| {
instruction_context
.try_borrow_last_program_account(self.transaction_context)
})
.map(|program_account| program_account.get_key() == program_id)
.unwrap_or(false)
});
let is_last = self
.transaction_context
.get_current_instruction_context()
.and_then(|instruction_context| {
instruction_context.try_borrow_last_program_account(self.transaction_context)
})
.map(|program_account| program_account.get_key() == program_id)
.unwrap_or(false);
if contains && !is_last {
// Reentrancy not allowed unless caller is calling itself
return Err(InstructionError::ReentrancyNotAllowed);
}
}
// Create the KeyedAccounts that will be passed to the program
#[allow(deprecated)]
let keyed_accounts = program_indices
.iter()
.map(|account_index| {
Ok((
false,
false,
self.transaction_context
.get_key_of_account_at_index(*account_index)?,
self.transaction_context
.get_account_at_index(*account_index)?,
))
})
.chain(instruction_accounts.iter().map(|instruction_account| {
Ok((
instruction_account.is_signer,
instruction_account.is_writable,
self.transaction_context
.get_key_of_account_at_index(instruction_account.index_in_transaction)?,
self.transaction_context
.get_account_at_index(instruction_account.index_in_transaction)?,
))
}))
.collect::<Result<Vec<_>, InstructionError>>()?;
// Unsafe will be removed together with the keyed_accounts
#[allow(deprecated)]
self.invoke_stack.push(StackFrame::new(
program_indices.len(),
create_keyed_accounts_unified(unsafe {
std::mem::transmute(keyed_accounts.as_slice())
}),
));
2022-05-19 15:14:28 -07:00
self.syscall_context.push(None);
self.transaction_context.push(
program_indices,
instruction_accounts,
instruction_data,
self.feature_set
.is_active(&record_instruction_in_transaction_context_push::id()),
)
}
/// Pop a stack frame from the invocation stack
pub fn pop(&mut self) -> Result<(), InstructionError> {
2022-05-19 15:14:28 -07:00
self.syscall_context.pop();
self.invoke_stack.pop();
self.transaction_context.pop()
}
/// Current height of the invocation stack, top level instructions are height
/// `solana_sdk::instruction::TRANSACTION_LEVEL_STACK_HEIGHT`
pub fn get_stack_height(&self) -> usize {
self.transaction_context
.get_instruction_context_stack_height()
}
/// Verify the results of an instruction
///
/// Note: `instruction_accounts` must be the same as passed to `InvokeContext::push()`,
/// so that they match the order of `pre_accounts`.
fn verify(
&mut self,
instruction_accounts: &[InstructionAccount],
program_indices: &[usize],
) -> Result<(), InstructionError> {
let cap_accounts_data_len = self.feature_set.is_active(&cap_accounts_data_len::id());
let instruction_context = self
.transaction_context
.get_current_instruction_context()
.map_err(|_| InstructionError::CallDepth)?;
let program_id = instruction_context
.get_last_program_key(self.transaction_context)
.map_err(|_| InstructionError::CallDepth)?;
// Verify all executable accounts have zero outstanding refs
for account_index in program_indices.iter() {
self.transaction_context
.get_account_at_index(*account_index)?
.try_borrow_mut()
.map_err(|_| InstructionError::AccountBorrowOutstanding)?;
}
// Verify the per-account instruction results
let (mut pre_sum, mut post_sum) = (0_u128, 0_u128);
let mut pre_account_index = 0;
for (instruction_account_index, instruction_account) in
instruction_accounts.iter().enumerate()
{
if instruction_account_index != instruction_account.index_in_callee {
continue; // Skip duplicate account
}
{
// Verify account has no outstanding references
let _ = self
.transaction_context
.get_account_at_index(instruction_account.index_in_transaction)?
.try_borrow_mut()
.map_err(|_| InstructionError::AccountBorrowOutstanding)?;
}
2022-03-13 08:43:07 -07:00
let pre_account = &self
.pre_accounts
.get(pre_account_index)
.ok_or(InstructionError::NotEnoughAccountKeys)?;
pre_account_index = pre_account_index.saturating_add(1);
let account = self
.transaction_context
.get_account_at_index(instruction_account.index_in_transaction)?
.borrow();
pre_account
.verify(
program_id,
instruction_account.is_writable,
&self.rent,
&account,
&mut self.timings,
true,
)
.map_err(|err| {
ic_logger_msg!(
self.log_collector,
"failed to verify account {}: {}",
pre_account.key(),
err
);
err
})?;
pre_sum = pre_sum
.checked_add(u128::from(pre_account.lamports()))
.ok_or(InstructionError::UnbalancedInstruction)?;
post_sum = post_sum
.checked_add(u128::from(account.lamports()))
.ok_or(InstructionError::UnbalancedInstruction)?;
let pre_data_len = pre_account.data().len() as i64;
let post_data_len = account.data().len() as i64;
let data_len_delta = post_data_len.saturating_sub(pre_data_len);
if cap_accounts_data_len {
self.accounts_data_meter.adjust_delta(data_len_delta)?;
} else {
self.accounts_data_meter
.adjust_delta_unchecked(data_len_delta);
}
}
// Verify that the total sum of all the lamports did not change
if pre_sum != post_sum {
return Err(InstructionError::UnbalancedInstruction);
}
Ok(())
}
/// Verify and update PreAccount state based on program execution
///
/// Note: `instruction_accounts` must be the same as passed to `InvokeContext::push()`,
/// so that they match the order of `pre_accounts`.
fn verify_and_update(
&mut self,
instruction_accounts: &[InstructionAccount],
before_instruction_context_push: bool,
) -> Result<(), InstructionError> {
let cap_accounts_data_len = self.feature_set.is_active(&cap_accounts_data_len::id());
let transaction_context = &self.transaction_context;
let instruction_context = transaction_context.get_current_instruction_context()?;
let program_id = instruction_context
.get_last_program_key(transaction_context)
.map_err(|_| InstructionError::CallDepth)?;
// Verify the per-account instruction results
let (mut pre_sum, mut post_sum) = (0_u128, 0_u128);
for (instruction_account_index, instruction_account) in
instruction_accounts.iter().enumerate()
{
if instruction_account_index != instruction_account.index_in_callee {
continue; // Skip duplicate account
}
if instruction_account.index_in_transaction
< transaction_context.get_number_of_accounts()
{
let key = transaction_context
.get_key_of_account_at_index(instruction_account.index_in_transaction)?;
let account = transaction_context
.get_account_at_index(instruction_account.index_in_transaction)?;
let is_writable = if before_instruction_context_push {
instruction_context
.is_instruction_account_writable(instruction_account.index_in_caller)?
} else {
instruction_account.is_writable
};
// Find the matching PreAccount
for pre_account in self.pre_accounts.iter_mut() {
if key == pre_account.key() {
{
// Verify account has no outstanding references
let _ = account
.try_borrow_mut()
.map_err(|_| InstructionError::AccountBorrowOutstanding)?;
}
let account = account.borrow();
pre_account
.verify(
program_id,
is_writable,
&self.rent,
&account,
&mut self.timings,
false,
)
.map_err(|err| {
ic_logger_msg!(
self.log_collector,
"failed to verify account {}: {}",
key,
err
);
err
})?;
pre_sum = pre_sum
.checked_add(u128::from(pre_account.lamports()))
.ok_or(InstructionError::UnbalancedInstruction)?;
post_sum = post_sum
.checked_add(u128::from(account.lamports()))
.ok_or(InstructionError::UnbalancedInstruction)?;
if is_writable && !pre_account.executable() {
pre_account.update(account.clone());
}
let pre_data_len = pre_account.data().len() as i64;
let post_data_len = account.data().len() as i64;
let data_len_delta = post_data_len.saturating_sub(pre_data_len);
if cap_accounts_data_len {
self.accounts_data_meter.adjust_delta(data_len_delta)?;
} else {
self.accounts_data_meter
.adjust_delta_unchecked(data_len_delta);
}
break;
}
}
}
}
// Verify that the total sum of all the lamports did not change
if pre_sum != post_sum {
return Err(InstructionError::UnbalancedInstruction);
}
Ok(())
}
/// Entrypoint for a cross-program invocation from a builtin program
pub fn native_invoke(
&mut self,
instruction: Instruction,
signers: &[Pubkey],
) -> Result<(), InstructionError> {
let (instruction_accounts, program_indices) =
self.prepare_instruction(&instruction, signers)?;
let mut prev_account_sizes = Vec::with_capacity(instruction_accounts.len());
for instruction_account in instruction_accounts.iter() {
let account_length = self
.transaction_context
.get_account_at_index(instruction_account.index_in_transaction)?
.borrow()
.data()
.len();
prev_account_sizes.push((instruction_account.index_in_transaction, account_length));
}
let mut compute_units_consumed = 0;
self.process_instruction(
&instruction.data,
&instruction_accounts,
&program_indices,
&mut compute_units_consumed,
2021-12-20 20:03:20 -08:00
&mut ExecuteTimings::default(),
)?;
Ok(())
}
/// Helper to prepare for process_instruction()
#[allow(clippy::type_complexity)]
pub fn prepare_instruction(
&mut self,
instruction: &Instruction,
signers: &[Pubkey],
) -> Result<(Vec<InstructionAccount>, Vec<usize>), InstructionError> {
// Finds the index of each account in the instruction by its pubkey.
// Then normalizes / unifies the privileges of duplicate accounts.
// Note: This is an O(n^2) algorithm,
// but performed on a very small slice and requires no heap allocations.
let instruction_context = self.transaction_context.get_current_instruction_context()?;
let mut deduplicated_instruction_accounts: Vec<InstructionAccount> = Vec::new();
let mut duplicate_indicies = Vec::with_capacity(instruction.accounts.len());
for (instruction_account_index, account_meta) in instruction.accounts.iter().enumerate() {
let index_in_transaction = self
.transaction_context
.find_index_of_account(&account_meta.pubkey)
.ok_or_else(|| {
ic_msg!(
self,
"Instruction references an unknown account {}",
account_meta.pubkey,
);
InstructionError::MissingAccount
})?;
if let Some(duplicate_index) =
deduplicated_instruction_accounts
.iter()
.position(|instruction_account| {
instruction_account.index_in_transaction == index_in_transaction
})
{
duplicate_indicies.push(duplicate_index);
2022-03-13 08:43:07 -07:00
let instruction_account = deduplicated_instruction_accounts
.get_mut(duplicate_index)
.ok_or(InstructionError::NotEnoughAccountKeys)?;
instruction_account.is_signer |= account_meta.is_signer;
instruction_account.is_writable |= account_meta.is_writable;
} else {
let index_in_caller = instruction_context
.find_index_of_instruction_account(
self.transaction_context,
&account_meta.pubkey,
)
.ok_or_else(|| {
ic_msg!(
self,
"Instruction references an unknown account {}",
account_meta.pubkey,
);
InstructionError::MissingAccount
})?;
duplicate_indicies.push(deduplicated_instruction_accounts.len());
deduplicated_instruction_accounts.push(InstructionAccount {
index_in_transaction,
index_in_caller,
index_in_callee: instruction_account_index,
is_signer: account_meta.is_signer,
is_writable: account_meta.is_writable,
});
}
}
for instruction_account in deduplicated_instruction_accounts.iter() {
let borrowed_account = instruction_context.try_borrow_instruction_account(
self.transaction_context,
instruction_account.index_in_caller,
)?;
// Readonly in caller cannot become writable in callee
if instruction_account.is_writable && !borrowed_account.is_writable() {
ic_msg!(
self,
"{}'s writable privilege escalated",
borrowed_account.get_key(),
);
return Err(InstructionError::PrivilegeEscalation);
}
// To be signed in the callee,
// it must be either signed in the caller or by the program
if instruction_account.is_signer
&& !(borrowed_account.is_signer() || signers.contains(borrowed_account.get_key()))
{
ic_msg!(
self,
"{}'s signer privilege escalated",
borrowed_account.get_key()
);
return Err(InstructionError::PrivilegeEscalation);
}
}
2022-03-13 08:43:07 -07:00
let instruction_accounts = duplicate_indicies
.into_iter()
2022-03-13 08:43:07 -07:00
.map(|duplicate_index| {
Ok(deduplicated_instruction_accounts
.get(duplicate_index)
.ok_or(InstructionError::NotEnoughAccountKeys)?
.clone())
})
.collect::<Result<Vec<InstructionAccount>, InstructionError>>()?;
// Find and validate executables / program accounts
let callee_program_id = instruction.program_id;
let program_account_index = instruction_context
.find_index_of_instruction_account(self.transaction_context, &callee_program_id)
.ok_or_else(|| {
ic_msg!(self, "Unknown program {}", callee_program_id);
InstructionError::MissingAccount
})?;
let borrowed_program_account = instruction_context
.try_borrow_instruction_account(self.transaction_context, program_account_index)?;
if !borrowed_program_account.is_executable() {
ic_msg!(self, "Account {} is not executable", callee_program_id);
return Err(InstructionError::AccountNotExecutable);
}
let mut program_indices = vec![];
if borrowed_program_account.get_owner() == &bpf_loader_upgradeable::id() {
if let UpgradeableLoaderState::Program {
programdata_address,
} = borrowed_program_account.get_state()?
{
if let Some(programdata_account_index) = self
.transaction_context
.find_index_of_program_account(&programdata_address)
{
program_indices.push(programdata_account_index);
} else {
ic_msg!(
self,
"Unknown upgradeable programdata account {}",
programdata_address,
);
return Err(InstructionError::MissingAccount);
}
} else {
ic_msg!(
self,
"Invalid upgradeable program account {}",
callee_program_id,
);
return Err(InstructionError::MissingAccount);
}
}
program_indices.push(borrowed_program_account.get_index_in_transaction());
Ok((instruction_accounts, program_indices))
}
/// Processes an instruction and returns how many compute units were used
pub fn process_instruction(
&mut self,
instruction_data: &[u8],
instruction_accounts: &[InstructionAccount],
program_indices: &[usize],
compute_units_consumed: &mut u64,
2021-12-20 20:03:20 -08:00
timings: &mut ExecuteTimings,
) -> Result<(), InstructionError> {
*compute_units_consumed = 0;
let nesting_level = self
.transaction_context
.get_instruction_context_stack_height();
let is_top_level_instruction = nesting_level == 0;
if !is_top_level_instruction {
// Verify the calling program hasn't misbehaved
2021-12-20 20:03:20 -08:00
let mut verify_caller_time = Measure::start("verify_caller_time");
let verify_caller_result = self.verify_and_update(instruction_accounts, true);
verify_caller_time.stop();
2022-01-05 16:08:35 -08:00
saturating_add_assign!(
timings
.execute_accessories
.process_instructions
.verify_caller_us,
2022-01-05 16:08:35 -08:00
verify_caller_time.as_us()
);
2021-12-20 20:03:20 -08:00
verify_caller_result?;
if !self
.feature_set
.is_active(&record_instruction_in_transaction_context_push::id())
{
self.transaction_context
.record_instruction(InstructionContext::new(
nesting_level,
program_indices,
instruction_accounts,
instruction_data,
));
}
}
self.push(instruction_accounts, program_indices, instruction_data)?;
self.process_executable_chain(compute_units_consumed, timings)
.and_then(|_| {
// Verify the called program has not misbehaved
2021-12-20 20:03:20 -08:00
let mut verify_callee_time = Measure::start("verify_callee_time");
let result = if is_top_level_instruction {
self.verify(instruction_accounts, program_indices)
} else {
self.verify_and_update(instruction_accounts, false)
};
2021-12-20 20:03:20 -08:00
verify_callee_time.stop();
2022-01-05 16:08:35 -08:00
saturating_add_assign!(
timings
.execute_accessories
.process_instructions
.verify_callee_us,
2022-01-05 16:08:35 -08:00
verify_callee_time.as_us()
);
2021-12-20 20:03:20 -08:00
result
})
// MUST pop if and only if `push` succeeded, independent of `result`.
// Thus, the `.and()` instead of an `.and_then()`.
.and(self.pop())
}
/// Calls the instruction's program entrypoint method
fn process_executable_chain(
&mut self,
compute_units_consumed: &mut u64,
timings: &mut ExecuteTimings,
) -> Result<(), InstructionError> {
let instruction_context = self.transaction_context.get_current_instruction_context()?;
let mut process_executable_chain_time = Measure::start("process_executable_chain_time");
let (first_instruction_account, builtin_id) = {
let borrowed_root_account = instruction_context
.try_borrow_program_account(self.transaction_context, 0)
.map_err(|_| InstructionError::UnsupportedProgramId)?;
let owner_id = borrowed_root_account.get_owner();
if native_loader::check_id(owner_id) {
(1, *borrowed_root_account.get_key())
} else {
(0, *owner_id)
}
};
for entry in self.builtin_programs {
if entry.program_id == builtin_id {
let program_id =
*instruction_context.get_last_program_key(self.transaction_context)?;
self.transaction_context
.set_return_data(program_id, Vec::new())?;
let pre_remaining_units = self.compute_meter.borrow().get_remaining();
let result = if builtin_id == program_id {
let logger = self.get_log_collector();
stable_log::program_invoke(&logger, &program_id, self.get_stack_height());
(entry.process_instruction)(first_instruction_account, self)
.map(|()| {
stable_log::program_success(&logger, &program_id);
})
.map_err(|err| {
stable_log::program_failure(&logger, &program_id, &err);
err
})
} else {
(entry.process_instruction)(first_instruction_account, self)
};
let post_remaining_units = self.compute_meter.borrow().get_remaining();
*compute_units_consumed = pre_remaining_units.saturating_sub(post_remaining_units);
process_executable_chain_time.stop();
saturating_add_assign!(
timings
.execute_accessories
.process_instructions
.process_executable_chain_us,
process_executable_chain_time.as_us()
);
return result;
}
}
Err(InstructionError::UnsupportedProgramId)
}
#[deprecated(
since = "1.11.0",
note = "Please use BorrowedAccount instead of KeyedAccount"
)]
#[allow(deprecated)]
/// Get the list of keyed accounts including the chain of program accounts
pub fn get_keyed_accounts(&self) -> Result<&[KeyedAccount], InstructionError> {
self.invoke_stack
.last()
2022-03-13 08:43:07 -07:00
.and_then(|frame| frame.keyed_accounts.get(frame.keyed_accounts_range.clone()))
.ok_or(InstructionError::CallDepth)
}
/// Get this invocation's LogCollector
pub fn get_log_collector(&self) -> Option<Rc<RefCell<LogCollector>>> {
self.log_collector.clone()
}
/// Get this invocation's ComputeMeter
pub fn get_compute_meter(&self) -> Rc<RefCell<ComputeMeter>> {
self.compute_meter.clone()
}
/// Get this invocation's AccountsDataMeter
pub fn get_accounts_data_meter(&self) -> &AccountsDataMeter {
&self.accounts_data_meter
}
/// Cache an executor that wasn't found in the cache
pub fn add_executor(&self, pubkey: &Pubkey, executor: Arc<dyn Executor>) {
self.executors
.borrow_mut()
.insert(*pubkey, TransactionExecutor::new_miss(executor));
}
/// Cache an executor that has changed
pub fn update_executor(&self, pubkey: &Pubkey, executor: Arc<dyn Executor>) {
self.executors
.borrow_mut()
.insert(*pubkey, TransactionExecutor::new_updated(executor));
}
/// Get the completed loader work that can be re-used across execution
pub fn get_executor(&self, pubkey: &Pubkey) -> Option<Arc<dyn Executor>> {
self.executors
.borrow()
.get(pubkey)
.map(|tx_executor| tx_executor.executor.clone())
}
/// Get this invocation's compute budget
pub fn get_compute_budget(&self) -> &ComputeBudget {
2021-11-11 14:09:28 -08:00
&self.current_compute_budget
}
/// Get cached sysvars
pub fn get_sysvar_cache(&self) -> &SysvarCache {
&self.sysvar_cache
}
// Get pubkey of account at index
pub fn get_key_of_account_at_index(
&self,
index_in_transaction: usize,
) -> Result<&Pubkey, InstructionError> {
self.transaction_context
.get_key_of_account_at_index(index_in_transaction)
}
2022-04-11 16:05:09 -07:00
2022-05-19 15:14:28 -07:00
// Set this instruction syscall context
pub fn set_syscall_context(
2022-04-11 16:05:09 -07:00
&mut self,
2022-05-19 15:14:28 -07:00
check_aligned: bool,
check_size: bool,
2022-04-11 16:05:09 -07:00
orig_account_lengths: Vec<usize>,
2022-05-19 15:14:28 -07:00
allocator: Rc<RefCell<dyn Alloc>>,
2022-04-11 16:05:09 -07:00
) -> Result<(), InstructionError> {
*self
2022-05-19 15:14:28 -07:00
.syscall_context
2022-04-11 16:05:09 -07:00
.last_mut()
2022-05-19 15:14:28 -07:00
.ok_or(InstructionError::CallDepth)? = Some(SyscallContext {
check_aligned,
check_size,
orig_account_lengths,
allocator,
});
2022-04-11 16:05:09 -07:00
Ok(())
}
2022-04-12 17:52:47 -07:00
// Should alignment be enforced during user pointer translation
2022-04-11 16:05:09 -07:00
pub fn get_check_aligned(&self) -> bool {
2022-05-19 15:14:28 -07:00
self.syscall_context
.last()
.and_then(|context| context.as_ref())
.map(|context| context.check_aligned)
.unwrap_or(true)
2022-04-11 16:05:09 -07:00
}
// Set should type size be checked during user pointer translation
2022-05-19 15:14:28 -07:00
pub fn get_check_size(&self) -> bool {
self.syscall_context
.last()
.and_then(|context| context.as_ref())
.map(|context| context.check_size)
.unwrap_or(true)
2022-04-11 16:05:09 -07:00
}
2022-05-19 15:14:28 -07:00
/// Get the original account lengths
pub fn get_orig_account_lengths(&self) -> Result<&[usize], InstructionError> {
self.syscall_context
.last()
.and_then(|context| context.as_ref())
.map(|context| context.orig_account_lengths.as_slice())
.ok_or(InstructionError::CallDepth)
2022-04-11 16:05:09 -07:00
}
// Get this instruction's memory allocator
pub fn get_allocator(&self) -> Result<Rc<RefCell<dyn Alloc>>, InstructionError> {
2022-05-19 15:14:28 -07:00
self.syscall_context
.last()
2022-05-19 15:14:28 -07:00
.and_then(|context| context.as_ref())
.map(|context| context.allocator.clone())
.ok_or(InstructionError::CallDepth)
}
}
pub struct MockInvokeContextPreparation {
pub transaction_accounts: Vec<TransactionAccount>,
pub instruction_accounts: Vec<InstructionAccount>,
}
pub fn prepare_mock_invoke_context(
transaction_accounts: Vec<TransactionAccount>,
instruction_account_metas: Vec<AccountMeta>,
_program_indices: &[usize],
) -> MockInvokeContextPreparation {
let mut instruction_accounts: Vec<InstructionAccount> =
Vec::with_capacity(instruction_account_metas.len());
for (instruction_account_index, account_meta) in instruction_account_metas.iter().enumerate() {
let index_in_transaction = transaction_accounts
.iter()
.position(|(key, _account)| *key == account_meta.pubkey)
.unwrap_or(transaction_accounts.len());
let index_in_callee = instruction_accounts
.get(0..instruction_account_index)
.unwrap()
.iter()
.position(|instruction_account| {
instruction_account.index_in_transaction == index_in_transaction
})
.unwrap_or(instruction_account_index);
instruction_accounts.push(InstructionAccount {
index_in_transaction,
index_in_caller: index_in_transaction,
index_in_callee,
is_signer: account_meta.is_signer,
is_writable: account_meta.is_writable,
});
}
MockInvokeContextPreparation {
transaction_accounts,
instruction_accounts,
}
}
pub fn with_mock_invoke_context<R, F: FnMut(&mut InvokeContext) -> R>(
loader_id: Pubkey,
account_size: usize,
mut callback: F,
) -> R {
let program_indices = vec![0, 1];
let transaction_accounts = vec![
(
loader_id,
AccountSharedData::new(0, 0, &native_loader::id()),
),
(
Pubkey::new_unique(),
AccountSharedData::new(1, 0, &loader_id),
),
(
Pubkey::new_unique(),
AccountSharedData::new(2, account_size, &Pubkey::new_unique()),
),
];
let instruction_accounts = vec![AccountMeta {
2022-03-13 08:43:07 -07:00
pubkey: transaction_accounts.get(2).unwrap().0,
is_signer: false,
is_writable: false,
}];
let preparation =
prepare_mock_invoke_context(transaction_accounts, instruction_accounts, &program_indices);
let mut transaction_context = TransactionContext::new(
preparation.transaction_accounts,
ComputeBudget::default().max_invoke_depth.saturating_add(1),
1,
);
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
invoke_context
.push(&preparation.instruction_accounts, &program_indices, &[])
.unwrap();
callback(&mut invoke_context)
}
pub fn mock_process_instruction(
loader_id: &Pubkey,
mut program_indices: Vec<usize>,
instruction_data: &[u8],
transaction_accounts: Vec<TransactionAccount>,
instruction_accounts: Vec<AccountMeta>,
sysvar_cache_override: Option<&SysvarCache>,
feature_set_override: Option<Arc<FeatureSet>>,
expected_result: Result<(), InstructionError>,
process_instruction: ProcessInstructionWithContext,
) -> Vec<AccountSharedData> {
program_indices.insert(0, transaction_accounts.len());
let mut preparation =
prepare_mock_invoke_context(transaction_accounts, instruction_accounts, &program_indices);
let processor_account = AccountSharedData::new(0, 0, &native_loader::id());
preparation
.transaction_accounts
.push((*loader_id, processor_account));
let mut transaction_context = TransactionContext::new(
preparation.transaction_accounts,
ComputeBudget::default().max_invoke_depth.saturating_add(1),
1,
);
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
if let Some(sysvar_cache) = sysvar_cache_override {
invoke_context.sysvar_cache = Cow::Borrowed(sysvar_cache);
}
if let Some(feature_set) = feature_set_override {
invoke_context.feature_set = feature_set;
}
let result = invoke_context
.push(
&preparation.instruction_accounts,
&program_indices,
instruction_data,
)
.and_then(|_| process_instruction(1, &mut invoke_context))
.and_then(|_| invoke_context.verify(&preparation.instruction_accounts, &program_indices));
invoke_context.pop().unwrap();
assert_eq!(result, expected_result);
let mut transaction_accounts = transaction_context.deconstruct_without_keys().unwrap();
transaction_accounts.pop();
transaction_accounts
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::compute_budget,
serde::{Deserialize, Serialize},
solana_sdk::account::{ReadableAccount, WritableAccount},
};
#[derive(Debug, Serialize, Deserialize)]
enum MockInstruction {
NoopSuccess,
NoopFail,
ModifyOwned,
ModifyNotOwned,
ModifyReadonly,
ConsumeComputeUnits {
compute_units_to_consume: u64,
desired_result: Result<(), InstructionError>,
},
Resize {
new_len: usize,
},
}
#[test]
fn test_program_entry_debug() {
#[allow(clippy::unnecessary_wraps)]
fn mock_process_instruction(
_first_instruction_account: usize,
_invoke_context: &mut InvokeContext,
) -> Result<(), InstructionError> {
Ok(())
}
#[allow(clippy::unnecessary_wraps)]
fn mock_ix_processor(
_first_instruction_account: usize,
_invoke_context: &mut InvokeContext,
) -> Result<(), InstructionError> {
Ok(())
}
let builtin_programs = &[
BuiltinProgram {
program_id: solana_sdk::pubkey::new_rand(),
process_instruction: mock_process_instruction,
},
BuiltinProgram {
program_id: solana_sdk::pubkey::new_rand(),
process_instruction: mock_ix_processor,
},
];
assert!(!format!("{:?}", builtin_programs).is_empty());
}
#[allow(clippy::integer_arithmetic)]
fn mock_process_instruction(
_first_instruction_account: usize,
invoke_context: &mut InvokeContext,
) -> Result<(), InstructionError> {
let transaction_context = &invoke_context.transaction_context;
let instruction_context = transaction_context.get_current_instruction_context()?;
let instruction_data = instruction_context.get_instruction_data();
let program_id = instruction_context.get_last_program_key(transaction_context)?;
assert_eq!(
program_id,
instruction_context
.try_borrow_instruction_account(transaction_context, 0)?
.get_owner()
);
assert_ne!(
instruction_context
.try_borrow_instruction_account(transaction_context, 1)?
.get_owner(),
instruction_context
.try_borrow_instruction_account(transaction_context, 0)?
.get_key()
);
if let Ok(instruction) = bincode::deserialize(instruction_data) {
match instruction {
MockInstruction::NoopSuccess => (),
MockInstruction::NoopFail => return Err(InstructionError::GenericError),
MockInstruction::ModifyOwned => instruction_context
.try_borrow_instruction_account(transaction_context, 0)?
.set_data(&[1])
.unwrap(),
MockInstruction::ModifyNotOwned => instruction_context
.try_borrow_instruction_account(transaction_context, 1)?
.set_data(&[1])
.unwrap(),
MockInstruction::ModifyReadonly => instruction_context
.try_borrow_instruction_account(transaction_context, 2)?
.set_data(&[1])
.unwrap(),
MockInstruction::ConsumeComputeUnits {
compute_units_to_consume,
desired_result,
} => {
invoke_context
.get_compute_meter()
.borrow_mut()
.consume(compute_units_to_consume)
.unwrap();
return desired_result;
}
MockInstruction::Resize { new_len } => instruction_context
.try_borrow_instruction_account(transaction_context, 0)?
.set_data(&vec![0; new_len])
.unwrap(),
}
} else {
return Err(InstructionError::InvalidInstructionData);
}
Ok(())
}
#[test]
fn test_invoke_context() {
const MAX_DEPTH: usize = 10;
let mut invoke_stack = vec![];
let mut accounts = vec![];
let mut instruction_accounts = vec![];
for index in 0..MAX_DEPTH {
invoke_stack.push(solana_sdk::pubkey::new_rand());
accounts.push((
solana_sdk::pubkey::new_rand(),
2022-03-13 08:43:07 -07:00
AccountSharedData::new(index as u64, 1, invoke_stack.get(index).unwrap()),
));
instruction_accounts.push(InstructionAccount {
index_in_transaction: index,
index_in_caller: index,
index_in_callee: instruction_accounts.len(),
is_signer: false,
is_writable: true,
});
}
for (index, program_id) in invoke_stack.iter().enumerate() {
accounts.push((
*program_id,
AccountSharedData::new(1, 1, &solana_sdk::pubkey::Pubkey::default()),
));
instruction_accounts.push(InstructionAccount {
index_in_transaction: index,
index_in_caller: index,
index_in_callee: index,
is_signer: false,
is_writable: false,
});
}
let mut transaction_context =
TransactionContext::new(accounts, ComputeBudget::default().max_invoke_depth, 1);
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
// Check call depth increases and has a limit
let mut depth_reached = 0;
for _ in 0..invoke_stack.len() {
if Err(InstructionError::CallDepth)
== invoke_context.push(&instruction_accounts, &[MAX_DEPTH + depth_reached], &[])
{
break;
}
depth_reached += 1;
}
assert_ne!(depth_reached, 0);
assert!(depth_reached < MAX_DEPTH);
// Mock each invocation
for owned_index in (1..depth_reached).rev() {
let not_owned_index = owned_index - 1;
let instruction_accounts = vec![
InstructionAccount {
index_in_transaction: not_owned_index,
index_in_caller: not_owned_index,
index_in_callee: 0,
is_signer: false,
is_writable: true,
},
InstructionAccount {
index_in_transaction: owned_index,
index_in_caller: owned_index,
index_in_callee: 1,
is_signer: false,
is_writable: true,
},
];
// modify account owned by the program
2022-03-13 08:43:07 -07:00
*invoke_context
.transaction_context
.get_account_at_index(owned_index)
.unwrap()
.borrow_mut()
2022-03-13 08:43:07 -07:00
.data_as_mut_slice()
.get_mut(0)
.unwrap() = (MAX_DEPTH + owned_index) as u8;
invoke_context
.verify_and_update(&instruction_accounts, false)
.unwrap();
assert_eq!(
2022-03-13 08:43:07 -07:00
*invoke_context
.pre_accounts
.get(owned_index)
.unwrap()
.data()
2022-06-21 13:26:51 -07:00
.first()
2022-03-13 08:43:07 -07:00
.unwrap(),
(MAX_DEPTH + owned_index) as u8
);
// modify account not owned by the program
2022-03-13 08:43:07 -07:00
let data = *invoke_context
.transaction_context
.get_account_at_index(not_owned_index)
.unwrap()
.borrow_mut()
2022-03-13 08:43:07 -07:00
.data()
2022-06-21 13:26:51 -07:00
.first()
2022-03-13 08:43:07 -07:00
.unwrap();
*invoke_context
.transaction_context
.get_account_at_index(not_owned_index)
.unwrap()
.borrow_mut()
2022-03-13 08:43:07 -07:00
.data_as_mut_slice()
.get_mut(0)
.unwrap() = (MAX_DEPTH + not_owned_index) as u8;
assert_eq!(
invoke_context.verify_and_update(&instruction_accounts, false),
Err(InstructionError::ExternalAccountDataModified)
);
2022-03-13 08:43:07 -07:00
assert_eq!(
*invoke_context
.pre_accounts
.get(not_owned_index)
.unwrap()
.data()
2022-06-21 13:26:51 -07:00
.first()
2022-03-13 08:43:07 -07:00
.unwrap(),
data
);
*invoke_context
.transaction_context
.get_account_at_index(not_owned_index)
.unwrap()
.borrow_mut()
2022-03-13 08:43:07 -07:00
.data_as_mut_slice()
.get_mut(0)
.unwrap() = data;
invoke_context.pop().unwrap();
}
}
#[test]
fn test_invoke_context_verify() {
let accounts = vec![(solana_sdk::pubkey::new_rand(), AccountSharedData::default())];
let instruction_accounts = vec![];
let program_indices = vec![0];
let mut transaction_context = TransactionContext::new(accounts, 1, 1);
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
invoke_context
.push(&instruction_accounts, &program_indices, &[])
.unwrap();
assert!(invoke_context
.verify(&instruction_accounts, &program_indices)
.is_ok());
}
#[test]
fn test_process_instruction() {
let callee_program_id = solana_sdk::pubkey::new_rand();
let builtin_programs = &[BuiltinProgram {
program_id: callee_program_id,
process_instruction: mock_process_instruction,
}];
let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
let not_owned_account = AccountSharedData::new(84, 1, &solana_sdk::pubkey::new_rand());
let readonly_account = AccountSharedData::new(168, 1, &solana_sdk::pubkey::new_rand());
let loader_account = AccountSharedData::new(0, 0, &native_loader::id());
let mut program_account = AccountSharedData::new(1, 0, &native_loader::id());
program_account.set_executable(true);
let accounts = vec![
(solana_sdk::pubkey::new_rand(), owned_account),
(solana_sdk::pubkey::new_rand(), not_owned_account),
(solana_sdk::pubkey::new_rand(), readonly_account),
(callee_program_id, program_account),
(solana_sdk::pubkey::new_rand(), loader_account),
];
let metas = vec![
2022-03-13 08:43:07 -07:00
AccountMeta::new(accounts.get(0).unwrap().0, false),
AccountMeta::new(accounts.get(1).unwrap().0, false),
AccountMeta::new_readonly(accounts.get(2).unwrap().0, false),
];
let instruction_accounts = (0..4)
.map(|instruction_account_index| InstructionAccount {
index_in_transaction: instruction_account_index,
index_in_caller: instruction_account_index,
index_in_callee: instruction_account_index,
is_signer: false,
is_writable: instruction_account_index < 2,
})
.collect::<Vec<_>>();
let mut transaction_context = TransactionContext::new(accounts, 2, 8);
let mut invoke_context =
InvokeContext::new_mock(&mut transaction_context, builtin_programs);
// External modification tests
{
invoke_context
.push(&instruction_accounts, &[4], &[])
.unwrap();
let inner_instruction = Instruction::new_with_bincode(
callee_program_id,
&MockInstruction::NoopSuccess,
metas.clone(),
);
// not owned account
2022-03-13 08:43:07 -07:00
*invoke_context
.transaction_context
.get_account_at_index(1)
.unwrap()
.borrow_mut()
2022-03-13 08:43:07 -07:00
.data_as_mut_slice()
.get_mut(0)
.unwrap() = 1;
assert_eq!(
invoke_context.native_invoke(inner_instruction.clone(), &[]),
Err(InstructionError::ExternalAccountDataModified)
);
2022-03-13 08:43:07 -07:00
*invoke_context
.transaction_context
.get_account_at_index(1)
.unwrap()
.borrow_mut()
2022-03-13 08:43:07 -07:00
.data_as_mut_slice()
.get_mut(0)
.unwrap() = 0;
// readonly account
2022-03-13 08:43:07 -07:00
*invoke_context
.transaction_context
.get_account_at_index(2)
.unwrap()
.borrow_mut()
2022-03-13 08:43:07 -07:00
.data_as_mut_slice()
.get_mut(0)
.unwrap() = 1;
assert_eq!(
invoke_context.native_invoke(inner_instruction, &[]),
Err(InstructionError::ReadonlyDataModified)
);
2022-03-13 08:43:07 -07:00
*invoke_context
.transaction_context
.get_account_at_index(2)
.unwrap()
.borrow_mut()
2022-03-13 08:43:07 -07:00
.data_as_mut_slice()
.get_mut(0)
.unwrap() = 0;
invoke_context.pop().unwrap();
}
// Internal modification tests
let cases = vec![
(MockInstruction::NoopSuccess, Ok(())),
(
MockInstruction::NoopFail,
Err(InstructionError::GenericError),
),
(MockInstruction::ModifyOwned, Ok(())),
(
MockInstruction::ModifyNotOwned,
Err(InstructionError::ExternalAccountDataModified),
),
(
MockInstruction::ModifyReadonly,
Err(InstructionError::ReadonlyDataModified),
),
];
for case in cases {
invoke_context
.push(&instruction_accounts, &[4], &[])
.unwrap();
let inner_instruction =
Instruction::new_with_bincode(callee_program_id, &case.0, metas.clone());
assert_eq!(invoke_context.native_invoke(inner_instruction, &[]), case.1);
invoke_context.pop().unwrap();
}
// Compute unit consumption tests
let compute_units_to_consume = 10;
let expected_results = vec![Ok(()), Err(InstructionError::GenericError)];
for expected_result in expected_results {
invoke_context
.push(&instruction_accounts, &[4], &[])
.unwrap();
let inner_instruction = Instruction::new_with_bincode(
callee_program_id,
&MockInstruction::ConsumeComputeUnits {
compute_units_to_consume,
desired_result: expected_result.clone(),
},
metas.clone(),
);
let (inner_instruction_accounts, program_indices) = invoke_context
.prepare_instruction(&inner_instruction, &[])
.unwrap();
let mut compute_units_consumed = 0;
let result = invoke_context.process_instruction(
&inner_instruction.data,
&inner_instruction_accounts,
&program_indices,
&mut compute_units_consumed,
2021-12-20 20:03:20 -08:00
&mut ExecuteTimings::default(),
);
// Because the instruction had compute cost > 0, then regardless of the execution result,
// the number of compute units consumed should be a non-default which is something greater
// than zero.
assert!(compute_units_consumed > 0);
assert_eq!(compute_units_consumed, compute_units_to_consume);
assert_eq!(result, expected_result);
invoke_context.pop().unwrap();
}
}
2021-11-11 14:09:28 -08:00
#[test]
fn test_invoke_context_compute_budget() {
let accounts = vec![(solana_sdk::pubkey::new_rand(), AccountSharedData::default())];
2021-11-11 14:09:28 -08:00
let mut transaction_context = TransactionContext::new(accounts, 1, 3);
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
invoke_context.compute_budget =
ComputeBudget::new(compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64);
2021-11-11 14:09:28 -08:00
invoke_context.push(&[], &[0], &[]).unwrap();
2021-11-11 14:09:28 -08:00
assert_eq!(
*invoke_context.get_compute_budget(),
ComputeBudget::new(compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64)
2021-11-11 14:09:28 -08:00
);
invoke_context.pop().unwrap();
2021-11-11 14:09:28 -08:00
}
#[test]
fn test_process_instruction_accounts_data_meter() {
solana_logger::setup();
let program_key = Pubkey::new_unique();
let user_account_data_len = 123;
let user_account = AccountSharedData::new(100, user_account_data_len, &program_key);
let dummy_account = AccountSharedData::new(10, 0, &program_key);
let mut program_account = AccountSharedData::new(500, 500, &native_loader::id());
program_account.set_executable(true);
let accounts = vec![
(Pubkey::new_unique(), user_account),
(Pubkey::new_unique(), dummy_account),
(program_key, program_account),
];
let builtin_programs = [BuiltinProgram {
program_id: program_key,
process_instruction: mock_process_instruction,
}];
let mut transaction_context = TransactionContext::new(accounts, 1, 3);
let mut invoke_context =
InvokeContext::new_mock(&mut transaction_context, &builtin_programs);
invoke_context
.accounts_data_meter
.set_initial(user_account_data_len as u64);
invoke_context
.accounts_data_meter
.set_maximum(user_account_data_len as u64 * 3);
let remaining_account_data_len = invoke_context.accounts_data_meter.remaining() as usize;
let instruction_accounts = [
InstructionAccount {
index_in_transaction: 0,
index_in_caller: 0,
index_in_callee: 0,
is_signer: false,
is_writable: true,
},
InstructionAccount {
index_in_transaction: 1,
index_in_caller: 1,
index_in_callee: 1,
is_signer: false,
is_writable: false,
},
];
// Test 1: Resize the account to use up all the space; this must succeed
{
let new_len = user_account_data_len + remaining_account_data_len;
let instruction_data =
bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
let result = invoke_context.process_instruction(
&instruction_data,
&instruction_accounts,
&[2],
&mut 0,
2021-12-20 20:03:20 -08:00
&mut ExecuteTimings::default(),
);
assert!(result.is_ok());
assert_eq!(invoke_context.accounts_data_meter.remaining(), 0);
}
// Test 2: Resize the account to *the same size*, so not consuming any additional size; this must succeed
{
let new_len = user_account_data_len + remaining_account_data_len;
let instruction_data =
bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
let result = invoke_context.process_instruction(
&instruction_data,
&instruction_accounts,
&[2],
&mut 0,
2021-12-20 20:03:20 -08:00
&mut ExecuteTimings::default(),
);
assert!(result.is_ok());
assert_eq!(invoke_context.accounts_data_meter.remaining(), 0);
}
// Test 3: Resize the account to exceed the budget; this must fail
{
let new_len = user_account_data_len + remaining_account_data_len + 1;
let instruction_data =
bincode::serialize(&MockInstruction::Resize { new_len }).unwrap();
let result = invoke_context.process_instruction(
&instruction_data,
&instruction_accounts,
&[2],
&mut 0,
2021-12-20 20:03:20 -08:00
&mut ExecuteTimings::default(),
);
assert!(result.is_err());
assert!(matches!(
result,
Err(solana_sdk::instruction::InstructionError::MaxAccountsDataSizeExceeded)
));
assert_eq!(invoke_context.accounts_data_meter.remaining(), 0);
}
}
}