use crate::{
CodeHash, Config, ContractAddressFor, Event, RawEvent, Trait,
TrieId, BalanceOf, ContractInfo, TrieIdGenerator,
gas::{Gas, GasMeter, Token}, rent, storage, Error, ContractInfoOf
};
use bitflags::bitflags;
use sp_std::prelude::*;
use sp_runtime::traits::{Bounded, Zero, Convert, Saturating};
use frame_support::{
dispatch::DispatchError,
traits::{ExistenceRequirement, Currency, Time, Randomness},
weights::Weight,
ensure, StorageMap,
};
pub type AccountIdOf<T> = <T as frame_system::Trait>::AccountId;
pub type MomentOf<T> = <<T as Trait>::Time as Time>::Moment;
pub type SeedOf<T> = <T as frame_system::Trait>::Hash;
pub type BlockNumberOf<T> = <T as frame_system::Trait>::BlockNumber;
pub type StorageKey = [u8; 32];
pub type TopicOf<T> = <T as frame_system::Trait>::Hash;
bitflags! {
pub struct ReturnFlags: u32 {
const REVERT = 0x0000_0001;
}
}
pub enum TransactorKind {
PlainAccount,
Contract,
}
#[cfg_attr(test, derive(PartialEq, Eq, Debug))]
pub struct ExecReturnValue {
pub flags: ReturnFlags,
pub data: Vec<u8>,
}
impl ExecReturnValue {
pub fn is_success(&self) -> bool {
!self.flags.contains(ReturnFlags::REVERT)
}
}
#[cfg_attr(test, derive(PartialEq, Eq, Debug))]
pub enum ErrorOrigin {
Caller,
Callee,
}
#[cfg_attr(test, derive(PartialEq, Eq, Debug))]
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 type ExecResult = Result<ExecReturnValue, ExecError>;
pub trait Ext {
type T: Trait;
fn get_storage(&self, key: &StorageKey) -> Option<Vec<u8>>;
fn set_storage(&mut self, key: StorageKey, value: Option<Vec<u8>>);
fn instantiate(
&mut self,
code: &CodeHash<Self::T>,
value: BalanceOf<Self::T>,
gas_meter: &mut GasMeter<Self::T>,
input_data: Vec<u8>,
) -> Result<(AccountIdOf<Self::T>, ExecReturnValue), ExecError>;
fn transfer(
&mut self,
to: &AccountIdOf<Self::T>,
value: BalanceOf<Self::T>,
gas_meter: &mut GasMeter<Self::T>,
) -> Result<(), DispatchError>;
fn terminate(
&mut self,
beneficiary: &AccountIdOf<Self::T>,
gas_meter: &mut GasMeter<Self::T>,
) -> Result<(), DispatchError>;
fn call(
&mut self,
to: &AccountIdOf<Self::T>,
value: BalanceOf<Self::T>,
gas_meter: &mut GasMeter<Self::T>,
input_data: Vec<u8>,
) -> ExecResult;
fn restore_to(
&mut self,
dest: AccountIdOf<Self::T>,
code_hash: CodeHash<Self::T>,
rent_allowance: BalanceOf<Self::T>,
delta: Vec<StorageKey>,
) -> Result<(), &'static str>;
fn caller(&self) -> &AccountIdOf<Self::T>;
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 tombstone_deposit(&self) -> BalanceOf<Self::T>;
fn random(&self, subject: &[u8]) -> SeedOf<Self::T>;
fn deposit_event(&mut self, topics: Vec<TopicOf<Self::T>>, data: Vec<u8>);
fn set_rent_allowance(&mut self, rent_allowance: BalanceOf<Self::T>);
fn rent_allowance(&self) -> BalanceOf<Self::T>;
fn block_number(&self) -> BlockNumberOf<Self::T>;
fn max_value_size(&self) -> u32;
fn get_weight_price(&self, weight: Weight) -> BalanceOf<Self::T>;
}
pub trait Loader<T: Trait> {
type Executable;
fn load_init(&self, code_hash: &CodeHash<T>) -> Result<Self::Executable, &'static str>;
fn load_main(&self, code_hash: &CodeHash<T>) -> Result<Self::Executable, &'static str>;
}
pub trait Vm<T: Trait> {
type Executable;
fn execute<E: Ext<T = T>>(
&self,
exec: &Self::Executable,
ext: E,
input_data: Vec<u8>,
gas_meter: &mut GasMeter<T>,
) -> ExecResult;
}
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
#[derive(Copy, Clone)]
pub enum ExecFeeToken {
Call,
Instantiate,
}
impl<T: Trait> Token<T> for ExecFeeToken {
type Metadata = Config<T>;
#[inline]
fn calculate_amount(&self, metadata: &Config<T>) -> Gas {
match *self {
ExecFeeToken::Call => metadata.schedule.call_base_cost,
ExecFeeToken::Instantiate => metadata.schedule.instantiate_base_cost,
}
}
}
pub struct ExecutionContext<'a, T: Trait + 'a, V, L> {
pub caller: Option<&'a ExecutionContext<'a, T, V, L>>,
pub self_account: T::AccountId,
pub self_trie_id: Option<TrieId>,
pub depth: usize,
pub config: &'a Config<T>,
pub vm: &'a V,
pub loader: &'a L,
pub timestamp: MomentOf<T>,
pub block_number: T::BlockNumber,
}
impl<'a, T, E, V, L> ExecutionContext<'a, T, V, L>
where
T: Trait,
L: Loader<T, Executable = E>,
V: Vm<T, Executable = E>,
{
pub fn top_level(origin: T::AccountId, cfg: &'a Config<T>, vm: &'a V, loader: &'a L) -> Self {
ExecutionContext {
caller: None,
self_trie_id: None,
self_account: origin,
depth: 0,
config: &cfg,
vm: &vm,
loader: &loader,
timestamp: T::Time::now(),
block_number: <frame_system::Module<T>>::block_number(),
}
}
fn nested<'b, 'c: 'b>(&'c self, dest: T::AccountId, trie_id: TrieId)
-> ExecutionContext<'b, T, V, L>
{
ExecutionContext {
caller: Some(self),
self_trie_id: Some(trie_id),
self_account: dest,
depth: self.depth + 1,
config: self.config,
vm: self.vm,
loader: self.loader,
timestamp: self.timestamp.clone(),
block_number: self.block_number.clone(),
}
}
pub fn call(
&mut self,
dest: T::AccountId,
value: BalanceOf<T>,
gas_meter: &mut GasMeter<T>,
input_data: Vec<u8>,
) -> ExecResult {
if self.depth == self.config.max_depth as usize {
Err(Error::<T>::MaxCallDepthReached)?
}
if gas_meter
.charge(self.config, ExecFeeToken::Call)
.is_out_of_gas()
{
Err(Error::<T>::OutOfGas)?
}
let contract = if let Some(ContractInfo::Alive(info)) = rent::collect_rent::<T>(&dest) {
info
} else {
Err(Error::<T>::NotCallable)?
};
let transactor_kind = self.transactor_kind();
let caller = self.self_account.clone();
self.with_nested_context(dest.clone(), contract.trie_id.clone(), |nested| {
if value > BalanceOf::<T>::zero() {
transfer(
gas_meter,
TransferCause::Call,
transactor_kind,
&caller,
&dest,
value,
nested,
)?
}
let executable = nested.loader.load_main(&contract.code_hash)
.map_err(|_| Error::<T>::CodeNotFound)?;
let output = nested.vm.execute(
&executable,
nested.new_call_context(caller, value),
input_data,
gas_meter,
).map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?;
Ok(output)
})
}
pub fn instantiate(
&mut self,
endowment: BalanceOf<T>,
gas_meter: &mut GasMeter<T>,
code_hash: &CodeHash<T>,
input_data: Vec<u8>,
) -> Result<(T::AccountId, ExecReturnValue), ExecError> {
if self.depth == self.config.max_depth as usize {
Err(Error::<T>::MaxCallDepthReached)?
}
if gas_meter
.charge(self.config, ExecFeeToken::Instantiate)
.is_out_of_gas()
{
Err(Error::<T>::OutOfGas)?
}
let transactor_kind = self.transactor_kind();
let caller = self.self_account.clone();
let dest = T::DetermineContractAddress::contract_address_for(
code_hash,
&input_data,
&caller,
);
let dest_trie_id = <T as Trait>::TrieIdGenerator::trie_id(&dest);
let output = self.with_nested_context(dest.clone(), dest_trie_id, |nested| {
storage::place_contract::<T>(
&dest,
nested
.self_trie_id
.clone()
.expect("the nested context always has to have self_trie_id"),
code_hash.clone()
)?;
transfer(
gas_meter,
TransferCause::Instantiate,
transactor_kind,
&caller,
&dest,
endowment,
nested,
)?;
let executable = nested.loader.load_init(&code_hash)
.map_err(|_| Error::<T>::CodeNotFound)?;
let output = nested.vm
.execute(
&executable,
nested.new_call_context(caller.clone(), endowment),
input_data,
gas_meter,
).map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?;
if T::Currency::total_balance(&dest) < nested.config.subsistence_threshold() {
Err(Error::<T>::NewContractNotFunded)?
}
deposit_event::<T>(vec![], RawEvent::Instantiated(caller.clone(), dest.clone()));
Ok(output)
})?;
Ok((dest, output))
}
fn new_call_context<'b>(
&'b mut self,
caller: T::AccountId,
value: BalanceOf<T>,
) -> CallContext<'b, 'a, T, V, L> {
let timestamp = self.timestamp.clone();
let block_number = self.block_number.clone();
CallContext {
ctx: self,
caller,
value_transferred: value,
timestamp,
block_number,
}
}
fn with_nested_context<F>(&mut self, dest: T::AccountId, trie_id: TrieId, func: F)
-> ExecResult
where F: FnOnce(&mut ExecutionContext<T, V, L>) -> ExecResult
{
use frame_support::storage::TransactionOutcome::*;
let mut nested = self.nested(dest, trie_id);
frame_support::storage::with_transaction(|| {
let output = func(&mut nested);
match output {
Ok(ref rv) if !rv.flags.contains(ReturnFlags::REVERT) => Commit(output),
_ => Rollback(output),
}
})
}
fn is_live(&self, account: &T::AccountId) -> bool {
&self.self_account == account ||
self.caller.map_or(false, |caller| caller.is_live(account))
}
fn transactor_kind(&self) -> TransactorKind {
if self.depth == 0 {
debug_assert!(self.self_trie_id.is_none());
debug_assert!(self.caller.is_none());
debug_assert!(ContractInfoOf::<T>::get(&self.self_account).is_none());
TransactorKind::PlainAccount
} else {
TransactorKind::Contract
}
}
}
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
#[derive(Copy, Clone)]
pub enum TransferFeeKind {
ContractInstantiate,
Transfer,
}
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
#[derive(Copy, Clone)]
pub struct TransferFeeToken {
kind: TransferFeeKind,
}
impl<T: Trait> Token<T> for TransferFeeToken {
type Metadata = Config<T>;
#[inline]
fn calculate_amount(&self, metadata: &Config<T>) -> Gas {
match self.kind {
TransferFeeKind::ContractInstantiate => metadata.schedule.instantiate_cost,
TransferFeeKind::Transfer => metadata.schedule.transfer_cost,
}
}
}
enum TransferCause {
Call,
Instantiate,
Terminate,
}
fn transfer<'a, T: Trait, V: Vm<T>, L: Loader<T>>(
gas_meter: &mut GasMeter<T>,
cause: TransferCause,
origin: TransactorKind,
transactor: &T::AccountId,
dest: &T::AccountId,
value: BalanceOf<T>,
ctx: &mut ExecutionContext<'a, T, V, L>,
) -> Result<(), DispatchError> {
use self::TransferCause::*;
use self::TransferFeeKind::*;
use self::TransactorKind::*;
let token = {
let kind: TransferFeeKind = match cause {
Instantiate => ContractInstantiate,
Call | Terminate => TransferFeeKind::Transfer,
};
TransferFeeToken {
kind,
}
};
if gas_meter.charge(ctx.config, token).is_out_of_gas() {
Err(Error::<T>::OutOfGas)?
}
let existence_requirement = match (cause, origin) {
(Terminate, _) => ExistenceRequirement::AllowDeath,
(_, Contract) => {
ensure!(
T::Currency::total_balance(transactor).saturating_sub(value) >=
ctx.config.subsistence_threshold(),
Error::<T>::BelowSubsistenceThreshold,
);
ExistenceRequirement::KeepAlive
},
(_, PlainAccount) => ExistenceRequirement::KeepAlive,
};
T::Currency::transfer(transactor, dest, value, existence_requirement)
.map_err(|_| Error::<T>::TransferFailed)?;
Ok(())
}
struct CallContext<'a, 'b: 'a, T: Trait + 'b, V: Vm<T> + 'b, L: Loader<T>> {
ctx: &'a mut ExecutionContext<'b, T, V, L>,
caller: T::AccountId,
value_transferred: BalanceOf<T>,
timestamp: MomentOf<T>,
block_number: T::BlockNumber,
}
impl<'a, 'b: 'a, T, E, V, L> Ext for CallContext<'a, 'b, T, V, L>
where
T: Trait + 'b,
V: Vm<T, Executable = E>,
L: Loader<T, Executable = E>,
{
type T = T;
fn get_storage(&self, key: &StorageKey) -> Option<Vec<u8>> {
let trie_id = self.ctx.self_trie_id.as_ref().expect(
"`ctx.self_trie_id` points to an alive contract within the `CallContext`;\
it cannot be `None`;\
expect can't fail;\
qed",
);
storage::read_contract_storage(trie_id, key)
}
fn set_storage(&mut self, key: StorageKey, value: Option<Vec<u8>>) {
let trie_id = self.ctx.self_trie_id.as_ref().expect(
"`ctx.self_trie_id` points to an alive contract within the `CallContext`;\
it cannot be `None`;\
expect can't fail;\
qed",
);
if let Err(storage::ContractAbsentError) =
storage::write_contract_storage::<T>(&self.ctx.self_account, trie_id, &key, value)
{
panic!(
"the contract must be in the alive state within the `CallContext`;\
the contract cannot be absent in storage;
write_contract_storage cannot return `None`;
qed"
);
}
}
fn instantiate(
&mut self,
code_hash: &CodeHash<T>,
endowment: BalanceOf<T>,
gas_meter: &mut GasMeter<T>,
input_data: Vec<u8>,
) -> Result<(AccountIdOf<T>, ExecReturnValue), ExecError> {
self.ctx.instantiate(endowment, gas_meter, code_hash, input_data)
}
fn transfer(
&mut self,
to: &T::AccountId,
value: BalanceOf<T>,
gas_meter: &mut GasMeter<T>,
) -> Result<(), DispatchError> {
transfer(
gas_meter,
TransferCause::Call,
TransactorKind::Contract,
&self.ctx.self_account.clone(),
&to,
value,
self.ctx,
)
}
fn terminate(
&mut self,
beneficiary: &AccountIdOf<Self::T>,
gas_meter: &mut GasMeter<Self::T>,
) -> Result<(), DispatchError> {
let self_id = self.ctx.self_account.clone();
let value = T::Currency::free_balance(&self_id);
if let Some(caller_ctx) = self.ctx.caller {
if caller_ctx.is_live(&self_id) {
return Err(DispatchError::Other(
"Cannot terminate a contract that is present on the call stack",
));
}
}
transfer(
gas_meter,
TransferCause::Terminate,
TransactorKind::Contract,
&self_id,
beneficiary,
value,
self.ctx,
)?;
let self_trie_id = self.ctx.self_trie_id.as_ref().expect(
"this function is only invoked by in the context of a contract;\
a contract has a trie id;\
this can't be None; qed",
);
storage::destroy_contract::<T>(&self_id, self_trie_id);
Ok(())
}
fn call(
&mut self,
to: &T::AccountId,
value: BalanceOf<T>,
gas_meter: &mut GasMeter<T>,
input_data: Vec<u8>,
) -> ExecResult {
self.ctx.call(to.clone(), value, gas_meter, input_data)
}
fn restore_to(
&mut self,
dest: AccountIdOf<Self::T>,
code_hash: CodeHash<Self::T>,
rent_allowance: BalanceOf<Self::T>,
delta: Vec<StorageKey>,
) -> Result<(), &'static str> {
if let Some(caller_ctx) = self.ctx.caller {
if caller_ctx.is_live(&self.ctx.self_account) {
return Err(
"Cannot perform restoration of a contract that is present on the call stack",
);
}
}
let result = crate::rent::restore_to::<T>(
self.ctx.self_account.clone(),
dest.clone(),
code_hash.clone(),
rent_allowance,
delta,
);
if let Ok(_) = result {
deposit_event::<Self::T>(
vec![],
RawEvent::Restored(
self.ctx.self_account.clone(),
dest,
code_hash,
rent_allowance,
),
);
}
result
}
fn address(&self) -> &T::AccountId {
&self.ctx.self_account
}
fn caller(&self) -> &T::AccountId {
&self.caller
}
fn balance(&self) -> BalanceOf<T> {
T::Currency::free_balance(&self.ctx.self_account)
}
fn value_transferred(&self) -> BalanceOf<T> {
self.value_transferred
}
fn random(&self, subject: &[u8]) -> SeedOf<T> {
T::Randomness::random(subject)
}
fn now(&self) -> &MomentOf<T> {
&self.timestamp
}
fn minimum_balance(&self) -> BalanceOf<T> {
self.ctx.config.existential_deposit
}
fn tombstone_deposit(&self) -> BalanceOf<T> {
self.ctx.config.tombstone_deposit
}
fn deposit_event(&mut self, topics: Vec<T::Hash>, data: Vec<u8>) {
deposit_event::<Self::T>(
topics,
RawEvent::ContractExecution(self.ctx.self_account.clone(), data)
);
}
fn set_rent_allowance(&mut self, rent_allowance: BalanceOf<T>) {
if let Err(storage::ContractAbsentError) =
storage::set_rent_allowance::<T>(&self.ctx.self_account, rent_allowance)
{
panic!(
"`self_account` points to an alive contract within the `CallContext`;
set_rent_allowance cannot return `Err`; qed"
);
}
}
fn rent_allowance(&self) -> BalanceOf<T> {
storage::rent_allowance::<T>(&self.ctx.self_account)
.unwrap_or_else(|_| <BalanceOf<T>>::max_value())
}
fn block_number(&self) -> T::BlockNumber { self.block_number }
fn max_value_size(&self) -> u32 {
self.ctx.config.max_value_size
}
fn get_weight_price(&self, weight: Weight) -> BalanceOf<Self::T> {
T::WeightPrice::convert(weight)
}
}
fn deposit_event<T: Trait>(
topics: Vec<T::Hash>,
event: Event<T>,
) {
<frame_system::Module<T>>::deposit_event_indexed(
&*topics,
<T as Trait>::Event::from(event).into(),
)
}
#[cfg(test)]
mod tests {
use super::{
BalanceOf, Event, ExecFeeToken, ExecResult, ExecutionContext, Ext, Loader,
RawEvent, TransferFeeKind, TransferFeeToken, Vm, ReturnFlags, ExecError, ErrorOrigin
};
use crate::{
gas::GasMeter, tests::{ExtBuilder, Test, MetaEvent},
exec::ExecReturnValue, CodeHash, Config,
gas::Gas,
storage, Error
};
use crate::tests::test_utils::{place_contract, set_balance, get_balance};
use sp_runtime::DispatchError;
use assert_matches::assert_matches;
use std::{cell::RefCell, collections::HashMap, marker::PhantomData, rc::Rc};
const ALICE: u64 = 1;
const BOB: u64 = 2;
const CHARLIE: u64 = 3;
const GAS_LIMIT: Gas = 10_000_000_000;
fn events() -> Vec<Event<Test>> {
<frame_system::Module<Test>>::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>,
gas_meter: &'a mut GasMeter<Test>,
}
#[derive(Clone)]
struct MockExecutable<'a>(Rc<dyn Fn(MockCtx) -> ExecResult + 'a>);
impl<'a> MockExecutable<'a> {
fn new(f: impl Fn(MockCtx) -> ExecResult + 'a) -> Self {
MockExecutable(Rc::new(f))
}
}
struct MockLoader<'a> {
map: HashMap<CodeHash<Test>, MockExecutable<'a>>,
counter: u64,
}
impl<'a> MockLoader<'a> {
fn empty() -> Self {
MockLoader {
map: HashMap::new(),
counter: 0,
}
}
fn insert(&mut self, f: impl Fn(MockCtx) -> ExecResult + 'a) -> CodeHash<Test> {
let code_hash = <Test as frame_system::Trait>::Hash::from_low_u64_be(self.counter);
self.counter += 1;
self.map.insert(code_hash, MockExecutable::new(f));
code_hash
}
}
struct MockVm<'a> {
_marker: PhantomData<&'a ()>,
}
impl<'a> MockVm<'a> {
fn new() -> Self {
MockVm { _marker: PhantomData }
}
}
impl<'a> Loader<Test> for MockLoader<'a> {
type Executable = MockExecutable<'a>;
fn load_init(&self, code_hash: &CodeHash<Test>) -> Result<Self::Executable, &'static str> {
self.map
.get(code_hash)
.cloned()
.ok_or_else(|| "code not found")
}
fn load_main(&self, code_hash: &CodeHash<Test>) -> Result<Self::Executable, &'static str> {
self.map
.get(code_hash)
.cloned()
.ok_or_else(|| "code not found")
}
}
impl<'a> Vm<Test> for MockVm<'a> {
type Executable = MockExecutable<'a>;
fn execute<E: Ext<T = Test>>(
&self,
exec: &MockExecutable,
mut ext: E,
input_data: Vec<u8>,
gas_meter: &mut GasMeter<Test>,
) -> ExecResult {
(exec.0)(MockCtx {
ext: &mut ext,
input_data,
gas_meter,
})
}
}
fn exec_success() -> ExecResult {
Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() })
}
#[test]
fn it_works() {
let value = Default::default();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let data = vec![];
let vm = MockVm::new();
let test_data = Rc::new(RefCell::new(vec![0usize]));
let mut loader = MockLoader::empty();
let exec_ch = loader.insert(|_ctx| {
test_data.borrow_mut().push(1);
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
place_contract(&BOB, exec_ch);
assert_matches!(
ctx.call(BOB, value, &mut gas_meter, data),
Ok(_)
);
});
assert_eq!(&*test_data.borrow(), &vec![0, 1]);
}
#[test]
fn base_fees() {
let origin = ALICE;
let dest = BOB;
ExtBuilder::default().build().execute_with(|| {
let vm = MockVm::new();
let loader = MockLoader::empty();
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader);
set_balance(&origin, 100);
set_balance(&dest, 0);
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let result = super::transfer(
&mut gas_meter,
super::TransferCause::Call,
super::TransactorKind::PlainAccount,
&origin,
&dest,
0,
&mut ctx,
);
assert_matches!(result, Ok(_));
let mut toks = gas_meter.tokens().iter();
match_tokens!(toks, TransferFeeToken { kind: TransferFeeKind::Transfer },);
});
ExtBuilder::default().build().execute_with(|| {
let mut loader = MockLoader::empty();
let code = loader.insert(|_| exec_success());
let vm = MockVm::new();
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader);
set_balance(&origin, 100);
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let result = ctx.instantiate(cfg.subsistence_threshold(), &mut gas_meter, &code, vec![]);
assert_matches!(result, Ok(_));
let mut toks = gas_meter.tokens().iter();
match_tokens!(toks, ExecFeeToken::Instantiate,);
});
}
#[test]
fn transfer_works() {
let origin = ALICE;
let dest = BOB;
let vm = MockVm::new();
let loader = MockLoader::empty();
ExtBuilder::default().build().execute_with(|| {
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader);
set_balance(&origin, 100);
set_balance(&dest, 0);
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
super::transfer(
&mut gas_meter,
super::TransferCause::Call,
super::TransactorKind::PlainAccount,
&origin,
&dest,
55,
&mut ctx,
).unwrap();
assert_eq!(get_balance(&origin), 45);
assert_eq!(get_balance(&dest), 55);
});
}
#[test]
fn changes_are_reverted_on_failing_call() {
let origin = ALICE;
let dest = BOB;
let vm = MockVm::new();
let mut loader = MockLoader::empty();
let return_ch = loader.insert(
|_| Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: Vec::new() })
);
ExtBuilder::default().build().execute_with(|| {
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader);
place_contract(&BOB, return_ch);
set_balance(&origin, 100);
set_balance(&dest, 0);
let output = ctx.call(
dest,
55,
&mut GasMeter::<Test>::new(GAS_LIMIT),
vec![],
).unwrap();
assert!(!output.is_success());
assert_eq!(get_balance(&origin), 100);
assert_eq!(get_balance(&dest), 0);
});
}
#[test]
fn transfer_fees() {
let origin = ALICE;
let dest = BOB;
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let vm = MockVm::new();
let loader = MockLoader::empty();
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader);
set_balance(&origin, 100);
set_balance(&dest, 0);
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let result = super::transfer(
&mut gas_meter,
super::TransferCause::Call,
super::TransactorKind::PlainAccount,
&origin,
&dest,
50,
&mut ctx,
);
assert_matches!(result, Ok(_));
let mut toks = gas_meter.tokens().iter();
match_tokens!(
toks,
TransferFeeToken {
kind: TransferFeeKind::Transfer,
},
);
});
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let vm = MockVm::new();
let loader = MockLoader::empty();
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader);
set_balance(&origin, 100);
set_balance(&dest, 15);
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let result = super::transfer(
&mut gas_meter,
super::TransferCause::Call,
super::TransactorKind::PlainAccount,
&origin,
&dest,
50,
&mut ctx,
);
assert_matches!(result, Ok(_));
let mut toks = gas_meter.tokens().iter();
match_tokens!(
toks,
TransferFeeToken {
kind: TransferFeeKind::Transfer,
},
);
});
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let mut loader = MockLoader::empty();
let code = loader.insert(|_| exec_success());
let vm = MockVm::new();
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader);
set_balance(&origin, 100);
set_balance(&dest, 15);
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let result = ctx.instantiate(50, &mut gas_meter, &code, vec![]);
assert_matches!(result, Ok(_));
let mut toks = gas_meter.tokens().iter();
match_tokens!(
toks,
ExecFeeToken::Instantiate,
TransferFeeToken {
kind: TransferFeeKind::ContractInstantiate,
},
);
});
}
#[test]
fn balance_too_low() {
let origin = ALICE;
let dest = BOB;
let vm = MockVm::new();
let loader = MockLoader::empty();
ExtBuilder::default().build().execute_with(|| {
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader);
set_balance(&origin, 0);
let result = super::transfer(
&mut GasMeter::<Test>::new(GAS_LIMIT),
super::TransferCause::Call,
super::TransactorKind::PlainAccount,
&origin,
&dest,
100,
&mut ctx,
);
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 vm = MockVm::new();
let mut loader = MockLoader::empty();
let return_ch = loader.insert(
|_| Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![1, 2, 3, 4] })
);
ExtBuilder::default().build().execute_with(|| {
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader);
place_contract(&BOB, return_ch);
let result = ctx.call(
dest,
0,
&mut GasMeter::<Test>::new(GAS_LIMIT),
vec![],
);
let output = result.unwrap();
assert!(output.is_success());
assert_eq!(output.data, vec![1, 2, 3, 4]);
});
}
#[test]
fn output_is_returned_on_failure() {
let origin = ALICE;
let dest = BOB;
let vm = MockVm::new();
let mut loader = MockLoader::empty();
let return_ch = loader.insert(
|_| Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![1, 2, 3, 4] })
);
ExtBuilder::default().build().execute_with(|| {
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader);
place_contract(&BOB, return_ch);
let result = ctx.call(
dest,
0,
&mut GasMeter::<Test>::new(GAS_LIMIT),
vec![],
);
let output = result.unwrap();
assert!(!output.is_success());
assert_eq!(output.data, vec![1, 2, 3, 4]);
});
}
#[test]
fn input_data_to_call() {
let vm = MockVm::new();
let mut loader = MockLoader::empty();
let input_data_ch = loader.insert(|ctx| {
assert_eq!(ctx.input_data, &[1, 2, 3, 4]);
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
place_contract(&BOB, input_data_ch);
let result = ctx.call(
BOB,
0,
&mut GasMeter::<Test>::new(GAS_LIMIT),
vec![1, 2, 3, 4],
);
assert_matches!(result, Ok(_));
});
}
#[test]
fn input_data_to_instantiate() {
let vm = MockVm::new();
let mut loader = MockLoader::empty();
let input_data_ch = loader.insert(|ctx| {
assert_eq!(ctx.input_data, &[1, 2, 3, 4]);
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
set_balance(&ALICE, 100);
let result = ctx.instantiate(
cfg.subsistence_threshold(),
&mut GasMeter::<Test>::new(GAS_LIMIT),
&input_data_ch,
vec![1, 2, 3, 4],
);
assert_matches!(result, Ok(_));
});
}
#[test]
fn max_depth() {
let value = Default::default();
let reached_bottom = RefCell::new(false);
let vm = MockVm::new();
let mut loader = MockLoader::empty();
let recurse_ch = loader.insert(|ctx| {
let r = ctx.ext.call(&BOB, 0, ctx.gas_meter, vec![]);
let mut reached_bottom = reached_bottom.borrow_mut();
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 cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
set_balance(&BOB, 1);
place_contract(&BOB, recurse_ch);
let result = ctx.call(
BOB,
value,
&mut GasMeter::<Test>::new(GAS_LIMIT),
vec![],
);
assert_matches!(result, Ok(_));
});
}
#[test]
fn caller_returns_proper_values() {
let origin = ALICE;
let dest = BOB;
let vm = MockVm::new();
let witnessed_caller_bob = RefCell::new(None::<u64>);
let witnessed_caller_charlie = RefCell::new(None::<u64>);
let mut loader = MockLoader::empty();
let bob_ch = loader.insert(|ctx| {
*witnessed_caller_bob.borrow_mut() = Some(*ctx.ext.caller());
assert_matches!(
ctx.ext.call(&CHARLIE, 0, ctx.gas_meter, vec![]),
Ok(_)
);
exec_success()
});
let charlie_ch = loader.insert(|ctx| {
*witnessed_caller_charlie.borrow_mut() = Some(*ctx.ext.caller());
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader);
place_contract(&dest, bob_ch);
place_contract(&CHARLIE, charlie_ch);
let result = ctx.call(
dest,
0,
&mut GasMeter::<Test>::new(GAS_LIMIT),
vec![],
);
assert_matches!(result, Ok(_));
});
assert_eq!(&*witnessed_caller_bob.borrow(), &Some(origin));
assert_eq!(&*witnessed_caller_charlie.borrow(), &Some(dest));
}
#[test]
fn address_returns_proper_values() {
let vm = MockVm::new();
let mut loader = MockLoader::empty();
let bob_ch = loader.insert(|ctx| {
assert_eq!(*ctx.ext.address(), BOB);
assert_matches!(
ctx.ext.call(&CHARLIE, 0, ctx.gas_meter, vec![]),
Ok(_)
);
exec_success()
});
let charlie_ch = loader.insert(|ctx| {
assert_eq!(*ctx.ext.address(), CHARLIE);
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
place_contract(&BOB, bob_ch);
place_contract(&CHARLIE, charlie_ch);
let result = ctx.call(
BOB,
0,
&mut GasMeter::<Test>::new(GAS_LIMIT),
vec![],
);
assert_matches!(result, Ok(_));
});
}
#[test]
fn refuse_instantiate_with_value_below_existential_deposit() {
let vm = MockVm::new();
let mut loader = MockLoader::empty();
let dummy_ch = loader.insert(|_| exec_success());
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
assert_matches!(
ctx.instantiate(
0,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&dummy_ch,
vec![],
),
Err(_)
);
});
}
#[test]
fn instantiation_work_with_success_output() {
let vm = MockVm::new();
let mut loader = MockLoader::empty();
let dummy_ch = loader.insert(
|_| Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![80, 65, 83, 83] })
);
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
set_balance(&ALICE, 1000);
let instantiated_contract_address = assert_matches!(
ctx.instantiate(
100,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&dummy_ch,
vec![],
),
Ok((address, ref output)) if output.data == vec![80, 65, 83, 83] => address
);
assert_eq!(storage::code_hash::<Test>(&instantiated_contract_address).unwrap(), dummy_ch);
assert_eq!(&events(), &[
RawEvent::Instantiated(ALICE, instantiated_contract_address)
]);
});
}
#[test]
fn instantiation_fails_with_failing_output() {
let vm = MockVm::new();
let mut loader = MockLoader::empty();
let dummy_ch = loader.insert(
|_| Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![70, 65, 73, 76] })
);
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
set_balance(&ALICE, 1000);
let instantiated_contract_address = assert_matches!(
ctx.instantiate(
100,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&dummy_ch,
vec![],
),
Ok((address, ref output)) if output.data == vec![70, 65, 73, 76] => address
);
assert!(storage::code_hash::<Test>(&instantiated_contract_address).is_err());
assert!(events().is_empty());
});
}
#[test]
fn instantiation_from_contract() {
let vm = MockVm::new();
let mut loader = MockLoader::empty();
let dummy_ch = loader.insert(|_| exec_success());
let instantiated_contract_address = Rc::new(RefCell::new(None::<u64>));
let instantiator_ch = loader.insert({
let dummy_ch = dummy_ch.clone();
let instantiated_contract_address = Rc::clone(&instantiated_contract_address);
move |ctx| {
let (address, output) = ctx.ext.instantiate(
&dummy_ch,
Config::<Test>::subsistence_threshold_uncached(),
ctx.gas_meter,
vec![]
).unwrap();
*instantiated_contract_address.borrow_mut() = address.into();
Ok(output)
}
});
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
set_balance(&ALICE, 1000);
set_balance(&BOB, 100);
place_contract(&BOB, instantiator_ch);
assert_matches!(
ctx.call(BOB, 20, &mut GasMeter::<Test>::new(GAS_LIMIT), vec![]),
Ok(_)
);
let instantiated_contract_address = instantiated_contract_address.borrow().as_ref().unwrap().clone();
assert_eq!(storage::code_hash::<Test>(&instantiated_contract_address).unwrap(), dummy_ch);
assert_eq!(&events(), &[
RawEvent::Instantiated(BOB, instantiated_contract_address)
]);
});
}
#[test]
fn instantiation_traps() {
let vm = MockVm::new();
let mut loader = MockLoader::empty();
let dummy_ch = loader.insert(
|_| Err("It's a trap!".into())
);
let instantiator_ch = loader.insert({
let dummy_ch = dummy_ch.clone();
move |ctx| {
assert_matches!(
ctx.ext.instantiate(
&dummy_ch,
15u64,
ctx.gas_meter,
vec![]
),
Err(ExecError {
error: DispatchError::Other("It's a trap!"),
origin: ErrorOrigin::Callee,
})
);
exec_success()
}
});
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
set_balance(&ALICE, 1000);
set_balance(&BOB, 100);
place_contract(&BOB, instantiator_ch);
assert_matches!(
ctx.call(BOB, 20, &mut GasMeter::<Test>::new(GAS_LIMIT), vec![]),
Ok(_)
);
assert_eq!(&events(), &[]);
});
}
#[test]
fn termination_from_instantiate_fails() {
let vm = MockVm::new();
let mut loader = MockLoader::empty();
let terminate_ch = loader.insert(|mut ctx| {
ctx.ext.terminate(&ALICE, &mut ctx.gas_meter).unwrap();
exec_success()
});
ExtBuilder::default()
.existential_deposit(15)
.build()
.execute_with(|| {
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
set_balance(&ALICE, 1000);
assert_eq!(
ctx.instantiate(
100,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&terminate_ch,
vec![],
),
Err(Error::<Test>::NewContractNotFunded.into())
);
assert_eq!(
&events(),
&[]
);
});
}
#[test]
fn rent_allowance() {
let vm = MockVm::new();
let mut loader = MockLoader::empty();
let rent_allowance_ch = loader.insert(|ctx| {
assert_eq!(ctx.ext.rent_allowance(), <BalanceOf<Test>>::max_value());
ctx.ext.set_rent_allowance(10);
assert_eq!(ctx.ext.rent_allowance(), 10);
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let cfg = Config::preload();
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
set_balance(&ALICE, 100);
let result = ctx.instantiate(
cfg.subsistence_threshold(),
&mut GasMeter::<Test>::new(GAS_LIMIT),
&rent_allowance_ch,
vec![],
);
assert_matches!(result, Ok(_));
});
}
}