// This file is part of Substrate. // Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use crate::{ CodeHash, ConfigCache, Event, RawEvent, Config, Module as Contracts, TrieId, BalanceOf, ContractInfo, gas::GasMeter, rent::Rent, storage::{self, Storage}, Error, ContractInfoOf, Schedule, }; use sp_core::crypto::UncheckedFrom; use sp_std::{ prelude::*, marker::PhantomData, }; use sp_runtime::traits::{Bounded, Zero, Convert, Saturating}; use frame_support::{ dispatch::{DispatchResult, DispatchError}, traits::{ExistenceRequirement, Currency, Time, Randomness}, weights::Weight, ensure, StorageMap, }; use pallet_contracts_primitives::{ErrorOrigin, ExecError, ExecReturnValue, ExecResult, ReturnFlags}; 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; /// 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, } /// 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: Config; /// 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>) -> DispatchResult; /// 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, salt: &[u8], ) -> Result<(AccountIdOf, ExecReturnValue), ExecError>; /// Transfer some amount of funds into the specified account. fn transfer( &mut self, to: &AccountIdOf, value: BalanceOf, ) -> DispatchResult; /// 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, ) -> DispatchResult; /// 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, ) -> DispatchResult; /// 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; /// Get a reference to the schedule used by the current call. fn schedule(&self) -> &Schedule; } /// Describes the different functions that can be exported by an [`Executable`]. pub enum ExportedFunction { /// The constructor function which is executed on deployment of a contract. Constructor, /// The function which is executed when a contract is called. Call, } /// A trait that represents something that can be executed. /// /// In the on-chain environment this would be represented by a wasm module. This trait exists in /// order to be able to mock the wasm logic for testing. pub trait Executable: Sized { /// Load the executable from storage. fn from_storage(code_hash: CodeHash, schedule: &Schedule) -> Result; /// Load the module from storage without re-instrumenting it. /// /// A code module is re-instrumented on-load when it was originally instrumented with /// an older schedule. This skips this step for cases where the code storage is /// queried for purposes other than execution. fn from_storage_noinstr(code_hash: CodeHash) -> Result; /// Decrements the refcount by one and deletes the code if it drops to zero. fn drop_from_storage(self); /// Increment the refcount by one. Fails if the code does not exist on-chain. fn add_user(code_hash: CodeHash) -> DispatchResult; /// Decrement the refcount by one and remove the code when it drops to zero. fn remove_user(code_hash: CodeHash); /// Execute the specified exported function and return the result. /// /// When the specified function is `Constructor` the executable is stored and its /// refcount incremented. /// /// # Note /// /// This functions expects to be executed in a storage transaction that rolls back /// all of its emitted storage changes. fn execute>( self, ext: E, function: &ExportedFunction, input_data: Vec, gas_meter: &mut GasMeter, ) -> ExecResult; /// The code hash of the executable. fn code_hash(&self) -> &CodeHash; /// The storage that is occupied by the instrumented executable and its pristine source. /// /// The returned size is already divided by the number of users who share the code. /// /// # Note /// /// This works with the current in-memory value of refcount. When calling any contract /// without refetching this from storage the result can be inaccurate as it might be /// working with a stale value. Usually this inaccuracy is tolerable. fn occupied_storage(&self) -> u32; } pub struct ExecutionContext<'a, T: Config + 'a, E> { pub caller: Option<&'a ExecutionContext<'a, T, E>>, pub self_account: T::AccountId, pub self_trie_id: Option, pub depth: usize, pub config: &'a ConfigCache, pub timestamp: MomentOf, pub block_number: T::BlockNumber, _phantom: PhantomData, } impl<'a, T, E> ExecutionContext<'a, T, E> where T: Config, T::AccountId: UncheckedFrom + AsRef<[u8]>, E: Executable, { /// 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 ConfigCache) -> Self { ExecutionContext { caller: None, self_trie_id: None, self_account: origin, depth: 0, config: &cfg, timestamp: T::Time::now(), block_number: >::block_number(), _phantom: Default::default(), } } fn nested<'b, 'c: 'b>(&'c self, dest: T::AccountId, trie_id: TrieId) -> ExecutionContext<'b, T, E> { ExecutionContext { caller: Some(self), self_trie_id: Some(trie_id), self_account: dest, depth: self.depth + 1, config: self.config, timestamp: self.timestamp.clone(), block_number: self.block_number.clone(), _phantom: Default::default(), } } /// 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)? } let contract = >::get(&dest) .and_then(|contract| contract.get_alive()) .ok_or(Error::::NotCallable)?; let executable = E::from_storage(contract.code_hash, &self.config.schedule)?; // This charges the rent and denies access to a contract that is in need of // eviction by returning `None`. We cannot evict eagerly here because those // changes would be rolled back in case this contract is called by another // contract. // See: https://github.com/paritytech/substrate/issues/6439#issuecomment-648754324 let contract = Rent::::charge(&dest, contract, executable.occupied_storage())? .ok_or(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( TransferCause::Call, transactor_kind, &caller, &dest, value, nested, )? } let output = executable.execute( nested.new_call_context(caller, value), &ExportedFunction::Call, 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, executable: E, input_data: Vec, salt: &[u8], ) -> Result<(T::AccountId, ExecReturnValue), ExecError> { if self.depth == self.config.max_depth as usize { Err(Error::::MaxCallDepthReached)? } let transactor_kind = self.transactor_kind(); let caller = self.self_account.clone(); let dest = Contracts::::contract_address(&caller, executable.code_hash(), salt); let output = frame_support::storage::with_transaction(|| { // Generate the trie id in a new transaction to only increment the counter on success. let dest_trie_id = Storage::::generate_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"), executable.code_hash().clone() )?; // Send funds unconditionally here. If the `endowment` is below existential_deposit // then error will be returned here. transfer( TransferCause::Instantiate, transactor_kind, &caller, &dest, endowment, nested, )?; // Cache the value before calling into the constructor because that // consumes the value. If the constructor creates additional contracts using // the same code hash we still charge the "1 block rent" as if they weren't // spawned. This is OK as overcharging is always safe. let occupied_storage = executable.occupied_storage(); let output = executable.execute( nested.new_call_context(caller.clone(), endowment), &ExportedFunction::Constructor, input_data, gas_meter, ).map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?; // We need to re-fetch the contract because changes are written to storage // eagerly during execution. let contract = >::get(&dest) .and_then(|contract| contract.get_alive()) .ok_or(Error::::NotCallable)?; // Collect the rent for the first block to prevent the creation of very large // contracts that never intended to pay for even one block. // This also makes sure that it is 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`. Rent::::charge(&dest, contract, occupied_storage)? .ok_or(Error::::NewContractNotFunded)?; // Deposit an instantiation event. deposit_event::(vec![], RawEvent::Instantiated(caller.clone(), dest.clone())); Ok(output) }); use frame_support::storage::TransactionOutcome::*; match output { Ok(_) => Commit(output), Err(_) => Rollback(output), } })?; Ok((dest, output)) } fn new_call_context<'b>( &'b mut self, caller: T::AccountId, value: BalanceOf, ) -> CallContext<'b, 'a, T, E> { let timestamp = self.timestamp.clone(); let block_number = self.block_number.clone(); CallContext { ctx: self, caller, value_transferred: value, timestamp, block_number, _phantom: Default::default(), } } /// 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 } } } /// Describes possible transfer causes. enum TransferCause { Call, Instantiate, Terminate, } /// Transfer some funds from `transactor` to `dest`. /// /// We only allow allow for draining all funds of the sender if `cause` is /// is specified as `Terminate`. Otherwise, any transfer that would bring the sender below the /// subsistence threshold (for contracts) or the existential deposit (for plain accounts) /// results in an error. fn transfer<'a, T: Config, E>( cause: TransferCause, origin: TransactorKind, transactor: &T::AccountId, dest: &T::AccountId, value: BalanceOf, ctx: &mut ExecutionContext<'a, T, E>, ) -> DispatchResult where T::AccountId: UncheckedFrom + AsRef<[u8]>, E: Executable, { use self::TransferCause::*; use self::TransactorKind::*; // 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: Config + 'b, E> { ctx: &'a mut ExecutionContext<'b, T, E>, caller: T::AccountId, value_transferred: BalanceOf, timestamp: MomentOf, block_number: T::BlockNumber, _phantom: PhantomData, } impl<'a, 'b: 'a, T, E> Ext for CallContext<'a, 'b, T, E> where T: Config + 'b, T::AccountId: UncheckedFrom + AsRef<[u8]>, E: Executable, { 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(trie_id, key) } fn set_storage(&mut self, key: StorageKey, value: Option>) -> DispatchResult { 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", ); // write panics if the passed account is not alive. // the contract must be in the alive state within the `CallContext`;\ // the contract cannot be absent in storage; // write cannot return `None`; // qed Storage::::write(&self.ctx.self_account, trie_id, &key, value) } fn instantiate( &mut self, code_hash: CodeHash, endowment: BalanceOf, gas_meter: &mut GasMeter, input_data: Vec, salt: &[u8], ) -> Result<(AccountIdOf, ExecReturnValue), ExecError> { let executable = E::from_storage(code_hash, &self.ctx.config.schedule)?; let result = self.ctx.instantiate(endowment, gas_meter, executable, input_data, salt)?; Ok(result) } fn transfer( &mut self, to: &T::AccountId, value: BalanceOf, ) -> DispatchResult { transfer( TransferCause::Call, TransactorKind::Contract, &self.ctx.self_account.clone(), to, value, self.ctx, ) } fn terminate( &mut self, beneficiary: &AccountIdOf, ) -> DispatchResult { 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(Error::::ReentranceDenied.into()); } } transfer( TransferCause::Terminate, TransactorKind::Contract, &self_id, beneficiary, value, self.ctx, )?; if let Some(ContractInfo::Alive(info)) = ContractInfoOf::::take(&self_id) { Storage::::queue_trie_for_deletion(&info)?; E::remove_user(info.code_hash); Contracts::::deposit_event(RawEvent::Terminated(self_id, beneficiary.clone())); Ok(()) } else { panic!( "this function is only invoked by in the context of a contract;\ this contract is therefore alive;\ qed" ); } } 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, ) -> DispatchResult { if let Some(caller_ctx) = self.ctx.caller { if caller_ctx.is_live(&self.ctx.self_account) { return Err(Error::::ReentranceDenied.into()); } } let result = 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::ContractEmitted(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 schedule(&self) -> &Schedule { &self.ctx.config.schedule } } 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::*; use crate::{ gas::GasMeter, tests::{ExtBuilder, Test, MetaEvent}, gas::Gas, storage::Storage, tests::{ ALICE, BOB, CHARLIE, test_utils::{place_contract, set_balance, get_balance}, }, Error, }; use sp_runtime::DispatchError; use assert_matches::assert_matches; use std::{cell::RefCell, collections::HashMap, rc::Rc}; type MockContext<'a> = ExecutionContext<'a, Test, MockExecutable>; const GAS_LIMIT: Gas = 10_000_000_000; thread_local! { static LOADER: RefCell = RefCell::new(MockLoader::default()); } 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(Rc ExecResult + 'static>, CodeHash); #[derive(Default)] struct MockLoader { map: HashMap, MockExecutable>, counter: u64, } impl MockLoader { fn insert(f: impl Fn(MockCtx) -> ExecResult + 'static) -> CodeHash { LOADER.with(|loader| { let mut loader = loader.borrow_mut(); // Generate code hashes as monotonically increasing values. let hash = ::Hash::from_low_u64_be(loader.counter); loader.counter += 1; loader.map.insert(hash, MockExecutable (Rc::new(f), hash.clone())); hash }) } } impl Executable for MockExecutable { fn from_storage( code_hash: CodeHash, _schedule: &Schedule ) -> Result { Self::from_storage_noinstr(code_hash) } fn from_storage_noinstr(code_hash: CodeHash) -> Result { LOADER.with(|loader| { loader.borrow_mut() .map .get(&code_hash) .cloned() .ok_or(Error::::CodeNotFound.into()) }) } fn drop_from_storage(self) {} fn add_user(_code_hash: CodeHash) -> DispatchResult { Ok(()) } fn remove_user(_code_hash: CodeHash) {} fn execute>( self, mut ext: E, _function: &ExportedFunction, input_data: Vec, gas_meter: &mut GasMeter, ) -> ExecResult { (self.0)(MockCtx { ext: &mut ext, input_data, gas_meter, }) } fn code_hash(&self) -> &CodeHash { &self.1 } fn occupied_storage(&self) -> u32 { 0 } } fn exec_success() -> ExecResult { Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }) } #[test] fn it_works() { thread_local! { static TEST_DATA: RefCell> = RefCell::new(vec![0]); } let value = Default::default(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); let exec_ch = MockLoader::insert(|_ctx| { TEST_DATA.with(|data| data.borrow_mut().push(1)); exec_success() }); ExtBuilder::default().build().execute_with(|| { let cfg = ConfigCache::preload(); let mut ctx = MockContext::top_level(ALICE, &cfg); place_contract(&BOB, exec_ch); assert_matches!( ctx.call(BOB, value, &mut gas_meter, vec![]), Ok(_) ); }); TEST_DATA.with(|data| assert_eq!(*data.borrow(), vec![0, 1])); } #[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; ExtBuilder::default().build().execute_with(|| { let cfg = ConfigCache::preload(); let mut ctx = MockContext::top_level(origin.clone(), &cfg); set_balance(&origin, 100); set_balance(&dest, 0); super::transfer( 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 return_ch = MockLoader::insert( |_| Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: Vec::new() }) ); ExtBuilder::default().build().execute_with(|| { let cfg = ConfigCache::preload(); let mut ctx = MockContext::top_level(origin.clone(), &cfg); place_contract(&BOB, return_ch); set_balance(&origin, 100); let balance = get_balance(&dest); let output = ctx.call( dest.clone(), 55, &mut GasMeter::::new(GAS_LIMIT), vec![], ).unwrap(); assert!(!output.is_success()); assert_eq!(get_balance(&origin), 100); // the rent is still charged assert!(get_balance(&dest) < balance); }); } #[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; ExtBuilder::default().build().execute_with(|| { let cfg = ConfigCache::preload(); let mut ctx = MockContext::top_level(origin.clone(), &cfg); set_balance(&origin, 0); let result = super::transfer( 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 return_ch = MockLoader::insert( |_| Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![1, 2, 3, 4] }) ); ExtBuilder::default().build().execute_with(|| { let cfg = ConfigCache::preload(); let mut ctx = MockContext::top_level(origin, &cfg); 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 return_ch = MockLoader::insert( |_| Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![1, 2, 3, 4] }) ); ExtBuilder::default().build().execute_with(|| { let cfg = ConfigCache::preload(); let mut ctx = MockContext::top_level(origin, &cfg); 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 input_data_ch = MockLoader::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 = ConfigCache::preload(); let mut ctx = MockContext::top_level(ALICE, &cfg); 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 input_data_ch = MockLoader::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 = ConfigCache::preload(); let mut ctx = MockContext::top_level(ALICE, &cfg); set_balance(&ALICE, cfg.subsistence_threshold() * 10); let result = ctx.instantiate( cfg.subsistence_threshold() * 3, &mut GasMeter::::new(GAS_LIMIT), MockExecutable::from_storage(input_data_ch, &cfg.schedule).unwrap(), 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. thread_local! { static REACHED_BOTTOM: RefCell = RefCell::new(false); } let value = Default::default(); let recurse_ch = MockLoader::insert(|ctx| { // Try to call into yourself. let r = ctx.ext.call(&BOB, 0, ctx.gas_meter, vec![]); REACHED_BOTTOM.with(|reached_bottom| { 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 = ConfigCache::preload(); let mut ctx = MockContext::top_level(ALICE, &cfg); 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; thread_local! { static WITNESSED_CALLER_BOB: RefCell>> = RefCell::new(None); static WITNESSED_CALLER_CHARLIE: RefCell>> = RefCell::new(None); } let bob_ch = MockLoader::insert(|ctx| { // Record the caller for bob. WITNESSED_CALLER_BOB.with(|caller| *caller.borrow_mut() = Some(ctx.ext.caller().clone()) ); // Call into CHARLIE contract. assert_matches!( ctx.ext.call(&CHARLIE, 0, ctx.gas_meter, vec![]), Ok(_) ); exec_success() }); let charlie_ch = MockLoader::insert(|ctx| { // Record the caller for charlie. WITNESSED_CALLER_CHARLIE.with(|caller| *caller.borrow_mut() = Some(ctx.ext.caller().clone()) ); exec_success() }); ExtBuilder::default().build().execute_with(|| { let cfg = ConfigCache::preload(); let mut ctx = MockContext::top_level(origin.clone(), &cfg); place_contract(&dest, bob_ch); place_contract(&CHARLIE, charlie_ch); let result = ctx.call( dest.clone(), 0, &mut GasMeter::::new(GAS_LIMIT), vec![], ); assert_matches!(result, Ok(_)); }); WITNESSED_CALLER_BOB.with(|caller| assert_eq!(*caller.borrow(), Some(origin))); WITNESSED_CALLER_CHARLIE.with(|caller| assert_eq!(*caller.borrow(), Some(dest))); } #[test] fn address_returns_proper_values() { let bob_ch = MockLoader::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 = MockLoader::insert(|ctx| { assert_eq!(*ctx.ext.address(), CHARLIE); exec_success() }); ExtBuilder::default().build().execute_with(|| { let cfg = ConfigCache::preload(); let mut ctx = MockContext::top_level(ALICE, &cfg); 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 dummy_ch = MockLoader::insert(|_| exec_success()); ExtBuilder::default().existential_deposit(15).build().execute_with(|| { let cfg = ConfigCache::preload(); let mut ctx = MockContext::top_level(ALICE, &cfg); assert_matches!( ctx.instantiate( 0, // <- zero endowment &mut GasMeter::::new(GAS_LIMIT), MockExecutable::from_storage(dummy_ch, &cfg.schedule).unwrap(), vec![], &[], ), Err(_) ); }); } #[test] fn instantiation_work_with_success_output() { let dummy_ch = MockLoader::insert( |_| Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![80, 65, 83, 83] }) ); ExtBuilder::default().existential_deposit(15).build().execute_with(|| { let cfg = ConfigCache::preload(); let mut ctx = MockContext::top_level(ALICE, &cfg); set_balance(&ALICE, 1000); let instantiated_contract_address = assert_matches!( ctx.instantiate( 100, &mut GasMeter::::new(GAS_LIMIT), MockExecutable::from_storage(dummy_ch, &cfg.schedule).unwrap(), 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 dummy_ch = MockLoader::insert( |_| Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![70, 65, 73, 76] }) ); ExtBuilder::default().existential_deposit(15).build().execute_with(|| { let cfg = ConfigCache::preload(); let mut ctx = MockContext::top_level(ALICE, &cfg); set_balance(&ALICE, 1000); let instantiated_contract_address = assert_matches!( ctx.instantiate( 100, &mut GasMeter::::new(GAS_LIMIT), MockExecutable::from_storage(dummy_ch, &cfg.schedule).unwrap(), 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 dummy_ch = MockLoader::insert(|_| exec_success()); let instantiated_contract_address = Rc::new(RefCell::new(None::>)); let instantiator_ch = MockLoader::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, ConfigCache::::subsistence_threshold_uncached() * 3, ctx.gas_meter, vec![], &[48, 49, 50], ).unwrap(); *instantiated_contract_address.borrow_mut() = address.into(); Ok(output) } }); ExtBuilder::default().existential_deposit(15).build().execute_with(|| { let cfg = ConfigCache::preload(); let mut ctx = MockContext::top_level(ALICE, &cfg); set_balance(&ALICE, cfg.subsistence_threshold() * 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 dummy_ch = MockLoader::insert( |_| Err("It's a trap!".into()) ); let instantiator_ch = MockLoader::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 = ConfigCache::preload(); let mut ctx = MockContext::top_level(ALICE, &cfg); 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 terminate_ch = MockLoader::insert(|ctx| { ctx.ext.terminate(&ALICE).unwrap(); exec_success() }); ExtBuilder::default() .existential_deposit(15) .build() .execute_with(|| { let cfg = ConfigCache::preload(); let mut ctx = MockContext::top_level(ALICE, &cfg); set_balance(&ALICE, 1000); assert_eq!( ctx.instantiate( 100, &mut GasMeter::::new(GAS_LIMIT), MockExecutable::from_storage(terminate_ch, &cfg.schedule).unwrap(), vec![], &[], ), Err(Error::::NotCallable.into()) ); assert_eq!( &events(), &[] ); }); } #[test] fn rent_allowance() { let rent_allowance_ch = MockLoader::insert(|ctx| { let allowance = ConfigCache::::subsistence_threshold_uncached() * 3; assert_eq!(ctx.ext.rent_allowance(), >::max_value()); ctx.ext.set_rent_allowance(allowance); assert_eq!(ctx.ext.rent_allowance(), allowance); exec_success() }); ExtBuilder::default().build().execute_with(|| { let cfg = ConfigCache::preload(); let mut ctx = MockContext::top_level(ALICE, &cfg); set_balance(&ALICE, cfg.subsistence_threshold() * 10); let result = ctx.instantiate( cfg.subsistence_threshold() * 5, &mut GasMeter::::new(GAS_LIMIT), MockExecutable::from_storage(rent_allowance_ch, &cfg.schedule).unwrap(), vec![], &[], ); assert_matches!(result, Ok(_)); }); } }