2021-12-03 09:00:31 -08:00
|
|
|
use {
|
|
|
|
crate::{
|
2021-12-28 05:14:48 -06:00
|
|
|
accounts_data_meter::AccountsDataMeter, ic_logger_msg, ic_msg,
|
|
|
|
instruction_recorder::InstructionRecorder, log_collector::LogCollector,
|
|
|
|
native_loader::NativeLoader, pre_account::PreAccount, timings::ExecuteDetailsTimings,
|
2021-12-03 09:00:31 -08:00
|
|
|
},
|
|
|
|
solana_sdk::{
|
|
|
|
account::{AccountSharedData, ReadableAccount},
|
|
|
|
account_utils::StateMut,
|
|
|
|
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
|
|
|
|
compute_budget::ComputeBudget,
|
|
|
|
feature_set::{
|
2021-12-16 17:27:22 -05:00
|
|
|
do_support_realloc, neon_evm_compute_budget, reject_empty_instruction_without_program,
|
|
|
|
remove_native_loader, requestable_heap_size, tx_wide_compute_cap, FeatureSet,
|
2021-12-03 09:00:31 -08:00
|
|
|
},
|
|
|
|
hash::Hash,
|
2021-12-25 13:35:43 +01:00
|
|
|
instruction::{AccountMeta, CompiledInstruction, Instruction, InstructionError},
|
2021-12-03 09:00:31 -08:00
|
|
|
keyed_account::{create_keyed_accounts_unified, keyed_account_at_index, KeyedAccount},
|
2021-12-24 16:17:55 +01:00
|
|
|
native_loader,
|
2021-12-03 09:00:31 -08:00
|
|
|
pubkey::Pubkey,
|
|
|
|
rent::Rent,
|
|
|
|
sysvar::Sysvar,
|
2021-12-27 18:49:32 +01:00
|
|
|
transaction_context::{InstructionAccount, TransactionAccount, TransactionContext},
|
2021-11-04 21:47:32 +01:00
|
|
|
},
|
2021-12-03 09:00:31 -08:00
|
|
|
std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc, sync::Arc},
|
2021-11-04 21:47:32 +01:00
|
|
|
};
|
2021-12-01 08:54:42 +01:00
|
|
|
|
|
|
|
pub type ProcessInstructionWithContext =
|
2021-12-02 18:47:16 +01:00
|
|
|
fn(usize, &[u8], &mut InvokeContext) -> Result<(), InstructionError>;
|
2021-12-01 08:54:42 +01:00
|
|
|
|
|
|
|
#[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 [u8],
|
2021-12-02 18:47:16 +01:00
|
|
|
&'static mut InvokeContext<'static>,
|
2021-12-01 08:54:42 +01:00
|
|
|
) -> 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
|
2021-12-02 18:47:16 +01:00
|
|
|
fn execute<'a, 'b>(
|
2021-12-01 08:54:42 +01:00
|
|
|
&self,
|
|
|
|
first_instruction_account: usize,
|
|
|
|
instruction_data: &[u8],
|
2021-12-02 18:47:16 +01:00
|
|
|
invoke_context: &'a mut InvokeContext<'b>,
|
2021-12-01 08:54:42 +01:00
|
|
|
use_jit: bool,
|
|
|
|
) -> Result<(), InstructionError>;
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
pub struct Executors {
|
|
|
|
pub executors: HashMap<Pubkey, Arc<dyn Executor>>,
|
|
|
|
pub is_dirty: bool,
|
|
|
|
}
|
|
|
|
impl Executors {
|
|
|
|
pub fn insert(&mut self, key: Pubkey, executor: Arc<dyn Executor>) {
|
|
|
|
let _ = self.executors.insert(key, executor);
|
|
|
|
self.is_dirty = true;
|
|
|
|
}
|
|
|
|
pub fn get(&self, key: &Pubkey) -> Option<Arc<dyn Executor>> {
|
|
|
|
self.executors.get(key).cloned()
|
|
|
|
}
|
|
|
|
}
|
2021-11-04 21:47:32 +01:00
|
|
|
|
2021-11-17 19:35:07 +01:00
|
|
|
/// Compute meter
|
2021-11-23 13:23:40 +01:00
|
|
|
pub struct ComputeMeter {
|
2021-11-04 21:47:32 +01:00
|
|
|
remaining: u64,
|
|
|
|
}
|
2021-11-23 13:23:40 +01:00
|
|
|
impl ComputeMeter {
|
|
|
|
/// Consume compute units
|
|
|
|
pub fn consume(&mut self, amount: u64) -> Result<(), InstructionError> {
|
2021-11-04 21:47:32 +01:00
|
|
|
let exceeded = self.remaining < amount;
|
|
|
|
self.remaining = self.remaining.saturating_sub(amount);
|
|
|
|
if exceeded {
|
|
|
|
return Err(InstructionError::ComputationalBudgetExceeded);
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
2021-11-23 13:23:40 +01:00
|
|
|
/// Get the number of remaining compute units
|
|
|
|
pub fn get_remaining(&self) -> u64 {
|
2021-11-04 21:47:32 +01:00
|
|
|
self.remaining
|
|
|
|
}
|
2021-12-02 08:58:02 +01:00
|
|
|
/// Set compute units
|
|
|
|
///
|
|
|
|
/// Only use for tests and benchmarks
|
|
|
|
pub fn mock_set_remaining(&mut self, remaining: u64) {
|
|
|
|
self.remaining = remaining;
|
|
|
|
}
|
2021-11-23 13:23:40 +01:00
|
|
|
/// Construct a new one with the given remaining units
|
2021-11-04 21:47:32 +01:00
|
|
|
pub fn new_ref(remaining: u64) -> Rc<RefCell<Self>> {
|
|
|
|
Rc::new(RefCell::new(Self { remaining }))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-02 08:58:02 +01:00
|
|
|
pub struct StackFrame<'a> {
|
2021-11-04 21:47:32 +01:00
|
|
|
pub number_of_program_accounts: usize,
|
|
|
|
pub keyed_accounts: Vec<KeyedAccount<'a>>,
|
|
|
|
pub keyed_accounts_range: std::ops::Range<usize>,
|
|
|
|
}
|
|
|
|
|
2021-12-02 08:58:02 +01:00
|
|
|
impl<'a> StackFrame<'a> {
|
2021-11-04 21:47:32 +01:00
|
|
|
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())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-02 18:47:16 +01:00
|
|
|
pub struct InvokeContext<'a> {
|
2021-12-27 18:49:32 +01:00
|
|
|
pub transaction_context: &'a TransactionContext,
|
|
|
|
pub return_data: (Pubkey, Vec<u8>),
|
2021-12-02 08:58:02 +01:00
|
|
|
invoke_stack: Vec<StackFrame<'a>>,
|
2021-11-04 21:47:32 +01:00
|
|
|
rent: Rent,
|
|
|
|
pre_accounts: Vec<PreAccount>,
|
2021-12-01 08:54:42 +01:00
|
|
|
builtin_programs: &'a [BuiltinProgram],
|
2021-12-03 12:15:22 +01:00
|
|
|
pub sysvars: &'a [(Pubkey, Vec<u8>)],
|
2021-11-23 13:23:40 +01:00
|
|
|
log_collector: Option<Rc<RefCell<LogCollector>>>,
|
2021-11-04 21:47:32 +01:00
|
|
|
compute_budget: ComputeBudget,
|
2021-11-11 14:09:28 -08:00
|
|
|
current_compute_budget: ComputeBudget,
|
2021-11-23 13:23:40 +01:00
|
|
|
compute_meter: Rc<RefCell<ComputeMeter>>,
|
2021-12-28 05:14:48 -06:00
|
|
|
accounts_data_meter: AccountsDataMeter,
|
2021-11-04 21:47:32 +01:00
|
|
|
executors: Rc<RefCell<Executors>>,
|
2021-12-25 13:35:43 +01:00
|
|
|
pub instruction_recorder: Option<Rc<RefCell<InstructionRecorder>>>,
|
2021-12-03 12:15:22 +01:00
|
|
|
pub feature_set: Arc<FeatureSet>,
|
2021-11-04 21:47:32 +01:00
|
|
|
pub timings: ExecuteDetailsTimings,
|
2021-12-03 12:15:22 +01:00
|
|
|
pub blockhash: Hash,
|
|
|
|
pub lamports_per_signature: u64,
|
2021-11-04 21:47:32 +01:00
|
|
|
}
|
2021-12-02 18:47:16 +01:00
|
|
|
|
|
|
|
impl<'a> InvokeContext<'a> {
|
2021-11-04 21:47:32 +01:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
pub fn new(
|
2021-12-27 18:49:32 +01:00
|
|
|
transaction_context: &'a TransactionContext,
|
2021-11-04 21:47:32 +01:00
|
|
|
rent: Rent,
|
2021-12-01 08:54:42 +01:00
|
|
|
builtin_programs: &'a [BuiltinProgram],
|
2021-11-04 21:47:32 +01:00
|
|
|
sysvars: &'a [(Pubkey, Vec<u8>)],
|
2021-11-23 13:23:40 +01:00
|
|
|
log_collector: Option<Rc<RefCell<LogCollector>>>,
|
2021-11-04 21:47:32 +01:00
|
|
|
compute_budget: ComputeBudget,
|
|
|
|
executors: Rc<RefCell<Executors>>,
|
2021-12-25 13:35:43 +01:00
|
|
|
instruction_recorder: Option<Rc<RefCell<InstructionRecorder>>>,
|
2021-11-04 21:47:32 +01:00
|
|
|
feature_set: Arc<FeatureSet>,
|
|
|
|
blockhash: Hash,
|
|
|
|
lamports_per_signature: u64,
|
2021-12-28 05:14:48 -06:00
|
|
|
current_accounts_data_len: u64,
|
2021-11-04 21:47:32 +01:00
|
|
|
) -> Self {
|
|
|
|
Self {
|
2021-12-27 18:49:32 +01:00
|
|
|
transaction_context,
|
|
|
|
return_data: (Pubkey::default(), Vec::new()),
|
2021-11-04 21:47:32 +01:00
|
|
|
invoke_stack: Vec::with_capacity(compute_budget.max_invoke_depth),
|
|
|
|
rent,
|
|
|
|
pre_accounts: Vec::new(),
|
2021-12-01 08:54:42 +01:00
|
|
|
builtin_programs,
|
2021-11-04 21:47:32 +01:00
|
|
|
sysvars,
|
2021-11-23 13:23:40 +01:00
|
|
|
log_collector,
|
2021-11-11 14:09:28 -08:00
|
|
|
current_compute_budget: compute_budget,
|
2021-11-04 21:47:32 +01:00
|
|
|
compute_budget,
|
2021-12-03 12:15:22 +01:00
|
|
|
compute_meter: ComputeMeter::new_ref(compute_budget.max_units),
|
2021-12-28 05:14:48 -06:00
|
|
|
accounts_data_meter: AccountsDataMeter::new(current_accounts_data_len),
|
2021-11-04 21:47:32 +01:00
|
|
|
executors,
|
2021-12-25 13:35:43 +01:00
|
|
|
instruction_recorder,
|
2021-11-04 21:47:32 +01:00
|
|
|
feature_set,
|
|
|
|
timings: ExecuteDetailsTimings::default(),
|
|
|
|
blockhash,
|
|
|
|
lamports_per_signature,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-03 12:15:22 +01:00
|
|
|
pub fn new_mock(
|
2021-12-27 18:49:32 +01:00
|
|
|
transaction_context: &'a TransactionContext,
|
2021-12-01 08:54:42 +01:00
|
|
|
builtin_programs: &'a [BuiltinProgram],
|
2021-11-04 21:47:32 +01:00
|
|
|
) -> Self {
|
|
|
|
Self::new(
|
2021-12-27 18:49:32 +01:00
|
|
|
transaction_context,
|
2021-11-04 21:47:32 +01:00
|
|
|
Rent::default(),
|
2021-12-01 08:54:42 +01:00
|
|
|
builtin_programs,
|
2021-12-07 23:00:04 +01:00
|
|
|
&[],
|
2021-12-02 08:58:02 +01:00
|
|
|
Some(LogCollector::new_ref()),
|
2021-11-04 21:47:32 +01:00
|
|
|
ComputeBudget::default(),
|
|
|
|
Rc::new(RefCell::new(Executors::default())),
|
2021-12-25 13:35:43 +01:00
|
|
|
None,
|
2021-12-03 12:15:22 +01:00
|
|
|
Arc::new(FeatureSet::all_enabled()),
|
2021-11-04 21:47:32 +01:00
|
|
|
Hash::default(),
|
|
|
|
0,
|
2021-12-28 05:14:48 -06:00
|
|
|
0,
|
2021-11-04 21:47:32 +01:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-11-17 19:35:07 +01:00
|
|
|
/// Push a stack frame onto the invocation stack
|
2021-12-02 18:47:16 +01:00
|
|
|
pub fn push(
|
2021-11-04 21:47:32 +01:00
|
|
|
&mut self,
|
2021-12-24 16:17:55 +01:00
|
|
|
instruction_accounts: &[InstructionAccount],
|
2021-11-04 21:47:32 +01:00
|
|
|
program_indices: &[usize],
|
|
|
|
) -> Result<(), InstructionError> {
|
|
|
|
if self.invoke_stack.len() > self.compute_budget.max_invoke_depth {
|
|
|
|
return Err(InstructionError::CallDepth);
|
|
|
|
}
|
|
|
|
|
2021-12-27 18:49:32 +01:00
|
|
|
let program_id = program_indices.last().map(|account_index| {
|
|
|
|
self.transaction_context
|
|
|
|
.get_key_of_account_at_index(*account_index)
|
|
|
|
});
|
2021-12-03 15:47:18 +01:00
|
|
|
if program_id.is_none()
|
|
|
|
&& self
|
|
|
|
.feature_set
|
|
|
|
.is_active(&reject_empty_instruction_without_program::id())
|
|
|
|
{
|
|
|
|
return Err(InstructionError::UnsupportedProgramId);
|
|
|
|
}
|
2021-11-04 21:47:32 +01:00
|
|
|
if self.invoke_stack.is_empty() {
|
2021-11-11 14:09:28 -08:00
|
|
|
let mut compute_budget = self.compute_budget;
|
2021-12-03 12:15:22 +01:00
|
|
|
if !self.feature_set.is_active(&tx_wide_compute_cap::id())
|
|
|
|
&& self.feature_set.is_active(&neon_evm_compute_budget::id())
|
2021-11-19 20:43:42 +01:00
|
|
|
&& program_id == Some(&crate::neon_evm_program::id())
|
2021-11-11 14:09:28 -08:00
|
|
|
{
|
|
|
|
// Bump the compute budget for neon_evm
|
|
|
|
compute_budget.max_units = compute_budget.max_units.max(500_000);
|
|
|
|
}
|
2021-12-03 12:15:22 +01:00
|
|
|
if !self.feature_set.is_active(&requestable_heap_size::id())
|
|
|
|
&& self.feature_set.is_active(&neon_evm_compute_budget::id())
|
2021-11-19 20:43:42 +01:00
|
|
|
&& program_id == Some(&crate::neon_evm_program::id())
|
2021-11-11 14:09:28 -08:00
|
|
|
{
|
|
|
|
// Bump the compute budget for neon_evm
|
|
|
|
compute_budget.heap_size = Some(256_usize.saturating_mul(1024));
|
|
|
|
}
|
|
|
|
self.current_compute_budget = compute_budget;
|
|
|
|
|
2021-11-04 21:47:32 +01:00
|
|
|
if !self.feature_set.is_active(&tx_wide_compute_cap::id()) {
|
2021-11-23 13:23:40 +01:00
|
|
|
self.compute_meter = ComputeMeter::new_ref(self.current_compute_budget.max_units);
|
2021-11-04 21:47:32 +01:00
|
|
|
}
|
|
|
|
|
2021-12-24 16:17:55 +01:00
|
|
|
self.pre_accounts = Vec::with_capacity(instruction_accounts.len());
|
2021-12-30 15:46:36 +01:00
|
|
|
let mut work = |_index_in_instruction: usize,
|
|
|
|
instruction_account: &InstructionAccount| {
|
|
|
|
if instruction_account.index_in_transaction
|
|
|
|
< self.transaction_context.get_number_of_accounts()
|
|
|
|
{
|
2021-12-27 18:49:32 +01:00
|
|
|
let account = self
|
|
|
|
.transaction_context
|
2021-12-30 15:46:36 +01:00
|
|
|
.get_account_at_index(instruction_account.index_in_transaction)
|
2021-12-27 18:49:32 +01:00
|
|
|
.borrow()
|
|
|
|
.clone();
|
|
|
|
self.pre_accounts.push(PreAccount::new(
|
|
|
|
self.transaction_context
|
2021-12-30 15:46:36 +01:00
|
|
|
.get_key_of_account_at_index(instruction_account.index_in_transaction),
|
2021-12-27 18:49:32 +01:00
|
|
|
account,
|
|
|
|
));
|
2021-11-04 21:47:32 +01:00
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
Err(InstructionError::MissingAccount)
|
|
|
|
};
|
2021-12-24 16:17:55 +01:00
|
|
|
visit_each_account_once(instruction_accounts, &mut work)?;
|
2021-11-19 20:43:42 +01:00
|
|
|
} else {
|
|
|
|
let contains = self
|
|
|
|
.invoke_stack
|
|
|
|
.iter()
|
|
|
|
.any(|frame| frame.program_id() == program_id);
|
|
|
|
let is_last = if let Some(last_frame) = self.invoke_stack.last() {
|
|
|
|
last_frame.program_id() == program_id
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
};
|
|
|
|
if contains && !is_last {
|
|
|
|
// Reentrancy not allowed unless caller is calling itself
|
|
|
|
return Err(InstructionError::ReentrancyNotAllowed);
|
|
|
|
}
|
2021-11-04 21:47:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create the KeyedAccounts that will be passed to the program
|
|
|
|
let keyed_accounts = program_indices
|
|
|
|
.iter()
|
|
|
|
.map(|account_index| {
|
|
|
|
(
|
|
|
|
false,
|
|
|
|
false,
|
2021-12-27 18:49:32 +01:00
|
|
|
self.transaction_context
|
|
|
|
.get_key_of_account_at_index(*account_index),
|
|
|
|
self.transaction_context
|
|
|
|
.get_account_at_index(*account_index),
|
2021-11-04 21:47:32 +01:00
|
|
|
)
|
|
|
|
})
|
2021-12-24 16:17:55 +01:00
|
|
|
.chain(instruction_accounts.iter().map(|instruction_account| {
|
2021-11-04 21:47:32 +01:00
|
|
|
(
|
2021-12-24 16:17:55 +01:00
|
|
|
instruction_account.is_signer,
|
|
|
|
instruction_account.is_writable,
|
2021-12-27 18:49:32 +01:00
|
|
|
self.transaction_context
|
2021-12-30 15:46:36 +01:00
|
|
|
.get_key_of_account_at_index(instruction_account.index_in_transaction),
|
2021-12-27 18:49:32 +01:00
|
|
|
self.transaction_context
|
2021-12-30 15:46:36 +01:00
|
|
|
.get_account_at_index(instruction_account.index_in_transaction),
|
2021-11-04 21:47:32 +01:00
|
|
|
)
|
|
|
|
}))
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
2021-12-02 08:58:02 +01:00
|
|
|
self.invoke_stack.push(StackFrame::new(
|
2021-11-04 21:47:32 +01:00
|
|
|
program_indices.len(),
|
|
|
|
create_keyed_accounts_unified(keyed_accounts.as_slice()),
|
|
|
|
));
|
|
|
|
Ok(())
|
|
|
|
}
|
2021-12-02 18:47:16 +01:00
|
|
|
|
|
|
|
/// Pop a stack frame from the invocation stack
|
|
|
|
pub fn pop(&mut self) {
|
2021-11-04 21:47:32 +01:00
|
|
|
self.invoke_stack.pop();
|
|
|
|
}
|
2021-12-02 18:47:16 +01:00
|
|
|
|
|
|
|
/// Current depth of the invocation stack
|
|
|
|
pub fn invoke_depth(&self) -> usize {
|
2021-11-04 21:47:32 +01:00
|
|
|
self.invoke_stack.len()
|
|
|
|
}
|
2021-12-02 18:47:16 +01:00
|
|
|
|
|
|
|
/// Verify the results of an instruction
|
2021-12-07 23:00:04 +01:00
|
|
|
fn verify(
|
2021-11-04 21:47:32 +01:00
|
|
|
&mut self,
|
2021-12-24 16:17:55 +01:00
|
|
|
instruction_accounts: &[InstructionAccount],
|
2021-11-04 21:47:32 +01:00
|
|
|
program_indices: &[usize],
|
|
|
|
) -> Result<(), InstructionError> {
|
2021-12-24 16:17:55 +01:00
|
|
|
let program_id = self
|
|
|
|
.invoke_stack
|
|
|
|
.last()
|
|
|
|
.and_then(|frame| frame.program_id())
|
|
|
|
.ok_or(InstructionError::CallDepth)?;
|
2021-12-03 12:15:22 +01:00
|
|
|
let do_support_realloc = self.feature_set.is_active(&do_support_realloc::id());
|
2021-11-04 21:47:32 +01:00
|
|
|
|
|
|
|
// Verify all executable accounts have zero outstanding refs
|
|
|
|
for account_index in program_indices.iter() {
|
2021-12-27 18:49:32 +01:00
|
|
|
self.transaction_context
|
|
|
|
.get_account_at_index(*account_index)
|
2021-11-04 21:47:32 +01:00
|
|
|
.try_borrow_mut()
|
|
|
|
.map_err(|_| InstructionError::AccountBorrowOutstanding)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify the per-account instruction results
|
|
|
|
let (mut pre_sum, mut post_sum) = (0_u128, 0_u128);
|
2021-12-24 16:17:55 +01:00
|
|
|
let mut pre_account_index = 0;
|
|
|
|
let mut work = |_index_in_instruction: usize, instruction_account: &InstructionAccount| {
|
2021-11-04 21:47:32 +01:00
|
|
|
{
|
|
|
|
// Verify account has no outstanding references
|
2021-12-27 18:49:32 +01:00
|
|
|
let _ = self
|
|
|
|
.transaction_context
|
2021-12-30 15:46:36 +01:00
|
|
|
.get_account_at_index(instruction_account.index_in_transaction)
|
2021-11-04 21:47:32 +01:00
|
|
|
.try_borrow_mut()
|
|
|
|
.map_err(|_| InstructionError::AccountBorrowOutstanding)?;
|
|
|
|
}
|
2021-12-24 16:17:55 +01:00
|
|
|
let pre_account = &self.pre_accounts[pre_account_index];
|
|
|
|
pre_account_index = pre_account_index.saturating_add(1);
|
2021-12-27 18:49:32 +01:00
|
|
|
let account = self
|
|
|
|
.transaction_context
|
2021-12-30 15:46:36 +01:00
|
|
|
.get_account_at_index(instruction_account.index_in_transaction)
|
2021-12-27 18:49:32 +01:00
|
|
|
.borrow();
|
2021-11-09 13:35:49 +01:00
|
|
|
pre_account
|
2021-11-04 21:47:32 +01:00
|
|
|
.verify(
|
|
|
|
program_id,
|
2021-12-24 16:17:55 +01:00
|
|
|
instruction_account.is_writable,
|
2021-11-04 21:47:32 +01:00
|
|
|
&self.rent,
|
|
|
|
&account,
|
|
|
|
&mut self.timings,
|
|
|
|
true,
|
|
|
|
do_support_realloc,
|
|
|
|
)
|
|
|
|
.map_err(|err| {
|
|
|
|
ic_logger_msg!(
|
2021-11-23 13:23:40 +01:00
|
|
|
self.log_collector,
|
2021-11-04 21:47:32 +01:00
|
|
|
"failed to verify account {}: {}",
|
2021-11-09 13:35:49 +01:00
|
|
|
pre_account.key(),
|
2021-11-04 21:47:32 +01:00
|
|
|
err
|
|
|
|
);
|
|
|
|
err
|
|
|
|
})?;
|
2021-11-09 13:35:49 +01:00
|
|
|
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)?;
|
2021-11-04 21:47:32 +01:00
|
|
|
Ok(())
|
|
|
|
};
|
2021-12-24 16:17:55 +01:00
|
|
|
visit_each_account_once(instruction_accounts, &mut work)?;
|
2021-11-04 21:47:32 +01:00
|
|
|
|
|
|
|
// Verify that the total sum of all the lamports did not change
|
|
|
|
if pre_sum != post_sum {
|
|
|
|
return Err(InstructionError::UnbalancedInstruction);
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
2021-12-02 18:47:16 +01:00
|
|
|
|
|
|
|
/// Verify and update PreAccount state based on program execution
|
2021-12-07 23:00:04 +01:00
|
|
|
fn verify_and_update(
|
2021-11-04 21:47:32 +01:00
|
|
|
&mut self,
|
2021-12-24 16:17:55 +01:00
|
|
|
instruction_accounts: &[InstructionAccount],
|
|
|
|
caller_write_privileges: Option<&[bool]>,
|
2021-11-04 21:47:32 +01:00
|
|
|
) -> Result<(), InstructionError> {
|
|
|
|
let do_support_realloc = self.feature_set.is_active(&do_support_realloc::id());
|
|
|
|
let program_id = self
|
|
|
|
.invoke_stack
|
|
|
|
.last()
|
|
|
|
.and_then(|frame| frame.program_id())
|
|
|
|
.ok_or(InstructionError::CallDepth)?;
|
|
|
|
let rent = &self.rent;
|
2021-11-23 13:23:40 +01:00
|
|
|
let log_collector = &self.log_collector;
|
2021-12-27 18:49:32 +01:00
|
|
|
let transaction_context = self.transaction_context;
|
2021-11-04 21:47:32 +01:00
|
|
|
let pre_accounts = &mut self.pre_accounts;
|
|
|
|
let timings = &mut self.timings;
|
|
|
|
|
|
|
|
// Verify the per-account instruction results
|
|
|
|
let (mut pre_sum, mut post_sum) = (0_u128, 0_u128);
|
2021-12-24 16:17:55 +01:00
|
|
|
let mut work = |index_in_instruction: usize, instruction_account: &InstructionAccount| {
|
2021-12-30 15:46:36 +01:00
|
|
|
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);
|
2021-12-24 16:17:55 +01:00
|
|
|
let is_writable = if let Some(caller_write_privileges) = caller_write_privileges {
|
|
|
|
caller_write_privileges[index_in_instruction]
|
|
|
|
} else {
|
|
|
|
instruction_account.is_writable
|
|
|
|
};
|
2021-11-04 21:47:32 +01:00
|
|
|
// Find the matching PreAccount
|
|
|
|
for pre_account in 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,
|
|
|
|
rent,
|
|
|
|
&account,
|
|
|
|
timings,
|
|
|
|
false,
|
|
|
|
do_support_realloc,
|
|
|
|
)
|
|
|
|
.map_err(|err| {
|
2021-11-23 13:23:40 +01:00
|
|
|
ic_logger_msg!(
|
|
|
|
log_collector,
|
|
|
|
"failed to verify account {}: {}",
|
|
|
|
key,
|
|
|
|
err
|
|
|
|
);
|
2021-11-04 21:47:32 +01:00
|
|
|
err
|
|
|
|
})?;
|
2021-11-09 13:35:49 +01:00
|
|
|
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)?;
|
2021-11-04 21:47:32 +01:00
|
|
|
if is_writable && !pre_account.executable() {
|
2021-12-14 15:44:31 +01:00
|
|
|
pre_account.update(account.clone());
|
2021-11-04 21:47:32 +01:00
|
|
|
}
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(InstructionError::MissingAccount)
|
|
|
|
};
|
2021-12-24 16:17:55 +01:00
|
|
|
visit_each_account_once(instruction_accounts, &mut work)?;
|
2021-11-04 21:47:32 +01:00
|
|
|
|
|
|
|
// Verify that the total sum of all the lamports did not change
|
|
|
|
if pre_sum != post_sum {
|
|
|
|
return Err(InstructionError::UnbalancedInstruction);
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
2021-12-02 18:47:16 +01:00
|
|
|
|
|
|
|
/// Entrypoint for a cross-program invocation from a builtin program
|
|
|
|
pub fn native_invoke(
|
2021-12-01 08:54:42 +01:00
|
|
|
&mut self,
|
|
|
|
instruction: Instruction,
|
|
|
|
signers: &[Pubkey],
|
|
|
|
) -> Result<(), InstructionError> {
|
2021-12-24 16:17:55 +01:00
|
|
|
let (instruction_accounts, caller_write_privileges, 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() {
|
2021-12-27 18:49:32 +01:00
|
|
|
let account_length = self
|
|
|
|
.transaction_context
|
2021-12-30 15:46:36 +01:00
|
|
|
.get_account_at_index(instruction_account.index_in_transaction)
|
2021-12-24 16:17:55 +01:00
|
|
|
.borrow()
|
|
|
|
.data()
|
|
|
|
.len();
|
2021-12-30 15:46:36 +01:00
|
|
|
prev_account_sizes.push((instruction_account.index_in_transaction, account_length));
|
2021-12-01 08:54:42 +01:00
|
|
|
}
|
|
|
|
|
2021-12-31 17:55:27 +01:00
|
|
|
let mut compute_units_consumed = 0;
|
2021-12-07 23:00:04 +01:00
|
|
|
self.process_instruction(
|
2021-12-24 16:17:55 +01:00
|
|
|
&instruction.data,
|
|
|
|
&instruction_accounts,
|
|
|
|
Some(&caller_write_privileges),
|
2021-12-01 08:54:42 +01:00
|
|
|
&program_indices,
|
2021-12-31 17:55:27 +01:00
|
|
|
&mut compute_units_consumed,
|
|
|
|
)?;
|
2021-12-01 08:54:42 +01:00
|
|
|
|
|
|
|
// Verify the called program has not misbehaved
|
2021-12-03 12:15:22 +01:00
|
|
|
let do_support_realloc = self.feature_set.is_active(&do_support_realloc::id());
|
2021-12-14 15:44:31 +01:00
|
|
|
for (account_index, prev_size) in prev_account_sizes.into_iter() {
|
|
|
|
if !do_support_realloc
|
2021-12-27 18:49:32 +01:00
|
|
|
&& prev_size
|
|
|
|
!= self
|
|
|
|
.transaction_context
|
|
|
|
.get_account_at_index(account_index)
|
|
|
|
.borrow()
|
|
|
|
.data()
|
|
|
|
.len()
|
2021-12-14 15:44:31 +01:00
|
|
|
&& prev_size != 0
|
2021-12-01 08:54:42 +01:00
|
|
|
{
|
|
|
|
// Only support for `CreateAccount` at this time.
|
|
|
|
// Need a way to limit total realloc size across multiple CPI calls
|
|
|
|
ic_msg!(
|
|
|
|
self,
|
|
|
|
"Inner instructions do not support realloc, only SystemProgram::CreateAccount",
|
|
|
|
);
|
|
|
|
return Err(InstructionError::InvalidRealloc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2021-12-02 18:47:16 +01:00
|
|
|
|
2021-12-07 23:00:04 +01:00
|
|
|
/// Helper to prepare for process_instruction()
|
2021-12-24 16:17:55 +01:00
|
|
|
#[allow(clippy::type_complexity)]
|
|
|
|
pub fn prepare_instruction(
|
2021-12-01 08:54:42 +01:00
|
|
|
&mut self,
|
|
|
|
instruction: &Instruction,
|
|
|
|
signers: &[Pubkey],
|
2021-12-24 16:17:55 +01:00
|
|
|
) -> Result<(Vec<InstructionAccount>, Vec<bool>, Vec<usize>), InstructionError> {
|
2021-12-25 10:00:40 +01:00
|
|
|
// Finds the index of each account in the instruction by its pubkey.
|
|
|
|
// Then normalizes / unifies the privileges of duplicate accounts.
|
|
|
|
// Note: This works like visit_each_account_once() and is an O(n^2) algorithm too.
|
2021-12-30 15:46:36 +01:00
|
|
|
let caller_keyed_accounts = self.get_keyed_accounts()?;
|
2021-12-25 10:00:40 +01:00
|
|
|
let mut deduplicated_instruction_accounts: Vec<InstructionAccount> = Vec::new();
|
|
|
|
let mut duplicate_indicies = Vec::with_capacity(instruction.accounts.len());
|
|
|
|
for account_meta in instruction.accounts.iter() {
|
2021-12-30 15:46:36 +01:00
|
|
|
let index_in_transaction = self
|
2021-12-27 18:49:32 +01:00
|
|
|
.transaction_context
|
|
|
|
.find_index_of_account(&account_meta.pubkey)
|
2021-12-25 10:00:40 +01:00
|
|
|
.ok_or_else(|| {
|
|
|
|
ic_msg!(
|
|
|
|
self,
|
|
|
|
"Instruction references an unknown account {}",
|
|
|
|
account_meta.pubkey,
|
|
|
|
);
|
|
|
|
InstructionError::MissingAccount
|
|
|
|
})?;
|
2021-12-30 15:46:36 +01:00
|
|
|
if let Some(duplicate_index) =
|
|
|
|
deduplicated_instruction_accounts
|
|
|
|
.iter()
|
|
|
|
.position(|instruction_account| {
|
|
|
|
instruction_account.index_in_transaction == index_in_transaction
|
|
|
|
})
|
2021-12-25 10:00:40 +01:00
|
|
|
{
|
|
|
|
duplicate_indicies.push(duplicate_index);
|
|
|
|
let instruction_account = &mut deduplicated_instruction_accounts[duplicate_index];
|
|
|
|
instruction_account.is_signer |= account_meta.is_signer;
|
|
|
|
instruction_account.is_writable |= account_meta.is_writable;
|
|
|
|
} else {
|
2021-12-30 15:46:36 +01:00
|
|
|
let index_in_caller = caller_keyed_accounts
|
|
|
|
.iter()
|
|
|
|
.position(|keyed_account| *keyed_account.unsigned_key() == account_meta.pubkey)
|
|
|
|
.ok_or_else(|| {
|
|
|
|
ic_msg!(
|
|
|
|
self,
|
|
|
|
"Instruction references an unknown account {}",
|
|
|
|
account_meta.pubkey,
|
|
|
|
);
|
|
|
|
InstructionError::MissingAccount
|
|
|
|
})?;
|
2021-12-25 10:00:40 +01:00
|
|
|
duplicate_indicies.push(deduplicated_instruction_accounts.len());
|
|
|
|
deduplicated_instruction_accounts.push(InstructionAccount {
|
2021-12-30 15:46:36 +01:00
|
|
|
index_in_transaction,
|
|
|
|
index_in_caller,
|
2021-12-25 10:00:40 +01:00
|
|
|
is_signer: account_meta.is_signer,
|
|
|
|
is_writable: account_meta.is_writable,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2021-12-30 15:46:36 +01:00
|
|
|
let instruction_accounts: Vec<InstructionAccount> = duplicate_indicies
|
2021-12-25 10:00:40 +01:00
|
|
|
.into_iter()
|
|
|
|
.map(|duplicate_index| deduplicated_instruction_accounts[duplicate_index].clone())
|
|
|
|
.collect();
|
2021-12-01 08:54:42 +01:00
|
|
|
|
|
|
|
// Check for privilege escalation
|
2021-12-30 15:46:36 +01:00
|
|
|
let caller_write_privileges = instruction_accounts
|
2021-12-24 16:17:55 +01:00
|
|
|
.iter()
|
2021-12-30 15:46:36 +01:00
|
|
|
.map(|instruction_account| {
|
|
|
|
let keyed_account = &caller_keyed_accounts[instruction_account.index_in_caller];
|
2021-12-24 16:17:55 +01:00
|
|
|
|
|
|
|
// Readonly in caller cannot become writable in callee
|
2021-12-30 15:46:36 +01:00
|
|
|
if instruction_account.is_writable && !keyed_account.is_writable() {
|
2021-12-01 08:54:42 +01:00
|
|
|
ic_msg!(
|
|
|
|
self,
|
2021-12-24 16:17:55 +01:00
|
|
|
"{}'s writable privilege escalated",
|
2021-12-30 15:46:36 +01:00
|
|
|
keyed_account.unsigned_key(),
|
2021-12-01 08:54:42 +01:00
|
|
|
);
|
2021-12-24 16:17:55 +01:00
|
|
|
return Err(InstructionError::PrivilegeEscalation);
|
|
|
|
}
|
2021-12-01 08:54:42 +01:00
|
|
|
|
2021-12-24 16:17:55 +01:00
|
|
|
// To be signed in the callee,
|
|
|
|
// it must be either signed in the caller or by the program
|
2021-12-30 15:46:36 +01:00
|
|
|
if instruction_account.is_signer
|
2021-12-24 16:17:55 +01:00
|
|
|
&& !(keyed_account.signer_key().is_some()
|
2021-12-30 15:46:36 +01:00
|
|
|
|| signers.contains(keyed_account.unsigned_key()))
|
2021-12-24 16:17:55 +01:00
|
|
|
{
|
2021-12-30 15:46:36 +01:00
|
|
|
ic_msg!(
|
|
|
|
self,
|
|
|
|
"{}'s signer privilege escalated",
|
|
|
|
keyed_account.unsigned_key()
|
|
|
|
);
|
2021-12-24 16:17:55 +01:00
|
|
|
return Err(InstructionError::PrivilegeEscalation);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(keyed_account.is_writable())
|
|
|
|
})
|
|
|
|
.collect::<Result<Vec<bool>, InstructionError>>()?;
|
2021-12-01 08:54:42 +01:00
|
|
|
|
|
|
|
// Find and validate executables / program accounts
|
|
|
|
let callee_program_id = instruction.program_id;
|
2021-12-24 16:17:55 +01:00
|
|
|
let program_account_index = caller_keyed_accounts
|
2021-12-01 08:54:42 +01:00
|
|
|
.iter()
|
|
|
|
.find(|keyed_account| &callee_program_id == keyed_account.unsigned_key())
|
2021-12-25 10:00:40 +01:00
|
|
|
.and_then(|_keyed_account| {
|
2021-12-27 18:49:32 +01:00
|
|
|
self.transaction_context
|
|
|
|
.find_index_of_program_account(&callee_program_id)
|
2021-12-25 10:00:40 +01:00
|
|
|
})
|
2021-12-01 08:54:42 +01:00
|
|
|
.ok_or_else(|| {
|
|
|
|
ic_msg!(self, "Unknown program {}", callee_program_id);
|
|
|
|
InstructionError::MissingAccount
|
|
|
|
})?;
|
2021-12-27 18:49:32 +01:00
|
|
|
let program_account = self
|
|
|
|
.transaction_context
|
|
|
|
.get_account_at_index(program_account_index)
|
|
|
|
.borrow();
|
2021-12-14 15:44:31 +01:00
|
|
|
if !program_account.executable() {
|
2021-12-01 08:54:42 +01:00
|
|
|
ic_msg!(self, "Account {} is not executable", callee_program_id);
|
|
|
|
return Err(InstructionError::AccountNotExecutable);
|
|
|
|
}
|
|
|
|
let mut program_indices = vec![];
|
2021-12-14 15:44:31 +01:00
|
|
|
if program_account.owner() == &bpf_loader_upgradeable::id() {
|
2021-12-01 08:54:42 +01:00
|
|
|
if let UpgradeableLoaderState::Program {
|
|
|
|
programdata_address,
|
2021-12-14 15:44:31 +01:00
|
|
|
} = program_account.state()?
|
2021-12-01 08:54:42 +01:00
|
|
|
{
|
2021-12-25 10:00:40 +01:00
|
|
|
if let Some(programdata_account_index) = self
|
2021-12-27 18:49:32 +01:00
|
|
|
.transaction_context
|
|
|
|
.find_index_of_program_account(&programdata_address)
|
2021-12-01 08:54:42 +01:00
|
|
|
{
|
|
|
|
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(program_account_index);
|
|
|
|
|
2021-12-24 16:17:55 +01:00
|
|
|
Ok((
|
|
|
|
instruction_accounts,
|
|
|
|
caller_write_privileges,
|
|
|
|
program_indices,
|
|
|
|
))
|
2021-12-01 08:54:42 +01:00
|
|
|
}
|
2021-12-02 18:47:16 +01:00
|
|
|
|
2021-12-16 15:15:58 +01:00
|
|
|
/// Processes a cross-program instruction and returns how many compute units were used
|
2021-12-07 23:00:04 +01:00
|
|
|
pub fn process_instruction(
|
2021-12-01 08:54:42 +01:00
|
|
|
&mut self,
|
2021-12-24 16:17:55 +01:00
|
|
|
instruction_data: &[u8],
|
|
|
|
instruction_accounts: &[InstructionAccount],
|
|
|
|
caller_write_privileges: Option<&[bool]>,
|
2021-12-01 08:54:42 +01:00
|
|
|
program_indices: &[usize],
|
2021-12-31 17:55:27 +01:00
|
|
|
compute_units_consumed: &mut u64,
|
|
|
|
) -> Result<(), InstructionError> {
|
|
|
|
*compute_units_consumed = 0;
|
2021-12-25 10:00:40 +01:00
|
|
|
let program_id = program_indices
|
|
|
|
.last()
|
2021-12-27 18:49:32 +01:00
|
|
|
.map(|index| *self.transaction_context.get_key_of_account_at_index(*index))
|
2021-12-25 10:00:40 +01:00
|
|
|
.unwrap_or_else(native_loader::id);
|
|
|
|
|
2021-12-07 23:00:04 +01:00
|
|
|
let is_lowest_invocation_level = self.invoke_stack.is_empty();
|
2021-12-25 13:35:43 +01:00
|
|
|
if is_lowest_invocation_level {
|
|
|
|
if let Some(instruction_recorder) = &self.instruction_recorder {
|
|
|
|
instruction_recorder.borrow_mut().begin_next_recording();
|
|
|
|
}
|
|
|
|
} else {
|
2021-12-07 23:00:04 +01:00
|
|
|
// Verify the calling program hasn't misbehaved
|
2021-12-31 17:55:27 +01:00
|
|
|
self.verify_and_update(instruction_accounts, caller_write_privileges)?;
|
|
|
|
|
2021-12-25 13:35:43 +01:00
|
|
|
// Record instruction
|
|
|
|
if let Some(instruction_recorder) = &self.instruction_recorder {
|
|
|
|
let compiled_instruction = CompiledInstruction {
|
|
|
|
program_id_index: self
|
2021-12-27 18:49:32 +01:00
|
|
|
.transaction_context
|
|
|
|
.find_index_of_account(&program_id)
|
2021-12-25 13:35:43 +01:00
|
|
|
.unwrap_or(0) as u8,
|
|
|
|
data: instruction_data.to_vec(),
|
|
|
|
accounts: instruction_accounts
|
|
|
|
.iter()
|
2021-12-30 15:46:36 +01:00
|
|
|
.map(|instruction_account| instruction_account.index_in_transaction as u8)
|
2021-12-25 13:35:43 +01:00
|
|
|
.collect(),
|
|
|
|
};
|
|
|
|
instruction_recorder
|
|
|
|
.borrow_mut()
|
|
|
|
.record_compiled_instruction(compiled_instruction);
|
|
|
|
}
|
2021-12-07 23:00:04 +01:00
|
|
|
}
|
2021-12-01 08:54:42 +01:00
|
|
|
|
2021-12-07 23:00:04 +01:00
|
|
|
let result = self
|
2021-12-24 16:17:55 +01:00
|
|
|
.push(instruction_accounts, program_indices)
|
2021-12-07 23:00:04 +01:00
|
|
|
.and_then(|_| {
|
2021-12-24 16:17:55 +01:00
|
|
|
self.return_data = (program_id, Vec::new());
|
2021-12-16 15:15:58 +01:00
|
|
|
let pre_remaining_units = self.compute_meter.borrow().get_remaining();
|
2021-12-30 21:21:42 -05:00
|
|
|
let execution_result = self.process_executable_chain(instruction_data);
|
2021-12-16 15:15:58 +01:00
|
|
|
let post_remaining_units = self.compute_meter.borrow().get_remaining();
|
2021-12-31 17:55:27 +01:00
|
|
|
*compute_units_consumed = pre_remaining_units.saturating_sub(post_remaining_units);
|
2021-12-30 21:21:42 -05:00
|
|
|
execution_result?;
|
2021-12-07 23:00:04 +01:00
|
|
|
|
|
|
|
// Verify the called program has not misbehaved
|
|
|
|
if is_lowest_invocation_level {
|
2021-12-30 21:21:42 -05:00
|
|
|
self.verify(instruction_accounts, program_indices)
|
2021-12-07 23:00:04 +01:00
|
|
|
} else {
|
2021-12-30 21:21:42 -05:00
|
|
|
self.verify_and_update(instruction_accounts, None)
|
2021-12-07 23:00:04 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Pop the invoke_stack to restore previous state
|
2021-12-01 08:54:42 +01:00
|
|
|
self.pop();
|
2021-12-31 17:55:27 +01:00
|
|
|
result
|
2021-12-01 08:54:42 +01:00
|
|
|
}
|
2021-12-02 18:47:16 +01:00
|
|
|
|
|
|
|
/// Calls the instruction's program entrypoint method
|
2021-12-07 23:00:04 +01:00
|
|
|
fn process_executable_chain(
|
|
|
|
&mut self,
|
|
|
|
instruction_data: &[u8],
|
|
|
|
) -> Result<(), InstructionError> {
|
2021-12-01 08:54:42 +01:00
|
|
|
let keyed_accounts = self.get_keyed_accounts()?;
|
|
|
|
let root_account = keyed_account_at_index(keyed_accounts, 0)
|
|
|
|
.map_err(|_| InstructionError::UnsupportedProgramId)?;
|
|
|
|
let root_id = root_account.unsigned_key();
|
|
|
|
let owner_id = &root_account.owner()?;
|
|
|
|
if solana_sdk::native_loader::check_id(owner_id) {
|
|
|
|
for entry in self.builtin_programs {
|
|
|
|
if entry.program_id == *root_id {
|
|
|
|
// Call the builtin program
|
|
|
|
return (entry.process_instruction)(
|
|
|
|
1, // root_id to be skipped
|
|
|
|
instruction_data,
|
|
|
|
self,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2021-12-03 12:15:22 +01:00
|
|
|
if !self.feature_set.is_active(&remove_native_loader::id()) {
|
2021-12-01 08:54:42 +01:00
|
|
|
let native_loader = NativeLoader::default();
|
|
|
|
// Call the program via the native loader
|
|
|
|
return native_loader.process_instruction(0, instruction_data, self);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for entry in self.builtin_programs {
|
|
|
|
if entry.program_id == *owner_id {
|
|
|
|
// Call the program via a builtin loader
|
|
|
|
return (entry.process_instruction)(
|
|
|
|
0, // no root_id was provided
|
|
|
|
instruction_data,
|
|
|
|
self,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(InstructionError::UnsupportedProgramId)
|
|
|
|
}
|
2021-12-02 18:47:16 +01:00
|
|
|
|
|
|
|
/// Get the program ID of the currently executing program
|
|
|
|
pub fn get_caller(&self) -> Result<&Pubkey, InstructionError> {
|
2021-11-04 21:47:32 +01:00
|
|
|
self.invoke_stack
|
|
|
|
.last()
|
|
|
|
.and_then(|frame| frame.program_id())
|
|
|
|
.ok_or(InstructionError::CallDepth)
|
|
|
|
}
|
2021-12-02 18:47:16 +01:00
|
|
|
|
|
|
|
/// Get the owner of the currently executing program
|
|
|
|
pub fn get_loader(&self) -> Result<Pubkey, InstructionError> {
|
2021-12-21 12:53:22 +01:00
|
|
|
let frame = self
|
|
|
|
.invoke_stack
|
|
|
|
.last()
|
|
|
|
.ok_or(InstructionError::CallDepth)?;
|
|
|
|
let first_instruction_account = frame
|
|
|
|
.number_of_program_accounts
|
|
|
|
.checked_sub(1)
|
|
|
|
.ok_or(InstructionError::CallDepth)?;
|
|
|
|
frame.keyed_accounts[first_instruction_account].owner()
|
2021-12-02 08:58:02 +01:00
|
|
|
}
|
2021-12-02 18:47:16 +01:00
|
|
|
|
|
|
|
/// Removes the first keyed account
|
|
|
|
#[deprecated(
|
|
|
|
since = "1.9.0",
|
|
|
|
note = "To be removed together with remove_native_loader"
|
|
|
|
)]
|
|
|
|
pub fn remove_first_keyed_account(&mut self) -> Result<(), InstructionError> {
|
2021-12-03 12:15:22 +01:00
|
|
|
if !self.feature_set.is_active(&remove_native_loader::id()) {
|
2021-11-04 21:47:32 +01:00
|
|
|
let stack_frame = &mut self
|
|
|
|
.invoke_stack
|
|
|
|
.last_mut()
|
|
|
|
.ok_or(InstructionError::CallDepth)?;
|
|
|
|
stack_frame.keyed_accounts_range.start =
|
|
|
|
stack_frame.keyed_accounts_range.start.saturating_add(1);
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
2021-12-02 18:47:16 +01:00
|
|
|
|
2021-12-06 21:20:16 +01:00
|
|
|
/// Get the list of keyed accounts including the chain of program accounts
|
2021-12-02 18:47:16 +01:00
|
|
|
pub fn get_keyed_accounts(&self) -> Result<&[KeyedAccount], InstructionError> {
|
2021-11-04 21:47:32 +01:00
|
|
|
self.invoke_stack
|
|
|
|
.last()
|
|
|
|
.map(|frame| &frame.keyed_accounts[frame.keyed_accounts_range.clone()])
|
|
|
|
.ok_or(InstructionError::CallDepth)
|
|
|
|
}
|
2021-12-02 18:47:16 +01:00
|
|
|
|
2021-12-06 21:20:16 +01:00
|
|
|
/// Get the list of keyed accounts without the chain of program accounts
|
|
|
|
///
|
2021-12-21 12:53:22 +01:00
|
|
|
/// Note: This only contains the `KeyedAccount`s passed by the caller.
|
2021-12-02 18:47:16 +01:00
|
|
|
pub fn get_instruction_keyed_accounts(&self) -> Result<&[KeyedAccount], InstructionError> {
|
2021-12-01 14:57:59 +01:00
|
|
|
let frame = self
|
|
|
|
.invoke_stack
|
|
|
|
.last()
|
|
|
|
.ok_or(InstructionError::CallDepth)?;
|
2021-12-21 12:53:22 +01:00
|
|
|
Ok(&frame.keyed_accounts[frame.number_of_program_accounts..])
|
2021-12-01 14:57:59 +01:00
|
|
|
}
|
2021-12-02 18:47:16 +01:00
|
|
|
|
|
|
|
/// Get this invocation's LogCollector
|
|
|
|
pub fn get_log_collector(&self) -> Option<Rc<RefCell<LogCollector>>> {
|
2021-11-23 13:23:40 +01:00
|
|
|
self.log_collector.clone()
|
2021-11-04 21:47:32 +01:00
|
|
|
}
|
2021-12-02 18:47:16 +01:00
|
|
|
|
|
|
|
/// Get this invocation's ComputeMeter
|
|
|
|
pub fn get_compute_meter(&self) -> Rc<RefCell<ComputeMeter>> {
|
2021-11-04 21:47:32 +01:00
|
|
|
self.compute_meter.clone()
|
|
|
|
}
|
2021-12-02 18:47:16 +01:00
|
|
|
|
2021-12-28 05:14:48 -06:00
|
|
|
/// Get this invocation's AccountsDataMeter
|
|
|
|
pub fn get_accounts_data_meter(&self) -> &AccountsDataMeter {
|
|
|
|
&self.accounts_data_meter
|
|
|
|
}
|
|
|
|
|
2021-12-02 18:47:16 +01:00
|
|
|
/// Loaders may need to do work in order to execute a program. Cache
|
|
|
|
/// the work that can be re-used across executions
|
|
|
|
pub fn add_executor(&self, pubkey: &Pubkey, executor: Arc<dyn Executor>) {
|
2021-11-04 21:47:32 +01:00
|
|
|
self.executors.borrow_mut().insert(*pubkey, executor);
|
|
|
|
}
|
2021-12-02 18:47:16 +01:00
|
|
|
|
|
|
|
/// Get the completed loader work that can be re-used across execution
|
|
|
|
pub fn get_executor(&self, pubkey: &Pubkey) -> Option<Arc<dyn Executor>> {
|
2021-11-04 21:47:32 +01:00
|
|
|
self.executors.borrow().get(pubkey)
|
|
|
|
}
|
2021-12-02 18:47:16 +01:00
|
|
|
|
|
|
|
/// 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
|
2021-11-04 21:47:32 +01:00
|
|
|
}
|
2021-12-02 18:47:16 +01:00
|
|
|
|
2021-12-03 12:15:22 +01:00
|
|
|
/// Get the value of a sysvar by its id
|
|
|
|
pub fn get_sysvar<T: Sysvar>(&self, id: &Pubkey) -> Result<T, InstructionError> {
|
|
|
|
self.sysvars
|
|
|
|
.iter()
|
|
|
|
.find_map(|(key, data)| {
|
|
|
|
if id == key {
|
|
|
|
bincode::deserialize(data).ok()
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.ok_or_else(|| {
|
|
|
|
ic_msg!(self, "Unable to get sysvar {}", id);
|
|
|
|
InstructionError::UnsupportedSysvar
|
|
|
|
})
|
2021-11-04 21:47:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct MockInvokeContextPreparation {
|
2021-12-27 18:49:32 +01:00
|
|
|
pub transaction_accounts: Vec<TransactionAccount>,
|
2021-12-24 16:17:55 +01:00
|
|
|
pub instruction_accounts: Vec<InstructionAccount>,
|
2021-11-04 21:47:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn prepare_mock_invoke_context(
|
2021-12-27 18:49:32 +01:00
|
|
|
transaction_accounts: Vec<TransactionAccount>,
|
2021-12-17 14:01:12 +01:00
|
|
|
instruction_accounts: Vec<AccountMeta>,
|
2021-12-30 15:46:36 +01:00
|
|
|
program_indices: &[usize],
|
2021-11-04 21:47:32 +01:00
|
|
|
) -> MockInvokeContextPreparation {
|
2021-12-24 16:17:55 +01:00
|
|
|
let instruction_accounts = instruction_accounts
|
2021-11-04 21:47:32 +01:00
|
|
|
.iter()
|
2021-12-30 15:46:36 +01:00
|
|
|
.map(|account_meta| {
|
|
|
|
let index_in_transaction = transaction_accounts
|
2021-11-04 21:47:32 +01:00
|
|
|
.iter()
|
2021-12-24 16:17:55 +01:00
|
|
|
.position(|(key, _account)| *key == account_meta.pubkey)
|
2021-12-30 15:46:36 +01:00
|
|
|
.unwrap_or(transaction_accounts.len());
|
|
|
|
InstructionAccount {
|
|
|
|
index_in_transaction,
|
|
|
|
index_in_caller: program_indices.len().saturating_add(index_in_transaction),
|
|
|
|
is_signer: account_meta.is_signer,
|
|
|
|
is_writable: account_meta.is_writable,
|
|
|
|
}
|
2021-11-04 21:47:32 +01:00
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
MockInvokeContextPreparation {
|
2021-12-24 16:17:55 +01:00
|
|
|
transaction_accounts,
|
|
|
|
instruction_accounts,
|
2021-11-04 21:47:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-02 18:47:16 +01:00
|
|
|
pub fn with_mock_invoke_context<R, F: FnMut(&mut InvokeContext) -> R>(
|
2021-11-04 21:47:32 +01:00
|
|
|
loader_id: Pubkey,
|
|
|
|
account_size: usize,
|
|
|
|
mut callback: F,
|
|
|
|
) -> R {
|
|
|
|
let program_indices = vec![0, 1];
|
2021-12-17 14:01:12 +01:00
|
|
|
let transaction_accounts = vec![
|
2021-11-04 21:47:32 +01:00
|
|
|
(
|
|
|
|
loader_id,
|
2021-12-17 14:01:12 +01:00
|
|
|
AccountSharedData::new(0, 0, &solana_sdk::native_loader::id()),
|
2021-11-04 21:47:32 +01:00
|
|
|
),
|
|
|
|
(
|
|
|
|
Pubkey::new_unique(),
|
2021-12-17 14:01:12 +01:00
|
|
|
AccountSharedData::new(1, 0, &loader_id),
|
2021-11-04 21:47:32 +01:00
|
|
|
),
|
|
|
|
(
|
|
|
|
Pubkey::new_unique(),
|
2021-12-17 14:01:12 +01:00
|
|
|
AccountSharedData::new(2, account_size, &Pubkey::new_unique()),
|
2021-11-04 21:47:32 +01:00
|
|
|
),
|
|
|
|
];
|
2021-12-17 14:01:12 +01:00
|
|
|
let instruction_accounts = vec![AccountMeta {
|
|
|
|
pubkey: transaction_accounts[2].0,
|
|
|
|
is_signer: false,
|
|
|
|
is_writable: false,
|
|
|
|
}];
|
2021-12-30 15:46:36 +01:00
|
|
|
let preparation =
|
|
|
|
prepare_mock_invoke_context(transaction_accounts, instruction_accounts, &program_indices);
|
2021-12-27 18:49:32 +01:00
|
|
|
let transaction_context = TransactionContext::new(
|
|
|
|
preparation.transaction_accounts,
|
|
|
|
ComputeBudget::default().max_invoke_depth,
|
|
|
|
);
|
|
|
|
let mut invoke_context = InvokeContext::new_mock(&transaction_context, &[]);
|
2021-11-04 21:47:32 +01:00
|
|
|
invoke_context
|
2021-12-24 16:17:55 +01:00
|
|
|
.push(&preparation.instruction_accounts, &program_indices)
|
2021-11-04 21:47:32 +01:00
|
|
|
.unwrap();
|
|
|
|
callback(&mut invoke_context)
|
|
|
|
}
|
|
|
|
|
2021-12-06 17:01:20 -05:00
|
|
|
pub fn mock_process_instruction_with_sysvars(
|
2021-11-04 21:47:32 +01:00
|
|
|
loader_id: &Pubkey,
|
|
|
|
mut program_indices: Vec<usize>,
|
|
|
|
instruction_data: &[u8],
|
2021-12-27 18:49:32 +01:00
|
|
|
transaction_accounts: Vec<TransactionAccount>,
|
2021-12-17 14:01:12 +01:00
|
|
|
instruction_accounts: Vec<AccountMeta>,
|
|
|
|
expected_result: Result<(), InstructionError>,
|
2021-12-06 17:01:20 -05:00
|
|
|
sysvars: &[(Pubkey, Vec<u8>)],
|
2021-11-04 21:47:32 +01:00
|
|
|
process_instruction: ProcessInstructionWithContext,
|
2021-12-17 14:01:12 +01:00
|
|
|
) -> Vec<AccountSharedData> {
|
2021-12-30 15:46:36 +01:00
|
|
|
program_indices.insert(0, transaction_accounts.len());
|
|
|
|
let mut preparation =
|
|
|
|
prepare_mock_invoke_context(transaction_accounts, instruction_accounts, &program_indices);
|
2021-12-27 18:49:32 +01:00
|
|
|
let processor_account = AccountSharedData::new(0, 0, &solana_sdk::native_loader::id());
|
2021-12-24 16:17:55 +01:00
|
|
|
preparation
|
|
|
|
.transaction_accounts
|
|
|
|
.push((*loader_id, processor_account));
|
2021-12-27 18:49:32 +01:00
|
|
|
let transaction_context = TransactionContext::new(
|
|
|
|
preparation.transaction_accounts,
|
|
|
|
ComputeBudget::default().max_invoke_depth,
|
|
|
|
);
|
|
|
|
let mut invoke_context = InvokeContext::new_mock(&transaction_context, &[]);
|
2021-12-07 23:00:04 +01:00
|
|
|
invoke_context.sysvars = sysvars;
|
2021-12-17 14:01:12 +01:00
|
|
|
let result = invoke_context
|
2021-12-24 16:17:55 +01:00
|
|
|
.push(&preparation.instruction_accounts, &program_indices)
|
2021-12-17 14:01:12 +01:00
|
|
|
.and_then(|_| process_instruction(1, instruction_data, &mut invoke_context));
|
|
|
|
assert_eq!(result, expected_result);
|
2021-12-27 18:49:32 +01:00
|
|
|
let mut transaction_accounts = transaction_context.deconstruct_without_keys().unwrap();
|
|
|
|
transaction_accounts.pop();
|
|
|
|
transaction_accounts
|
2021-11-04 21:47:32 +01:00
|
|
|
}
|
|
|
|
|
2021-12-06 17:01:20 -05:00
|
|
|
pub fn mock_process_instruction(
|
|
|
|
loader_id: &Pubkey,
|
|
|
|
program_indices: Vec<usize>,
|
|
|
|
instruction_data: &[u8],
|
2021-12-27 18:49:32 +01:00
|
|
|
transaction_accounts: Vec<TransactionAccount>,
|
2021-12-17 14:01:12 +01:00
|
|
|
instruction_accounts: Vec<AccountMeta>,
|
|
|
|
expected_result: Result<(), InstructionError>,
|
2021-12-06 17:01:20 -05:00
|
|
|
process_instruction: ProcessInstructionWithContext,
|
2021-12-17 14:01:12 +01:00
|
|
|
) -> Vec<AccountSharedData> {
|
2021-12-06 17:01:20 -05:00
|
|
|
mock_process_instruction_with_sysvars(
|
|
|
|
loader_id,
|
|
|
|
program_indices,
|
|
|
|
instruction_data,
|
2021-12-17 14:01:12 +01:00
|
|
|
transaction_accounts,
|
|
|
|
instruction_accounts,
|
|
|
|
expected_result,
|
2021-12-06 17:01:20 -05:00
|
|
|
&[],
|
|
|
|
process_instruction,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-12-24 16:17:55 +01:00
|
|
|
/// Visit each unique instruction account index once
|
|
|
|
fn visit_each_account_once(
|
|
|
|
instruction_accounts: &[InstructionAccount],
|
|
|
|
work: &mut dyn FnMut(usize, &InstructionAccount) -> Result<(), InstructionError>,
|
|
|
|
) -> Result<(), InstructionError> {
|
|
|
|
'root: for (index, instruction_account) in instruction_accounts.iter().enumerate() {
|
|
|
|
// Note: This is an O(n^2) algorithm,
|
|
|
|
// but performed on a very small slice and requires no heap allocations
|
|
|
|
for before in instruction_accounts[..index].iter() {
|
2021-12-30 15:46:36 +01:00
|
|
|
if before.index_in_transaction == instruction_account.index_in_transaction {
|
2021-12-24 16:17:55 +01:00
|
|
|
continue 'root; // skip dups
|
|
|
|
}
|
|
|
|
}
|
|
|
|
work(index, instruction_account)?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-11-04 21:47:32 +01:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2021-12-03 09:00:31 -08:00
|
|
|
use {
|
|
|
|
super::*,
|
|
|
|
serde::{Deserialize, Serialize},
|
2021-12-25 10:00:40 +01:00
|
|
|
solana_sdk::account::{ReadableAccount, WritableAccount},
|
2021-11-04 21:47:32 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
enum MockInstruction {
|
|
|
|
NoopSuccess,
|
|
|
|
NoopFail,
|
|
|
|
ModifyOwned,
|
|
|
|
ModifyNotOwned,
|
|
|
|
ModifyReadonly,
|
2021-12-30 21:21:42 -05:00
|
|
|
ConsumeComputeUnits {
|
2021-12-31 17:55:27 +01:00
|
|
|
compute_units_to_consume: u64,
|
2021-12-30 21:21:42 -05:00
|
|
|
desired_result: Result<(), InstructionError>,
|
|
|
|
},
|
2021-11-04 21:47:32 +01:00
|
|
|
}
|
|
|
|
|
2021-12-24 16:17:55 +01:00
|
|
|
#[test]
|
|
|
|
fn test_visit_each_account_once() {
|
|
|
|
let do_work = |accounts: &[InstructionAccount]| -> (usize, usize, usize) {
|
|
|
|
let mut unique_entries = 0;
|
|
|
|
let mut index_sum_a = 0;
|
|
|
|
let mut index_sum_b = 0;
|
|
|
|
let mut work = |index_in_instruction: usize, entry: &InstructionAccount| {
|
|
|
|
unique_entries += 1;
|
|
|
|
index_sum_a += index_in_instruction;
|
2021-12-30 15:46:36 +01:00
|
|
|
index_sum_b += entry.index_in_transaction;
|
2021-12-24 16:17:55 +01:00
|
|
|
Ok(())
|
|
|
|
};
|
|
|
|
visit_each_account_once(accounts, &mut work).unwrap();
|
|
|
|
|
|
|
|
(unique_entries, index_sum_a, index_sum_b)
|
|
|
|
};
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
(3, 3, 19),
|
|
|
|
do_work(&[
|
|
|
|
InstructionAccount {
|
2021-12-30 15:46:36 +01:00
|
|
|
index_in_transaction: 7,
|
|
|
|
index_in_caller: 0,
|
2021-12-24 16:17:55 +01:00
|
|
|
is_signer: false,
|
|
|
|
is_writable: false,
|
|
|
|
},
|
|
|
|
InstructionAccount {
|
2021-12-30 15:46:36 +01:00
|
|
|
index_in_transaction: 3,
|
|
|
|
index_in_caller: 1,
|
2021-12-24 16:17:55 +01:00
|
|
|
is_signer: false,
|
|
|
|
is_writable: false,
|
|
|
|
},
|
|
|
|
InstructionAccount {
|
2021-12-30 15:46:36 +01:00
|
|
|
index_in_transaction: 9,
|
|
|
|
index_in_caller: 2,
|
2021-12-24 16:17:55 +01:00
|
|
|
is_signer: false,
|
|
|
|
is_writable: false,
|
|
|
|
},
|
|
|
|
InstructionAccount {
|
2021-12-30 15:46:36 +01:00
|
|
|
index_in_transaction: 3,
|
|
|
|
index_in_caller: 1,
|
2021-12-24 16:17:55 +01:00
|
|
|
is_signer: false,
|
|
|
|
is_writable: false,
|
|
|
|
},
|
|
|
|
])
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-12-01 08:54:42 +01:00
|
|
|
#[test]
|
|
|
|
fn test_program_entry_debug() {
|
|
|
|
#[allow(clippy::unnecessary_wraps)]
|
|
|
|
fn mock_process_instruction(
|
|
|
|
_first_instruction_account: usize,
|
|
|
|
_data: &[u8],
|
2021-12-02 18:47:16 +01:00
|
|
|
_invoke_context: &mut InvokeContext,
|
2021-12-01 08:54:42 +01:00
|
|
|
) -> Result<(), InstructionError> {
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
#[allow(clippy::unnecessary_wraps)]
|
|
|
|
fn mock_ix_processor(
|
|
|
|
_first_instruction_account: usize,
|
|
|
|
_data: &[u8],
|
2021-12-02 18:47:16 +01:00
|
|
|
_context: &mut InvokeContext,
|
2021-12-01 08:54:42 +01:00
|
|
|
) -> 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());
|
|
|
|
}
|
|
|
|
|
2021-11-09 13:35:49 +01:00
|
|
|
#[allow(clippy::integer_arithmetic)]
|
2021-11-04 21:47:32 +01:00
|
|
|
fn mock_process_instruction(
|
|
|
|
first_instruction_account: usize,
|
|
|
|
data: &[u8],
|
2021-12-02 18:47:16 +01:00
|
|
|
invoke_context: &mut InvokeContext,
|
2021-11-04 21:47:32 +01:00
|
|
|
) -> Result<(), InstructionError> {
|
|
|
|
let program_id = invoke_context.get_caller()?;
|
|
|
|
let keyed_accounts = invoke_context.get_keyed_accounts()?;
|
|
|
|
assert_eq!(
|
|
|
|
*program_id,
|
|
|
|
keyed_account_at_index(keyed_accounts, first_instruction_account)?.owner()?
|
|
|
|
);
|
|
|
|
assert_ne!(
|
|
|
|
keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?.owner()?,
|
|
|
|
*keyed_account_at_index(keyed_accounts, first_instruction_account)?.unsigned_key()
|
|
|
|
);
|
|
|
|
|
|
|
|
if let Ok(instruction) = bincode::deserialize(data) {
|
|
|
|
match instruction {
|
|
|
|
MockInstruction::NoopSuccess => (),
|
|
|
|
MockInstruction::NoopFail => return Err(InstructionError::GenericError),
|
|
|
|
MockInstruction::ModifyOwned => {
|
|
|
|
keyed_account_at_index(keyed_accounts, first_instruction_account)?
|
|
|
|
.try_account_ref_mut()?
|
|
|
|
.data_as_mut_slice()[0] = 1
|
|
|
|
}
|
|
|
|
MockInstruction::ModifyNotOwned => {
|
|
|
|
keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?
|
|
|
|
.try_account_ref_mut()?
|
|
|
|
.data_as_mut_slice()[0] = 1
|
|
|
|
}
|
|
|
|
MockInstruction::ModifyReadonly => {
|
|
|
|
keyed_account_at_index(keyed_accounts, first_instruction_account + 2)?
|
|
|
|
.try_account_ref_mut()?
|
|
|
|
.data_as_mut_slice()[0] = 1
|
|
|
|
}
|
2021-12-30 21:21:42 -05:00
|
|
|
MockInstruction::ConsumeComputeUnits {
|
2021-12-31 17:55:27 +01:00
|
|
|
compute_units_to_consume,
|
2021-12-30 21:21:42 -05:00
|
|
|
desired_result,
|
|
|
|
} => {
|
|
|
|
invoke_context
|
|
|
|
.get_compute_meter()
|
|
|
|
.borrow_mut()
|
2021-12-31 17:55:27 +01:00
|
|
|
.consume(compute_units_to_consume)
|
2021-12-30 21:21:42 -05:00
|
|
|
.unwrap();
|
|
|
|
return desired_result;
|
|
|
|
}
|
2021-11-04 21:47:32 +01:00
|
|
|
}
|
|
|
|
} 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![];
|
2021-12-24 16:17:55 +01:00
|
|
|
let mut instruction_accounts = vec![];
|
|
|
|
for index in 0..MAX_DEPTH {
|
2021-11-04 21:47:32 +01:00
|
|
|
invoke_stack.push(solana_sdk::pubkey::new_rand());
|
|
|
|
accounts.push((
|
|
|
|
solana_sdk::pubkey::new_rand(),
|
2021-12-27 18:49:32 +01:00
|
|
|
AccountSharedData::new(index as u64, 1, &invoke_stack[index]),
|
2021-11-04 21:47:32 +01:00
|
|
|
));
|
2021-12-24 16:17:55 +01:00
|
|
|
instruction_accounts.push(InstructionAccount {
|
2021-12-30 15:46:36 +01:00
|
|
|
index_in_transaction: index,
|
|
|
|
index_in_caller: 1 + index,
|
2021-12-24 16:17:55 +01:00
|
|
|
is_signer: false,
|
|
|
|
is_writable: true,
|
|
|
|
});
|
2021-11-04 21:47:32 +01:00
|
|
|
}
|
2021-12-24 16:17:55 +01:00
|
|
|
for (index, program_id) in invoke_stack.iter().enumerate() {
|
2021-11-04 21:47:32 +01:00
|
|
|
accounts.push((
|
|
|
|
*program_id,
|
2021-12-27 18:49:32 +01:00
|
|
|
AccountSharedData::new(1, 1, &solana_sdk::pubkey::Pubkey::default()),
|
2021-11-04 21:47:32 +01:00
|
|
|
));
|
2021-12-24 16:17:55 +01:00
|
|
|
instruction_accounts.push(InstructionAccount {
|
2021-12-30 15:46:36 +01:00
|
|
|
index_in_transaction: index,
|
|
|
|
index_in_caller: 1 + index,
|
2021-12-24 16:17:55 +01:00
|
|
|
is_signer: false,
|
|
|
|
is_writable: false,
|
|
|
|
});
|
2021-11-04 21:47:32 +01:00
|
|
|
}
|
2021-12-30 15:46:36 +01:00
|
|
|
let transaction_context = TransactionContext::new(accounts, MAX_DEPTH);
|
2021-12-27 18:49:32 +01:00
|
|
|
let mut invoke_context = InvokeContext::new_mock(&transaction_context, &[]);
|
2021-11-04 21:47:32 +01:00
|
|
|
|
|
|
|
// Check call depth increases and has a limit
|
|
|
|
let mut depth_reached = 0;
|
|
|
|
for _ in 0..invoke_stack.len() {
|
|
|
|
if Err(InstructionError::CallDepth)
|
2021-12-24 16:17:55 +01:00
|
|
|
== invoke_context.push(&instruction_accounts, &[MAX_DEPTH + depth_reached])
|
2021-11-04 21:47:32 +01:00
|
|
|
{
|
|
|
|
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;
|
2021-12-24 16:17:55 +01:00
|
|
|
let instruction_accounts = vec![
|
|
|
|
InstructionAccount {
|
2021-12-30 15:46:36 +01:00
|
|
|
index_in_transaction: not_owned_index,
|
|
|
|
index_in_caller: 1 + not_owned_index,
|
2021-12-24 16:17:55 +01:00
|
|
|
is_signer: false,
|
|
|
|
is_writable: true,
|
|
|
|
},
|
|
|
|
InstructionAccount {
|
2021-12-30 15:46:36 +01:00
|
|
|
index_in_transaction: owned_index,
|
|
|
|
index_in_caller: 1 + owned_index,
|
2021-12-24 16:17:55 +01:00
|
|
|
is_signer: false,
|
|
|
|
is_writable: true,
|
|
|
|
},
|
2021-11-04 21:47:32 +01:00
|
|
|
];
|
|
|
|
|
|
|
|
// modify account owned by the program
|
2021-12-27 18:49:32 +01:00
|
|
|
transaction_context
|
|
|
|
.get_account_at_index(owned_index)
|
|
|
|
.borrow_mut()
|
|
|
|
.data_as_mut_slice()[0] = (MAX_DEPTH + owned_index) as u8;
|
2021-11-04 21:47:32 +01:00
|
|
|
invoke_context
|
2021-12-24 16:17:55 +01:00
|
|
|
.verify_and_update(&instruction_accounts, None)
|
2021-11-04 21:47:32 +01:00
|
|
|
.unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
invoke_context.pre_accounts[owned_index].data()[0],
|
|
|
|
(MAX_DEPTH + owned_index) as u8
|
|
|
|
);
|
|
|
|
|
|
|
|
// modify account not owned by the program
|
2021-12-27 18:49:32 +01:00
|
|
|
let data = transaction_context
|
|
|
|
.get_account_at_index(not_owned_index)
|
|
|
|
.borrow_mut()
|
|
|
|
.data()[0];
|
|
|
|
transaction_context
|
|
|
|
.get_account_at_index(not_owned_index)
|
|
|
|
.borrow_mut()
|
|
|
|
.data_as_mut_slice()[0] = (MAX_DEPTH + not_owned_index) as u8;
|
2021-11-04 21:47:32 +01:00
|
|
|
assert_eq!(
|
2021-12-24 16:17:55 +01:00
|
|
|
invoke_context.verify_and_update(&instruction_accounts, None),
|
2021-11-04 21:47:32 +01:00
|
|
|
Err(InstructionError::ExternalAccountDataModified)
|
|
|
|
);
|
|
|
|
assert_eq!(invoke_context.pre_accounts[not_owned_index].data()[0], data);
|
2021-12-27 18:49:32 +01:00
|
|
|
transaction_context
|
|
|
|
.get_account_at_index(not_owned_index)
|
|
|
|
.borrow_mut()
|
|
|
|
.data_as_mut_slice()[0] = data;
|
2021-11-04 21:47:32 +01:00
|
|
|
|
|
|
|
invoke_context.pop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_invoke_context_verify() {
|
2021-12-27 18:49:32 +01:00
|
|
|
let accounts = vec![(solana_sdk::pubkey::new_rand(), AccountSharedData::default())];
|
2021-12-24 16:17:55 +01:00
|
|
|
let instruction_accounts = vec![];
|
|
|
|
let program_indices = vec![0];
|
2021-12-27 18:49:32 +01:00
|
|
|
let transaction_context = TransactionContext::new(accounts, 1);
|
|
|
|
let mut invoke_context = InvokeContext::new_mock(&transaction_context, &[]);
|
2021-11-04 21:47:32 +01:00
|
|
|
invoke_context
|
2021-12-24 16:17:55 +01:00
|
|
|
.push(&instruction_accounts, &program_indices)
|
2021-11-04 21:47:32 +01:00
|
|
|
.unwrap();
|
|
|
|
assert!(invoke_context
|
2021-12-24 16:17:55 +01:00
|
|
|
.verify(&instruction_accounts, &program_indices)
|
2021-11-04 21:47:32 +01:00
|
|
|
.is_ok());
|
|
|
|
|
2021-12-27 18:49:32 +01:00
|
|
|
let mut _borrowed = transaction_context.get_account_at_index(0).borrow();
|
2021-11-04 21:47:32 +01:00
|
|
|
assert_eq!(
|
2021-12-24 16:17:55 +01:00
|
|
|
invoke_context.verify(&instruction_accounts, &program_indices),
|
2021-11-04 21:47:32 +01:00
|
|
|
Err(InstructionError::AccountBorrowOutstanding)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2021-12-31 17:55:27 +01:00
|
|
|
fn test_process_instruction() {
|
2021-11-04 21:47:32 +01:00
|
|
|
let callee_program_id = solana_sdk::pubkey::new_rand();
|
2021-12-27 18:49:32 +01:00
|
|
|
let builtin_programs = &[BuiltinProgram {
|
|
|
|
program_id: callee_program_id,
|
|
|
|
process_instruction: mock_process_instruction,
|
|
|
|
}];
|
2021-11-04 21:47:32 +01:00
|
|
|
|
|
|
|
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![
|
2021-12-27 18:49:32 +01:00
|
|
|
(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),
|
2021-12-31 17:55:27 +01:00
|
|
|
(solana_sdk::pubkey::new_rand(), loader_account),
|
2021-11-04 21:47:32 +01:00
|
|
|
];
|
|
|
|
let metas = vec![
|
|
|
|
AccountMeta::new(accounts[0].0, false),
|
|
|
|
AccountMeta::new(accounts[1].0, false),
|
|
|
|
AccountMeta::new_readonly(accounts[2].0, false),
|
|
|
|
];
|
2021-12-31 17:55:27 +01:00
|
|
|
let instruction_accounts = (0..4)
|
|
|
|
.map(|index_in_transaction| InstructionAccount {
|
2021-12-30 15:46:36 +01:00
|
|
|
index_in_transaction,
|
2021-12-31 17:55:27 +01:00
|
|
|
index_in_caller: 1 + index_in_transaction,
|
|
|
|
is_signer: false,
|
|
|
|
is_writable: index_in_transaction < 2,
|
2021-12-25 10:00:40 +01:00
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
2021-12-31 17:55:27 +01:00
|
|
|
let transaction_context = TransactionContext::new(accounts, 2);
|
2021-12-27 18:49:32 +01:00
|
|
|
let mut invoke_context = InvokeContext::new_mock(&transaction_context, builtin_programs);
|
2021-11-04 21:47:32 +01:00
|
|
|
|
2021-12-31 17:55:27 +01:00
|
|
|
// 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
|
2021-12-30 21:21:42 -05:00
|
|
|
invoke_context
|
2021-12-31 17:55:27 +01:00
|
|
|
.transaction_context
|
|
|
|
.get_account_at_index(1)
|
|
|
|
.borrow_mut()
|
|
|
|
.data_as_mut_slice()[0] = 1;
|
|
|
|
assert_eq!(
|
|
|
|
invoke_context.native_invoke(inner_instruction.clone(), &[]),
|
|
|
|
Err(InstructionError::ExternalAccountDataModified)
|
|
|
|
);
|
2021-12-30 21:21:42 -05:00
|
|
|
invoke_context
|
2021-12-31 17:55:27 +01:00
|
|
|
.transaction_context
|
|
|
|
.get_account_at_index(1)
|
|
|
|
.borrow_mut()
|
|
|
|
.data_as_mut_slice()[0] = 0;
|
2021-11-04 21:47:32 +01:00
|
|
|
|
2021-12-31 17:55:27 +01:00
|
|
|
// readonly account
|
2021-11-04 21:47:32 +01:00
|
|
|
invoke_context
|
2021-12-31 17:55:27 +01:00
|
|
|
.transaction_context
|
|
|
|
.get_account_at_index(2)
|
|
|
|
.borrow_mut()
|
|
|
|
.data_as_mut_slice()[0] = 1;
|
2021-11-04 21:47:32 +01:00
|
|
|
assert_eq!(
|
2021-12-31 17:55:27 +01:00
|
|
|
invoke_context.native_invoke(inner_instruction, &[]),
|
|
|
|
Err(InstructionError::ReadonlyDataModified)
|
2021-11-04 21:47:32 +01:00
|
|
|
);
|
2021-12-31 17:55:27 +01:00
|
|
|
invoke_context
|
|
|
|
.transaction_context
|
|
|
|
.get_account_at_index(2)
|
|
|
|
.borrow_mut()
|
|
|
|
.data_as_mut_slice()[0] = 0;
|
|
|
|
|
2021-11-04 21:47:32 +01:00
|
|
|
invoke_context.pop();
|
|
|
|
}
|
|
|
|
|
2021-12-31 17:55:27 +01:00
|
|
|
// Internal modification tests
|
2021-11-04 21:47:32 +01:00
|
|
|
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 {
|
2021-12-31 17:55:27 +01:00
|
|
|
invoke_context.push(&instruction_accounts, &[4]).unwrap();
|
|
|
|
let inner_instruction =
|
2021-11-04 21:47:32 +01:00
|
|
|
Instruction::new_with_bincode(callee_program_id, &case.0, metas.clone());
|
2021-12-31 17:55:27 +01:00
|
|
|
assert_eq!(invoke_context.native_invoke(inner_instruction, &[]), case.1);
|
|
|
|
invoke_context.pop();
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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(),
|
2021-11-04 21:47:32 +01:00
|
|
|
);
|
2021-12-31 17:55:27 +01:00
|
|
|
let (inner_instruction_accounts, caller_write_privileges, 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,
|
|
|
|
Some(&caller_write_privileges),
|
|
|
|
&program_indices,
|
|
|
|
&mut compute_units_consumed,
|
|
|
|
);
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
2021-11-04 21:47:32 +01:00
|
|
|
invoke_context.pop();
|
|
|
|
}
|
|
|
|
}
|
2021-11-11 14:09:28 -08:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_invoke_context_compute_budget() {
|
|
|
|
let accounts = vec![
|
2021-12-27 18:49:32 +01:00
|
|
|
(solana_sdk::pubkey::new_rand(), AccountSharedData::default()),
|
|
|
|
(crate::neon_evm_program::id(), AccountSharedData::default()),
|
2021-11-11 14:09:28 -08:00
|
|
|
];
|
|
|
|
|
|
|
|
let mut feature_set = FeatureSet::all_enabled();
|
|
|
|
feature_set.deactivate(&tx_wide_compute_cap::id());
|
|
|
|
feature_set.deactivate(&requestable_heap_size::id());
|
2021-12-27 18:49:32 +01:00
|
|
|
let transaction_context = TransactionContext::new(accounts, 1);
|
|
|
|
let mut invoke_context = InvokeContext::new_mock(&transaction_context, &[]);
|
2021-12-03 12:15:22 +01:00
|
|
|
invoke_context.feature_set = Arc::new(feature_set);
|
2021-11-11 14:09:28 -08:00
|
|
|
|
2021-12-24 16:17:55 +01:00
|
|
|
invoke_context.push(&[], &[0]).unwrap();
|
2021-11-11 14:09:28 -08:00
|
|
|
assert_eq!(
|
|
|
|
*invoke_context.get_compute_budget(),
|
|
|
|
ComputeBudget::default()
|
|
|
|
);
|
|
|
|
invoke_context.pop();
|
|
|
|
|
2021-12-24 16:17:55 +01:00
|
|
|
invoke_context.push(&[], &[1]).unwrap();
|
2021-11-11 14:09:28 -08:00
|
|
|
let expected_compute_budget = ComputeBudget {
|
|
|
|
max_units: 500_000,
|
|
|
|
heap_size: Some(256_usize.saturating_mul(1024)),
|
|
|
|
..ComputeBudget::default()
|
|
|
|
};
|
|
|
|
assert_eq!(
|
|
|
|
*invoke_context.get_compute_budget(),
|
|
|
|
expected_compute_budget
|
|
|
|
);
|
|
|
|
invoke_context.pop();
|
|
|
|
|
2021-12-24 16:17:55 +01:00
|
|
|
invoke_context.push(&[], &[0]).unwrap();
|
2021-11-11 14:09:28 -08:00
|
|
|
assert_eq!(
|
|
|
|
*invoke_context.get_compute_budget(),
|
|
|
|
ComputeBudget::default()
|
|
|
|
);
|
|
|
|
invoke_context.pop();
|
|
|
|
}
|
2021-11-04 21:47:32 +01:00
|
|
|
}
|