// Copyright 2018-2020 Parity Technologies (UK) Ltd. // This file is part of Substrate. // Substrate is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Substrate is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . 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 = ::AccountId; pub type MomentOf = <::Time as Time>::Moment; pub type SeedOf = ::Hash; pub type BlockNumberOf = ::BlockNumber; pub type StorageKey = [u8; 32]; /// A type that represents a topic of an event. At the moment a hash is used. pub type TopicOf = ::Hash; bitflags! { /// Flags used by a contract to customize exit behaviour. pub struct ReturnFlags: u32 { /// If this bit is set all changes made by the contract exection are rolled back. const REVERT = 0x0000_0001; } } /// Describes whether we deal with a contract or a plain account. pub enum TransactorKind { /// Transaction was initiated from a plain account. That can be either be through a /// signed transaction or through RPC. PlainAccount, /// The call was initiated by a contract account. Contract, } /// Output of a contract call or instantiation which ran to completion. #[cfg_attr(test, derive(PartialEq, Eq, Debug))] pub struct ExecReturnValue { /// Flags passed along by `seal_return`. Empty when `seal_return` was never called. pub flags: ReturnFlags, /// Buffer passed along by `seal_return`. Empty when `seal_return` was never called. pub data: Vec, } impl ExecReturnValue { /// We understand the absense of a revert flag as success. pub fn is_success(&self) -> bool { !self.flags.contains(ReturnFlags::REVERT) } } /// Call or instantiate both call into other contracts and pass through errors happening /// in those to the caller. This enum is for the caller to distinguish whether the error /// happened during the execution of the callee or in the current execution context. #[cfg_attr(test, derive(PartialEq, Eq, Debug))] pub enum ErrorOrigin { /// The error happened in the current exeuction context rather than in the one /// of the contract that is called into. Caller, /// The error happened during execution of the called contract. Callee, } /// Error returned by contract exection. #[cfg_attr(test, derive(PartialEq, Eq, Debug))] pub struct ExecError { /// The reason why the execution failed. pub error: DispatchError, /// Origin of the error. pub origin: ErrorOrigin, } impl> From for ExecError { fn from(error: T) -> Self { Self { error: error.into(), origin: ErrorOrigin::Caller, } } } /// The result that is returned from contract execution. It either contains the output /// buffer or an error describing the reason for failure. pub type ExecResult = Result; /// An interface that provides access to the external environment in which the /// smart-contract is executed. /// /// This interface is specialized to an account of the executing code, so all /// operations are implicitly performed on that account. pub trait Ext { type T: Trait; /// Returns the storage entry of the executing account by the given `key`. /// /// Returns `None` if the `key` wasn't previously set by `set_storage` or /// was deleted. fn get_storage(&self, key: &StorageKey) -> Option>; /// Sets the storage entry by the given key to the specified value. If `value` is `None` then /// the storage entry is deleted. fn set_storage(&mut self, key: StorageKey, value: Option>); /// Instantiate a contract from the given code. /// /// The newly created account will be associated with `code`. `value` specifies the amount of value /// transferred from this to the newly created account (also known as endowment). fn instantiate( &mut self, code: &CodeHash, value: BalanceOf, gas_meter: &mut GasMeter, input_data: Vec, ) -> Result<(AccountIdOf, ExecReturnValue), ExecError>; /// Transfer some amount of funds into the specified account. fn transfer( &mut self, to: &AccountIdOf, value: BalanceOf, gas_meter: &mut GasMeter, ) -> Result<(), DispatchError>; /// Transfer all funds to `beneficiary` and delete the contract. /// /// Since this function removes the self contract eagerly, if succeeded, no further actions should /// be performed on this `Ext` instance. /// /// This function will fail if the same contract is present on the contract /// call stack. fn terminate( &mut self, beneficiary: &AccountIdOf, gas_meter: &mut GasMeter, ) -> Result<(), DispatchError>; /// Call (possibly transferring some amount of funds) into the specified account. fn call( &mut self, to: &AccountIdOf, value: BalanceOf, gas_meter: &mut GasMeter, input_data: Vec, ) -> ExecResult; /// Restores the given destination contract sacrificing the current one. /// /// Since this function removes the self contract eagerly, if succeeded, no further actions should /// be performed on this `Ext` instance. /// /// This function will fail if the same contract is present /// on the contract call stack. fn restore_to( &mut self, dest: AccountIdOf, code_hash: CodeHash, rent_allowance: BalanceOf, delta: Vec, ) -> Result<(), &'static str>; /// Returns a reference to the account id of the caller. fn caller(&self) -> &AccountIdOf; /// Returns a reference to the account id of the current contract. fn address(&self) -> &AccountIdOf; /// Returns the balance of the current contract. /// /// The `value_transferred` is already added. fn balance(&self) -> BalanceOf; /// Returns the value transferred along with this call or as endowment. fn value_transferred(&self) -> BalanceOf; /// Returns a reference to the timestamp of the current block fn now(&self) -> &MomentOf; /// Returns the minimum balance that is required for creating an account. fn minimum_balance(&self) -> BalanceOf; /// Returns the deposit required to create a tombstone upon contract eviction. fn tombstone_deposit(&self) -> BalanceOf; /// Returns a random number for the current block with the given subject. fn random(&self, subject: &[u8]) -> SeedOf; /// Deposit an event with the given topics. /// /// There should not be any duplicates in `topics`. fn deposit_event(&mut self, topics: Vec>, data: Vec); /// Set rent allowance of the contract fn set_rent_allowance(&mut self, rent_allowance: BalanceOf); /// Rent allowance of the contract fn rent_allowance(&self) -> BalanceOf; /// Returns the current block number. fn block_number(&self) -> BlockNumberOf; /// Returns the maximum allowed size of a storage item. fn max_value_size(&self) -> u32; /// Returns the price for the specified amount of weight. fn get_weight_price(&self, weight: Weight) -> BalanceOf; } /// Loader is a companion of the `Vm` trait. It loads an appropriate abstract /// executable to be executed by an accompanying `Vm` implementation. pub trait Loader { type Executable; /// Load the initializer portion of the code specified by the `code_hash`. This /// executable is called upon instantiation. fn load_init(&self, code_hash: &CodeHash) -> Result; /// Load the main portion of the code specified by the `code_hash`. This executable /// is called for each call to a contract. fn load_main(&self, code_hash: &CodeHash) -> Result; } /// A trait that represent a virtual machine. /// /// You can view a virtual machine as something that takes code, an input data buffer, /// queries it and/or performs actions on the given `Ext` and optionally /// returns an output data buffer. The type of code depends on the particular virtual machine. /// /// Execution of code can end by either implicit termination (that is, reached the end of /// executable), explicit termination via returning a buffer or termination due to a trap. pub trait Vm { type Executable; fn execute>( &self, exec: &Self::Executable, ext: E, input_data: Vec, gas_meter: &mut GasMeter, ) -> ExecResult; } #[cfg_attr(test, derive(Debug, PartialEq, Eq))] #[derive(Copy, Clone)] pub enum ExecFeeToken { /// Base fee charged for a call. Call, /// Base fee charged for a instantiate. Instantiate, } impl Token for ExecFeeToken { type Metadata = Config; #[inline] fn calculate_amount(&self, metadata: &Config) -> 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, pub depth: usize, pub config: &'a Config, pub vm: &'a V, pub loader: &'a L, pub timestamp: MomentOf, pub block_number: T::BlockNumber, } impl<'a, T, E, V, L> ExecutionContext<'a, T, V, L> where T: Trait, L: Loader, V: Vm, { /// Create the top level execution context. /// /// The specified `origin` address will be used as `sender` for. The `origin` must be a regular /// account (not a contract). pub fn top_level(origin: T::AccountId, cfg: &'a Config, 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: >::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(), } } /// Make a call to the specified address, optionally transferring some funds. pub fn call( &mut self, dest: T::AccountId, value: BalanceOf, gas_meter: &mut GasMeter, input_data: Vec, ) -> ExecResult { if self.depth == self.config.max_depth as usize { Err(Error::::MaxCallDepthReached)? } if gas_meter .charge(self.config, ExecFeeToken::Call) .is_out_of_gas() { Err(Error::::OutOfGas)? } // Assumption: `collect_rent` doesn't collide with overlay because // `collect_rent` will be done on first call and destination contract and balance // cannot be changed before the first call // We do not allow 'calling' plain accounts. For transfering value // `seal_transfer` must be used. let contract = if let Some(ContractInfo::Alive(info)) = rent::collect_rent::(&dest) { info } else { Err(Error::::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::::zero() { transfer( gas_meter, TransferCause::Call, transactor_kind, &caller, &dest, value, nested, )? } let executable = nested.loader.load_main(&contract.code_hash) .map_err(|_| Error::::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, gas_meter: &mut GasMeter, code_hash: &CodeHash, input_data: Vec, ) -> Result<(T::AccountId, ExecReturnValue), ExecError> { if self.depth == self.config.max_depth as usize { Err(Error::::MaxCallDepthReached)? } if gas_meter .charge(self.config, ExecFeeToken::Instantiate) .is_out_of_gas() { Err(Error::::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, ); // TrieId has not been generated yet and storage is empty since contract is new. // // Generate it now. let dest_trie_id = ::TrieIdGenerator::trie_id(&dest); let output = self.with_nested_context(dest.clone(), dest_trie_id, |nested| { storage::place_contract::( &dest, nested .self_trie_id .clone() .expect("the nested context always has to have self_trie_id"), code_hash.clone() )?; // Send funds unconditionally here. If the `endowment` is below existential_deposit // then error will be returned here. transfer( gas_meter, TransferCause::Instantiate, transactor_kind, &caller, &dest, endowment, nested, )?; let executable = nested.loader.load_init(&code_hash) .map_err(|_| Error::::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 })?; // We need each contract that exists to be above the subsistence threshold // in order to keep up the guarantuee that we always leave a tombstone behind // with the exception of a contract that called `seal_terminate`. if T::Currency::total_balance(&dest) < nested.config.subsistence_threshold() { Err(Error::::NewContractNotFunded)? } // Deposit an instantiation event. deposit_event::(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, ) -> 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, } } /// Execute the given closure within a nested execution context. fn with_nested_context(&mut self, dest: T::AccountId, trie_id: TrieId, func: F) -> ExecResult where F: FnOnce(&mut ExecutionContext) -> 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), } }) } /// Returns whether a contract, identified by address, is currently live in the execution /// stack, meaning it is in the middle of an execution. 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::::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 Token for TransferFeeToken { type Metadata = Config; #[inline] fn calculate_amount(&self, metadata: &Config) -> Gas { match self.kind { TransferFeeKind::ContractInstantiate => metadata.schedule.instantiate_cost, TransferFeeKind::Transfer => metadata.schedule.transfer_cost, } } } /// Describes possible transfer causes. enum TransferCause { Call, Instantiate, Terminate, } /// Transfer some funds from `transactor` to `dest`. /// /// All balance changes are performed in the `overlay`. /// /// This function also handles charging the fee. The fee depends /// on whether the transfer happening because of contract instantiation /// (transferring endowment) or because of a transfer via `call`. This /// is specified using the `cause` parameter. /// /// NOTE: that the fee is denominated in `BalanceOf` units, but /// charged in `Gas` from the provided `gas_meter`. This means /// that the actual amount charged might differ. /// /// NOTE: that we allow for draining all funds of the contract so it /// can go below existential deposit, essentially giving a contract /// the chance to give up it's life. fn transfer<'a, T: Trait, V: Vm, L: Loader>( gas_meter: &mut GasMeter, cause: TransferCause, origin: TransactorKind, transactor: &T::AccountId, dest: &T::AccountId, value: BalanceOf, 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 { // If this function is called from `Instantiate` routine, then we always // charge contract account creation fee. Instantiate => ContractInstantiate, // Otherwise the fee is to transfer to an account. Call | Terminate => TransferFeeKind::Transfer, }; TransferFeeToken { kind, } }; if gas_meter.charge(ctx.config, token).is_out_of_gas() { Err(Error::::OutOfGas)? } // Only seal_terminate is allowed to bring the sender below the subsistence // threshold or even existential deposit. let existence_requirement = match (cause, origin) { (Terminate, _) => ExistenceRequirement::AllowDeath, (_, Contract) => { ensure!( T::Currency::total_balance(transactor).saturating_sub(value) >= ctx.config.subsistence_threshold(), Error::::BelowSubsistenceThreshold, ); ExistenceRequirement::KeepAlive }, (_, PlainAccount) => ExistenceRequirement::KeepAlive, }; T::Currency::transfer(transactor, dest, value, existence_requirement) .map_err(|_| Error::::TransferFailed)?; Ok(()) } /// A context that is active within a call. /// /// This context has some invariants that must be held at all times. Specifically: ///`ctx` always points to a context of an alive contract. That implies that it has an existent /// `self_trie_id`. /// /// Be advised that there are brief time spans where these invariants could be invalidated. /// For example, when a contract requests self-termination the contract is removed eagerly. That /// implies that the control won't be returned to the contract anymore, but there is still some code /// on the path of the return from that call context. Therefore, care must be taken in these /// situations. struct CallContext<'a, 'b: 'a, T: Trait + 'b, V: Vm + 'b, L: Loader> { ctx: &'a mut ExecutionContext<'b, T, V, L>, caller: T::AccountId, value_transferred: BalanceOf, timestamp: MomentOf, 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, L: Loader, { type T = T; fn get_storage(&self, key: &StorageKey) -> Option> { 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>) { 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::(&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, endowment: BalanceOf, gas_meter: &mut GasMeter, input_data: Vec, ) -> Result<(AccountIdOf, ExecReturnValue), ExecError> { self.ctx.instantiate(endowment, gas_meter, code_hash, input_data) } fn transfer( &mut self, to: &T::AccountId, value: BalanceOf, gas_meter: &mut GasMeter, ) -> Result<(), DispatchError> { transfer( gas_meter, TransferCause::Call, TransactorKind::Contract, &self.ctx.self_account.clone(), &to, value, self.ctx, ) } fn terminate( &mut self, beneficiary: &AccountIdOf, gas_meter: &mut GasMeter, ) -> 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::(&self_id, self_trie_id); Ok(()) } fn call( &mut self, to: &T::AccountId, value: BalanceOf, gas_meter: &mut GasMeter, input_data: Vec, ) -> ExecResult { self.ctx.call(to.clone(), value, gas_meter, input_data) } fn restore_to( &mut self, dest: AccountIdOf, code_hash: CodeHash, rent_allowance: BalanceOf, delta: Vec, ) -> 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::( self.ctx.self_account.clone(), dest.clone(), code_hash.clone(), rent_allowance, delta, ); if let Ok(_) = result { deposit_event::( 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::Currency::free_balance(&self.ctx.self_account) } fn value_transferred(&self) -> BalanceOf { self.value_transferred } fn random(&self, subject: &[u8]) -> SeedOf { T::Randomness::random(subject) } fn now(&self) -> &MomentOf { &self.timestamp } fn minimum_balance(&self) -> BalanceOf { self.ctx.config.existential_deposit } fn tombstone_deposit(&self) -> BalanceOf { self.ctx.config.tombstone_deposit } fn deposit_event(&mut self, topics: Vec, data: Vec) { deposit_event::( topics, RawEvent::ContractExecution(self.ctx.self_account.clone(), data) ); } fn set_rent_allowance(&mut self, rent_allowance: BalanceOf) { if let Err(storage::ContractAbsentError) = storage::set_rent_allowance::(&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 { storage::rent_allowance::(&self.ctx.self_account) .unwrap_or_else(|_| >::max_value()) // Must never be triggered actually } 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 { T::WeightPrice::convert(weight) } } fn deposit_event( topics: Vec, event: Event, ) { >::deposit_event_indexed( &*topics, ::Event::from(event).into(), ) } /// These tests exercise the executive layer. /// /// In these tests the VM/loader are mocked. Instead of dealing with wasm bytecode they use simple closures. /// This allows you to tackle executive logic more thoroughly without writing a /// wasm VM code. #[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> { >::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, input_data: Vec, gas_meter: &'a mut GasMeter, } #[derive(Clone)] struct MockExecutable<'a>(Rc ExecResult + 'a>); impl<'a> MockExecutable<'a> { fn new(f: impl Fn(MockCtx) -> ExecResult + 'a) -> Self { MockExecutable(Rc::new(f)) } } struct MockLoader<'a> { map: HashMap, 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 { // Generate code hashes as monotonically increasing values. let code_hash = ::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 for MockLoader<'a> { type Executable = MockExecutable<'a>; fn load_init(&self, code_hash: &CodeHash) -> Result { self.map .get(code_hash) .cloned() .ok_or_else(|| "code not found") } fn load_main(&self, code_hash: &CodeHash) -> Result { self.map .get(code_hash) .cloned() .ok_or_else(|| "code not found") } } impl<'a> Vm for MockVm<'a> { type Executable = MockExecutable<'a>; fn execute>( &self, exec: &MockExecutable, mut ext: E, input_data: Vec, gas_meter: &mut GasMeter, ) -> 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::::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; // This test verifies that base fee for call is taken. 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::::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 },); }); // This test verifies that base fee for instantiation is taken. 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::::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() { // This test verifies that a contract is able to transfer // some funds to another account. 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::::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() { // This test verifies that changes are reverted on a call which fails (or equally, returns // a non-zero status code). 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::::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; // This test sends 50 units of currency to a non-existent account. // This should lead to creation of a new account thus // a fee should be charged. 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::::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, }, ); }); // This one is similar to the previous one but transfer to an existing account. // In this test we expect that a regular transfer fee is charged. 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::::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, }, ); }); // This test sends 50 units of currency as an endowment to a newly // instantiated contract. 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::::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() { // This test verifies that a contract can't send value if it's // balance is 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::::new(GAS_LIMIT), super::TransferCause::Call, super::TransactorKind::PlainAccount, &origin, &dest, 100, &mut ctx, ); assert_eq!( result, Err(Error::::TransferFailed.into()) ); assert_eq!(get_balance(&origin), 0); assert_eq!(get_balance(&dest), 0); }); } #[test] fn output_is_returned_on_success() { // Verifies that if a contract returns data with a successful exit status, this data // is returned from the execution context. 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::::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() { // Verifies that if a contract returns data with a failing exit status, this data // is returned from the execution context. 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::::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() }); // This one tests passing the input data into a contract via call. 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::::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() }); // This one tests passing the input data into a contract via instantiate. 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::::new(GAS_LIMIT), &input_data_ch, vec![1, 2, 3, 4], ); assert_matches!(result, Ok(_)); }); } #[test] fn max_depth() { // This test verifies that when we reach the maximal depth creation of an // yet another context fails. 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| { // Try to call into yourself. let r = ctx.ext.call(&BOB, 0, ctx.gas_meter, vec![]); let mut reached_bottom = reached_bottom.borrow_mut(); if !*reached_bottom { // We are first time here, it means we just reached bottom. // Verify that we've got proper error and set `reached_bottom`. assert_eq!( r, Err(Error::::MaxCallDepthReached.into()) ); *reached_bottom = true; } else { // We just unwinding stack here. 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::::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::); let witnessed_caller_charlie = RefCell::new(None::); let mut loader = MockLoader::empty(); let bob_ch = loader.insert(|ctx| { // Record the caller for bob. *witnessed_caller_bob.borrow_mut() = Some(*ctx.ext.caller()); // Call into CHARLIE contract. assert_matches!( ctx.ext.call(&CHARLIE, 0, ctx.gas_meter, vec![]), Ok(_) ); exec_success() }); let charlie_ch = loader.insert(|ctx| { // Record the caller for charlie. *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::::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| { // Verify that address matches BOB. assert_eq!(*ctx.ext.address(), BOB); // Call into charlie contract. 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::::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, // <- zero endowment &mut GasMeter::::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::::new(GAS_LIMIT), &dummy_ch, vec![], ), Ok((address, ref output)) if output.data == vec![80, 65, 83, 83] => address ); // Check that the newly created account has the expected code hash and // there are instantiation event. assert_eq!(storage::code_hash::(&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::::new(GAS_LIMIT), &dummy_ch, vec![], ), Ok((address, ref output)) if output.data == vec![70, 65, 73, 76] => address ); // Check that the account has not been created. assert!(storage::code_hash::(&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::)); let instantiator_ch = loader.insert({ let dummy_ch = dummy_ch.clone(); let instantiated_contract_address = Rc::clone(&instantiated_contract_address); move |ctx| { // Instantiate a contract and save it's address in `instantiated_contract_address`. let (address, output) = ctx.ext.instantiate( &dummy_ch, Config::::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::::new(GAS_LIMIT), vec![]), Ok(_) ); let instantiated_contract_address = instantiated_contract_address.borrow().as_ref().unwrap().clone(); // Check that the newly created account has the expected code hash and // there are instantiation event. assert_eq!(storage::code_hash::(&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| { // Instantiate a contract and save it's address in `instantiated_contract_address`. 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::::new(GAS_LIMIT), vec![]), Ok(_) ); // The contract wasn't instantiated so we don't expect to see an instantiation // event here. 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::::new(GAS_LIMIT), &terminate_ch, vec![], ), Err(Error::::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(), >::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::::new(GAS_LIMIT), &rent_allowance_ch, vec![], ); assert_matches!(result, Ok(_)); }); } }