use crate::{
debug::{CallSpan, Tracing},
gas::GasMeter,
storage::{self, meter::Diff, WriteOutcome},
BalanceOf, CodeHash, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf,
DebugBufferVec, Determinism, Error, Event, Nonce, Origin, Pallet as Contracts, Schedule,
WasmBlob, LOG_TARGET,
};
use frame_support::{
crypto::ecdsa::ECDSAExt,
dispatch::{
fmt::Debug, DispatchError, DispatchResult, DispatchResultWithPostInfo, Dispatchable,
},
ensure,
storage::{with_transaction, TransactionOutcome},
traits::{
fungible::{Inspect, Mutate},
tokens::Preservation,
Contains, OriginTrait, Randomness, Time,
},
weights::Weight,
Blake2_128Concat, BoundedVec, StorageHasher,
};
use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin};
use pallet_contracts_primitives::{ExecReturnValue, StorageDeposit};
use smallvec::{Array, SmallVec};
use sp_core::{
ecdsa::Public as ECDSAPublic,
sr25519::{Public as SR25519Public, Signature as SR25519Signature},
Get,
};
use sp_io::{crypto::secp256k1_ecdsa_recover_compressed, hashing::blake2_256};
use sp_runtime::traits::{Convert, Hash, Zero};
use sp_std::{marker::PhantomData, mem, prelude::*, vec::Vec};
pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
pub type MomentOf<T> = <<T as Config>::Time as Time>::Moment;
pub type SeedOf<T> = <T as frame_system::Config>::Hash;
pub type ExecResult = Result<ExecReturnValue, ExecError>;
pub type TopicOf<T> = <T as frame_system::Config>::Hash;
type VarSizedKey<T> = BoundedVec<u8, <T as Config>::MaxStorageKeyLen>;
pub enum Key<T: Config> {
Fix([u8; 32]),
Var(VarSizedKey<T>),
}
impl<T: Config> Key<T> {
pub fn to_vec(&self) -> Vec<u8> {
match self {
Key::Fix(v) => v.to_vec(),
Key::Var(v) => v.to_vec(),
}
}
pub fn hash(&self) -> Vec<u8> {
match self {
Key::Fix(v) => blake2_256(v.as_slice()).to_vec(),
Key::Var(v) => Blake2_128Concat::hash(v.as_slice()),
}
}
pub fn try_from_fix(v: Vec<u8>) -> Result<Self, Vec<u8>> {
<[u8; 32]>::try_from(v).map(Self::Fix)
}
pub fn try_from_var(v: Vec<u8>) -> Result<Self, Vec<u8>> {
VarSizedKey::<T>::try_from(v).map(Self::Var)
}
}
#[cfg_attr(test, derive(Debug, PartialEq))]
pub enum ErrorOrigin {
Caller,
Callee,
}
#[cfg_attr(test, derive(Debug, PartialEq))]
pub struct ExecError {
pub error: DispatchError,
pub origin: ErrorOrigin,
}
impl<T: Into<DispatchError>> From<T> for ExecError {
fn from(error: T) -> Self {
Self { error: error.into(), origin: ErrorOrigin::Caller }
}
}
pub trait Ext: sealing::Sealed {
type T: Config;
fn call(
&mut self,
gas_limit: Weight,
deposit_limit: BalanceOf<Self::T>,
to: AccountIdOf<Self::T>,
value: BalanceOf<Self::T>,
input_data: Vec<u8>,
allows_reentry: bool,
) -> Result<ExecReturnValue, ExecError>;
fn delegate_call(
&mut self,
code: CodeHash<Self::T>,
input_data: Vec<u8>,
) -> Result<ExecReturnValue, ExecError>;
fn instantiate(
&mut self,
gas_limit: Weight,
deposit_limit: BalanceOf<Self::T>,
code: CodeHash<Self::T>,
value: BalanceOf<Self::T>,
input_data: Vec<u8>,
salt: &[u8],
) -> Result<(AccountIdOf<Self::T>, ExecReturnValue), ExecError>;
fn terminate(&mut self, beneficiary: &AccountIdOf<Self::T>) -> Result<(), DispatchError>;
fn transfer(&mut self, to: &AccountIdOf<Self::T>, value: BalanceOf<Self::T>) -> DispatchResult;
fn get_storage(&mut self, key: &Key<Self::T>) -> Option<Vec<u8>>;
fn get_storage_size(&mut self, key: &Key<Self::T>) -> Option<u32>;
fn set_storage(
&mut self,
key: &Key<Self::T>,
value: Option<Vec<u8>>,
take_old: bool,
) -> Result<WriteOutcome, DispatchError>;
fn caller(&self) -> Origin<Self::T>;
fn is_contract(&self, address: &AccountIdOf<Self::T>) -> bool;
fn code_hash(&self, address: &AccountIdOf<Self::T>) -> Option<CodeHash<Self::T>>;
fn own_code_hash(&mut self) -> &CodeHash<Self::T>;
fn caller_is_origin(&self) -> bool;
fn caller_is_root(&self) -> bool;
fn address(&self) -> &AccountIdOf<Self::T>;
fn balance(&self) -> BalanceOf<Self::T>;
fn value_transferred(&self) -> BalanceOf<Self::T>;
fn now(&self) -> &MomentOf<Self::T>;
fn minimum_balance(&self) -> BalanceOf<Self::T>;
fn random(&self, subject: &[u8]) -> (SeedOf<Self::T>, BlockNumberFor<Self::T>);
fn deposit_event(&mut self, topics: Vec<TopicOf<Self::T>>, data: Vec<u8>);
fn block_number(&self) -> BlockNumberFor<Self::T>;
fn max_value_size(&self) -> u32;
fn get_weight_price(&self, weight: Weight) -> BalanceOf<Self::T>;
fn schedule(&self) -> &Schedule<Self::T>;
fn gas_meter(&self) -> &GasMeter<Self::T>;
fn gas_meter_mut(&mut self) -> &mut GasMeter<Self::T>;
fn charge_storage(&mut self, diff: &Diff);
fn append_debug_buffer(&mut self, msg: &str) -> bool;
fn call_runtime(&self, call: <Self::T as Config>::RuntimeCall) -> DispatchResultWithPostInfo;
fn ecdsa_recover(&self, signature: &[u8; 65], message_hash: &[u8; 32]) -> Result<[u8; 33], ()>;
fn sr25519_verify(&self, signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> bool;
fn ecdsa_to_eth_address(&self, pk: &[u8; 33]) -> Result<[u8; 20], ()>;
#[cfg(test)]
fn contract_info(&mut self) -> &mut ContractInfo<Self::T>;
fn set_code_hash(&mut self, hash: CodeHash<Self::T>) -> Result<(), DispatchError>;
fn reentrance_count(&self) -> u32;
fn account_reentrance_count(&self, account_id: &AccountIdOf<Self::T>) -> u32;
fn nonce(&mut self) -> u64;
fn add_delegate_dependency(
&mut self,
code_hash: CodeHash<Self::T>,
) -> Result<(), DispatchError>;
fn remove_delegate_dependency(
&mut self,
code_hash: &CodeHash<Self::T>,
) -> Result<(), DispatchError>;
}
#[derive(
Copy,
Clone,
PartialEq,
Eq,
sp_core::RuntimeDebug,
codec::Decode,
codec::Encode,
codec::MaxEncodedLen,
scale_info::TypeInfo,
)]
pub enum ExportedFunction {
Constructor,
Call,
}
pub trait Executable<T: Config>: Sized {
fn from_storage(
code_hash: CodeHash<T>,
gas_meter: &mut GasMeter<T>,
) -> Result<Self, DispatchError>;
fn increment_refcount(code_hash: CodeHash<T>) -> Result<(), DispatchError>;
fn decrement_refcount(code_hash: CodeHash<T>);
fn execute<E: Ext<T = T>>(
self,
ext: &mut E,
function: &ExportedFunction,
input_data: Vec<u8>,
) -> ExecResult;
fn code_info(&self) -> &CodeInfo<T>;
fn code_hash(&self) -> &CodeHash<T>;
fn code_len(&self) -> u32;
fn is_deterministic(&self) -> bool;
}
pub struct Stack<'a, T: Config, E> {
origin: Origin<T>,
schedule: &'a Schedule<T>,
gas_meter: &'a mut GasMeter<T>,
storage_meter: &'a mut storage::meter::Meter<T>,
timestamp: MomentOf<T>,
block_number: BlockNumberFor<T>,
nonce: Option<u64>,
frames: SmallVec<T::CallStack>,
first_frame: Frame<T>,
debug_message: Option<&'a mut DebugBufferVec<T>>,
determinism: Determinism,
_phantom: PhantomData<E>,
}
pub struct Frame<T: Config> {
account_id: T::AccountId,
contract_info: CachedContract<T>,
value_transferred: BalanceOf<T>,
entry_point: ExportedFunction,
nested_gas: GasMeter<T>,
nested_storage: storage::meter::NestedMeter<T>,
allows_reentry: bool,
delegate_caller: Option<Origin<T>>,
}
struct DelegatedCall<T: Config, E> {
executable: E,
caller: Origin<T>,
}
enum FrameArgs<'a, T: Config, E> {
Call {
dest: T::AccountId,
cached_info: Option<ContractInfo<T>>,
delegated_call: Option<DelegatedCall<T, E>>,
},
Instantiate {
sender: T::AccountId,
nonce: u64,
executable: E,
salt: &'a [u8],
input_data: &'a [u8],
},
}
enum CachedContract<T: Config> {
Cached(ContractInfo<T>),
Invalidated,
Terminated,
}
impl<T: Config> CachedContract<T> {
fn into_contract(self) -> Option<ContractInfo<T>> {
if let CachedContract::Cached(contract) = self {
Some(contract)
} else {
None
}
}
fn as_contract(&mut self) -> Option<&mut ContractInfo<T>> {
if let CachedContract::Cached(contract) = self {
Some(contract)
} else {
None
}
}
}
impl<T: Config> Frame<T> {
fn contract_info(&mut self) -> &mut ContractInfo<T> {
self.contract_info.get(&self.account_id)
}
fn terminate(&mut self) -> ContractInfo<T> {
self.contract_info.terminate(&self.account_id)
}
}
macro_rules! get_cached_or_panic_after_load {
($c:expr) => {{
if let CachedContract::Cached(contract) = $c {
contract
} else {
panic!(
"It is impossible to remove a contract that is on the call stack;\
See implementations of terminate;\
Therefore fetching a contract will never fail while using an account id
that is currently active on the call stack;\
qed"
);
}
}};
}
macro_rules! top_frame {
($stack:expr) => {
$stack.frames.last().unwrap_or(&$stack.first_frame)
};
}
macro_rules! top_frame_mut {
($stack:expr) => {
$stack.frames.last_mut().unwrap_or(&mut $stack.first_frame)
};
}
impl<T: Config> CachedContract<T> {
fn load(&mut self, account_id: &T::AccountId) {
if let CachedContract::Invalidated = self {
let contract = <ContractInfoOf<T>>::get(&account_id);
if let Some(contract) = contract {
*self = CachedContract::Cached(contract);
}
}
}
fn get(&mut self, account_id: &T::AccountId) -> &mut ContractInfo<T> {
self.load(account_id);
get_cached_or_panic_after_load!(self)
}
fn terminate(&mut self, account_id: &T::AccountId) -> ContractInfo<T> {
self.load(account_id);
get_cached_or_panic_after_load!(mem::replace(self, Self::Terminated))
}
}
impl<'a, T, E> Stack<'a, T, E>
where
T: Config,
E: Executable<T>,
{
pub fn run_call(
origin: Origin<T>,
dest: T::AccountId,
gas_meter: &'a mut GasMeter<T>,
storage_meter: &'a mut storage::meter::Meter<T>,
schedule: &'a Schedule<T>,
value: BalanceOf<T>,
input_data: Vec<u8>,
debug_message: Option<&'a mut DebugBufferVec<T>>,
determinism: Determinism,
) -> Result<ExecReturnValue, ExecError> {
let (mut stack, executable) = Self::new(
FrameArgs::Call { dest, cached_info: None, delegated_call: None },
origin,
gas_meter,
storage_meter,
schedule,
value,
debug_message,
determinism,
)?;
stack.run(executable, input_data)
}
pub fn run_instantiate(
origin: T::AccountId,
executable: E,
gas_meter: &'a mut GasMeter<T>,
storage_meter: &'a mut storage::meter::Meter<T>,
schedule: &'a Schedule<T>,
value: BalanceOf<T>,
input_data: Vec<u8>,
salt: &[u8],
debug_message: Option<&'a mut DebugBufferVec<T>>,
) -> Result<(T::AccountId, ExecReturnValue), ExecError> {
let (mut stack, executable) = Self::new(
FrameArgs::Instantiate {
sender: origin.clone(),
nonce: <Nonce<T>>::get().wrapping_add(1),
executable,
salt,
input_data: input_data.as_ref(),
},
Origin::from_account_id(origin),
gas_meter,
storage_meter,
schedule,
value,
debug_message,
Determinism::Enforced,
)?;
let account_id = stack.top_frame().account_id.clone();
stack.run(executable, input_data).map(|ret| (account_id, ret))
}
fn new(
args: FrameArgs<T, E>,
origin: Origin<T>,
gas_meter: &'a mut GasMeter<T>,
storage_meter: &'a mut storage::meter::Meter<T>,
schedule: &'a Schedule<T>,
value: BalanceOf<T>,
debug_message: Option<&'a mut DebugBufferVec<T>>,
determinism: Determinism,
) -> Result<(Self, E), ExecError> {
let (first_frame, executable, nonce) = Self::new_frame(
args,
value,
gas_meter,
Weight::zero(),
storage_meter,
BalanceOf::<T>::zero(),
determinism,
)?;
let stack = Self {
origin,
schedule,
gas_meter,
storage_meter,
timestamp: T::Time::now(),
block_number: <frame_system::Pallet<T>>::block_number(),
nonce,
first_frame,
frames: Default::default(),
debug_message,
determinism,
_phantom: Default::default(),
};
Ok((stack, executable))
}
fn new_frame<S: storage::meter::State + Default + Debug>(
frame_args: FrameArgs<T, E>,
value_transferred: BalanceOf<T>,
gas_meter: &mut GasMeter<T>,
gas_limit: Weight,
storage_meter: &mut storage::meter::GenericMeter<T, S>,
deposit_limit: BalanceOf<T>,
determinism: Determinism,
) -> Result<(Frame<T>, E, Option<u64>), ExecError> {
let (account_id, contract_info, executable, delegate_caller, entry_point, nonce) =
match frame_args {
FrameArgs::Call { dest, cached_info, delegated_call } => {
let contract = if let Some(contract) = cached_info {
contract
} else {
<ContractInfoOf<T>>::get(&dest).ok_or(<Error<T>>::ContractNotFound)?
};
let (executable, delegate_caller) =
if let Some(DelegatedCall { executable, caller }) = delegated_call {
(executable, Some(caller))
} else {
(E::from_storage(contract.code_hash, gas_meter)?, None)
};
(dest, contract, executable, delegate_caller, ExportedFunction::Call, None)
},
FrameArgs::Instantiate { sender, nonce, executable, salt, input_data } => {
let account_id = Contracts::<T>::contract_address(
&sender,
&executable.code_hash(),
input_data,
salt,
);
let contract = ContractInfo::new(&account_id, nonce, *executable.code_hash())?;
(
account_id,
contract,
executable,
None,
ExportedFunction::Constructor,
Some(nonce),
)
},
};
if !(executable.is_deterministic() ||
(matches!(determinism, Determinism::Relaxed) &&
matches!(entry_point, ExportedFunction::Call)))
{
return Err(Error::<T>::Indeterministic.into())
}
let frame = Frame {
delegate_caller,
value_transferred,
contract_info: CachedContract::Cached(contract_info),
account_id,
entry_point,
nested_gas: gas_meter.nested(gas_limit)?,
nested_storage: storage_meter.nested(deposit_limit),
allows_reentry: true,
};
Ok((frame, executable, nonce))
}
fn push_frame(
&mut self,
frame_args: FrameArgs<T, E>,
value_transferred: BalanceOf<T>,
gas_limit: Weight,
deposit_limit: BalanceOf<T>,
) -> Result<E, ExecError> {
if self.frames.len() == T::CallStack::size() {
return Err(Error::<T>::MaxCallDepthReached.into())
}
let frame = self.top_frame();
if let (CachedContract::Cached(contract), ExportedFunction::Call) =
(&frame.contract_info, frame.entry_point)
{
<ContractInfoOf<T>>::insert(frame.account_id.clone(), contract.clone());
}
let frame = top_frame_mut!(self);
let nested_gas = &mut frame.nested_gas;
let nested_storage = &mut frame.nested_storage;
let (frame, executable, _) = Self::new_frame(
frame_args,
value_transferred,
nested_gas,
gas_limit,
nested_storage,
deposit_limit,
self.determinism,
)?;
self.frames.push(frame);
Ok(executable)
}
fn run(&mut self, executable: E, input_data: Vec<u8>) -> Result<ExecReturnValue, ExecError> {
let frame = self.top_frame();
let entry_point = frame.entry_point;
let delegated_code_hash =
if frame.delegate_caller.is_some() { Some(*executable.code_hash()) } else { None };
let do_transaction = || {
if entry_point == ExportedFunction::Constructor {
let origin = &self.origin.account_id()?;
let frame = top_frame_mut!(self);
frame.nested_storage.charge_instantiate(
origin,
&frame.account_id,
frame.contract_info.get(&frame.account_id),
executable.code_info(),
)?;
}
self.initial_transfer()?;
let call_span =
T::Debug::new_call_span(executable.code_hash(), entry_point, &input_data);
let output = executable
.execute(self, &entry_point, input_data)
.map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?;
call_span.after_call(&output);
if output.did_revert() {
return Ok(output)
}
if self.frames.is_empty() {
let frame = &mut self.first_frame;
frame.contract_info.load(&frame.account_id);
let contract = frame.contract_info.as_contract();
frame.nested_storage.enforce_limit(contract)?;
}
let frame = self.top_frame();
let account_id = &frame.account_id.clone();
match (entry_point, delegated_code_hash) {
(ExportedFunction::Constructor, _) => {
if matches!(frame.contract_info, CachedContract::Terminated) {
return Err(Error::<T>::TerminatedInConstructor.into())
}
let frame = self.top_frame_mut();
let contract = frame.contract_info.as_contract();
frame.nested_storage.enforce_subcall_limit(contract)?;
let caller = self.caller().account_id()?.clone();
Contracts::<T>::deposit_event(
vec![T::Hashing::hash_of(&caller), T::Hashing::hash_of(account_id)],
Event::Instantiated { deployer: caller, contract: account_id.clone() },
);
},
(ExportedFunction::Call, Some(code_hash)) => {
Contracts::<T>::deposit_event(
vec![T::Hashing::hash_of(account_id), T::Hashing::hash_of(&code_hash)],
Event::DelegateCalled { contract: account_id.clone(), code_hash },
);
},
(ExportedFunction::Call, None) => {
let frame = self.top_frame_mut();
let contract = frame.contract_info.as_contract();
frame.nested_storage.enforce_subcall_limit(contract)?;
let caller = self.caller();
Contracts::<T>::deposit_event(
vec![T::Hashing::hash_of(&caller), T::Hashing::hash_of(&account_id)],
Event::Called { caller: caller.clone(), contract: account_id.clone() },
);
},
}
Ok(output)
};
let transaction_outcome =
with_transaction(|| -> TransactionOutcome<Result<_, DispatchError>> {
let output = do_transaction();
match &output {
Ok(result) if !result.did_revert() =>
TransactionOutcome::Commit(Ok((true, output))),
_ => TransactionOutcome::Rollback(Ok((false, output))),
}
});
let (success, output) = match transaction_outcome {
Ok((success, output)) => (success, output),
Err(error) => (false, Err(error.into())),
};
self.pop_frame(success);
output
}
fn pop_frame(&mut self, persist: bool) {
if !persist && self.top_frame().entry_point == ExportedFunction::Constructor {
self.nonce.as_mut().map(|c| *c = c.wrapping_sub(1));
}
let frame = self.frames.pop();
if let Some(mut frame) = frame {
let account_id = &frame.account_id;
let prev = top_frame_mut!(self);
prev.nested_gas.absorb_nested(frame.nested_gas);
if !persist {
return
}
frame.contract_info.load(account_id);
let mut contract = frame.contract_info.into_contract();
prev.nested_storage.absorb(frame.nested_storage, account_id, contract.as_mut());
if let Some(contract) = contract {
if prev.account_id == *account_id {
prev.contract_info = CachedContract::Cached(contract);
return
}
<ContractInfoOf<T>>::insert(account_id, contract);
if let Some(c) = self.frames_mut().skip(1).find(|f| f.account_id == *account_id) {
c.contract_info = CachedContract::Invalidated;
}
}
} else {
if let Some((msg, false)) = self.debug_message.as_ref().map(|m| (m, m.is_empty())) {
log::debug!(
target: LOG_TARGET,
"Execution finished with debug buffer: {}",
core::str::from_utf8(msg).unwrap_or("<Invalid UTF8>"),
);
}
self.gas_meter.absorb_nested(mem::take(&mut self.first_frame.nested_gas));
if !persist {
return
}
let mut contract = self.first_frame.contract_info.as_contract();
self.storage_meter.absorb(
mem::take(&mut self.first_frame.nested_storage),
&self.first_frame.account_id,
contract.as_deref_mut(),
);
if let Some(contract) = contract {
<ContractInfoOf<T>>::insert(&self.first_frame.account_id, contract);
}
if let Some(nonce) = self.nonce {
<Nonce<T>>::set(nonce);
}
}
}
fn transfer(
preservation: Preservation,
from: &T::AccountId,
to: &T::AccountId,
value: BalanceOf<T>,
) -> DispatchResult {
if !value.is_zero() && from != to {
T::Currency::transfer(from, to, value, preservation)
.map_err(|_| Error::<T>::TransferFailed)?;
}
Ok(())
}
fn initial_transfer(&self) -> DispatchResult {
let frame = self.top_frame();
if frame.delegate_caller.is_some() {
return Ok(())
}
let value = frame.value_transferred;
let caller = match self.caller() {
Origin::Signed(caller) => caller,
Origin::Root if value.is_zero() => return Ok(()),
Origin::Root => return DispatchError::RootNotAllowed.into(),
};
Self::transfer(Preservation::Preserve, &caller, &frame.account_id, value)
}
fn top_frame(&self) -> &Frame<T> {
top_frame!(self)
}
fn top_frame_mut(&mut self) -> &mut Frame<T> {
top_frame_mut!(self)
}
fn frames(&self) -> impl Iterator<Item = &Frame<T>> {
sp_std::iter::once(&self.first_frame).chain(&self.frames).rev()
}
fn frames_mut(&mut self) -> impl Iterator<Item = &mut Frame<T>> {
sp_std::iter::once(&mut self.first_frame).chain(&mut self.frames).rev()
}
fn is_recursive(&self) -> bool {
let account_id = &self.top_frame().account_id;
self.frames().skip(1).any(|f| &f.account_id == account_id)
}
fn allows_reentry(&self, id: &AccountIdOf<T>) -> bool {
!self.frames().any(|f| &f.account_id == id && !f.allows_reentry)
}
fn next_nonce(&mut self) -> u64 {
let next = self.nonce().wrapping_add(1);
self.nonce = Some(next);
next
}
}
impl<'a, T, E> Ext for Stack<'a, T, E>
where
T: Config,
E: Executable<T>,
{
type T = T;
fn call(
&mut self,
gas_limit: Weight,
deposit_limit: BalanceOf<T>,
to: T::AccountId,
value: BalanceOf<T>,
input_data: Vec<u8>,
allows_reentry: bool,
) -> Result<ExecReturnValue, ExecError> {
self.top_frame_mut().allows_reentry = allows_reentry;
let try_call = || {
if !self.allows_reentry(&to) {
return Err(<Error<T>>::ReentranceDenied.into())
}
let cached_info = self
.frames()
.find(|f| f.entry_point == ExportedFunction::Call && f.account_id == to)
.and_then(|f| match &f.contract_info {
CachedContract::Cached(contract) => Some(contract.clone()),
_ => None,
});
let executable = self.push_frame(
FrameArgs::Call { dest: to, cached_info, delegated_call: None },
value,
gas_limit,
deposit_limit,
)?;
self.run(executable, input_data)
};
let result = try_call();
self.top_frame_mut().allows_reentry = true;
result
}
fn delegate_call(
&mut self,
code_hash: CodeHash<Self::T>,
input_data: Vec<u8>,
) -> Result<ExecReturnValue, ExecError> {
let executable = E::from_storage(code_hash, self.gas_meter_mut())?;
let top_frame = self.top_frame_mut();
let contract_info = top_frame.contract_info().clone();
let account_id = top_frame.account_id.clone();
let value = top_frame.value_transferred;
let executable = self.push_frame(
FrameArgs::Call {
dest: account_id,
cached_info: Some(contract_info),
delegated_call: Some(DelegatedCall { executable, caller: self.caller().clone() }),
},
value,
Weight::zero(),
BalanceOf::<T>::zero(),
)?;
self.run(executable, input_data)
}
fn instantiate(
&mut self,
gas_limit: Weight,
deposit_limit: BalanceOf<Self::T>,
code_hash: CodeHash<T>,
value: BalanceOf<T>,
input_data: Vec<u8>,
salt: &[u8],
) -> Result<(AccountIdOf<T>, ExecReturnValue), ExecError> {
let executable = E::from_storage(code_hash, self.gas_meter_mut())?;
let nonce = self.next_nonce();
let executable = self.push_frame(
FrameArgs::Instantiate {
sender: self.top_frame().account_id.clone(),
nonce,
executable,
salt,
input_data: input_data.as_ref(),
},
value,
gas_limit,
deposit_limit,
)?;
let account_id = self.top_frame().account_id.clone();
self.run(executable, input_data).map(|ret| (account_id, ret))
}
fn terminate(&mut self, beneficiary: &AccountIdOf<Self::T>) -> Result<(), DispatchError> {
if self.is_recursive() {
return Err(Error::<T>::TerminatedWhileReentrant.into())
}
let frame = self.top_frame_mut();
let info = frame.terminate();
frame.nested_storage.terminate(&info, beneficiary.clone());
info.queue_trie_for_deletion();
ContractInfoOf::<T>::remove(&frame.account_id);
E::decrement_refcount(info.code_hash);
for (code_hash, deposit) in info.delegate_dependencies() {
E::decrement_refcount(*code_hash);
frame
.nested_storage
.charge_deposit(frame.account_id.clone(), StorageDeposit::Refund(*deposit));
}
Contracts::<T>::deposit_event(
vec![T::Hashing::hash_of(&frame.account_id), T::Hashing::hash_of(&beneficiary)],
Event::Terminated {
contract: frame.account_id.clone(),
beneficiary: beneficiary.clone(),
},
);
Ok(())
}
fn transfer(&mut self, to: &T::AccountId, value: BalanceOf<T>) -> DispatchResult {
Self::transfer(Preservation::Preserve, &self.top_frame().account_id, to, value)
}
fn get_storage(&mut self, key: &Key<T>) -> Option<Vec<u8>> {
self.top_frame_mut().contract_info().read(key)
}
fn get_storage_size(&mut self, key: &Key<T>) -> Option<u32> {
self.top_frame_mut().contract_info().size(key.into())
}
fn set_storage(
&mut self,
key: &Key<T>,
value: Option<Vec<u8>>,
take_old: bool,
) -> Result<WriteOutcome, DispatchError> {
let frame = self.top_frame_mut();
frame.contract_info.get(&frame.account_id).write(
key.into(),
value,
Some(&mut frame.nested_storage),
take_old,
)
}
fn address(&self) -> &T::AccountId {
&self.top_frame().account_id
}
fn caller(&self) -> Origin<T> {
if let Some(caller) = &self.top_frame().delegate_caller {
caller.clone()
} else {
self.frames()
.nth(1)
.map(|f| Origin::from_account_id(f.account_id.clone()))
.unwrap_or(self.origin.clone())
}
}
fn is_contract(&self, address: &T::AccountId) -> bool {
ContractInfoOf::<T>::contains_key(&address)
}
fn code_hash(&self, address: &T::AccountId) -> Option<CodeHash<Self::T>> {
<ContractInfoOf<T>>::get(&address).map(|contract| contract.code_hash)
}
fn own_code_hash(&mut self) -> &CodeHash<Self::T> {
&self.top_frame_mut().contract_info().code_hash
}
fn caller_is_origin(&self) -> bool {
self.origin == self.caller()
}
fn caller_is_root(&self) -> bool {
self.caller_is_origin() && self.origin == Origin::Root
}
fn balance(&self) -> BalanceOf<T> {
T::Currency::balance(&self.top_frame().account_id)
}
fn value_transferred(&self) -> BalanceOf<T> {
self.top_frame().value_transferred
}
fn random(&self, subject: &[u8]) -> (SeedOf<T>, BlockNumberFor<T>) {
T::Randomness::random(subject)
}
fn now(&self) -> &MomentOf<T> {
&self.timestamp
}
fn minimum_balance(&self) -> BalanceOf<T> {
T::Currency::minimum_balance()
}
fn deposit_event(&mut self, topics: Vec<T::Hash>, data: Vec<u8>) {
Contracts::<Self::T>::deposit_event(
topics,
Event::ContractEmitted { contract: self.top_frame().account_id.clone(), data },
);
}
fn block_number(&self) -> BlockNumberFor<T> {
self.block_number
}
fn max_value_size(&self) -> u32 {
self.schedule.limits.payload_len
}
fn get_weight_price(&self, weight: Weight) -> BalanceOf<Self::T> {
T::WeightPrice::convert(weight)
}
fn schedule(&self) -> &Schedule<Self::T> {
self.schedule
}
fn gas_meter(&self) -> &GasMeter<Self::T> {
&self.top_frame().nested_gas
}
fn gas_meter_mut(&mut self) -> &mut GasMeter<Self::T> {
&mut self.top_frame_mut().nested_gas
}
fn charge_storage(&mut self, diff: &Diff) {
self.top_frame_mut().nested_storage.charge(diff)
}
fn append_debug_buffer(&mut self, msg: &str) -> bool {
if let Some(buffer) = &mut self.debug_message {
buffer
.try_extend(&mut msg.bytes())
.map_err(|_| {
log::debug!(
target: LOG_TARGET,
"Debug buffer (of {} bytes) exhausted!",
DebugBufferVec::<T>::bound(),
)
})
.ok();
true
} else {
false
}
}
fn call_runtime(&self, call: <Self::T as Config>::RuntimeCall) -> DispatchResultWithPostInfo {
let mut origin: T::RuntimeOrigin = RawOrigin::Signed(self.address().clone()).into();
origin.add_filter(T::CallFilter::contains);
call.dispatch(origin)
}
fn ecdsa_recover(&self, signature: &[u8; 65], message_hash: &[u8; 32]) -> Result<[u8; 33], ()> {
secp256k1_ecdsa_recover_compressed(signature, message_hash).map_err(|_| ())
}
fn sr25519_verify(&self, signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> bool {
sp_io::crypto::sr25519_verify(
&SR25519Signature(*signature),
message,
&SR25519Public(*pub_key),
)
}
fn ecdsa_to_eth_address(&self, pk: &[u8; 33]) -> Result<[u8; 20], ()> {
ECDSAPublic(*pk).to_eth_address()
}
#[cfg(test)]
fn contract_info(&mut self) -> &mut ContractInfo<Self::T> {
self.top_frame_mut().contract_info()
}
fn set_code_hash(&mut self, hash: CodeHash<Self::T>) -> Result<(), DispatchError> {
let frame = top_frame_mut!(self);
if !E::from_storage(hash, &mut frame.nested_gas)?.is_deterministic() {
return Err(<Error<T>>::Indeterministic.into())
}
let info = frame.contract_info();
let prev_hash = info.code_hash;
info.code_hash = hash;
let code_info = CodeInfoOf::<T>::get(hash).ok_or(Error::<T>::CodeNotFound)?;
let old_base_deposit = info.storage_base_deposit();
let new_base_deposit = info.update_base_deposit(&code_info);
let deposit = StorageDeposit::Charge(new_base_deposit)
.saturating_sub(&StorageDeposit::Charge(old_base_deposit));
frame.nested_storage.charge_deposit(frame.account_id.clone(), deposit);
E::increment_refcount(hash)?;
E::decrement_refcount(prev_hash);
Contracts::<Self::T>::deposit_event(
vec![T::Hashing::hash_of(&frame.account_id), hash, prev_hash],
Event::ContractCodeUpdated {
contract: frame.account_id.clone(),
new_code_hash: hash,
old_code_hash: prev_hash,
},
);
Ok(())
}
fn reentrance_count(&self) -> u32 {
let id: &AccountIdOf<Self::T> = &self.top_frame().account_id;
self.account_reentrance_count(id).saturating_sub(1)
}
fn account_reentrance_count(&self, account_id: &AccountIdOf<Self::T>) -> u32 {
self.frames()
.filter(|f| f.delegate_caller.is_none() && &f.account_id == account_id)
.count() as u32
}
fn nonce(&mut self) -> u64 {
if let Some(current) = self.nonce {
current
} else {
let current = <Nonce<T>>::get();
self.nonce = Some(current);
current
}
}
fn add_delegate_dependency(
&mut self,
code_hash: CodeHash<Self::T>,
) -> Result<(), DispatchError> {
let frame = self.top_frame_mut();
let info = frame.contract_info.get(&frame.account_id);
ensure!(code_hash != info.code_hash, Error::<T>::CannotAddSelfAsDelegateDependency);
let code_info = CodeInfoOf::<T>::get(code_hash).ok_or(Error::<T>::CodeNotFound)?;
let deposit = T::CodeHashLockupDepositPercent::get().mul_ceil(code_info.deposit());
info.add_delegate_dependency(code_hash, deposit)?;
<WasmBlob<T>>::increment_refcount(code_hash)?;
frame
.nested_storage
.charge_deposit(frame.account_id.clone(), StorageDeposit::Charge(deposit));
Ok(())
}
fn remove_delegate_dependency(
&mut self,
code_hash: &CodeHash<Self::T>,
) -> Result<(), DispatchError> {
let frame = self.top_frame_mut();
let info = frame.contract_info.get(&frame.account_id);
let deposit = info.remove_delegate_dependency(code_hash)?;
<WasmBlob<T>>::decrement_refcount(*code_hash);
frame
.nested_storage
.charge_deposit(frame.account_id.clone(), StorageDeposit::Refund(deposit));
Ok(())
}
}
mod sealing {
use super::*;
pub trait Sealed {}
impl<'a, T: Config, E> Sealed for Stack<'a, T, E> {}
#[cfg(test)]
impl Sealed for crate::wasm::MockExt {}
#[cfg(test)]
impl Sealed for &mut crate::wasm::MockExt {}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
exec::ExportedFunction::*,
gas::GasMeter,
tests::{
test_utils::{get_balance, hash, place_contract, set_balance},
ExtBuilder, RuntimeCall, RuntimeEvent as MetaEvent, Test, TestFilter, ALICE, BOB,
CHARLIE, GAS_LIMIT,
},
Error,
};
use assert_matches::assert_matches;
use codec::{Decode, Encode};
use frame_support::{assert_err, assert_ok, parameter_types};
use frame_system::{EventRecord, Phase};
use pallet_contracts_primitives::ReturnFlags;
use pretty_assertions::assert_eq;
use sp_runtime::{traits::Hash, DispatchError};
use std::{
cell::RefCell,
collections::hash_map::{Entry, HashMap},
rc::Rc,
};
type System = frame_system::Pallet<Test>;
type MockStack<'a> = Stack<'a, Test, MockExecutable>;
parameter_types! {
static Loader: MockLoader = MockLoader::default();
}
fn events() -> Vec<Event<Test>> {
System::events()
.into_iter()
.filter_map(|meta| match meta.event {
MetaEvent::Contracts(contract_event) => Some(contract_event),
_ => None,
})
.collect()
}
struct MockCtx<'a> {
ext: &'a mut dyn Ext<T = Test>,
input_data: Vec<u8>,
}
#[derive(Clone)]
struct MockExecutable {
func: Rc<dyn Fn(MockCtx, &Self) -> ExecResult + 'static>,
func_type: ExportedFunction,
code_hash: CodeHash<Test>,
code_info: CodeInfo<Test>,
refcount: u64,
}
#[derive(Default, Clone)]
pub struct MockLoader {
map: HashMap<CodeHash<Test>, MockExecutable>,
counter: u64,
}
impl MockLoader {
fn code_hashes() -> Vec<CodeHash<Test>> {
Loader::get().map.keys().copied().collect()
}
fn insert(
func_type: ExportedFunction,
f: impl Fn(MockCtx, &MockExecutable) -> ExecResult + 'static,
) -> CodeHash<Test> {
Loader::mutate(|loader| {
let hash = <Test as frame_system::Config>::Hash::from_low_u64_be(loader.counter);
loader.counter += 1;
loader.map.insert(
hash,
MockExecutable {
func: Rc::new(f),
func_type,
code_hash: hash,
code_info: CodeInfo::<Test>::new(ALICE),
refcount: 1,
},
);
hash
})
}
fn increment_refcount(code_hash: CodeHash<Test>) -> Result<(), DispatchError> {
Loader::mutate(|loader| {
match loader.map.entry(code_hash) {
Entry::Vacant(_) => Err(<Error<Test>>::CodeNotFound)?,
Entry::Occupied(mut entry) => entry.get_mut().refcount += 1,
}
Ok(())
})
}
fn decrement_refcount(code_hash: CodeHash<Test>) {
use std::collections::hash_map::Entry::Occupied;
Loader::mutate(|loader| {
let mut entry = match loader.map.entry(code_hash) {
Occupied(e) => e,
_ => panic!("code_hash does not exist"),
};
let refcount = &mut entry.get_mut().refcount;
*refcount -= 1;
if *refcount == 0 {
entry.remove();
}
});
}
}
impl Executable<Test> for MockExecutable {
fn from_storage(
code_hash: CodeHash<Test>,
_gas_meter: &mut GasMeter<Test>,
) -> Result<Self, DispatchError> {
Loader::mutate(|loader| {
loader.map.get(&code_hash).cloned().ok_or(Error::<Test>::CodeNotFound.into())
})
}
fn increment_refcount(code_hash: CodeHash<Test>) -> Result<(), DispatchError> {
MockLoader::increment_refcount(code_hash)
}
fn decrement_refcount(code_hash: CodeHash<Test>) {
MockLoader::decrement_refcount(code_hash);
}
fn execute<E: Ext<T = Test>>(
self,
ext: &mut E,
function: &ExportedFunction,
input_data: Vec<u8>,
) -> ExecResult {
if let &Constructor = function {
Self::increment_refcount(self.code_hash).unwrap();
}
if function == &self.func_type {
(self.func)(MockCtx { ext, input_data }, &self)
} else {
exec_success()
}
}
fn code_hash(&self) -> &CodeHash<Test> {
&self.code_hash
}
fn code_info(&self) -> &CodeInfo<Test> {
&self.code_info
}
fn code_len(&self) -> u32 {
0
}
fn is_deterministic(&self) -> bool {
true
}
}
fn exec_success() -> ExecResult {
Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() })
}
fn exec_trapped() -> ExecResult {
Err(ExecError { error: <Error<Test>>::ContractTrapped.into(), origin: ErrorOrigin::Callee })
}
#[test]
fn it_works() {
parameter_types! {
static TestData: Vec<usize> = vec![0];
}
let value = Default::default();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let exec_ch = MockLoader::insert(Call, |_ctx, _executable| {
TestData::mutate(|data| data.push(1));
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, exec_ch);
let mut storage_meter =
storage::meter::Meter::new(&Origin::from_account_id(ALICE), Some(0), value)
.unwrap();
assert_matches!(
MockStack::run_call(
Origin::from_account_id(ALICE),
BOB,
&mut gas_meter,
&mut storage_meter,
&schedule,
value,
vec![],
None,
Determinism::Enforced,
),
Ok(_)
);
});
assert_eq!(TestData::get(), vec![0, 1]);
}
#[test]
fn transfer_works() {
let origin = ALICE;
let dest = BOB;
ExtBuilder::default().build().execute_with(|| {
set_balance(&origin, 100);
set_balance(&dest, 0);
MockStack::transfer(Preservation::Preserve, &origin, &dest, 55).unwrap();
assert_eq!(get_balance(&origin), 45);
assert_eq!(get_balance(&dest), 55);
});
}
#[test]
fn correct_transfer_on_call() {
let origin = ALICE;
let dest = BOB;
let value = 55;
let success_ch = MockLoader::insert(Call, move |ctx, _| {
assert_eq!(ctx.ext.value_transferred(), value);
Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() })
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
place_contract(&dest, success_ch);
set_balance(&origin, 100);
let balance = get_balance(&dest);
let contract_origin = Origin::from_account_id(origin.clone());
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), value).unwrap();
let _ = MockStack::run_call(
contract_origin.clone(),
dest.clone(),
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
&schedule,
value,
vec![],
None,
Determinism::Enforced,
)
.unwrap();
assert_eq!(get_balance(&origin), 100 - value);
assert_eq!(get_balance(&dest), balance + value);
});
}
#[test]
fn correct_transfer_on_delegate_call() {
let origin = ALICE;
let dest = BOB;
let value = 35;
let success_ch = MockLoader::insert(Call, move |ctx, _| {
assert_eq!(ctx.ext.value_transferred(), value);
Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() })
});
let delegate_ch = MockLoader::insert(Call, move |ctx, _| {
assert_eq!(ctx.ext.value_transferred(), value);
let _ = ctx.ext.delegate_call(success_ch, Vec::new())?;
Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() })
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
place_contract(&dest, delegate_ch);
set_balance(&origin, 100);
let balance = get_balance(&dest);
let contract_origin = Origin::from_account_id(origin.clone());
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 55).unwrap();
let _ = MockStack::run_call(
contract_origin.clone(),
dest.clone(),
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
&schedule,
value,
vec![],
None,
Determinism::Enforced,
)
.unwrap();
assert_eq!(get_balance(&origin), 100 - value);
assert_eq!(get_balance(&dest), balance + value);
});
}
#[test]
fn changes_are_reverted_on_failing_call() {
let origin = ALICE;
let dest = BOB;
let return_ch = MockLoader::insert(Call, |_, _| {
Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: Vec::new() })
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
place_contract(&dest, return_ch);
set_balance(&origin, 100);
let balance = get_balance(&dest);
let contract_origin = Origin::from_account_id(origin.clone());
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 55).unwrap();
let output = MockStack::run_call(
contract_origin.clone(),
dest.clone(),
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
&schedule,
55,
vec![],
None,
Determinism::Enforced,
)
.unwrap();
assert!(output.did_revert());
assert_eq!(get_balance(&origin), 100);
assert_eq!(get_balance(&dest), balance);
});
}
#[test]
fn balance_too_low() {
let origin = ALICE;
let dest = BOB;
ExtBuilder::default().build().execute_with(|| {
set_balance(&origin, 0);
let result = MockStack::transfer(Preservation::Preserve, &origin, &dest, 100);
assert_eq!(result, Err(Error::<Test>::TransferFailed.into()));
assert_eq!(get_balance(&origin), 0);
assert_eq!(get_balance(&dest), 0);
});
}
#[test]
fn output_is_returned_on_success() {
let origin = ALICE;
let dest = BOB;
let return_ch = MockLoader::insert(Call, |_, _| {
Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![1, 2, 3, 4] })
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
let contract_origin = Origin::from_account_id(origin);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
place_contract(&BOB, return_ch);
let result = MockStack::run_call(
contract_origin,
dest,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
&schedule,
0,
vec![],
None,
Determinism::Enforced,
);
let output = result.unwrap();
assert!(!output.did_revert());
assert_eq!(output.data, vec![1, 2, 3, 4]);
});
}
#[test]
fn output_is_returned_on_failure() {
let origin = ALICE;
let dest = BOB;
let return_ch = MockLoader::insert(Call, |_, _| {
Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![1, 2, 3, 4] })
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, return_ch);
let contract_origin = Origin::from_account_id(origin);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
let result = MockStack::run_call(
contract_origin,
dest,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
&schedule,
0,
vec![],
None,
Determinism::Enforced,
);
let output = result.unwrap();
assert!(output.did_revert());
assert_eq!(output.data, vec![1, 2, 3, 4]);
});
}
#[test]
fn input_data_to_call() {
let input_data_ch = MockLoader::insert(Call, |ctx, _| {
assert_eq!(ctx.input_data, &[1, 2, 3, 4]);
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, input_data_ch);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
let result = MockStack::run_call(
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
&schedule,
0,
vec![1, 2, 3, 4],
None,
Determinism::Enforced,
);
assert_matches!(result, Ok(_));
});
}
#[test]
fn input_data_to_instantiate() {
let input_data_ch = MockLoader::insert(Constructor, |ctx, _| {
assert_eq!(ctx.input_data, &[1, 2, 3, 4]);
exec_success()
});
ExtBuilder::default()
.with_code_hashes(MockLoader::code_hashes())
.build()
.execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
let min_balance = <Test as Config>::Currency::minimum_balance();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable =
MockExecutable::from_storage(input_data_ch, &mut gas_meter).unwrap();
set_balance(&ALICE, min_balance * 10_000);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, None, min_balance).unwrap();
let result = MockStack::run_instantiate(
ALICE,
executable,
&mut gas_meter,
&mut storage_meter,
&schedule,
min_balance,
vec![1, 2, 3, 4],
&[],
None,
);
assert_matches!(result, Ok(_));
});
}
#[test]
fn max_depth() {
parameter_types! {
static ReachedBottom: bool = false;
}
let value = Default::default();
let recurse_ch = MockLoader::insert(Call, |ctx, _| {
let r = ctx.ext.call(Weight::zero(), BalanceOf::<Test>::zero(), BOB, 0, vec![], true);
ReachedBottom::mutate(|reached_bottom| {
if !*reached_bottom {
assert_eq!(r, Err(Error::<Test>::MaxCallDepthReached.into()));
*reached_bottom = true;
} else {
assert_matches!(r, Ok(_));
}
});
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
set_balance(&BOB, 1);
place_contract(&BOB, recurse_ch);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), value).unwrap();
let result = MockStack::run_call(
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
&schedule,
value,
vec![],
None,
Determinism::Enforced,
);
assert_matches!(result, Ok(_));
});
}
#[test]
fn caller_returns_proper_values() {
let origin = ALICE;
let dest = BOB;
parameter_types! {
static WitnessedCallerBob: Option<AccountIdOf<Test>> = None;
static WitnessedCallerCharlie: Option<AccountIdOf<Test>> = None;
}
let bob_ch = MockLoader::insert(Call, |ctx, _| {
WitnessedCallerBob::mutate(|caller| {
*caller = Some(ctx.ext.caller().account_id().unwrap().clone())
});
assert_matches!(
ctx.ext
.call(Weight::zero(), BalanceOf::<Test>::zero(), CHARLIE, 0, vec![], true),
Ok(_)
);
exec_success()
});
let charlie_ch = MockLoader::insert(Call, |ctx, _| {
WitnessedCallerCharlie::mutate(|caller| {
*caller = Some(ctx.ext.caller().account_id().unwrap().clone())
});
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
place_contract(&dest, bob_ch);
place_contract(&CHARLIE, charlie_ch);
let contract_origin = Origin::from_account_id(origin.clone());
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
let result = MockStack::run_call(
contract_origin.clone(),
dest.clone(),
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
&schedule,
0,
vec![],
None,
Determinism::Enforced,
);
assert_matches!(result, Ok(_));
});
assert_eq!(WitnessedCallerBob::get(), Some(origin));
assert_eq!(WitnessedCallerCharlie::get(), Some(dest));
}
#[test]
fn is_contract_returns_proper_values() {
let bob_ch = MockLoader::insert(Call, |ctx, _| {
assert!(ctx.ext.is_contract(&BOB));
assert!(!ctx.ext.is_contract(&ALICE));
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, bob_ch);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
let result = MockStack::run_call(
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
&schedule,
0,
vec![],
None,
Determinism::Enforced,
);
assert_matches!(result, Ok(_));
});
}
#[test]
fn code_hash_returns_proper_values() {
let code_bob = MockLoader::insert(Call, |ctx, _| {
assert!(ctx.ext.code_hash(&ALICE).is_none());
assert!(ctx.ext.code_hash(&BOB).is_some());
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, code_bob);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
let result = MockStack::run_call(
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
&schedule,
0,
vec![0],
None,
Determinism::Enforced,
);
assert_matches!(result, Ok(_));
});
}
#[test]
fn own_code_hash_returns_proper_values() {
let bob_ch = MockLoader::insert(Call, |ctx, _| {
let code_hash = ctx.ext.code_hash(&BOB).unwrap();
assert_eq!(*ctx.ext.own_code_hash(), code_hash);
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, bob_ch);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
let result = MockStack::run_call(
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
&schedule,
0,
vec![0],
None,
Determinism::Enforced,
);
assert_matches!(result, Ok(_));
});
}
#[test]
fn caller_is_origin_returns_proper_values() {
let code_charlie = MockLoader::insert(Call, |ctx, _| {
assert!(!ctx.ext.caller_is_origin());
exec_success()
});
let code_bob = MockLoader::insert(Call, |ctx, _| {
assert!(ctx.ext.caller_is_origin());
ctx.ext
.call(Weight::zero(), BalanceOf::<Test>::zero(), CHARLIE, 0, vec![], true)
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, code_bob);
place_contract(&CHARLIE, code_charlie);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
let result = MockStack::run_call(
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
&schedule,
0,
vec![0],
None,
Determinism::Enforced,
);
assert_matches!(result, Ok(_));
});
}
#[test]
fn root_caller_succeeds() {
let code_bob = MockLoader::insert(Call, |ctx, _| {
assert!(ctx.ext.caller_is_root());
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, code_bob);
let contract_origin = Origin::Root;
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
let result = MockStack::run_call(
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
&schedule,
0,
vec![0],
None,
Determinism::Enforced,
);
assert_matches!(result, Ok(_));
});
}
#[test]
fn root_caller_does_not_succeed_when_value_not_zero() {
let code_bob = MockLoader::insert(Call, |ctx, _| {
assert!(ctx.ext.caller_is_root());
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, code_bob);
let contract_origin = Origin::Root;
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
let result = MockStack::run_call(
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
&schedule,
1,
vec![0],
None,
Determinism::Enforced,
);
assert_matches!(result, Err(_));
});
}
#[test]
fn root_caller_succeeds_with_consecutive_calls() {
let code_charlie = MockLoader::insert(Call, |ctx, _| {
assert!(!ctx.ext.caller_is_root());
exec_success()
});
let code_bob = MockLoader::insert(Call, |ctx, _| {
assert!(ctx.ext.caller_is_root());
ctx.ext
.call(Weight::zero(), BalanceOf::<Test>::zero(), CHARLIE, 0, vec![], true)
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, code_bob);
place_contract(&CHARLIE, code_charlie);
let contract_origin = Origin::Root;
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
let result = MockStack::run_call(
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
&schedule,
0,
vec![0],
None,
Determinism::Enforced,
);
assert_matches!(result, Ok(_));
});
}
#[test]
fn address_returns_proper_values() {
let bob_ch = MockLoader::insert(Call, |ctx, _| {
assert_eq!(*ctx.ext.address(), BOB);
assert_matches!(
ctx.ext
.call(Weight::zero(), BalanceOf::<Test>::zero(), CHARLIE, 0, vec![], true),
Ok(_)
);
exec_success()
});
let charlie_ch = MockLoader::insert(Call, |ctx, _| {
assert_eq!(*ctx.ext.address(), CHARLIE);
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, bob_ch);
place_contract(&CHARLIE, charlie_ch);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
let result = MockStack::run_call(
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
&schedule,
0,
vec![],
None,
Determinism::Enforced,
);
assert_matches!(result, Ok(_));
});
}
#[test]
fn refuse_instantiate_with_value_below_existential_deposit() {
let dummy_ch = MockLoader::insert(Constructor, |_, _| exec_success());
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable = MockExecutable::from_storage(dummy_ch, &mut gas_meter).unwrap();
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
assert_matches!(
MockStack::run_instantiate(
ALICE,
executable,
&mut gas_meter,
&mut storage_meter,
&schedule,
0, vec![],
&[],
None,
),
Err(_)
);
});
}
#[test]
fn instantiation_work_with_success_output() {
let dummy_ch = MockLoader::insert(Constructor, |_, _| {
Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![80, 65, 83, 83] })
});
ExtBuilder::default()
.with_code_hashes(MockLoader::code_hashes())
.existential_deposit(15)
.build()
.execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
let min_balance = <Test as Config>::Currency::minimum_balance();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable = MockExecutable::from_storage(dummy_ch, &mut gas_meter).unwrap();
set_balance(&ALICE, min_balance * 1000);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter = storage::meter::Meter::new(
&contract_origin,
Some(min_balance * 100),
min_balance,
)
.unwrap();
let instantiated_contract_address = assert_matches!(
MockStack::run_instantiate(
ALICE,
executable,
&mut gas_meter,
&mut storage_meter,
&schedule,
min_balance,
vec![],
&[],
None,
),
Ok((address, ref output)) if output.data == vec![80, 65, 83, 83] => address
);
assert_eq!(
ContractInfo::<Test>::load_code_hash(&instantiated_contract_address).unwrap(),
dummy_ch
);
assert_eq!(
&events(),
&[Event::Instantiated {
deployer: ALICE,
contract: instantiated_contract_address
}]
);
});
}
#[test]
fn instantiation_fails_with_failing_output() {
let dummy_ch = MockLoader::insert(Constructor, |_, _| {
Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![70, 65, 73, 76] })
});
ExtBuilder::default()
.with_code_hashes(MockLoader::code_hashes())
.existential_deposit(15)
.build()
.execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
let min_balance = <Test as Config>::Currency::minimum_balance();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable = MockExecutable::from_storage(dummy_ch, &mut gas_meter).unwrap();
set_balance(&ALICE, min_balance * 1000);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter = storage::meter::Meter::new(
&contract_origin,
Some(min_balance * 100),
min_balance,
)
.unwrap();
let instantiated_contract_address = assert_matches!(
MockStack::run_instantiate(
ALICE,
executable,
&mut gas_meter,
&mut storage_meter,
&schedule,
min_balance,
vec![],
&[],
None,
),
Ok((address, ref output)) if output.data == vec![70, 65, 73, 76] => address
);
assert!(
ContractInfo::<Test>::load_code_hash(&instantiated_contract_address).is_none()
);
assert!(events().is_empty());
});
}
#[test]
fn instantiation_from_contract() {
let dummy_ch = MockLoader::insert(Call, |_, _| exec_success());
let instantiated_contract_address = Rc::new(RefCell::new(None::<AccountIdOf<Test>>));
let instantiator_ch = MockLoader::insert(Call, {
let instantiated_contract_address = Rc::clone(&instantiated_contract_address);
move |ctx, _| {
let (address, output) = ctx
.ext
.instantiate(
Weight::zero(),
BalanceOf::<Test>::zero(),
dummy_ch,
<Test as Config>::Currency::minimum_balance(),
vec![],
&[48, 49, 50],
)
.unwrap();
*instantiated_contract_address.borrow_mut() = address.into();
Ok(output)
}
});
ExtBuilder::default()
.with_code_hashes(MockLoader::code_hashes())
.existential_deposit(15)
.build()
.execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
let min_balance = <Test as Config>::Currency::minimum_balance();
set_balance(&ALICE, min_balance * 100);
place_contract(&BOB, instantiator_ch);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter = storage::meter::Meter::new(
&contract_origin,
Some(min_balance * 10),
min_balance * 10,
)
.unwrap();
assert_matches!(
MockStack::run_call(
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
&schedule,
min_balance * 10,
vec![],
None,
Determinism::Enforced,
),
Ok(_)
);
let instantiated_contract_address =
instantiated_contract_address.borrow().as_ref().unwrap().clone();
assert_eq!(
ContractInfo::<Test>::load_code_hash(&instantiated_contract_address).unwrap(),
dummy_ch
);
assert_eq!(
&events(),
&[
Event::Instantiated {
deployer: BOB,
contract: instantiated_contract_address
},
Event::Called { caller: Origin::from_account_id(ALICE), contract: BOB },
]
);
});
}
#[test]
fn instantiation_traps() {
let dummy_ch = MockLoader::insert(Constructor, |_, _| Err("It's a trap!".into()));
let instantiator_ch = MockLoader::insert(Call, {
move |ctx, _| {
assert_matches!(
ctx.ext.instantiate(
Weight::zero(),
BalanceOf::<Test>::zero(),
dummy_ch,
<Test as Config>::Currency::minimum_balance(),
vec![],
&[],
),
Err(ExecError {
error: DispatchError::Other("It's a trap!"),
origin: ErrorOrigin::Callee,
})
);
exec_success()
}
});
ExtBuilder::default()
.with_code_hashes(MockLoader::code_hashes())
.existential_deposit(15)
.build()
.execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
set_balance(&ALICE, 1000);
set_balance(&BOB, 100);
place_contract(&BOB, instantiator_ch);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(200), 0).unwrap();
assert_matches!(
MockStack::run_call(
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
&schedule,
0,
vec![],
None,
Determinism::Enforced,
),
Ok(_)
);
assert_eq!(
&events(),
&[Event::Called { caller: Origin::from_account_id(ALICE), contract: BOB },]
);
});
}
#[test]
fn termination_from_instantiate_fails() {
let terminate_ch = MockLoader::insert(Constructor, |ctx, _| {
ctx.ext.terminate(&ALICE).unwrap();
exec_success()
});
ExtBuilder::default()
.with_code_hashes(MockLoader::code_hashes())
.existential_deposit(15)
.build()
.execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable =
MockExecutable::from_storage(terminate_ch, &mut gas_meter).unwrap();
set_balance(&ALICE, 10_000);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, None, 100).unwrap();
assert_eq!(
MockStack::run_instantiate(
ALICE,
executable,
&mut gas_meter,
&mut storage_meter,
&schedule,
100,
vec![],
&[],
None,
),
Err(Error::<Test>::TerminatedInConstructor.into())
);
assert_eq!(&events(), &[]);
});
}
#[test]
fn in_memory_changes_not_discarded() {
let code_bob = MockLoader::insert(Call, |ctx, _| {
if ctx.input_data[0] == 0 {
let info = ctx.ext.contract_info();
assert_eq!(info.storage_byte_deposit, 0);
info.storage_byte_deposit = 42;
assert_eq!(
ctx.ext.call(
Weight::zero(),
BalanceOf::<Test>::zero(),
CHARLIE,
0,
vec![],
true
),
exec_trapped()
);
assert_eq!(ctx.ext.contract_info().storage_byte_deposit, 42);
}
exec_success()
});
let code_charlie = MockLoader::insert(Call, |ctx, _| {
assert!(ctx
.ext
.call(Weight::zero(), BalanceOf::<Test>::zero(), BOB, 0, vec![99], true)
.is_ok());
exec_trapped()
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, code_bob);
place_contract(&CHARLIE, code_charlie);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
let result = MockStack::run_call(
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
&schedule,
0,
vec![0],
None,
Determinism::Enforced,
);
assert_matches!(result, Ok(_));
});
}
#[test]
fn recursive_call_during_constructor_fails() {
let code = MockLoader::insert(Constructor, |ctx, _| {
assert_matches!(
ctx.ext.call(Weight::zero(), BalanceOf::<Test>::zero(), ctx.ext.address().clone(), 0, vec![], true),
Err(ExecError{error, ..}) if error == <Error<Test>>::ContractNotFound.into()
);
exec_success()
});
ExtBuilder::default()
.with_code_hashes(MockLoader::code_hashes())
.build()
.execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
let min_balance = <Test as Config>::Currency::minimum_balance();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable = MockExecutable::from_storage(code, &mut gas_meter).unwrap();
set_balance(&ALICE, min_balance * 10_000);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, None, min_balance).unwrap();
let result = MockStack::run_instantiate(
ALICE,
executable,
&mut gas_meter,
&mut storage_meter,
&schedule,
min_balance,
vec![],
&[],
None,
);
assert_matches!(result, Ok(_));
});
}
#[test]
fn printing_works() {
let code_hash = MockLoader::insert(Call, |ctx, _| {
ctx.ext.append_debug_buffer("This is a test");
ctx.ext.append_debug_buffer("More text");
exec_success()
});
let mut debug_buffer = DebugBufferVec::<Test>::try_from(Vec::new()).unwrap();
ExtBuilder::default().build().execute_with(|| {
let min_balance = <Test as Config>::Currency::minimum_balance();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, min_balance * 10);
place_contract(&BOB, code_hash);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
MockStack::run_call(
contract_origin,
BOB,
&mut gas_meter,
&mut storage_meter,
&schedule,
0,
vec![],
Some(&mut debug_buffer),
Determinism::Enforced,
)
.unwrap();
});
assert_eq!(&String::from_utf8(debug_buffer.to_vec()).unwrap(), "This is a testMore text");
}
#[test]
fn printing_works_on_fail() {
let code_hash = MockLoader::insert(Call, |ctx, _| {
ctx.ext.append_debug_buffer("This is a test");
ctx.ext.append_debug_buffer("More text");
exec_trapped()
});
let mut debug_buffer = DebugBufferVec::<Test>::try_from(Vec::new()).unwrap();
ExtBuilder::default().build().execute_with(|| {
let min_balance = <Test as Config>::Currency::minimum_balance();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, min_balance * 10);
place_contract(&BOB, code_hash);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
let result = MockStack::run_call(
contract_origin,
BOB,
&mut gas_meter,
&mut storage_meter,
&schedule,
0,
vec![],
Some(&mut debug_buffer),
Determinism::Enforced,
);
assert!(result.is_err());
});
assert_eq!(&String::from_utf8(debug_buffer.to_vec()).unwrap(), "This is a testMore text");
}
#[test]
fn debug_buffer_is_limited() {
let code_hash = MockLoader::insert(Call, move |ctx, _| {
ctx.ext.append_debug_buffer("overflowing bytes");
exec_success()
});
let debug_buf_before =
DebugBufferVec::<Test>::try_from(vec![0u8; DebugBufferVec::<Test>::bound() - 5])
.unwrap();
let mut debug_buf_after = debug_buf_before.clone();
ExtBuilder::default().build().execute_with(|| {
let schedule: Schedule<Test> = <Test as Config>::Schedule::get();
let min_balance = <Test as Config>::Currency::minimum_balance();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, min_balance * 10);
place_contract(&BOB, code_hash);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
MockStack::run_call(
contract_origin,
BOB,
&mut gas_meter,
&mut storage_meter,
&schedule,
0,
vec![],
Some(&mut debug_buf_after),
Determinism::Enforced,
)
.unwrap();
assert_eq!(debug_buf_before, debug_buf_after);
});
}
#[test]
fn call_reentry_direct_recursion() {
let code_bob = MockLoader::insert(Call, |ctx, _| {
let dest = Decode::decode(&mut ctx.input_data.as_ref()).unwrap();
ctx.ext.call(Weight::zero(), BalanceOf::<Test>::zero(), dest, 0, vec![], false)
});
let code_charlie = MockLoader::insert(Call, |_, _| exec_success());
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, code_bob);
place_contract(&CHARLIE, code_charlie);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
assert_ok!(MockStack::run_call(
contract_origin.clone(),
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
&schedule,
0,
CHARLIE.encode(),
None,
Determinism::Enforced
));
assert_err!(
MockStack::run_call(
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
&schedule,
0,
BOB.encode(),
None,
Determinism::Enforced
)
.map_err(|e| e.error),
<Error<Test>>::ReentranceDenied,
);
});
}
#[test]
fn call_deny_reentry() {
let code_bob = MockLoader::insert(Call, |ctx, _| {
if ctx.input_data[0] == 0 {
ctx.ext
.call(Weight::zero(), BalanceOf::<Test>::zero(), CHARLIE, 0, vec![], false)
} else {
exec_success()
}
});
let code_charlie = MockLoader::insert(Call, |ctx, _| {
ctx.ext.call(Weight::zero(), BalanceOf::<Test>::zero(), BOB, 0, vec![1], true)
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, code_bob);
place_contract(&CHARLIE, code_charlie);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
assert_err!(
MockStack::run_call(
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
&schedule,
0,
vec![0],
None,
Determinism::Enforced
)
.map_err(|e| e.error),
<Error<Test>>::ReentranceDenied,
);
});
}
#[test]
fn call_runtime_works() {
let code_hash = MockLoader::insert(Call, |ctx, _| {
let call = RuntimeCall::System(frame_system::Call::remark_with_event {
remark: b"Hello World".to_vec(),
});
ctx.ext.call_runtime(call).unwrap();
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let min_balance = <Test as Config>::Currency::minimum_balance();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, min_balance * 10);
place_contract(&BOB, code_hash);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
System::reset_events();
MockStack::run_call(
contract_origin,
BOB,
&mut gas_meter,
&mut storage_meter,
&schedule,
0,
vec![],
None,
Determinism::Enforced,
)
.unwrap();
let remark_hash = <Test as frame_system::Config>::Hashing::hash(b"Hello World");
assert_eq!(
System::events(),
vec![
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::System(frame_system::Event::Remarked {
sender: BOB,
hash: remark_hash
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::Contracts(crate::Event::Called {
caller: Origin::from_account_id(ALICE),
contract: BOB,
}),
topics: vec![hash(&Origin::<Test>::from_account_id(ALICE)), hash(&BOB)],
},
]
);
});
}
#[test]
fn call_runtime_filter() {
let code_hash = MockLoader::insert(Call, |ctx, _| {
use frame_system::Call as SysCall;
use pallet_balances::Call as BalanceCall;
use pallet_utility::Call as UtilCall;
let allowed_call =
RuntimeCall::System(SysCall::remark_with_event { remark: b"Hello".to_vec() });
let forbidden_call = RuntimeCall::Balances(BalanceCall::transfer_allow_death {
dest: CHARLIE,
value: 22,
});
assert_err!(
ctx.ext.call_runtime(forbidden_call.clone()),
frame_system::Error::<Test>::CallFiltered
);
assert_ok!(ctx.ext.call_runtime(RuntimeCall::Utility(UtilCall::batch {
calls: vec![allowed_call.clone(), forbidden_call, allowed_call]
})),);
assert_eq!(get_balance(&CHARLIE), 0);
exec_success()
});
TestFilter::set_filter(|call| match call {
RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { .. }) => false,
_ => true,
});
ExtBuilder::default().build().execute_with(|| {
let min_balance = <Test as Config>::Currency::minimum_balance();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, min_balance * 10);
place_contract(&BOB, code_hash);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
System::reset_events();
MockStack::run_call(
contract_origin,
BOB,
&mut gas_meter,
&mut storage_meter,
&schedule,
0,
vec![],
None,
Determinism::Enforced,
)
.unwrap();
let remark_hash = <Test as frame_system::Config>::Hashing::hash(b"Hello");
assert_eq!(
System::events(),
vec![
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::System(frame_system::Event::Remarked {
sender: BOB,
hash: remark_hash
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::Utility(pallet_utility::Event::ItemCompleted),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::Utility(pallet_utility::Event::BatchInterrupted {
index: 1,
error: frame_system::Error::<Test>::CallFiltered.into()
},),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::Contracts(crate::Event::Called {
caller: Origin::from_account_id(ALICE),
contract: BOB,
}),
topics: vec![hash(&Origin::<Test>::from_account_id(ALICE)), hash(&BOB)],
},
]
);
});
}
#[test]
fn nonce() {
let fail_code = MockLoader::insert(Constructor, |_, _| exec_trapped());
let success_code = MockLoader::insert(Constructor, |_, _| exec_success());
let succ_fail_code = MockLoader::insert(Constructor, move |ctx, _| {
ctx.ext
.instantiate(
Weight::zero(),
BalanceOf::<Test>::zero(),
fail_code,
ctx.ext.minimum_balance() * 100,
vec![],
&[],
)
.ok();
exec_success()
});
let succ_succ_code = MockLoader::insert(Constructor, move |ctx, _| {
let (account_id, _) = ctx
.ext
.instantiate(
Weight::zero(),
BalanceOf::<Test>::zero(),
success_code,
ctx.ext.minimum_balance() * 100,
vec![],
&[],
)
.unwrap();
ctx.ext
.call(Weight::zero(), BalanceOf::<Test>::zero(), account_id, 0, vec![], false)
.unwrap();
exec_success()
});
ExtBuilder::default()
.with_code_hashes(MockLoader::code_hashes())
.build()
.execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
let min_balance = <Test as Config>::Currency::minimum_balance();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let fail_executable =
MockExecutable::from_storage(fail_code, &mut gas_meter).unwrap();
let success_executable =
MockExecutable::from_storage(success_code, &mut gas_meter).unwrap();
let succ_fail_executable =
MockExecutable::from_storage(succ_fail_code, &mut gas_meter).unwrap();
let succ_succ_executable =
MockExecutable::from_storage(succ_succ_code, &mut gas_meter).unwrap();
set_balance(&ALICE, min_balance * 10_000);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, None, min_balance * 100).unwrap();
MockStack::run_instantiate(
ALICE,
fail_executable,
&mut gas_meter,
&mut storage_meter,
&schedule,
min_balance * 100,
vec![],
&[],
None,
)
.ok();
assert_eq!(<Nonce<Test>>::get(), 0);
assert_ok!(MockStack::run_instantiate(
ALICE,
success_executable,
&mut gas_meter,
&mut storage_meter,
&schedule,
min_balance * 100,
vec![],
&[],
None,
));
assert_eq!(<Nonce<Test>>::get(), 1);
assert_ok!(MockStack::run_instantiate(
ALICE,
succ_fail_executable,
&mut gas_meter,
&mut storage_meter,
&schedule,
min_balance * 200,
vec![],
&[],
None,
));
assert_eq!(<Nonce<Test>>::get(), 2);
assert_ok!(MockStack::run_instantiate(
ALICE,
succ_succ_executable,
&mut gas_meter,
&mut storage_meter,
&schedule,
min_balance * 200,
vec![],
&[],
None,
));
assert_eq!(<Nonce<Test>>::get(), 4);
});
}
#[test]
fn set_storage_works() {
let code_hash = MockLoader::insert(Call, |ctx, _| {
assert_eq!(
ctx.ext.set_storage(&Key::Fix([1; 32]), Some(vec![1, 2, 3]), false),
Ok(WriteOutcome::New)
);
assert_eq!(
ctx.ext.set_storage(&Key::Fix([2; 32]), Some(vec![4, 5, 6]), true),
Ok(WriteOutcome::New)
);
assert_eq!(ctx.ext.set_storage(&Key::Fix([3; 32]), None, false), Ok(WriteOutcome::New));
assert_eq!(ctx.ext.set_storage(&Key::Fix([4; 32]), None, true), Ok(WriteOutcome::New));
assert_eq!(
ctx.ext.set_storage(&Key::Fix([5; 32]), Some(vec![]), false),
Ok(WriteOutcome::New)
);
assert_eq!(
ctx.ext.set_storage(&Key::Fix([6; 32]), Some(vec![]), true),
Ok(WriteOutcome::New)
);
assert_eq!(
ctx.ext.set_storage(&Key::Fix([1; 32]), Some(vec![42]), false),
Ok(WriteOutcome::Overwritten(3))
);
assert_eq!(
ctx.ext.set_storage(&Key::Fix([2; 32]), Some(vec![48]), true),
Ok(WriteOutcome::Taken(vec![4, 5, 6]))
);
assert_eq!(ctx.ext.set_storage(&Key::Fix([3; 32]), None, false), Ok(WriteOutcome::New));
assert_eq!(ctx.ext.set_storage(&Key::Fix([4; 32]), None, true), Ok(WriteOutcome::New));
assert_eq!(
ctx.ext.set_storage(&Key::Fix([5; 32]), Some(vec![]), false),
Ok(WriteOutcome::Overwritten(0))
);
assert_eq!(
ctx.ext.set_storage(&Key::Fix([6; 32]), Some(vec![]), true),
Ok(WriteOutcome::Taken(vec![]))
);
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let min_balance = <Test as Config>::Currency::minimum_balance();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, min_balance * 1000);
place_contract(&BOB, code_hash);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 0).unwrap();
assert_ok!(MockStack::run_call(
contract_origin,
BOB,
&mut gas_meter,
&mut storage_meter,
&schedule,
0,
vec![],
None,
Determinism::Enforced
));
});
}
#[test]
fn set_storage_varsized_key_works() {
let code_hash = MockLoader::insert(Call, |ctx, _| {
assert_eq!(
ctx.ext.set_storage(
&Key::<Test>::try_from_var([1; 64].to_vec()).unwrap(),
Some(vec![1, 2, 3]),
false
),
Ok(WriteOutcome::New)
);
assert_eq!(
ctx.ext.set_storage(
&Key::<Test>::try_from_var([2; 19].to_vec()).unwrap(),
Some(vec![4, 5, 6]),
true
),
Ok(WriteOutcome::New)
);
assert_eq!(
ctx.ext.set_storage(
&Key::<Test>::try_from_var([3; 19].to_vec()).unwrap(),
None,
false
),
Ok(WriteOutcome::New)
);
assert_eq!(
ctx.ext.set_storage(
&Key::<Test>::try_from_var([4; 64].to_vec()).unwrap(),
None,
true
),
Ok(WriteOutcome::New)
);
assert_eq!(
ctx.ext.set_storage(
&Key::<Test>::try_from_var([5; 30].to_vec()).unwrap(),
Some(vec![]),
false
),
Ok(WriteOutcome::New)
);
assert_eq!(
ctx.ext.set_storage(
&Key::<Test>::try_from_var([6; 128].to_vec()).unwrap(),
Some(vec![]),
true
),
Ok(WriteOutcome::New)
);
assert_eq!(
ctx.ext.set_storage(
&Key::<Test>::try_from_var([1; 64].to_vec()).unwrap(),
Some(vec![42, 43, 44]),
false
),
Ok(WriteOutcome::Overwritten(3))
);
assert_eq!(
ctx.ext.set_storage(
&Key::<Test>::try_from_var([2; 19].to_vec()).unwrap(),
Some(vec![48]),
true
),
Ok(WriteOutcome::Taken(vec![4, 5, 6]))
);
assert_eq!(
ctx.ext.set_storage(
&Key::<Test>::try_from_var([3; 19].to_vec()).unwrap(),
None,
false
),
Ok(WriteOutcome::New)
);
assert_eq!(
ctx.ext.set_storage(
&Key::<Test>::try_from_var([4; 64].to_vec()).unwrap(),
None,
true
),
Ok(WriteOutcome::New)
);
assert_eq!(
ctx.ext.set_storage(
&Key::<Test>::try_from_var([5; 30].to_vec()).unwrap(),
Some(vec![]),
false
),
Ok(WriteOutcome::Overwritten(0))
);
assert_eq!(
ctx.ext.set_storage(
&Key::<Test>::try_from_var([6; 128].to_vec()).unwrap(),
Some(vec![]),
true
),
Ok(WriteOutcome::Taken(vec![]))
);
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let min_balance = <Test as Config>::Currency::minimum_balance();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, min_balance * 1000);
place_contract(&BOB, code_hash);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 0).unwrap();
assert_ok!(MockStack::run_call(
contract_origin,
BOB,
&mut gas_meter,
&mut storage_meter,
&schedule,
0,
vec![],
None,
Determinism::Enforced
));
});
}
#[test]
fn get_storage_works() {
let code_hash = MockLoader::insert(Call, |ctx, _| {
assert_eq!(
ctx.ext.set_storage(&Key::Fix([1; 32]), Some(vec![1, 2, 3]), false),
Ok(WriteOutcome::New)
);
assert_eq!(
ctx.ext.set_storage(&Key::Fix([2; 32]), Some(vec![]), false),
Ok(WriteOutcome::New)
);
assert_eq!(ctx.ext.get_storage(&Key::Fix([1; 32])), Some(vec![1, 2, 3]));
assert_eq!(ctx.ext.get_storage(&Key::Fix([2; 32])), Some(vec![]));
assert_eq!(ctx.ext.get_storage(&Key::Fix([3; 32])), None);
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let min_balance = <Test as Config>::Currency::minimum_balance();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, min_balance * 1000);
place_contract(&BOB, code_hash);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 0).unwrap();
assert_ok!(MockStack::run_call(
contract_origin,
BOB,
&mut gas_meter,
&mut storage_meter,
&schedule,
0,
vec![],
None,
Determinism::Enforced
));
});
}
#[test]
fn get_storage_size_works() {
let code_hash = MockLoader::insert(Call, |ctx, _| {
assert_eq!(
ctx.ext.set_storage(&Key::Fix([1; 32]), Some(vec![1, 2, 3]), false),
Ok(WriteOutcome::New)
);
assert_eq!(
ctx.ext.set_storage(&Key::Fix([2; 32]), Some(vec![]), false),
Ok(WriteOutcome::New)
);
assert_eq!(ctx.ext.get_storage_size(&Key::Fix([1; 32])), Some(3));
assert_eq!(ctx.ext.get_storage_size(&Key::Fix([2; 32])), Some(0));
assert_eq!(ctx.ext.get_storage_size(&Key::Fix([3; 32])), None);
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let min_balance = <Test as Config>::Currency::minimum_balance();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, min_balance * 1000);
place_contract(&BOB, code_hash);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 0).unwrap();
assert_ok!(MockStack::run_call(
contract_origin,
BOB,
&mut gas_meter,
&mut storage_meter,
&schedule,
0,
vec![],
None,
Determinism::Enforced
));
});
}
#[test]
fn get_storage_varsized_key_works() {
let code_hash = MockLoader::insert(Call, |ctx, _| {
assert_eq!(
ctx.ext.set_storage(
&Key::<Test>::try_from_var([1; 19].to_vec()).unwrap(),
Some(vec![1, 2, 3]),
false
),
Ok(WriteOutcome::New)
);
assert_eq!(
ctx.ext.set_storage(
&Key::<Test>::try_from_var([2; 16].to_vec()).unwrap(),
Some(vec![]),
false
),
Ok(WriteOutcome::New)
);
assert_eq!(
ctx.ext.get_storage(&Key::<Test>::try_from_var([1; 19].to_vec()).unwrap()),
Some(vec![1, 2, 3])
);
assert_eq!(
ctx.ext.get_storage(&Key::<Test>::try_from_var([2; 16].to_vec()).unwrap()),
Some(vec![])
);
assert_eq!(
ctx.ext.get_storage(&Key::<Test>::try_from_var([3; 8].to_vec()).unwrap()),
None
);
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let min_balance = <Test as Config>::Currency::minimum_balance();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, min_balance * 1000);
place_contract(&BOB, code_hash);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 0).unwrap();
assert_ok!(MockStack::run_call(
contract_origin,
BOB,
&mut gas_meter,
&mut storage_meter,
&schedule,
0,
vec![],
None,
Determinism::Enforced
));
});
}
#[test]
fn get_storage_size_varsized_key_works() {
let code_hash = MockLoader::insert(Call, |ctx, _| {
assert_eq!(
ctx.ext.set_storage(
&Key::<Test>::try_from_var([1; 19].to_vec()).unwrap(),
Some(vec![1, 2, 3]),
false
),
Ok(WriteOutcome::New)
);
assert_eq!(
ctx.ext.set_storage(
&Key::<Test>::try_from_var([2; 16].to_vec()).unwrap(),
Some(vec![]),
false
),
Ok(WriteOutcome::New)
);
assert_eq!(
ctx.ext.get_storage_size(&Key::<Test>::try_from_var([1; 19].to_vec()).unwrap()),
Some(3)
);
assert_eq!(
ctx.ext.get_storage_size(&Key::<Test>::try_from_var([2; 16].to_vec()).unwrap()),
Some(0)
);
assert_eq!(
ctx.ext.get_storage_size(&Key::<Test>::try_from_var([3; 8].to_vec()).unwrap()),
None
);
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let min_balance = <Test as Config>::Currency::minimum_balance();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, min_balance * 1000);
place_contract(&BOB, code_hash);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 0).unwrap();
assert_ok!(MockStack::run_call(
contract_origin,
BOB,
&mut gas_meter,
&mut storage_meter,
&schedule,
0,
vec![],
None,
Determinism::Enforced
));
});
}
#[test]
fn ecdsa_to_eth_address_returns_proper_value() {
let bob_ch = MockLoader::insert(Call, |ctx, _| {
let pubkey_compressed = array_bytes::hex2array_unchecked(
"028db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd91",
);
assert_eq!(
ctx.ext.ecdsa_to_eth_address(&pubkey_compressed).unwrap(),
array_bytes::hex2array_unchecked::<_, 20>(
"09231da7b19A016f9e576d23B16277062F4d46A8"
)
);
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, bob_ch);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
let result = MockStack::run_call(
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
&schedule,
0,
vec![],
None,
Determinism::Enforced,
);
assert_matches!(result, Ok(_));
});
}
#[test]
fn nonce_api_works() {
let fail_code = MockLoader::insert(Constructor, |_, _| exec_trapped());
let success_code = MockLoader::insert(Constructor, |_, _| exec_success());
let code_hash = MockLoader::insert(Call, move |ctx, _| {
assert_eq!(ctx.ext.nonce(), 1);
assert_eq!(ctx.ext.nonce(), 1);
assert_err!(
ctx.ext.instantiate(
Weight::zero(),
BalanceOf::<Test>::zero(),
fail_code,
0,
vec![],
&[],
),
ExecError {
error: <Error<Test>>::ContractTrapped.into(),
origin: ErrorOrigin::Callee
}
);
assert_eq!(ctx.ext.nonce(), 1);
ctx.ext
.instantiate(
Weight::zero(),
BalanceOf::<Test>::zero(),
success_code,
0,
vec![],
&[],
)
.unwrap();
assert_eq!(ctx.ext.nonce(), 2);
exec_success()
});
ExtBuilder::default()
.with_code_hashes(MockLoader::code_hashes())
.build()
.execute_with(|| {
let min_balance = <Test as Config>::Currency::minimum_balance();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, min_balance * 1000);
place_contract(&BOB, code_hash);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, None, 0).unwrap();
assert_ok!(MockStack::run_call(
contract_origin,
BOB,
&mut gas_meter,
&mut storage_meter,
&schedule,
0,
vec![],
None,
Determinism::Enforced
));
});
}
#[test]
fn randomness_works() {
let subject = b"nice subject".as_ref();
let code_hash = MockLoader::insert(Call, move |ctx, _| {
let rand = <Test as Config>::Randomness::random(subject);
assert_eq!(rand, ctx.ext.random(subject));
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, code_hash);
let contract_origin = Origin::from_account_id(ALICE);
let mut storage_meter =
storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap();
let result = MockStack::run_call(
contract_origin,
BOB,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&mut storage_meter,
&schedule,
0,
vec![],
None,
Determinism::Enforced,
);
assert_matches!(result, Ok(_));
});
}
}