#![cfg(feature = "full")]
use itertools::Itertools;
use gemachain_sdk::{
account::AccountSharedData,
compute_budget::ComputeBudget,
fee_calculator::FeeCalculator,
hash::Hash,
instruction::{CompiledInstruction, Instruction, InstructionError},
keyed_account::{create_keyed_accounts_unified, KeyedAccount},
message::Message,
pubkey::Pubkey,
sysvar::Sysvar,
};
use std::{cell::RefCell, collections::HashSet, fmt::Debug, rc::Rc, sync::Arc};
pub type LoaderEntrypoint = unsafe extern "C" fn(
program_id: &Pubkey,
instruction_data: &[u8],
invoke_context: &dyn InvokeContext,
) -> Result<(), InstructionError>;
pub type ProcessInstructionWithContext =
fn(&Pubkey, &[u8], &mut dyn InvokeContext) -> Result<(), InstructionError>;
pub struct InvokeContextStackFrame<'a> {
pub key: Pubkey,
pub keyed_accounts: Vec<KeyedAccount<'a>>,
pub keyed_accounts_range: std::ops::Range<usize>,
}
impl<'a> InvokeContextStackFrame<'a> {
pub fn new(key: Pubkey, keyed_accounts: Vec<KeyedAccount<'a>>) -> Self {
let keyed_accounts_range = std::ops::Range {
start: 0,
end: keyed_accounts.len(),
};
Self {
key,
keyed_accounts,
keyed_accounts_range,
}
}
}
pub trait InvokeContext {
fn push(
&mut self,
key: &Pubkey,
message: &Message,
instruction: &CompiledInstruction,
program_indices: &[usize],
account_indices: &[usize],
) -> Result<(), InstructionError>;
fn pop(&mut self);
fn invoke_depth(&self) -> usize;
fn verify_and_update(
&mut self,
instruction: &CompiledInstruction,
account_indices: &[usize],
write_privileges: &[bool],
) -> Result<(), InstructionError>;
fn get_caller(&self) -> Result<&Pubkey, InstructionError>;
fn remove_first_keyed_account(&mut self) -> Result<(), InstructionError>;
fn get_keyed_accounts(&self) -> Result<&[KeyedAccount], InstructionError>;
fn get_programs(&self) -> &[(Pubkey, ProcessInstructionWithContext)];
fn get_logger(&self) -> Rc<RefCell<dyn Logger>>;
#[allow(deprecated)]
#[deprecated(since = "1.8.0", note = "please use `get_compute_budget` instead")]
fn get_bpf_compute_budget(&self) -> &BpfComputeBudget;
fn get_compute_meter(&self) -> Rc<RefCell<dyn ComputeMeter>>;
fn add_executor(&self, pubkey: &Pubkey, executor: Arc<dyn Executor>);
fn get_executor(&self, pubkey: &Pubkey) -> Option<Arc<dyn Executor>>;
fn record_instruction(&self, instruction: &Instruction);
fn is_feature_active(&self, feature_id: &Pubkey) -> bool;
fn get_account(&self, pubkey: &Pubkey) -> Option<(usize, Rc<RefCell<AccountSharedData>>)>;
fn update_timing(
&mut self,
serialize_us: u64,
create_vm_us: u64,
execute_us: u64,
deserialize_us: u64,
);
fn get_sysvar_data(&self, id: &Pubkey) -> Option<Rc<Vec<u8>>>;
fn get_compute_budget(&self) -> &ComputeBudget;
fn get_blockhash(&self) -> &Hash;
fn get_fee_calculator(&self) -> &FeeCalculator;
fn set_return_data(&mut self, return_data: Option<(Pubkey, Vec<u8>)>);
fn get_return_data(&self) -> &Option<(Pubkey, Vec<u8>)>;
}
#[macro_export]
macro_rules! ic_logger_msg {
($logger:expr, $message:expr) => {
if let Ok(logger) = $logger.try_borrow_mut() {
if logger.log_enabled() {
logger.log($message);
}
}
};
($logger:expr, $fmt:expr, $($arg:tt)*) => {
if let Ok(logger) = $logger.try_borrow_mut() {
if logger.log_enabled() {
logger.log(&format!($fmt, $($arg)*));
}
}
};
}
#[macro_export]
macro_rules! ic_msg {
($invoke_context:expr, $message:expr) => {
$crate::ic_logger_msg!($invoke_context.get_logger(), $message)
};
($invoke_context:expr, $fmt:expr, $($arg:tt)*) => {
$crate::ic_logger_msg!($invoke_context.get_logger(), $fmt, $($arg)*)
};
}
pub fn get_sysvar<T: Sysvar>(
invoke_context: &dyn InvokeContext,
id: &Pubkey,
) -> Result<T, InstructionError> {
let sysvar_data = invoke_context.get_sysvar_data(id).ok_or_else(|| {
ic_msg!(invoke_context, "Unable to get sysvar {}", id);
InstructionError::UnsupportedSysvar
})?;
bincode::deserialize(&sysvar_data).map_err(|err| {
ic_msg!(invoke_context, "Unable to get sysvar {}: {:?}", id, err);
InstructionError::UnsupportedSysvar
})
}
#[deprecated(since = "1.8.0", note = "please use `ComputeBudget` instead")]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct BpfComputeBudget {
pub max_units: u64,
pub log_64_units: u64,
pub create_program_address_units: u64,
pub invoke_units: u64,
pub max_invoke_depth: usize,
pub sha256_base_cost: u64,
pub sha256_byte_cost: u64,
pub max_call_depth: usize,
pub stack_frame_size: usize,
pub log_pubkey_units: u64,
pub max_cpi_instruction_size: usize,
pub cpi_bytes_per_unit: u64,
pub sysvar_base_cost: u64,
pub secp256k1_recover_cost: u64,
pub syscall_base_cost: u64,
pub heap_size: Option<usize>,
}
#[allow(deprecated)]
impl From<ComputeBudget> for BpfComputeBudget {
fn from(item: ComputeBudget) -> Self {
BpfComputeBudget {
max_units: item.max_units,
log_64_units: item.log_64_units,
create_program_address_units: item.create_program_address_units,
invoke_units: item.invoke_units,
max_invoke_depth: item.max_invoke_depth,
sha256_base_cost: item.sha256_base_cost,
sha256_byte_cost: item.sha256_byte_cost,
max_call_depth: item.max_call_depth,
stack_frame_size: item.stack_frame_size,
log_pubkey_units: item.log_pubkey_units,
max_cpi_instruction_size: item.max_cpi_instruction_size,
cpi_bytes_per_unit: item.cpi_bytes_per_unit,
sysvar_base_cost: item.sysvar_base_cost,
syscall_base_cost: item.syscall_base_cost,
secp256k1_recover_cost: item.secp256k1_recover_cost,
heap_size: item.heap_size,
}
}
}
#[allow(deprecated)]
impl From<BpfComputeBudget> for ComputeBudget {
fn from(item: BpfComputeBudget) -> Self {
ComputeBudget {
max_units: item.max_units,
log_64_units: item.log_64_units,
create_program_address_units: item.create_program_address_units,
invoke_units: item.invoke_units,
max_invoke_depth: item.max_invoke_depth,
sha256_base_cost: item.sha256_base_cost,
sha256_byte_cost: item.sha256_byte_cost,
max_call_depth: item.max_call_depth,
stack_frame_size: item.stack_frame_size,
log_pubkey_units: item.log_pubkey_units,
max_cpi_instruction_size: item.max_cpi_instruction_size,
cpi_bytes_per_unit: item.cpi_bytes_per_unit,
sysvar_base_cost: item.sysvar_base_cost,
secp256k1_recover_cost: item.secp256k1_recover_cost,
syscall_base_cost: item.syscall_base_cost,
heap_size: item.heap_size,
}
}
}
#[allow(deprecated)]
impl Default for BpfComputeBudget {
fn default() -> Self {
ComputeBudget::default().into()
}
}
#[allow(deprecated)]
impl BpfComputeBudget {
pub fn new() -> Self {
BpfComputeBudget::default()
}
}
pub trait ComputeMeter {
fn consume(&mut self, amount: u64) -> Result<(), InstructionError>;
fn get_remaining(&self) -> u64;
}
pub trait Logger {
fn log_enabled(&self) -> bool;
fn log(&self, message: &str);
}
pub mod stable_log {
use super::*;
pub fn program_invoke(
logger: &Rc<RefCell<dyn Logger>>,
program_id: &Pubkey,
invoke_depth: usize,
) {
ic_logger_msg!(logger, "Program {} invoke [{}]", program_id, invoke_depth);
}
pub fn program_log(logger: &Rc<RefCell<dyn Logger>>, message: &str) {
ic_logger_msg!(logger, "Program log: {}", message);
}
pub fn program_data(logger: &Rc<RefCell<dyn Logger>>, data: &[&[u8]]) {
ic_logger_msg!(
logger,
"Program data: {}",
data.iter().map(base64::encode).join(" ")
);
}
pub fn program_return(logger: &Rc<RefCell<dyn Logger>>, program_id: &Pubkey, data: &[u8]) {
ic_logger_msg!(
logger,
"Program return: {} {}",
program_id,
base64::encode(data)
);
}
pub fn program_success(logger: &Rc<RefCell<dyn Logger>>, program_id: &Pubkey) {
ic_logger_msg!(logger, "Program {} success", program_id);
}
pub fn program_failure(
logger: &Rc<RefCell<dyn Logger>>,
program_id: &Pubkey,
err: &InstructionError,
) {
ic_logger_msg!(logger, "Program {} failed: {}", program_id, err);
}
}
pub trait Executor: Debug + Send + Sync {
fn execute(
&self,
loader_id: &Pubkey,
program_id: &Pubkey,
instruction_data: &[u8],
invoke_context: &mut dyn InvokeContext,
use_jit: bool,
) -> Result<(), InstructionError>;
}
#[derive(Debug, Default, Clone)]
pub struct MockComputeMeter {
pub remaining: u64,
}
impl ComputeMeter for MockComputeMeter {
fn consume(&mut self, amount: u64) -> Result<(), InstructionError> {
let exceeded = self.remaining < amount;
self.remaining = self.remaining.saturating_sub(amount);
if exceeded {
return Err(InstructionError::ComputationalBudgetExceeded);
}
Ok(())
}
fn get_remaining(&self) -> u64 {
self.remaining
}
}
#[derive(Debug, Default, Clone)]
pub struct MockLogger {
pub log: Rc<RefCell<Vec<String>>>,
}
impl Logger for MockLogger {
fn log_enabled(&self) -> bool {
true
}
fn log(&self, message: &str) {
self.log.borrow_mut().push(message.to_string());
}
}
#[allow(deprecated)]
pub struct MockInvokeContext<'a> {
pub invoke_stack: Vec<InvokeContextStackFrame<'a>>,
pub logger: MockLogger,
pub compute_budget: ComputeBudget,
pub bpf_compute_budget: BpfComputeBudget,
pub compute_meter: MockComputeMeter,
pub programs: Vec<(Pubkey, ProcessInstructionWithContext)>,
pub accounts: Vec<(Pubkey, Rc<RefCell<AccountSharedData>>)>,
pub sysvars: Vec<(Pubkey, Option<Rc<Vec<u8>>>)>,
pub disabled_features: HashSet<Pubkey>,
pub blockhash: Hash,
pub fee_calculator: FeeCalculator,
pub return_data: Option<(Pubkey, Vec<u8>)>,
}
impl<'a> MockInvokeContext<'a> {
pub fn new(keyed_accounts: Vec<KeyedAccount<'a>>) -> Self {
let compute_budget = ComputeBudget::default();
let mut invoke_context = MockInvokeContext {
invoke_stack: Vec::with_capacity(compute_budget.max_invoke_depth),
logger: MockLogger::default(),
compute_budget,
bpf_compute_budget: compute_budget.into(),
compute_meter: MockComputeMeter {
remaining: std::i64::MAX as u64,
},
programs: vec![],
accounts: vec![],
sysvars: vec![],
disabled_features: HashSet::default(),
blockhash: Hash::default(),
fee_calculator: FeeCalculator::default(),
return_data: None,
};
invoke_context
.invoke_stack
.push(InvokeContextStackFrame::new(
Pubkey::default(),
keyed_accounts,
));
invoke_context
}
}
pub fn mock_set_sysvar<T: Sysvar>(
mock_invoke_context: &mut MockInvokeContext,
id: Pubkey,
sysvar: T,
) -> Result<(), InstructionError> {
let mut data = Vec::with_capacity(T::size_of());
bincode::serialize_into(&mut data, &sysvar).map_err(|err| {
ic_msg!(mock_invoke_context, "Unable to serialize sysvar: {:?}", err);
InstructionError::GenericError
})?;
mock_invoke_context.sysvars.push((id, Some(Rc::new(data))));
Ok(())
}
impl<'a> InvokeContext for MockInvokeContext<'a> {
fn push(
&mut self,
_key: &Pubkey,
_message: &Message,
_instruction: &CompiledInstruction,
_program_indices: &[usize],
_account_indices: &[usize],
) -> Result<(), InstructionError> {
self.invoke_stack.push(InvokeContextStackFrame::new(
*_key,
create_keyed_accounts_unified(&[]),
));
Ok(())
}
fn pop(&mut self) {
self.invoke_stack.pop();
}
fn invoke_depth(&self) -> usize {
self.invoke_stack.len()
}
fn verify_and_update(
&mut self,
_instruction: &CompiledInstruction,
_account_indices: &[usize],
_write_pivileges: &[bool],
) -> Result<(), InstructionError> {
Ok(())
}
fn get_caller(&self) -> Result<&Pubkey, InstructionError> {
self.invoke_stack
.last()
.map(|frame| &frame.key)
.ok_or(InstructionError::CallDepth)
}
fn remove_first_keyed_account(&mut self) -> Result<(), InstructionError> {
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(())
}
fn get_keyed_accounts(&self) -> Result<&[KeyedAccount], InstructionError> {
self.invoke_stack
.last()
.map(|frame| &frame.keyed_accounts[frame.keyed_accounts_range.clone()])
.ok_or(InstructionError::CallDepth)
}
fn get_programs(&self) -> &[(Pubkey, ProcessInstructionWithContext)] {
&self.programs
}
fn get_logger(&self) -> Rc<RefCell<dyn Logger>> {
Rc::new(RefCell::new(self.logger.clone()))
}
#[allow(deprecated)]
fn get_bpf_compute_budget(&self) -> &BpfComputeBudget {
#[allow(deprecated)]
&self.bpf_compute_budget
}
fn get_compute_meter(&self) -> Rc<RefCell<dyn ComputeMeter>> {
Rc::new(RefCell::new(self.compute_meter.clone()))
}
fn add_executor(&self, _pubkey: &Pubkey, _executor: Arc<dyn Executor>) {}
fn get_executor(&self, _pubkey: &Pubkey) -> Option<Arc<dyn Executor>> {
None
}
fn record_instruction(&self, _instruction: &Instruction) {}
fn is_feature_active(&self, feature_id: &Pubkey) -> bool {
!self.disabled_features.contains(feature_id)
}
fn get_account(&self, pubkey: &Pubkey) -> Option<(usize, Rc<RefCell<AccountSharedData>>)> {
for (index, (key, account)) in self.accounts.iter().enumerate().rev() {
if key == pubkey {
return Some((index, account.clone()));
}
}
None
}
fn update_timing(
&mut self,
_serialize_us: u64,
_create_vm_us: u64,
_execute_us: u64,
_deserialize_us: u64,
) {
}
fn get_sysvar_data(&self, id: &Pubkey) -> Option<Rc<Vec<u8>>> {
self.sysvars
.iter()
.find_map(|(key, sysvar)| if id == key { sysvar.clone() } else { None })
}
fn get_compute_budget(&self) -> &ComputeBudget {
&self.compute_budget
}
fn get_blockhash(&self) -> &Hash {
&self.blockhash
}
fn get_fee_calculator(&self) -> &FeeCalculator {
&self.fee_calculator
}
fn set_return_data(&mut self, return_data: Option<(Pubkey, Vec<u8>)>) {
self.return_data = return_data;
}
fn get_return_data(&self) -> &Option<(Pubkey, Vec<u8>)> {
&self.return_data
}
}