diff --git a/Cargo.lock b/Cargo.lock index 5fa2f4232323b..47fa5f3ae7ee4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2163,7 +2163,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "pwasm-utils" -version = "0.3.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2797,10 +2797,11 @@ name = "srml-contract" version = "0.1.0" dependencies = [ "assert_matches 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "hex-literal 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "parity-codec 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-codec-derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-wasm 0.31.3 (registry+https://github.com/rust-lang/crates.io-index)", - "pwasm-utils 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pwasm-utils 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", "sr-io 0.1.0", "sr-primitives 0.1.0", @@ -4186,7 +4187,7 @@ name = "twox-hash" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -4700,7 +4701,7 @@ dependencies = [ "checksum proc-macro2 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "77997c53ae6edd6d187fec07ec41b207063b5ee6f33680e9fa86d405cdd313d4" "checksum proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)" = "77619697826f31a02ae974457af0b29b723e5619e113e9397b8b82c6bd253f09" "checksum protobuf 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eae0479da11de87d371fa47eb033059715ffa8f714748da20aa364d56ec5bee2" -"checksum pwasm-utils 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "efd695333cfae6e9dbe2703a6d040e252b57a6fc3b9a65c712615ac042b2e0c5" +"checksum pwasm-utils 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "efb0dcbddbb600f47a7098d33762a00552c671992171637f5bb310b37fe1f0e4" "checksum quick-error 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5fb6ccf8db7bbcb9c2eae558db5ab4f3da1c2a87e4e597ed394726bc8ea6ca1d" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" "checksum quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9949cfe66888ffe1d53e6ec9d9f3b70714083854be20fd5e271b232a017401e8" diff --git a/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm b/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm index 7180a33698590..ffc7eabf55ea1 100644 Binary files a/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm and b/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm differ diff --git a/node/executor/src/lib.rs b/node/executor/src/lib.rs index 15499ba31f429..af366faf0a768 100644 --- a/node/executor/src/lib.rs +++ b/node/executor/src/lib.rs @@ -61,7 +61,7 @@ mod tests { use primitives::{twox_128, Blake2Hasher, ChangesTrieConfiguration, ed25519::{Public, Pair}}; use node_primitives::{Hash, BlockNumber, AccountId}; - use runtime_primitives::traits::{Header as HeaderT, Digest as DigestT}; + use runtime_primitives::traits::{Header as HeaderT, Digest as DigestT, Hash as HashT}; use runtime_primitives::{generic, generic::Era, ApplyOutcome, ApplyError, ApplyResult, Perbill}; use {balances, indices, staking, session, system, consensus, timestamp, treasury, contract}; use contract::ContractAddressFor; @@ -315,9 +315,9 @@ mod tests { 1, GENESIS_HASH.into(), if support_changes_trie { - hex!("e2dc1ed62a3878e084c49baef3eed4bc462caaa1ee9db0763eaa8d39d1affc7e").into() + hex!("cc63808897a07869d3b9103df5ad92f9be2f865ece506df5de0a87b2a95131d5").into() } else { - hex!("7dad66de05ab6391ece9db092288cb39ba723f7f5d0b69b507b753fc92c98b8e").into() + hex!("fe5275f4d9f8130c8e80d0132f0a718ae0eeea2872c841843192720ad5c3f05a").into() }, if support_changes_trie { vec![changes_trie_log( @@ -343,7 +343,7 @@ mod tests { construct_block( 2, block1(false).1, - hex!("f085a4077d071d3334f7553bcc3f10d97fd612e25c6df5c8f94151bbe557e24a").into(), + hex!("45b6655508fb524467b5c24184a7509b9ae07db4f95e16052ed425af182f39a8").into(), vec![ // session changes here, so we add a grandpa change signal log. Log::from(::grandpa::RawLog::AuthoritiesChangeSignal(0, vec![ (Keyring::One.to_raw_public().into(), 1), @@ -372,7 +372,7 @@ mod tests { construct_block( 1, GENESIS_HASH.into(), - hex!("1d47422645f5fa3d79cff9ab5cd69ee62919c909860f063bdaf18eda834baa05").into(), + hex!("ec00658cc2826d3499dde2954e399f0a0b2596eec1b0da9b76bc72394161dc99").into(), vec![], vec![ CheckedExtrinsic { @@ -546,6 +546,8 @@ mod tests { (import "env" "ext_input_size" (func $ext_input_size (result i32))) (import "env" "ext_input_copy" (func $ext_input_copy (param i32 i32 i32))) (import "env" "memory" (memory 1 1)) + (func (export "deploy") + ) (func (export "call") (block $fail ;; fail if ext_input_size != 4 @@ -613,55 +615,15 @@ mod tests { ) "#; - /// Convert a byte slice to a string with hex values. - /// Convert a byte slice to a string with hex values. - /// - /// Each value is preceeded with a `\` character. - fn escaped_bytestring(bytes: &[u8]) -> String { - use std::fmt::Write; - let mut result = String::new(); - for b in bytes { - write!(result, "\\{:02x}", b).unwrap(); - } - result - } - - /// Create a constructor for the specified code. - /// - /// When constructor is executed, it will call `ext_return` with code that - /// specified in `child_bytecode`. - fn code_ctor(child_bytecode: &[u8]) -> String { - format!( - r#" - (module - ;; ext_return(data_ptr: u32, data_len: u32) -> ! - (import "env" "ext_return" (func $ext_return (param i32 i32))) - (import "env" "memory" (memory 1 1)) - (func (export "call") - (call $ext_return - (i32.const 4) - (i32.const {code_len}) - ) - ;; ext_return is diverging, i.e. doesn't return. - unreachable - ) - (data (i32.const 4) "{escaped_bytecode}") - ) - "#, - escaped_bytecode = escaped_bytestring(child_bytecode), - code_len = child_bytecode.len(), - ) - } - #[test] fn deploying_wasm_contract_should_work() { let mut t = new_test_ext(COMPACT_CODE, false); - let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap(); - let code_ctor_transfer = wabt::wat2wasm(&code_ctor(&code_transfer)).unwrap(); + let transfer_code = wabt::wat2wasm(CODE_TRANSFER).unwrap(); + let transfer_ch = ::Hashing::hash(&transfer_code); let addr = ::DetermineContractAddress::contract_address_for( - &code_ctor_transfer, + &transfer_ch, &[], &charlie(), ); @@ -669,7 +631,7 @@ mod tests { let b = construct_block( 1, GENESIS_HASH.into(), - hex!("c442a475a16805d20c326f1134fe5a9656511ed7e1a7f0c7dae2dfc8612e418b").into(), + hex!("6a4da4ed61c4d9eba0477aa67024d573693df781176dfe7fe903d1088b38b266").into(), vec![], vec![ CheckedExtrinsic { @@ -679,11 +641,17 @@ mod tests { CheckedExtrinsic { signed: Some((charlie(), 0)), function: Call::Contract( - contract::Call::create::(10.into(), 10_000.into(), code_ctor_transfer, Vec::new()) + contract::Call::put_code::(10_000.into(), transfer_code) ), }, CheckedExtrinsic { signed: Some((charlie(), 1)), + function: Call::Contract( + contract::Call::create::(10.into(), 10_000.into(), transfer_ch, Vec::new()) + ), + }, + CheckedExtrinsic { + signed: Some((charlie(), 2)), function: Call::Contract( contract::Call::call::(indices::address::Address::Id(addr), 10.into(), 10_000.into(), vec![0x00, 0x01, 0x02, 0x03]) ), @@ -695,7 +663,7 @@ mod tests { runtime_io::with_externalities(&mut t, || { // Verify that the contract constructor worked well and code of TRANSFER contract is actually deployed. - assert_eq!(&contract::CodeOf::::get(addr), &code_transfer); + assert_eq!(&contract::CodeHashOf::::get(addr).unwrap(), &transfer_ch); }); } diff --git a/node/runtime/wasm/Cargo.lock b/node/runtime/wasm/Cargo.lock index 2dd8260e8331e..e8b2819f0fbb7 100644 --- a/node/runtime/wasm/Cargo.lock +++ b/node/runtime/wasm/Cargo.lock @@ -703,7 +703,7 @@ dependencies = [ [[package]] name = "pwasm-utils" -version = "0.3.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1093,7 +1093,7 @@ dependencies = [ "parity-codec 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-codec-derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-wasm 0.31.3 (registry+https://github.com/rust-lang/crates.io-index)", - "pwasm-utils 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pwasm-utils 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.81 (registry+https://github.com/rust-lang/crates.io-index)", "sr-io 0.1.0", "sr-primitives 0.1.0", @@ -1998,7 +1998,7 @@ dependencies = [ "checksum proc-macro-hack 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2c725b36c99df7af7bf9324e9c999b9e37d92c8f8caf106d82e1d7953218d2d8" "checksum proc-macro-hack-impl 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2b753ad9ed99dd8efeaa7d2fb8453c8f6bc3e54b97966d35f1bc77ca6865254a" "checksum proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)" = "77619697826f31a02ae974457af0b29b723e5619e113e9397b8b82c6bd253f09" -"checksum pwasm-utils 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "efd695333cfae6e9dbe2703a6d040e252b57a6fc3b9a65c712615ac042b2e0c5" +"checksum pwasm-utils 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e9135bed7b452e20dbb395a2d519abaf0c46d60e7ecc02daeeab447d29bada1" "checksum quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "53fa22a1994bd0f9372d7a816207d8a2677ad0325b073f5c5332760f0fb62b5c" "checksum rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8356f47b32624fef5b3301c1be97e5944ecdd595409cc5da11d05f211db6cfbd" "checksum rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e464cd887e869cddcae8792a4ee31d23c7edd516700695608f5b98c67ee0131c" diff --git a/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm b/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm index 9ed1cd63060bd..874b773d579dd 100644 Binary files a/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm and b/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm differ diff --git a/srml/contract/Cargo.toml b/srml/contract/Cargo.toml index 08ae5c0c5418e..2a265cb0fac53 100644 --- a/srml/contract/Cargo.toml +++ b/srml/contract/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Parity Technologies "] [dependencies] serde = { version = "1.0", default-features = false } -pwasm-utils = { version = "0.3", default-features = false } +pwasm-utils = { version = "0.6.1", default-features = false } parity-codec = { version = "2.2", default-features = false } parity-codec-derive = { version = "2.1", default-features = false } parity-wasm = { version = "0.31", default-features = false } @@ -21,6 +21,7 @@ srml-balances = { path = "../balances", default-features = false } [dev-dependencies] wabt = "~0.7.4" assert_matches = "1.1" +hex-literal = "0.1.0" [features] default = ["std"] diff --git a/srml/contract/src/account_db.rs b/srml/contract/src/account_db.rs index af71b6bbd91fc..6142abcbe2223 100644 --- a/srml/contract/src/account_db.rs +++ b/srml/contract/src/account_db.rs @@ -16,7 +16,7 @@ //! Auxilliaries to help with managing partial changes to accounts state. -use super::{CodeOf, StorageOf, Trait}; +use super::{CodeHash, CodeHashOf, StorageOf, Trait}; use rstd::cell::RefCell; use rstd::collections::btree_map::{BTreeMap, Entry}; use rstd::prelude::*; @@ -25,7 +25,8 @@ use {balances, system}; pub struct ChangeEntry { balance: Option, - code: Option>, + /// In the case the outer option is None, the code_hash remains untouched, while providing `Some(None)` signifies a removing of the code in question + code: Option>>, storage: BTreeMap, Option>>, } @@ -44,7 +45,7 @@ pub type ChangeSet = BTreeMap<::AccountId, ChangeEntry pub trait AccountDb { fn get_storage(&self, account: &T::AccountId, location: &[u8]) -> Option>; - fn get_code(&self, account: &T::AccountId) -> Vec; + fn get_code(&self, account: &T::AccountId) -> Option>; fn get_balance(&self, account: &T::AccountId) -> T::Balance; fn commit(&mut self, change_set: ChangeSet); @@ -55,8 +56,8 @@ impl AccountDb for DirectAccountDb { fn get_storage(&self, account: &T::AccountId, location: &[u8]) -> Option> { >::get(account.clone(), location.to_vec()) } - fn get_code(&self, account: &T::AccountId) -> Vec { - >::get(account) + fn get_code(&self, account: &T::AccountId) -> Option> { + >::get(account) } fn get_balance(&self, account: &T::AccountId) -> T::Balance { balances::Module::::free_balance(account) @@ -68,13 +69,17 @@ impl AccountDb for DirectAccountDb { balances::Module::::set_free_balance_creating(&address, balance) { // Account killed. This will ultimately lead to calling `OnFreeBalanceZero` callback - // which will make removal of CodeOf and StorageOf for this account. + // which will make removal of CodeHashOf and StorageOf for this account. // In order to avoid writing over the deleted properties we `continue` here. continue; } } if let Some(code) = changed.code { - >::insert(&address, &code); + if let Some(code) = code { + >::insert(&address, code); + } else { + >::remove(&address); + } } for (k, v) in changed.storage.into_iter() { if let Some(value) = v { @@ -116,7 +121,7 @@ impl<'a, T: Trait> OverlayAccountDb<'a, T> { .storage .insert(location, value); } - pub fn set_code(&mut self, account: &T::AccountId, code: Vec) { + pub fn set_code(&mut self, account: &T::AccountId, code: Option>) { self.local .borrow_mut() .entry(account.clone()) @@ -141,7 +146,7 @@ impl<'a, T: Trait> AccountDb for OverlayAccountDb<'a, T> { .cloned() .unwrap_or_else(|| self.underlying.get_storage(account, location)) } - fn get_code(&self, account: &T::AccountId) -> Vec { + fn get_code(&self, account: &T::AccountId) -> Option> { self.local .borrow() .get(account) diff --git a/srml/contract/src/exec.rs b/srml/contract/src/exec.rs index 25dfe2e8fe0c4..7b5cede5cfd89 100644 --- a/srml/contract/src/exec.rs +++ b/srml/contract/src/exec.rs @@ -14,68 +14,268 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -use super::{ContractAddressFor, Trait, Event, RawEvent, Config}; -use account_db::{AccountDb, OverlayAccountDb}; -use gas::GasMeter; -use vm; +use super::{CodeHash, Config, ContractAddressFor, Event, RawEvent, Trait}; +use account_db::{AccountDb, DirectAccountDb, OverlayAccountDb}; +use gas::{GasMeter, Token}; -use rstd::prelude::*; -use runtime_primitives::traits::{Zero, CheckedAdd, CheckedSub}; use balances::{self, EnsureAccountLiquid}; +use rstd::prelude::*; +use runtime_primitives::traits::{As, CheckedAdd, CheckedSub, Zero}; + +pub type BalanceOf = ::Balance; +pub type AccountIdOf = ::AccountId; + +#[cfg_attr(test, derive(Debug))] +pub struct InstantiateReceipt { + pub address: AccountId, +} + +#[cfg_attr(test, derive(Debug))] +pub struct CallReceipt { + /// Output data received as a result of a call. + pub output_data: Vec, +} + +/// An interface that provides access to the external environment in which the +/// smart-contract is executed. +/// +/// This interface is specialised to an account of the executing code, so all +/// operations are implicitly performed on that account. +pub trait Ext { + type T: Trait; + + /// Returns the storage entry of the executing account by the given `key`. + /// + /// Returns `None` if the `key` wasn't previously set by `set_storage` or + /// was deleted. + fn get_storage(&self, key: &[u8]) -> 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: &[u8], value: Option>); + + /// Instantiate a contract from the given code. + /// + /// The newly created account will be associated with `code`. `value` specifies the amount of value + /// transfered from this to the newly created account (also known as endowment). + fn instantiate( + &mut self, + code: &CodeHash, + value: BalanceOf, + gas_meter: &mut GasMeter, + input_data: &[u8], + ) -> Result>, &'static str>; + + /// Call (possibly transfering some amount of funds) into the specified account. + fn call( + &mut self, + to: &AccountIdOf, + value: BalanceOf, + gas_meter: &mut GasMeter, + input_data: &[u8], + empty_output_buf: EmptyOutputBuf, + ) -> Result; + + /// Returns a reference to the account id of the caller. + fn caller(&self) -> &AccountIdOf; + + /// Returns a reference to the account id of the current contract. + fn address(&self) -> &AccountIdOf; +} + +/// Loader is a companion of the `Vm` trait. It loads an appropriate abstract +/// executable to be executed by an accompanying `Vm` implementation. +pub trait Loader { + type Executable; + + /// Load the initializer portion of the code specified by the `code_hash`. This + /// executable is called upon instantiation. + fn load_init(&self, code_hash: &CodeHash) -> Result; + /// Load the main portion of the code specified by the `code_hash`. This executable + /// is called for each call to a contract. + fn load_main(&self, code_hash: &CodeHash) -> Result; +} + +/// An `EmptyOutputBuf` is used as an optimization for reusing empty vectors when +/// available. +/// +/// You can create this structure from a spare vector if you have any and then +/// you can fill it (only once), converting it to `OutputBuf`. +pub struct EmptyOutputBuf(Vec); + +impl EmptyOutputBuf { + /// Create an output buffer from a spare vector which is not longer needed. + /// + /// All contents are discarded, but capacity is preserved. + pub fn from_spare_vec(mut v: Vec) -> Self { + v.clear(); + EmptyOutputBuf(v) + } + + /// Create an output buffer ready for receiving a result. + /// + /// Use this function to create output buffer if you don't have a spare + /// vector. Otherwise, use `from_spare_vec`. + pub fn new() -> Self { + EmptyOutputBuf(Vec::new()) + } + + /// Write to the buffer result of the specified size. + /// + /// Calls closure with the buffer of the requested size. + pub fn fill Result<(), E>>(mut self, size: usize, f: F) -> Result { + assert!(self.0.len() == 0, "the vector is always cleared; it's written only once"); + self.0.resize(size, 0); + f(&mut self.0).map(|()| OutputBuf(self.0)) + } +} -// TODO: Add logs -pub struct CreateReceipt { - pub address: T::AccountId, +/// `OutputBuf` is the end result of filling an `EmptyOutputBuf`. +pub struct OutputBuf(Vec); + +#[must_use] +pub enum VmExecResult { + Ok, + Returned(OutputBuf), + /// A program executed some forbidden operation. + /// + /// This can include, e.g.: division by 0, OOB access or failure to satisfy some precondition + /// of a system call. + /// + /// Contains some vm-specific description of an trap. + Trap(&'static str), +} + +impl VmExecResult { + pub fn into_result(self) -> Result, &'static str> { + match self { + VmExecResult::Ok => Ok(Vec::new()), + VmExecResult::Returned(buf) => Ok(buf.0), + VmExecResult::Trap(description) => Err(description), + } + } +} + +/// A trait that represent a virtual machine. +/// +/// You can view a virtual machine as something that takes code, an input data buffer, +/// queries it and/or performs actions on the given `Ext` and optionally +/// returns an output data buffer. The type of code depends on the particular virtual machine. +/// +/// Execution of code can end by either implicit termination (that is, reached the end of +/// executable), explicit termination via returning a buffer or termination due to a trap. +/// +/// You can optionally provide a vector for collecting output if a spare is available. If you don't have +/// it will be created anyway. +pub trait Vm { + type Executable; + + fn execute>( + &self, + exec: &Self::Executable, + ext: &mut E, + input_data: &[u8], + empty_output_buf: EmptyOutputBuf, + gas_meter: &mut GasMeter, + ) -> VmExecResult; } -// TODO: Add logs. -pub struct CallReceipt; +#[cfg_attr(test, derive(Debug, PartialEq, Eq))] +#[derive(Copy, Clone)] +pub enum ExecFeeToken { + /// Base fee charged for a call. + Call, + /// Base fee charged for a instantiate. + Instantiate, +} -pub struct ExecutionContext<'a, T: Trait + 'a> { - // typically should be dest +impl Token for ExecFeeToken { + type Metadata = Config; + #[inline] + fn calculate_amount(&self, metadata: &Config) -> T::Gas { + match *self { + ExecFeeToken::Call => metadata.call_base_fee, + ExecFeeToken::Instantiate => metadata.instantiate_base_fee, + } + } +} + +pub struct ExecutionContext<'a, T: Trait + 'a, V, L> { pub self_account: T::AccountId, pub overlay: OverlayAccountDb<'a, T>, pub depth: usize, pub events: Vec>, pub config: &'a Config, + pub vm: &'a V, + pub loader: &'a L, } -impl<'a, T: Trait> ExecutionContext<'a, T> { - /// Make a call to the specified address. +impl<'a, T, E, V, L> ExecutionContext<'a, T, V, L> +where + T: Trait, + L: Loader, + V: Vm, +{ + /// Create the top level execution context. + /// + /// The specified `origin` address will be used as `sender` for + pub fn top_level(origin: T::AccountId, cfg: &'a Config, vm: &'a V, loader: &'a L) -> Self { + let overlay = OverlayAccountDb::::new(&DirectAccountDb); + ExecutionContext { + self_account: origin, + depth: 0, + overlay, + events: Vec::new(), + config: &cfg, + vm: &vm, + loader: &loader, + } + } + + fn nested(&self, overlay: OverlayAccountDb<'a, T>, dest: T::AccountId) -> Self { + ExecutionContext { + overlay: overlay, + self_account: dest, + depth: self.depth + 1, + events: Vec::new(), + config: self.config, + vm: self.vm, + loader: self.loader, + } + } + + /// Make a call to the specified address, optionally transfering some funds. pub fn call( &mut self, - caller: T::AccountId, dest: T::AccountId, value: T::Balance, gas_meter: &mut GasMeter, - data: &[u8], - output_data: &mut Vec, + input_data: &[u8], + empty_output_buf: EmptyOutputBuf, ) -> Result { if self.depth == self.config.max_depth as usize { return Err("reached maximum depth, cannot make a call"); } - if gas_meter.charge(self.config.call_base_fee).is_out_of_gas() { + if gas_meter + .charge(self.config, ExecFeeToken::Call) + .is_out_of_gas() + { return Err("not enough gas to pay base call fee"); } - let dest_code = self.overlay.get_code(&dest); + let dest_code_hash = self.overlay.get_code(&dest); + let mut output_data = Vec::new(); let (change_set, events) = { let mut overlay = OverlayAccountDb::new(&self.overlay); - - let mut nested = ExecutionContext { - overlay: overlay, - self_account: dest.clone(), - depth: self.depth + 1, - events: Vec::new(), - config: self.config, - }; + let mut nested = self.nested(overlay, dest.clone()); if value > T::Balance::zero() { transfer( gas_meter, - false, + TransferCause::Call, &self.self_account, &dest, value, @@ -83,18 +283,21 @@ impl<'a, T: Trait> ExecutionContext<'a, T> { )?; } - if !dest_code.is_empty() { - vm::execute( - &dest_code, - data, - output_data, - &mut CallContext { - ctx: &mut nested, - caller: caller, - }, - &self.config.schedule, - gas_meter, - ).map_err(|_| "vm execute returned error while call")?; + if let Some(dest_code_hash) = dest_code_hash { + let executable = self.loader.load_main(&dest_code_hash)?; + output_data = self + .vm + .execute( + &executable, + &mut CallContext { + ctx: &mut nested, + caller: self.self_account.clone(), + }, + input_data, + empty_output_buf, + gas_meter, + ) + .into_result()?; } (nested.overlay.into_change_set(), nested.events) @@ -103,88 +306,128 @@ impl<'a, T: Trait> ExecutionContext<'a, T> { self.overlay.commit(change_set); self.events.extend(events); - Ok(CallReceipt) + Ok(CallReceipt { output_data }) } - pub fn create( + pub fn instantiate( &mut self, - caller: T::AccountId, endowment: T::Balance, gas_meter: &mut GasMeter, - init_code: &[u8], - data: &[u8], - ) -> Result, &'static str> { + code_hash: &CodeHash, + input_data: &[u8], + ) -> Result, &'static str> { if self.depth == self.config.max_depth as usize { return Err("reached maximum depth, cannot create"); } - if gas_meter.charge(self.config.create_base_fee).is_out_of_gas() { - return Err("not enough gas to pay base create fee"); + if gas_meter + .charge(self.config, ExecFeeToken::Instantiate) + .is_out_of_gas() + { + return Err("not enough gas to pay base instantiate fee"); } - let dest = T::DetermineContractAddress::contract_address_for(init_code, data, &self.self_account); + let dest = T::DetermineContractAddress::contract_address_for( + code_hash, + input_data, + &self.self_account, + ); - if !self.overlay.get_code(&dest).is_empty() { + if self.overlay.get_code(&dest).is_some() { // It should be enough to check only the code. return Err("contract already exists"); } let (change_set, events) = { let mut overlay = OverlayAccountDb::new(&self.overlay); + overlay.set_code(&dest, Some(code_hash.clone())); + let mut nested = self.nested(overlay, dest.clone()); - let mut nested = ExecutionContext { - overlay: overlay, - self_account: dest.clone(), - depth: self.depth + 1, - events: Vec::new(), - config: self.config, - }; + // Send funds unconditionally here. If the `endowment` is below existential_deposit + // then error will be returned here. + transfer( + gas_meter, + TransferCause::Instantiate, + &self.self_account, + &dest, + endowment, + &mut nested, + )?; - if endowment > T::Balance::zero() { - transfer( + let executable = self.loader.load_init(&code_hash)?; + self.vm + .execute( + &executable, + &mut CallContext { + ctx: &mut nested, + caller: self.self_account.clone(), + }, + input_data, + EmptyOutputBuf::new(), gas_meter, - true, - &self.self_account, - &dest, - endowment, - &mut nested, - )?; - } + ) + .into_result()?; - let mut contract_code = Vec::new(); - vm::execute( - init_code, - data, - &mut contract_code, - &mut CallContext { - ctx: &mut nested, - caller: caller, - }, - &self.config.schedule, - gas_meter, - ).map_err(|_| "vm execute returned error while create")?; + // Deposit an instantiation event. + nested.events.push(RawEvent::Instantiated(self.self_account.clone(), dest.clone())); - nested.overlay.set_code(&dest, contract_code); (nested.overlay.into_change_set(), nested.events) }; self.overlay.commit(change_set); self.events.extend(events); - Ok(CreateReceipt { - address: dest, - }) + Ok(InstantiateReceipt { address: dest }) } } +#[cfg_attr(test, derive(Debug, PartialEq, Eq))] +#[derive(Copy, Clone)] +pub enum TransferFeeKind { + ContractInstantiate, + AccountCreate, + Transfer, +} + +#[cfg_attr(test, derive(Debug, PartialEq, Eq))] +#[derive(Copy, Clone)] +pub struct TransferFeeToken { + kind: TransferFeeKind, + gas_price: Balance, +} + +impl Token for TransferFeeToken { + type Metadata = Config; + + #[inline] + fn calculate_amount(&self, metadata: &Config) -> T::Gas { + let balance_fee = match self.kind { + TransferFeeKind::ContractInstantiate => metadata.contract_account_instantiate_fee, + TransferFeeKind::AccountCreate => metadata.account_create_fee, + TransferFeeKind::Transfer => metadata.transfer_fee, + }; + + let amount_in_gas: T::Balance = balance_fee / self.gas_price; + let amount_in_gas: T::Gas = >::sa(amount_in_gas); + + amount_in_gas + } +} + +/// Describes possible transfer causes. +enum TransferCause { + Call, + Instantiate, +} + /// Transfer some funds from `transactor` to `dest`. /// /// All balance changes are performed in the `overlay`. /// /// This function also handles charging the fee. The fee depends -/// on whether the transfer happening because of contract creation -/// (transfering endowment), specified by `contract_create` flag, -/// or because of a transfer via `call`. +/// on whether the transfer happening because of contract instantiation +/// (transfering endowment) or because of a transfer via `call`. This +/// is specified using the `cause` parameter. /// /// NOTE: that the fee is denominated in `T::Balance` units, but /// charged in `T::Gas` from the provided `gas_meter`. This means @@ -193,38 +436,47 @@ impl<'a, T: Trait> ExecutionContext<'a, T> { /// NOTE: that we allow for draining all funds of the contract so it /// can go below existential deposit, essentially giving a contract /// the chance to give up it's life. -fn transfer<'a, T: Trait>( +fn transfer<'a, T: Trait, V: Vm, L: Loader>( gas_meter: &mut GasMeter, - contract_create: bool, + cause: TransferCause, transactor: &T::AccountId, dest: &T::AccountId, value: T::Balance, - ctx: &mut ExecutionContext<'a, T>, + ctx: &mut ExecutionContext<'a, T, V, L>, ) -> Result<(), &'static str> { + use self::TransferCause::*; + use self::TransferFeeKind::*; + let to_balance = ctx.overlay.get_balance(dest); - // This flag is totally distinct from `contract_create`, which shows if this function - // is called from `CREATE` procedure. - // // `would_create` indicates whether the account will be created if this transfer gets executed. - // For example, we can create a contract at the address which already has some funds. In this - // case `contract_create` will be `true` but `would_create` will be `false`. Another example would - // be when this function is called from `CALL`, but `dest` doesn't exist yet. In this case - // `contract_create` will be `false` but `would_create` will be `true`. + // This flag is orthogonal to `cause. + // For example, we can instantiate a contract at the address which already has some funds. In this + // `would_create` will be `false`. Another example would be when this function is called from `call`, + // and account with the address `dest` doesn't exist yet `would_create` will be `true`. let would_create = to_balance.is_zero(); - let fee: T::Balance = match (contract_create, would_create) { - // If this function is called from `CREATE` routine, then we always - // charge contract account creation fee. - (true, _) => ctx.config.contract_account_create_fee, + let token = { + let kind: TransferFeeKind = match cause { + // If this function is called from `Instantiate` routine, then we always + // charge contract account creation fee. + Instantiate => ContractInstantiate, - // Otherwise the fee depends on whether we create a new account or transfer - // to an existing one. - (false, true) => ctx.config.account_create_fee, - (false, false) => ctx.config.transfer_fee, + // Otherwise the fee depends on whether we create a new account or transfer + // to an existing one. + Call => if would_create { + TransferFeeKind::AccountCreate + } else { + TransferFeeKind::Transfer + }, + }; + TransferFeeToken { + kind, + gas_price: gas_meter.gas_price(), + } }; - if gas_meter.charge_by_balance(fee).is_out_of_gas() { + if gas_meter.charge(ctx.config, token).is_out_of_gas() { return Err("not enough gas to pay transfer fee"); } @@ -247,18 +499,24 @@ fn transfer<'a, T: Trait>( if transactor != dest { ctx.overlay.set_balance(transactor, new_from_balance); ctx.overlay.set_balance(dest, new_to_balance); - ctx.events.push(RawEvent::Transfer(transactor.clone(), dest.clone(), value)); + ctx.events + .push(RawEvent::Transfer(transactor.clone(), dest.clone(), value)); } Ok(()) } -struct CallContext<'a, 'b: 'a, T: Trait + 'b> { - ctx: &'a mut ExecutionContext<'b, T>, +struct CallContext<'a, 'b: 'a, T: Trait + 'b, V: Vm + 'b, L: Loader> { + ctx: &'a mut ExecutionContext<'b, T, V, L>, caller: T::AccountId, } -impl<'a, 'b: 'a, T: Trait + 'b> vm::Ext for CallContext<'a, 'b, T> { +impl<'a, 'b: 'a, T, E, V, L> Ext for CallContext<'a, 'b, T, V, L> +where + T: Trait + 'b, + V: Vm, + L: Loader, +{ type T = T; fn get_storage(&self, key: &[u8]) -> Option> { @@ -271,17 +529,14 @@ impl<'a, 'b: 'a, T: Trait + 'b> vm::Ext for CallContext<'a, 'b, T> { .set_storage(&self.ctx.self_account, key.to_vec(), value) } - fn create( + fn instantiate( &mut self, - code: &[u8], + code_hash: &CodeHash, endowment: T::Balance, gas_meter: &mut GasMeter, - data: &[u8], - ) -> Result, ()> { - let caller = self.ctx.self_account.clone(); - self.ctx - .create(caller, endowment, gas_meter, code, &data) - .map_err(|_| ()) + input_data: &[u8], + ) -> Result>, &'static str> { + self.ctx.instantiate(endowment, gas_meter, code_hash, input_data) } fn call( @@ -289,17 +544,741 @@ impl<'a, 'b: 'a, T: Trait + 'b> vm::Ext for CallContext<'a, 'b, T> { to: &T::AccountId, value: T::Balance, gas_meter: &mut GasMeter, - data: &[u8], - output_data: &mut Vec, - ) -> Result<(), ()> { - let caller = self.ctx.self_account.clone(); + input_data: &[u8], + empty_output_buf: EmptyOutputBuf, + ) -> Result { self.ctx - .call(caller, to.clone(), value, gas_meter, data, output_data) - .map_err(|_| ()) - .map(|_| ()) + .call(to.clone(), value, gas_meter, input_data, empty_output_buf) + } + + fn address(&self) -> &T::AccountId { + &self.ctx.self_account } fn caller(&self) -> &T::AccountId { &self.caller } } + +/// 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. +/// +/// Because it's the executive layer: +/// +/// - no gas meter setup and teardown logic. All balances are *AFTER* gas purchase. +/// - executive layer doesn't alter any storage! +#[cfg(test)] +mod tests { + use super::{ + ExecFeeToken, ExecutionContext, Ext, Loader, EmptyOutputBuf, TransferFeeKind, TransferFeeToken, + Vm, VmExecResult, InstantiateReceipt, RawEvent, + }; + use account_db::AccountDb; + use gas::GasMeter; + use runtime_io::with_externalities; + use std::cell::RefCell; + use std::collections::HashMap; + use std::marker::PhantomData; + use std::rc::Rc; + use tests::{ExtBuilder, Test}; + use {CodeHash, Config}; + + const ALICE: u64 = 1; + const BOB: u64 = 2; + const CHARLIE: u64 = 3; + + struct MockCtx<'a> { + ext: &'a mut dyn Ext, + input_data: &'a [u8], + empty_output_buf: Option, + gas_meter: &'a mut GasMeter, + } + + #[derive(Clone)] + struct MockExecutable<'a>(Rc VmExecResult + 'a>); + + impl<'a> MockExecutable<'a> { + fn new(f: impl Fn(MockCtx) -> VmExecResult + 'a) -> Self { + MockExecutable(Rc::new(f)) + } + } + + struct MockLoader<'a> { + map: HashMap, MockExecutable<'a>>, + counter: u64, + } + + impl<'a> MockLoader<'a> { + fn empty() -> Self { + MockLoader { + map: HashMap::new(), + counter: 0, + } + } + + fn insert(&mut self, f: impl Fn(MockCtx) -> VmExecResult + 'a) -> CodeHash { + // Generate code hashes as monotonically increasing values. + let code_hash = self.counter.into(); + + self.counter += 1; + self.map.insert(code_hash, MockExecutable::new(f)); + code_hash + } + } + + struct MockVm<'a> { + _marker: PhantomData<&'a ()>, + } + + impl<'a> MockVm<'a> { + fn new() -> Self { + MockVm { _marker: PhantomData } + } + } + + impl<'a> Loader for MockLoader<'a> { + type Executable = MockExecutable<'a>; + + fn load_init(&self, code_hash: &CodeHash) -> Result { + self.map + .get(code_hash) + .cloned() + .ok_or_else(|| "code not found") + } + fn load_main(&self, code_hash: &CodeHash) -> Result { + self.map + .get(code_hash) + .cloned() + .ok_or_else(|| "code not found") + } + } + + impl<'a> Vm for MockVm<'a> { + type Executable = MockExecutable<'a>; + + fn execute>( + &self, + exec: &MockExecutable, + ext: &mut E, + input_data: &[u8], + empty_output_buf: EmptyOutputBuf, + gas_meter: &mut GasMeter, + ) -> VmExecResult { + (exec.0)(MockCtx { + ext, + input_data, + empty_output_buf: Some(empty_output_buf), + gas_meter, + }) + } + } + + #[test] + fn it_works() { + let value = Default::default(); + let mut gas_meter = GasMeter::::with_limit(10000, 1); + let data = vec![]; + + let vm = MockVm::new(); + + let test_data = Rc::new(RefCell::new(vec![0usize])); + + let mut loader = MockLoader::empty(); + let exec_ch = loader.insert(|_ctx| { + test_data.borrow_mut().push(1); + VmExecResult::Ok + }); + + with_externalities(&mut ExtBuilder::default().build(), || { + let cfg = Config::preload(); + let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); + ctx.overlay.set_code(&BOB, Some(exec_ch)); + + assert_matches!( + ctx.call(BOB, value, &mut gas_meter, &data, EmptyOutputBuf::new()), + Ok(_) + ); + }); + + assert_eq!(&*test_data.borrow(), &vec![0, 1]); + } + + #[test] + fn base_fees() { + let origin = ALICE; + let dest = BOB; + + // This test verifies that base fee for call is taken. + with_externalities(&mut ExtBuilder::default().build(), || { + let vm = MockVm::new(); + let loader = MockLoader::empty(); + let cfg = Config::preload(); + let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); + ctx.overlay.set_balance(&origin, 100); + ctx.overlay.set_balance(&dest, 0); + + let mut gas_meter = GasMeter::::with_limit(1000, 1); + + let result = ctx.call(dest, 0, &mut gas_meter, &[], EmptyOutputBuf::new()); + assert_matches!(result, Ok(_)); + + let mut toks = gas_meter.tokens().iter(); + match_tokens!(toks, ExecFeeToken::Call,); + }); + + // This test verifies that base fee for instantiation is taken. + with_externalities(&mut ExtBuilder::default().build(), || { + let mut loader = MockLoader::empty(); + let code = loader.insert(|_| VmExecResult::Ok); + + let vm = MockVm::new(); + let cfg = Config::preload(); + let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); + + ctx.overlay.set_balance(&origin, 100); + + let mut gas_meter = GasMeter::::with_limit(1000, 1); + + let result = ctx.instantiate(0, &mut gas_meter, &code, &[]); + assert_matches!(result, Ok(_)); + + let mut toks = gas_meter.tokens().iter(); + match_tokens!(toks, ExecFeeToken::Instantiate,); + }); + } + + #[test] + fn transfer_works() { + // This test verifies that a contract is able to transfer + // some funds to another account. + let origin = ALICE; + let dest = BOB; + + let vm = MockVm::new(); + let loader = MockLoader::empty(); + + with_externalities(&mut ExtBuilder::default().build(), || { + let cfg = Config::preload(); + let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); + ctx.overlay.set_balance(&origin, 100); + ctx.overlay.set_balance(&dest, 0); + + let result = ctx.call( + dest, + 55, + &mut GasMeter::::with_limit(1000, 1), + &[], + EmptyOutputBuf::new(), + ); + assert_matches!(result, Ok(_)); + assert_eq!(ctx.overlay.get_balance(&origin), 45); + assert_eq!(ctx.overlay.get_balance(&dest), 55); + }); + } + + #[test] + fn transfer_fees() { + let origin = ALICE; + let dest = BOB; + + // This test sends 50 units of currency to a non-existent account. + // This should create lead to creation of a new account thus + // a fee should be charged. + with_externalities( + &mut ExtBuilder::default().existential_deposit(15).build(), + || { + let vm = MockVm::new(); + let loader = MockLoader::empty(); + let cfg = Config::preload(); + let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); + ctx.overlay.set_balance(&origin, 100); + ctx.overlay.set_balance(&dest, 0); + + let mut gas_meter = GasMeter::::with_limit(1000, 1); + + let result = ctx.call(dest, 50, &mut gas_meter, &[], EmptyOutputBuf::new()); + assert_matches!(result, Ok(_)); + + let mut toks = gas_meter.tokens().iter(); + match_tokens!( + toks, + ExecFeeToken::Call, + TransferFeeToken { + kind: TransferFeeKind::AccountCreate, + gas_price: 1u64 + }, + ); + }, + ); + + // This one is similar to the previous one but transfer to an existing account. + // In this test we expect that a regular transfer fee is charged. + with_externalities( + &mut ExtBuilder::default().existential_deposit(15).build(), + || { + let vm = MockVm::new(); + let loader = MockLoader::empty(); + let cfg = Config::preload(); + let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); + ctx.overlay.set_balance(&origin, 100); + ctx.overlay.set_balance(&dest, 15); + + let mut gas_meter = GasMeter::::with_limit(1000, 1); + + let result = ctx.call(dest, 50, &mut gas_meter, &[], EmptyOutputBuf::new()); + assert_matches!(result, Ok(_)); + + let mut toks = gas_meter.tokens().iter(); + match_tokens!( + toks, + ExecFeeToken::Call, + TransferFeeToken { + kind: TransferFeeKind::Transfer, + gas_price: 1u64 + }, + ); + }, + ); + + // This test sends 50 units of currency as an endownment to a newly + // created contract. + with_externalities( + &mut ExtBuilder::default().existential_deposit(15).build(), + || { + let mut loader = MockLoader::empty(); + let code = loader.insert(|_| VmExecResult::Ok); + + let vm = MockVm::new(); + let cfg = Config::preload(); + let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); + + ctx.overlay.set_balance(&origin, 100); + ctx.overlay.set_balance(&dest, 15); + + let mut gas_meter = GasMeter::::with_limit(1000, 1); + + let result = ctx.instantiate(50, &mut gas_meter, &code, &[]); + assert_matches!(result, Ok(_)); + + let mut toks = gas_meter.tokens().iter(); + match_tokens!( + toks, + ExecFeeToken::Instantiate, + TransferFeeToken { + kind: TransferFeeKind::ContractInstantiate, + gas_price: 1u64 + }, + ); + }, + ); + } + + #[test] + fn balance_too_low() { + // This test verifies that a contract can't send value if it's + // balance is too low. + let origin = ALICE; + let dest = BOB; + + let vm = MockVm::new(); + let loader = MockLoader::empty(); + + with_externalities(&mut ExtBuilder::default().build(), || { + let cfg = Config::preload(); + let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); + ctx.overlay.set_balance(&origin, 0); + + let result = ctx.call( + dest, + 100, + &mut GasMeter::::with_limit(1000, 1), + &[], + EmptyOutputBuf::new(), + ); + + assert_matches!(result, Err("balance too low to send value")); + assert_eq!(ctx.overlay.get_balance(&origin), 0); + assert_eq!(ctx.overlay.get_balance(&dest), 0); + }); + } + + #[test] + fn output_is_returned() { + // Verifies that if a contract returns data, this data + // is returned from the execution context. + let origin = ALICE; + let dest = BOB; + + let vm = MockVm::new(); + let mut loader = MockLoader::empty(); + let return_ch = loader.insert(|mut ctx| { + #[derive(Debug)] + enum Void {} + let empty_output_buf = ctx.empty_output_buf.take().unwrap(); + let output_buf = + empty_output_buf.fill::(4, |data| { + data.copy_from_slice(&[1, 2, 3, 4]); + Ok(()) + }) + .expect("Ok is always returned"); + VmExecResult::Returned(output_buf) + }); + + with_externalities(&mut ExtBuilder::default().build(), || { + let cfg = Config::preload(); + let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); + ctx.overlay.set_code(&BOB, Some(return_ch)); + + let result = ctx.call( + dest, + 0, + &mut GasMeter::::with_limit(1000, 1), + &[], + EmptyOutputBuf::new(), + ); + + let output_data = result.unwrap().output_data; + assert_eq!(&output_data, &[1, 2, 3, 4]); + }); + } + + #[test] + fn input_data() { + let vm = MockVm::new(); + let mut loader = MockLoader::empty(); + let input_data_ch = loader.insert(|ctx| { + assert_eq!(ctx.input_data, &[1, 2, 3, 4]); + VmExecResult::Ok + }); + + // This one tests passing the input data into a contract via call. + with_externalities(&mut ExtBuilder::default().build(), || { + let cfg = Config::preload(); + let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); + ctx.overlay.set_code(&BOB, Some(input_data_ch)); + + let result = ctx.call( + BOB, + 0, + &mut GasMeter::::with_limit(10000, 1), + &[1, 2, 3, 4], + EmptyOutputBuf::new(), + ); + assert_matches!(result, Ok(_)); + }); + + // This one tests passing the input data into a contract via call. + with_externalities(&mut ExtBuilder::default().build(), || { + let cfg = Config::preload(); + let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); + + let result = ctx.instantiate( + 0, + &mut GasMeter::::with_limit(10000, 1), + &input_data_ch, + &[1, 2, 3, 4], + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn max_depth() { + // This test verifies that when we reach the maximal depth creation of an + // yet another context fails. + let value = Default::default(); + let reached_bottom = RefCell::new(false); + + let vm = MockVm::new(); + let mut loader = MockLoader::empty(); + let recurse_ch = loader.insert(|ctx| { + // Try to call into yourself. + let r = ctx + .ext + .call(&BOB, 0, ctx.gas_meter, &[], EmptyOutputBuf::new()); + + let mut reached_bottom = reached_bottom.borrow_mut(); + if !*reached_bottom { + // We are first time here, it means we just reached bottom. + // Verify that we've got proper error and set `reached_bottom`. + assert_matches!(r, Err("reached maximum depth, cannot make a call")); + *reached_bottom = true; + } else { + // We just unwinding stack here. + assert_matches!(r, Ok(_)); + } + + VmExecResult::Ok + }); + + with_externalities(&mut ExtBuilder::default().build(), || { + let cfg = Config::preload(); + let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); + ctx.overlay.set_code(&BOB, Some(recurse_ch)); + + let result = ctx.call( + BOB, + value, + &mut GasMeter::::with_limit(100000, 1), + &[], + EmptyOutputBuf::new(), + ); + + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn caller_returns_proper_values() { + let origin = ALICE; + let dest = BOB; + + let vm = MockVm::new(); + + let witnessed_caller_bob = RefCell::new(None::); + let witnessed_caller_charlie = RefCell::new(None::); + + let mut loader = MockLoader::empty(); + let bob_ch = loader.insert(|ctx| { + // Record the caller for bob. + *witnessed_caller_bob.borrow_mut() = Some(*ctx.ext.caller()); + + // Call into CHARLIE contract. + assert_matches!( + ctx.ext + .call(&CHARLIE, 0, ctx.gas_meter, &[], EmptyOutputBuf::new()), + Ok(_) + ); + VmExecResult::Ok + }); + let charlie_ch = loader.insert(|ctx| { + // Record the caller for charlie. + *witnessed_caller_charlie.borrow_mut() = Some(*ctx.ext.caller()); + VmExecResult::Ok + }); + + with_externalities(&mut ExtBuilder::default().build(), || { + let cfg = Config::preload(); + + let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); + ctx.overlay.set_code(&dest, Some(bob_ch)); + ctx.overlay.set_code(&CHARLIE, Some(charlie_ch)); + + let result = ctx.call( + dest, + 0, + &mut GasMeter::::with_limit(10000, 1), + &[], + EmptyOutputBuf::new(), + ); + + assert_matches!(result, Ok(_)); + }); + + assert_eq!(&*witnessed_caller_bob.borrow(), &Some(origin)); + assert_eq!(&*witnessed_caller_charlie.borrow(), &Some(dest)); + } + + #[test] + fn address_returns_proper_values() { + let vm = MockVm::new(); + + let mut loader = MockLoader::empty(); + let bob_ch = loader.insert(|ctx| { + // Verify that address matches BOB. + assert_eq!(*ctx.ext.address(), BOB); + + // Call into charlie contract. + assert_matches!( + ctx.ext + .call(&CHARLIE, 0, ctx.gas_meter, &[], EmptyOutputBuf::new()), + Ok(_) + ); + VmExecResult::Ok + }); + let charlie_ch = loader.insert(|ctx| { + assert_eq!(*ctx.ext.address(), CHARLIE); + VmExecResult::Ok + }); + + with_externalities(&mut ExtBuilder::default().build(), || { + let cfg = Config::preload(); + let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); + ctx.overlay.set_code(&BOB, Some(bob_ch)); + ctx.overlay.set_code(&CHARLIE, Some(charlie_ch)); + + let result = ctx.call( + BOB, + 0, + &mut GasMeter::::with_limit(10000, 1), + &[], + EmptyOutputBuf::new(), + ); + + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn refuse_instantiate_with_value_below_existential_deposit() { + let vm = MockVm::new(); + + let mut loader = MockLoader::empty(); + let dummy_ch = loader.insert(|_| VmExecResult::Ok); + + with_externalities( + &mut ExtBuilder::default().existential_deposit(15).build(), + || { + let cfg = Config::preload(); + let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); + + assert_matches!( + ctx.instantiate( + 0, // <- zero endowment + &mut GasMeter::::with_limit(10000, 1), + &dummy_ch, + &[], + ), + Err(_) + ); + } + ); + } + + #[test] + fn instantiation() { + let vm = MockVm::new(); + + let mut loader = MockLoader::empty(); + let dummy_ch = loader.insert(|_| VmExecResult::Ok); + + with_externalities( + &mut ExtBuilder::default().existential_deposit(15).build(), + || { + let cfg = Config::preload(); + let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); + ctx.overlay.set_balance(&ALICE, 1000); + + let created_contract_address = assert_matches!( + ctx.instantiate( + 100, + &mut GasMeter::::with_limit(10000, 1), + &dummy_ch, + &[], + ), + Ok(InstantiateReceipt { address }) => address + ); + + // Check that the newly created account has the expected code hash and + // there are instantiation event. + assert_eq!(ctx.overlay.get_code(&created_contract_address).unwrap(), dummy_ch); + assert_eq!(&ctx.events, &[ + RawEvent::Transfer(ALICE, created_contract_address, 100), + RawEvent::Instantiated(ALICE, created_contract_address), + ]); + } + ); + } + + #[test] + fn instantiation_from_contract() { + let vm = MockVm::new(); + + let mut loader = MockLoader::empty(); + let dummy_ch = loader.insert(|_| VmExecResult::Ok); + let created_contract_address = Rc::new(RefCell::new(None::)); + let creator_ch = loader.insert({ + let dummy_ch = dummy_ch.clone(); + let created_contract_address = Rc::clone(&created_contract_address); + move |ctx| { + // Instantiate a contract and save it's address in `created_contract_address`. + *created_contract_address.borrow_mut() = + ctx.ext.instantiate( + &dummy_ch, + 15u64, + ctx.gas_meter, + &[] + ) + .unwrap() + .address.into(); + + VmExecResult::Ok + } + }); + + with_externalities( + &mut ExtBuilder::default().existential_deposit(15).build(), + || { + let cfg = Config::preload(); + let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); + ctx.overlay.set_balance(&ALICE, 1000); + ctx.overlay.set_code(&BOB, Some(creator_ch)); + + assert_matches!( + ctx.call(BOB, 20, &mut GasMeter::::with_limit(1000, 1), &[], EmptyOutputBuf::new()), + Ok(_) + ); + + let created_contract_address = created_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!(ctx.overlay.get_code(&created_contract_address).unwrap(), dummy_ch); + assert_eq!(&ctx.events, &[ + RawEvent::Transfer(ALICE, BOB, 20), + RawEvent::Transfer(BOB, created_contract_address, 15), + RawEvent::Instantiated(BOB, created_contract_address), + ]); + } + ); + } + + #[test] + fn instantiation_fails() { + let vm = MockVm::new(); + + let mut loader = MockLoader::empty(); + let dummy_ch = loader.insert(|_| VmExecResult::Trap("It's a trap!")); + let creator_ch = loader.insert({ + let dummy_ch = dummy_ch.clone(); + move |ctx| { + // Instantiate a contract and save it's address in `created_contract_address`. + assert_matches!( + ctx.ext.instantiate( + &dummy_ch, + 15u64, + ctx.gas_meter, + &[] + ), + Err("It's a trap!") + ); + + VmExecResult::Ok + } + }); + + with_externalities( + &mut ExtBuilder::default().existential_deposit(15).build(), + || { + let cfg = Config::preload(); + let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader); + ctx.overlay.set_balance(&ALICE, 1000); + ctx.overlay.set_code(&BOB, Some(creator_ch)); + + assert_matches!( + ctx.call(BOB, 20, &mut GasMeter::::with_limit(1000, 1), &[], EmptyOutputBuf::new()), + Ok(_) + ); + + // The contract wasn't created so we don't expect to see an instantiation + // event here. + assert_eq!(&ctx.events, &[ + RawEvent::Transfer(ALICE, BOB, 20), + ]); + } + ); + } +} diff --git a/srml/contract/src/gas.rs b/srml/contract/src/gas.rs index af94ae05c10fe..afec6dfcb7290 100644 --- a/srml/contract/src/gas.rs +++ b/srml/contract/src/gas.rs @@ -14,10 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -use {Trait, Module, GasSpent}; +use balances; use runtime_primitives::traits::{As, CheckedMul, CheckedSub, Zero}; use runtime_support::StorageValue; -use balances; +use {GasSpent, Module, Trait}; + +#[cfg(test)] +use std::{any::Any, fmt::Debug}; #[must_use] #[derive(Debug, PartialEq, Eq)] @@ -35,11 +38,54 @@ impl GasMeterResult { } } +#[cfg(not(test))] +pub trait TestAuxiliaries {} +#[cfg(not(test))] +impl TestAuxiliaries for T {} + +#[cfg(test)] +pub trait TestAuxiliaries: Any + Debug + PartialEq + Eq {} +#[cfg(test)] +impl TestAuxiliaries for T {} + +/// This trait represents a token that can be used for charging `GasMeter`. +/// There is no other way of charging it. +/// +/// Implementing type is expected to be super lightweight hence `Copy` (`Clone` is added +/// for consistency). If inlined there should be no observable difference compared +/// to a hand-written code. +pub trait Token: Copy + Clone + TestAuxiliaries { + /// Metadata type, which the token can require for calculating the amount + /// of gas to charge. Can be a some configuration type or + /// just the `()`. + type Metadata; + + /// Calculate amount of gas that should be taken by this token. + /// + /// This function should be really lightweight and must not fail. It is not + /// expected that implementors will query the storage or do any kinds of heavy operations. + /// + /// That said, implementors of this function still can run into overflows + /// while calculating the amount. In this case it is ok to use saturating operations + /// since on overflow they will return `max_value` which should consume all gas. + fn calculate_amount(&self, metadata: &Self::Metadata) -> T::Gas; +} + +/// A wrapper around a type-erased trait object of what used to be a `Token`. +#[cfg(test)] +pub struct ErasedToken { + pub description: String, + pub token: Box, +} + pub struct GasMeter { limit: T::Gas, /// Amount of gas left from initial gas limit. Can reach zero. gas_left: T::Gas, gas_price: T::Balance, + + #[cfg(test)] + tokens: Vec, } impl GasMeter { #[cfg(test)] @@ -48,17 +94,37 @@ impl GasMeter { limit: gas_limit, gas_left: gas_limit, gas_price, + #[cfg(test)] + tokens: Vec::new(), } } /// Account for used gas. /// + /// Amount is calculated by the given `token`. + /// /// Returns `OutOfGas` if there is not enough gas or addition of the specified /// amount of gas has lead to overflow. On success returns `Proceed`. /// - /// NOTE that `amount` is always consumed, i.e. if there is not enough gas + /// NOTE that amount is always consumed, i.e. if there is not enough gas /// then the counter will be set to zero. - pub fn charge(&mut self, amount: T::Gas) -> GasMeterResult { + #[inline] + pub fn charge>( + &mut self, + metadata: &Tok::Metadata, + token: Tok, + ) -> GasMeterResult { + #[cfg(test)] + { + // Unconditionally add the token to the storage. + let erased_tok = ErasedToken { + description: format!("{:?}", token), + token: Box::new(token), + }; + self.tokens.push(erased_tok); + } + + let amount = token.calculate_amount(metadata); let new_value = match self.gas_left.checked_sub(&amount) { None => None, Some(val) if val.is_zero() => None, @@ -74,18 +140,6 @@ impl GasMeter { } } - /// Account for used gas expressed in balance units. - /// - /// Same as [`charge`], but amount to be charged is converted from units of balance to - /// units of gas. - /// - /// [`charge`]: #method.charge - pub fn charge_by_balance(&mut self, amount: T::Balance) -> GasMeterResult { - let amount_in_gas: T::Balance = amount / self.gas_price; - let amount_in_gas: T::Gas = >::sa(amount_in_gas); - self.charge(amount_in_gas) - } - /// Allocate some amount of gas and perform some work with /// a newly created nested gas meter. /// @@ -108,6 +162,8 @@ impl GasMeter { limit: amount, gas_left: amount, gas_price: self.gas_price, + #[cfg(test)] + tokens: Vec::new(), }; let r = f(Some(&mut nested)); @@ -118,6 +174,10 @@ impl GasMeter { } } + pub fn gas_price(&self) -> T::Balance { + self.gas_price + } + /// Returns how much gas left from the initial budget. pub fn gas_left(&self) -> T::Gas { self.gas_left @@ -127,6 +187,11 @@ impl GasMeter { fn spent(&self) -> T::Gas { self.limit - self.gas_left } + + #[cfg(test)] + pub fn tokens(&self) -> &[ErasedToken] { + &self.tokens + } } /// Buy the given amount of gas. @@ -162,6 +227,8 @@ pub fn buy_gas( limit: gas_limit, gas_left: gas_limit, gas_price, + #[cfg(test)] + tokens: Vec::new(), }) } @@ -179,3 +246,101 @@ pub fn refund_unused_gas(transactor: &T::AccountId, gas_meter: GasMete >::set_free_balance(transactor, b + refund); >::increase_total_stake_by(refund); } + +/// A simple utility macro that helps to match against a +/// list of tokens. +#[macro_export] +macro_rules! match_tokens { + ($tokens_iter:ident,) => { + }; + ($tokens_iter:ident, $x:expr, $($rest:tt)*) => { + { + let next = ($tokens_iter).next().unwrap(); + let pattern = $x; + + // Note that we don't specify the type name directly in this macro, + // we only have some expression $x of some type. At the same time, we + // have an iterator of Box and to downcast we need to specify + // the type which we want downcast to. + // + // So what we do is we assign `_pattern_typed_next_ref` to the a variable which has + // the required type. + // + // Then we make `_pattern_typed_next_ref = token.downcast_ref()`. This makes + // rustc infer the type `T` (in `downcast_ref`) to be the same as in $x. + + let mut _pattern_typed_next_ref = &pattern; + _pattern_typed_next_ref = match next.token.downcast_ref() { + Some(p) => { + assert_eq!(p, &pattern); + p + } + None => { + panic!("expected type {} got {}", stringify!($x), next.description); + } + }; + } + + match_tokens!($tokens_iter, $($rest)*); + }; +} + +#[cfg(test)] +mod tests { + use super::{GasMeter, Token}; + use tests::Test; + + /// A trivial token that charges 1 unit of gas. + #[derive(Copy, Clone, PartialEq, Eq, Debug)] + struct UnitToken; + impl Token for UnitToken { + type Metadata = (); + fn calculate_amount(&self, _metadata: &()) -> u64 { 1 } + } + + struct DoubleTokenMetadata { + multiplier: u64, + } + /// A simple token that charges for the given amount multipled to + /// a multiplier taken from a given metadata. + #[derive(Copy, Clone, PartialEq, Eq, Debug)] + struct DoubleToken(u64); + + impl Token for DoubleToken { + type Metadata = DoubleTokenMetadata; + fn calculate_amount(&self, metadata: &DoubleTokenMetadata) -> u64 { + // Probably you want to use saturating mul in producation code. + self.0 * metadata.multiplier + } + } + + #[test] + fn it_works() { + let gas_meter = GasMeter::::with_limit(50000, 10); + assert_eq!(gas_meter.gas_left(), 50000); + } + + #[test] + fn simple() { + let mut gas_meter = GasMeter::::with_limit(50000, 10); + + let result = gas_meter.charge(&DoubleTokenMetadata { multiplier: 3 }, DoubleToken(10)); + assert!(!result.is_out_of_gas()); + + assert_eq!(gas_meter.gas_left(), 49_970); + assert_eq!(gas_meter.spent(), 30); + assert_eq!(gas_meter.gas_price(), 10); + } + + #[test] + fn tracing() { + let mut gas_meter = GasMeter::::with_limit(50000, 10); + assert!(!gas_meter.charge(&(), UnitToken).is_out_of_gas()); + assert!(!gas_meter + .charge(&DoubleTokenMetadata { multiplier: 3 }, DoubleToken(10)) + .is_out_of_gas()); + + let mut tokens = gas_meter.tokens()[0..2].iter(); + match_tokens!(tokens, UnitToken, DoubleToken(10),); + } +} diff --git a/srml/contract/src/lib.rs b/srml/contract/src/lib.rs index 4bef3b1d8c750..d827b86718526 100644 --- a/srml/contract/src/lib.rs +++ b/srml/contract/src/lib.rs @@ -39,7 +39,7 @@ //! This module requires performing some finalization steps at the end of the block. If not performed //! the module will have incorrect behavior. //! -//! Call [`Module::execute`] at the end of the block. The order in relation to +//! Thus [`Module::on_finalise`] must be called at the end of the block. The order in relation to //! the other module doesn't matter. //! //! ## Account killing @@ -48,7 +48,7 @@ //! exsistential deposit) then it reaps the account. That will lead to deletion of the associated //! code and storage of the account. //! -//! [`Module::execute`]: struct.Module.html#impl-OnFinalise +//! [`Module::on_finalise`]: struct.Module.html#impl-OnFinalise #![cfg_attr(not(feature = "std"), no_std)] @@ -83,39 +83,47 @@ extern crate assert_matches; #[cfg(test)] extern crate wabt; +#[cfg(test)] +#[macro_use] +extern crate hex_literal; + +#[macro_use] +mod gas; + mod account_db; mod exec; -mod vm; -mod gas; +mod wasm; #[cfg(test)] mod tests; use exec::ExecutionContext; -use account_db::{AccountDb, OverlayAccountDb}; +use account_db::AccountDb; use rstd::prelude::*; use rstd::marker::PhantomData; use codec::{Codec, HasCompact}; -use runtime_primitives::traits::{Hash, As, SimpleArithmetic, StaticLookup}; +use runtime_primitives::traits::{Hash, As, SimpleArithmetic,Bounded, StaticLookup}; use runtime_support::dispatch::Result; use runtime_support::{Parameter, StorageMap, StorageValue, StorageDoubleMap}; use system::ensure_signed; use runtime_io::{blake2_256, twox_128}; +pub type CodeHash = ::Hash; + pub trait Trait: balances::Trait { /// Function type to get the contract address given the creator. - type DetermineContractAddress: ContractAddressFor; + type DetermineContractAddress: ContractAddressFor, Self::AccountId>; // As is needed for wasm-utils - type Gas: Parameter + Default + Codec + SimpleArithmetic + Copy + As + As + As; + type Gas: Parameter + Default + Codec + SimpleArithmetic + Bounded + Copy + As + As + As; /// The overarching event type. type Event: From> + Into<::Event>; } -pub trait ContractAddressFor { - fn contract_address_for(code: &[u8], data: &[u8], origin: &AccountId) -> AccountId; +pub trait ContractAddressFor { + fn contract_address_for(code_hash: &CodeHash, data: &[u8], origin: &AccountId) -> AccountId; } /// Simple contract address determintator. @@ -126,12 +134,11 @@ pub trait ContractAddressFor { /// Formula: `blake2_256(blake2_256(code) + blake2_256(data) + origin)` pub struct SimpleAddressDeterminator(PhantomData); -impl ContractAddressFor for SimpleAddressDeterminator +impl ContractAddressFor, T::AccountId> for SimpleAddressDeterminator where T::AccountId: From + AsRef<[u8]> { - fn contract_address_for(code: &[u8], data: &[u8], origin: &T::AccountId) -> T::AccountId { - let code_hash = T::Hashing::hash(code); + fn contract_address_for(code_hash: &CodeHash, data: &[u8], origin: &T::AccountId) -> T::AccountId { let data_hash = T::Hashing::hash(data); let mut buf = Vec::new(); @@ -147,8 +154,43 @@ decl_module! { /// Contracts module. pub struct Module for enum Call where origin: T::Origin { fn deposit_event() = default; - // TODO: Change AccountId to staking::Address - /// Make a call to a specified account, optionally transferring some balance. + + /// Updates the schedule for metering contracts. + /// + /// The schedule must have a greater version than the stored schedule. + fn update_schedule(schedule: Schedule) -> Result { + if >::current_schedule().version >= schedule.version { + return Err("new schedule must have a greater version than current"); + } + + Self::deposit_event(RawEvent::ScheduleUpdated(schedule.version)); + >::put(schedule); + + Ok(()) + } + + /// Stores code in the storage. You can instantiate contracts only with stored code. + fn put_code( + origin, + gas_limit: ::Type, + code: Vec + ) -> Result { + let origin = ensure_signed(origin)?; + let gas_limit = gas_limit.into(); + let schedule = >::current_schedule(); + + let mut gas_meter = gas::buy_gas::(&origin, gas_limit)?; + + let result = wasm::save_code::(code, &mut gas_meter, &schedule); + if let Ok(code_hash) = result { + Self::deposit_event(RawEvent::CodeStored(code_hash)); + } + + gas::refund_unused_gas::(&origin, gas_meter); + + result.map(|_| ()) + } + /// Make a call to a specified account, optionally transferring some balance. fn call( origin, @@ -169,16 +211,11 @@ decl_module! { let mut gas_meter = gas::buy_gas::(&origin, gas_limit)?; let cfg = Config::preload(); - let mut ctx = ExecutionContext { - self_account: origin.clone(), - depth: 0, - overlay: OverlayAccountDb::::new(&account_db::DirectAccountDb), - events: Vec::new(), - config: &cfg, - }; + let vm = ::wasm::WasmVm::new(&cfg.schedule); + let loader = ::wasm::WasmLoader::new(&cfg.schedule); + let mut ctx = ExecutionContext::top_level(origin.clone(), &cfg, &vm, &loader); - let mut output_data = Vec::new(); - let result = ctx.call(origin.clone(), dest, value, &mut gas_meter, &data, &mut output_data); + let result = ctx.call(dest, value, &mut gas_meter, &data, exec::EmptyOutputBuf::new()); if let Ok(_) = result { // Commit all changes that made it thus far into the persistant storage. @@ -210,7 +247,7 @@ decl_module! { origin, endowment: ::Type, gas_limit: ::Type, - ctor_code: Vec, + code_hash: CodeHash, data: Vec ) -> Result { let origin = ensure_signed(origin)?; @@ -224,23 +261,17 @@ decl_module! { let mut gas_meter = gas::buy_gas::(&origin, gas_limit)?; let cfg = Config::preload(); - let mut ctx = ExecutionContext { - self_account: origin.clone(), - depth: 0, - overlay: OverlayAccountDb::::new(&account_db::DirectAccountDb), - events: Vec::new(), - config: &cfg, - }; - let result = ctx.create(origin.clone(), endowment, &mut gas_meter, &ctor_code, &data); - - if let Ok(ref r) = result { + let vm = ::wasm::WasmVm::new(&cfg.schedule); + let loader = ::wasm::WasmLoader::new(&cfg.schedule); + let mut ctx = ExecutionContext::top_level(origin.clone(), &cfg, &vm, &loader); + let result = ctx.instantiate(endowment, &mut gas_meter, &code_hash, &data); + + if let Ok(_) = result { // Commit all changes that made it thus far into the persistant storage. account_db::DirectAccountDb.commit(ctx.overlay.into_change_set()); // Then deposit all events produced. ctx.events.into_iter().for_each(Self::deposit_event); - - Self::deposit_event(RawEvent::Created(origin.clone(), r.address.clone())); } // Refund cost of the unused gas. @@ -262,13 +293,20 @@ decl_event! { pub enum Event where ::Balance, - ::AccountId + ::AccountId, + ::Hash { /// Transfer happened `from` -> `to` with given `value` as part of a `message-call` or `create`. Transfer(AccountId, AccountId, Balance), /// Contract deployed by address at the specified address. - Created(AccountId, AccountId), + Instantiated(AccountId, AccountId), + + /// Code with the specified hash has been stored. + CodeStored(Hash), + + /// Triggered when the current schedule is updated. + ScheduleUpdated(u32), } } @@ -290,8 +328,12 @@ decl_storage! { GasSpent get(gas_spent): T::Gas; /// Current cost schedule for contracts. CurrentSchedule get(current_schedule) config(): Schedule = Schedule::default(); - /// The code associated with an account. - pub CodeOf: map T::AccountId => Vec; // TODO Vec values should be optimised to not do a length prefix. + /// The code associated with a given account. + pub CodeHashOf: map T::AccountId => Option>; + /// A mapping from an original code hash to the original code, untouched by instrumentation. + pub PristineCode: map CodeHash => Option>; + /// A mapping between an original code hash and instrumented wasm code, ready for the execution. + pub CodeStorage: map CodeHash => Option; } } @@ -322,7 +364,7 @@ impl StorageDoubleMap for StorageOf { impl balances::OnFreeBalanceZero for Module { fn on_free_balance_zero(who: &T::AccountId) { - >::remove(who); + >::remove(who); >::remove_prefix(who.clone()); } } @@ -335,11 +377,11 @@ pub struct Config { pub schedule: Schedule, pub existential_deposit: T::Balance, pub max_depth: u32, - pub contract_account_create_fee: T::Balance, + pub contract_account_instantiate_fee: T::Balance, pub account_create_fee: T::Balance, pub transfer_fee: T::Balance, pub call_base_fee: T::Gas, - pub create_base_fee: T::Gas, + pub instantiate_base_fee: T::Gas, } impl Config { @@ -348,19 +390,25 @@ impl Config { schedule: >::current_schedule(), existential_deposit: >::existential_deposit(), max_depth: >::max_depth(), - contract_account_create_fee: >::contract_fee(), + contract_account_instantiate_fee: >::contract_fee(), account_create_fee: >::creation_fee(), transfer_fee: >::transfer_fee(), call_base_fee: >::call_base_fee(), - create_base_fee: >::create_base_fee(), + instantiate_base_fee: >::create_base_fee(), } } } /// Definition of the cost schedule and other parameterizations for wasm vm. #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))] -#[derive(Clone, Encode, Decode)] +#[derive(Clone, Encode, Decode, PartialEq, Eq)] pub struct Schedule { + /// Version of the schedule. + pub version: u32, + + /// Cost of putting a byte of code into the storage. + pub put_code_per_byte_cost: Gas, + /// Gas cost of a growing memory by single page. pub grow_mem_cost: Gas, @@ -371,10 +419,10 @@ pub struct Schedule { pub return_data_per_byte_cost: Gas, /// Gas cost per one byte read from the sandbox memory. - sandbox_data_read_cost: Gas, + pub sandbox_data_read_cost: Gas, /// Gas cost per one byte written to the sandbox memory. - sandbox_data_write_cost: Gas, + pub sandbox_data_write_cost: Gas, /// How tall the stack is allowed to grow? /// @@ -382,7 +430,7 @@ pub struct Schedule { /// how the stack frame cost is calculated. pub max_stack_height: u32, - //// What is the maximal memory pages amount is allowed to have for + /// What is the maximal memory pages amount is allowed to have for /// a contract. pub max_memory_pages: u32, } @@ -390,6 +438,8 @@ pub struct Schedule { impl> Default for Schedule { fn default() -> Schedule { Schedule { + version: 0, + put_code_per_byte_cost: Gas::sa(1), grow_mem_cost: Gas::sa(1), regular_op_cost: Gas::sa(1), return_data_per_byte_cost: Gas::sa(1), diff --git a/srml/contract/src/tests.rs b/srml/contract/src/tests.rs index 399004c6a4103..542f2c258f3b2 100644 --- a/srml/contract/src/tests.rs +++ b/srml/contract/src/tests.rs @@ -14,6 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . +// TODO: #1417 Add more integration tests +// also remove the #![allow(unused)] below. + +#![allow(unused)] + use runtime_io::with_externalities; use runtime_primitives::testing::{Digest, DigestItem, H256, Header}; use runtime_primitives::traits::{BlakeTwo256, IdentityLookup}; @@ -23,8 +28,8 @@ use substrate_primitives::{Blake2Hasher}; use system::{Phase, EventRecord}; use wabt; use { - runtime_io, balances, system, CodeOf, ContractAddressFor, - GenesisConfig, Module, StorageOf, Trait, RawEvent, + balances, runtime_io, system, ContractAddressFor, GenesisConfig, Module, RawEvent, StorageOf, + Trait, }; impl_outer_origin! { @@ -32,6 +37,9 @@ impl_outer_origin! { } mod contract { + // Re-export contents of the root. This basically + // needs to give a name for the current crate. + // This hack is required for `impl_outer_event!`. pub use super::super::*; } impl_outer_event! { @@ -73,13 +81,17 @@ type Contract = Module; type System = system::Module; pub struct DummyContractAddressFor; -impl ContractAddressFor for DummyContractAddressFor { - fn contract_address_for(_code: &[u8], _data: &[u8], origin: &u64) -> u64 { - origin + 1 +impl ContractAddressFor for DummyContractAddressFor { + fn contract_address_for(_code_hash: &H256, _data: &[u8], origin: &u64) -> u64 { + *origin + 1 } } -struct ExtBuilder { +const ALICE: u64 = 1; +const BOB: u64 = 2; +const CHARLIE: u64 = 3; + +pub struct ExtBuilder { existential_deposit: u64, gas_price: u64, block_gas_limit: u64, @@ -98,30 +110,31 @@ impl Default for ExtBuilder { } } impl ExtBuilder { - fn existential_deposit(mut self, existential_deposit: u64) -> Self { + pub fn existential_deposit(mut self, existential_deposit: u64) -> Self { self.existential_deposit = existential_deposit; self } - fn gas_price(mut self, gas_price: u64) -> Self { + pub fn gas_price(mut self, gas_price: u64) -> Self { self.gas_price = gas_price; self } - fn block_gas_limit(mut self, block_gas_limit: u64) -> Self { + pub fn block_gas_limit(mut self, block_gas_limit: u64) -> Self { self.block_gas_limit = block_gas_limit; self } - fn transfer_fee(mut self, transfer_fee: u64) -> Self { + pub fn transfer_fee(mut self, transfer_fee: u64) -> Self { self.transfer_fee = transfer_fee; self } - fn creation_fee(mut self, creation_fee: u64) -> Self { + pub fn creation_fee(mut self, creation_fee: u64) -> Self { self.creation_fee = creation_fee; self } - fn build(self) -> runtime_io::TestExternalities { + pub fn build(self) -> runtime_io::TestExternalities { let mut t = system::GenesisConfig::::default() .build_storage() - .unwrap().0; + .unwrap() + .0; t.extend( balances::GenesisConfig:: { balances: vec![], @@ -130,8 +143,10 @@ impl ExtBuilder { existential_deposit: self.existential_deposit, transfer_fee: self.transfer_fee, creation_fee: self.creation_fee, - }.build_storage() - .unwrap().0, + } + .build_storage() + .unwrap() + .0, ); t.extend( GenesisConfig:: { @@ -142,544 +157,33 @@ impl ExtBuilder { max_depth: 100, block_gas_limit: self.block_gas_limit, current_schedule: Default::default(), - }.build_storage() - .unwrap().0, + } + .build_storage() + .unwrap() + .0, ); runtime_io::TestExternalities::new(t) } } -const CODE_TRANSFER: &str = r#" -(module - ;; ext_call( - ;; callee_ptr: u32, - ;; callee_len: u32, - ;; gas: u64, - ;; value_ptr: u32, - ;; value_len: u32, - ;; input_data_ptr: u32, - ;; input_data_len: u32 - ;; ) -> u32 - (import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result i32))) - (import "env" "memory" (memory 1 1)) - (func (export "call") - (drop - (call $ext_call - (i32.const 4) ;; Pointer to "callee" address. - (i32.const 8) ;; Length of "callee" address. - (i64.const 0) ;; How much gas to devote for the execution. 0 = all. - (i32.const 12) ;; Pointer to the buffer with value to transfer - (i32.const 8) ;; Length of the buffer with value to transfer. - (i32.const 0) ;; Pointer to input data buffer address - (i32.const 0) ;; Length of input data buffer - ) - ) - ) - ;; Destination AccountId to transfer the funds. - ;; Represented by u64 (8 bytes long) in little endian. - (data (i32.const 4) "\09\00\00\00\00\00\00\00") - ;; Amount of value to transfer. - ;; Represented by u64 (8 bytes long) in little endian. - (data (i32.const 12) "\06\00\00\00\00\00\00\00") -) -"#; - #[test] -fn contract_transfer() { - const CONTRACT_SHOULD_TRANSFER_VALUE: u64 = 6; - const CONTRACT_SHOULD_TRANSFER_TO: u64 = 9; - - let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap(); - - with_externalities(&mut ExtBuilder::default().build(), || { - >::insert(1, code_transfer.to_vec()); - - Balances::set_free_balance(&0, 100_000_000); - Balances::increase_total_stake_by(100_000_000); - Balances::set_free_balance(&1, 11); - Balances::increase_total_stake_by(11); - - assert_ok!(Contract::call(Origin::signed(0), 1, 3.into(), 100_000.into(), Vec::new())); - - assert_eq!( - Balances::free_balance(&0), - // 3 - value sent with the transaction - // 2 * 26 - gas used by the contract (26) multiplied by gas price (2) - // 2 * 135 - base gas fee for call (by transaction) - // 2 * 135 - base gas fee for call (by the contract) - 100_000_000 - 3 - (2 * 26) - (2 * 135) - (2 * 135), - ); - assert_eq!( - Balances::free_balance(&1), - 11 + 3 - CONTRACT_SHOULD_TRANSFER_VALUE, - ); - assert_eq!( - Balances::free_balance(&CONTRACT_SHOULD_TRANSFER_TO), - CONTRACT_SHOULD_TRANSFER_VALUE, - ); - - assert_eq!(System::events(), vec![ - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::balances( - balances::RawEvent::NewAccount( - CONTRACT_SHOULD_TRANSFER_TO, - 6 - ) - ), - }, - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::contract(RawEvent::Transfer(0, 1, 3)), - }, - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::contract(RawEvent::Transfer(1, CONTRACT_SHOULD_TRANSFER_TO, 6)), - }, - ]); - }); -} - -#[test] -fn contract_transfer_to_death() { - const CONTRACT_SHOULD_TRANSFER_VALUE: u64 = 6; - - let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap(); - - with_externalities(&mut ExtBuilder::default().existential_deposit(5).build(), || { - >::insert(1, code_transfer.to_vec()); - - Balances::set_free_balance(&0, 100_000_000); - Balances::increase_total_stake_by(100_000_000); - - Balances::set_free_balance(&1, 6); - Balances::increase_total_stake_by(6); - >::insert(1, b"foo".to_vec(), b"1".to_vec()); - - assert_ok!(Contract::call(Origin::signed(0), 1, 0.into(), 100_000.into(), Vec::new())); - - assert_eq!( - Balances::free_balance(&0), - // 2 * 26 - gas used by the contract (26) multiplied by gas price (2) - // 2 * 135 - base gas fee for call (by transaction) - // 2 * 135 - base gas fee for call (by the contract) - 100_000_000 - (2 * 26) - (2 * 135) - (2 * 135), - ); - - assert!(!>::exists(1)); - assert!(!>::exists(1, b"foo".to_vec())); - assert_eq!(Balances::free_balance(&1), 0); - - assert_eq!(Balances::free_balance(&9), CONTRACT_SHOULD_TRANSFER_VALUE); - }); -} - -#[test] -fn contract_transfer_takes_creation_fee() { - const CONTRACT_SHOULD_TRANSFER_VALUE: u64 = 6; - const CONTRACT_SHOULD_TRANSFER_TO: u64 = 9; - - let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap(); - - with_externalities(&mut ExtBuilder::default().creation_fee(105).build(), || { - >::insert(1, code_transfer.to_vec()); - - Balances::set_free_balance(&0, 100_000_000); - Balances::increase_total_stake_by(100_000_000); - Balances::set_free_balance(&1, 11); - Balances::increase_total_stake_by(11); - - assert_ok!(Contract::call(Origin::signed(0), 1, 3.into(), 100_000.into(), Vec::new())); - - assert_eq!( - Balances::free_balance(&0), - // 3 - value sent with the transaction - // 2 * 26 - gas used by the contract (26) multiplied by gas price (2) - // 2 * 135 - base gas fee for call (by transaction) - // 2 * 135 - base gas fee for call (by the contract) - // 104 - (rounded) fee per creation (by the contract) - 100_000_000 - 3 - (2 * 26) - (2 * 135) - (2 * 135) - 104, - ); - assert_eq!( - Balances::free_balance(&1), - 11 + 3 - CONTRACT_SHOULD_TRANSFER_VALUE, - ); - assert_eq!( - Balances::free_balance(&CONTRACT_SHOULD_TRANSFER_TO), - CONTRACT_SHOULD_TRANSFER_VALUE, - ); - }); -} - -#[test] -fn contract_transfer_takes_transfer_fee() { - const CONTRACT_SHOULD_TRANSFER_VALUE: u64 = 6; - const CONTRACT_SHOULD_TRANSFER_TO: u64 = 9; - - let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap(); - - with_externalities(&mut ExtBuilder::default().creation_fee(105).transfer_fee(45).build(), || { - >::insert(1, code_transfer.to_vec()); - - Balances::set_free_balance(&0, 100_000_000); - Balances::increase_total_stake_by(100_000_000); - Balances::set_free_balance(&1, 11); - Balances::increase_total_stake_by(11); - - // Create destination account here so we can check that transfer fee - // is charged (and creation fee is not). - Balances::set_free_balance(&CONTRACT_SHOULD_TRANSFER_TO, 25); - - assert_ok!(Contract::call(Origin::signed(0), 1, 3.into(), 100_000.into(), Vec::new())); - - assert_eq!( - Balances::free_balance(&0), - // 3 - value sent with the transaction - // 2 * 26 - gas used by the contract (26) multiplied by gas price (2) - // 2 * 135 - base gas fee for call (by transaction) - // 44 - (rounded from 45) fee per transfer (by transaction) - // 2 * 135 - base gas fee for call (by the contract) - // 44 - (rounded from 45) fee per transfer (by the contract) - 100_000_000 - 3 - (2 * 26) - (2 * 135) - 44 - (2 * 135) - 44, - ); - assert_eq!( - Balances::free_balance(&1), - 11 + 3 - CONTRACT_SHOULD_TRANSFER_VALUE, - ); - assert_eq!( - Balances::free_balance(&CONTRACT_SHOULD_TRANSFER_TO), - 25 + CONTRACT_SHOULD_TRANSFER_VALUE, - ); - }); -} - -#[test] -fn contract_transfer_oog() { - const CONTRACT_SHOULD_TRANSFER_TO: u64 = 9; - - let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap(); - - with_externalities(&mut ExtBuilder::default().build(), || { - >::insert(1, code_transfer.to_vec()); - - Balances::set_free_balance(&0, 100_000_000); - Balances::increase_total_stake_by(100_000_000); - Balances::set_free_balance(&1, 11); - Balances::increase_total_stake_by(11); - - assert_ok!(Contract::call(Origin::signed(0), 1, 3.into(), (135 + 135 + 7).into(), Vec::new())); - - assert_eq!( - Balances::free_balance(&0), - // 3 - value sent with the transaction - // 2 * 7 - gas used by the contract (7) multiplied by gas price (2) - // 2 * 135 - base gas fee for call (by transaction) - // 2 * 135 - base gas fee for call (by contract) - 100_000_000 - 3 - (2 * 7) - (2 * 135) - (2 * 135), - ); - - // Transaction level transfer should succeed. - assert_eq!(Balances::free_balance(&1), 14); - // But `ext_call` should not. - assert_eq!(Balances::free_balance(&CONTRACT_SHOULD_TRANSFER_TO), 0); - - assert_eq!(System::events(), vec![ - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::contract(RawEvent::Transfer(0, 1, 3)), - }, - ]); - }); -} - -#[test] -fn contract_transfer_max_depth() { - const CONTRACT_SHOULD_TRANSFER_TO: u64 = 9; - - let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap(); - - with_externalities(&mut ExtBuilder::default().build(), || { - >::insert(CONTRACT_SHOULD_TRANSFER_TO, code_transfer.to_vec()); - - Balances::set_free_balance(&0, 100_000_000); - Balances::increase_total_stake_by(100_000_000); - Balances::set_free_balance(&CONTRACT_SHOULD_TRANSFER_TO, 11); - Balances::increase_total_stake_by(11); - - assert_ok!(Contract::call(Origin::signed(0), CONTRACT_SHOULD_TRANSFER_TO, 3.into(), 100_000.into(), Vec::new())); - - assert_eq!( - Balances::free_balance(&0), - // 3 - value sent with the transaction - // 2 * 26 * 100 - gas used by the contract (26) multiplied by gas price (2) - // multiplied by max depth (100). - // 2 * 135 * 100 - base gas fee for call (by transaction) multiplied by max depth (100). - 100_000_000 - 3 - (2 * 26 * 100) - (2 * 135 * 100), - ); - assert_eq!(Balances::free_balance(&CONTRACT_SHOULD_TRANSFER_TO), 14); - }); -} - -/// Convert a byte slice to a string with hex values. -/// -/// Each value is preceeded with a `\` character. -fn escaped_bytestring(bytes: &[u8]) -> String { - use std::fmt::Write; - let mut result = String::new(); - for b in bytes { - write!(result, "\\{:02x}", b).unwrap(); - } - result -} - -/// Create a constructor for the specified code. -/// -/// When constructor is executed, it will call `ext_return` with code that -/// specified in `child_bytecode`. -fn code_ctor(child_bytecode: &[u8]) -> String { - format!( - r#" -(module - ;; ext_return(data_ptr: u32, data_len: u32) -> ! - (import "env" "ext_return" (func $ext_return (param i32 i32))) - (import "env" "memory" (memory 1 1)) - (func (export "call") - (call $ext_return - (i32.const 4) - (i32.const {code_len}) - ) - ;; ext_return is diverging, i.e. doesn't return. - unreachable - ) - (data (i32.const 4) "{escaped_bytecode}") -) -"#, - escaped_bytecode = escaped_bytestring(child_bytecode), - code_len = child_bytecode.len(), - ) -} - -/// Returns code that uses `ext_create` runtime call. -/// -/// Takes bytecode of the contract that needs to be deployed. -fn code_create(constructor: &[u8]) -> String { - format!( - r#" -(module - ;; ext_create( - ;; code_ptr: u32, - ;; code_len: u32, - ;; gas: u64, - ;; value_ptr: u32, - ;; value_len: u32, - ;; input_data_ptr: u32, - ;; input_data_len: u32, - ;; ) -> u32 - (import "env" "ext_create" (func $ext_create (param i32 i32 i64 i32 i32 i32 i32) (result i32))) - (import "env" "memory" (memory 1 1)) - (func (export "call") - (drop - (call $ext_create - (i32.const 12) ;; Pointer to `code` - (i32.const {code_len}) ;; Length of `code` - (i64.const 0) ;; How much gas to devote for the execution. 0 = all. - (i32.const 4) ;; Pointer to the buffer with value to transfer - (i32.const 8) ;; Length of the buffer with value to transfer - (i32.const 0) ;; Pointer to input data buffer address - (i32.const 0) ;; Length of input data buffer - ) - ) - ) - ;; Amount of value to transfer. - ;; Represented by u64 (8 bytes long) in little endian. - (data (i32.const 4) "\03\00\00\00\00\00\00\00") - ;; Embedded wasm code. - (data (i32.const 12) "{escaped_constructor}") -) -"#, - escaped_constructor = escaped_bytestring(constructor), - code_len = constructor.len(), - ) -} - -#[test] -fn contract_create() { - let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap(); - let code_ctor_transfer = wabt::wat2wasm(&code_ctor(&code_transfer)).unwrap(); - let code_create = wabt::wat2wasm(&code_create(&code_ctor_transfer)).unwrap(); - +fn refunds_unused_gas() { with_externalities(&mut ExtBuilder::default().build(), || { Balances::set_free_balance(&0, 100_000_000); Balances::increase_total_stake_by(100_000_000); - Balances::set_free_balance(&1, 0); - Balances::set_free_balance(&9, 30); - Balances::increase_total_stake_by(30); - - >::insert(1, code_create.to_vec()); - - // When invoked, the contract at address `1` must create a contract with 'transfer' code. - assert_ok!(Contract::call(Origin::signed(0), 1, 11.into(), 100_000.into(), Vec::new())); - - let derived_address = ::DetermineContractAddress::contract_address_for( - &code_ctor_transfer, - &[], - &1, - ); - - // 11 - value sent with the transaction - // 2 * 362 - gas spent by the deployer contract (362) multiplied by gas price (2) - // 2 * 135 - base gas fee for call (top level) - // 2 * 175 - base gas fee for create (by contract) - // ((21 / 2) * 2) - price per account creation - let expected_gas_after_create = - 100_000_000 - 11 - (2 * 362) - (2 * 135) - (2 * 175) - ((21 / 2) * 2); - assert_eq!(Balances::free_balance(&0), expected_gas_after_create); - assert_eq!(Balances::free_balance(&1), 8); - assert_eq!(Balances::free_balance(&derived_address), 3); - - assert_eq!(System::events(), vec![ - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::balances( - balances::RawEvent::NewAccount( - derived_address, - 3 - ) - ), - }, - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::contract(RawEvent::Transfer(0, 1, 11)), - }, - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::contract(RawEvent::Transfer(1, 2, 3)), - }, - ]); - - // Initiate transfer to the newly created contract. - assert_ok!(Contract::call(Origin::signed(0), derived_address, 22.into(), 100_000.into(), Vec::new())); - - assert_eq!( - Balances::free_balance(&0), - // 22 - value sent with the transaction - // (2 * 26) - gas used by the contract - // (2 * 135) - base gas fee for call (top level) - // (2 * 135) - base gas fee for call (by transfer contract) - expected_gas_after_create - 22 - (2 * 26) - (2 * 135) - (2 * 135), - ); - assert_eq!(Balances::free_balance(&derived_address), 22 - 3); - assert_eq!(Balances::free_balance(&9), 36); - }); -} - -#[test] -fn top_level_create() { - let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap(); - let code_ctor_transfer = wabt::wat2wasm(&code_ctor(&code_transfer)).unwrap(); - - with_externalities(&mut ExtBuilder::default().gas_price(3).build(), || { - let derived_address = ::DetermineContractAddress::contract_address_for( - &code_ctor_transfer, - &[], - &0, - ); - - Balances::set_free_balance(&0, 100_000_000); - Balances::increase_total_stake_by(100_000_000); - Balances::set_free_balance(&derived_address, 30); - Balances::increase_total_stake_by(30); - assert_ok!(Contract::create( + assert_ok!(Contract::call( Origin::signed(0), - 11.into(), + 1, + 0.into(), 100_000.into(), - code_ctor_transfer.clone(), - Vec::new(), + Vec::new() )); - // 11 - value sent with the transaction - // (3 * 129) - gas spent by the init_code. - // (3 * 175) - base gas fee for create (175) (top level) multipled by gas price (3) - // ((21 / 3) * 3) - price for contract creation - assert_eq!( - Balances::free_balance(&0), - 100_000_000 - 11 - (3 * 129) - (3 * 175) - ((21 / 3) * 3) - ); - assert_eq!(Balances::free_balance(&derived_address), 30 + 11); - - assert_eq!(>::get(&derived_address), code_transfer); - - assert_eq!(System::events(), vec![ - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::contract(RawEvent::Transfer(0, derived_address, 11)), - }, - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: MetaEvent::contract(RawEvent::Created(0, 1)), - }, - ]); - }); -} - -const CODE_NOP: &'static str = r#" -(module - (func (export "call") - nop - ) -) -"#; - -#[test] -fn refunds_unused_gas() { - let code_nop = wabt::wat2wasm(CODE_NOP).unwrap(); - - with_externalities(&mut ExtBuilder::default().build(), || { - >::insert(1, code_nop.to_vec()); - - Balances::set_free_balance(&0, 100_000_000); - Balances::increase_total_stake_by(100_000_000); - - assert_ok!(Contract::call(Origin::signed(0), 1, 0.into(), 100_000.into(), Vec::new())); - - assert_eq!(Balances::free_balance(&0), 100_000_000 - 4 - (2 * 135)); - }); -} - -#[test] -fn call_with_zero_value() { - with_externalities(&mut ExtBuilder::default().build(), || { - >::insert(1, vec![]); - - Balances::set_free_balance(&0, 100_000_000); - Balances::increase_total_stake_by(100_000_000); - - assert_ok!(Contract::call(Origin::signed(0), 1, 0.into(), 100_000.into(), Vec::new())); - assert_eq!(Balances::free_balance(&0), 100_000_000 - (2 * 135)); }); } -#[test] -fn create_with_zero_endowment() { - let code_nop = wabt::wat2wasm(CODE_NOP).unwrap(); - - with_externalities(&mut ExtBuilder::default().build(), || { - Balances::set_free_balance(&0, 100_000_000); - Balances::increase_total_stake_by(100_000_000); - - assert_ok!(Contract::create(Origin::signed(0), 0.into(), 100_000.into(), code_nop, Vec::new())); - - assert_eq!( - Balances::free_balance(&0), - // 4 - for the gas spent by the constructor - // 2 * 175 - base gas fee for create (175) multiplied by gas price (2) (top level) - 100_000_000 - 4 - (2 * 175), - ); - }); -} - #[test] fn account_removal_removes_storage() { with_externalities( @@ -723,232 +227,74 @@ fn account_removal_removes_storage() { ); } -const CODE_UNREACHABLE: &'static str = r#" +const CODE_RETURN_FROM_START_FN: &str = r#" (module - (func (export "call") - nop - unreachable - ) -) -"#; - -#[test] -fn top_level_call_refunds_even_if_fails() { - let code_unreachable = wabt::wat2wasm(CODE_UNREACHABLE).unwrap(); - with_externalities(&mut ExtBuilder::default().gas_price(4).build(), || { - >::insert(1, code_unreachable.to_vec()); - - Balances::set_free_balance(&0, 100_000_000); - Balances::increase_total_stake_by(100_000_000); - - assert_err!( - Contract::call(Origin::signed(0), 1, 0.into(), 100_000.into(), Vec::new()), - "vm execute returned error while call" - ); - - assert_eq!(Balances::free_balance(&0), 100_000_000 - (4 * 3) - (4 * 135)); - - assert_eq!(System::events(), vec![]); - }); -} - -const CODE_LOOP: &'static str = r#" -(module - (func (export "call") - (loop - (br 0) - ) - ) -) -"#; - -#[test] -fn block_gas_limit() { - let code_loop = wabt::wat2wasm(CODE_LOOP).unwrap(); - with_externalities( - &mut ExtBuilder::default().block_gas_limit(100_000).build(), - || { - >::insert(1, code_loop.to_vec()); - - Balances::set_free_balance(&0, 100_000_000); - Balances::increase_total_stake_by(100_000_000); - - // Spend 50_000 units of gas (OOG). - assert_err!( - Contract::call(Origin::signed(0), 1, 0.into(), 50_000.into(), Vec::new()), - "vm execute returned error while call" - ); - - // Ensure we can't spend more gas than available in block gas limit. - assert_err!( - Contract::call(Origin::signed(0), 1, 0.into(), 50_001.into(), Vec::new()), - "block gas limit is reached" - ); - - // However, we can spend another 50_000 - assert_err!( - Contract::call(Origin::signed(0), 1, 0.into(), 50_000.into(), Vec::new()), - "vm execute returned error while call" - ); - }, - ); -} - -const CODE_INPUT_DATA: &'static str = r#" -(module - (import "env" "ext_input_size" (func $ext_input_size (result i32))) - (import "env" "ext_input_copy" (func $ext_input_copy (param i32 i32 i32))) + (import "env" "ext_return" (func $ext_return (param i32 i32))) (import "env" "memory" (memory 1 1)) - (func (export "call") - (block $fail - ;; fail if ext_input_size != 4 - (br_if $fail - (i32.ne - (i32.const 4) - (call $ext_input_size) - ) - ) - - (call $ext_input_copy - (i32.const 0) - (i32.const 0) - (i32.const 4) - ) - - - (br_if $fail - (i32.ne - (i32.load8_u (i32.const 0)) - (i32.const 0) - ) - ) - (br_if $fail - (i32.ne - (i32.load8_u (i32.const 1)) - (i32.const 1) - ) - ) - (br_if $fail - (i32.ne - (i32.load8_u (i32.const 2)) - (i32.const 2) - ) - ) - (br_if $fail - (i32.ne - (i32.load8_u (i32.const 3)) - (i32.const 3) - ) - ) - - (return) + (start $start) + (func $start + (call $ext_return + (i32.const 8) + (i32.const 4) ) - unreachable + (unreachable) ) -) -"#; - -#[test] -fn input_data() { - let code_input_data = wabt::wat2wasm(CODE_INPUT_DATA).unwrap(); - with_externalities( - &mut ExtBuilder::default().build(), - || { - >::insert(1, code_input_data.to_vec()); - - Balances::set_free_balance(&0, 100_000_000); - Balances::increase_total_stake_by(100_000_000); - - assert_ok!(Contract::call(Origin::signed(0), 1, 0.into(), 50_000.into(), vec![0, 1, 2, 3])); - - // all asserts are made within contract code itself. - }, - ); -} - -/// Stores the caller into the storage under the [0x00; 32] key in the contract's storage. -const CODE_CALLER_LOGGER: &'static str = r#" -(module - (import "env" "ext_caller" (func $ext_caller)) - (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) - (import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32))) - (import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32 i32))) - (import "env" "memory" (memory 1 1)) - - ;; Memory layout - ;; [0..32]: the storage key (passed as the key for ext_set_storage) - ;; [32..40]: contents of the scratch buffer (which is expected to be 8 bytes long) (func (export "call") - ;; Fill the scratch buffer with the caller. - (call $ext_caller) - - ;; Copy contents of the scratch buffer into the contract's memory. - (call $ext_scratch_copy - (i32.const 32) ;; Store scratch's buffer contents at this address. - (i32.const 0) ;; Offset from the start of the scratch buffer. - (i32.const 8) ;; Count of bytes to copy. - ) - - (call $ext_set_storage - (i32.const 0) ;; The storage key to save the value at. 32 bytes long. - (i32.const 1) ;; value_not_null=1, i.e. we are not removing the value - (i32.const 32) ;; the pointer to the value to store - (i32.const 8) ;; the length of the value - ) + (unreachable) ) + (func (export "deploy")) + + (data (i32.const 8) "\01\02\03\04") ) "#; +const HASH_RETURN_FROM_START_FN: [u8; 32] = hex!("e6411d12daa2a19e4e9c7d8306c31c7d53a352cb8ed84385c8a1d48fc232e708"); #[test] -fn caller_top_level() { - let code_caller_logger = wabt::wat2wasm(CODE_CALLER_LOGGER).unwrap(); +fn instantiate_and_call() { + let wasm = wabt::wat2wasm(CODE_RETURN_FROM_START_FN).unwrap(); + with_externalities( - &mut ExtBuilder::default().build(), + &mut ExtBuilder::default().existential_deposit(100).build(), || { - >::insert(1, code_caller_logger.to_vec()); - - Balances::set_free_balance(&2, 100_000_000); - Balances::increase_total_stake_by(100_000_000); - - assert_ok!(Contract::call(Origin::signed(2), 1, 0.into(), 50_000.into(), vec![])); - - // Load the zero-th slot of the storage of the caller logger contract. - // We verify here that the caller logger contract has witnessed the call coming from - // the account with address 0x02 (see the origin above) - the origin of the tx. - assert_eq!( - >::get(1, vec![0; 32]), - Some(vec![0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), - ); + Balances::set_free_balance(&ALICE, 1_000_000); + Balances::increase_total_stake_by(1_000_000); + + assert_ok!(Contract::put_code( + Origin::signed(ALICE), + 100_000.into(), + wasm, + )); + + assert_ok!(Contract::create( + Origin::signed(ALICE), + 100.into(), + 100_000.into(), + HASH_RETURN_FROM_START_FN.into(), + vec![], + )); + + assert_eq!(System::events(), vec![ + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::contract(RawEvent::CodeStored(HASH_RETURN_FROM_START_FN.into())), + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::balances( + balances::RawEvent::NewAccount(BOB, 100) + ) + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::contract(RawEvent::Transfer(ALICE, BOB, 100)) + }, + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: MetaEvent::contract(RawEvent::Instantiated(ALICE, BOB)) + } + ]); }, ); } - -#[test] -fn caller_contract() { - const CONTRACT_SHOULD_TRANSFER_TO: u64 = 9; - - let code_caller_logger = wabt::wat2wasm(CODE_CALLER_LOGGER).unwrap(); - let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap(); - - with_externalities(&mut ExtBuilder::default().build(), || { - >::insert(1, code_transfer.to_vec()); - >::insert(CONTRACT_SHOULD_TRANSFER_TO, code_caller_logger); - - Balances::set_free_balance(&0, 100_000_000); - Balances::increase_total_stake_by(100_000_000); - Balances::set_free_balance(&1, 11); - Balances::increase_total_stake_by(11); - - assert_ok!(Contract::call(Origin::signed(0), 1, 3.into(), 100_000.into(), Vec::new())); - - // Load the zero-th slot of the storage of the caller logger contract. - // We verify here that the caller logger contract has witnessed the call coming from - // the caller contract - 0x01. - assert_eq!( - >::get(CONTRACT_SHOULD_TRANSFER_TO, vec![0; 32]), - Some(vec![0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), - ); - }); -} diff --git a/srml/contract/src/vm/prepare.rs b/srml/contract/src/vm/prepare.rs deleted file mode 100644 index 8ec056dfc71b5..0000000000000 --- a/srml/contract/src/vm/prepare.rs +++ /dev/null @@ -1,285 +0,0 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. -// This file is part of Substrate. - -// Substrate is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Substrate is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Substrate. If not, see . - -//! Module that takes care of loading, checking and preprocessing of a -//! wasm module before execution. - -use super::env_def::HostFunctionSet; -use super::{Error, Ext}; -use rstd::prelude::*; -use parity_wasm::elements::{self, External, MemoryType, Type}; -use pwasm_utils; -use pwasm_utils::rules; -use runtime_primitives::traits::As; -use sandbox; -use {Trait, Schedule}; - -struct ContractModule<'a, Gas: 'a> { - // An `Option` is used here for loaning (`take()`-ing) the module. - // Invariant: Can't be `None` (i.e. on enter and on exit from the function - // the value *must* be `Some`). - module: Option, - schedule: &'a Schedule, -} - -impl<'a, Gas: 'a + As + Clone> ContractModule<'a, Gas> { - fn new(original_code: &[u8], schedule: &'a Schedule) -> Result, Error> { - let module = - elements::deserialize_buffer(original_code).map_err(|_| Error::Deserialization)?; - Ok(ContractModule { - module: Some(module), - schedule, - }) - } - - /// Ensures that module doesn't declare internal memories. - /// - /// In this runtime we only allow wasm module to import memory from the environment. - /// Memory section contains declarations of internal linear memories, so if we find one - /// we reject such a module. - fn ensure_no_internal_memory(&self) -> Result<(), Error> { - let module = self - .module - .as_ref() - .expect("On entry to the function `module` can't be None; qed"); - if module - .memory_section() - .map_or(false, |ms| ms.entries().len() > 0) - { - return Err(Error::InternalMemoryDeclared); - } - Ok(()) - } - - fn inject_gas_metering(&mut self) -> Result<(), Error> { - let gas_rules = rules::Set::new(self.schedule.regular_op_cost.clone().as_(), Default::default()) - .with_grow_cost(self.schedule.grow_mem_cost.clone().as_()) - .with_forbidden_floats(); - - let module = self - .module - .take() - .expect("On entry to the function `module` can't be `None`; qed"); - - let contract_module = pwasm_utils::inject_gas_counter(module, &gas_rules) - .map_err(|_| Error::GasInstrumentation)?; - - self.module = Some(contract_module); - Ok(()) - } - - fn inject_stack_height_metering(&mut self) -> Result<(), Error> { - let module = self - .module - .take() - .expect("On entry to the function `module` can't be `None`; qed"); - - let contract_module = - pwasm_utils::stack_height::inject_limiter(module, self.schedule.max_stack_height) - .map_err(|_| Error::StackHeightInstrumentation)?; - - self.module = Some(contract_module); - Ok(()) - } - - /// Scan an import section if any. - /// - /// This accomplishes two tasks: - /// - /// - checks any imported function against defined host functions set, incl. - /// their signatures. - /// - if there is a memory import, returns it's descriptor - fn scan_imports(&self, env: &HostFunctionSet) -> Result, Error> { - let module = self - .module - .as_ref() - .expect("On entry to the function `module` can't be `None`; qed"); - - let types = module.type_section().map(|ts| ts.types()).unwrap_or(&[]); - let import_entries = module - .import_section() - .map(|is| is.entries()) - .unwrap_or(&[]); - - let mut imported_mem_type = None; - - for import in import_entries { - if import.module() != "env" { - // This import tries to import something from non-"env" module, - // but all imports are located in "env" at the moment. - return Err(Error::Instantiate); - } - - let type_idx = match import.external() { - &External::Function(ref type_idx) => type_idx, - &External::Memory(ref memory_type) => { - imported_mem_type = Some(memory_type); - continue; - } - _ => continue, - }; - - let Type::Function(ref func_ty) = types - .get(*type_idx as usize) - .ok_or_else(|| Error::Instantiate)?; - - let ext_func = env - .funcs - .get(import.field().as_bytes()) - .ok_or_else(|| Error::Instantiate)?; - if !ext_func.func_type_matches(func_ty) { - return Err(Error::Instantiate); - } - } - Ok(imported_mem_type) - } - - fn into_wasm_code(mut self) -> Result, Error> { - elements::serialize( - self.module - .take() - .expect("On entry to the function `module` can't be `None`; qed"), - ).map_err(|_| Error::Serialization) - } -} - -pub(super) struct PreparedContract { - pub instrumented_code: Vec, - pub memory: sandbox::Memory, -} - -/// Loads the given module given in `original_code`, performs some checks on it and -/// does some preprocessing. -/// -/// The checks are: -/// -/// - module doesn't define an internal memory instance, -/// - imported memory (if any) doesn't reserve more memory than permitted by the `schedule`, -/// - all imported functions from the external environment matches defined by `env` module, -/// -/// The preprocessing includes injecting code for gas metering and metering the height of stack. -pub(super) fn prepare_contract( - original_code: &[u8], - schedule: &Schedule<::Gas>, - env: &HostFunctionSet, -) -> Result { - let mut contract_module = ContractModule::new(original_code, schedule)?; - contract_module.ensure_no_internal_memory()?; - contract_module.inject_gas_metering()?; - contract_module.inject_stack_height_metering()?; - - let memory = if let Some(memory_type) = contract_module.scan_imports(env)? { - // Inspect the module to extract the initial and maximum page count. - let limits = memory_type.limits(); - match (limits.initial(), limits.maximum()) { - (initial, Some(maximum)) if initial > maximum => { - // Requested initial number of pages should not exceed the requested maximum. - return Err(Error::Memory); - } - (_, Some(maximum)) if maximum > schedule.max_memory_pages => { - // Maximum number of pages should not exceed the configured maximum. - return Err(Error::Memory); - } - (_, None) => { - // Maximum number of pages should be always declared. - // This isn't a hard requirement and can be treated as a maxiumum set - // to configured maximum. - return Err(Error::Memory); - } - (initial, maximum) => sandbox::Memory::new(initial, maximum), - } - } else { - // If none memory imported then just crate an empty placeholder. - // Any access to it will lead to out of bounds trap. - sandbox::Memory::new(0, Some(0)) - }; - let memory = memory.map_err(|_| Error::Memory)?; - - Ok(PreparedContract { - instrumented_code: contract_module.into_wasm_code()?, - memory, - }) -} - -#[cfg(test)] -mod tests { - use super::*; - use std::fmt; - use vm::tests::MockExt; - use wabt; - - impl fmt::Debug for PreparedContract { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "PreparedContract {{ .. }}") - } - } - - fn parse_and_prepare_wat(wat: &str) -> Result { - let wasm = wabt::Wat2Wasm::new().validate(false).convert(wat).unwrap(); - let schedule = Schedule::::default(); - let env = ::vm::runtime::init_env(); - prepare_contract::(wasm.as_ref(), &schedule, &env) - } - - #[test] - fn internal_memory_declaration() { - let r = parse_and_prepare_wat(r#"(module (memory 1 1))"#); - assert_matches!(r, Err(Error::InternalMemoryDeclared)); - } - - #[test] - fn memory() { - // This test assumes that maximum page number is configured to a certain number. - assert_eq!(Schedule::::default().max_memory_pages, 16); - - let r = parse_and_prepare_wat(r#"(module (import "env" "memory" (memory 1 1)))"#); - assert_matches!(r, Ok(_)); - - // No memory import - let r = parse_and_prepare_wat(r#"(module)"#); - assert_matches!(r, Ok(_)); - - // initial exceed maximum - let r = parse_and_prepare_wat(r#"(module (import "env" "memory" (memory 16 1)))"#); - assert_matches!(r, Err(Error::Memory)); - - // no maximum - let r = parse_and_prepare_wat(r#"(module (import "env" "memory" (memory 1)))"#); - assert_matches!(r, Err(Error::Memory)); - - // requested maximum exceed configured maximum - let r = parse_and_prepare_wat(r#"(module (import "env" "memory" (memory 1 17)))"#); - assert_matches!(r, Err(Error::Memory)); - } - - #[test] - fn imports() { - // nothing can be imported from non-"env" module for now. - let r = parse_and_prepare_wat(r#"(module (import "another_module" "memory" (memory 1 1)))"#); - assert_matches!(r, Err(Error::Instantiate)); - - let r = parse_and_prepare_wat(r#"(module (import "env" "gas" (func (param i32))))"#); - assert_matches!(r, Ok(_)); - - // wrong signature - let r = parse_and_prepare_wat(r#"(module (import "env" "gas" (func (param i64))))"#); - assert_matches!(r, Err(Error::Instantiate)); - - // unknown function name - let r = parse_and_prepare_wat(r#"(module (import "env" "unknown_func" (func)))"#); - assert_matches!(r, Err(Error::Instantiate)); - } -} diff --git a/srml/contract/src/wasm/code_cache.rs b/srml/contract/src/wasm/code_cache.rs new file mode 100644 index 0000000000000..e0c5bd4b97ae4 --- /dev/null +++ b/srml/contract/src/wasm/code_cache.rs @@ -0,0 +1,106 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! A module that implements instrumented code cache. +//! +//! - In order to run contract code we need to instrument it with gas metering. +//! To do that we need to provide the schedule which will supply exact gas costs values. +//! We cache this code in the storage saving the schedule version. +//! - Before running contract code we check if the cached code has the schedule version that is equal to the current saved schedule. +//! If it is equal then run the code, if it isn't reinstrument with the current schedule. +//! - When we update the schedule we want it to have strictly greater version than the current saved one: +//! this guarantees that every instrumented contract code in cache cannot have the version equal to the current one. +//! Thus, before executing a contract it should be reinstrument with new schedule. + +use gas::{GasMeter, Token}; +use rstd::prelude::*; +use runtime_primitives::traits::{As, CheckedMul, Hash, Bounded}; +use runtime_support::StorageMap; +use wasm::{prepare, runtime::Env, PrefabWasmModule}; +use {CodeHash, CodeStorage, PristineCode, Schedule, Trait}; + +/// Gas metering token that used for charging storing code into the code storage. +/// +/// Specifies the code length in bytes. +#[cfg_attr(test, derive(Debug, PartialEq, Eq))] +#[derive(Copy, Clone)] +pub struct PutCodeToken(u64); + +impl Token for PutCodeToken { + type Metadata = Schedule; + + fn calculate_amount(&self, metadata: &Schedule) -> T::Gas { + let code_len_in_gas = >::sa(self.0); + metadata + .put_code_per_byte_cost + .checked_mul(&code_len_in_gas) + .unwrap_or_else(|| Bounded::max_value()) + } +} + +/// Put code in the storage. The hash of code is used as a key and is returned +/// as a result of this function. +/// +/// This function instruments the given code and caches it in the storage. +pub fn save( + original_code: Vec, + gas_meter: &mut GasMeter, + schedule: &Schedule, +) -> Result, &'static str> { + // The first time instrumentation is on the user. However, consequent reinstrumentation + // due to the schedule changes is on governance system. + if gas_meter + .charge(schedule, PutCodeToken(original_code.len() as u64)) + .is_out_of_gas() + { + return Err("there is not enough gas for storing the code"); + } + + let prefab_module = prepare::prepare_contract::(&original_code, schedule)?; + let code_hash = T::Hashing::hash(&original_code); + + // TODO: #1416 validate the code. If the code is not valid, then don't store it. + + >::insert(code_hash, prefab_module); + >::insert(code_hash, original_code); + + Ok(code_hash) +} + +/// Load code with the given code hash. +/// +/// If the module was instrumented with a lower version of schedule than +/// the current one given as an argument, then this function will perform +/// re-instrumentation and update the cache in the storage. +pub fn load( + code_hash: &CodeHash, + schedule: &Schedule, +) -> Result { + let mut prefab_module = + >::get(code_hash).ok_or_else(|| "code is not found")?; + + if prefab_module.schedule_version < schedule.version { + // The current schedule version is greater than the version of the one cached + // in the storage. + // + // We need to re-instrument the code with the latest schedule here. + let original_code = + >::get(code_hash).ok_or_else(|| "pristine code is not found")?; + prefab_module = prepare::prepare_contract::(&original_code, schedule)?; + >::insert(code_hash, prefab_module.clone()); + } + Ok(prefab_module) +} diff --git a/srml/contract/src/vm/env_def/macros.rs b/srml/contract/src/wasm/env_def/macros.rs similarity index 70% rename from srml/contract/src/vm/env_def/macros.rs rename to srml/contract/src/wasm/env_def/macros.rs index c751bd0c12728..bd123c43604c8 100644 --- a/srml/contract/src/vm/env_def/macros.rs +++ b/srml/contract/src/wasm/env_def/macros.rs @@ -17,12 +17,12 @@ //! Definition of macros that hides boilerplate of defining external environment //! for a wasm module. //! -//! Typically you should use `define_env` macro. +//! Most likely you should use `define_env` macro. #[macro_export] macro_rules! convert_args { () => (vec![]); - ( $( $t:ty ),* ) => ( vec![ $( { use $crate::vm::env_def::ConvertibleToWasm; <$t>::VALUE_TYPE }, )* ] ); + ( $( $t:ty ),* ) => ( vec![ $( { use $crate::wasm::env_def::ConvertibleToWasm; <$t>::VALUE_TYPE }, )* ] ); } #[macro_export] @@ -36,19 +36,39 @@ macro_rules! gen_signature { ( ( $( $params: ty ),* ) -> $returns: ty ) => ( { $crate::parity_wasm::elements::FunctionType::new(convert_args!($($params),*), Some({ - use $crate::vm::env_def::ConvertibleToWasm; <$returns>::VALUE_TYPE + use $crate::wasm::env_def::ConvertibleToWasm; <$returns>::VALUE_TYPE })) } ); } +#[macro_export] +macro_rules! gen_signature_dispatch { + ( + $needle_name:ident, + $needle_sig:ident ; + $name:ident + ( $ctx:ident $( , $names:ident : $params:ty )* ) $( -> $returns:ty )* , $($rest:tt)* ) => { + if stringify!($name).as_bytes() == $needle_name { + let signature = gen_signature!( ( $( $params ),* ) $( -> $returns )* ); + if $needle_sig == &signature { + return true; + } + } else { + gen_signature_dispatch!($needle_name, $needle_sig ; $($rest)*); + } + }; + ( $needle_name:ident, $needle_sig:ident ; ) => { + }; +} + /// Unmarshall arguments and then execute `body` expression and return its result. macro_rules! unmarshall_then_body { ( $body:tt, $ctx:ident, $args_iter:ident, $( $names:ident : $params:ty ),* ) => ({ $( - let $names : <$params as $crate::vm::env_def::ConvertibleToWasm>::NativeType = + let $names : <$params as $crate::wasm::env_def::ConvertibleToWasm>::NativeType = $args_iter.next() - .and_then(|v| <$params as $crate::vm::env_def::ConvertibleToWasm> + .and_then(|v| <$params as $crate::wasm::env_def::ConvertibleToWasm> ::from_typed_value(v.clone())) .expect( "precondition: all imports should be checked against the signatures of corresponding @@ -84,16 +104,16 @@ where #[macro_export] macro_rules! unmarshall_then_body_then_marshall { ( $args_iter:ident, $ctx:ident, ( $( $names:ident : $params:ty ),* ) -> $returns:ty => $body:tt ) => ({ - let body = $crate::vm::env_def::macros::constrain_closure::< - <$returns as $crate::vm::env_def::ConvertibleToWasm>::NativeType, _ + let body = $crate::wasm::env_def::macros::constrain_closure::< + <$returns as $crate::wasm::env_def::ConvertibleToWasm>::NativeType, _ >(|| { unmarshall_then_body!($body, $ctx, $args_iter, $( $names : $params ),*) }); let r = body()?; - return Ok($crate::sandbox::ReturnValue::Value({ use $crate::vm::env_def::ConvertibleToWasm; r.to_typed_value() })) + return Ok($crate::sandbox::ReturnValue::Value({ use $crate::wasm::env_def::ConvertibleToWasm; r.to_typed_value() })) }); ( $args_iter:ident, $ctx:ident, ( $( $names:ident : $params:ty ),* ) => $body:tt ) => ({ - let body = $crate::vm::env_def::macros::constrain_closure::<(), _>(|| { + let body = $crate::wasm::env_def::macros::constrain_closure::<(), _>(|| { unmarshall_then_body!($body, $ctx, $args_iter, $( $names : $params ),*) }); body()?; @@ -105,7 +125,7 @@ macro_rules! unmarshall_then_body_then_marshall { macro_rules! define_func { ( < E: $ext_ty:tt > $name:ident ( $ctx: ident $(, $names:ident : $params:ty)*) $(-> $returns:ty)* => $body:tt ) => { fn $name< E: $ext_ty >( - $ctx: &mut $crate::vm::Runtime, + $ctx: &mut $crate::wasm::Runtime, args: &[$crate::sandbox::TypedValue], ) -> Result { #[allow(unused)] @@ -120,6 +140,27 @@ macro_rules! define_func { }; } +#[macro_export] +macro_rules! register_func { + ( $reg_cb:ident, < E: $ext_ty:tt > ; ) => {}; + + ( $reg_cb:ident, < E: $ext_ty:tt > ; + $name:ident ( $ctx:ident $( , $names:ident : $params:ty )* ) + $( -> $returns:ty )* => $body:tt $($rest:tt)* + ) => { + $reg_cb( + stringify!($name).as_bytes(), + { + define_func!( + < E: $ext_ty > $name ( $ctx $(, $names : $params )* ) $( -> $returns )* => $body + ); + $name:: + } + ); + register_func!( $reg_cb, < E: $ext_ty > ; $($rest)* ); + }; +} + /// Define a function set that can be imported by executing wasm code. /// /// **NB**: Be advised that all functions defined by this macro @@ -132,25 +173,20 @@ macro_rules! define_env { $( $name:ident ( $ctx:ident $( , $names:ident : $params:ty )* ) $( -> $returns:ty )* => $body:tt , )* ) => { - pub(crate) fn $init_name() -> $crate::vm::env_def::HostFunctionSet { - let mut env = $crate::vm::env_def::HostFunctionSet::new(); - - $( - env.funcs.insert( - stringify!( $name ).into(), - $crate::vm::env_def::HostFunction::new( - gen_signature!( ( $( $params ),* ) $( -> $returns )* ), - { - define_func!( - < E: $ext_ty > $name ( $ctx $(, $names : $params )* ) $( -> $returns )* => $body - ); - $name:: - }, - ), - ); - )* + pub struct $init_name; - env + impl $crate::wasm::env_def::ImportSatisfyCheck for $init_name { + fn can_satisfy(name: &[u8], func_type: &$crate::parity_wasm::elements::FunctionType) -> bool { + gen_signature_dispatch!( name, func_type ; $( $name ( $ctx $(, $names : $params )* ) $( -> $returns )* , )* ); + + return false; + } + } + + impl $crate::wasm::env_def::FunctionImplProvider for $init_name { + fn impls)>(f: &mut F) { + register_func!(f, < E: $ext_ty > ; $( $name ( $ctx $( , $names : $params )* ) $( -> $returns)* => $body )* ); + } } }; } @@ -161,8 +197,9 @@ mod tests { use parity_wasm::elements::ValueType; use runtime_primitives::traits::{As, Zero}; use sandbox::{self, ReturnValue, TypedValue}; - use vm::tests::MockExt; - use vm::{Ext, Runtime}; + use wasm::tests::MockExt; + use wasm::Runtime; + use exec::Ext; use Trait; #[test] @@ -267,7 +304,9 @@ mod tests { #[test] fn macro_define_env() { - define_env!(init_env, , + use wasm::env_def::ImportSatisfyCheck; + + define_env!(Env, , ext_gas( _ctx, amount: u32 ) => { let amount = <::Gas as As>::sa(amount); if !amount.is_zero() { @@ -278,7 +317,7 @@ mod tests { }, ); - let env = init_env::(); - assert!(env.funcs.get(&b"ext_gas"[..]).is_some()); + assert!(Env::can_satisfy(b"ext_gas", &FunctionType::new(vec![ValueType::I32], None))); + assert!(!Env::can_satisfy(b"not_exists", &FunctionType::new(vec![], None))); } } diff --git a/srml/contract/src/vm/env_def/mod.rs b/srml/contract/src/wasm/env_def/mod.rs similarity index 58% rename from srml/contract/src/vm/env_def/mod.rs rename to srml/contract/src/wasm/env_def/mod.rs index d66e1b8961ac8..0f4de9186036d 100644 --- a/srml/contract/src/vm/env_def/mod.rs +++ b/srml/contract/src/wasm/env_def/mod.rs @@ -14,10 +14,9 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -use super::{Ext, Runtime}; +use super::Runtime; +use exec::Ext; use parity_wasm::elements::{FunctionType, ValueType}; -use rstd::prelude::*; -use rstd::collections::btree_map::BTreeMap; use sandbox::{self, TypedValue}; #[macro_use] @@ -66,45 +65,21 @@ impl ConvertibleToWasm for u64 { } } -/// Represents a set of function that defined in this particular environment and -/// which can be imported and called by the module. -pub(crate) struct HostFunctionSet { - /// Functions which defined in the environment. - pub funcs: BTreeMap, HostFunction>, -} -impl HostFunctionSet { - pub fn new() -> Self { - HostFunctionSet { - funcs: BTreeMap::new(), - } - } -} +pub(crate) type HostFunc = + fn( + &mut Runtime, + &[sandbox::TypedValue] + ) -> Result; -pub(crate) struct HostFunction { - pub(crate) f: fn(&mut Runtime, &[sandbox::TypedValue]) - -> Result, - func_type: FunctionType, +pub(crate) trait FunctionImplProvider { + fn impls)>(f: &mut F); } -impl HostFunction { - /// Create a new instance of a host function. - pub fn new( - func_type: FunctionType, - f: fn(&mut Runtime, &[sandbox::TypedValue]) - -> Result, - ) -> Self { - HostFunction { func_type, f } - } - - /// Returns a function pointer of this host function. - pub fn raw_fn_ptr( - &self, - ) -> fn(&mut Runtime, &[sandbox::TypedValue]) - -> Result { - self.f - } - /// Check if the this function could be invoked with the given function signature. - pub fn func_type_matches(&self, func_type: &FunctionType) -> bool { - &self.func_type == func_type - } +/// This trait can be used to check whether the host environment can satisfy +/// a requested function import. +pub trait ImportSatisfyCheck { + /// Returns `true` if the host environment contains a function with + /// the specified name and its type matches to the given type, or `false` + /// otherwise. + fn can_satisfy(name: &[u8], func_type: &FunctionType) -> bool; } diff --git a/srml/contract/src/vm/mod.rs b/srml/contract/src/wasm/mod.rs similarity index 53% rename from srml/contract/src/vm/mod.rs rename to srml/contract/src/wasm/mod.rs index b46264bbd6fc2..a2430064cbb3d 100644 --- a/srml/contract/src/vm/mod.rs +++ b/srml/contract/src/wasm/mod.rs @@ -17,154 +17,172 @@ //! This module provides a means for executing contracts //! represented in wasm. -use exec::CreateReceipt; +use codec::Compact; +use exec::{Ext, EmptyOutputBuf, VmExecResult}; use gas::GasMeter; use rstd::prelude::*; -use {Trait, Schedule}; -use {balances, sandbox, system}; +use sandbox; +use wasm::env_def::FunctionImplProvider; +use {CodeHash, Schedule, Trait}; -type BalanceOf = ::Balance; -type AccountIdOf = ::AccountId; - -mod prepare; #[macro_use] mod env_def; +mod code_cache; +mod prepare; mod runtime; -use self::prepare::{prepare_contract, PreparedContract}; use self::runtime::{to_execution_result, Runtime}; - -/// An interface that provides an access to the external environment in which the -/// smart-contract is executed. -/// -/// This interface is specialised to an account of the executing code, so all -/// operations are implicitly performed on that account. -pub trait Ext { - type T: Trait; - - /// Returns the storage entry of the executing account by the given key. - fn get_storage(&self, key: &[u8]) -> Option>; - - /// Sets the storage entry by the given key to the specified value. - fn set_storage(&mut self, key: &[u8], value: Option>); - - /// Create a new account for a contract. +use self::code_cache::load as load_code; + +pub use self::code_cache::save as save_code; + +/// A prepared wasm module ready for execution. +#[derive(Clone, Encode, Decode)] +pub struct PrefabWasmModule { + /// Version of the schedule with which the code was instrumented. + #[codec(compact)] + schedule_version: u32, + #[codec(compact)] + initial: u32, + #[codec(compact)] + maximum: u32, + /// This field is reserved for future evolution of format. /// - /// The newly created account will be associated with the `code`. `value` specifies the amount of value - /// transfered from this to the newly created account. - fn create( - &mut self, - code: &[u8], - value: BalanceOf, - gas_meter: &mut GasMeter, - data: &[u8], - ) -> Result, ()>; - - /// Call (possibly transfering some amount of funds) into the specified account. - fn call( - &mut self, - to: &AccountIdOf, - value: BalanceOf, - gas_meter: &mut GasMeter, - data: &[u8], - output_data: &mut Vec, - ) -> Result<(), ()>; - - /// Returns a reference to the account id of the caller. - fn caller(&self) -> &AccountIdOf; + /// Basically, for now this field will be serialized as `None`. In the future + /// we would be able to extend this structure with. + _reserved: Option<()>, + /// Code instrumented with the latest schedule. + code: Vec, } -/// Error that can occur while preparing or executing wasm smart-contract. -#[derive(Debug, PartialEq, Eq)] -pub enum Error { - /// Error happened while serializing the module. - Serialization, - - /// Error happened while deserializing the module. - Deserialization, - - /// Internal memory declaration has been found in the module. - InternalMemoryDeclared, +/// Wasm executable loaded by `WasmLoader` and executed by `WasmVm`. +pub struct WasmExecutable { + entrypoint_name: &'static [u8], + prefab_module: PrefabWasmModule, +} - /// Gas instrumentation failed. - /// - /// This most likely indicates the module isn't valid. - GasInstrumentation, +/// Loader which fetches `WasmExecutable` from the code cache. +pub struct WasmLoader<'a, T: Trait> { + schedule: &'a Schedule, +} - /// Stack instrumentation failed. - /// - /// This most likely indicates the module isn't valid. - StackHeightInstrumentation, +impl<'a, T: Trait> WasmLoader<'a, T> { + pub fn new(schedule: &'a Schedule) -> Self { + WasmLoader { schedule } + } +} - /// Error happened during invocation of the contract's entrypoint. - /// - /// Most likely because of trap. - Invoke, +impl<'a, T: Trait> ::exec::Loader for WasmLoader<'a, T> { + type Executable = WasmExecutable; - /// Error happened during instantiation. - /// - /// This might indicate that `start` function trapped, or module isn't - /// instantiable and/or unlinkable. - Instantiate, + fn load_init(&self, code_hash: &CodeHash) -> Result { + let prefab_module = load_code::(code_hash, self.schedule)?; + Ok(WasmExecutable { + entrypoint_name: b"deploy", + prefab_module, + }) + } + fn load_main(&self, code_hash: &CodeHash) -> Result { + let prefab_module = load_code::(code_hash, self.schedule)?; + Ok(WasmExecutable { + entrypoint_name: b"call", + prefab_module, + }) + } +} - /// Memory creation error. - /// - /// This might happen when the memory import has invalid descriptor or - /// requested too much resources. - Memory, +/// Implementation of `Vm` that takes `WasmExecutable` and executes it. +pub struct WasmVm<'a, T: Trait> { + schedule: &'a Schedule, } -/// Execute the given code as a contract. -pub fn execute<'a, E: Ext>( - code: &[u8], - input_data: &[u8], - output_data: &mut Vec, - ext: &'a mut E, - schedule: &Schedule<::Gas>, - gas_meter: &mut GasMeter, -) -> Result<(), Error> { - let env = runtime::init_env(); - - let PreparedContract { - instrumented_code, - memory, - } = prepare_contract(code, &schedule, &env)?; - - let mut imports = sandbox::EnvironmentDefinitionBuilder::new(); - for (func_name, ext_func) in &env.funcs { - imports.add_host_func("env", &func_name[..], ext_func.raw_fn_ptr()); +impl<'a, T: Trait> WasmVm<'a, T> { + pub fn new(schedule: &'a Schedule) -> Self { + WasmVm { schedule } } - imports.add_memory("env", "memory", memory.clone()); +} - let mut runtime = Runtime::new(ext, input_data, output_data, &schedule, memory, gas_meter); +impl<'a, T: Trait> ::exec::Vm for WasmVm<'a, T> { + type Executable = WasmExecutable; + + fn execute>( + &self, + exec: &WasmExecutable, + ext: &mut E, + input_data: &[u8], + empty_output_buf: EmptyOutputBuf, + gas_meter: &mut GasMeter, + ) -> VmExecResult { + let memory = + sandbox::Memory::new(exec.prefab_module.initial, Some(exec.prefab_module.maximum)) + .unwrap_or_else(|_| { + // unlike `.expect`, explicit panic preserves the source location. + // Needed as we can't use `RUST_BACKTRACE` in here. + panic!( + "exec.prefab_module.initial can't be greater than exec.prefab_module.maximum; + thus Memory::new must not fail; + qed" + ) + }); + + let mut imports = sandbox::EnvironmentDefinitionBuilder::new(); + imports.add_memory("env", "memory", memory.clone()); + runtime::Env::impls(&mut |name, func_ptr| { + imports.add_host_func("env", name, func_ptr); + }); + + let mut runtime = Runtime::new( + ext, + input_data, + empty_output_buf, + &self.schedule, + memory, + gas_meter, + ); - // Instantiate the instance from the instrumented module code. - match sandbox::Instance::new(&instrumented_code, &imports, &mut runtime) { - // No errors or traps were generated on instantiation! That - // means we can now invoke the contract entrypoint. - Ok(mut instance) => { - let err = instance.invoke(b"call", &[], &mut runtime).err(); - to_execution_result(runtime, err) + // Instantiate the instance from the instrumented module code. + match sandbox::Instance::new(&exec.prefab_module.code, &imports, &mut runtime) { + // No errors or traps were generated on instantiation! That + // means we can now invoke the contract entrypoint. + Ok(mut instance) => { + let err = instance + .invoke(exec.entrypoint_name, &[], &mut runtime) + .err(); + to_execution_result(runtime, err) + } + // `start` function trapped. Treat it in the same manner as an execution error. + Err(err @ sandbox::Error::Execution) => to_execution_result(runtime, Some(err)), + Err(_err @ sandbox::Error::Module) => { + // `Error::Module` is returned only if instantiation or linking failed (i.e. + // wasm bianry tried to import a function that is not provided by the host). + // This shouldn't happen because validation proccess ought to reject such binaries. + // + // Because panics are really undesirable in the runtime code, we treat this as + // a trap for now. Eventually, we might want to revisit this. + return VmExecResult::Trap("validation error"); + } + // Other instantiation errors. + // Return without executing anything. + Err(_) => return VmExecResult::Trap("during start function"), } - // `start` function trapped. Treat it in the same manner as an execution error. - Err(err @ sandbox::Error::Execution) => to_execution_result(runtime, Some(err)), - // Other instantiation errors. - // Return without executing anything. - Err(_) => return Err(Error::Instantiate), } } #[cfg(test)] mod tests { use super::*; - use gas::GasMeter; use std::collections::HashMap; + use substrate_primitives::H256; + use exec::{CallReceipt, Ext, InstantiateReceipt, EmptyOutputBuf}; + use gas::GasMeter; use tests::Test; use wabt; + use wasm::prepare::prepare_contract; + use CodeHash; #[derive(Debug, PartialEq, Eq)] struct CreateEntry { - code: Vec, + code_hash: H256, endowment: u64, data: Vec, gas_left: u64, @@ -192,15 +210,15 @@ mod tests { fn set_storage(&mut self, key: &[u8], value: Option>) { *self.storage.entry(key.to_vec()).or_insert(Vec::new()) = value.unwrap_or(Vec::new()); } - fn create( + fn instantiate( &mut self, - code: &[u8], + code_hash: &CodeHash, endowment: u64, gas_meter: &mut GasMeter, data: &[u8], - ) -> Result, ()> { + ) -> Result, &'static str> { self.creates.push(CreateEntry { - code: code.to_vec(), + code_hash: code_hash.clone(), endowment, data: data.to_vec(), gas_left: gas_meter.gas_left(), @@ -208,7 +226,7 @@ mod tests { let address = self.next_account_id; self.next_account_id += 1; - Ok(CreateReceipt { address }) + Ok(InstantiateReceipt { address }) } fn call( &mut self, @@ -216,8 +234,8 @@ mod tests { value: u64, gas_meter: &mut GasMeter, data: &[u8], - _output_data: &mut Vec, - ) -> Result<(), ()> { + _output_data: EmptyOutputBuf, + ) -> Result { self.transfers.push(TransferEntry { to: *to, value, @@ -226,11 +244,46 @@ mod tests { }); // Assume for now that it was just a plain transfer. // TODO: Add tests for different call outcomes. - Ok(()) + Ok(CallReceipt { + output_data: Vec::new(), + }) } fn caller(&self) -> &u64 { &42 } + fn address(&self) -> &u64 { + &69 + } + } + + fn execute( + wat: &str, + input_data: &[u8], + output_data: &mut Vec, + ext: &mut E, + gas_meter: &mut GasMeter, + ) -> Result<(), &'static str> { + use exec::Vm; + + let wasm = wabt::wat2wasm(wat).unwrap(); + let schedule = ::Schedule::::default(); + let prefab_module = + prepare_contract::(&wasm, &schedule).unwrap(); + + let exec = WasmExecutable { + // Use a "call" convention. + entrypoint_name: b"call", + prefab_module, + }; + + let cfg = Default::default(); + let vm = WasmVm::new(&cfg); + + *output_data = vm + .execute(&exec, ext, input_data, EmptyOutputBuf::new(), gas_meter) + .into_result()?; + + Ok(()) } const CODE_TRANSFER: &str = r#" @@ -259,6 +312,8 @@ mod tests { ) ) ) + (func (export "deploy")) + ;; Destination AccountId to transfer the funds. ;; Represented by u64 (8 bytes long) in little endian. (data (i32.const 4) "\09\00\00\00\00\00\00\00") @@ -272,26 +327,22 @@ mod tests { #[test] fn contract_transfer() { - let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap(); - let mut mock_ext = MockExt::default(); execute( - &code_transfer, + CODE_TRANSFER, &[], &mut Vec::new(), &mut mock_ext, - &Schedule::::default(), &mut GasMeter::with_limit(50_000, 1), - ).unwrap(); + ) + .unwrap(); assert_eq!( &mock_ext.transfers, &[TransferEntry { to: 9, value: 6, - data: vec![ - 1, 2, 3, 4, - ], + data: vec![1, 2, 3, 4], gas_left: 49970, }] ); @@ -313,83 +364,51 @@ mod tests { (func (export "call") (drop (call $ext_create - (i32.const 12) ;; Pointer to `code` - (i32.const 8) ;; Length of `code` + (i32.const 16) ;; Pointer to `code_hash` + (i32.const 32) ;; Length of `code_hash` (i64.const 0) ;; How much gas to devote for the execution. 0 = all. (i32.const 4) ;; Pointer to the buffer with value to transfer (i32.const 8) ;; Length of the buffer with value to transfer - (i32.const 20) ;; Pointer to input data buffer address + (i32.const 12) ;; Pointer to input data buffer address (i32.const 4) ;; Length of input data buffer ) ) ) + (func (export "deploy")) + ;; Amount of value to transfer. ;; Represented by u64 (8 bytes long) in little endian. (data (i32.const 4) "\03\00\00\00\00\00\00\00") - ;; Embedded wasm code. - (data (i32.const 12) "\00\61\73\6d\01\00\00\00") ;; Input data to pass to the contract being created. - (data (i32.const 20) "\01\02\03\04") + (data (i32.const 12) "\01\02\03\04") + ;; Hash of code. + (data (i32.const 16) "\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11") ) "#; #[test] fn contract_create() { - let code_create = wabt::wat2wasm(CODE_CREATE).unwrap(); - let mut mock_ext = MockExt::default(); execute( - &code_create, + CODE_CREATE, &[], &mut Vec::new(), &mut mock_ext, - &Schedule::default(), &mut GasMeter::with_limit(50_000, 1), - ).unwrap(); + ) + .unwrap(); assert_eq!( &mock_ext.creates, &[CreateEntry { - code: vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00], + code_hash: [0x11; 32].into(), endowment: 3, - data: vec![ - 1, 2, 3, 4, - ], - gas_left: 49970, + data: vec![1, 2, 3, 4], + gas_left: 49946, }] ); } - const CODE_MEM: &str = r#" -(module - ;; Internal memory is not allowed. - (memory 1 1) - - (func (export "call") - nop - ) -) -"#; - - #[test] - fn contract_internal_mem() { - let code_mem = wabt::wat2wasm(CODE_MEM).unwrap(); - - let mut mock_ext = MockExt::default(); - - assert_matches!( - execute( - &code_mem, - &[], - &mut Vec::new(), - &mut mock_ext, - &Schedule::default(), - &mut GasMeter::with_limit(100_000, 1) - ), - Err(_) - ); - } - const CODE_TRANSFER_LIMITED_GAS: &str = r#" (module ;; ext_call( @@ -416,6 +435,8 @@ mod tests { ) ) ) + (func (export "deploy")) + ;; Destination AccountId to transfer the funds. ;; Represented by u64 (8 bytes long) in little endian. (data (i32.const 4) "\09\00\00\00\00\00\00\00") @@ -429,26 +450,22 @@ mod tests { #[test] fn contract_call_limited_gas() { - let code_transfer = wabt::wat2wasm(CODE_TRANSFER_LIMITED_GAS).unwrap(); - let mut mock_ext = MockExt::default(); execute( - &code_transfer, + &CODE_TRANSFER_LIMITED_GAS, &[], &mut Vec::new(), &mut mock_ext, - &Schedule::default(), &mut GasMeter::with_limit(50_000, 1), - ).unwrap(); + ) + .unwrap(); assert_eq!( &mock_ext.transfers, &[TransferEntry { to: 9, value: 6, - data: vec![ - 1, 2, 3, 4, - ], + data: vec![1, 2, 3, 4], gas_left: 228, }] ); @@ -513,38 +530,35 @@ mod tests { (unreachable) ) + (func (export "deploy")) + (data (i32.const 4) "\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11") ) "#; #[test] fn get_storage_puts_data_into_scratch_buf() { - let code_get_storage = wabt::wat2wasm(CODE_GET_STORAGE).unwrap(); - let mut mock_ext = MockExt::default(); - mock_ext.storage.insert([0x11; 32].to_vec(), [0x22; 32].to_vec()); + mock_ext + .storage + .insert([0x11; 32].to_vec(), [0x22; 32].to_vec()); let mut return_buf = Vec::new(); execute( - &code_get_storage, + CODE_GET_STORAGE, &[], &mut return_buf, &mut mock_ext, - &Schedule::default(), &mut GasMeter::with_limit(50_000, 1), - ).unwrap(); + ) + .unwrap(); - assert_eq!( - return_buf, - [0x22; 32].to_vec(), - ); + assert_eq!(return_buf, [0x22; 32].to_vec()); } - /// calls `ext_caller`, loads the address from the scratch buffer and /// compares it with the constant 42. - const CODE_CALLER: &'static str = -r#" + const CODE_CALLER: &'static str = r#" (module (import "env" "ext_caller" (func $ext_caller)) (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) @@ -589,21 +603,125 @@ r#" ) ) ) + + (func (export "deploy")) ) "#; #[test] fn caller() { - let code_caller = wabt::wat2wasm(CODE_CALLER).unwrap(); + let mut mock_ext = MockExt::default(); + execute( + CODE_CALLER, + &[], + &mut Vec::new(), + &mut mock_ext, + &mut GasMeter::with_limit(50_000, 1), + ) + .unwrap(); + } + /// calls `ext_address`, loads the address from the scratch buffer and + /// compares it with the constant 69. + const CODE_ADDRESS: &'static str = r#" +(module + (import "env" "ext_address" (func $ext_address)) + (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) + (import "env" "ext_scratch_copy" (func $ext_scratch_copy (param i32 i32 i32))) + (import "env" "memory" (memory 1 1)) + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "call") + ;; fill the scratch buffer with the self address. + (call $ext_address) + + ;; assert $ext_scratch_size == 8 + (call $assert + (i32.eq + (call $ext_scratch_size) + (i32.const 8) + ) + ) + + ;; copy contents of the scratch buffer into the contract's memory. + (call $ext_scratch_copy + (i32.const 8) ;; Pointer in memory to the place where to copy. + (i32.const 0) ;; Offset from the start of the scratch buffer. + (i32.const 8) ;; Count of bytes to copy. + ) + + ;; assert that contents of the buffer is equal to the i64 value of 69. + (call $assert + (i64.eq + (i64.load + (i32.const 8) + ) + (i64.const 69) + ) + ) + ) + + (func (export "deploy")) +) +"#; + + #[test] + fn address() { let mut mock_ext = MockExt::default(); execute( - &code_caller, + CODE_ADDRESS, &[], &mut Vec::new(), &mut mock_ext, - &Schedule::::default(), &mut GasMeter::with_limit(50_000, 1), - ).unwrap(); + ) + .unwrap(); + } + + const CODE_RETURN_FROM_START_FN: &str = r#" +(module + (import "env" "ext_return" (func $ext_return (param i32 i32))) + (import "env" "memory" (memory 1 1)) + + (start $start) + (func $start + (call $ext_return + (i32.const 8) + (i32.const 4) + ) + (unreachable) + ) + + (func (export "call") + (unreachable) + ) + (func (export "deploy")) + + (data (i32.const 8) "\01\02\03\04") +) +"#; + + #[test] + fn return_from_start_fn() { + let mut mock_ext = MockExt::default(); + let mut output_data = Vec::new(); + execute( + CODE_RETURN_FROM_START_FN, + &[], + &mut output_data, + &mut mock_ext, + &mut GasMeter::with_limit(50_000, 1), + ) + .unwrap(); + + assert_eq!(output_data, vec![1, 2, 3, 4]); } } diff --git a/srml/contract/src/wasm/prepare.rs b/srml/contract/src/wasm/prepare.rs new file mode 100644 index 0000000000000..622e48e686307 --- /dev/null +++ b/srml/contract/src/wasm/prepare.rs @@ -0,0 +1,546 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! This module takes care of loading, checking and preprocessing of a +//! wasm module before execution. It also extracts some essential information +//! from a module. + +use parity_wasm::elements::{self, Internal, External, MemoryType, Type}; +use pwasm_utils; +use pwasm_utils::rules; +use rstd::prelude::*; +use runtime_primitives::traits::As; +use wasm::env_def::ImportSatisfyCheck; +use wasm::PrefabWasmModule; +use {Schedule, Trait}; + +struct ContractModule<'a, Gas: 'a> { + // An `Option` is used here for loaning (`take()`-ing) the module. + // Invariant: Can't be `None` (i.e. on enter and on exit from the function + // the value *must* be `Some`). + module: Option, + schedule: &'a Schedule, +} + +impl<'a, Gas: 'a + As + Clone> ContractModule<'a, Gas> { + fn new( + original_code: &[u8], + schedule: &'a Schedule, + ) -> Result, &'static str> { + let module = + elements::deserialize_buffer(original_code).map_err(|_| "can't decode wasm code")?; + Ok(ContractModule { + module: Some(module), + schedule, + }) + } + + /// Ensures that module doesn't declare internal memories. + /// + /// In this runtime we only allow wasm module to import memory from the environment. + /// Memory section contains declarations of internal linear memories, so if we find one + /// we reject such a module. + fn ensure_no_internal_memory(&self) -> Result<(), &'static str> { + let module = self + .module + .as_ref() + .expect("On entry to the function `module` can't be None; qed"); + if module + .memory_section() + .map_or(false, |ms| ms.entries().len() > 0) + { + return Err("module declares internal memory"); + } + Ok(()) + } + + fn inject_gas_metering(&mut self) -> Result<(), &'static str> { + let gas_rules = + rules::Set::new( + self.schedule.regular_op_cost.clone().as_(), + Default::default(), + ) + .with_grow_cost(self.schedule.grow_mem_cost.clone().as_()) + .with_forbidden_floats(); + + let module = self + .module + .take() + .expect("On entry to the function `module` can't be `None`; qed"); + + let contract_module = pwasm_utils::inject_gas_counter(module, &gas_rules) + .map_err(|_| "gas instrumentation failed")?; + + self.module = Some(contract_module); + Ok(()) + } + + fn inject_stack_height_metering(&mut self) -> Result<(), &'static str> { + let module = self + .module + .take() + .expect("On entry to the function `module` can't be `None`; qed"); + + let contract_module = + pwasm_utils::stack_height::inject_limiter(module, self.schedule.max_stack_height) + .map_err(|_| "stack height instrumentation failed")?; + + self.module = Some(contract_module); + Ok(()) + } + + /// Check that the module has required exported functions. For now + /// these are just entrypoints: + /// + /// - 'call' + /// - 'deploy' + fn scan_exports(&self) -> Result<(), &'static str> { + let mut deploy_found = false; + let mut call_found = false; + + let module = self + .module + .as_ref() + .expect("On entry to the function `module` can't be `None`; qed"); + + let types = module.type_section().map(|ts| ts.types()).unwrap_or(&[]); + let export_entries = module + .export_section() + .map(|is| is.entries()) + .unwrap_or(&[]); + let func_entries = module + .function_section() + .map(|fs| fs.entries()) + .unwrap_or(&[]); + + // Function index space consists of imported function following by + // declared functions. Calculate the total number of imported functions so + // we can use it to convert indexes from function space to declared function space. + let fn_space_offset = module + .import_section() + .map(|is| is.entries()) + .unwrap_or(&[]) + .iter() + .filter(|entry| { + match *entry.external() { + External::Function(_) => true, + _ => false, + } + }) + .count(); + + for export in export_entries { + match export.field() { + "call" => call_found = true, + "deploy" => deploy_found = true, + _ => continue, + } + + // Then check the export kind. "call" and "deploy" are + // functions. + let fn_idx = match export.internal() { + Internal::Function(ref fn_idx) => *fn_idx, + _ => return Err("expected a function"), + }; + + // convert index from function index space to declared index space. + let fn_idx = match fn_idx.checked_sub(fn_space_offset as u32) { + Some(fn_idx) => fn_idx, + None => { + // Underflow here means fn_idx points to imported function which we don't allow! + return Err("entry point points to an imported function"); + } + }; + + // Then check the signature. + // Both "call" and "deploy" has a () -> () function type. + let func_ty_idx = func_entries.get(fn_idx as usize) + .ok_or_else(|| "export refers to non-existent function")? + .type_ref(); + let Type::Function(ref func_ty) = types + .get(func_ty_idx as usize) + .ok_or_else(|| "function has a non-existent type")?; + if !(func_ty.params().is_empty() && func_ty.return_type().is_none()) { + return Err("entry point has wrong signature"); + } + } + + if !deploy_found { + return Err("deploy function isn't exported"); + } + if !call_found { + return Err("call function isn't exported"); + } + + Ok(()) + } + + /// Scan an import section if any. + /// + /// This accomplishes two tasks: + /// + /// - checks any imported function against defined host functions set, incl. + /// their signatures. + /// - if there is a memory import, returns it's descriptor + fn scan_imports(&self) -> Result, &'static str> { + let module = self + .module + .as_ref() + .expect("On entry to the function `module` can't be `None`; qed"); + + let types = module.type_section().map(|ts| ts.types()).unwrap_or(&[]); + let import_entries = module + .import_section() + .map(|is| is.entries()) + .unwrap_or(&[]); + + let mut imported_mem_type = None; + + for import in import_entries { + if import.module() != "env" { + // This import tries to import something from non-"env" module, + // but all imports are located in "env" at the moment. + return Err("module has imports from a non-'env' namespace"); + } + + let type_idx = match import.external() { + &External::Function(ref type_idx) => type_idx, + &External::Memory(ref memory_type) => { + imported_mem_type = Some(memory_type); + continue; + } + _ => continue, + }; + + let Type::Function(ref func_ty) = types + .get(*type_idx as usize) + .ok_or_else(|| "validation: import entry points to a non-existent type")?; + + if !C::can_satisfy(import.field().as_bytes(), func_ty) { + return Err("module imports a non-existent function"); + } + } + Ok(imported_mem_type) + } + + fn into_wasm_code(mut self) -> Result, &'static str> { + elements::serialize( + self.module + .take() + .expect("On entry to the function `module` can't be `None`; qed"), + ) + .map_err(|_| "error serializing instrumented module") + } +} + +/// Loads the given module given in `original_code`, performs some checks on it and +/// does some preprocessing. +/// +/// The checks are: +/// +/// - module doesn't define an internal memory instance, +/// - imported memory (if any) doesn't reserve more memory than permitted by the `schedule`, +/// - all imported functions from the external environment matches defined by `env` module, +/// +/// The preprocessing includes injecting code for gas metering and metering the height of stack. +pub fn prepare_contract( + original_code: &[u8], + schedule: &Schedule, +) -> Result { + let mut contract_module = ContractModule::new(original_code, schedule)?; + contract_module.scan_exports()?; + contract_module.ensure_no_internal_memory()?; + contract_module.inject_gas_metering()?; + contract_module.inject_stack_height_metering()?; + + struct MemoryDefinition { + initial: u32, + maximum: u32, + } + + let memory_def = if let Some(memory_type) = contract_module.scan_imports::()? { + // Inspect the module to extract the initial and maximum page count. + let limits = memory_type.limits(); + match (limits.initial(), limits.maximum()) { + (initial, Some(maximum)) if initial > maximum => { + return Err( + "Requested initial number of pages should not exceed the requested maximum", + ); + } + (_, Some(maximum)) if maximum > schedule.max_memory_pages => { + return Err("Maximum number of pages should not exceed the configured maximum."); + } + (initial, Some(maximum)) => MemoryDefinition { initial, maximum }, + (_, None) => { + // Maximum number of pages should be always declared. + // This isn't a hard requirement and can be treated as a maxiumum set + // to configured maximum. + return Err("Maximum number of pages should be always declared."); + } + } + } else { + // If none memory imported then just crate an empty placeholder. + // Any access to it will lead to out of bounds trap. + MemoryDefinition { + initial: 0, + maximum: 0, + } + }; + + Ok(PrefabWasmModule { + schedule_version: schedule.version, + initial: memory_def.initial, + maximum: memory_def.maximum, + _reserved: None, + code: contract_module.into_wasm_code()?, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fmt; + use tests::Test; + use exec::Ext; + use wabt; + + impl fmt::Debug for PrefabWasmModule { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "PreparedContract {{ .. }}") + } + } + + // Define test environment for tests. We need ImportSatisfyCheck + // implementation from it. So actual implementations doesn't matter. + define_env!(TestEnv, , + panic(_ctx) => { unreachable!(); }, + gas(_ctx, _amount: u32) => { unreachable!(); }, + ); + + macro_rules! prepare_test { + ($name:ident, $wat:expr, $($expected:tt)*) => { + #[test] + fn $name() { + let wasm = wabt::Wat2Wasm::new().validate(false).convert($wat).unwrap(); + let schedule = Schedule::::default(); + let r = prepare_contract::(wasm.as_ref(), &schedule); + assert_matches!(r, $($expected)*); + } + }; + } + + mod memories { + use super::*; + + // Tests below assumes that maximum page number is configured to a certain number. + #[test] + fn assume_memory_size() { + assert_eq!(Schedule::::default().max_memory_pages, 16); + } + + prepare_test!(memory_with_one_page, + r#" + (module + (import "env" "memory" (memory 1 1)) + + (func (export "call")) + (func (export "deploy")) + ) + "#, + Ok(_) + ); + + prepare_test!(internal_memory_declaration, + r#" + (module + (memory 1 1) + + (func (export "call")) + (func (export "deploy")) + ) + "#, + Err("module declares internal memory") + ); + + prepare_test!(no_memory_import, + r#" + (module + ;; no memory imported + + (func (export "call")) + (func (export "deploy")) + )"#, + Ok(_) + ); + + prepare_test!(initial_exceeds_maximum, + r#" + (module + (import "env" "memory" (memory 16 1)) + + (func (export "call")) + (func (export "deploy")) + ) + "#, + Err("Requested initial number of pages should not exceed the requested maximum") + ); + + prepare_test!(no_maximum, + r#" + (module + (import "env" "memory" (memory 1)) + + (func (export "call")) + (func (export "deploy")) + ) + "#, + Err("Maximum number of pages should be always declared.") + ); + + prepare_test!(requested_maximum_exceeds_configured_maximum, + r#" + (module + (import "env" "memory" (memory 1 17)) + + (func (export "call")) + (func (export "deploy")) + ) + "#, + Err("Maximum number of pages should not exceed the configured maximum.") + ); + } + + mod imports { + use super::*; + + prepare_test!(can_import_legit_function, + r#" + (module + (import "env" "gas" (func (param i32))) + + (func (export "call")) + (func (export "deploy")) + ) + "#, + Ok(_) + ); + + // nothing can be imported from non-"env" module for now. + prepare_test!(non_env_import, + r#" + (module + (import "another_module" "memory" (memory 1 1)) + + (func (export "call")) + (func (export "deploy")) + ) + "#, + Err("module has imports from a non-'env' namespace") + ); + + // wrong signature + prepare_test!(wrong_signature, + r#" + (module + (import "env" "gas" (func (param i64))) + + (func (export "call")) + (func (export "deploy")) + ) + "#, + Err("module imports a non-existent function") + ); + + prepare_test!(unknown_func_name, + r#" + (module + (import "env" "unknown_func" (func)) + + (func (export "call")) + (func (export "deploy")) + ) + "#, + Err("module imports a non-existent function") + ); + } + + mod entrypoints { + use super::*; + + prepare_test!(it_works, + r#" + (module + (func (export "call")) + (func (export "deploy")) + ) + "#, + Ok(_) + ); + + prepare_test!(omit_deploy, + r#" + (module + (func (export "call")) + ) + "#, + Err("deploy function isn't exported") + ); + + prepare_test!(omit_call, + r#" + (module + (func (export "deploy")) + ) + "#, + Err("call function isn't exported") + ); + + // Try to use imported function as an entry point. + prepare_test!(try_sneak_export_as_entrypoint, + r#" + (module + (import "env" "panic" (func)) + + (func (export "deploy")) + + (export "call" (func 0)) + ) + "#, + Err("entry point points to an imported function") + ); + + // Try to use imported function as an entry point. + prepare_test!(try_sneak_export_as_global, + r#" + (module + (func (export "deploy")) + (global (export "call") i32 (i32.const 0)) + ) + "#, + Err("expected a function") + ); + + prepare_test!(wrong_signature, + r#" + (module + (func (export "deploy")) + (func (export "call") (param i32)) + ) + "#, + Err("entry point has wrong signature") + ); + } +} diff --git a/srml/contract/src/vm/runtime.rs b/srml/contract/src/wasm/runtime.rs similarity index 72% rename from srml/contract/src/vm/runtime.rs rename to srml/contract/src/wasm/runtime.rs index d32808635957b..33154fc429e57 100644 --- a/srml/contract/src/vm/runtime.rs +++ b/srml/contract/src/wasm/runtime.rs @@ -16,16 +16,16 @@ //! Environment definition of the wasm smart-contract runtime. -use super::{BalanceOf, Schedule, CreateReceipt, Error, Ext}; +use super::{Schedule}; +use exec::{Ext, BalanceOf, VmExecResult, OutputBuf, EmptyOutputBuf, CallReceipt, InstantiateReceipt}; use rstd::prelude::*; +use rstd::mem; use codec::{Decode, Encode}; -use gas::{GasMeter, GasMeterResult}; -use runtime_primitives::traits::{As, CheckedMul}; +use gas::{GasMeter, Token, GasMeterResult}; +use runtime_primitives::traits::{As, CheckedMul, Bounded}; use sandbox; use system; -use Trait; - -type GasOf = <::T as Trait>::Gas; +use {Trait, CodeHash}; /// Enumerates all possible *special* trap conditions. /// @@ -33,13 +33,16 @@ type GasOf = <::T as Trait>::Gas; /// to just terminate quickly in some cases. enum SpecialTrap { /// Signals that trap was generated in response to call `ext_return` host function. - Return, + Return(OutputBuf), } +/// Can only be used for one call. pub(crate) struct Runtime<'a, 'data, E: Ext + 'a> { ext: &'a mut E, input_data: &'data [u8], - output_data: &'data mut Vec, + // A VM can return a result only once and only by value. So + // we wrap output buffer to make it possible to take the buffer out. + empty_output_buf: Option, scratch_buf: Vec, schedule: &'a Schedule<::Gas>, memory: sandbox::Memory, @@ -50,7 +53,7 @@ impl<'a, 'data, E: Ext + 'a> Runtime<'a, 'data, E> { pub(crate) fn new( ext: &'a mut E, input_data: &'data [u8], - output_data: &'data mut Vec, + empty_output_buf: EmptyOutputBuf, schedule: &'a Schedule<::Gas>, memory: sandbox::Memory, gas_meter: &'a mut GasMeter, @@ -58,7 +61,7 @@ impl<'a, 'data, E: Ext + 'a> Runtime<'a, 'data, E> { Runtime { ext, input_data, - output_data, + empty_output_buf: Some(empty_output_buf), scratch_buf: Vec::new(), schedule, memory, @@ -75,30 +78,68 @@ impl<'a, 'data, E: Ext + 'a> Runtime<'a, 'data, E> { pub(crate) fn to_execution_result( runtime: Runtime, sandbox_err: Option, -) -> Result<(), Error> { +) -> VmExecResult { // Check the exact type of the error. It could be plain trap or // special runtime trap the we must recognize. match (sandbox_err, runtime.special_trap) { // No traps were generated. Proceed normally. - (None, None) => Ok(()), + (None, None) => VmExecResult::Ok, // Special case. The trap was the result of the execution `return` host function. - (Some(sandbox::Error::Execution), Some(SpecialTrap::Return)) => Ok(()), + (Some(sandbox::Error::Execution), Some(SpecialTrap::Return(buf))) => VmExecResult::Returned(buf), // Any other kind of a trap should result in a failure. - (Some(_), _) => Err(Error::Invoke), + (Some(_), _) => VmExecResult::Trap("during execution"), // Any other case (such as special trap flag without actual trap) signifies // a logic error. _ => unreachable!(), } } -/// Charge the specified amount of gas. +#[cfg_attr(test, derive(Debug, PartialEq, Eq))] +#[derive(Copy, Clone)] +pub enum RuntimeToken { + /// Explicit call to the `gas` function. Charge the gas meter + /// with the value provided. + Explicit(u32), + /// The given number of bytes is read from the sandbox memory. + ReadMemory(u32), + /// The given number of bytes is written to the sandbox memory. + WriteMemory(u32), + /// The given number of bytes is read from the sandbox memory and + /// is returned as the return data buffer of the call. + ReturnData(u32), +} + +impl Token for RuntimeToken { + type Metadata = Schedule; + + fn calculate_amount(&self, metadata: &Schedule) -> T::Gas { + use self::RuntimeToken::*; + let value = match *self { + Explicit(amount) => Some(>::sa(amount)), + ReadMemory(byte_count) => metadata + .sandbox_data_read_cost + .checked_mul(&>::sa(byte_count)), + WriteMemory(byte_count) => metadata + .sandbox_data_write_cost + .checked_mul(&>::sa(byte_count)), + ReturnData(byte_count) => metadata + .return_data_per_byte_cost + .checked_mul(&>::sa(byte_count)), + }; + + value.unwrap_or_else(|| Bounded::max_value()) + } +} + +/// Charge the gas meter with the specified token. /// -/// Returns `Err` if there is not enough gas. -fn charge_gas( +/// Returns `Err(HostError)` if there is not enough gas. +fn charge_gas>( gas_meter: &mut GasMeter, - amount: T::Gas, + metadata: &Tok::Metadata, + token: Tok, ) -> Result<(), sandbox::HostError> { - match gas_meter.charge(amount) { + match gas_meter.charge(metadata, token) { GasMeterResult::Proceed => Ok(()), GasMeterResult::OutOfGas => Err(sandbox::HostError), } @@ -117,10 +158,7 @@ fn read_sandbox_memory( ptr: u32, len: u32, ) -> Result, sandbox::HostError> { - let price = (ctx.schedule.sandbox_data_read_cost) - .checked_mul(& as As>::sa(len)) - .ok_or(sandbox::HostError)?; - charge_gas(ctx.gas_meter, price)?; + charge_gas(ctx.gas_meter, ctx.schedule, RuntimeToken::ReadMemory(len))?; let mut buf = Vec::new(); buf.resize(len as usize, 0); @@ -139,16 +177,13 @@ fn read_sandbox_memory( /// - out of gas /// - designated area is not within the bounds of the sandbox memory. fn write_sandbox_memory( - per_byte_cost: T::Gas, + schedule: &Schedule, gas_meter: &mut GasMeter, memory: &sandbox::Memory, ptr: u32, buf: &[u8], ) -> Result<(), sandbox::HostError> { - let price = per_byte_cost - .checked_mul(&>::sa(buf.len() as u32)) - .ok_or(sandbox::HostError)?; - charge_gas(gas_meter, price)?; + charge_gas(gas_meter, schedule, RuntimeToken::WriteMemory(buf.len() as u32))?; memory.set(ptr, buf)?; @@ -163,15 +198,13 @@ fn write_sandbox_memory( // Define a function `fn init_env() -> HostFunctionSet` that returns // a function set which can be imported by an executed contract. -define_env!(init_env, , +define_env!(Env, , // Account for used gas. Traps if gas used is greater than gas limit. // // - amount: How much gas is used. gas(ctx, amount: u32) => { - let amount = <::Gas as As>::sa(amount); - charge_gas(&mut ctx.gas_meter, amount)?; - + charge_gas(&mut ctx.gas_meter, ctx.schedule, RuntimeToken::Explicit(amount))?; Ok(()) }, @@ -251,8 +284,10 @@ define_env!(init_env, , }; let input_data = read_sandbox_memory(ctx, input_data_ptr, input_data_len)?; - // Clear the scratch buffer in any case. - ctx.scratch_buf.clear(); + // Grab the scratch buffer and put in its' place an empty one. + // We will use it for creating `EmptyOutputBuf` container for the call. + let scratch_buf = mem::replace(&mut ctx.scratch_buf, Vec::new()); + let empty_output_buf = EmptyOutputBuf::from_spare_vec(scratch_buf); let nested_gas_limit = if gas == 0 { ctx.gas_meter.gas_left() @@ -260,22 +295,33 @@ define_env!(init_env, , <::Gas as As>::sa(gas) }; let ext = &mut ctx.ext; - let scratch_buf = &mut ctx.scratch_buf; let call_outcome = ctx.gas_meter.with_nested(nested_gas_limit, |nested_meter| { match nested_meter { - Some(nested_meter) => ext.call(&callee, value, nested_meter, &input_data, scratch_buf), + Some(nested_meter) => { + ext.call( + &callee, + value, + nested_meter, + &input_data, + empty_output_buf + ) + .map_err(|_| ()) + } // there is not enough gas to allocate for the nested call. None => Err(()), } }); match call_outcome { - Ok(()) => Ok(0), + Ok(CallReceipt { output_data }) => { + ctx.scratch_buf = output_data; + Ok(0) + }, Err(_) => Ok(1), } }, - // Create a contract with code returned by the specified initializer code. + // Instantiate a contract with code returned by the specified initializer code. // // This function creates an account and executes initializer code. After the execution, // the returned buffer is saved as the code of the created account. @@ -302,7 +348,10 @@ define_env!(init_env, , input_data_ptr: u32, input_data_len: u32 ) -> u32 => { - let init_code = read_sandbox_memory(ctx, init_code_ptr, init_code_len)?; + let code_hash = { + let code_hash_buf = read_sandbox_memory(ctx, init_code_ptr, init_code_len)?; + ::T>>::decode(&mut &code_hash_buf[..]).ok_or_else(|| sandbox::HostError)? + }; let value = { let value_buf = read_sandbox_memory(ctx, value_ptr, value_len)?; BalanceOf::<::T>::decode(&mut &value_buf[..]) @@ -319,15 +368,23 @@ define_env!(init_env, , <::Gas as As>::sa(gas) }; let ext = &mut ctx.ext; - let create_outcome = ctx.gas_meter.with_nested(nested_gas_limit, |nested_meter| { + let instantiate_outcome = ctx.gas_meter.with_nested(nested_gas_limit, |nested_meter| { match nested_meter { - Some(nested_meter) => ext.create(&init_code, value, nested_meter, &input_data), + Some(nested_meter) => { + ext.instantiate( + &code_hash, + value, + nested_meter, + &input_data + ) + .map_err(|_| ()) + } // there is not enough gas to allocate for the nested call. None => Err(()), } }); - match create_outcome { - Ok(CreateReceipt { address }) => { + match instantiate_outcome { + Ok(InstantiateReceipt { address }) => { // Write the address to the scratch buffer. address.encode_to(&mut ctx.scratch_buf); Ok(0) @@ -339,20 +396,34 @@ define_env!(init_env, , // Save a data buffer as a result of the execution, terminate the execution and return a // successful result to the caller. ext_return(ctx, data_ptr: u32, data_len: u32) => { - let data_len_in_gas = <::Gas as As>::sa(data_len as u64); - let price = (ctx.schedule.return_data_per_byte_cost) - .checked_mul(&data_len_in_gas) - .ok_or(sandbox::HostError)?; - - match ctx.gas_meter.charge(price) { + match ctx + .gas_meter + .charge( + ctx.schedule, + RuntimeToken::ReturnData(data_len) + ) + { GasMeterResult::Proceed => (), GasMeterResult::OutOfGas => return Err(sandbox::HostError), } - ctx.output_data.resize(data_len as usize, 0); - ctx.memory.get(data_ptr, &mut ctx.output_data)?; - - ctx.special_trap = Some(SpecialTrap::Return); + let empty_output_buf = ctx + .empty_output_buf + .take() + .expect( + "`empty_output_buf` is taken only here; + `ext_return` traps; + `Runtime` can only be used only for one execution; + qed" + ); + let output_buf = empty_output_buf.fill( + data_len as usize, + |slice_mut| { + // Read the memory at the specified pointer to the provided slice. + ctx.memory.get(data_ptr, slice_mut) + } + )?; + ctx.special_trap = Some(SpecialTrap::Return(output_buf)); // The trap mechanism is used to immediately terminate the execution. // This trap should be handled appropriately before returning the result @@ -370,6 +441,12 @@ define_env!(init_env, , Ok(()) }, + // Stores the address of the current contract into the scratch buffer. + ext_address(ctx) => { + ctx.scratch_buf = ctx.ext.address().encode(); + Ok(()) + }, + // Returns the size of the input buffer. ext_input_size(ctx) -> u32 => { Ok(ctx.input_data.len() as u32) @@ -392,7 +469,7 @@ define_env!(init_env, , // Finally, perform the write. write_sandbox_memory( - ctx.schedule.sandbox_data_write_cost, + ctx.schedule, ctx.gas_meter, &ctx.memory, dest_ptr, @@ -424,7 +501,7 @@ define_env!(init_env, , // Finally, perform the write. write_sandbox_memory( - ctx.schedule.sandbox_data_write_cost, + ctx.schedule, ctx.gas_meter, &ctx.memory, dest_ptr,