#![deny(clippy::indexing_slicing)]
#[cfg(all(not(target_os = "solana"), feature = "full", debug_assertions))]
use crate::signature::Signature;
use {
crate::{instruction::InstructionError, pubkey::Pubkey},
solana_account::{AccountSharedData, ReadableAccount},
std::{
cell::{Ref, RefCell, RefMut},
collections::HashSet,
pin::Pin,
rc::Rc,
},
};
#[cfg(not(target_os = "solana"))]
use {
crate::{
rent::Rent,
system_instruction::{
MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION, MAX_PERMITTED_DATA_LENGTH,
},
},
solana_account::WritableAccount,
solana_program::entrypoint::MAX_PERMITTED_DATA_INCREASE,
std::mem::MaybeUninit,
};
pub type IndexOfAccount = u16;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct InstructionAccount {
pub index_in_transaction: IndexOfAccount,
pub index_in_caller: IndexOfAccount,
pub index_in_callee: IndexOfAccount,
pub is_signer: bool,
pub is_writable: bool,
}
pub type TransactionAccount = (Pubkey, AccountSharedData);
#[derive(Clone, Debug, PartialEq)]
pub struct TransactionAccounts {
accounts: Vec<RefCell<AccountSharedData>>,
touched_flags: RefCell<Box<[bool]>>,
}
impl TransactionAccounts {
#[cfg(not(target_os = "solana"))]
fn new(accounts: Vec<RefCell<AccountSharedData>>) -> TransactionAccounts {
TransactionAccounts {
touched_flags: RefCell::new(vec![false; accounts.len()].into_boxed_slice()),
accounts,
}
}
fn len(&self) -> usize {
self.accounts.len()
}
pub fn get(&self, index: IndexOfAccount) -> Option<&RefCell<AccountSharedData>> {
self.accounts.get(index as usize)
}
#[cfg(not(target_os = "solana"))]
pub fn touch(&self, index: IndexOfAccount) -> Result<(), InstructionError> {
*self
.touched_flags
.borrow_mut()
.get_mut(index as usize)
.ok_or(InstructionError::NotEnoughAccountKeys)? = true;
Ok(())
}
#[cfg(not(target_os = "solana"))]
pub fn touched_count(&self) -> usize {
self.touched_flags
.borrow()
.iter()
.fold(0usize, |accumulator, was_touched| {
accumulator.saturating_add(*was_touched as usize)
})
}
pub fn try_borrow(
&self,
index: IndexOfAccount,
) -> Result<Ref<'_, AccountSharedData>, InstructionError> {
self.accounts
.get(index as usize)
.ok_or(InstructionError::MissingAccount)?
.try_borrow()
.map_err(|_| InstructionError::AccountBorrowFailed)
}
pub fn try_borrow_mut(
&self,
index: IndexOfAccount,
) -> Result<RefMut<'_, AccountSharedData>, InstructionError> {
self.accounts
.get(index as usize)
.ok_or(InstructionError::MissingAccount)?
.try_borrow_mut()
.map_err(|_| InstructionError::AccountBorrowFailed)
}
pub fn into_accounts(self) -> Vec<AccountSharedData> {
self.accounts
.into_iter()
.map(|account| account.into_inner())
.collect()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TransactionContext {
account_keys: Pin<Box<[Pubkey]>>,
accounts: Rc<TransactionAccounts>,
instruction_stack_capacity: usize,
instruction_trace_capacity: usize,
instruction_stack: Vec<usize>,
instruction_trace: Vec<InstructionContext>,
return_data: TransactionReturnData,
accounts_resize_delta: RefCell<i64>,
#[cfg(not(target_os = "solana"))]
rent: Rent,
#[cfg(all(not(target_os = "solana"), feature = "full", debug_assertions))]
signature: Signature,
}
impl TransactionContext {
#[cfg(not(target_os = "solana"))]
pub fn new(
transaction_accounts: Vec<TransactionAccount>,
rent: Rent,
instruction_stack_capacity: usize,
instruction_trace_capacity: usize,
) -> Self {
let (account_keys, accounts): (Vec<_>, Vec<_>) = transaction_accounts
.into_iter()
.map(|(key, account)| (key, RefCell::new(account)))
.unzip();
Self {
account_keys: Pin::new(account_keys.into_boxed_slice()),
accounts: Rc::new(TransactionAccounts::new(accounts)),
instruction_stack_capacity,
instruction_trace_capacity,
instruction_stack: Vec::with_capacity(instruction_stack_capacity),
instruction_trace: vec![InstructionContext::default()],
return_data: TransactionReturnData::default(),
accounts_resize_delta: RefCell::new(0),
rent,
#[cfg(all(not(target_os = "solana"), feature = "full", debug_assertions))]
signature: Signature::default(),
}
}
#[cfg(not(target_os = "solana"))]
pub fn deconstruct_without_keys(self) -> Result<Vec<AccountSharedData>, InstructionError> {
if !self.instruction_stack.is_empty() {
return Err(InstructionError::CallDepth);
}
Ok(Rc::try_unwrap(self.accounts)
.expect("transaction_context.accounts has unexpected outstanding refs")
.into_accounts())
}
#[cfg(not(target_os = "solana"))]
pub fn accounts(&self) -> &Rc<TransactionAccounts> {
&self.accounts
}
#[cfg(all(not(target_os = "solana"), feature = "full", debug_assertions))]
pub fn set_signature(&mut self, signature: &Signature) {
self.signature = *signature;
}
#[cfg(all(not(target_os = "solana"), feature = "full", debug_assertions))]
pub fn get_signature(&self) -> &Signature {
&self.signature
}
pub fn get_number_of_accounts(&self) -> IndexOfAccount {
self.accounts.len() as IndexOfAccount
}
pub fn get_key_of_account_at_index(
&self,
index_in_transaction: IndexOfAccount,
) -> Result<&Pubkey, InstructionError> {
self.account_keys
.get(index_in_transaction as usize)
.ok_or(InstructionError::NotEnoughAccountKeys)
}
#[cfg(not(target_os = "solana"))]
pub fn get_account_at_index(
&self,
index_in_transaction: IndexOfAccount,
) -> Result<&RefCell<AccountSharedData>, InstructionError> {
self.accounts
.get(index_in_transaction)
.ok_or(InstructionError::NotEnoughAccountKeys)
}
pub fn find_index_of_account(&self, pubkey: &Pubkey) -> Option<IndexOfAccount> {
self.account_keys
.iter()
.position(|key| key == pubkey)
.map(|index| index as IndexOfAccount)
}
pub fn find_index_of_program_account(&self, pubkey: &Pubkey) -> Option<IndexOfAccount> {
self.account_keys
.iter()
.rposition(|key| key == pubkey)
.map(|index| index as IndexOfAccount)
}
pub fn get_instruction_trace_capacity(&self) -> usize {
self.instruction_trace_capacity
}
pub fn get_instruction_trace_length(&self) -> usize {
self.instruction_trace.len().saturating_sub(1)
}
pub fn get_instruction_context_at_index_in_trace(
&self,
index_in_trace: usize,
) -> Result<&InstructionContext, InstructionError> {
self.instruction_trace
.get(index_in_trace)
.ok_or(InstructionError::CallDepth)
}
pub fn get_instruction_context_at_nesting_level(
&self,
nesting_level: usize,
) -> Result<&InstructionContext, InstructionError> {
let index_in_trace = *self
.instruction_stack
.get(nesting_level)
.ok_or(InstructionError::CallDepth)?;
let instruction_context = self.get_instruction_context_at_index_in_trace(index_in_trace)?;
debug_assert_eq!(instruction_context.nesting_level, nesting_level);
Ok(instruction_context)
}
pub fn get_instruction_stack_capacity(&self) -> usize {
self.instruction_stack_capacity
}
pub fn get_instruction_context_stack_height(&self) -> usize {
self.instruction_stack.len()
}
pub fn get_current_instruction_context(&self) -> Result<&InstructionContext, InstructionError> {
let level = self
.get_instruction_context_stack_height()
.checked_sub(1)
.ok_or(InstructionError::CallDepth)?;
self.get_instruction_context_at_nesting_level(level)
}
pub fn get_next_instruction_context(
&mut self,
) -> Result<&mut InstructionContext, InstructionError> {
self.instruction_trace
.last_mut()
.ok_or(InstructionError::CallDepth)
}
#[cfg(not(target_os = "solana"))]
pub fn push(&mut self) -> Result<(), InstructionError> {
let nesting_level = self.get_instruction_context_stack_height();
let caller_instruction_context = self
.instruction_trace
.last()
.ok_or(InstructionError::CallDepth)?;
let callee_instruction_accounts_lamport_sum =
self.instruction_accounts_lamport_sum(caller_instruction_context)?;
if !self.instruction_stack.is_empty() {
let caller_instruction_context = self.get_current_instruction_context()?;
let original_caller_instruction_accounts_lamport_sum =
caller_instruction_context.instruction_accounts_lamport_sum;
let current_caller_instruction_accounts_lamport_sum =
self.instruction_accounts_lamport_sum(caller_instruction_context)?;
if original_caller_instruction_accounts_lamport_sum
!= current_caller_instruction_accounts_lamport_sum
{
return Err(InstructionError::UnbalancedInstruction);
}
}
{
let instruction_context = self.get_next_instruction_context()?;
instruction_context.nesting_level = nesting_level;
instruction_context.instruction_accounts_lamport_sum =
callee_instruction_accounts_lamport_sum;
}
let index_in_trace = self.get_instruction_trace_length();
if index_in_trace >= self.instruction_trace_capacity {
return Err(InstructionError::MaxInstructionTraceLengthExceeded);
}
self.instruction_trace.push(InstructionContext::default());
if nesting_level >= self.instruction_stack_capacity {
return Err(InstructionError::CallDepth);
}
self.instruction_stack.push(index_in_trace);
Ok(())
}
#[cfg(not(target_os = "solana"))]
pub fn pop(&mut self) -> Result<(), InstructionError> {
if self.instruction_stack.is_empty() {
return Err(InstructionError::CallDepth);
}
let detected_an_unbalanced_instruction =
self.get_current_instruction_context()
.and_then(|instruction_context| {
for account_index in instruction_context.program_accounts.iter() {
self.get_account_at_index(*account_index)?
.try_borrow_mut()
.map_err(|_| InstructionError::AccountBorrowOutstanding)?;
}
self.instruction_accounts_lamport_sum(instruction_context)
.map(|instruction_accounts_lamport_sum| {
instruction_context.instruction_accounts_lamport_sum
!= instruction_accounts_lamport_sum
})
});
self.instruction_stack.pop();
if detected_an_unbalanced_instruction? {
Err(InstructionError::UnbalancedInstruction)
} else {
Ok(())
}
}
pub fn get_return_data(&self) -> (&Pubkey, &[u8]) {
(&self.return_data.program_id, &self.return_data.data)
}
pub fn set_return_data(
&mut self,
program_id: Pubkey,
data: Vec<u8>,
) -> Result<(), InstructionError> {
self.return_data = TransactionReturnData { program_id, data };
Ok(())
}
#[cfg(not(target_os = "solana"))]
fn instruction_accounts_lamport_sum(
&self,
instruction_context: &InstructionContext,
) -> Result<u128, InstructionError> {
let mut instruction_accounts_lamport_sum: u128 = 0;
for instruction_account_index in 0..instruction_context.get_number_of_instruction_accounts()
{
if instruction_context
.is_instruction_account_duplicate(instruction_account_index)?
.is_some()
{
continue; }
let index_in_transaction = instruction_context
.get_index_of_instruction_account_in_transaction(instruction_account_index)?;
instruction_accounts_lamport_sum = (self
.get_account_at_index(index_in_transaction)?
.try_borrow()
.map_err(|_| InstructionError::AccountBorrowOutstanding)?
.lamports() as u128)
.checked_add(instruction_accounts_lamport_sum)
.ok_or(InstructionError::ArithmeticOverflow)?;
}
Ok(instruction_accounts_lamport_sum)
}
pub fn accounts_resize_delta(&self) -> Result<i64, InstructionError> {
self.accounts_resize_delta
.try_borrow()
.map_err(|_| InstructionError::GenericError)
.map(|value_ref| *value_ref)
}
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
pub struct TransactionReturnData {
pub program_id: Pubkey,
pub data: Vec<u8>,
}
#[derive(Debug, Clone, Default, Eq, PartialEq)]
pub struct InstructionContext {
nesting_level: usize,
instruction_accounts_lamport_sum: u128,
program_accounts: Vec<IndexOfAccount>,
instruction_accounts: Vec<InstructionAccount>,
instruction_data: Vec<u8>,
}
impl InstructionContext {
#[cfg(not(target_os = "solana"))]
pub fn configure(
&mut self,
program_accounts: &[IndexOfAccount],
instruction_accounts: &[InstructionAccount],
instruction_data: &[u8],
) {
self.program_accounts = program_accounts.to_vec();
self.instruction_accounts = instruction_accounts.to_vec();
self.instruction_data = instruction_data.to_vec();
}
pub fn get_stack_height(&self) -> usize {
self.nesting_level.saturating_add(1)
}
pub fn get_number_of_program_accounts(&self) -> IndexOfAccount {
self.program_accounts.len() as IndexOfAccount
}
pub fn get_number_of_instruction_accounts(&self) -> IndexOfAccount {
self.instruction_accounts.len() as IndexOfAccount
}
pub fn check_number_of_instruction_accounts(
&self,
expected_at_least: IndexOfAccount,
) -> Result<(), InstructionError> {
if self.get_number_of_instruction_accounts() < expected_at_least {
Err(InstructionError::NotEnoughAccountKeys)
} else {
Ok(())
}
}
pub fn get_instruction_data(&self) -> &[u8] {
&self.instruction_data
}
pub fn find_index_of_program_account(
&self,
transaction_context: &TransactionContext,
pubkey: &Pubkey,
) -> Option<IndexOfAccount> {
self.program_accounts
.iter()
.position(|index_in_transaction| {
transaction_context
.account_keys
.get(*index_in_transaction as usize)
== Some(pubkey)
})
.map(|index| index as IndexOfAccount)
}
pub fn find_index_of_instruction_account(
&self,
transaction_context: &TransactionContext,
pubkey: &Pubkey,
) -> Option<IndexOfAccount> {
self.instruction_accounts
.iter()
.position(|instruction_account| {
transaction_context
.account_keys
.get(instruction_account.index_in_transaction as usize)
== Some(pubkey)
})
.map(|index| index as IndexOfAccount)
}
pub fn get_index_of_program_account_in_transaction(
&self,
program_account_index: IndexOfAccount,
) -> Result<IndexOfAccount, InstructionError> {
Ok(*self
.program_accounts
.get(program_account_index as usize)
.ok_or(InstructionError::NotEnoughAccountKeys)?)
}
pub fn get_index_of_instruction_account_in_transaction(
&self,
instruction_account_index: IndexOfAccount,
) -> Result<IndexOfAccount, InstructionError> {
Ok(self
.instruction_accounts
.get(instruction_account_index as usize)
.ok_or(InstructionError::NotEnoughAccountKeys)?
.index_in_transaction as IndexOfAccount)
}
pub fn is_instruction_account_duplicate(
&self,
instruction_account_index: IndexOfAccount,
) -> Result<Option<IndexOfAccount>, InstructionError> {
let index_in_callee = self
.instruction_accounts
.get(instruction_account_index as usize)
.ok_or(InstructionError::NotEnoughAccountKeys)?
.index_in_callee;
Ok(if index_in_callee == instruction_account_index {
None
} else {
Some(index_in_callee)
})
}
pub fn get_last_program_key<'a, 'b: 'a>(
&'a self,
transaction_context: &'b TransactionContext,
) -> Result<&'b Pubkey, InstructionError> {
self.get_index_of_program_account_in_transaction(
self.get_number_of_program_accounts().saturating_sub(1),
)
.and_then(|index_in_transaction| {
transaction_context.get_key_of_account_at_index(index_in_transaction)
})
}
fn try_borrow_account<'a, 'b: 'a>(
&'a self,
transaction_context: &'b TransactionContext,
index_in_transaction: IndexOfAccount,
index_in_instruction: IndexOfAccount,
) -> Result<BorrowedAccount<'a>, InstructionError> {
let account = transaction_context
.accounts
.get(index_in_transaction)
.ok_or(InstructionError::MissingAccount)?
.try_borrow_mut()
.map_err(|_| InstructionError::AccountBorrowFailed)?;
Ok(BorrowedAccount {
transaction_context,
instruction_context: self,
index_in_transaction,
index_in_instruction,
account,
})
}
pub fn try_borrow_last_program_account<'a, 'b: 'a>(
&'a self,
transaction_context: &'b TransactionContext,
) -> Result<BorrowedAccount<'a>, InstructionError> {
let result = self.try_borrow_program_account(
transaction_context,
self.get_number_of_program_accounts().saturating_sub(1),
);
debug_assert!(result.is_ok());
result
}
pub fn try_borrow_program_account<'a, 'b: 'a>(
&'a self,
transaction_context: &'b TransactionContext,
program_account_index: IndexOfAccount,
) -> Result<BorrowedAccount<'a>, InstructionError> {
let index_in_transaction =
self.get_index_of_program_account_in_transaction(program_account_index)?;
self.try_borrow_account(
transaction_context,
index_in_transaction,
program_account_index,
)
}
pub fn try_borrow_instruction_account<'a, 'b: 'a>(
&'a self,
transaction_context: &'b TransactionContext,
instruction_account_index: IndexOfAccount,
) -> Result<BorrowedAccount<'a>, InstructionError> {
let index_in_transaction =
self.get_index_of_instruction_account_in_transaction(instruction_account_index)?;
self.try_borrow_account(
transaction_context,
index_in_transaction,
self.get_number_of_program_accounts()
.saturating_add(instruction_account_index),
)
}
pub fn is_instruction_account_signer(
&self,
instruction_account_index: IndexOfAccount,
) -> Result<bool, InstructionError> {
Ok(self
.instruction_accounts
.get(instruction_account_index as usize)
.ok_or(InstructionError::MissingAccount)?
.is_signer)
}
pub fn is_instruction_account_writable(
&self,
instruction_account_index: IndexOfAccount,
) -> Result<bool, InstructionError> {
Ok(self
.instruction_accounts
.get(instruction_account_index as usize)
.ok_or(InstructionError::MissingAccount)?
.is_writable)
}
pub fn get_signers(
&self,
transaction_context: &TransactionContext,
) -> Result<HashSet<Pubkey>, InstructionError> {
let mut result = HashSet::new();
for instruction_account in self.instruction_accounts.iter() {
if instruction_account.is_signer {
result.insert(
*transaction_context
.get_key_of_account_at_index(instruction_account.index_in_transaction)?,
);
}
}
Ok(result)
}
}
#[derive(Debug)]
pub struct BorrowedAccount<'a> {
transaction_context: &'a TransactionContext,
instruction_context: &'a InstructionContext,
index_in_transaction: IndexOfAccount,
index_in_instruction: IndexOfAccount,
account: RefMut<'a, AccountSharedData>,
}
impl<'a> BorrowedAccount<'a> {
pub fn transaction_context(&self) -> &TransactionContext {
self.transaction_context
}
#[inline]
pub fn get_index_in_transaction(&self) -> IndexOfAccount {
self.index_in_transaction
}
#[inline]
pub fn get_key(&self) -> &Pubkey {
self.transaction_context
.get_key_of_account_at_index(self.index_in_transaction)
.unwrap()
}
#[inline]
pub fn get_owner(&self) -> &Pubkey {
self.account.owner()
}
#[cfg(not(target_os = "solana"))]
pub fn set_owner(&mut self, pubkey: &[u8]) -> Result<(), InstructionError> {
if !self.is_owned_by_current_program() {
return Err(InstructionError::ModifiedProgramId);
}
if !self.is_writable() {
return Err(InstructionError::ModifiedProgramId);
}
if self.is_executable() {
return Err(InstructionError::ModifiedProgramId);
}
if !is_zeroed(self.get_data()) {
return Err(InstructionError::ModifiedProgramId);
}
if self.get_owner().to_bytes() == pubkey {
return Ok(());
}
self.touch()?;
self.account.copy_into_owner_from_slice(pubkey);
Ok(())
}
#[inline]
pub fn get_lamports(&self) -> u64 {
self.account.lamports()
}
#[cfg(not(target_os = "solana"))]
pub fn set_lamports(&mut self, lamports: u64) -> Result<(), InstructionError> {
if !self.is_owned_by_current_program() && lamports < self.get_lamports() {
return Err(InstructionError::ExternalAccountLamportSpend);
}
if !self.is_writable() {
return Err(InstructionError::ReadonlyLamportChange);
}
if self.is_executable() {
return Err(InstructionError::ExecutableLamportChange);
}
if self.get_lamports() == lamports {
return Ok(());
}
self.touch()?;
self.account.set_lamports(lamports);
Ok(())
}
#[cfg(not(target_os = "solana"))]
pub fn checked_add_lamports(&mut self, lamports: u64) -> Result<(), InstructionError> {
self.set_lamports(
self.get_lamports()
.checked_add(lamports)
.ok_or(InstructionError::ArithmeticOverflow)?,
)
}
#[cfg(not(target_os = "solana"))]
pub fn checked_sub_lamports(&mut self, lamports: u64) -> Result<(), InstructionError> {
self.set_lamports(
self.get_lamports()
.checked_sub(lamports)
.ok_or(InstructionError::ArithmeticOverflow)?,
)
}
#[inline]
pub fn get_data(&self) -> &[u8] {
self.account.data()
}
#[cfg(not(target_os = "solana"))]
pub fn get_data_mut(&mut self) -> Result<&mut [u8], InstructionError> {
self.can_data_be_changed()?;
self.touch()?;
self.make_data_mut();
Ok(self.account.data_as_mut_slice())
}
#[cfg(not(target_os = "solana"))]
pub fn spare_data_capacity_mut(&mut self) -> Result<&mut [MaybeUninit<u8>], InstructionError> {
debug_assert!(!self.account.is_shared());
Ok(self.account.spare_data_capacity_mut())
}
#[cfg(all(
not(target_os = "solana"),
any(test, feature = "dev-context-only-utils")
))]
pub fn set_data(&mut self, data: Vec<u8>) -> Result<(), InstructionError> {
self.can_data_be_resized(data.len())?;
self.can_data_be_changed()?;
self.touch()?;
self.update_accounts_resize_delta(data.len())?;
self.account.set_data(data);
Ok(())
}
#[cfg(not(target_os = "solana"))]
pub fn set_data_from_slice(&mut self, data: &[u8]) -> Result<(), InstructionError> {
self.can_data_be_resized(data.len())?;
self.can_data_be_changed()?;
self.touch()?;
self.update_accounts_resize_delta(data.len())?;
self.account.set_data_from_slice(data);
Ok(())
}
#[cfg(not(target_os = "solana"))]
pub fn set_data_length(&mut self, new_length: usize) -> Result<(), InstructionError> {
self.can_data_be_resized(new_length)?;
self.can_data_be_changed()?;
if self.get_data().len() == new_length {
return Ok(());
}
self.touch()?;
self.update_accounts_resize_delta(new_length)?;
self.account.resize(new_length, 0);
Ok(())
}
#[cfg(not(target_os = "solana"))]
pub fn extend_from_slice(&mut self, data: &[u8]) -> Result<(), InstructionError> {
let new_len = self.get_data().len().saturating_add(data.len());
self.can_data_be_resized(new_len)?;
self.can_data_be_changed()?;
if data.is_empty() {
return Ok(());
}
self.touch()?;
self.update_accounts_resize_delta(new_len)?;
self.make_data_mut();
self.account.extend_from_slice(data);
Ok(())
}
#[cfg(not(target_os = "solana"))]
pub fn reserve(&mut self, additional: usize) -> Result<(), InstructionError> {
self.make_data_mut();
self.account.reserve(additional);
Ok(())
}
#[cfg(not(target_os = "solana"))]
pub fn capacity(&self) -> usize {
self.account.capacity()
}
#[cfg(not(target_os = "solana"))]
pub fn is_shared(&self) -> bool {
self.account.is_shared()
}
#[cfg(not(target_os = "solana"))]
fn make_data_mut(&mut self) {
if self.account.is_shared() {
self.account.reserve(MAX_PERMITTED_DATA_INCREASE);
}
}
#[cfg(not(target_os = "solana"))]
pub fn get_state<T: serde::de::DeserializeOwned>(&self) -> Result<T, InstructionError> {
self.account
.deserialize_data()
.map_err(|_| InstructionError::InvalidAccountData)
}
#[cfg(not(target_os = "solana"))]
pub fn set_state<T: serde::Serialize>(&mut self, state: &T) -> Result<(), InstructionError> {
let data = self.get_data_mut()?;
let serialized_size =
bincode::serialized_size(state).map_err(|_| InstructionError::GenericError)?;
if serialized_size > data.len() as u64 {
return Err(InstructionError::AccountDataTooSmall);
}
bincode::serialize_into(&mut *data, state).map_err(|_| InstructionError::GenericError)?;
Ok(())
}
#[cfg(not(target_os = "solana"))]
pub fn is_rent_exempt_at_data_length(&self, data_length: usize) -> bool {
self.transaction_context
.rent
.is_exempt(self.get_lamports(), data_length)
}
#[inline]
pub fn is_executable(&self) -> bool {
self.account.executable()
}
#[cfg(not(target_os = "solana"))]
pub fn set_executable(&mut self, is_executable: bool) -> Result<(), InstructionError> {
if !self
.transaction_context
.rent
.is_exempt(self.get_lamports(), self.get_data().len())
{
return Err(InstructionError::ExecutableAccountNotRentExempt);
}
if !self.is_owned_by_current_program() {
return Err(InstructionError::ExecutableModified);
}
if !self.is_writable() {
return Err(InstructionError::ExecutableModified);
}
if self.is_executable() && !is_executable {
return Err(InstructionError::ExecutableModified);
}
if self.is_executable() == is_executable {
return Ok(());
}
self.touch()?;
self.account.set_executable(is_executable);
Ok(())
}
#[cfg(not(target_os = "solana"))]
#[inline]
pub fn get_rent_epoch(&self) -> u64 {
self.account.rent_epoch()
}
pub fn is_signer(&self) -> bool {
if self.index_in_instruction < self.instruction_context.get_number_of_program_accounts() {
return false;
}
self.instruction_context
.is_instruction_account_signer(
self.index_in_instruction
.saturating_sub(self.instruction_context.get_number_of_program_accounts()),
)
.unwrap_or_default()
}
pub fn is_writable(&self) -> bool {
if self.index_in_instruction < self.instruction_context.get_number_of_program_accounts() {
return false;
}
self.instruction_context
.is_instruction_account_writable(
self.index_in_instruction
.saturating_sub(self.instruction_context.get_number_of_program_accounts()),
)
.unwrap_or_default()
}
pub fn is_owned_by_current_program(&self) -> bool {
self.instruction_context
.get_last_program_key(self.transaction_context)
.map(|key| key == self.get_owner())
.unwrap_or_default()
}
#[cfg(not(target_os = "solana"))]
pub fn can_data_be_changed(&self) -> Result<(), InstructionError> {
if self.is_executable() {
return Err(InstructionError::ExecutableDataModified);
}
if !self.is_writable() {
return Err(InstructionError::ReadonlyDataModified);
}
if !self.is_owned_by_current_program() {
return Err(InstructionError::ExternalAccountDataModified);
}
Ok(())
}
#[cfg(not(target_os = "solana"))]
pub fn can_data_be_resized(&self, new_length: usize) -> Result<(), InstructionError> {
let old_length = self.get_data().len();
if new_length != old_length && !self.is_owned_by_current_program() {
return Err(InstructionError::AccountDataSizeChanged);
}
if new_length > MAX_PERMITTED_DATA_LENGTH as usize {
return Err(InstructionError::InvalidRealloc);
}
let length_delta = (new_length as i64).saturating_sub(old_length as i64);
if self
.transaction_context
.accounts_resize_delta()?
.saturating_add(length_delta)
> MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION
{
return Err(InstructionError::MaxAccountsDataAllocationsExceeded);
}
Ok(())
}
#[cfg(not(target_os = "solana"))]
fn touch(&self) -> Result<(), InstructionError> {
self.transaction_context
.accounts()
.touch(self.index_in_transaction)
}
#[cfg(not(target_os = "solana"))]
fn update_accounts_resize_delta(&mut self, new_len: usize) -> Result<(), InstructionError> {
let mut accounts_resize_delta = self
.transaction_context
.accounts_resize_delta
.try_borrow_mut()
.map_err(|_| InstructionError::GenericError)?;
*accounts_resize_delta = accounts_resize_delta
.saturating_add((new_len as i64).saturating_sub(self.get_data().len() as i64));
Ok(())
}
}
#[cfg(not(target_os = "solana"))]
pub struct ExecutionRecord {
pub accounts: Vec<TransactionAccount>,
pub return_data: TransactionReturnData,
pub touched_account_count: u64,
pub accounts_resize_delta: i64,
}
#[cfg(not(target_os = "solana"))]
impl From<TransactionContext> for ExecutionRecord {
fn from(context: TransactionContext) -> Self {
let accounts = Rc::try_unwrap(context.accounts)
.expect("transaction_context.accounts has unexpected outstanding refs");
let touched_account_count = accounts.touched_count() as u64;
let accounts = accounts.into_accounts();
Self {
accounts: Vec::from(Pin::into_inner(context.account_keys))
.into_iter()
.zip(accounts)
.collect(),
return_data: context.return_data,
touched_account_count,
accounts_resize_delta: RefCell::into_inner(context.accounts_resize_delta),
}
}
}
#[cfg(not(target_os = "solana"))]
fn is_zeroed(buf: &[u8]) -> bool {
const ZEROS_LEN: usize = 1024;
const ZEROS: [u8; ZEROS_LEN] = [0; ZEROS_LEN];
let mut chunks = buf.chunks_exact(ZEROS_LEN);
#[allow(clippy::indexing_slicing)]
{
chunks.all(|chunk| chunk == &ZEROS[..])
&& chunks.remainder() == &ZEROS[..chunks.remainder().len()]
}
}