// This file is part of Substrate. // Copyright (C) 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::{ debug::{CallInterceptor, CallSpan, Tracing}, gas::GasMeter, primitives::{ExecReturnValue, StorageDeposit}, storage::{self, meter::Diff, WriteOutcome}, BalanceOf, CodeHash, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf, DebugBufferVec, Determinism, Error, Event, Nonce, Origin, Pallet as Contracts, Schedule, LOG_TARGET, }; use frame_support::{ crypto::ecdsa::ECDSAExt, dispatch::{DispatchResult, DispatchResultWithPostInfo}, ensure, storage::{with_transaction, TransactionOutcome}, traits::{ fungible::{Inspect, Mutate}, tokens::{Fortitude, Preservation}, Contains, OriginTrait, Randomness, Time, }, weights::Weight, Blake2_128Concat, BoundedVec, StorageHasher, }; use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; use smallvec::{Array, SmallVec}; use sp_core::{ ecdsa::Public as ECDSAPublic, sr25519::{Public as SR25519Public, Signature as SR25519Signature}, Get, }; use sp_io::{crypto::secp256k1_ecdsa_recover_compressed, hashing::blake2_256}; use sp_runtime::{ traits::{Convert, Dispatchable, Hash, Zero}, DispatchError, }; use sp_std::{fmt::Debug, marker::PhantomData, mem, prelude::*, vec::Vec}; pub type AccountIdOf = ::AccountId; pub type MomentOf = <::Time as Time>::Moment; pub type SeedOf = ::Hash; pub type ExecResult = Result; /// A type that represents a topic of an event. At the moment a hash is used. pub type TopicOf = ::Hash; /// Type for variable sized storage key. Used for transparent hashing. type VarSizedKey = BoundedVec::MaxStorageKeyLen>; /// Combined key type for both fixed and variable sized storage keys. pub enum Key { /// Variant for fixed sized keys. Fix([u8; 32]), /// Variant for variable sized keys. Var(VarSizedKey), } impl Key { /// Copies self into a new vec. pub fn to_vec(&self) -> Vec { match self { Key::Fix(v) => v.to_vec(), Key::Var(v) => v.to_vec(), } } pub fn hash(&self) -> Vec { match self { Key::Fix(v) => blake2_256(v.as_slice()).to_vec(), Key::Var(v) => Blake2_128Concat::hash(v.as_slice()), } } pub fn try_from_fix(v: Vec) -> Result> { <[u8; 32]>::try_from(v).map(Self::Fix) } pub fn try_from_var(v: Vec) -> Result> { VarSizedKey::::try_from(v).map(Self::Var) } } /// Origin of the error. /// /// Call or instantiate both called 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. #[derive(Copy, Clone, PartialEq, Eq, Debug, codec::Decode, codec::Encode)] pub enum ErrorOrigin { /// Caller error origin. /// /// The error happened in the current execution 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 execution. #[derive(Copy, Clone, PartialEq, Eq, Debug, codec::Decode, codec::Encode)] 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 } } } /// 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. /// /// # Note /// /// This trait is sealed and cannot be implemented by downstream crates. pub trait Ext: sealing::Sealed { type T: Config; /// Call (possibly transferring some amount of funds) into the specified account. /// /// Returns the code size of the called contract. fn call( &mut self, gas_limit: Weight, deposit_limit: BalanceOf, to: AccountIdOf, value: BalanceOf, input_data: Vec, allows_reentry: bool, ) -> Result; /// Execute code in the current frame. /// /// Returns the code size of the called contract. fn delegate_call( &mut self, code: CodeHash, input_data: Vec, ) -> Result; /// Instantiate a contract from the given code. /// /// Returns the original code size of the called contract. /// The newly created account will be associated with `code`. `value` specifies the amount of /// value transferred from the caller to the newly created account. fn instantiate( &mut self, gas_limit: Weight, deposit_limit: BalanceOf, code: CodeHash, value: BalanceOf, input_data: Vec, salt: &[u8], ) -> Result<(AccountIdOf, ExecReturnValue), ExecError>; /// 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) -> Result<(), DispatchError>; /// Transfer some amount of funds into the specified account. fn transfer(&mut self, to: &AccountIdOf, value: BalanceOf) -> DispatchResult; /// 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(&mut self, key: &Key) -> Option>; /// Returns `Some(len)` (in bytes) if a storage item exists at `key`. /// /// Returns `None` if the `key` wasn't previously set by `set_storage` or /// was deleted. fn get_storage_size(&mut self, key: &Key) -> 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: &Key, value: Option>, take_old: bool, ) -> Result; /// Returns the caller. fn caller(&self) -> Origin; /// Check if a contract lives at the specified `address`. fn is_contract(&self, address: &AccountIdOf) -> bool; /// Returns the code hash of the contract for the given `address`. /// /// Returns `None` if the `address` does not belong to a contract. fn code_hash(&self, address: &AccountIdOf) -> Option>; /// Returns the code hash of the contract being executed. fn own_code_hash(&mut self) -> &CodeHash; /// Check if the caller of the current contract is the origin of the whole call stack. /// /// This can be checked with `is_contract(self.caller())` as well. /// However, this function does not require any storage lookup and therefore uses less weight. fn caller_is_origin(&self) -> bool; /// Check if the caller is origin, and this origin is root. fn caller_is_root(&self) -> bool; /// 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. 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 a random number for the current block with the given subject. fn random(&self, subject: &[u8]) -> (SeedOf, BlockNumberFor); /// Deposit an event with the given topics. /// /// There should not be any duplicates in `topics`. fn deposit_event(&mut self, topics: Vec>, data: Vec); /// Returns the current block number. fn block_number(&self) -> BlockNumberFor; /// 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; /// Get an immutable reference to the nested gas meter. fn gas_meter(&self) -> &GasMeter; /// Get a mutable reference to the nested gas meter. fn gas_meter_mut(&mut self) -> &mut GasMeter; /// Charges `diff` from the meter. fn charge_storage(&mut self, diff: &Diff); /// Append a string to the debug buffer. /// /// It is added as-is without any additional new line. /// /// This is a no-op if debug message recording is disabled which is always the case /// when the code is executing on-chain. /// /// Returns `true` if debug message recording is enabled. Otherwise `false` is returned. fn append_debug_buffer(&mut self, msg: &str) -> bool; /// Returns `true` if debug message recording is enabled. Otherwise `false` is returned. fn debug_buffer_enabled(&self) -> bool; /// Call some dispatchable and return the result. fn call_runtime(&self, call: ::RuntimeCall) -> DispatchResultWithPostInfo; /// Recovers ECDSA compressed public key based on signature and message hash. fn ecdsa_recover(&self, signature: &[u8; 65], message_hash: &[u8; 32]) -> Result<[u8; 33], ()>; /// Verify a sr25519 signature. fn sr25519_verify(&self, signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> bool; /// Returns Ethereum address from the ECDSA compressed public key. fn ecdsa_to_eth_address(&self, pk: &[u8; 33]) -> Result<[u8; 20], ()>; /// Tests sometimes need to modify and inspect the contract info directly. #[cfg(test)] fn contract_info(&mut self) -> &mut ContractInfo; /// Sets new code hash for existing contract. fn set_code_hash(&mut self, hash: CodeHash) -> Result<(), DispatchError>; /// Returns the number of times the currently executing contract exists on the call stack in /// addition to the calling instance. A value of 0 means no reentrancy. fn reentrance_count(&self) -> u32; /// Returns the number of times the specified contract exists on the call stack. Delegated calls /// are not calculated as separate entrance. /// A value of 0 means it does not exist on the call stack. fn account_reentrance_count(&self, account_id: &AccountIdOf) -> u32; /// Returns a nonce that is incremented for every instantiated contract. fn nonce(&mut self) -> u64; /// Increment the reference count of a of a stored code by one. /// /// # Errors /// /// [`Error::CodeNotFound`] is returned if no stored code found having the specified /// `code_hash`. fn increment_refcount(code_hash: CodeHash) -> Result<(), DispatchError>; /// Decrement the reference count of a stored code by one. /// /// # Note /// /// A contract whose reference count dropped to zero isn't automatically removed. A /// `remove_code` transaction must be submitted by the original uploader to do so. fn decrement_refcount(code_hash: CodeHash); /// Adds a delegate dependency to [`ContractInfo`]'s `delegate_dependencies` field. /// /// This ensures that the delegated contract is not removed while it is still in use. It /// increases the reference count of the code hash and charges a fraction (see /// [`Config::CodeHashLockupDepositPercent`]) of the code deposit. /// /// # Errors /// /// - [`Error::::MaxDelegateDependenciesReached`] /// - [`Error::::CannotAddSelfAsDelegateDependency`] /// - [`Error::::DelegateDependencyAlreadyExists`] fn lock_delegate_dependency( &mut self, code_hash: CodeHash, ) -> Result<(), DispatchError>; /// Removes a delegate dependency from [`ContractInfo`]'s `delegate_dependencies` field. /// /// This is the counterpart of [`Self::lock_delegate_dependency`]. It decreases the reference /// count and refunds the deposit that was charged by [`Self::lock_delegate_dependency`]. /// /// # Errors /// /// - [`Error::::DelegateDependencyNotFound`] fn unlock_delegate_dependency( &mut self, code_hash: &CodeHash, ) -> Result<(), DispatchError>; } /// Describes the different functions that can be exported by an [`Executable`]. #[derive( Copy, Clone, PartialEq, Eq, sp_core::RuntimeDebug, codec::Decode, codec::Encode, codec::MaxEncodedLen, scale_info::TypeInfo, )] 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. /// /// # Note /// Charges size base load weight from the gas meter. fn from_storage( code_hash: CodeHash, gas_meter: &mut GasMeter, ) -> Result; /// 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: &mut E, function: &ExportedFunction, input_data: Vec, ) -> ExecResult; /// The code info of the executable. fn code_info(&self) -> &CodeInfo; /// The code hash of the executable. fn code_hash(&self) -> &CodeHash; /// Size of the contract code in bytes. fn code_len(&self) -> u32; /// The code does not contain any instructions which could lead to indeterminism. fn is_deterministic(&self) -> bool; } /// The complete call stack of a contract execution. /// /// The call stack is initiated by either a signed origin or one of the contract RPC calls. /// This type implements `Ext` and by that exposes the business logic of contract execution to /// the runtime module which interfaces with the contract (the wasm blob) itself. pub struct Stack<'a, T: Config, E> { /// The origin that initiated the call stack. It could either be a Signed plain account that /// holds an account id or Root. /// /// # Note /// /// Please note that it is possible that the id of a Signed origin belongs to a contract rather /// than a plain account when being called through one of the contract RPCs where the /// client can freely choose the origin. This usually makes no sense but is still possible. origin: Origin, /// The cost schedule used when charging from the gas meter. schedule: &'a Schedule, /// The gas meter where costs are charged to. gas_meter: &'a mut GasMeter, /// The storage meter makes sure that the storage deposit limit is obeyed. storage_meter: &'a mut storage::meter::Meter, /// The timestamp at the point of call stack instantiation. timestamp: MomentOf, /// The block number at the time of call stack instantiation. block_number: BlockNumberFor, /// The nonce is cached here when accessed. It is written back when the call stack /// finishes executing. Please refer to [`Nonce`] to a description of /// the nonce itself. nonce: Option, /// The actual call stack. One entry per nested contract called/instantiated. /// This does **not** include the [`Self::first_frame`]. frames: SmallVec, /// Statically guarantee that each call stack has at least one frame. first_frame: Frame, /// A text buffer used to output human readable information. /// /// All the bytes added to this field should be valid UTF-8. The buffer has no defined /// structure and is intended to be shown to users as-is for debugging purposes. debug_message: Option<&'a mut DebugBufferVec>, /// The determinism requirement of this call stack. determinism: Determinism, /// No executable is held by the struct but influences its behaviour. _phantom: PhantomData, } /// Represents one entry in the call stack. /// /// For each nested contract call or instantiate one frame is created. It holds specific /// information for the said call and caches the in-storage `ContractInfo` data structure. /// /// # Note /// /// This is an internal data structure. It is exposed to the public for the sole reason /// of specifying [`Config::CallStack`]. pub struct Frame { /// The account id of the executing contract. account_id: T::AccountId, /// The cached in-storage data of the contract. contract_info: CachedContract, /// The amount of balance transferred by the caller as part of the call. value_transferred: BalanceOf, /// Determines whether this is a call or instantiate frame. entry_point: ExportedFunction, /// The gas meter capped to the supplied gas limit. nested_gas: GasMeter, /// The storage meter for the individual call. nested_storage: storage::meter::NestedMeter, /// If `false` the contract enabled its defense against reentrance attacks. allows_reentry: bool, /// The caller of the currently executing frame which was spawned by `delegate_call`. delegate_caller: Option>, } /// Used in a delegate call frame arguments in order to override the executable and caller. struct DelegatedCall { /// The executable which is run instead of the contracts own `executable`. executable: E, /// The caller of the contract. caller: Origin, } /// Parameter passed in when creating a new `Frame`. /// /// It determines whether the new frame is for a call or an instantiate. enum FrameArgs<'a, T: Config, E> { Call { /// The account id of the contract that is to be called. dest: T::AccountId, /// If `None` the contract info needs to be reloaded from storage. cached_info: Option>, /// This frame was created by `seal_delegate_call` and hence uses different code than /// what is stored at [`Self::Call::dest`]. Its caller ([`DelegatedCall::caller`]) is the /// account which called the caller contract delegated_call: Option>, }, Instantiate { /// The contract or signed origin which instantiates the new contract. sender: T::AccountId, /// The nonce that should be used to derive a new trie id for the contract. nonce: u64, /// The executable whose `deploy` function is run. executable: E, /// A salt used in the contract address derivation of the new contract. salt: &'a [u8], /// The input data is used in the contract address derivation of the new contract. input_data: &'a [u8], }, } /// Describes the different states of a contract as contained in a `Frame`. enum CachedContract { /// The cached contract is up to date with the in-storage value. Cached(ContractInfo), /// A recursive call into the same contract did write to the contract info. /// /// In this case the cached contract is stale and needs to be reloaded from storage. Invalidated, /// The current contract executed `terminate` and removed the contract. /// /// In this case a reload is neither allowed nor possible. Please note that recursive /// calls cannot remove a contract as this is checked and denied. Terminated, } impl CachedContract { /// Return `Some(ContractInfo)` if the contract is in cached state. `None` otherwise. fn into_contract(self) -> Option> { if let CachedContract::Cached(contract) = self { Some(contract) } else { None } } /// Return `Some(&mut ContractInfo)` if the contract is in cached state. `None` otherwise. fn as_contract(&mut self) -> Option<&mut ContractInfo> { if let CachedContract::Cached(contract) = self { Some(contract) } else { None } } } impl Frame { /// Return the `contract_info` of the current contract. fn contract_info(&mut self) -> &mut ContractInfo { self.contract_info.get(&self.account_id) } /// Terminate and return the `contract_info` of the current contract. /// /// # Note /// /// Under no circumstances the contract is allowed to access the `contract_info` after /// a call to this function. This would constitute a programming error in the exec module. fn terminate(&mut self) -> ContractInfo { self.contract_info.terminate(&self.account_id) } } /// Extract the contract info after loading it from storage. /// /// This assumes that `load` was executed before calling this macro. macro_rules! get_cached_or_panic_after_load { ($c:expr) => {{ if let CachedContract::Cached(contract) = $c { contract } else { panic!( "It is impossible to remove a contract that is on the call stack;\ See implementations of terminate;\ Therefore fetching a contract will never fail while using an account id that is currently active on the call stack;\ qed" ); } }}; } /// Same as [`Stack::top_frame`]. /// /// We need this access as a macro because sometimes hiding the lifetimes behind /// a function won't work out. macro_rules! top_frame { ($stack:expr) => { $stack.frames.last().unwrap_or(&$stack.first_frame) }; } /// Same as [`Stack::top_frame_mut`]. /// /// We need this access as a macro because sometimes hiding the lifetimes behind /// a function won't work out. macro_rules! top_frame_mut { ($stack:expr) => { $stack.frames.last_mut().unwrap_or(&mut $stack.first_frame) }; } impl CachedContract { /// Load the `contract_info` from storage if necessary. fn load(&mut self, account_id: &T::AccountId) { if let CachedContract::Invalidated = self { let contract = >::get(&account_id); if let Some(contract) = contract { *self = CachedContract::Cached(contract); } } } /// Return the cached contract_info. fn get(&mut self, account_id: &T::AccountId) -> &mut ContractInfo { self.load(account_id); get_cached_or_panic_after_load!(self) } /// Terminate and return the contract info. fn terminate(&mut self, account_id: &T::AccountId) -> ContractInfo { self.load(account_id); get_cached_or_panic_after_load!(mem::replace(self, Self::Terminated)) } } impl<'a, T, E> Stack<'a, T, E> where T: Config, E: Executable, { /// Create and run a new call stack by calling into `dest`. /// /// # Note /// /// `debug_message` should only ever be set to `Some` when executing as an RPC because /// it adds allocations and could be abused to drive the runtime into an OOM panic. /// /// # Return Value /// /// Result<(ExecReturnValue, CodeSize), (ExecError, CodeSize)> pub fn run_call( origin: Origin, dest: T::AccountId, gas_meter: &'a mut GasMeter, storage_meter: &'a mut storage::meter::Meter, schedule: &'a Schedule, value: BalanceOf, input_data: Vec, debug_message: Option<&'a mut DebugBufferVec>, determinism: Determinism, ) -> Result { let (mut stack, executable) = Self::new( FrameArgs::Call { dest, cached_info: None, delegated_call: None }, origin, gas_meter, storage_meter, schedule, value, debug_message, determinism, )?; stack.run(executable, input_data) } /// Create and run a new call stack by instantiating a new contract. /// /// # Note /// /// `debug_message` should only ever be set to `Some` when executing as an RPC because /// it adds allocations and could be abused to drive the runtime into an OOM panic. /// /// # Return Value /// /// Result<(NewContractAccountId, ExecReturnValue), ExecError)> pub fn run_instantiate( origin: T::AccountId, executable: E, gas_meter: &'a mut GasMeter, storage_meter: &'a mut storage::meter::Meter, schedule: &'a Schedule, value: BalanceOf, input_data: Vec, salt: &[u8], debug_message: Option<&'a mut DebugBufferVec>, ) -> Result<(T::AccountId, ExecReturnValue), ExecError> { let (mut stack, executable) = Self::new( FrameArgs::Instantiate { sender: origin.clone(), nonce: >::get().wrapping_add(1), executable, salt, input_data: input_data.as_ref(), }, Origin::from_account_id(origin), gas_meter, storage_meter, schedule, value, debug_message, Determinism::Enforced, )?; let account_id = stack.top_frame().account_id.clone(); stack.run(executable, input_data).map(|ret| (account_id, ret)) } /// Create a new call stack. fn new( args: FrameArgs, origin: Origin, gas_meter: &'a mut GasMeter, storage_meter: &'a mut storage::meter::Meter, schedule: &'a Schedule, value: BalanceOf, debug_message: Option<&'a mut DebugBufferVec>, determinism: Determinism, ) -> Result<(Self, E), ExecError> { let (first_frame, executable, nonce) = Self::new_frame( args, value, gas_meter, Weight::zero(), storage_meter, BalanceOf::::zero(), determinism, )?; let stack = Self { origin, schedule, gas_meter, storage_meter, timestamp: T::Time::now(), block_number: >::block_number(), nonce, first_frame, frames: Default::default(), debug_message, determinism, _phantom: Default::default(), }; Ok((stack, executable)) } /// Construct a new frame. /// /// This does not take `self` because when constructing the first frame `self` is /// not initialized, yet. fn new_frame( frame_args: FrameArgs, value_transferred: BalanceOf, gas_meter: &mut GasMeter, gas_limit: Weight, storage_meter: &mut storage::meter::GenericMeter, deposit_limit: BalanceOf, determinism: Determinism, ) -> Result<(Frame, E, Option), ExecError> { let (account_id, contract_info, executable, delegate_caller, entry_point, nonce) = match frame_args { FrameArgs::Call { dest, cached_info, delegated_call } => { let contract = if let Some(contract) = cached_info { contract } else { >::get(&dest).ok_or(>::ContractNotFound)? }; let (executable, delegate_caller) = if let Some(DelegatedCall { executable, caller }) = delegated_call { (executable, Some(caller)) } else { (E::from_storage(contract.code_hash, gas_meter)?, None) }; (dest, contract, executable, delegate_caller, ExportedFunction::Call, None) }, FrameArgs::Instantiate { sender, nonce, executable, salt, input_data } => { let account_id = Contracts::::contract_address( &sender, &executable.code_hash(), input_data, salt, ); let contract = ContractInfo::new(&account_id, nonce, *executable.code_hash())?; ( account_id, contract, executable, None, ExportedFunction::Constructor, Some(nonce), ) }, }; // `Relaxed` will only be ever set in case of off-chain execution. // Instantiations are never allowed even when executing off-chain. if !(executable.is_deterministic() || (matches!(determinism, Determinism::Relaxed) && matches!(entry_point, ExportedFunction::Call))) { return Err(Error::::Indeterministic.into()) } let frame = Frame { delegate_caller, value_transferred, contract_info: CachedContract::Cached(contract_info), account_id, entry_point, nested_gas: gas_meter.nested(gas_limit), nested_storage: storage_meter.nested(deposit_limit), allows_reentry: true, }; Ok((frame, executable, nonce)) } /// Create a subsequent nested frame. fn push_frame( &mut self, frame_args: FrameArgs, value_transferred: BalanceOf, gas_limit: Weight, deposit_limit: BalanceOf, ) -> Result { if self.frames.len() == T::CallStack::size() { return Err(Error::::MaxCallDepthReached.into()) } // We need to make sure that changes made to the contract info are not discarded. // See the `in_memory_changes_not_discarded` test for more information. // We do not store on instantiate because we do not allow to call into a contract // from its own constructor. let frame = self.top_frame(); if let (CachedContract::Cached(contract), ExportedFunction::Call) = (&frame.contract_info, frame.entry_point) { >::insert(frame.account_id.clone(), contract.clone()); } let frame = top_frame_mut!(self); let nested_gas = &mut frame.nested_gas; let nested_storage = &mut frame.nested_storage; let (frame, executable, _) = Self::new_frame( frame_args, value_transferred, nested_gas, gas_limit, nested_storage, deposit_limit, self.determinism, )?; self.frames.push(frame); Ok(executable) } /// Run the current (top) frame. /// /// This can be either a call or an instantiate. fn run(&mut self, executable: E, input_data: Vec) -> Result { let frame = self.top_frame(); let entry_point = frame.entry_point; let delegated_code_hash = if frame.delegate_caller.is_some() { Some(*executable.code_hash()) } else { None }; let do_transaction = || { // We need to charge the storage deposit before the initial transfer so that // it can create the account in case the initial transfer is < ed. if entry_point == ExportedFunction::Constructor { // Root origin can't be used to instantiate a contract, so it is safe to assume that // if we reached this point the origin has an associated account. let origin = &self.origin.account_id()?; let frame = top_frame_mut!(self); frame.nested_storage.charge_instantiate( origin, &frame.account_id, frame.contract_info.get(&frame.account_id), executable.code_info(), )?; } // Every non delegate call or instantiate also optionally transfers the balance. self.initial_transfer()?; let contract_address = &top_frame!(self).account_id; let call_span = T::Debug::new_call_span(contract_address, entry_point, &input_data); let output = T::Debug::intercept_call(contract_address, &entry_point, &input_data) .unwrap_or_else(|| { executable .execute(self, &entry_point, input_data) .map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee }) })?; call_span.after_call(&output); // Avoid useless work that would be reverted anyways. if output.did_revert() { return Ok(output) } // Storage limit is normally enforced as late as possible (when the last frame returns) // so that the ordering of storage accesses does not matter. // (However, if a special limit was set for a sub-call, it should be enforced right // after the sub-call returned. See below for this case of enforcement). if self.frames.is_empty() { let frame = &mut self.first_frame; frame.contract_info.load(&frame.account_id); let contract = frame.contract_info.as_contract(); frame.nested_storage.enforce_limit(contract)?; } let frame = self.top_frame(); let account_id = &frame.account_id.clone(); match (entry_point, delegated_code_hash) { (ExportedFunction::Constructor, _) => { // It is not allowed to terminate a contract inside its constructor. if matches!(frame.contract_info, CachedContract::Terminated) { return Err(Error::::TerminatedInConstructor.into()) } // If a special limit was set for the sub-call, we enforce it here. // This is needed because contract constructor might write to storage. // The sub-call will be rolled back in case the limit is exhausted. let frame = self.top_frame_mut(); let contract = frame.contract_info.as_contract(); frame.nested_storage.enforce_subcall_limit(contract)?; let caller = self.caller().account_id()?.clone(); // Deposit an instantiation event. Contracts::::deposit_event( vec![T::Hashing::hash_of(&caller), T::Hashing::hash_of(account_id)], Event::Instantiated { deployer: caller, contract: account_id.clone() }, ); }, (ExportedFunction::Call, Some(code_hash)) => { Contracts::::deposit_event( vec![T::Hashing::hash_of(account_id), T::Hashing::hash_of(&code_hash)], Event::DelegateCalled { contract: account_id.clone(), code_hash }, ); }, (ExportedFunction::Call, None) => { // If a special limit was set for the sub-call, we enforce it here. // The sub-call will be rolled back in case the limit is exhausted. let frame = self.top_frame_mut(); let contract = frame.contract_info.as_contract(); frame.nested_storage.enforce_subcall_limit(contract)?; let caller = self.caller(); Contracts::::deposit_event( vec![T::Hashing::hash_of(&caller), T::Hashing::hash_of(&account_id)], Event::Called { caller: caller.clone(), contract: account_id.clone() }, ); }, } Ok(output) }; // All changes performed by the contract are executed under a storage transaction. // This allows for roll back on error. Changes to the cached contract_info are // committed or rolled back when popping the frame. // // `with_transactional` may return an error caused by a limit in the // transactional storage depth. let transaction_outcome = with_transaction(|| -> TransactionOutcome> { let output = do_transaction(); match &output { Ok(result) if !result.did_revert() => TransactionOutcome::Commit(Ok((true, output))), _ => TransactionOutcome::Rollback(Ok((false, output))), } }); let (success, output) = match transaction_outcome { // `with_transactional` executed successfully, and we have the expected output. Ok((success, output)) => (success, output), // `with_transactional` returned an error, and we propagate that error and note no state // has changed. Err(error) => (false, Err(error.into())), }; self.pop_frame(success); output } /// Remove the current (top) frame from the stack. /// /// This is called after running the current frame. It commits cached values to storage /// and invalidates all stale references to it that might exist further down the call stack. fn pop_frame(&mut self, persist: bool) { // Revert changes to the nonce in case of a failed instantiation. if !persist && self.top_frame().entry_point == ExportedFunction::Constructor { self.nonce.as_mut().map(|c| *c = c.wrapping_sub(1)); } // Pop the current frame from the stack and return it in case it needs to interact // with duplicates that might exist on the stack. // A `None` means that we are returning from the `first_frame`. let frame = self.frames.pop(); // Both branches do essentially the same with the exception. The difference is that // the else branch does consume the hardcoded `first_frame`. if let Some(mut frame) = frame { let account_id = &frame.account_id; let prev = top_frame_mut!(self); prev.nested_gas.absorb_nested(frame.nested_gas); // Only gas counter changes are persisted in case of a failure. if !persist { return } // Record the storage meter changes of the nested call into the parent meter. // If the dropped frame's contract wasn't terminated we update the deposit counter // in its contract info. The load is necessary to pull it from storage in case // it was invalidated. frame.contract_info.load(account_id); let mut contract = frame.contract_info.into_contract(); prev.nested_storage.absorb(frame.nested_storage, account_id, contract.as_mut()); // In case the contract wasn't terminated we need to persist changes made to it. if let Some(contract) = contract { // optimization: Predecessor is the same contract. // We can just copy the contract into the predecessor without a storage write. // This is possible when there is no other contract in-between that could // trigger a rollback. if prev.account_id == *account_id { prev.contract_info = CachedContract::Cached(contract); return } // Predecessor is a different contract: We persist the info and invalidate the first // stale cache we find. This triggers a reload from storage on next use. We skip(1) // because that case is already handled by the optimization above. Only the first // cache needs to be invalidated because that one will invalidate the next cache // when it is popped from the stack. >::insert(account_id, contract); if let Some(c) = self.frames_mut().skip(1).find(|f| f.account_id == *account_id) { c.contract_info = CachedContract::Invalidated; } } } else { if let Some((msg, false)) = self.debug_message.as_ref().map(|m| (m, m.is_empty())) { log::debug!( target: LOG_TARGET, "Execution finished with debug buffer: {}", core::str::from_utf8(msg).unwrap_or(""), ); } self.gas_meter.absorb_nested(mem::take(&mut self.first_frame.nested_gas)); if !persist { return } let mut contract = self.first_frame.contract_info.as_contract(); self.storage_meter.absorb( mem::take(&mut self.first_frame.nested_storage), &self.first_frame.account_id, contract.as_deref_mut(), ); if let Some(contract) = contract { >::insert(&self.first_frame.account_id, contract); } if let Some(nonce) = self.nonce { >::set(nonce); } } } /// Transfer some funds from `from` to `to`. fn transfer( preservation: Preservation, from: &T::AccountId, to: &T::AccountId, value: BalanceOf, ) -> DispatchResult { if !value.is_zero() && from != to { T::Currency::transfer(from, to, value, preservation) .map_err(|_| Error::::TransferFailed)?; } Ok(()) } // The transfer as performed by a call or instantiate. fn initial_transfer(&self) -> DispatchResult { let frame = self.top_frame(); // If it is a delegate call, then we've already transferred tokens in the // last non-delegate frame. if frame.delegate_caller.is_some() { return Ok(()) } let value = frame.value_transferred; // Get the account id from the caller. // If the caller is root there is no account to transfer from, and therefore we can't take // any `value` other than 0. let caller = match self.caller() { Origin::Signed(caller) => caller, Origin::Root if value.is_zero() => return Ok(()), Origin::Root => return DispatchError::RootNotAllowed.into(), }; Self::transfer(Preservation::Preserve, &caller, &frame.account_id, value) } /// Reference to the current (top) frame. fn top_frame(&self) -> &Frame { top_frame!(self) } /// Mutable reference to the current (top) frame. fn top_frame_mut(&mut self) -> &mut Frame { top_frame_mut!(self) } /// Iterator over all frames. /// /// The iterator starts with the top frame and ends with the root frame. fn frames(&self) -> impl Iterator> { sp_std::iter::once(&self.first_frame).chain(&self.frames).rev() } /// Same as `frames` but with a mutable reference as iterator item. fn frames_mut(&mut self) -> impl Iterator> { sp_std::iter::once(&mut self.first_frame).chain(&mut self.frames).rev() } /// Returns whether the current contract is on the stack multiple times. fn is_recursive(&self) -> bool { let account_id = &self.top_frame().account_id; self.frames().skip(1).any(|f| &f.account_id == account_id) } /// Returns whether the specified contract allows to be reentered right now. fn allows_reentry(&self, id: &AccountIdOf) -> bool { !self.frames().any(|f| &f.account_id == id && !f.allows_reentry) } /// Increments and returns the next nonce. Pulls it from storage if it isn't in cache. fn next_nonce(&mut self) -> u64 { let next = self.nonce().wrapping_add(1); self.nonce = Some(next); next } } impl<'a, T, E> Ext for Stack<'a, T, E> where T: Config, E: Executable, { type T = T; fn call( &mut self, gas_limit: Weight, deposit_limit: BalanceOf, to: T::AccountId, value: BalanceOf, input_data: Vec, allows_reentry: bool, ) -> Result { // Before pushing the new frame: Protect the caller contract against reentrancy attacks. // It is important to do this before calling `allows_reentry` so that a direct recursion // is caught by it. self.top_frame_mut().allows_reentry = allows_reentry; let try_call = || { if !self.allows_reentry(&to) { return Err(>::ReentranceDenied.into()) } // We ignore instantiate frames in our search for a cached contract. // Otherwise it would be possible to recursively call a contract from its own // constructor: We disallow calling not fully constructed contracts. let cached_info = self .frames() .find(|f| f.entry_point == ExportedFunction::Call && f.account_id == to) .and_then(|f| match &f.contract_info { CachedContract::Cached(contract) => Some(contract.clone()), _ => None, }); let executable = self.push_frame( FrameArgs::Call { dest: to, cached_info, delegated_call: None }, value, gas_limit, deposit_limit, )?; self.run(executable, input_data) }; // We need to make sure to reset `allows_reentry` even on failure. let result = try_call(); // Protection is on a per call basis. self.top_frame_mut().allows_reentry = true; result } fn delegate_call( &mut self, code_hash: CodeHash, input_data: Vec, ) -> Result { let executable = E::from_storage(code_hash, self.gas_meter_mut())?; let top_frame = self.top_frame_mut(); let contract_info = top_frame.contract_info().clone(); let account_id = top_frame.account_id.clone(); let value = top_frame.value_transferred; let executable = self.push_frame( FrameArgs::Call { dest: account_id, cached_info: Some(contract_info), delegated_call: Some(DelegatedCall { executable, caller: self.caller().clone() }), }, value, Weight::zero(), BalanceOf::::zero(), )?; self.run(executable, input_data) } fn instantiate( &mut self, gas_limit: Weight, deposit_limit: BalanceOf, code_hash: CodeHash, value: BalanceOf, input_data: Vec, salt: &[u8], ) -> Result<(AccountIdOf, ExecReturnValue), ExecError> { let executable = E::from_storage(code_hash, self.gas_meter_mut())?; let nonce = self.next_nonce(); let executable = self.push_frame( FrameArgs::Instantiate { sender: self.top_frame().account_id.clone(), nonce, executable, salt, input_data: input_data.as_ref(), }, value, gas_limit, deposit_limit, )?; let account_id = self.top_frame().account_id.clone(); self.run(executable, input_data).map(|ret| (account_id, ret)) } fn terminate(&mut self, beneficiary: &AccountIdOf) -> Result<(), DispatchError> { if self.is_recursive() { return Err(Error::::TerminatedWhileReentrant.into()) } let frame = self.top_frame_mut(); let info = frame.terminate(); frame.nested_storage.terminate(&info, beneficiary.clone()); info.queue_trie_for_deletion(); ContractInfoOf::::remove(&frame.account_id); Self::decrement_refcount(info.code_hash); for (code_hash, deposit) in info.delegate_dependencies() { Self::decrement_refcount(*code_hash); frame .nested_storage .charge_deposit(frame.account_id.clone(), StorageDeposit::Refund(*deposit)); } Contracts::::deposit_event( vec![T::Hashing::hash_of(&frame.account_id), T::Hashing::hash_of(&beneficiary)], Event::Terminated { contract: frame.account_id.clone(), beneficiary: beneficiary.clone(), }, ); Ok(()) } fn transfer(&mut self, to: &T::AccountId, value: BalanceOf) -> DispatchResult { Self::transfer(Preservation::Preserve, &self.top_frame().account_id, to, value) } fn get_storage(&mut self, key: &Key) -> Option> { self.top_frame_mut().contract_info().read(key) } fn get_storage_size(&mut self, key: &Key) -> Option { self.top_frame_mut().contract_info().size(key.into()) } fn set_storage( &mut self, key: &Key, value: Option>, take_old: bool, ) -> Result { let frame = self.top_frame_mut(); frame.contract_info.get(&frame.account_id).write( key.into(), value, Some(&mut frame.nested_storage), take_old, ) } fn address(&self) -> &T::AccountId { &self.top_frame().account_id } fn caller(&self) -> Origin { if let Some(caller) = &self.top_frame().delegate_caller { caller.clone() } else { self.frames() .nth(1) .map(|f| Origin::from_account_id(f.account_id.clone())) .unwrap_or(self.origin.clone()) } } fn is_contract(&self, address: &T::AccountId) -> bool { ContractInfoOf::::contains_key(&address) } fn code_hash(&self, address: &T::AccountId) -> Option> { >::get(&address).map(|contract| contract.code_hash) } fn own_code_hash(&mut self) -> &CodeHash { &self.top_frame_mut().contract_info().code_hash } fn caller_is_origin(&self) -> bool { self.origin == self.caller() } fn caller_is_root(&self) -> bool { // if the caller isn't origin, then it can't be root. self.caller_is_origin() && self.origin == Origin::Root } fn balance(&self) -> BalanceOf { T::Currency::reducible_balance( &self.top_frame().account_id, Preservation::Preserve, Fortitude::Polite, ) } fn value_transferred(&self) -> BalanceOf { self.top_frame().value_transferred } fn random(&self, subject: &[u8]) -> (SeedOf, BlockNumberFor) { T::Randomness::random(subject) } fn now(&self) -> &MomentOf { &self.timestamp } fn minimum_balance(&self) -> BalanceOf { T::Currency::minimum_balance() } fn deposit_event(&mut self, topics: Vec, data: Vec) { Contracts::::deposit_event( topics, Event::ContractEmitted { contract: self.top_frame().account_id.clone(), data }, ); } fn block_number(&self) -> BlockNumberFor { self.block_number } fn max_value_size(&self) -> u32 { self.schedule.limits.payload_len } fn get_weight_price(&self, weight: Weight) -> BalanceOf { T::WeightPrice::convert(weight) } fn schedule(&self) -> &Schedule { self.schedule } fn gas_meter(&self) -> &GasMeter { &self.top_frame().nested_gas } fn gas_meter_mut(&mut self) -> &mut GasMeter { &mut self.top_frame_mut().nested_gas } fn charge_storage(&mut self, diff: &Diff) { self.top_frame_mut().nested_storage.charge(diff) } fn debug_buffer_enabled(&self) -> bool { self.debug_message.is_some() } fn append_debug_buffer(&mut self, msg: &str) -> bool { if let Some(buffer) = &mut self.debug_message { buffer .try_extend(&mut msg.bytes()) .map_err(|_| { log::debug!( target: LOG_TARGET, "Debug buffer (of {} bytes) exhausted!", DebugBufferVec::::bound(), ) }) .ok(); true } else { false } } fn call_runtime(&self, call: ::RuntimeCall) -> DispatchResultWithPostInfo { let mut origin: T::RuntimeOrigin = RawOrigin::Signed(self.address().clone()).into(); origin.add_filter(T::CallFilter::contains); call.dispatch(origin) } fn ecdsa_recover(&self, signature: &[u8; 65], message_hash: &[u8; 32]) -> Result<[u8; 33], ()> { secp256k1_ecdsa_recover_compressed(signature, message_hash).map_err(|_| ()) } fn sr25519_verify(&self, signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> bool { sp_io::crypto::sr25519_verify( &SR25519Signature::from(*signature), message, &SR25519Public::from(*pub_key), ) } fn ecdsa_to_eth_address(&self, pk: &[u8; 33]) -> Result<[u8; 20], ()> { ECDSAPublic::from(*pk).to_eth_address() } #[cfg(test)] fn contract_info(&mut self) -> &mut ContractInfo { self.top_frame_mut().contract_info() } fn set_code_hash(&mut self, hash: CodeHash) -> Result<(), DispatchError> { let frame = top_frame_mut!(self); if !E::from_storage(hash, &mut frame.nested_gas)?.is_deterministic() { return Err(>::Indeterministic.into()) } let info = frame.contract_info(); let prev_hash = info.code_hash; info.code_hash = hash; let code_info = CodeInfoOf::::get(hash).ok_or(Error::::CodeNotFound)?; let old_base_deposit = info.storage_base_deposit(); let new_base_deposit = info.update_base_deposit(&code_info); let deposit = StorageDeposit::Charge(new_base_deposit) .saturating_sub(&StorageDeposit::Charge(old_base_deposit)); frame.nested_storage.charge_deposit(frame.account_id.clone(), deposit); Self::increment_refcount(hash)?; Self::decrement_refcount(prev_hash); Contracts::::deposit_event( vec![T::Hashing::hash_of(&frame.account_id), hash, prev_hash], Event::ContractCodeUpdated { contract: frame.account_id.clone(), new_code_hash: hash, old_code_hash: prev_hash, }, ); Ok(()) } fn reentrance_count(&self) -> u32 { let id: &AccountIdOf = &self.top_frame().account_id; self.account_reentrance_count(id).saturating_sub(1) } fn account_reentrance_count(&self, account_id: &AccountIdOf) -> u32 { self.frames() .filter(|f| f.delegate_caller.is_none() && &f.account_id == account_id) .count() as u32 } fn nonce(&mut self) -> u64 { if let Some(current) = self.nonce { current } else { let current = >::get(); self.nonce = Some(current); current } } fn increment_refcount(code_hash: CodeHash) -> Result<(), DispatchError> { >::mutate(code_hash, |existing| -> Result<(), DispatchError> { if let Some(info) = existing { *info.refcount_mut() = info.refcount().saturating_add(1); Ok(()) } else { Err(Error::::CodeNotFound.into()) } }) } fn decrement_refcount(code_hash: CodeHash) { >::mutate(code_hash, |existing| { if let Some(info) = existing { *info.refcount_mut() = info.refcount().saturating_sub(1); } }); } fn lock_delegate_dependency( &mut self, code_hash: CodeHash, ) -> Result<(), DispatchError> { let frame = self.top_frame_mut(); let info = frame.contract_info.get(&frame.account_id); ensure!(code_hash != info.code_hash, Error::::CannotAddSelfAsDelegateDependency); let code_info = CodeInfoOf::::get(code_hash).ok_or(Error::::CodeNotFound)?; let deposit = T::CodeHashLockupDepositPercent::get().mul_ceil(code_info.deposit()); info.lock_delegate_dependency(code_hash, deposit)?; Self::increment_refcount(code_hash)?; frame .nested_storage .charge_deposit(frame.account_id.clone(), StorageDeposit::Charge(deposit)); Ok(()) } fn unlock_delegate_dependency( &mut self, code_hash: &CodeHash, ) -> Result<(), DispatchError> { let frame = self.top_frame_mut(); let info = frame.contract_info.get(&frame.account_id); let deposit = info.unlock_delegate_dependency(code_hash)?; Self::decrement_refcount(*code_hash); frame .nested_storage .charge_deposit(frame.account_id.clone(), StorageDeposit::Refund(deposit)); Ok(()) } } mod sealing { use super::*; pub trait Sealed {} impl<'a, T: Config, E> Sealed for Stack<'a, T, E> {} #[cfg(test)] impl Sealed for crate::wasm::MockExt {} #[cfg(test)] impl Sealed for &mut crate::wasm::MockExt {} } /// 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::{ exec::ExportedFunction::*, gas::GasMeter, tests::{ test_utils::{get_balance, hash, place_contract, set_balance}, ExtBuilder, RuntimeCall, RuntimeEvent as MetaEvent, Test, TestFilter, ALICE, BOB, CHARLIE, GAS_LIMIT, }, Error, }; use assert_matches::assert_matches; use codec::{Decode, Encode}; use frame_support::{assert_err, assert_ok, parameter_types}; use frame_system::{EventRecord, Phase}; use pallet_contracts_uapi::ReturnFlags; use pretty_assertions::assert_eq; use sp_runtime::{traits::Hash, DispatchError}; use std::{cell::RefCell, collections::hash_map::HashMap, rc::Rc}; type System = frame_system::Pallet; type MockStack<'a> = Stack<'a, Test, MockExecutable>; parameter_types! { static Loader: MockLoader = MockLoader::default(); } fn events() -> Vec> { System::events() .into_iter() .filter_map(|meta| match meta.event { MetaEvent::Contracts(contract_event) => Some(contract_event), _ => None, }) .collect() } struct MockCtx<'a> { ext: &'a mut MockStack<'a>, input_data: Vec, } #[derive(Clone)] struct MockExecutable { func: Rc Fn(MockCtx<'a>, &Self) -> ExecResult + 'static>, func_type: ExportedFunction, code_hash: CodeHash, code_info: CodeInfo, } #[derive(Default, Clone)] pub struct MockLoader { map: HashMap, MockExecutable>, counter: u64, } impl MockLoader { fn code_hashes() -> Vec> { Loader::get().map.keys().copied().collect() } fn insert( func_type: ExportedFunction, f: impl Fn(MockCtx, &MockExecutable) -> ExecResult + 'static, ) -> CodeHash { Loader::mutate(|loader| { // Generate code hashes as monotonically increasing values. let hash = ::Hash::from_low_u64_be(loader.counter); loader.counter += 1; loader.map.insert( hash, MockExecutable { func: Rc::new(f), func_type, code_hash: hash, code_info: CodeInfo::::new(ALICE), }, ); hash }) } } impl Executable for MockExecutable { fn from_storage( code_hash: CodeHash, _gas_meter: &mut GasMeter, ) -> Result { Loader::mutate(|loader| { loader.map.get(&code_hash).cloned().ok_or(Error::::CodeNotFound.into()) }) } fn execute>( self, ext: &mut E, function: &ExportedFunction, input_data: Vec, ) -> ExecResult { if let &Constructor = function { E::increment_refcount(self.code_hash).unwrap(); } // # Safety // // We know that we **always** call execute with a `MockStack` in this test. // // # Note // // The transmute is necessary because `execute` has to be generic over all // `E: Ext`. However, `MockExecutable` can't be generic over `E` as it would // constitute a cycle. let ext = unsafe { mem::transmute(ext) }; if function == &self.func_type { (self.func)(MockCtx { ext, input_data }, &self) } else { exec_success() } } fn code_hash(&self) -> &CodeHash { &self.code_hash } fn code_info(&self) -> &CodeInfo { &self.code_info } fn code_len(&self) -> u32 { 0 } fn is_deterministic(&self) -> bool { true } } fn exec_success() -> ExecResult { Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }) } fn exec_trapped() -> ExecResult { Err(ExecError { error: >::ContractTrapped.into(), origin: ErrorOrigin::Callee }) } #[test] fn it_works() { parameter_types! { static TestData: Vec = vec![0]; } let value = Default::default(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); let exec_ch = MockLoader::insert(Call, |_ctx, _executable| { TestData::mutate(|data| data.push(1)); exec_success() }); ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&BOB, exec_ch); let mut storage_meter = storage::meter::Meter::new(&Origin::from_account_id(ALICE), Some(0), value) .unwrap(); assert_matches!( MockStack::run_call( Origin::from_account_id(ALICE), BOB, &mut gas_meter, &mut storage_meter, &schedule, value, vec![], None, Determinism::Enforced, ), Ok(_) ); }); assert_eq!(TestData::get(), vec![0, 1]); } #[test] fn transfer_works() { // 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(|| { set_balance(&origin, 100); set_balance(&dest, 0); MockStack::transfer(Preservation::Preserve, &origin, &dest, 55).unwrap(); assert_eq!(get_balance(&origin), 45); assert_eq!(get_balance(&dest), 55); }); } #[test] fn correct_transfer_on_call() { let origin = ALICE; let dest = BOB; let value = 55; let success_ch = MockLoader::insert(Call, move |ctx, _| { assert_eq!(ctx.ext.value_transferred(), value); Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }) }); ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&dest, success_ch); set_balance(&origin, 100); let balance = get_balance(&dest); let contract_origin = Origin::from_account_id(origin.clone()); let mut storage_meter = storage::meter::Meter::new(&contract_origin, Some(0), value).unwrap(); let _ = MockStack::run_call( contract_origin.clone(), dest.clone(), &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, &schedule, value, vec![], None, Determinism::Enforced, ) .unwrap(); assert_eq!(get_balance(&origin), 100 - value); assert_eq!(get_balance(&dest), balance + value); }); } #[test] fn correct_transfer_on_delegate_call() { let origin = ALICE; let dest = BOB; let value = 35; let success_ch = MockLoader::insert(Call, move |ctx, _| { assert_eq!(ctx.ext.value_transferred(), value); Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }) }); let delegate_ch = MockLoader::insert(Call, move |ctx, _| { assert_eq!(ctx.ext.value_transferred(), value); let _ = ctx.ext.delegate_call(success_ch, Vec::new())?; Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }) }); ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&dest, delegate_ch); set_balance(&origin, 100); let balance = get_balance(&dest); let contract_origin = Origin::from_account_id(origin.clone()); let mut storage_meter = storage::meter::Meter::new(&contract_origin, Some(0), 55).unwrap(); let _ = MockStack::run_call( contract_origin.clone(), dest.clone(), &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, &schedule, value, vec![], None, Determinism::Enforced, ) .unwrap(); assert_eq!(get_balance(&origin), 100 - value); assert_eq!(get_balance(&dest), balance + value); }); } #[test] fn changes_are_reverted_on_failing_call() { // 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(Call, |_, _| { Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: Vec::new() }) }); ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&dest, return_ch); set_balance(&origin, 100); let balance = get_balance(&dest); let contract_origin = Origin::from_account_id(origin.clone()); let mut storage_meter = storage::meter::Meter::new(&contract_origin, Some(0), 55).unwrap(); let output = MockStack::run_call( contract_origin.clone(), dest.clone(), &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, &schedule, 55, vec![], None, Determinism::Enforced, ) .unwrap(); assert!(output.did_revert()); assert_eq!(get_balance(&origin), 100); assert_eq!(get_balance(&dest), balance); }); } #[test] fn balance_too_low() { // 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(|| { set_balance(&origin, 0); let result = MockStack::transfer(Preservation::Preserve, &origin, &dest, 100); 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(Call, |_, _| { Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![1, 2, 3, 4] }) }); ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); let contract_origin = Origin::from_account_id(origin); let mut storage_meter = storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); place_contract(&BOB, return_ch); let result = MockStack::run_call( contract_origin, dest, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, &schedule, 0, vec![], None, Determinism::Enforced, ); let output = result.unwrap(); assert!(!output.did_revert()); assert_eq!(output.data, vec![1, 2, 3, 4]); }); } #[test] fn output_is_returned_on_failure() { // 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(Call, |_, _| { Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![1, 2, 3, 4] }) }); ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&BOB, return_ch); let contract_origin = Origin::from_account_id(origin); let mut storage_meter = storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); let result = MockStack::run_call( contract_origin, dest, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, &schedule, 0, vec![], None, Determinism::Enforced, ); let output = result.unwrap(); assert!(output.did_revert()); assert_eq!(output.data, vec![1, 2, 3, 4]); }); } #[test] fn input_data_to_call() { let input_data_ch = MockLoader::insert(Call, |ctx, _| { assert_eq!(ctx.input_data, &[1, 2, 3, 4]); exec_success() }); // This one tests passing the input data into a contract via call. ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&BOB, input_data_ch); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); let result = MockStack::run_call( contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, &schedule, 0, vec![1, 2, 3, 4], None, Determinism::Enforced, ); assert_matches!(result, Ok(_)); }); } #[test] fn input_data_to_instantiate() { let input_data_ch = MockLoader::insert(Constructor, |ctx, _| { assert_eq!(ctx.input_data, &[1, 2, 3, 4]); exec_success() }); // This one tests passing the input data into a contract via instantiate. ExtBuilder::default() .with_code_hashes(MockLoader::code_hashes()) .build() .execute_with(|| { let schedule = ::Schedule::get(); let min_balance = ::Currency::minimum_balance(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); let executable = MockExecutable::from_storage(input_data_ch, &mut gas_meter).unwrap(); set_balance(&ALICE, min_balance * 10_000); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, min_balance).unwrap(); let result = MockStack::run_instantiate( ALICE, executable, &mut gas_meter, &mut storage_meter, &schedule, min_balance, vec![1, 2, 3, 4], &[], None, ); assert_matches!(result, Ok(_)); }); } #[test] fn max_depth() { // This test verifies that when we reach the maximal depth creation of an // yet another context fails. parameter_types! { static ReachedBottom: bool = false; } let value = Default::default(); let recurse_ch = MockLoader::insert(Call, |ctx, _| { // Try to call into yourself. let r = ctx.ext.call(Weight::zero(), BalanceOf::::zero(), BOB, 0, vec![], true); ReachedBottom::mutate(|reached_bottom| { 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 schedule = ::Schedule::get(); set_balance(&BOB, 1); place_contract(&BOB, recurse_ch); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, Some(0), value).unwrap(); let result = MockStack::run_call( contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, &schedule, value, vec![], None, Determinism::Enforced, ); assert_matches!(result, Ok(_)); }); } #[test] fn caller_returns_proper_values() { let origin = ALICE; let dest = BOB; parameter_types! { static WitnessedCallerBob: Option> = None; static WitnessedCallerCharlie: Option> = None; } let bob_ch = MockLoader::insert(Call, |ctx, _| { // Record the caller for bob. WitnessedCallerBob::mutate(|caller| { *caller = Some(ctx.ext.caller().account_id().unwrap().clone()) }); // Call into CHARLIE contract. assert_matches!( ctx.ext .call(Weight::zero(), BalanceOf::::zero(), CHARLIE, 0, vec![], true), Ok(_) ); exec_success() }); let charlie_ch = MockLoader::insert(Call, |ctx, _| { // Record the caller for charlie. WitnessedCallerCharlie::mutate(|caller| { *caller = Some(ctx.ext.caller().account_id().unwrap().clone()) }); exec_success() }); ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&dest, bob_ch); place_contract(&CHARLIE, charlie_ch); let contract_origin = Origin::from_account_id(origin.clone()); let mut storage_meter = storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); let result = MockStack::run_call( contract_origin.clone(), dest.clone(), &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, &schedule, 0, vec![], None, Determinism::Enforced, ); assert_matches!(result, Ok(_)); }); assert_eq!(WitnessedCallerBob::get(), Some(origin)); assert_eq!(WitnessedCallerCharlie::get(), Some(dest)); } #[test] fn is_contract_returns_proper_values() { let bob_ch = MockLoader::insert(Call, |ctx, _| { // Verify that BOB is a contract assert!(ctx.ext.is_contract(&BOB)); // Verify that ALICE is not a contract assert!(!ctx.ext.is_contract(&ALICE)); exec_success() }); ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&BOB, bob_ch); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); let result = MockStack::run_call( contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, &schedule, 0, vec![], None, Determinism::Enforced, ); assert_matches!(result, Ok(_)); }); } #[test] fn code_hash_returns_proper_values() { let code_bob = MockLoader::insert(Call, |ctx, _| { // ALICE is not a contract and hence they do not have a code_hash assert!(ctx.ext.code_hash(&ALICE).is_none()); // BOB is a contract and hence it has a code_hash assert!(ctx.ext.code_hash(&BOB).is_some()); exec_success() }); ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&BOB, code_bob); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); // ALICE (not contract) -> BOB (contract) let result = MockStack::run_call( contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, &schedule, 0, vec![0], None, Determinism::Enforced, ); assert_matches!(result, Ok(_)); }); } #[test] fn own_code_hash_returns_proper_values() { let bob_ch = MockLoader::insert(Call, |ctx, _| { let code_hash = ctx.ext.code_hash(&BOB).unwrap(); assert_eq!(*ctx.ext.own_code_hash(), code_hash); exec_success() }); ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&BOB, bob_ch); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); // ALICE (not contract) -> BOB (contract) let result = MockStack::run_call( contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, &schedule, 0, vec![0], None, Determinism::Enforced, ); assert_matches!(result, Ok(_)); }); } #[test] fn caller_is_origin_returns_proper_values() { let code_charlie = MockLoader::insert(Call, |ctx, _| { // BOB is not the origin of the stack call assert!(!ctx.ext.caller_is_origin()); exec_success() }); let code_bob = MockLoader::insert(Call, |ctx, _| { // ALICE is the origin of the call stack assert!(ctx.ext.caller_is_origin()); // BOB calls CHARLIE ctx.ext .call(Weight::zero(), BalanceOf::::zero(), CHARLIE, 0, vec![], true) }); ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&BOB, code_bob); place_contract(&CHARLIE, code_charlie); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); // ALICE -> BOB (caller is origin) -> CHARLIE (caller is not origin) let result = MockStack::run_call( contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, &schedule, 0, vec![0], None, Determinism::Enforced, ); assert_matches!(result, Ok(_)); }); } #[test] fn root_caller_succeeds() { let code_bob = MockLoader::insert(Call, |ctx, _| { // root is the origin of the call stack. assert!(ctx.ext.caller_is_root()); exec_success() }); ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&BOB, code_bob); let contract_origin = Origin::Root; let mut storage_meter = storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); // root -> BOB (caller is root) let result = MockStack::run_call( contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, &schedule, 0, vec![0], None, Determinism::Enforced, ); assert_matches!(result, Ok(_)); }); } #[test] fn root_caller_does_not_succeed_when_value_not_zero() { let code_bob = MockLoader::insert(Call, |ctx, _| { // root is the origin of the call stack. assert!(ctx.ext.caller_is_root()); exec_success() }); ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&BOB, code_bob); let contract_origin = Origin::Root; let mut storage_meter = storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); // root -> BOB (caller is root) let result = MockStack::run_call( contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, &schedule, 1, vec![0], None, Determinism::Enforced, ); assert_matches!(result, Err(_)); }); } #[test] fn root_caller_succeeds_with_consecutive_calls() { let code_charlie = MockLoader::insert(Call, |ctx, _| { // BOB is not root, even though the origin is root. assert!(!ctx.ext.caller_is_root()); exec_success() }); let code_bob = MockLoader::insert(Call, |ctx, _| { // root is the origin of the call stack. assert!(ctx.ext.caller_is_root()); // BOB calls CHARLIE. ctx.ext .call(Weight::zero(), BalanceOf::::zero(), CHARLIE, 0, vec![], true) }); ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&BOB, code_bob); place_contract(&CHARLIE, code_charlie); let contract_origin = Origin::Root; let mut storage_meter = storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); // root -> BOB (caller is root) -> CHARLIE (caller is not root) let result = MockStack::run_call( contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, &schedule, 0, vec![0], None, Determinism::Enforced, ); assert_matches!(result, Ok(_)); }); } #[test] fn address_returns_proper_values() { let bob_ch = MockLoader::insert(Call, |ctx, _| { // Verify that address matches BOB. assert_eq!(*ctx.ext.address(), BOB); // Call into charlie contract. assert_matches!( ctx.ext .call(Weight::zero(), BalanceOf::::zero(), CHARLIE, 0, vec![], true), Ok(_) ); exec_success() }); let charlie_ch = MockLoader::insert(Call, |ctx, _| { assert_eq!(*ctx.ext.address(), CHARLIE); exec_success() }); ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&BOB, bob_ch); place_contract(&CHARLIE, charlie_ch); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); let result = MockStack::run_call( contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, &schedule, 0, vec![], None, Determinism::Enforced, ); assert_matches!(result, Ok(_)); }); } #[test] fn refuse_instantiate_with_value_below_existential_deposit() { let dummy_ch = MockLoader::insert(Constructor, |_, _| exec_success()); ExtBuilder::default().existential_deposit(15).build().execute_with(|| { let schedule = ::Schedule::get(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); let executable = MockExecutable::from_storage(dummy_ch, &mut gas_meter).unwrap(); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); assert_matches!( MockStack::run_instantiate( ALICE, executable, &mut gas_meter, &mut storage_meter, &schedule, 0, // <- zero value vec![], &[], None, ), Err(_) ); }); } #[test] fn instantiation_work_with_success_output() { let dummy_ch = MockLoader::insert(Constructor, |_, _| { Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![80, 65, 83, 83] }) }); ExtBuilder::default() .with_code_hashes(MockLoader::code_hashes()) .existential_deposit(15) .build() .execute_with(|| { let schedule = ::Schedule::get(); let min_balance = ::Currency::minimum_balance(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); let executable = MockExecutable::from_storage(dummy_ch, &mut gas_meter).unwrap(); set_balance(&ALICE, min_balance * 1000); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new( &contract_origin, Some(min_balance * 100), min_balance, ) .unwrap(); let instantiated_contract_address = assert_matches!( MockStack::run_instantiate( ALICE, executable, &mut gas_meter, &mut storage_meter, &schedule, min_balance, vec![], &[], None, ), Ok((address, ref output)) if output.data == vec![80, 65, 83, 83] => address ); // Check that the newly created account has the expected code hash and // there are instantiation event. assert_eq!( ContractInfo::::load_code_hash(&instantiated_contract_address).unwrap(), dummy_ch ); assert_eq!( &events(), &[Event::Instantiated { deployer: ALICE, contract: instantiated_contract_address }] ); }); } #[test] fn instantiation_fails_with_failing_output() { let dummy_ch = MockLoader::insert(Constructor, |_, _| { Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![70, 65, 73, 76] }) }); ExtBuilder::default() .with_code_hashes(MockLoader::code_hashes()) .existential_deposit(15) .build() .execute_with(|| { let schedule = ::Schedule::get(); let min_balance = ::Currency::minimum_balance(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); let executable = MockExecutable::from_storage(dummy_ch, &mut gas_meter).unwrap(); set_balance(&ALICE, min_balance * 1000); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new( &contract_origin, Some(min_balance * 100), min_balance, ) .unwrap(); let instantiated_contract_address = assert_matches!( MockStack::run_instantiate( ALICE, executable, &mut gas_meter, &mut storage_meter, &schedule, min_balance, vec![], &[], None, ), Ok((address, ref output)) if output.data == vec![70, 65, 73, 76] => address ); // Check that the account has not been created. assert!( ContractInfo::::load_code_hash(&instantiated_contract_address).is_none() ); assert!(events().is_empty()); }); } #[test] fn instantiation_from_contract() { let dummy_ch = MockLoader::insert(Call, |_, _| exec_success()); let instantiated_contract_address = Rc::new(RefCell::new(None::>)); let instantiator_ch = MockLoader::insert(Call, { 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( Weight::zero(), BalanceOf::::zero(), dummy_ch, ::Currency::minimum_balance(), vec![], &[48, 49, 50], ) .unwrap(); *instantiated_contract_address.borrow_mut() = address.into(); Ok(output) } }); ExtBuilder::default() .with_code_hashes(MockLoader::code_hashes()) .existential_deposit(15) .build() .execute_with(|| { let schedule = ::Schedule::get(); let min_balance = ::Currency::minimum_balance(); set_balance(&ALICE, min_balance * 100); place_contract(&BOB, instantiator_ch); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new( &contract_origin, Some(min_balance * 10), min_balance * 10, ) .unwrap(); assert_matches!( MockStack::run_call( contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, &schedule, min_balance * 10, vec![], None, Determinism::Enforced, ), Ok(_) ); let instantiated_contract_address = instantiated_contract_address.borrow().as_ref().unwrap().clone(); // Check that the newly created account has the expected code hash and // there are instantiation event. assert_eq!( ContractInfo::::load_code_hash(&instantiated_contract_address).unwrap(), dummy_ch ); assert_eq!( &events(), &[ Event::Instantiated { deployer: BOB, contract: instantiated_contract_address }, Event::Called { caller: Origin::from_account_id(ALICE), contract: BOB }, ] ); }); } #[test] fn instantiation_traps() { let dummy_ch = MockLoader::insert(Constructor, |_, _| Err("It's a trap!".into())); let instantiator_ch = MockLoader::insert(Call, { move |ctx, _| { // Instantiate a contract and save it's address in `instantiated_contract_address`. assert_matches!( ctx.ext.instantiate( Weight::zero(), BalanceOf::::zero(), dummy_ch, ::Currency::minimum_balance(), vec![], &[], ), Err(ExecError { error: DispatchError::Other("It's a trap!"), origin: ErrorOrigin::Callee, }) ); exec_success() } }); ExtBuilder::default() .with_code_hashes(MockLoader::code_hashes()) .existential_deposit(15) .build() .execute_with(|| { let schedule = ::Schedule::get(); set_balance(&ALICE, 1000); set_balance(&BOB, 100); place_contract(&BOB, instantiator_ch); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, Some(200), 0).unwrap(); assert_matches!( MockStack::run_call( contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, &schedule, 0, vec![], None, Determinism::Enforced, ), Ok(_) ); // The contract wasn't instantiated so we don't expect to see an instantiation // event here. assert_eq!( &events(), &[Event::Called { caller: Origin::from_account_id(ALICE), contract: BOB },] ); }); } #[test] fn termination_from_instantiate_fails() { let terminate_ch = MockLoader::insert(Constructor, |ctx, _| { ctx.ext.terminate(&ALICE).unwrap(); exec_success() }); ExtBuilder::default() .with_code_hashes(MockLoader::code_hashes()) .existential_deposit(15) .build() .execute_with(|| { let schedule = ::Schedule::get(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); let executable = MockExecutable::from_storage(terminate_ch, &mut gas_meter).unwrap(); set_balance(&ALICE, 10_000); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 100).unwrap(); assert_eq!( MockStack::run_instantiate( ALICE, executable, &mut gas_meter, &mut storage_meter, &schedule, 100, vec![], &[], None, ), Err(Error::::TerminatedInConstructor.into()) ); assert_eq!(&events(), &[]); }); } #[test] fn in_memory_changes_not_discarded() { // Call stack: BOB -> CHARLIE (trap) -> BOB' (success) // This tests verifies some edge case of the contract info cache: // We change some value in our contract info before calling into a contract // that calls into ourself. This triggers a case where BOBs contract info // is written to storage and invalidated by the successful execution of BOB'. // The trap of CHARLIE reverts the storage changes to BOB. When the root BOB regains // control it reloads its contract info from storage. We check that changes that // are made before calling into CHARLIE are not discarded. let code_bob = MockLoader::insert(Call, |ctx, _| { if ctx.input_data[0] == 0 { let info = ctx.ext.contract_info(); assert_eq!(info.storage_byte_deposit, 0); info.storage_byte_deposit = 42; assert_eq!( ctx.ext.call( Weight::zero(), BalanceOf::::zero(), CHARLIE, 0, vec![], true ), exec_trapped() ); assert_eq!(ctx.ext.contract_info().storage_byte_deposit, 42); } exec_success() }); let code_charlie = MockLoader::insert(Call, |ctx, _| { assert!(ctx .ext .call(Weight::zero(), BalanceOf::::zero(), BOB, 0, vec![99], true) .is_ok()); exec_trapped() }); // This one tests passing the input data into a contract via call. ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&BOB, code_bob); place_contract(&CHARLIE, code_charlie); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); let result = MockStack::run_call( contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, &schedule, 0, vec![0], None, Determinism::Enforced, ); assert_matches!(result, Ok(_)); }); } #[test] fn recursive_call_during_constructor_fails() { let code = MockLoader::insert(Constructor, |ctx, _| { assert_matches!( ctx.ext.call(Weight::zero(), BalanceOf::::zero(), ctx.ext.address().clone(), 0, vec![], true), Err(ExecError{error, ..}) if error == >::ContractNotFound.into() ); exec_success() }); // This one tests passing the input data into a contract via instantiate. ExtBuilder::default() .with_code_hashes(MockLoader::code_hashes()) .build() .execute_with(|| { let schedule = ::Schedule::get(); let min_balance = ::Currency::minimum_balance(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); let executable = MockExecutable::from_storage(code, &mut gas_meter).unwrap(); set_balance(&ALICE, min_balance * 10_000); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, min_balance).unwrap(); let result = MockStack::run_instantiate( ALICE, executable, &mut gas_meter, &mut storage_meter, &schedule, min_balance, vec![], &[], None, ); assert_matches!(result, Ok(_)); }); } #[test] fn printing_works() { let code_hash = MockLoader::insert(Call, |ctx, _| { ctx.ext.append_debug_buffer("This is a test"); ctx.ext.append_debug_buffer("More text"); exec_success() }); let mut debug_buffer = DebugBufferVec::::try_from(Vec::new()).unwrap(); ExtBuilder::default().build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); let schedule = ::Schedule::get(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); set_balance(&ALICE, min_balance * 10); place_contract(&BOB, code_hash); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); MockStack::run_call( contract_origin, BOB, &mut gas_meter, &mut storage_meter, &schedule, 0, vec![], Some(&mut debug_buffer), Determinism::Enforced, ) .unwrap(); }); assert_eq!(&String::from_utf8(debug_buffer.to_vec()).unwrap(), "This is a testMore text"); } #[test] fn printing_works_on_fail() { let code_hash = MockLoader::insert(Call, |ctx, _| { ctx.ext.append_debug_buffer("This is a test"); ctx.ext.append_debug_buffer("More text"); exec_trapped() }); let mut debug_buffer = DebugBufferVec::::try_from(Vec::new()).unwrap(); ExtBuilder::default().build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); let schedule = ::Schedule::get(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); set_balance(&ALICE, min_balance * 10); place_contract(&BOB, code_hash); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); let result = MockStack::run_call( contract_origin, BOB, &mut gas_meter, &mut storage_meter, &schedule, 0, vec![], Some(&mut debug_buffer), Determinism::Enforced, ); assert!(result.is_err()); }); assert_eq!(&String::from_utf8(debug_buffer.to_vec()).unwrap(), "This is a testMore text"); } #[test] fn debug_buffer_is_limited() { let code_hash = MockLoader::insert(Call, move |ctx, _| { ctx.ext.append_debug_buffer("overflowing bytes"); exec_success() }); // Pre-fill the buffer almost up to its limit, leaving not enough space to the message let debug_buf_before = DebugBufferVec::::try_from(vec![0u8; DebugBufferVec::::bound() - 5]) .unwrap(); let mut debug_buf_after = debug_buf_before.clone(); ExtBuilder::default().build().execute_with(|| { let schedule: Schedule = ::Schedule::get(); let min_balance = ::Currency::minimum_balance(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); set_balance(&ALICE, min_balance * 10); place_contract(&BOB, code_hash); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); MockStack::run_call( contract_origin, BOB, &mut gas_meter, &mut storage_meter, &schedule, 0, vec![], Some(&mut debug_buf_after), Determinism::Enforced, ) .unwrap(); assert_eq!(debug_buf_before, debug_buf_after); }); } #[test] fn call_reentry_direct_recursion() { // call the contract passed as input with disabled reentry let code_bob = MockLoader::insert(Call, |ctx, _| { let dest = Decode::decode(&mut ctx.input_data.as_ref()).unwrap(); ctx.ext.call(Weight::zero(), BalanceOf::::zero(), dest, 0, vec![], false) }); let code_charlie = MockLoader::insert(Call, |_, _| exec_success()); ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&BOB, code_bob); place_contract(&CHARLIE, code_charlie); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); // Calling another contract should succeed assert_ok!(MockStack::run_call( contract_origin.clone(), BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, &schedule, 0, CHARLIE.encode(), None, Determinism::Enforced )); // Calling into oneself fails assert_err!( MockStack::run_call( contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, &schedule, 0, BOB.encode(), None, Determinism::Enforced ) .map_err(|e| e.error), >::ReentranceDenied, ); }); } #[test] fn call_deny_reentry() { let code_bob = MockLoader::insert(Call, |ctx, _| { if ctx.input_data[0] == 0 { ctx.ext .call(Weight::zero(), BalanceOf::::zero(), CHARLIE, 0, vec![], false) } else { exec_success() } }); // call BOB with input set to '1' let code_charlie = MockLoader::insert(Call, |ctx, _| { ctx.ext.call(Weight::zero(), BalanceOf::::zero(), BOB, 0, vec![1], true) }); ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&BOB, code_bob); place_contract(&CHARLIE, code_charlie); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); // BOB -> CHARLIE -> BOB fails as BOB denies reentry. assert_err!( MockStack::run_call( contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, &schedule, 0, vec![0], None, Determinism::Enforced ) .map_err(|e| e.error), >::ReentranceDenied, ); }); } #[test] fn call_runtime_works() { let code_hash = MockLoader::insert(Call, |ctx, _| { let call = RuntimeCall::System(frame_system::Call::remark_with_event { remark: b"Hello World".to_vec(), }); ctx.ext.call_runtime(call).unwrap(); exec_success() }); ExtBuilder::default().build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); let schedule = ::Schedule::get(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); set_balance(&ALICE, min_balance * 10); place_contract(&BOB, code_hash); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); System::reset_events(); MockStack::run_call( contract_origin, BOB, &mut gas_meter, &mut storage_meter, &schedule, 0, vec![], None, Determinism::Enforced, ) .unwrap(); let remark_hash = ::Hashing::hash(b"Hello World"); assert_eq!( System::events(), vec![ EventRecord { phase: Phase::Initialization, event: MetaEvent::System(frame_system::Event::Remarked { sender: BOB, hash: remark_hash }), topics: vec![], }, EventRecord { phase: Phase::Initialization, event: MetaEvent::Contracts(crate::Event::Called { caller: Origin::from_account_id(ALICE), contract: BOB, }), topics: vec![hash(&Origin::::from_account_id(ALICE)), hash(&BOB)], }, ] ); }); } #[test] fn call_runtime_filter() { let code_hash = MockLoader::insert(Call, |ctx, _| { use frame_system::Call as SysCall; use pallet_balances::Call as BalanceCall; use pallet_utility::Call as UtilCall; // remark should still be allowed let allowed_call = RuntimeCall::System(SysCall::remark_with_event { remark: b"Hello".to_vec() }); // transfers are disallowed by the `TestFiler` (see below) let forbidden_call = RuntimeCall::Balances(BalanceCall::transfer_allow_death { dest: CHARLIE, value: 22, }); // simple cases: direct call assert_err!( ctx.ext.call_runtime(forbidden_call.clone()), frame_system::Error::::CallFiltered ); // as part of a patch: return is OK (but it interrupted the batch) assert_ok!(ctx.ext.call_runtime(RuntimeCall::Utility(UtilCall::batch { calls: vec![allowed_call.clone(), forbidden_call, allowed_call] })),); // the transfer wasn't performed assert_eq!(get_balance(&CHARLIE), 0); exec_success() }); TestFilter::set_filter(|call| match call { RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { .. }) => false, _ => true, }); ExtBuilder::default().build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); let schedule = ::Schedule::get(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); set_balance(&ALICE, min_balance * 10); place_contract(&BOB, code_hash); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); System::reset_events(); MockStack::run_call( contract_origin, BOB, &mut gas_meter, &mut storage_meter, &schedule, 0, vec![], None, Determinism::Enforced, ) .unwrap(); let remark_hash = ::Hashing::hash(b"Hello"); assert_eq!( System::events(), vec![ EventRecord { phase: Phase::Initialization, event: MetaEvent::System(frame_system::Event::Remarked { sender: BOB, hash: remark_hash }), topics: vec![], }, EventRecord { phase: Phase::Initialization, event: MetaEvent::Utility(pallet_utility::Event::ItemCompleted), topics: vec![], }, EventRecord { phase: Phase::Initialization, event: MetaEvent::Utility(pallet_utility::Event::BatchInterrupted { index: 1, error: frame_system::Error::::CallFiltered.into() },), topics: vec![], }, EventRecord { phase: Phase::Initialization, event: MetaEvent::Contracts(crate::Event::Called { caller: Origin::from_account_id(ALICE), contract: BOB, }), topics: vec![hash(&Origin::::from_account_id(ALICE)), hash(&BOB)], }, ] ); }); } #[test] fn nonce() { let fail_code = MockLoader::insert(Constructor, |_, _| exec_trapped()); let success_code = MockLoader::insert(Constructor, |_, _| exec_success()); let succ_fail_code = MockLoader::insert(Constructor, move |ctx, _| { ctx.ext .instantiate( Weight::zero(), BalanceOf::::zero(), fail_code, ctx.ext.minimum_balance() * 100, vec![], &[], ) .ok(); exec_success() }); let succ_succ_code = MockLoader::insert(Constructor, move |ctx, _| { let (account_id, _) = ctx .ext .instantiate( Weight::zero(), BalanceOf::::zero(), success_code, ctx.ext.minimum_balance() * 100, vec![], &[], ) .unwrap(); // a plain call should not influence the account counter ctx.ext .call(Weight::zero(), BalanceOf::::zero(), account_id, 0, vec![], false) .unwrap(); exec_success() }); ExtBuilder::default() .with_code_hashes(MockLoader::code_hashes()) .build() .execute_with(|| { let schedule = ::Schedule::get(); let min_balance = ::Currency::minimum_balance(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); let fail_executable = MockExecutable::from_storage(fail_code, &mut gas_meter).unwrap(); let success_executable = MockExecutable::from_storage(success_code, &mut gas_meter).unwrap(); let succ_fail_executable = MockExecutable::from_storage(succ_fail_code, &mut gas_meter).unwrap(); let succ_succ_executable = MockExecutable::from_storage(succ_succ_code, &mut gas_meter).unwrap(); set_balance(&ALICE, min_balance * 10_000); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, min_balance * 100).unwrap(); MockStack::run_instantiate( ALICE, fail_executable, &mut gas_meter, &mut storage_meter, &schedule, min_balance * 100, vec![], &[], None, ) .ok(); assert_eq!(>::get(), 0); assert_ok!(MockStack::run_instantiate( ALICE, success_executable, &mut gas_meter, &mut storage_meter, &schedule, min_balance * 100, vec![], &[], None, )); assert_eq!(>::get(), 1); assert_ok!(MockStack::run_instantiate( ALICE, succ_fail_executable, &mut gas_meter, &mut storage_meter, &schedule, min_balance * 200, vec![], &[], None, )); assert_eq!(>::get(), 2); assert_ok!(MockStack::run_instantiate( ALICE, succ_succ_executable, &mut gas_meter, &mut storage_meter, &schedule, min_balance * 200, vec![], &[], None, )); assert_eq!(>::get(), 4); }); } #[test] fn set_storage_works() { let code_hash = MockLoader::insert(Call, |ctx, _| { // Write assert_eq!( ctx.ext.set_storage(&Key::Fix([1; 32]), Some(vec![1, 2, 3]), false), Ok(WriteOutcome::New) ); assert_eq!( ctx.ext.set_storage(&Key::Fix([2; 32]), Some(vec![4, 5, 6]), true), Ok(WriteOutcome::New) ); assert_eq!(ctx.ext.set_storage(&Key::Fix([3; 32]), None, false), Ok(WriteOutcome::New)); assert_eq!(ctx.ext.set_storage(&Key::Fix([4; 32]), None, true), Ok(WriteOutcome::New)); assert_eq!( ctx.ext.set_storage(&Key::Fix([5; 32]), Some(vec![]), false), Ok(WriteOutcome::New) ); assert_eq!( ctx.ext.set_storage(&Key::Fix([6; 32]), Some(vec![]), true), Ok(WriteOutcome::New) ); // Overwrite assert_eq!( ctx.ext.set_storage(&Key::Fix([1; 32]), Some(vec![42]), false), Ok(WriteOutcome::Overwritten(3)) ); assert_eq!( ctx.ext.set_storage(&Key::Fix([2; 32]), Some(vec![48]), true), Ok(WriteOutcome::Taken(vec![4, 5, 6])) ); assert_eq!(ctx.ext.set_storage(&Key::Fix([3; 32]), None, false), Ok(WriteOutcome::New)); assert_eq!(ctx.ext.set_storage(&Key::Fix([4; 32]), None, true), Ok(WriteOutcome::New)); assert_eq!( ctx.ext.set_storage(&Key::Fix([5; 32]), Some(vec![]), false), Ok(WriteOutcome::Overwritten(0)) ); assert_eq!( ctx.ext.set_storage(&Key::Fix([6; 32]), Some(vec![]), true), Ok(WriteOutcome::Taken(vec![])) ); exec_success() }); ExtBuilder::default().build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); let schedule = ::Schedule::get(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); set_balance(&ALICE, min_balance * 1000); place_contract(&BOB, code_hash); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 0).unwrap(); assert_ok!(MockStack::run_call( contract_origin, BOB, &mut gas_meter, &mut storage_meter, &schedule, 0, vec![], None, Determinism::Enforced )); }); } #[test] fn set_storage_varsized_key_works() { let code_hash = MockLoader::insert(Call, |ctx, _| { // Write assert_eq!( ctx.ext.set_storage( &Key::::try_from_var([1; 64].to_vec()).unwrap(), Some(vec![1, 2, 3]), false ), Ok(WriteOutcome::New) ); assert_eq!( ctx.ext.set_storage( &Key::::try_from_var([2; 19].to_vec()).unwrap(), Some(vec![4, 5, 6]), true ), Ok(WriteOutcome::New) ); assert_eq!( ctx.ext.set_storage( &Key::::try_from_var([3; 19].to_vec()).unwrap(), None, false ), Ok(WriteOutcome::New) ); assert_eq!( ctx.ext.set_storage( &Key::::try_from_var([4; 64].to_vec()).unwrap(), None, true ), Ok(WriteOutcome::New) ); assert_eq!( ctx.ext.set_storage( &Key::::try_from_var([5; 30].to_vec()).unwrap(), Some(vec![]), false ), Ok(WriteOutcome::New) ); assert_eq!( ctx.ext.set_storage( &Key::::try_from_var([6; 128].to_vec()).unwrap(), Some(vec![]), true ), Ok(WriteOutcome::New) ); // Overwrite assert_eq!( ctx.ext.set_storage( &Key::::try_from_var([1; 64].to_vec()).unwrap(), Some(vec![42, 43, 44]), false ), Ok(WriteOutcome::Overwritten(3)) ); assert_eq!( ctx.ext.set_storage( &Key::::try_from_var([2; 19].to_vec()).unwrap(), Some(vec![48]), true ), Ok(WriteOutcome::Taken(vec![4, 5, 6])) ); assert_eq!( ctx.ext.set_storage( &Key::::try_from_var([3; 19].to_vec()).unwrap(), None, false ), Ok(WriteOutcome::New) ); assert_eq!( ctx.ext.set_storage( &Key::::try_from_var([4; 64].to_vec()).unwrap(), None, true ), Ok(WriteOutcome::New) ); assert_eq!( ctx.ext.set_storage( &Key::::try_from_var([5; 30].to_vec()).unwrap(), Some(vec![]), false ), Ok(WriteOutcome::Overwritten(0)) ); assert_eq!( ctx.ext.set_storage( &Key::::try_from_var([6; 128].to_vec()).unwrap(), Some(vec![]), true ), Ok(WriteOutcome::Taken(vec![])) ); exec_success() }); ExtBuilder::default().build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); let schedule = ::Schedule::get(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); set_balance(&ALICE, min_balance * 1000); place_contract(&BOB, code_hash); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 0).unwrap(); assert_ok!(MockStack::run_call( contract_origin, BOB, &mut gas_meter, &mut storage_meter, &schedule, 0, vec![], None, Determinism::Enforced )); }); } #[test] fn get_storage_works() { let code_hash = MockLoader::insert(Call, |ctx, _| { assert_eq!( ctx.ext.set_storage(&Key::Fix([1; 32]), Some(vec![1, 2, 3]), false), Ok(WriteOutcome::New) ); assert_eq!( ctx.ext.set_storage(&Key::Fix([2; 32]), Some(vec![]), false), Ok(WriteOutcome::New) ); assert_eq!(ctx.ext.get_storage(&Key::Fix([1; 32])), Some(vec![1, 2, 3])); assert_eq!(ctx.ext.get_storage(&Key::Fix([2; 32])), Some(vec![])); assert_eq!(ctx.ext.get_storage(&Key::Fix([3; 32])), None); exec_success() }); ExtBuilder::default().build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); let schedule = ::Schedule::get(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); set_balance(&ALICE, min_balance * 1000); place_contract(&BOB, code_hash); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 0).unwrap(); assert_ok!(MockStack::run_call( contract_origin, BOB, &mut gas_meter, &mut storage_meter, &schedule, 0, vec![], None, Determinism::Enforced )); }); } #[test] fn get_storage_size_works() { let code_hash = MockLoader::insert(Call, |ctx, _| { assert_eq!( ctx.ext.set_storage(&Key::Fix([1; 32]), Some(vec![1, 2, 3]), false), Ok(WriteOutcome::New) ); assert_eq!( ctx.ext.set_storage(&Key::Fix([2; 32]), Some(vec![]), false), Ok(WriteOutcome::New) ); assert_eq!(ctx.ext.get_storage_size(&Key::Fix([1; 32])), Some(3)); assert_eq!(ctx.ext.get_storage_size(&Key::Fix([2; 32])), Some(0)); assert_eq!(ctx.ext.get_storage_size(&Key::Fix([3; 32])), None); exec_success() }); ExtBuilder::default().build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); let schedule = ::Schedule::get(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); set_balance(&ALICE, min_balance * 1000); place_contract(&BOB, code_hash); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 0).unwrap(); assert_ok!(MockStack::run_call( contract_origin, BOB, &mut gas_meter, &mut storage_meter, &schedule, 0, vec![], None, Determinism::Enforced )); }); } #[test] fn get_storage_varsized_key_works() { let code_hash = MockLoader::insert(Call, |ctx, _| { assert_eq!( ctx.ext.set_storage( &Key::::try_from_var([1; 19].to_vec()).unwrap(), Some(vec![1, 2, 3]), false ), Ok(WriteOutcome::New) ); assert_eq!( ctx.ext.set_storage( &Key::::try_from_var([2; 16].to_vec()).unwrap(), Some(vec![]), false ), Ok(WriteOutcome::New) ); assert_eq!( ctx.ext.get_storage(&Key::::try_from_var([1; 19].to_vec()).unwrap()), Some(vec![1, 2, 3]) ); assert_eq!( ctx.ext.get_storage(&Key::::try_from_var([2; 16].to_vec()).unwrap()), Some(vec![]) ); assert_eq!( ctx.ext.get_storage(&Key::::try_from_var([3; 8].to_vec()).unwrap()), None ); exec_success() }); ExtBuilder::default().build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); let schedule = ::Schedule::get(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); set_balance(&ALICE, min_balance * 1000); place_contract(&BOB, code_hash); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 0).unwrap(); assert_ok!(MockStack::run_call( contract_origin, BOB, &mut gas_meter, &mut storage_meter, &schedule, 0, vec![], None, Determinism::Enforced )); }); } #[test] fn get_storage_size_varsized_key_works() { let code_hash = MockLoader::insert(Call, |ctx, _| { assert_eq!( ctx.ext.set_storage( &Key::::try_from_var([1; 19].to_vec()).unwrap(), Some(vec![1, 2, 3]), false ), Ok(WriteOutcome::New) ); assert_eq!( ctx.ext.set_storage( &Key::::try_from_var([2; 16].to_vec()).unwrap(), Some(vec![]), false ), Ok(WriteOutcome::New) ); assert_eq!( ctx.ext.get_storage_size(&Key::::try_from_var([1; 19].to_vec()).unwrap()), Some(3) ); assert_eq!( ctx.ext.get_storage_size(&Key::::try_from_var([2; 16].to_vec()).unwrap()), Some(0) ); assert_eq!( ctx.ext.get_storage_size(&Key::::try_from_var([3; 8].to_vec()).unwrap()), None ); exec_success() }); ExtBuilder::default().build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); let schedule = ::Schedule::get(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); set_balance(&ALICE, min_balance * 1000); place_contract(&BOB, code_hash); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 0).unwrap(); assert_ok!(MockStack::run_call( contract_origin, BOB, &mut gas_meter, &mut storage_meter, &schedule, 0, vec![], None, Determinism::Enforced )); }); } #[test] fn ecdsa_to_eth_address_returns_proper_value() { let bob_ch = MockLoader::insert(Call, |ctx, _| { let pubkey_compressed = array_bytes::hex2array_unchecked( "028db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd91", ); assert_eq!( ctx.ext.ecdsa_to_eth_address(&pubkey_compressed).unwrap(), array_bytes::hex2array_unchecked::<_, 20>( "09231da7b19A016f9e576d23B16277062F4d46A8" ) ); exec_success() }); ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&BOB, bob_ch); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); let result = MockStack::run_call( contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, &schedule, 0, vec![], None, Determinism::Enforced, ); assert_matches!(result, Ok(_)); }); } #[test] fn nonce_api_works() { let fail_code = MockLoader::insert(Constructor, |_, _| exec_trapped()); let success_code = MockLoader::insert(Constructor, |_, _| exec_success()); let code_hash = MockLoader::insert(Call, move |ctx, _| { // It is set to one when this contract was instantiated by `place_contract` assert_eq!(ctx.ext.nonce(), 1); // Should not change without any instantiation in-between assert_eq!(ctx.ext.nonce(), 1); // Should not change with a failed instantiation assert_err!( ctx.ext.instantiate( Weight::zero(), BalanceOf::::zero(), fail_code, 0, vec![], &[], ), ExecError { error: >::ContractTrapped.into(), origin: ErrorOrigin::Callee } ); assert_eq!(ctx.ext.nonce(), 1); // Successful instantiation increments ctx.ext .instantiate( Weight::zero(), BalanceOf::::zero(), success_code, 0, vec![], &[], ) .unwrap(); assert_eq!(ctx.ext.nonce(), 2); exec_success() }); ExtBuilder::default() .with_code_hashes(MockLoader::code_hashes()) .build() .execute_with(|| { let min_balance = ::Currency::minimum_balance(); let schedule = ::Schedule::get(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); set_balance(&ALICE, min_balance * 1000); place_contract(&BOB, code_hash); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 0).unwrap(); assert_ok!(MockStack::run_call( contract_origin, BOB, &mut gas_meter, &mut storage_meter, &schedule, 0, vec![], None, Determinism::Enforced )); }); } /// This works even though random interface is deprecated, as the check to ban deprecated /// functions happens in the wasm stack which is mocked for exec tests. #[test] fn randomness_works() { let subject = b"nice subject".as_ref(); let code_hash = MockLoader::insert(Call, move |ctx, _| { let rand = ::Randomness::random(subject); assert_eq!(rand, ctx.ext.random(subject)); exec_success() }); ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&BOB, code_hash); let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); let result = MockStack::run_call( contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, &schedule, 0, vec![], None, Determinism::Enforced, ); assert_matches!(result, Ok(_)); }); } }