2021-12-03 09:00:31 -08:00
|
|
|
use {
|
|
|
|
crate::{
|
2022-07-08 11:26:54 -07:00
|
|
|
accounts_data_meter::AccountsDataMeter,
|
2022-01-17 04:48:00 -08:00
|
|
|
compute_budget::ComputeBudget,
|
2022-10-12 09:09:03 -07:00
|
|
|
executor_cache::TransactionExecutorCache,
|
2021-12-20 20:03:20 -08:00
|
|
|
ic_logger_msg, ic_msg,
|
|
|
|
log_collector::LogCollector,
|
|
|
|
pre_account::PreAccount,
|
2022-05-25 16:27:00 -07:00
|
|
|
stable_log,
|
2022-01-11 19:19:11 -08:00
|
|
|
sysvar_cache::SysvarCache,
|
2021-12-20 20:03:20 -08:00
|
|
|
timings::{ExecuteDetailsTimings, ExecuteTimings},
|
2021-12-03 09:00:31 -08:00
|
|
|
},
|
2021-12-20 20:03:20 -08:00
|
|
|
solana_measure::measure::Measure,
|
2022-11-15 06:21:11 -08:00
|
|
|
solana_rbpf::vm::ContextObject,
|
2021-12-03 09:00:31 -08:00
|
|
|
solana_sdk::{
|
|
|
|
account::{AccountSharedData, ReadableAccount},
|
2023-03-24 09:31:01 -07:00
|
|
|
feature_set::{
|
|
|
|
enable_early_verification_of_account_modifications, native_programs_consume_cu,
|
|
|
|
FeatureSet,
|
|
|
|
},
|
2021-12-03 09:00:31 -08:00
|
|
|
hash::Hash,
|
2023-02-16 05:16:25 -08:00
|
|
|
instruction::{AccountMeta, InstructionError},
|
2021-12-24 07:17:55 -08:00
|
|
|
native_loader,
|
2021-12-03 09:00:31 -08:00
|
|
|
pubkey::Pubkey,
|
|
|
|
rent::Rent,
|
2022-01-05 16:08:35 -08:00
|
|
|
saturating_add_assign,
|
2023-02-16 05:16:25 -08:00
|
|
|
stable_layout::stable_instruction::StableInstruction,
|
2022-09-06 02:31:40 -07:00
|
|
|
transaction_context::{
|
|
|
|
IndexOfAccount, InstructionAccount, TransactionAccount, TransactionContext,
|
|
|
|
},
|
2021-11-04 13:47:32 -07:00
|
|
|
},
|
2022-04-11 16:05:09 -07:00
|
|
|
std::{
|
|
|
|
alloc::Layout,
|
|
|
|
cell::RefCell,
|
|
|
|
fmt::{self, Debug},
|
|
|
|
rc::Rc,
|
|
|
|
sync::Arc,
|
|
|
|
},
|
2021-11-04 13:47:32 -07:00
|
|
|
};
|
2021-11-30 23:54:42 -08:00
|
|
|
|
2023-04-05 06:50:34 -07:00
|
|
|
/// Adapter so we can unify the interfaces of built-in programs and syscalls
|
|
|
|
#[macro_export]
|
|
|
|
macro_rules! declare_process_instruction {
|
|
|
|
($cu_to_consume:expr) => {
|
|
|
|
pub fn process_instruction(
|
|
|
|
invoke_context: &mut InvokeContext,
|
|
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
|
|
if invoke_context
|
|
|
|
.feature_set
|
|
|
|
.is_active(&feature_set::native_programs_consume_cu::id())
|
|
|
|
{
|
|
|
|
invoke_context.consume_checked($cu_to_consume)?;
|
|
|
|
}
|
|
|
|
process_instruction_inner(invoke_context)
|
|
|
|
.map_err(|err| Box::new(err) as Box<dyn std::error::Error>)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
pub type ProcessInstructionWithContext =
|
|
|
|
fn(&mut InvokeContext) -> Result<(), Box<dyn std::error::Error>>;
|
2021-11-30 23:54:42 -08: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
|
2022-04-01 06:48:05 -07:00
|
|
|
type ErasedProcessInstructionWithContext =
|
2023-04-05 06:50:34 -07:00
|
|
|
fn(&'static mut InvokeContext<'static>) -> Result<(), Box<dyn std::error::Error>>;
|
2021-11-30 23:54:42 -08:00
|
|
|
|
|
|
|
// 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;
|
2023-03-16 09:21:06 -07:00
|
|
|
write!(f, "{}: {:p}", self.program_id, erased_instruction)
|
2021-11-30 23:54:42 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-15 06:21:11 -08:00
|
|
|
impl<'a> ContextObject for InvokeContext<'a> {
|
|
|
|
fn trace(&mut self, state: [u64; 12]) {
|
2023-01-28 01:03:31 -08:00
|
|
|
self.trace_log_stack
|
|
|
|
.last_mut()
|
|
|
|
.expect("Inconsistent trace log stack")
|
2023-02-10 07:17:44 -08:00
|
|
|
.trace_log
|
2023-01-28 01:03:31 -08:00
|
|
|
.push(state);
|
2021-11-04 13:47:32 -07:00
|
|
|
}
|
2022-11-15 06:21:11 -08:00
|
|
|
|
|
|
|
fn consume(&mut self, amount: u64) {
|
2023-02-10 07:17:44 -08:00
|
|
|
self.log_consumed_bpf_units(amount);
|
2022-11-15 06:21:11 -08:00
|
|
|
// 1 to 1 instruction to compute unit mapping
|
|
|
|
// ignore overflow, Ebpf will bail if exceeded
|
|
|
|
let mut compute_meter = self.compute_meter.borrow_mut();
|
|
|
|
*compute_meter = compute_meter.saturating_sub(amount);
|
2021-12-01 23:58:02 -08:00
|
|
|
}
|
2022-11-15 06:21:11 -08:00
|
|
|
|
|
|
|
fn get_remaining(&self) -> u64 {
|
|
|
|
*self.compute_meter.borrow()
|
2021-11-04 13:47:32 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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>>,
|
|
|
|
}
|
|
|
|
|
2023-02-10 07:17:44 -08:00
|
|
|
#[derive(Default)]
|
|
|
|
pub struct TraceLogStackFrame {
|
|
|
|
pub trace_log: Vec<[u64; 12]>,
|
|
|
|
pub consumed_bpf_units: RefCell<Vec<(usize, u64)>>,
|
|
|
|
}
|
|
|
|
|
2021-12-02 09:47:16 -08:00
|
|
|
pub struct InvokeContext<'a> {
|
2022-01-03 14:30:56 -08:00
|
|
|
pub transaction_context: &'a mut TransactionContext,
|
2021-11-04 13:47:32 -07:00
|
|
|
rent: Rent,
|
|
|
|
pre_accounts: Vec<PreAccount>,
|
2021-11-30 23:54:42 -08:00
|
|
|
builtin_programs: &'a [BuiltinProgram],
|
2023-04-03 08:23:24 -07:00
|
|
|
sysvar_cache: &'a SysvarCache,
|
2023-02-10 07:17:44 -08:00
|
|
|
pub trace_log_stack: Vec<TraceLogStackFrame>,
|
2021-11-23 04:23:40 -08:00
|
|
|
log_collector: Option<Rc<RefCell<LogCollector>>>,
|
2021-11-04 13:47:32 -07:00
|
|
|
compute_budget: ComputeBudget,
|
2021-11-11 14:09:28 -08:00
|
|
|
current_compute_budget: ComputeBudget,
|
2022-11-15 06:21:11 -08:00
|
|
|
compute_meter: RefCell<u64>,
|
2021-12-28 03:14:48 -08:00
|
|
|
accounts_data_meter: AccountsDataMeter,
|
2022-10-12 09:09:03 -07:00
|
|
|
pub tx_executor_cache: Rc<RefCell<TransactionExecutorCache>>,
|
2021-12-03 03:15:22 -08:00
|
|
|
pub feature_set: Arc<FeatureSet>,
|
2021-11-04 13:47:32 -07:00
|
|
|
pub timings: ExecuteDetailsTimings,
|
2021-12-03 03:15:22 -08:00
|
|
|
pub blockhash: Hash,
|
|
|
|
pub lamports_per_signature: u64,
|
2022-05-19 15:14:28 -07:00
|
|
|
syscall_context: Vec<Option<SyscallContext>>,
|
2023-02-10 07:17:44 -08:00
|
|
|
pub enable_instruction_tracing: bool,
|
2021-11-04 13:47:32 -07:00
|
|
|
}
|
2021-12-02 09:47:16 -08:00
|
|
|
|
|
|
|
impl<'a> InvokeContext<'a> {
|
2021-11-04 13:47:32 -07:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
pub fn new(
|
2022-01-03 14:30:56 -08:00
|
|
|
transaction_context: &'a mut TransactionContext,
|
2021-11-04 13:47:32 -07:00
|
|
|
rent: Rent,
|
2021-11-30 23:54:42 -08:00
|
|
|
builtin_programs: &'a [BuiltinProgram],
|
2023-04-03 08:23:24 -07:00
|
|
|
sysvar_cache: &'a SysvarCache,
|
2021-11-23 04:23:40 -08:00
|
|
|
log_collector: Option<Rc<RefCell<LogCollector>>>,
|
2021-11-04 13:47:32 -07:00
|
|
|
compute_budget: ComputeBudget,
|
2022-10-12 09:09:03 -07:00
|
|
|
tx_executor_cache: Rc<RefCell<TransactionExecutorCache>>,
|
2021-11-04 13:47:32 -07:00
|
|
|
feature_set: Arc<FeatureSet>,
|
|
|
|
blockhash: Hash,
|
|
|
|
lamports_per_signature: u64,
|
2022-07-06 10:27:42 -07:00
|
|
|
prev_accounts_data_len: u64,
|
2021-11-04 13:47:32 -07:00
|
|
|
) -> Self {
|
|
|
|
Self {
|
2021-12-27 09:49:32 -08:00
|
|
|
transaction_context,
|
2021-11-04 13:47:32 -07:00
|
|
|
rent,
|
|
|
|
pre_accounts: Vec::new(),
|
2021-11-30 23:54:42 -08:00
|
|
|
builtin_programs,
|
2022-01-11 19:19:11 -08:00
|
|
|
sysvar_cache,
|
2023-02-10 07:17:44 -08:00
|
|
|
trace_log_stack: vec![TraceLogStackFrame::default()],
|
2021-11-23 04:23:40 -08:00
|
|
|
log_collector,
|
2021-11-11 14:09:28 -08:00
|
|
|
current_compute_budget: compute_budget,
|
2021-11-04 13:47:32 -07:00
|
|
|
compute_budget,
|
2022-11-15 06:21:11 -08:00
|
|
|
compute_meter: RefCell::new(compute_budget.compute_unit_limit),
|
2022-07-06 10:27:42 -07:00
|
|
|
accounts_data_meter: AccountsDataMeter::new(prev_accounts_data_len),
|
2022-10-12 09:09:03 -07:00
|
|
|
tx_executor_cache,
|
2021-11-04 13:47:32 -07:00
|
|
|
feature_set,
|
|
|
|
timings: ExecuteDetailsTimings::default(),
|
|
|
|
blockhash,
|
|
|
|
lamports_per_signature,
|
2022-05-19 15:14:28 -07:00
|
|
|
syscall_context: Vec::new(),
|
2023-02-10 07:17:44 -08:00
|
|
|
enable_instruction_tracing: false,
|
2021-11-04 13:47:32 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-17 10:35:07 -08:00
|
|
|
/// Push a stack frame onto the invocation stack
|
2022-09-03 01:34:57 -07:00
|
|
|
pub fn push(&mut self) -> Result<(), InstructionError> {
|
|
|
|
let instruction_context = self
|
|
|
|
.transaction_context
|
|
|
|
.get_instruction_context_at_index_in_trace(
|
|
|
|
self.transaction_context.get_instruction_trace_length(),
|
|
|
|
)?;
|
|
|
|
let program_id = instruction_context
|
|
|
|
.get_last_program_key(self.transaction_context)
|
|
|
|
.map_err(|_| InstructionError::UnsupportedProgramId)?;
|
2022-01-03 14:30:56 -08:00
|
|
|
if self
|
|
|
|
.transaction_context
|
|
|
|
.get_instruction_context_stack_height()
|
|
|
|
== 0
|
|
|
|
{
|
2022-06-22 15:12:12 -07:00
|
|
|
self.current_compute_budget = self.compute_budget;
|
2021-11-11 14:09:28 -08:00
|
|
|
|
2022-07-15 00:31:34 -07:00
|
|
|
if !self
|
|
|
|
.feature_set
|
|
|
|
.is_active(&enable_early_verification_of_account_modifications::id())
|
2022-05-26 11:55:58 -07:00
|
|
|
{
|
2022-09-06 02:31:40 -07:00
|
|
|
self.pre_accounts = Vec::with_capacity(
|
|
|
|
instruction_context.get_number_of_instruction_accounts() as usize,
|
|
|
|
);
|
2022-09-03 01:34:57 -07:00
|
|
|
for instruction_account_index in
|
|
|
|
0..instruction_context.get_number_of_instruction_accounts()
|
2021-12-30 06:46:36 -08:00
|
|
|
{
|
2022-09-03 01:34:57 -07:00
|
|
|
if instruction_context
|
|
|
|
.is_instruction_account_duplicate(instruction_account_index)?
|
|
|
|
.is_some()
|
|
|
|
{
|
2022-07-15 00:31:34 -07:00
|
|
|
continue; // Skip duplicate account
|
|
|
|
}
|
2022-09-03 01:34:57 -07:00
|
|
|
let index_in_transaction = instruction_context
|
|
|
|
.get_index_of_instruction_account_in_transaction(
|
|
|
|
instruction_account_index,
|
|
|
|
)?;
|
|
|
|
if index_in_transaction >= self.transaction_context.get_number_of_accounts() {
|
2022-07-15 00:31:34 -07:00
|
|
|
return Err(InstructionError::MissingAccount);
|
|
|
|
}
|
|
|
|
let account = self
|
|
|
|
.transaction_context
|
2022-09-03 01:34:57 -07:00
|
|
|
.get_account_at_index(index_in_transaction)?
|
2022-07-15 00:31:34 -07:00
|
|
|
.borrow()
|
|
|
|
.clone();
|
|
|
|
self.pre_accounts.push(PreAccount::new(
|
2022-09-03 01:34:57 -07:00
|
|
|
self.transaction_context
|
|
|
|
.get_key_of_account_at_index(index_in_transaction)?,
|
2022-07-15 00:31:34 -07:00
|
|
|
account,
|
|
|
|
));
|
2021-11-04 13:47:32 -07:00
|
|
|
}
|
2022-05-26 11:55:58 -07:00
|
|
|
}
|
2021-11-19 11:43:42 -08:00
|
|
|
} else {
|
2022-01-03 14:30:56 -08:00
|
|
|
let contains = (0..self
|
|
|
|
.transaction_context
|
|
|
|
.get_instruction_context_stack_height())
|
|
|
|
.any(|level| {
|
|
|
|
self.transaction_context
|
2022-08-20 02:20:47 -07:00
|
|
|
.get_instruction_context_at_nesting_level(level)
|
2022-01-03 14:30:56 -08:00
|
|
|
.and_then(|instruction_context| {
|
2022-06-16 09:46:17 -07:00
|
|
|
instruction_context
|
|
|
|
.try_borrow_last_program_account(self.transaction_context)
|
2022-01-03 14:30:56 -08:00
|
|
|
})
|
2022-06-14 09:00:12 -07:00
|
|
|
.map(|program_account| program_account.get_key() == program_id)
|
|
|
|
.unwrap_or(false)
|
2022-01-03 14:30:56 -08:00
|
|
|
});
|
|
|
|
let is_last = self
|
|
|
|
.transaction_context
|
|
|
|
.get_current_instruction_context()
|
|
|
|
.and_then(|instruction_context| {
|
2022-06-16 09:46:17 -07:00
|
|
|
instruction_context.try_borrow_last_program_account(self.transaction_context)
|
2022-01-03 14:30:56 -08:00
|
|
|
})
|
2022-06-14 09:00:12 -07:00
|
|
|
.map(|program_account| program_account.get_key() == program_id)
|
|
|
|
.unwrap_or(false);
|
2021-11-19 11:43:42 -08:00
|
|
|
if contains && !is_last {
|
|
|
|
// Reentrancy not allowed unless caller is calling itself
|
|
|
|
return Err(InstructionError::ReentrancyNotAllowed);
|
|
|
|
}
|
2021-11-04 13:47:32 -07:00
|
|
|
}
|
|
|
|
|
2023-02-10 07:17:44 -08:00
|
|
|
self.trace_log_stack.push(TraceLogStackFrame::default());
|
2022-05-19 15:14:28 -07:00
|
|
|
self.syscall_context.push(None);
|
2022-09-03 01:34:57 -07:00
|
|
|
self.transaction_context.push()
|
2021-11-04 13:47:32 -07:00
|
|
|
}
|
2021-12-02 09:47:16 -08:00
|
|
|
|
|
|
|
/// Pop a stack frame from the invocation stack
|
2022-01-03 14:30:56 -08:00
|
|
|
pub fn pop(&mut self) -> Result<(), InstructionError> {
|
2023-01-28 01:03:31 -08:00
|
|
|
self.trace_log_stack.pop();
|
2022-05-19 15:14:28 -07:00
|
|
|
self.syscall_context.pop();
|
2022-01-03 14:30:56 -08:00
|
|
|
self.transaction_context.pop()
|
2021-11-04 13:47:32 -07:00
|
|
|
}
|
2021-12-02 09:47:16 -08:00
|
|
|
|
2022-02-02 16:45:57 -08:00
|
|
|
/// 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 {
|
2022-01-03 14:30:56 -08:00
|
|
|
self.transaction_context
|
|
|
|
.get_instruction_context_stack_height()
|
2021-11-04 13:47:32 -07:00
|
|
|
}
|
2021-12-02 09:47:16 -08:00
|
|
|
|
|
|
|
/// Verify the results of an instruction
|
2022-05-26 11:55:58 -07:00
|
|
|
///
|
|
|
|
/// Note: `instruction_accounts` must be the same as passed to `InvokeContext::push()`,
|
|
|
|
/// so that they match the order of `pre_accounts`.
|
2021-12-07 14:00:04 -08:00
|
|
|
fn verify(
|
2021-11-04 13:47:32 -07:00
|
|
|
&mut self,
|
2021-12-24 07:17:55 -08:00
|
|
|
instruction_accounts: &[InstructionAccount],
|
2022-09-06 02:31:40 -07:00
|
|
|
program_indices: &[IndexOfAccount],
|
2021-11-04 13:47:32 -07:00
|
|
|
) -> Result<(), InstructionError> {
|
2022-02-17 01:16:28 -08:00
|
|
|
let instruction_context = self
|
2022-01-03 14:30:56 -08:00
|
|
|
.transaction_context
|
2022-02-17 01:16:28 -08:00
|
|
|
.get_current_instruction_context()
|
|
|
|
.map_err(|_| InstructionError::CallDepth)?;
|
|
|
|
let program_id = instruction_context
|
2022-06-16 09:46:17 -07:00
|
|
|
.get_last_program_key(self.transaction_context)
|
2022-01-03 14:30:56 -08:00
|
|
|
.map_err(|_| InstructionError::CallDepth)?;
|
2021-11-04 13:47:32 -07:00
|
|
|
|
|
|
|
// Verify all executable accounts have zero outstanding refs
|
|
|
|
for account_index in program_indices.iter() {
|
2021-12-27 09:49:32 -08:00
|
|
|
self.transaction_context
|
2022-02-03 02:34:51 -08:00
|
|
|
.get_account_at_index(*account_index)?
|
2021-11-04 13:47:32 -07: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 07:17:55 -08:00
|
|
|
let mut pre_account_index = 0;
|
2022-06-16 09:46:17 -07:00
|
|
|
for (instruction_account_index, instruction_account) in
|
|
|
|
instruction_accounts.iter().enumerate()
|
|
|
|
{
|
2022-09-06 02:31:40 -07:00
|
|
|
if instruction_account_index as IndexOfAccount != instruction_account.index_in_callee {
|
2022-05-26 11:55:58 -07:00
|
|
|
continue; // Skip duplicate account
|
|
|
|
}
|
2021-11-04 13:47:32 -07:00
|
|
|
{
|
|
|
|
// Verify account has no outstanding references
|
2021-12-27 09:49:32 -08:00
|
|
|
let _ = self
|
|
|
|
.transaction_context
|
2022-02-03 02:34:51 -08:00
|
|
|
.get_account_at_index(instruction_account.index_in_transaction)?
|
2021-11-04 13:47:32 -07:00
|
|
|
.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)?;
|
2021-12-24 07:17:55 -08:00
|
|
|
pre_account_index = pre_account_index.saturating_add(1);
|
2021-12-27 09:49:32 -08:00
|
|
|
let account = self
|
|
|
|
.transaction_context
|
2022-02-03 02:34:51 -08:00
|
|
|
.get_account_at_index(instruction_account.index_in_transaction)?
|
2021-12-27 09:49:32 -08:00
|
|
|
.borrow();
|
2021-11-09 04:35:49 -08:00
|
|
|
pre_account
|
2021-11-04 13:47:32 -07:00
|
|
|
.verify(
|
|
|
|
program_id,
|
2021-12-24 07:17:55 -08:00
|
|
|
instruction_account.is_writable,
|
2021-11-04 13:47:32 -07:00
|
|
|
&self.rent,
|
|
|
|
&account,
|
|
|
|
&mut self.timings,
|
|
|
|
true,
|
|
|
|
)
|
|
|
|
.map_err(|err| {
|
|
|
|
ic_logger_msg!(
|
2021-11-23 04:23:40 -08:00
|
|
|
self.log_collector,
|
2021-11-04 13:47:32 -07:00
|
|
|
"failed to verify account {}: {}",
|
2021-11-09 04:35:49 -08:00
|
|
|
pre_account.key(),
|
2021-11-04 13:47:32 -07:00
|
|
|
err
|
|
|
|
);
|
|
|
|
err
|
|
|
|
})?;
|
2021-11-09 04:35:49 -08: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)?;
|
2022-01-04 08:00:21 -08:00
|
|
|
|
2022-02-10 09:57:00 -08:00
|
|
|
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);
|
2022-08-08 08:05:25 -07:00
|
|
|
self.accounts_data_meter
|
|
|
|
.adjust_delta_unchecked(data_len_delta);
|
2022-05-26 11:55:58 -07:00
|
|
|
}
|
2021-11-04 13:47:32 -07: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 09:47:16 -08:00
|
|
|
|
|
|
|
/// Verify and update PreAccount state based on program execution
|
2022-05-26 11:55:58 -07:00
|
|
|
///
|
|
|
|
/// Note: `instruction_accounts` must be the same as passed to `InvokeContext::push()`,
|
|
|
|
/// so that they match the order of `pre_accounts`.
|
2021-12-07 14:00:04 -08:00
|
|
|
fn verify_and_update(
|
2021-11-04 13:47:32 -07:00
|
|
|
&mut self,
|
2021-12-24 07:17:55 -08:00
|
|
|
instruction_accounts: &[InstructionAccount],
|
2022-01-03 14:30:56 -08:00
|
|
|
before_instruction_context_push: bool,
|
2021-11-04 13:47:32 -07:00
|
|
|
) -> Result<(), InstructionError> {
|
2022-01-03 14:30:56 -08:00
|
|
|
let transaction_context = &self.transaction_context;
|
|
|
|
let instruction_context = transaction_context.get_current_instruction_context()?;
|
2022-02-17 01:16:28 -08:00
|
|
|
let program_id = instruction_context
|
2022-06-16 09:46:17 -07:00
|
|
|
.get_last_program_key(transaction_context)
|
2022-01-03 14:30:56 -08:00
|
|
|
.map_err(|_| InstructionError::CallDepth)?;
|
2021-11-04 13:47:32 -07:00
|
|
|
|
|
|
|
// Verify the per-account instruction results
|
|
|
|
let (mut pre_sum, mut post_sum) = (0_u128, 0_u128);
|
2022-06-16 09:46:17 -07:00
|
|
|
for (instruction_account_index, instruction_account) in
|
|
|
|
instruction_accounts.iter().enumerate()
|
|
|
|
{
|
2022-09-06 02:31:40 -07:00
|
|
|
if instruction_account_index as IndexOfAccount != instruction_account.index_in_callee {
|
2022-05-26 11:55:58 -07:00
|
|
|
continue; // Skip duplicate account
|
|
|
|
}
|
2021-12-30 06:46:36 -08:00
|
|
|
if instruction_account.index_in_transaction
|
|
|
|
< transaction_context.get_number_of_accounts()
|
|
|
|
{
|
|
|
|
let key = transaction_context
|
2022-02-03 02:34:51 -08:00
|
|
|
.get_key_of_account_at_index(instruction_account.index_in_transaction)?;
|
2021-12-30 06:46:36 -08:00
|
|
|
let account = transaction_context
|
2022-02-03 02:34:51 -08:00
|
|
|
.get_account_at_index(instruction_account.index_in_transaction)?;
|
2022-01-03 14:30:56 -08:00
|
|
|
let is_writable = if before_instruction_context_push {
|
|
|
|
instruction_context
|
2022-06-16 09:46:17 -07:00
|
|
|
.is_instruction_account_writable(instruction_account.index_in_caller)?
|
2021-12-24 07:17:55 -08:00
|
|
|
} else {
|
|
|
|
instruction_account.is_writable
|
|
|
|
};
|
2021-11-04 13:47:32 -07:00
|
|
|
// Find the matching PreAccount
|
2022-01-03 14:30:56 -08:00
|
|
|
for pre_account in self.pre_accounts.iter_mut() {
|
2021-11-04 13:47:32 -07:00
|
|
|
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,
|
2022-01-03 14:30:56 -08:00
|
|
|
&self.rent,
|
2021-11-04 13:47:32 -07:00
|
|
|
&account,
|
2022-01-03 14:30:56 -08:00
|
|
|
&mut self.timings,
|
2021-11-04 13:47:32 -07:00
|
|
|
false,
|
|
|
|
)
|
|
|
|
.map_err(|err| {
|
2021-11-23 04:23:40 -08:00
|
|
|
ic_logger_msg!(
|
2022-01-03 14:30:56 -08:00
|
|
|
self.log_collector,
|
2021-11-23 04:23:40 -08:00
|
|
|
"failed to verify account {}: {}",
|
|
|
|
key,
|
|
|
|
err
|
|
|
|
);
|
2021-11-04 13:47:32 -07:00
|
|
|
err
|
|
|
|
})?;
|
2021-11-09 04:35:49 -08: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 13:47:32 -07:00
|
|
|
if is_writable && !pre_account.executable() {
|
2021-12-14 06:44:31 -08:00
|
|
|
pre_account.update(account.clone());
|
2021-11-04 13:47:32 -07:00
|
|
|
}
|
2022-01-04 08:00:21 -08:00
|
|
|
|
2022-02-10 09:57:00 -08:00
|
|
|
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);
|
2022-08-08 08:05:25 -07:00
|
|
|
self.accounts_data_meter
|
|
|
|
.adjust_delta_unchecked(data_len_delta);
|
2022-01-04 08:00:21 -08:00
|
|
|
|
2022-05-26 11:55:58 -07:00
|
|
|
break;
|
2021-11-04 13:47:32 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-05-26 11:55:58 -07:00
|
|
|
}
|
2021-11-04 13:47:32 -07: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 09:47:16 -08:00
|
|
|
|
|
|
|
/// Entrypoint for a cross-program invocation from a builtin program
|
|
|
|
pub fn native_invoke(
|
2021-11-30 23:54:42 -08:00
|
|
|
&mut self,
|
2023-02-16 05:16:25 -08:00
|
|
|
instruction: StableInstruction,
|
2021-11-30 23:54:42 -08:00
|
|
|
signers: &[Pubkey],
|
|
|
|
) -> Result<(), InstructionError> {
|
2022-01-03 14:30:56 -08:00
|
|
|
let (instruction_accounts, program_indices) =
|
2021-12-24 07:17:55 -08:00
|
|
|
self.prepare_instruction(&instruction, signers)?;
|
2021-12-31 08:55:27 -08:00
|
|
|
let mut compute_units_consumed = 0;
|
2021-12-07 14:00:04 -08:00
|
|
|
self.process_instruction(
|
2021-12-24 07:17:55 -08:00
|
|
|
&instruction.data,
|
|
|
|
&instruction_accounts,
|
2021-11-30 23:54:42 -08:00
|
|
|
&program_indices,
|
2021-12-31 08:55:27 -08:00
|
|
|
&mut compute_units_consumed,
|
2021-12-20 20:03:20 -08:00
|
|
|
&mut ExecuteTimings::default(),
|
2021-12-31 08:55:27 -08:00
|
|
|
)?;
|
2021-11-30 23:54:42 -08:00
|
|
|
Ok(())
|
|
|
|
}
|
2021-12-02 09:47:16 -08:00
|
|
|
|
2021-12-07 14:00:04 -08:00
|
|
|
/// Helper to prepare for process_instruction()
|
2021-12-24 07:17:55 -08:00
|
|
|
#[allow(clippy::type_complexity)]
|
|
|
|
pub fn prepare_instruction(
|
2021-11-30 23:54:42 -08:00
|
|
|
&mut self,
|
2023-02-16 05:16:25 -08:00
|
|
|
instruction: &StableInstruction,
|
2021-11-30 23:54:42 -08:00
|
|
|
signers: &[Pubkey],
|
2022-09-06 02:31:40 -07:00
|
|
|
) -> Result<(Vec<InstructionAccount>, Vec<IndexOfAccount>), InstructionError> {
|
2021-12-25 01:00:40 -08:00
|
|
|
// Finds the index of each account in the instruction by its pubkey.
|
|
|
|
// Then normalizes / unifies the privileges of duplicate accounts.
|
2022-05-26 11:55:58 -07:00
|
|
|
// Note: This is an O(n^2) algorithm,
|
|
|
|
// but performed on a very small slice and requires no heap allocations.
|
2022-01-03 14:30:56 -08:00
|
|
|
let instruction_context = self.transaction_context.get_current_instruction_context()?;
|
2021-12-25 01:00:40 -08:00
|
|
|
let mut deduplicated_instruction_accounts: Vec<InstructionAccount> = Vec::new();
|
|
|
|
let mut duplicate_indicies = Vec::with_capacity(instruction.accounts.len());
|
2022-06-16 09:46:17 -07:00
|
|
|
for (instruction_account_index, account_meta) in instruction.accounts.iter().enumerate() {
|
2021-12-30 06:46:36 -08:00
|
|
|
let index_in_transaction = self
|
2021-12-27 09:49:32 -08:00
|
|
|
.transaction_context
|
|
|
|
.find_index_of_account(&account_meta.pubkey)
|
2021-12-25 01:00:40 -08:00
|
|
|
.ok_or_else(|| {
|
|
|
|
ic_msg!(
|
|
|
|
self,
|
|
|
|
"Instruction references an unknown account {}",
|
|
|
|
account_meta.pubkey,
|
|
|
|
);
|
|
|
|
InstructionError::MissingAccount
|
|
|
|
})?;
|
2021-12-30 06:46:36 -08:00
|
|
|
if let Some(duplicate_index) =
|
|
|
|
deduplicated_instruction_accounts
|
|
|
|
.iter()
|
|
|
|
.position(|instruction_account| {
|
|
|
|
instruction_account.index_in_transaction == index_in_transaction
|
|
|
|
})
|
2021-12-25 01:00:40 -08:00
|
|
|
{
|
|
|
|
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)?;
|
2021-12-25 01:00:40 -08:00
|
|
|
instruction_account.is_signer |= account_meta.is_signer;
|
|
|
|
instruction_account.is_writable |= account_meta.is_writable;
|
|
|
|
} else {
|
2022-01-03 14:30:56 -08:00
|
|
|
let index_in_caller = instruction_context
|
2022-06-16 09:46:17 -07:00
|
|
|
.find_index_of_instruction_account(
|
|
|
|
self.transaction_context,
|
|
|
|
&account_meta.pubkey,
|
|
|
|
)
|
2021-12-30 06:46:36 -08:00
|
|
|
.ok_or_else(|| {
|
|
|
|
ic_msg!(
|
|
|
|
self,
|
|
|
|
"Instruction references an unknown account {}",
|
|
|
|
account_meta.pubkey,
|
|
|
|
);
|
|
|
|
InstructionError::MissingAccount
|
|
|
|
})?;
|
2022-01-03 14:30:56 -08:00
|
|
|
duplicate_indicies.push(deduplicated_instruction_accounts.len());
|
|
|
|
deduplicated_instruction_accounts.push(InstructionAccount {
|
|
|
|
index_in_transaction,
|
|
|
|
index_in_caller,
|
2022-09-06 02:31:40 -07:00
|
|
|
index_in_callee: instruction_account_index as IndexOfAccount,
|
2022-01-03 14:30:56 -08:00
|
|
|
is_signer: account_meta.is_signer,
|
|
|
|
is_writable: account_meta.is_writable,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2022-01-27 15:52:02 -08:00
|
|
|
for instruction_account in deduplicated_instruction_accounts.iter() {
|
2022-05-25 04:43:20 -07:00
|
|
|
let borrowed_account = instruction_context.try_borrow_instruction_account(
|
2022-01-27 15:52:02 -08:00
|
|
|
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
|
2022-01-03 14:30:56 -08:00
|
|
|
.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>>()?;
|
2021-11-30 23:54:42 -08:00
|
|
|
|
|
|
|
// Find and validate executables / program accounts
|
|
|
|
let callee_program_id = instruction.program_id;
|
2022-01-03 14:30:56 -08:00
|
|
|
let program_account_index = instruction_context
|
2022-06-16 09:46:17 -07:00
|
|
|
.find_index_of_instruction_account(self.transaction_context, &callee_program_id)
|
2021-11-30 23:54:42 -08:00
|
|
|
.ok_or_else(|| {
|
|
|
|
ic_msg!(self, "Unknown program {}", callee_program_id);
|
|
|
|
InstructionError::MissingAccount
|
|
|
|
})?;
|
2022-01-03 14:30:56 -08:00
|
|
|
let borrowed_program_account = instruction_context
|
2022-06-16 09:46:17 -07:00
|
|
|
.try_borrow_instruction_account(self.transaction_context, program_account_index)?;
|
2022-01-03 14:30:56 -08:00
|
|
|
if !borrowed_program_account.is_executable() {
|
2021-11-30 23:54:42 -08:00
|
|
|
ic_msg!(self, "Account {} is not executable", callee_program_id);
|
|
|
|
return Err(InstructionError::AccountNotExecutable);
|
|
|
|
}
|
|
|
|
|
2023-03-28 11:49:56 -07:00
|
|
|
Ok((
|
|
|
|
instruction_accounts,
|
|
|
|
vec![borrowed_program_account.get_index_in_transaction()],
|
|
|
|
))
|
2021-11-30 23:54:42 -08:00
|
|
|
}
|
2021-12-02 09:47:16 -08:00
|
|
|
|
2022-01-03 14:30:56 -08:00
|
|
|
/// Processes an instruction and returns how many compute units were used
|
2021-12-07 14:00:04 -08:00
|
|
|
pub fn process_instruction(
|
2021-11-30 23:54:42 -08:00
|
|
|
&mut self,
|
2021-12-24 07:17:55 -08:00
|
|
|
instruction_data: &[u8],
|
|
|
|
instruction_accounts: &[InstructionAccount],
|
2022-09-06 02:31:40 -07:00
|
|
|
program_indices: &[IndexOfAccount],
|
2021-12-31 08:55:27 -08:00
|
|
|
compute_units_consumed: &mut u64,
|
2021-12-20 20:03:20 -08:00
|
|
|
timings: &mut ExecuteTimings,
|
2021-12-31 08:55:27 -08:00
|
|
|
) -> Result<(), InstructionError> {
|
|
|
|
*compute_units_consumed = 0;
|
2021-12-25 01:00:40 -08:00
|
|
|
|
2022-02-09 11:04:49 -08:00
|
|
|
let nesting_level = self
|
2022-01-03 14:30:56 -08:00
|
|
|
.transaction_context
|
2022-02-02 16:45:57 -08:00
|
|
|
.get_instruction_context_stack_height();
|
2022-02-09 11:04:49 -08:00
|
|
|
let is_top_level_instruction = nesting_level == 0;
|
2022-07-21 03:49:34 -07:00
|
|
|
if !is_top_level_instruction
|
|
|
|
&& !self
|
2022-07-15 00:31:34 -07:00
|
|
|
.feature_set
|
|
|
|
.is_active(&enable_early_verification_of_account_modifications::id())
|
2022-07-21 03:49:34 -07:00
|
|
|
{
|
|
|
|
// Verify the calling program hasn't misbehaved
|
|
|
|
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();
|
|
|
|
saturating_add_assign!(
|
|
|
|
timings
|
|
|
|
.execute_accessories
|
|
|
|
.process_instructions
|
|
|
|
.verify_caller_us,
|
|
|
|
verify_caller_time.as_us()
|
|
|
|
);
|
|
|
|
verify_caller_result?;
|
2021-12-07 14:00:04 -08:00
|
|
|
}
|
2021-11-30 23:54:42 -08:00
|
|
|
|
2022-09-03 01:34:57 -07:00
|
|
|
self.transaction_context
|
|
|
|
.get_next_instruction_context()?
|
|
|
|
.configure(program_indices, instruction_accounts, instruction_data);
|
|
|
|
self.push()?;
|
2022-06-29 03:07:50 -07:00
|
|
|
self.process_executable_chain(compute_units_consumed, timings)
|
2021-12-07 14:00:04 -08:00
|
|
|
.and_then(|_| {
|
2022-07-15 00:31:34 -07:00
|
|
|
if self
|
|
|
|
.feature_set
|
|
|
|
.is_active(&enable_early_verification_of_account_modifications::id())
|
|
|
|
{
|
|
|
|
Ok(())
|
2022-06-29 03:07:50 -07:00
|
|
|
} else {
|
2022-07-15 00:31:34 -07:00
|
|
|
// Verify the called program has not misbehaved
|
|
|
|
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)
|
|
|
|
};
|
|
|
|
verify_callee_time.stop();
|
|
|
|
saturating_add_assign!(
|
|
|
|
timings
|
|
|
|
.execute_accessories
|
|
|
|
.process_instructions
|
|
|
|
.verify_callee_us,
|
|
|
|
verify_callee_time.as_us()
|
|
|
|
);
|
|
|
|
result
|
|
|
|
}
|
2022-06-29 03:07:50 -07:00
|
|
|
})
|
|
|
|
// MUST pop if and only if `push` succeeded, independent of `result`.
|
|
|
|
// Thus, the `.and()` instead of an `.and_then()`.
|
|
|
|
.and(self.pop())
|
2021-11-30 23:54:42 -08:00
|
|
|
}
|
2021-12-02 09:47:16 -08:00
|
|
|
|
|
|
|
/// Calls the instruction's program entrypoint method
|
2022-06-29 03:07:50 -07:00
|
|
|
fn process_executable_chain(
|
|
|
|
&mut self,
|
|
|
|
compute_units_consumed: &mut u64,
|
|
|
|
timings: &mut ExecuteTimings,
|
|
|
|
) -> Result<(), InstructionError> {
|
2022-01-03 14:30:56 -08:00
|
|
|
let instruction_context = self.transaction_context.get_current_instruction_context()?;
|
2022-06-29 03:07:50 -07:00
|
|
|
let mut process_executable_chain_time = Measure::start("process_executable_chain_time");
|
2022-05-25 16:27:00 -07:00
|
|
|
|
2023-03-06 08:37:37 -08:00
|
|
|
let builtin_id = {
|
2022-05-25 16:27:00 -07:00
|
|
|
let borrowed_root_account = instruction_context
|
2022-06-16 09:46:17 -07:00
|
|
|
.try_borrow_program_account(self.transaction_context, 0)
|
2022-05-25 16:27:00 -07:00
|
|
|
.map_err(|_| InstructionError::UnsupportedProgramId)?;
|
|
|
|
let owner_id = borrowed_root_account.get_owner();
|
2022-06-29 03:07:50 -07:00
|
|
|
if native_loader::check_id(owner_id) {
|
2023-03-06 08:37:37 -08:00
|
|
|
*borrowed_root_account.get_key()
|
2022-05-25 16:27:00 -07:00
|
|
|
} else {
|
2023-03-06 08:37:37 -08:00
|
|
|
*owner_id
|
2021-11-30 23:54:42 -08:00
|
|
|
}
|
2022-05-25 16:27:00 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
for entry in self.builtin_programs {
|
|
|
|
if entry.program_id == builtin_id {
|
2022-06-16 09:46:17 -07:00
|
|
|
let program_id =
|
|
|
|
*instruction_context.get_last_program_key(self.transaction_context)?;
|
2022-06-29 03:07:50 -07:00
|
|
|
self.transaction_context
|
|
|
|
.set_return_data(program_id, Vec::new())?;
|
|
|
|
|
2022-11-15 06:21:11 -08:00
|
|
|
let pre_remaining_units = self.get_remaining();
|
2023-04-05 06:50:34 -07:00
|
|
|
let logger = self.get_log_collector();
|
|
|
|
stable_log::program_invoke(&logger, &program_id, self.get_stack_height());
|
|
|
|
let result = (entry.process_instruction)(self)
|
|
|
|
.map(|()| {
|
|
|
|
stable_log::program_success(&logger, &program_id);
|
|
|
|
})
|
|
|
|
.map_err(|err| {
|
|
|
|
stable_log::program_failure(&logger, &program_id, err.as_ref());
|
|
|
|
if let Some(err) = err.downcast_ref::<InstructionError>() {
|
|
|
|
err.clone()
|
|
|
|
} else {
|
|
|
|
InstructionError::ProgramFailedToComplete
|
|
|
|
}
|
|
|
|
});
|
2022-11-15 06:21:11 -08:00
|
|
|
let post_remaining_units = self.get_remaining();
|
2022-06-29 03:07:50 -07:00
|
|
|
*compute_units_consumed = pre_remaining_units.saturating_sub(post_remaining_units);
|
|
|
|
|
2023-04-05 06:50:34 -07:00
|
|
|
if builtin_id == program_id
|
2023-03-24 14:45:03 -07:00
|
|
|
&& result.is_ok()
|
2023-03-24 09:31:01 -07:00
|
|
|
&& *compute_units_consumed == 0
|
|
|
|
&& self
|
|
|
|
.feature_set
|
|
|
|
.is_active(&native_programs_consume_cu::id())
|
|
|
|
{
|
|
|
|
return Err(InstructionError::BuiltinProgramsMustConsumeComputeUnits);
|
|
|
|
}
|
|
|
|
|
2022-06-29 03:07:50 -07:00
|
|
|
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;
|
2021-11-30 23:54:42 -08:00
|
|
|
}
|
|
|
|
}
|
2022-05-25 16:27:00 -07:00
|
|
|
|
2021-11-30 23:54:42 -08:00
|
|
|
Err(InstructionError::UnsupportedProgramId)
|
|
|
|
}
|
2021-12-02 09:47:16 -08:00
|
|
|
|
|
|
|
/// Get this invocation's LogCollector
|
|
|
|
pub fn get_log_collector(&self) -> Option<Rc<RefCell<LogCollector>>> {
|
2021-11-23 04:23:40 -08:00
|
|
|
self.log_collector.clone()
|
2021-11-04 13:47:32 -07:00
|
|
|
}
|
2021-12-02 09:47:16 -08:00
|
|
|
|
2022-11-15 06:21:11 -08:00
|
|
|
/// Consume compute units
|
2023-04-05 06:50:34 -07:00
|
|
|
pub fn consume_checked(&self, amount: u64) -> Result<(), Box<dyn std::error::Error>> {
|
2023-02-10 07:17:44 -08:00
|
|
|
self.log_consumed_bpf_units(amount);
|
2022-11-15 06:21:11 -08:00
|
|
|
let mut compute_meter = self.compute_meter.borrow_mut();
|
|
|
|
let exceeded = *compute_meter < amount;
|
|
|
|
*compute_meter = compute_meter.saturating_sub(amount);
|
|
|
|
if exceeded {
|
2023-04-05 06:50:34 -07:00
|
|
|
return Err(Box::new(InstructionError::ComputationalBudgetExceeded));
|
2022-11-15 06:21:11 -08:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set compute units
|
|
|
|
///
|
|
|
|
/// Only use for tests and benchmarks
|
|
|
|
pub fn mock_set_remaining(&self, remaining: u64) {
|
|
|
|
*self.compute_meter.borrow_mut() = remaining;
|
2021-11-04 13:47:32 -07:00
|
|
|
}
|
2021-12-02 09:47:16 -08:00
|
|
|
|
2021-12-28 03:14:48 -08:00
|
|
|
/// Get this invocation's AccountsDataMeter
|
|
|
|
pub fn get_accounts_data_meter(&self) -> &AccountsDataMeter {
|
|
|
|
&self.accounts_data_meter
|
|
|
|
}
|
|
|
|
|
2021-12-02 09:47:16 -08: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 13:47:32 -07:00
|
|
|
}
|
2021-12-02 09:47:16 -08:00
|
|
|
|
2022-01-12 21:36:21 -08:00
|
|
|
/// Get cached sysvars
|
|
|
|
pub fn get_sysvar_cache(&self) -> &SysvarCache {
|
2023-04-03 08:23:24 -07:00
|
|
|
self.sysvar_cache
|
2021-11-04 13:47:32 -07:00
|
|
|
}
|
2022-02-02 16:45:57 -08: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> {
|
2022-04-12 09:49:42 -07:00
|
|
|
*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
|
|
|
}
|
|
|
|
|
2022-04-12 09:49:42 -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
|
|
|
}
|
|
|
|
|
2022-04-12 09:49:42 -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
|
2022-04-12 09:49:42 -07:00
|
|
|
.last()
|
2022-05-19 15:14:28 -07:00
|
|
|
.and_then(|context| context.as_ref())
|
|
|
|
.map(|context| context.allocator.clone())
|
2022-04-12 09:49:42 -07:00
|
|
|
.ok_or(InstructionError::CallDepth)
|
|
|
|
}
|
2023-02-10 07:17:44 -08:00
|
|
|
|
|
|
|
fn log_consumed_bpf_units(&self, amount: u64) {
|
|
|
|
if self.enable_instruction_tracing && amount != 0 {
|
|
|
|
let trace_log_stack_frame = self
|
|
|
|
.trace_log_stack
|
|
|
|
.last()
|
|
|
|
.expect("Inconsistent trace log stack");
|
|
|
|
|
|
|
|
trace_log_stack_frame.consumed_bpf_units.borrow_mut().push((
|
|
|
|
trace_log_stack_frame.trace_log.len().saturating_sub(1),
|
|
|
|
amount,
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
2021-11-04 13:47:32 -07:00
|
|
|
}
|
|
|
|
|
2023-04-03 08:23:24 -07:00
|
|
|
#[macro_export]
|
|
|
|
macro_rules! with_mock_invoke_context {
|
|
|
|
($invoke_context:ident, $transaction_context:ident, $transaction_accounts:expr) => {
|
|
|
|
use {
|
|
|
|
solana_sdk::{
|
|
|
|
account::ReadableAccount, feature_set::FeatureSet, hash::Hash, sysvar::rent::Rent,
|
|
|
|
transaction_context::TransactionContext,
|
|
|
|
},
|
|
|
|
std::{cell::RefCell, rc::Rc, sync::Arc},
|
|
|
|
$crate::{
|
|
|
|
compute_budget::ComputeBudget, executor_cache::TransactionExecutorCache,
|
|
|
|
invoke_context::InvokeContext, log_collector::LogCollector,
|
|
|
|
sysvar_cache::SysvarCache,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
let compute_budget = ComputeBudget::default();
|
|
|
|
let mut $transaction_context = TransactionContext::new(
|
|
|
|
$transaction_accounts,
|
|
|
|
Some(Rent::default()),
|
|
|
|
compute_budget.max_invoke_stack_height,
|
|
|
|
compute_budget.max_instruction_trace_length,
|
|
|
|
);
|
|
|
|
$transaction_context.enable_cap_accounts_data_allocations_per_transaction();
|
|
|
|
let mut sysvar_cache = SysvarCache::default();
|
|
|
|
sysvar_cache.fill_missing_entries(|pubkey, callback| {
|
|
|
|
for index in 0..$transaction_context.get_number_of_accounts() {
|
|
|
|
if $transaction_context
|
|
|
|
.get_key_of_account_at_index(index)
|
|
|
|
.unwrap()
|
|
|
|
== pubkey
|
|
|
|
{
|
|
|
|
callback(
|
|
|
|
$transaction_context
|
|
|
|
.get_account_at_index(index)
|
|
|
|
.unwrap()
|
|
|
|
.borrow()
|
|
|
|
.data(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
let mut $invoke_context = InvokeContext::new(
|
|
|
|
&mut $transaction_context,
|
|
|
|
Rent::default(),
|
|
|
|
&[],
|
|
|
|
&sysvar_cache,
|
|
|
|
Some(LogCollector::new_ref()),
|
|
|
|
compute_budget,
|
|
|
|
Rc::new(RefCell::new(TransactionExecutorCache::default())),
|
|
|
|
Arc::new(FeatureSet::all_enabled()),
|
|
|
|
Hash::default(),
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
);
|
|
|
|
};
|
2021-11-04 13:47:32 -07:00
|
|
|
}
|
|
|
|
|
2023-04-03 08:23:24 -07:00
|
|
|
pub fn mock_process_instruction<F: FnMut(&mut InvokeContext)>(
|
|
|
|
loader_id: &Pubkey,
|
|
|
|
mut program_indices: Vec<IndexOfAccount>,
|
|
|
|
instruction_data: &[u8],
|
|
|
|
mut transaction_accounts: Vec<TransactionAccount>,
|
2022-05-24 15:04:46 -07:00
|
|
|
instruction_account_metas: Vec<AccountMeta>,
|
2023-04-03 08:23:24 -07:00
|
|
|
expected_result: Result<(), InstructionError>,
|
|
|
|
process_instruction: ProcessInstructionWithContext,
|
|
|
|
mut pre_adjustments: F,
|
|
|
|
) -> Vec<AccountSharedData> {
|
2022-05-24 15:04:46 -07:00
|
|
|
let mut instruction_accounts: Vec<InstructionAccount> =
|
|
|
|
Vec::with_capacity(instruction_account_metas.len());
|
2022-06-16 09:46:17 -07:00
|
|
|
for (instruction_account_index, account_meta) in instruction_account_metas.iter().enumerate() {
|
2022-05-24 15:04:46 -07:00
|
|
|
let index_in_transaction = transaction_accounts
|
|
|
|
.iter()
|
|
|
|
.position(|(key, _account)| *key == account_meta.pubkey)
|
2022-09-06 02:31:40 -07:00
|
|
|
.unwrap_or(transaction_accounts.len())
|
|
|
|
as IndexOfAccount;
|
2022-05-24 15:04:46 -07:00
|
|
|
let index_in_callee = instruction_accounts
|
2022-06-16 09:46:17 -07:00
|
|
|
.get(0..instruction_account_index)
|
2022-05-24 15:04:46 -07:00
|
|
|
.unwrap()
|
|
|
|
.iter()
|
|
|
|
.position(|instruction_account| {
|
|
|
|
instruction_account.index_in_transaction == index_in_transaction
|
|
|
|
})
|
2022-09-06 02:31:40 -07:00
|
|
|
.unwrap_or(instruction_account_index) as IndexOfAccount;
|
2022-05-24 15:04:46 -07:00
|
|
|
instruction_accounts.push(InstructionAccount {
|
|
|
|
index_in_transaction,
|
2022-05-25 04:43:20 -07:00
|
|
|
index_in_caller: index_in_transaction,
|
2022-05-24 15:04:46 -07:00
|
|
|
index_in_callee,
|
|
|
|
is_signer: account_meta.is_signer,
|
|
|
|
is_writable: account_meta.is_writable,
|
|
|
|
});
|
|
|
|
}
|
2022-09-06 02:31:40 -07:00
|
|
|
program_indices.insert(0, transaction_accounts.len() as IndexOfAccount);
|
2022-06-29 03:07:50 -07:00
|
|
|
let processor_account = AccountSharedData::new(0, 0, &native_loader::id());
|
2023-04-03 08:23:24 -07:00
|
|
|
transaction_accounts.push((*loader_id, processor_account));
|
|
|
|
with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
|
2023-03-24 14:45:03 -07:00
|
|
|
let builtin_programs = &[BuiltinProgram {
|
|
|
|
program_id: *loader_id,
|
|
|
|
process_instruction,
|
|
|
|
}];
|
|
|
|
invoke_context.builtin_programs = builtin_programs;
|
2023-04-03 08:23:24 -07:00
|
|
|
pre_adjustments(&mut invoke_context);
|
2023-03-24 14:45:03 -07:00
|
|
|
let result = invoke_context.process_instruction(
|
|
|
|
instruction_data,
|
2023-04-03 08:23:24 -07:00
|
|
|
&instruction_accounts,
|
2023-03-24 14:45:03 -07:00
|
|
|
&program_indices,
|
|
|
|
&mut 0,
|
|
|
|
&mut ExecuteTimings::default(),
|
|
|
|
);
|
|
|
|
assert_eq!(result, expected_result);
|
2021-12-27 09:49:32 -08:00
|
|
|
let mut transaction_accounts = transaction_context.deconstruct_without_keys().unwrap();
|
|
|
|
transaction_accounts.pop();
|
|
|
|
transaction_accounts
|
2021-11-04 13:47:32 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2021-12-03 09:00:31 -08:00
|
|
|
use {
|
|
|
|
super::*,
|
2022-03-16 08:34:15 -07:00
|
|
|
crate::compute_budget,
|
2021-12-03 09:00:31 -08:00
|
|
|
serde::{Deserialize, Serialize},
|
2023-02-16 05:16:25 -08:00
|
|
|
solana_sdk::{account::WritableAccount, instruction::Instruction},
|
2021-11-04 13:47:32 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
enum MockInstruction {
|
|
|
|
NoopSuccess,
|
|
|
|
NoopFail,
|
|
|
|
ModifyOwned,
|
|
|
|
ModifyNotOwned,
|
|
|
|
ModifyReadonly,
|
2022-07-15 00:31:34 -07:00
|
|
|
UnbalancedPush,
|
|
|
|
UnbalancedPop,
|
2021-12-30 18:21:42 -08:00
|
|
|
ConsumeComputeUnits {
|
2021-12-31 08:55:27 -08:00
|
|
|
compute_units_to_consume: u64,
|
2021-12-30 18:21:42 -08:00
|
|
|
desired_result: Result<(), InstructionError>,
|
|
|
|
},
|
2022-01-04 08:00:21 -08:00
|
|
|
Resize {
|
2022-07-06 10:27:42 -07:00
|
|
|
new_len: u64,
|
2022-01-04 08:00:21 -08:00
|
|
|
},
|
2021-11-04 13:47:32 -07:00
|
|
|
}
|
|
|
|
|
2021-12-24 07:17:55 -08:00
|
|
|
#[test]
|
2021-11-30 23:54:42 -08:00
|
|
|
fn test_program_entry_debug() {
|
|
|
|
fn mock_process_instruction(
|
2021-12-02 09:47:16 -08:00
|
|
|
_invoke_context: &mut InvokeContext,
|
2023-04-05 06:50:34 -07:00
|
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
2021-11-30 23:54:42 -08:00
|
|
|
Ok(())
|
|
|
|
}
|
2023-04-05 06:50:34 -07:00
|
|
|
fn mock_ix_processor(
|
|
|
|
_invoke_context: &mut InvokeContext,
|
|
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
2021-11-30 23:54:42 -08:00
|
|
|
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,
|
|
|
|
},
|
|
|
|
];
|
2022-12-06 06:30:06 -08:00
|
|
|
assert!(!format!("{builtin_programs:?}").is_empty());
|
2021-11-30 23:54:42 -08:00
|
|
|
}
|
|
|
|
|
2023-03-24 09:31:01 -07:00
|
|
|
const MOCK_BUILTIN_COMPUTE_UNIT_COST: u64 = 1;
|
|
|
|
|
2021-11-09 04:35:49 -08:00
|
|
|
#[allow(clippy::integer_arithmetic)]
|
2021-11-04 13:47:32 -07:00
|
|
|
fn mock_process_instruction(
|
2021-12-02 09:47:16 -08:00
|
|
|
invoke_context: &mut InvokeContext,
|
2023-04-05 06:50:34 -07:00
|
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
2022-01-03 14:30:56 -08:00
|
|
|
let transaction_context = &invoke_context.transaction_context;
|
2022-01-05 00:39:37 -08:00
|
|
|
let instruction_context = transaction_context.get_current_instruction_context()?;
|
2022-04-01 06:48:05 -07:00
|
|
|
let instruction_data = instruction_context.get_instruction_data();
|
2022-06-16 09:46:17 -07:00
|
|
|
let program_id = instruction_context.get_last_program_key(transaction_context)?;
|
2023-03-24 09:31:01 -07:00
|
|
|
// mock builtin must consume units
|
|
|
|
invoke_context.consume_checked(MOCK_BUILTIN_COMPUTE_UNIT_COST)?;
|
2022-07-15 00:31:34 -07:00
|
|
|
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: false,
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
2021-11-04 13:47:32 -07:00
|
|
|
assert_eq!(
|
2022-01-05 00:39:37 -08:00
|
|
|
program_id,
|
|
|
|
instruction_context
|
|
|
|
.try_borrow_instruction_account(transaction_context, 0)?
|
|
|
|
.get_owner()
|
2021-11-04 13:47:32 -07:00
|
|
|
);
|
|
|
|
assert_ne!(
|
2022-01-05 00:39:37 -08:00
|
|
|
instruction_context
|
|
|
|
.try_borrow_instruction_account(transaction_context, 1)?
|
|
|
|
.get_owner(),
|
|
|
|
instruction_context
|
|
|
|
.try_borrow_instruction_account(transaction_context, 0)?
|
|
|
|
.get_key()
|
2021-11-04 13:47:32 -07:00
|
|
|
);
|
|
|
|
|
2022-04-01 06:48:05 -07:00
|
|
|
if let Ok(instruction) = bincode::deserialize(instruction_data) {
|
2021-11-04 13:47:32 -07:00
|
|
|
match instruction {
|
|
|
|
MockInstruction::NoopSuccess => (),
|
2023-04-05 06:50:34 -07:00
|
|
|
MockInstruction::NoopFail => return Err(Box::new(InstructionError::GenericError)),
|
2022-01-05 00:39:37 -08:00
|
|
|
MockInstruction::ModifyOwned => instruction_context
|
|
|
|
.try_borrow_instruction_account(transaction_context, 0)?
|
2022-09-23 17:37:02 -07:00
|
|
|
.set_data_from_slice(&[1])?,
|
2022-01-05 00:39:37 -08:00
|
|
|
MockInstruction::ModifyNotOwned => instruction_context
|
|
|
|
.try_borrow_instruction_account(transaction_context, 1)?
|
2022-09-23 17:37:02 -07:00
|
|
|
.set_data_from_slice(&[1])?,
|
2022-01-05 00:39:37 -08:00
|
|
|
MockInstruction::ModifyReadonly => instruction_context
|
|
|
|
.try_borrow_instruction_account(transaction_context, 2)?
|
2022-09-23 17:37:02 -07:00
|
|
|
.set_data_from_slice(&[1])?,
|
2022-07-15 00:31:34 -07:00
|
|
|
MockInstruction::UnbalancedPush => {
|
|
|
|
instruction_context
|
|
|
|
.try_borrow_instruction_account(transaction_context, 0)?
|
|
|
|
.checked_add_lamports(1)?;
|
|
|
|
let program_id = *transaction_context.get_key_of_account_at_index(3)?;
|
|
|
|
let metas = vec![
|
|
|
|
AccountMeta::new_readonly(
|
|
|
|
*transaction_context.get_key_of_account_at_index(0)?,
|
|
|
|
false,
|
|
|
|
),
|
|
|
|
AccountMeta::new_readonly(
|
|
|
|
*transaction_context.get_key_of_account_at_index(1)?,
|
|
|
|
false,
|
|
|
|
),
|
|
|
|
];
|
|
|
|
let inner_instruction = Instruction::new_with_bincode(
|
|
|
|
program_id,
|
|
|
|
&MockInstruction::NoopSuccess,
|
|
|
|
metas,
|
|
|
|
);
|
2022-09-03 01:34:57 -07:00
|
|
|
invoke_context
|
|
|
|
.transaction_context
|
|
|
|
.get_next_instruction_context()
|
|
|
|
.unwrap()
|
|
|
|
.configure(&[3], &instruction_accounts, &[]);
|
|
|
|
let result = invoke_context.push();
|
2022-07-15 00:31:34 -07:00
|
|
|
assert_eq!(result, Err(InstructionError::UnbalancedInstruction));
|
|
|
|
result?;
|
|
|
|
invoke_context
|
2023-02-16 05:16:25 -08:00
|
|
|
.native_invoke(inner_instruction.into(), &[])
|
2022-07-15 00:31:34 -07:00
|
|
|
.and(invoke_context.pop())?;
|
|
|
|
}
|
|
|
|
MockInstruction::UnbalancedPop => instruction_context
|
|
|
|
.try_borrow_instruction_account(transaction_context, 0)?
|
|
|
|
.checked_add_lamports(1)?,
|
2021-12-30 18:21:42 -08:00
|
|
|
MockInstruction::ConsumeComputeUnits {
|
2021-12-31 08:55:27 -08:00
|
|
|
compute_units_to_consume,
|
2021-12-30 18:21:42 -08:00
|
|
|
desired_result,
|
|
|
|
} => {
|
2022-11-15 06:21:11 -08:00
|
|
|
invoke_context.consume_checked(compute_units_to_consume)?;
|
2023-04-05 06:50:34 -07:00
|
|
|
return desired_result
|
|
|
|
.map_err(|err| Box::new(err) as Box<dyn std::error::Error>);
|
2021-12-30 18:21:42 -08:00
|
|
|
}
|
2022-01-05 00:39:37 -08:00
|
|
|
MockInstruction::Resize { new_len } => instruction_context
|
|
|
|
.try_borrow_instruction_account(transaction_context, 0)?
|
2022-09-23 17:37:02 -07:00
|
|
|
.set_data(vec![0; new_len as usize])?,
|
2021-11-04 13:47:32 -07:00
|
|
|
}
|
|
|
|
} else {
|
2023-04-05 06:50:34 -07:00
|
|
|
return Err(Box::new(InstructionError::InvalidInstructionData));
|
2021-11-04 13:47:32 -07:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2022-07-15 00:31:34 -07:00
|
|
|
fn test_instruction_stack_height() {
|
2023-04-03 08:23:24 -07:00
|
|
|
let one_more_than_max_depth = ComputeBudget::default()
|
|
|
|
.max_invoke_stack_height
|
|
|
|
.saturating_add(1);
|
2021-11-04 13:47:32 -07:00
|
|
|
let mut invoke_stack = vec![];
|
2023-04-03 08:23:24 -07:00
|
|
|
let mut transaction_accounts = vec![];
|
2021-12-24 07:17:55 -08:00
|
|
|
let mut instruction_accounts = vec![];
|
2023-04-03 08:23:24 -07:00
|
|
|
for index in 0..one_more_than_max_depth {
|
2021-11-04 13:47:32 -07:00
|
|
|
invoke_stack.push(solana_sdk::pubkey::new_rand());
|
2023-04-03 08:23:24 -07:00
|
|
|
transaction_accounts.push((
|
2021-11-04 13:47:32 -07:00
|
|
|
solana_sdk::pubkey::new_rand(),
|
2022-03-13 08:43:07 -07:00
|
|
|
AccountSharedData::new(index as u64, 1, invoke_stack.get(index).unwrap()),
|
2021-11-04 13:47:32 -07:00
|
|
|
));
|
2021-12-24 07:17:55 -08:00
|
|
|
instruction_accounts.push(InstructionAccount {
|
2022-09-06 02:31:40 -07:00
|
|
|
index_in_transaction: index as IndexOfAccount,
|
|
|
|
index_in_caller: index as IndexOfAccount,
|
|
|
|
index_in_callee: instruction_accounts.len() as IndexOfAccount,
|
2021-12-24 07:17:55 -08:00
|
|
|
is_signer: false,
|
|
|
|
is_writable: true,
|
|
|
|
});
|
2021-11-04 13:47:32 -07:00
|
|
|
}
|
2021-12-24 07:17:55 -08:00
|
|
|
for (index, program_id) in invoke_stack.iter().enumerate() {
|
2023-04-03 08:23:24 -07:00
|
|
|
transaction_accounts.push((
|
2021-11-04 13:47:32 -07:00
|
|
|
*program_id,
|
2021-12-27 09:49:32 -08:00
|
|
|
AccountSharedData::new(1, 1, &solana_sdk::pubkey::Pubkey::default()),
|
2021-11-04 13:47:32 -07:00
|
|
|
));
|
2021-12-24 07:17:55 -08:00
|
|
|
instruction_accounts.push(InstructionAccount {
|
2022-09-06 02:31:40 -07:00
|
|
|
index_in_transaction: index as IndexOfAccount,
|
|
|
|
index_in_caller: index as IndexOfAccount,
|
|
|
|
index_in_callee: index as IndexOfAccount,
|
2021-12-24 07:17:55 -08:00
|
|
|
is_signer: false,
|
|
|
|
is_writable: false,
|
|
|
|
});
|
2021-11-04 13:47:32 -07:00
|
|
|
}
|
2023-04-03 08:23:24 -07:00
|
|
|
with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
|
2021-11-04 13:47:32 -07:00
|
|
|
|
|
|
|
// Check call depth increases and has a limit
|
|
|
|
let mut depth_reached = 0;
|
|
|
|
for _ in 0..invoke_stack.len() {
|
2022-09-03 01:34:57 -07:00
|
|
|
invoke_context
|
|
|
|
.transaction_context
|
|
|
|
.get_next_instruction_context()
|
|
|
|
.unwrap()
|
2022-09-06 02:31:40 -07:00
|
|
|
.configure(
|
2023-04-03 08:23:24 -07:00
|
|
|
&[one_more_than_max_depth.saturating_add(depth_reached) as IndexOfAccount],
|
2022-09-06 02:31:40 -07:00
|
|
|
&instruction_accounts,
|
|
|
|
&[],
|
|
|
|
);
|
2022-09-03 01:34:57 -07:00
|
|
|
if Err(InstructionError::CallDepth) == invoke_context.push() {
|
2021-11-04 13:47:32 -07:00
|
|
|
break;
|
|
|
|
}
|
2023-04-03 08:23:24 -07:00
|
|
|
depth_reached = depth_reached.saturating_add(1);
|
2021-11-04 13:47:32 -07:00
|
|
|
}
|
|
|
|
assert_ne!(depth_reached, 0);
|
2023-04-03 08:23:24 -07:00
|
|
|
assert!(depth_reached < one_more_than_max_depth);
|
2021-11-04 13:47:32 -07:00
|
|
|
}
|
|
|
|
|
2022-09-26 01:47:16 -07:00
|
|
|
#[test]
|
|
|
|
fn test_max_instruction_trace_length() {
|
|
|
|
const MAX_INSTRUCTIONS: usize = 8;
|
|
|
|
let mut transaction_context =
|
|
|
|
TransactionContext::new(Vec::new(), Some(Rent::default()), 1, MAX_INSTRUCTIONS);
|
|
|
|
for _ in 0..MAX_INSTRUCTIONS {
|
|
|
|
transaction_context.push().unwrap();
|
|
|
|
transaction_context.pop().unwrap();
|
|
|
|
}
|
|
|
|
assert_eq!(
|
|
|
|
transaction_context.push(),
|
|
|
|
Err(InstructionError::MaxInstructionTraceLengthExceeded)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-11-04 13:47:32 -07:00
|
|
|
#[test]
|
2021-12-31 08:55:27 -08:00
|
|
|
fn test_process_instruction() {
|
2021-11-04 13:47:32 -07:00
|
|
|
let callee_program_id = solana_sdk::pubkey::new_rand();
|
|
|
|
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);
|
2023-04-03 08:23:24 -07:00
|
|
|
let transaction_accounts = vec![
|
2021-12-27 09:49:32 -08: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 08:55:27 -08:00
|
|
|
(solana_sdk::pubkey::new_rand(), loader_account),
|
2021-11-04 13:47:32 -07:00
|
|
|
];
|
|
|
|
let metas = vec![
|
2023-04-03 08:23:24 -07:00
|
|
|
AccountMeta::new(transaction_accounts.get(0).unwrap().0, false),
|
|
|
|
AccountMeta::new(transaction_accounts.get(1).unwrap().0, false),
|
|
|
|
AccountMeta::new_readonly(transaction_accounts.get(2).unwrap().0, false),
|
2021-11-04 13:47:32 -07:00
|
|
|
];
|
2021-12-31 08:55:27 -08:00
|
|
|
let instruction_accounts = (0..4)
|
2022-06-16 09:46:17 -07:00
|
|
|
.map(|instruction_account_index| InstructionAccount {
|
|
|
|
index_in_transaction: instruction_account_index,
|
|
|
|
index_in_caller: instruction_account_index,
|
|
|
|
index_in_callee: instruction_account_index,
|
2021-12-31 08:55:27 -08:00
|
|
|
is_signer: false,
|
2022-06-16 09:46:17 -07:00
|
|
|
is_writable: instruction_account_index < 2,
|
2021-12-25 01:00:40 -08:00
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
2023-04-03 08:23:24 -07:00
|
|
|
with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
|
|
|
|
let builtin_programs = &[BuiltinProgram {
|
|
|
|
program_id: callee_program_id,
|
|
|
|
process_instruction: mock_process_instruction,
|
|
|
|
}];
|
|
|
|
invoke_context.builtin_programs = builtin_programs;
|
2021-11-04 13:47:32 -07:00
|
|
|
|
2022-07-15 00:31:34 -07:00
|
|
|
// Account modification tests
|
2021-11-04 13:47:32 -07: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),
|
|
|
|
),
|
2022-07-15 00:31:34 -07:00
|
|
|
(
|
|
|
|
MockInstruction::UnbalancedPush,
|
|
|
|
Err(InstructionError::UnbalancedInstruction),
|
|
|
|
),
|
|
|
|
(
|
|
|
|
MockInstruction::UnbalancedPop,
|
|
|
|
Err(InstructionError::UnbalancedInstruction),
|
|
|
|
),
|
2021-11-04 13:47:32 -07:00
|
|
|
];
|
|
|
|
for case in cases {
|
2022-01-03 14:30:56 -08:00
|
|
|
invoke_context
|
2022-09-03 01:34:57 -07:00
|
|
|
.transaction_context
|
|
|
|
.get_next_instruction_context()
|
|
|
|
.unwrap()
|
|
|
|
.configure(&[4], &instruction_accounts, &[]);
|
|
|
|
invoke_context.push().unwrap();
|
2021-12-31 08:55:27 -08:00
|
|
|
let inner_instruction =
|
2021-11-04 13:47:32 -07:00
|
|
|
Instruction::new_with_bincode(callee_program_id, &case.0, metas.clone());
|
2022-07-15 00:31:34 -07:00
|
|
|
let result = invoke_context
|
2023-02-16 05:16:25 -08:00
|
|
|
.native_invoke(inner_instruction.into(), &[])
|
2022-07-15 00:31:34 -07:00
|
|
|
.and(invoke_context.pop());
|
|
|
|
assert_eq!(result, case.1);
|
2021-12-31 08:55:27 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Compute unit consumption tests
|
|
|
|
let compute_units_to_consume = 10;
|
|
|
|
let expected_results = vec![Ok(()), Err(InstructionError::GenericError)];
|
|
|
|
for expected_result in expected_results {
|
2022-01-03 14:30:56 -08:00
|
|
|
invoke_context
|
2022-09-03 01:34:57 -07:00
|
|
|
.transaction_context
|
|
|
|
.get_next_instruction_context()
|
|
|
|
.unwrap()
|
|
|
|
.configure(&[4], &instruction_accounts, &[]);
|
|
|
|
invoke_context.push().unwrap();
|
2021-12-31 08:55:27 -08:00
|
|
|
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 13:47:32 -07:00
|
|
|
);
|
2023-02-16 05:16:25 -08:00
|
|
|
let inner_instruction = StableInstruction::from(inner_instruction);
|
2022-01-03 14:30:56 -08:00
|
|
|
let (inner_instruction_accounts, program_indices) = invoke_context
|
|
|
|
.prepare_instruction(&inner_instruction, &[])
|
|
|
|
.unwrap();
|
2021-12-31 08:55:27 -08:00
|
|
|
|
|
|
|
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(),
|
2021-12-31 08:55:27 -08:00
|
|
|
);
|
|
|
|
|
|
|
|
// 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);
|
2023-03-24 09:31:01 -07:00
|
|
|
assert_eq!(
|
|
|
|
compute_units_consumed,
|
2023-04-03 08:23:24 -07:00
|
|
|
compute_units_to_consume.saturating_add(MOCK_BUILTIN_COMPUTE_UNIT_COST),
|
2023-03-24 09:31:01 -07:00
|
|
|
);
|
2021-12-31 08:55:27 -08:00
|
|
|
assert_eq!(result, expected_result);
|
|
|
|
|
2022-01-03 14:30:56 -08:00
|
|
|
invoke_context.pop().unwrap();
|
2021-11-04 13:47:32 -07:00
|
|
|
}
|
|
|
|
}
|
2021-11-11 14:09:28 -08:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_invoke_context_compute_budget() {
|
2023-04-03 08:23:24 -07:00
|
|
|
let transaction_accounts =
|
|
|
|
vec![(solana_sdk::pubkey::new_rand(), AccountSharedData::default())];
|
2021-11-11 14:09:28 -08:00
|
|
|
|
2023-04-03 08:23:24 -07:00
|
|
|
with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
|
2022-05-17 22:14:31 -07:00
|
|
|
invoke_context.compute_budget =
|
|
|
|
ComputeBudget::new(compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64);
|
2021-11-11 14:09:28 -08:00
|
|
|
|
2022-09-03 01:34:57 -07:00
|
|
|
invoke_context
|
|
|
|
.transaction_context
|
|
|
|
.get_next_instruction_context()
|
|
|
|
.unwrap()
|
|
|
|
.configure(&[0], &[], &[]);
|
|
|
|
invoke_context.push().unwrap();
|
2021-11-11 14:09:28 -08:00
|
|
|
assert_eq!(
|
|
|
|
*invoke_context.get_compute_budget(),
|
2022-05-17 22:14:31 -07:00
|
|
|
ComputeBudget::new(compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64)
|
2021-11-11 14:09:28 -08:00
|
|
|
);
|
2022-01-03 14:30:56 -08:00
|
|
|
invoke_context.pop().unwrap();
|
2021-11-11 14:09:28 -08:00
|
|
|
}
|
2022-01-04 08:00:21 -08:00
|
|
|
|
|
|
|
#[test]
|
2022-08-08 08:05:25 -07:00
|
|
|
fn test_process_instruction_accounts_resize_delta() {
|
2022-01-04 08:00:21 -08:00
|
|
|
let program_key = Pubkey::new_unique();
|
2022-07-06 10:27:42 -07:00
|
|
|
let user_account_data_len = 123u64;
|
|
|
|
let user_account =
|
|
|
|
AccountSharedData::new(100, user_account_data_len as usize, &program_key);
|
2022-01-04 08:00:21 -08:00
|
|
|
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);
|
2023-04-03 08:23:24 -07:00
|
|
|
let transaction_accounts = vec![
|
2022-01-04 08:00:21 -08:00
|
|
|
(Pubkey::new_unique(), user_account),
|
|
|
|
(Pubkey::new_unique(), dummy_account),
|
|
|
|
(program_key, program_account),
|
|
|
|
];
|
|
|
|
let instruction_accounts = [
|
|
|
|
InstructionAccount {
|
|
|
|
index_in_transaction: 0,
|
2022-05-25 04:43:20 -07:00
|
|
|
index_in_caller: 0,
|
2022-05-24 15:04:46 -07:00
|
|
|
index_in_callee: 0,
|
2022-01-04 08:00:21 -08:00
|
|
|
is_signer: false,
|
|
|
|
is_writable: true,
|
|
|
|
},
|
|
|
|
InstructionAccount {
|
|
|
|
index_in_transaction: 1,
|
2022-05-25 04:43:20 -07:00
|
|
|
index_in_caller: 1,
|
2022-05-24 15:04:46 -07:00
|
|
|
index_in_callee: 1,
|
2022-01-04 08:00:21 -08:00
|
|
|
is_signer: false,
|
|
|
|
is_writable: false,
|
|
|
|
},
|
|
|
|
];
|
2023-04-03 08:23:24 -07:00
|
|
|
with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
|
|
|
|
let builtin_programs = &[BuiltinProgram {
|
|
|
|
program_id: program_key,
|
|
|
|
process_instruction: mock_process_instruction,
|
|
|
|
}];
|
|
|
|
invoke_context.builtin_programs = builtin_programs;
|
2022-01-04 08:00:21 -08:00
|
|
|
|
2022-08-08 08:05:25 -07:00
|
|
|
// Test: Resize the account to *the same size*, so not consuming any additional size; this must succeed
|
2022-01-04 08:00:21 -08:00
|
|
|
{
|
2022-08-08 08:05:25 -07:00
|
|
|
let resize_delta: i64 = 0;
|
2023-04-03 08:23:24 -07:00
|
|
|
let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
|
2022-01-04 08:00:21 -08:00
|
|
|
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(),
|
2022-01-04 08:00:21 -08:00
|
|
|
);
|
|
|
|
|
|
|
|
assert!(result.is_ok());
|
2022-07-15 00:31:34 -07:00
|
|
|
assert_eq!(
|
|
|
|
invoke_context
|
|
|
|
.transaction_context
|
|
|
|
.accounts_resize_delta()
|
|
|
|
.unwrap(),
|
2022-08-08 08:05:25 -07:00
|
|
|
resize_delta
|
2022-07-15 00:31:34 -07:00
|
|
|
);
|
2022-01-04 08:00:21 -08:00
|
|
|
}
|
|
|
|
|
2022-08-08 08:05:25 -07:00
|
|
|
// Test: Resize the account larger; this must succeed
|
2022-01-04 08:00:21 -08:00
|
|
|
{
|
2022-08-08 08:05:25 -07:00
|
|
|
let resize_delta: i64 = 1;
|
2023-04-03 08:23:24 -07:00
|
|
|
let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
|
2022-01-04 08:00:21 -08:00
|
|
|
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(),
|
2022-01-04 08:00:21 -08:00
|
|
|
);
|
|
|
|
|
|
|
|
assert!(result.is_ok());
|
2022-07-15 00:31:34 -07:00
|
|
|
assert_eq!(
|
|
|
|
invoke_context
|
|
|
|
.transaction_context
|
|
|
|
.accounts_resize_delta()
|
|
|
|
.unwrap(),
|
2022-08-08 08:05:25 -07:00
|
|
|
resize_delta
|
2022-07-15 00:31:34 -07:00
|
|
|
);
|
2022-01-04 08:00:21 -08:00
|
|
|
}
|
|
|
|
|
2022-08-08 08:05:25 -07:00
|
|
|
// Test: Resize the account smaller; this must succeed
|
2022-01-04 08:00:21 -08:00
|
|
|
{
|
2022-08-08 08:05:25 -07:00
|
|
|
let resize_delta: i64 = -1;
|
2023-04-03 08:23:24 -07:00
|
|
|
let new_len = (user_account_data_len as i64).saturating_add(resize_delta) as u64;
|
2022-01-04 08:00:21 -08:00
|
|
|
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(),
|
2022-01-04 08:00:21 -08:00
|
|
|
);
|
|
|
|
|
2022-07-15 00:31:34 -07:00
|
|
|
assert!(result.is_ok());
|
|
|
|
assert_eq!(
|
|
|
|
invoke_context
|
|
|
|
.transaction_context
|
|
|
|
.accounts_resize_delta()
|
|
|
|
.unwrap(),
|
2022-08-08 08:05:25 -07:00
|
|
|
resize_delta
|
2022-07-15 00:31:34 -07:00
|
|
|
);
|
2022-01-04 08:00:21 -08:00
|
|
|
}
|
|
|
|
}
|
2021-11-04 13:47:32 -07:00
|
|
|
}
|