diff --git a/Cargo.lock b/Cargo.lock index c49662d9772d4..a72b5e07946a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2795,6 +2795,7 @@ dependencies = [ name = "substrate-runtime-sandbox" version = "0.1.0" dependencies = [ + "assert_matches 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", "substrate-primitives 0.1.0", diff --git a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm index 3bd4f0d765cfd..13f21018e4b30 100644 Binary files a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm and b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm differ diff --git a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm index b8a70756300b0..18a824ea0eb23 100755 Binary files a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm and b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm differ diff --git a/substrate/runtime-sandbox/Cargo.toml b/substrate/runtime-sandbox/Cargo.toml index c9a7dc0ae1b3c..b80cb4443f8ae 100755 --- a/substrate/runtime-sandbox/Cargo.toml +++ b/substrate/runtime-sandbox/Cargo.toml @@ -16,6 +16,7 @@ substrate-codec = { path = "../codec", default_features = false } [dev-dependencies] wabt = "0.4" +assert_matches = "1.1" [features] default = ["std"] diff --git a/substrate/runtime-sandbox/src/lib.rs b/substrate/runtime-sandbox/src/lib.rs index f9195c10efe46..2556608047085 100755 --- a/substrate/runtime-sandbox/src/lib.rs +++ b/substrate/runtime-sandbox/src/lib.rs @@ -29,8 +29,11 @@ //! When this crate is used in `std` environment all these functions are implemented by directly //! calling wasm VM. //! -//! Typical use-case for this library might be used for implementing smart-contract runtimes -//! which uses wasm for contract code. +//! Example of possible use-cases for this library are following: +//! +//! - implementing smart-contract runtimes which uses wasm for contract code +//! - executing wasm substrate runtime inside of a wasm parachain +//! - etc #![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] @@ -47,6 +50,10 @@ extern crate substrate_primitives as primitives; #[cfg(test)] extern crate wabt; +#[cfg(test)] +#[macro_use] +extern crate assert_matches; + use rstd::prelude::*; pub use primitives::sandbox::{TypedValue, ReturnValue, HostError}; @@ -149,6 +156,11 @@ impl EnvironmentDefinitionBuilder { } /// Register a host function in this environment defintion. + /// + /// NOTE that there is no constraints on type of this function. An instance + /// can import function passed here with any signature it wants. It can even import + /// the same function (i.e. with same `module` and `field`) several times. It's up to + /// the user code to check or constrain the types of signatures. pub fn add_host_func(&mut self, module: N1, field: N2, f: HostFuncType) where N1: Into>, diff --git a/substrate/runtime-sandbox/with_std.rs b/substrate/runtime-sandbox/with_std.rs index 70ebc793e20e5..e4e4aa131ea34 100755 --- a/substrate/runtime-sandbox/with_std.rs +++ b/substrate/runtime-sandbox/with_std.rs @@ -309,7 +309,7 @@ impl Instance { #[cfg(test)] mod tests { use wabt; - use ::{TypedValue, ReturnValue, HostError, EnvironmentDefinitionBuilder, Instance}; + use ::{Error, TypedValue, ReturnValue, HostError, EnvironmentDefinitionBuilder, Instance}; fn execute_sandboxed(code: &[u8], args: &[TypedValue]) -> Result { struct State { @@ -335,12 +335,20 @@ mod tests { e.counter += inc_by as u32; Ok(ReturnValue::Value(TypedValue::I32(e.counter as i32))) } + /// Function that takes one argument of any type and returns that value. + fn env_polymorphic_id(_e: &mut State, args: &[TypedValue]) -> Result { + if args.len() != 1 { + return Err(HostError); + } + Ok(ReturnValue::Value(args[0])) + } let mut state = State { counter: 0 }; let mut env_builder = EnvironmentDefinitionBuilder::new(); env_builder.add_host_func("env", "assert", env_assert); env_builder.add_host_func("env", "inc_counter", env_inc_counter); + env_builder.add_host_func("env", "polymorphic_id", env_polymorphic_id); let mut instance = Instance::new(code, &env_builder, &mut state)?; let result = instance.invoke(b"call", args, &mut state); @@ -404,4 +412,71 @@ mod tests { ).unwrap(); assert_eq!(return_val, ReturnValue::Value(TypedValue::I32(0x1337))); } + + #[test] + fn signatures_dont_matter() { + let code = wabt::wat2wasm(r#" + (module + (import "env" "polymorphic_id" (func $id_i32 (param i32) (result i32))) + (import "env" "polymorphic_id" (func $id_i64 (param i64) (result i64))) + (import "env" "assert" (func $assert (param i32))) + + (func (export "call") + ;; assert that we can actually call the "same" function with different + ;; signatures. + (call $assert + (i32.eq + (call $id_i32 + (i32.const 0x012345678) + ) + (i32.const 0x012345678) + ) + ) + (call $assert + (i64.eq + (call $id_i64 + (i64.const 0x0123456789abcdef) + ) + (i64.const 0x0123456789abcdef) + ) + ) + ) + ) + "#).unwrap(); + + let return_val = execute_sandboxed(&code, &[]).unwrap(); + assert_eq!(return_val, ReturnValue::Unit); + } + + #[test] + fn cant_return_unmatching_type() { + fn env_returns_i32(_e: &mut (), _args: &[TypedValue]) -> Result { + Ok(ReturnValue::Value(TypedValue::I32(42))) + } + + let mut env_builder = EnvironmentDefinitionBuilder::new(); + env_builder.add_host_func("env", "returns_i32", env_returns_i32); + + let code = wabt::wat2wasm(r#" + (module + ;; It's actually returns i32, but imported as if it returned i64 + (import "env" "returns_i32" (func $returns_i32 (result i64))) + + (func (export "call") + (drop + (call $returns_i32) + ) + ) + ) + "#).unwrap(); + + // It succeeds since we are able to import functions with types we want. + let mut instance = Instance::new(&code, &env_builder, &mut ()).unwrap(); + + // But this fails since we imported a function that returns i32 as if it returned i64. + assert_matches!( + instance.invoke(b"call", &[], &mut ()), + Err(Error::Execution) + ); + } } diff --git a/substrate/runtime/contract/src/exec.rs b/substrate/runtime/contract/src/exec.rs index 7755582245cce..a62e2ce97f972 100644 --- a/substrate/runtime/contract/src/exec.rs +++ b/substrate/runtime/contract/src/exec.rs @@ -226,7 +226,9 @@ struct CallContext<'a, 'b: 'a, T: Trait + 'b> { _caller: T::AccountId, } -impl<'a, 'b: 'a, T: Trait + 'b> vm::Ext for CallContext<'a, 'b, T> { +impl<'a, 'b: 'a, T: Trait + 'b> vm::Ext for CallContext<'a, 'b, T> { + type T = T; + fn get_storage(&self, key: &[u8]) -> Option> { self.ctx.overlay.get_storage(&self.ctx.self_account, key) } diff --git a/substrate/runtime/contract/src/vm.rs b/substrate/runtime/contract/src/vm.rs deleted file mode 100644 index 1e594883eeb15..0000000000000 --- a/substrate/runtime/contract/src/vm.rs +++ /dev/null @@ -1,762 +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 . - -//! Crate for executing smart-contracts. -//! -//! It provides an means for executing contracts represented in WebAssembly (Wasm for short). -//! Contracts are able to create other contracts, transfer funds -//! to each other and operate on a simple key-value storage. - -use codec::Decode; -use parity_wasm::elements::{self, External, MemoryType}; -use pwasm_utils; -use pwasm_utils::rules; -use rstd::prelude::*; -use sandbox; -use gas::{GasMeter, GasMeterResult}; -use runtime_primitives::traits::{As, CheckedMul}; -use {Trait}; -use exec::{CallReceipt, CreateReceipt}; - -/// 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 { - /// 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>); - - // TODO: Return the address of the created contract. - /// Create a new account for a contract. - /// - /// 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: T::Balance, - gas_meter: &mut GasMeter, - data: &[u8], - ) -> Result, ()>; - - /// Call (possibly transfering some amount of funds) into the specified account. - fn call( - &mut self, - to: &T::AccountId, - value: T::Balance, - gas_meter: &mut GasMeter, - data: &[u8], - ) -> Result; -} - -/// 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, - - /// Gas instrumentation failed. - /// - /// This most likely indicates the module isn't valid. - GasInstrumentation, - - /// Stack instrumentation failed. - /// - /// This most likely indicates the module isn't valid. - StackHeightInstrumentation, - - /// Error happened during invocation of the contract's entrypoint. - /// - /// Most likely because of trap. - Invoke, - - /// Error happened during instantiation. - /// - /// This might indicate that `start` function trapped, or module isn't - /// instantiable and/or unlinkable. - Instantiate, - - /// Memory creation error. - /// - /// This might happen when the memory import has invalid descriptor or - /// requested too much resources. - Memory, -} - -/// Enumerates all possible *special* trap conditions. -/// -/// In this runtime traps used not only for signaling about errors but also -/// to just terminate quickly in some cases. -enum SpecialTrap { - // TODO: Can we pass wrapped memory instance instead of copying? - /// Signals that trap was generated in response to call `ext_return` host function. - Return(Vec), -} - -struct Runtime<'a, T: Trait + 'a, E: Ext + 'a> { - ext: &'a mut E, - config: &'a Config, - memory: sandbox::Memory, - gas_meter: &'a mut GasMeter, - special_trap: Option, -} -impl<'a, T: Trait, E: Ext + 'a> Runtime<'a, T, E> { - fn memory(&self) -> &sandbox::Memory { - &self.memory - } - /// Save a data buffer as a result of the execution. - /// - /// This function also charges gas for the returning. - /// - /// Returns `Err` if there is not enough gas. - fn store_return_data(&mut self, data: Vec) -> Result<(), ()> { - let data_len = >::sa(data.len() as u64); - let price = (self.config.return_data_per_byte_cost) - .checked_mul(&data_len) - .ok_or_else(|| ())?; - - match self.gas_meter.charge(price) { - GasMeterResult::Proceed => { - self.special_trap = Some(SpecialTrap::Return(data)); - Ok(()) - } - GasMeterResult::OutOfGas => Err(()), - } - } -} - -fn to_execution_result>( - runtime: Runtime, - run_err: Option, -) -> Result { - // Check the exact type of the error. It could be plain trap or - // special runtime trap the we must recognize. - let return_data = match (run_err, runtime.special_trap) { - // No traps were generated. Proceed normally. - (None, None) => Vec::new(), - // Special case. The trap was the result of the execution `return` host function. - (Some(sandbox::Error::Execution), Some(SpecialTrap::Return(rd))) => rd, - // Any other kind of a trap should result in a failure. - (Some(_), _) => return Err(Error::Invoke), - // Any other case (such as special trap flag without actual trap) signifies - // a logic error. - _ => unreachable!(), - }; - - Ok(ExecutionResult { - return_data, - }) -} - -/// The result of execution of a smart-contract. -#[derive(PartialEq, Eq)] -#[cfg_attr(feature = "std", derive(Debug))] -pub struct ExecutionResult { - /// The result produced by the execution of the contract. - /// - /// The contract can designate some buffer at the execution time via a special function. - /// If contract called this function with non-empty buffer it will be copied here. - /// - /// Note that gas is already charged for returning the data. - pub return_data: Vec, -} - -/// Execute the given code as a contract. -pub fn execute<'a, T: Trait, E: Ext>( - code: &[u8], - ext: &'a mut E, - gas_meter: &mut GasMeter, -) -> Result { - // TODO: Receive data as an argument - - // ext_gas(amount: u32) - // - // Account for used gas. Traps if gas used is greater than gas limit. - // - // - amount: How much gas is used. - fn ext_gas>( - e: &mut Runtime, - args: &[sandbox::TypedValue], - ) -> Result { - let amount = args[0].as_i32().unwrap() as u32; - let amount = >::sa(amount); - - match e.gas_meter.charge(amount) { - GasMeterResult::Proceed => Ok(sandbox::ReturnValue::Unit), - GasMeterResult::OutOfGas => Err(sandbox::HostError), - } - } - - // ext_put_storage(location_ptr: u32, value_non_null: u32, value_ptr: u32); - // - // Change the value at the given location in storage or remove it. - // - // - location_ptr: pointer into the linear - // memory where the location of the requested value is placed. - // - value_non_null: if set to 0, then the entry - // at the given location will be removed. - // - value_ptr: pointer into the linear memory - // where the value to set is placed. If `value_non_null` is set to 0, then this parameter is ignored. - fn ext_set_storage>( - e: &mut Runtime, - args: &[sandbox::TypedValue], - ) -> Result { - let location_ptr = args[0].as_i32().unwrap() as u32; - let value_non_null = args[1].as_i32().unwrap() as u32; - let value_ptr = args[2].as_i32().unwrap() as u32; - - let mut location = [0; 32]; - - e.memory().get(location_ptr, &mut location)?; - - let value = if value_non_null != 0 { - let mut value = [0; 32]; - e.memory().get(value_ptr, &mut value)?; - Some(value.to_vec()) - } else { - None - }; - e.ext.set_storage(&location, value); - - Ok(sandbox::ReturnValue::Unit) - } - - // ext_get_storage(location_ptr: u32, dest_ptr: u32); - // - // Retrieve the value at the given location from the strorage. - // If there is no entry at the given location then all-zero-value - // will be returned. - // - // - location_ptr: pointer into the linear - // memory where the location of the requested value is placed. - // - dest_ptr: pointer where contents of the specified storage location - // should be placed. - fn ext_get_storage>( - e: &mut Runtime, - args: &[sandbox::TypedValue], - ) -> Result { - let location_ptr = args[0].as_i32().unwrap() as u32; - let dest_ptr = args[1].as_i32().unwrap() as u32; - - let mut location = [0; 32]; - e.memory().get(location_ptr, &mut location)?; - - if let Some(value) = e.ext.get_storage(&location) { - e.memory().set(dest_ptr, &value)?; - } else { - e.memory().set(dest_ptr, &[0u8; 32])?; - } - - Ok(sandbox::ReturnValue::Unit) - } - - // ext_transfer(transfer_to: u32, transfer_to_len: u32, value_ptr: u32, value_len: u32) - fn ext_transfer>( - e: &mut Runtime, - args: &[sandbox::TypedValue], - ) -> Result { - let transfer_to_ptr = args[0].as_i32().unwrap() as u32; - let transfer_to_len = args[1].as_i32().unwrap() as u32; - let value_ptr = args[2].as_i32().unwrap() as u32; - let value_len = args[3].as_i32().unwrap() as u32; - - let mut transfer_to = Vec::new(); - transfer_to.resize(transfer_to_len as usize, 0); - e.memory().get(transfer_to_ptr, &mut transfer_to)?; - let transfer_to = T::AccountId::decode(&mut &transfer_to[..]).unwrap(); - - let mut value_buf = Vec::new(); - value_buf.resize(value_len as usize, 0); - e.memory().get(value_ptr, &mut value_buf)?; - let value = T::Balance::decode(&mut &value_buf[..]).unwrap(); - - // TODO: Read input data from memory. - let input_data = Vec::new(); - - // TODO: Let user to choose how much gas to allocate for the execution. - let nested_gas_limit = e.gas_meter.gas_left(); - let ext = &mut e.ext; - let call_outcome = e.gas_meter.with_nested(nested_gas_limit, |nested_meter| { - match nested_meter { - Some(nested_meter) => ext.call(&transfer_to, value, nested_meter, &input_data), - // there is not enough gas to allocate for the nested call. - None => Err(()), - } - }); - - match call_outcome { - // TODO: Find a way how to pass return_data back to the this sandbox. - Ok(CallReceipt { .. }) => Ok(sandbox::ReturnValue::Unit), - // TODO: Return a status code value that can be handled by the caller instead of a trap. - Err(_) => Err(sandbox::HostError), - } - } - - // ext_create(code_ptr: u32, code_len: u32, value_ptr: u32, value_len: u32) - fn ext_create>( - e: &mut Runtime, - args: &[sandbox::TypedValue], - ) -> Result { - let code_ptr = args[0].as_i32().unwrap() as u32; - let code_len = args[1].as_i32().unwrap() as u32; - let value_ptr = args[2].as_i32().unwrap() as u32; - let value_len = args[3].as_i32().unwrap() as u32; - - let mut value_buf = Vec::new(); - value_buf.resize(value_len as usize, 0); - e.memory().get(value_ptr, &mut value_buf)?; - let value = T::Balance::decode(&mut &value_buf[..]).unwrap(); - - let mut code = Vec::new(); - code.resize(code_len as usize, 0u8); - e.memory().get(code_ptr, &mut code)?; - - // TODO: Read input data from the sandbox. - let input_data = Vec::new(); - - // TODO: Let user to choose how much gas to allocate for the execution. - let nested_gas_limit = e.gas_meter.gas_left(); - let ext = &mut e.ext; - let create_outcome = e.gas_meter.with_nested(nested_gas_limit, |nested_meter| { - match nested_meter { - Some(nested_meter) => ext.create(&code, value, nested_meter, &input_data), - // there is not enough gas to allocate for the nested call. - None => Err(()), - } - }); - - match create_outcome { - // TODO: Copy an address of the created contract in the sandbox. - Ok(CreateReceipt { .. }) => Ok(sandbox::ReturnValue::Unit), - // TODO: Return a status code value that can be handled by the caller instead of a trap. - Err(_) => Err(sandbox::HostError), - } - } - - // ext_return(data_ptr: u32, data_len: u32) -> ! - fn ext_return>( - e: &mut Runtime, - args: &[sandbox::TypedValue], - ) -> Result { - let data_ptr = args[0].as_i32().unwrap() as u32; - let data_len = args[1].as_i32().unwrap() as u32; - - let mut data_buf = Vec::new(); - data_buf.resize(data_len as usize, 0); - e.memory().get(data_ptr, &mut data_buf)?; - - e.store_return_data(data_buf) - .map_err(|_| sandbox::HostError)?; - - // The trap mechanism is used to immediately terminate the execution. - // This trap should be handled appropriately before returning the result - // to the user of this crate. - Err(sandbox::HostError) - } - - let config = Config::default(); - - let PreparedContract { - instrumented_code, - memory, - } = prepare_contract(code, &config)?; - - let mut imports = sandbox::EnvironmentDefinitionBuilder::new(); - imports.add_host_func("env", "gas", ext_gas::); - imports.add_host_func("env", "ext_set_storage", ext_set_storage::); - imports.add_host_func("env", "ext_get_storage", ext_get_storage::); - // TODO: Rename it to ext_call. - imports.add_host_func("env", "ext_transfer", ext_transfer::); - imports.add_host_func("env", "ext_create", ext_create::); - imports.add_host_func("env", "ext_return", ext_return::); - // TODO: ext_balance, ext_address, ext_callvalue, etc. - imports.add_memory("env", "memory", memory.clone()); - - let mut runtime = Runtime { - ext, - config: &config, - memory, - gas_meter, - special_trap: None, - }; - - let mut instance = sandbox::Instance::new(&instrumented_code, &imports, &mut runtime) - .map_err(|_| Error::Instantiate)?; - - let run_result = instance.invoke(b"call", &[], &mut runtime); - - to_execution_result(runtime, run_result.err()) -} - -// TODO: Extract it to the root of the crate -#[derive(Clone)] -struct Config { - /// Gas cost of a growing memory by single page. - grow_mem_cost: T::Gas, - - /// Gas cost of a regular operation. - regular_op_cost: T::Gas, - - /// Gas cost per one byte returned. - return_data_per_byte_cost: T::Gas, - - /// How tall the stack is allowed to grow? - /// - /// See https://wiki.parity.io/WebAssembly-StackHeight to find out - /// how the stack frame cost is calculated. - max_stack_height: u32, - - //// What is the maximal memory pages amount is allowed to have for - /// a contract. - max_memory_pages: u32, -} - -impl Default for Config { - fn default() -> Config { - Config { - grow_mem_cost: T::Gas::sa(1), - regular_op_cost: T::Gas::sa(1), - return_data_per_byte_cost: T::Gas::sa(1), - max_stack_height: 64 * 1024, - max_memory_pages: 16, - } - } -} - -struct ContractModule<'a, T: Trait + '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, - config: &'a Config, -} - -impl<'a, T: Trait> ContractModule<'a, T> { - fn new(original_code: &[u8], config: &'a Config) -> Result, Error> { - let module = - elements::deserialize_buffer(original_code).map_err(|_| Error::Deserialization)?; - Ok(ContractModule { - module: Some(module), - config, - }) - } - - /// 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.config.regular_op_cost.as_(), Default::default()) - .with_grow_cost(self.config.grow_mem_cost.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.config.max_stack_height) - .map_err(|_| Error::StackHeightInstrumentation)?; - - self.module = Some(contract_module); - Ok(()) - } - - /// Find the memory import entry and return it's descriptor. - fn find_mem_import(&self) -> Option<&MemoryType> { - let import_section = self - .module - .as_ref() - .expect("On entry to the function `module` can't be `None`; qed") - .import_section()?; - for import in import_section.entries() { - if let ("env", "memory", &External::Memory(ref memory_type)) = - (import.module(), import.field(), import.external()) - { - return Some(memory_type); - } - } - None - } - - 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) - } -} - -struct PreparedContract { - instrumented_code: Vec, - memory: sandbox::Memory, -} - -fn prepare_contract(original_code: &[u8], config: &Config) -> Result { - let mut contract_module = ContractModule::new(original_code, config)?; - contract_module.ensure_no_internal_memory()?; - contract_module.inject_gas_metering()?; - contract_module.inject_stack_height_metering()?; - - // Inspect the module to extract the initial and maximum page count. - let memory = if let Some(memory_type) = contract_module.find_mem_import() { - 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 > config.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::collections::HashMap; - use std::fmt; - use wabt; - use gas::GasMeter; - use ::tests::Test; - - #[derive(Debug, PartialEq, Eq)] - struct CreateEntry { - code: Vec, - endowment: u64, - data: Vec, - } - #[derive(Debug, PartialEq, Eq)] - struct TransferEntry { - to: u64, - value: u64, - } - #[derive(Default)] - struct MockExt { - storage: HashMap, Vec>, - creates: Vec, - transfers: Vec, - next_account_id: u64, - } - impl Ext for MockExt { - fn get_storage(&self, key: &[u8]) -> Option> { - self.storage.get(key).cloned() - } - 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( - &mut self, - code: &[u8], - endowment: u64, - _gas_meter: &mut GasMeter, - data: &[u8], - ) -> Result, ()> { - self.creates.push(CreateEntry { - code: code.to_vec(), - endowment, - data: data.to_vec(), - }); - let address = self.next_account_id; - self.next_account_id += 1; - - Ok(CreateReceipt { - address, - }) - } - fn call( - &mut self, - to: &u64, - value: u64, - _gas_meter: &mut GasMeter, - _data: &[u8], - ) -> Result { - self.transfers.push(TransferEntry { to: *to, value }); - // Assume for now that it was just a plain transfer. - // TODO: Add tests for different call outcomes. - Ok(CallReceipt { - return_data: Vec::new(), - }) - } - } - - 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 config = Config::::default(); - prepare_contract(wasm.as_ref(), &config) - } - - #[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!(Config::::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(_)); - - // incorrect import name. That's kinda ok, since this will fail - // at later stage when imports will be resolved. - let r = parse_and_prepare_wat(r#"(module (import "vne" "memory" (memory 1 1)))"#); - 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)); - } - - const CODE_TRANSFER: &str = r#" -(module - ;; ext_transfer(transfer_to: u32, transfer_to_len: u32, value_ptr: u32, value_len: u32) - (import "env" "ext_transfer" (func $ext_transfer (param i32 i32 i32 i32))) - - (import "env" "memory" (memory 1 1)) - - (func (export "call") - (call $ext_transfer - (i32.const 4) ;; Pointer to "Transfer to" address. - (i32.const 8) ;; Length of "Transfer to" address. - (i32.const 12) ;; Pointer to the buffer with value to transfer - (i32.const 8) ;; Length of the buffer with value to transfer. - ) - ) - - ;; Destination AccountId to transfer the funds. - ;; Represented by u64 (8 bytes long) in little endian. - (data (i32.const 4) "\02\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() { - let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap(); - - let mut mock_ext = MockExt::default(); - execute(&code_transfer, &mut mock_ext, &mut GasMeter::with_limit(50_000, 1)).unwrap(); - - assert_eq!(&mock_ext.transfers, &[TransferEntry { to: 2, value: 6 }]); - } - - 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 mock_ext, &mut GasMeter::with_limit(100_000, 1)), - Err(_) - ); - } -} diff --git a/substrate/runtime/contract/src/vm/env_def/macros.rs b/substrate/runtime/contract/src/vm/env_def/macros.rs new file mode 100644 index 0000000000000..1472b407d4b37 --- /dev/null +++ b/substrate/runtime/contract/src/vm/env_def/macros.rs @@ -0,0 +1,285 @@ +// 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 . + +//! Definition of macros that hides boilerplate of defining external environment +//! for a wasm module. +//! +//! Typically you should use `define_env` macro. + +#[macro_export] +macro_rules! convert_args { + () => ([]); + ( $( $t:ty ),* ) => ( vec![ $( { use $crate::vm::env_def::ConvertibleToWasm; <$t>::VALUE_TYPE }, )* ] ); +} + +#[macro_export] +macro_rules! gen_signature { + ( ( $( $params: ty ),* ) ) => ( + { + FunctionType::new(convert_args!($($params),*), None) + } + ); + + ( ( $( $params: ty ),* ) -> $returns: ty ) => ( + { + FunctionType::new(convert_args!($($params),*), Some({ + use $crate::vm::env_def::ConvertibleToWasm; <$returns>::VALUE_TYPE + })) + } + ); +} + +/// 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 = + $args_iter.next() + .and_then(|v| <$params as $crate::vm::env_def::ConvertibleToWasm> + ::from_typed_value(v.clone())) + .expect( + "precondition: all imports should be checked against the signatures of corresponding + functions defined by `define_env!` macro by the user of the macro; + signatures of these functions defined by `$params`; + calls always made with arguments types of which are defined by the corresponding imports; + thus types of arguments should be equal to type list in `$params` and + length of argument list and $params should be equal; + thus this can never be `None`; + qed; + " + ); + )* + $body + }) +} + +/// Since we can't specify the type of closure directly at binding site: +/// +/// ```rust,ignore +/// let f: FnOnce() -> Result<::NativeType, _> = || { /* ... */ }; +/// ``` +/// +/// we use this function to constrain the type of the closure. +#[inline(always)] +pub fn constrain_closure(f: F) -> F +where + F: FnOnce() -> Result, +{ + f +} + +#[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, _ + >(|| { + unmarshall_then_body!($body, $ctx, $args_iter, $( $names : $params ),*) + }); + let r = body()?; + return Ok(ReturnValue::Value({ use $crate::vm::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::<(), _>(|| { + unmarshall_then_body!($body, $ctx, $args_iter, $( $names : $params ),*) + }); + body()?; + return Ok($crate::sandbox::ReturnValue::Unit) + }) +} + +#[macro_export] +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, + args: &[$crate::sandbox::TypedValue], + ) -> Result { + #[allow(unused)] + let mut args = args.iter(); + + unmarshall_then_body_then_marshall!( + args, + $ctx, + ( $( $names : $params ),* ) $( -> $returns )* => $body + ) + } + }; +} + +/// Define a function set that can be imported by executing wasm code. +/// +/// **NB**: Be advised that all functions defined by this macro +/// will panic if called with unexpected arguments. +/// +/// It's up to the user of this macro to check signatures of wasm code to be executed +/// and reject the code if any imported function has a mismached signature. +macro_rules! define_env { + ( $init_name:ident , < E: $ext_ty:tt > , + $( $name:ident ( $ctx:ident, $( $names:ident : $params:ty ),* ) + $( -> $returns:ty )* => $body:tt , )* + ) => { + pub(crate) fn $init_name() -> HostFunctionSet { + let mut env = HostFunctionSet::new(); + + $( + env.funcs.insert( + stringify!( $name ).to_string(), + HostFunction::new( + gen_signature!( ( $( $params ),* ) $( -> $returns )* ), + { + define_func!( + < E: $ext_ty > $name ( $ctx, $( $names : $params ),* ) $( -> $returns )* => $body + ); + $name:: + }, + ), + ); + )* + + env + } + }; +} + +#[cfg(test)] +mod tests { + use parity_wasm::elements::FunctionType; + use parity_wasm::elements::ValueType; + use runtime_primitives::traits::{As, Zero}; + use sandbox::{self, ReturnValue, TypedValue}; + use vm::env_def::{HostFunction, HostFunctionSet}; + use vm::tests::MockExt; + use vm::{Ext, Runtime}; + use Trait; + + #[test] + fn macro_unmarshall_then_body_then_marshall_value_or_trap() { + fn test_value( + _ctx: &mut u32, + args: &[sandbox::TypedValue], + ) -> Result { + let mut args = args.iter(); + unmarshall_then_body_then_marshall!( + args, + _ctx, + (a: u32, b: u32) -> u32 => { + if b == 0 { + Err(sandbox::HostError) + } else { + Ok(a / b) + } + } + ) + } + + let ctx = &mut 0; + assert_eq!( + test_value(ctx, &[TypedValue::I32(15), TypedValue::I32(3)]).unwrap(), + ReturnValue::Value(TypedValue::I32(5)), + ); + assert!(test_value(ctx, &[TypedValue::I32(15), TypedValue::I32(0)]).is_err()); + } + + #[test] + fn macro_unmarshall_then_body_then_marshall_unit() { + fn test_unit( + ctx: &mut u32, + args: &[sandbox::TypedValue], + ) -> Result { + let mut args = args.iter(); + unmarshall_then_body_then_marshall!( + args, + ctx, + (a: u32, b: u32) => { + *ctx = a + b; + Ok(()) + } + ) + } + + let ctx = &mut 0; + let result = test_unit(ctx, &[TypedValue::I32(2), TypedValue::I32(3)]).unwrap(); + assert_eq!(result, ReturnValue::Unit); + assert_eq!(*ctx, 5); + } + + #[test] + fn macro_define_func() { + define_func!( ext_gas (_ctx, amount: u32) => { + let amount = <<::T as Trait>::Gas as As>::sa(amount); + if !amount.is_zero() { + Ok(()) + } else { + Err(sandbox::HostError) + } + }); + let _f: fn(&mut Runtime, &[sandbox::TypedValue]) + -> Result = ext_gas::; + } + + #[test] + fn macro_gen_signature() { + assert_eq!( + gen_signature!((i32)), + FunctionType::new(vec![ValueType::I32], None), + ); + + assert_eq!( + gen_signature!( (i32, u32) -> u32 ), + FunctionType::new(vec![ValueType::I32, ValueType::I32], Some(ValueType::I32)), + ); + } + + #[test] + fn macro_unmarshall_then_body() { + let args = vec![TypedValue::I32(5), TypedValue::I32(3)]; + let mut args = args.iter(); + + let ctx: &mut u32 = &mut 0; + + let r = unmarshall_then_body!( + { + *ctx = a + b; + a * b + }, + ctx, + args, + a: u32, + b: u32 + ); + + assert_eq!(*ctx, 8); + assert_eq!(r, 15); + } + + #[test] + fn macro_define_env() { + define_env!(init_env, , + ext_gas( _ctx, amount: u32 ) => { + let amount = <<::T as Trait>::Gas as As>::sa(amount); + if !amount.is_zero() { + Ok(()) + } else { + Err(sandbox::HostError) + } + }, + ); + + let env = init_env::(); + assert!(env.funcs.get("ext_gas").is_some()); + } +} diff --git a/substrate/runtime/contract/src/vm/env_def/mod.rs b/substrate/runtime/contract/src/vm/env_def/mod.rs new file mode 100644 index 0000000000000..1222c23290428 --- /dev/null +++ b/substrate/runtime/contract/src/vm/env_def/mod.rs @@ -0,0 +1,252 @@ +// 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 . + +use super::{BalanceOf, CallReceipt, CreateReceipt, Ext, GasMeterResult, Runtime}; +use codec::Decode; +use parity_wasm::elements::{FunctionType, ValueType}; +use rstd::collections::btree_map::BTreeMap; +use runtime_primitives::traits::As; +use sandbox::{self, TypedValue}; +use system; +use Trait; + +#[macro_use] +mod macros; + +pub trait ConvertibleToWasm: Sized { + const VALUE_TYPE: ValueType; + type NativeType; + fn to_typed_value(self) -> TypedValue; + fn from_typed_value(TypedValue) -> Option; +} +impl ConvertibleToWasm for i32 { + type NativeType = i32; + const VALUE_TYPE: ValueType = ValueType::I32; + fn to_typed_value(self) -> TypedValue { + TypedValue::I32(self) + } + fn from_typed_value(v: TypedValue) -> Option { + v.as_i32() + } +} +impl ConvertibleToWasm for u32 { + type NativeType = u32; + const VALUE_TYPE: ValueType = ValueType::I32; + fn to_typed_value(self) -> TypedValue { + TypedValue::I32(self as i32) + } + fn from_typed_value(v: TypedValue) -> Option { + v.as_i32().map(|v| v as u32) + } +} + +/// 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>, +} +impl HostFunctionSet { + pub fn new() -> Self { + HostFunctionSet { + funcs: BTreeMap::new(), + } + } +} + +pub(crate) struct HostFunction { + pub(crate) f: fn(&mut Runtime, &[sandbox::TypedValue]) + -> Result, + func_type: FunctionType, +} +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 + } +} + +// TODO: ext_balance, ext_address, ext_callvalue, etc. + +// Define a function `fn init_env() -> HostFunctionSet` that returns +// a function set which can be imported by an executed contract. +define_env!(init_env, , + + // gas(amount: u32) + // + // 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 = <<::T as Trait>::Gas as As>::sa(amount); + + match ctx.gas_meter.charge(amount) { + GasMeterResult::Proceed => Ok(()), + GasMeterResult::OutOfGas => Err(sandbox::HostError), + } + }, + + // ext_put_storage(location_ptr: u32, value_non_null: u32, value_ptr: u32); + // + // Change the value at the given location in storage or remove it. + // + // - location_ptr: pointer into the linear + // memory where the location of the requested value is placed. + // - value_non_null: if set to 0, then the entry + // at the given location will be removed. + // - value_ptr: pointer into the linear memory + // where the value to set is placed. If `value_non_null` is set to 0, then this parameter is ignored. + ext_set_storage(ctx, location_ptr: u32, value_non_null: u32, value_ptr: u32) => { + let mut location = [0; 32]; + + ctx.memory().get(location_ptr, &mut location)?; + + let value = if value_non_null != 0 { + let mut value = [0; 32]; + ctx.memory().get(value_ptr, &mut value)?; + Some(value.to_vec()) + } else { + None + }; + ctx.ext.set_storage(&location, value); + + Ok(()) + }, + + // ext_get_storage(location_ptr: u32, dest_ptr: u32); + // + // Retrieve the value at the given location from the strorage. + // If there is no entry at the given location then all-zero-value + // will be returned. + // + // - location_ptr: pointer into the linear + // memory where the location of the requested value is placed. + // - dest_ptr: pointer where contents of the specified storage location + // should be placed. + ext_get_storage(ctx, location_ptr: u32, dest_ptr: u32) => { + let mut location = [0; 32]; + ctx.memory().get(location_ptr, &mut location)?; + + if let Some(value) = ctx.ext.get_storage(&location) { + ctx.memory().set(dest_ptr, &value)?; + } else { + ctx.memory().set(dest_ptr, &[0u8; 32])?; + } + + Ok(()) + }, + + // TODO: Rename ext_transfer to ext_call. + // ext_transfer(transfer_to_ptr: u32, transfer_to_len: u32, value_ptr: u32, value_len: u32) + ext_transfer(ctx, transfer_to_ptr: u32, transfer_to_len: u32, value_ptr: u32, value_len: u32) => { + let mut transfer_to = Vec::new(); + transfer_to.resize(transfer_to_len as usize, 0); + ctx.memory().get(transfer_to_ptr, &mut transfer_to)?; + let transfer_to = + <::T as system::Trait>::AccountId::decode(&mut &transfer_to[..]).unwrap(); + + let mut value_buf = Vec::new(); + value_buf.resize(value_len as usize, 0); + ctx.memory().get(value_ptr, &mut value_buf)?; + let value = BalanceOf::<::T>::decode(&mut &value_buf[..]).unwrap(); + + // TODO: Read input data from memory. + let input_data = Vec::new(); + + // TODO: Let user to choose how much gas to allocate for the execution. + let nested_gas_limit = ctx.gas_meter.gas_left(); + let ext = &mut ctx.ext; + let call_outcome = ctx.gas_meter.with_nested(nested_gas_limit, |nested_meter| { + match nested_meter { + Some(nested_meter) => ext.call(&transfer_to, value, nested_meter, &input_data), + // there is not enough gas to allocate for the nested call. + None => Err(()), + } + }); + + match call_outcome { + // TODO: Find a way how to pass return_data back to the this sandbox. + Ok(CallReceipt { .. }) => Ok(()), + // TODO: Return a status code value that can be handled by the caller instead of a trap. + Err(_) => Err(sandbox::HostError), + } + }, + + // ext_create(code_ptr: u32, code_len: u32, value_ptr: u32, value_len: u32) + ext_create(ctx, code_ptr: u32, code_len: u32, value_ptr: u32, value_len: u32) => { + let mut value_buf = Vec::new(); + value_buf.resize(value_len as usize, 0); + ctx.memory().get(value_ptr, &mut value_buf)?; + let value = BalanceOf::<::T>::decode(&mut &value_buf[..]).unwrap(); + + let mut code = Vec::new(); + code.resize(code_len as usize, 0u8); + ctx.memory().get(code_ptr, &mut code)?; + + // TODO: Read input data from the sandbox. + let input_data = Vec::new(); + + // TODO: Let user to choose how much gas to allocate for the execution. + let nested_gas_limit = ctx.gas_meter.gas_left(); + let ext = &mut ctx.ext; + let create_outcome = ctx.gas_meter.with_nested(nested_gas_limit, |nested_meter| { + match nested_meter { + Some(nested_meter) => ext.create(&code, value, nested_meter, &input_data), + // there is not enough gas to allocate for the nested call. + None => Err(()), + } + }); + + match create_outcome { + // TODO: Copy an address of the created contract in the sandbox. + Ok(CreateReceipt { .. }) => Ok(()), + // TODO: Return a status code value that can be handled by the caller instead of a trap. + Err(_) => Err(sandbox::HostError), + } + }, + + // ext_return(data_ptr: u32, data_len: u32) -> ! + ext_return(ctx, data_ptr: u32, data_len: u32) => { + let mut data_buf = Vec::new(); + data_buf.resize(data_len as usize, 0); + ctx.memory().get(data_ptr, &mut data_buf)?; + + ctx.store_return_data(data_buf) + .map_err(|_| sandbox::HostError)?; + + // The trap mechanism is used to immediately terminate the execution. + // This trap should be handled appropriately before returning the result + // to the user of this crate. + Err(sandbox::HostError) + }, +); diff --git a/substrate/runtime/contract/src/vm/mod.rs b/substrate/runtime/contract/src/vm/mod.rs new file mode 100644 index 0000000000000..31e98f8cad81e --- /dev/null +++ b/substrate/runtime/contract/src/vm/mod.rs @@ -0,0 +1,399 @@ +// 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 provides a means for executing contracts +//! represented in wasm. + +use exec::{CallReceipt, CreateReceipt}; +use gas::{GasMeter, GasMeterResult}; +use rstd::prelude::*; +use runtime_primitives::traits::{As, CheckedMul}; +use sandbox; +use staking; +use system; +use Trait; + +type BalanceOf = ::Balance; +type AccountIdOf = ::AccountId; + +mod prepare; + +#[macro_use] +mod env_def; + +use self::prepare::{prepare_contract, PreparedContract}; + +/// 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>); + + // TODO: Return the address of the created contract. + /// Create a new account for a contract. + /// + /// 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], + ) -> Result; +} + +/// 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, + + /// Gas instrumentation failed. + /// + /// This most likely indicates the module isn't valid. + GasInstrumentation, + + /// Stack instrumentation failed. + /// + /// This most likely indicates the module isn't valid. + StackHeightInstrumentation, + + /// Error happened during invocation of the contract's entrypoint. + /// + /// Most likely because of trap. + Invoke, + + /// Error happened during instantiation. + /// + /// This might indicate that `start` function trapped, or module isn't + /// instantiable and/or unlinkable. + Instantiate, + + /// Memory creation error. + /// + /// This might happen when the memory import has invalid descriptor or + /// requested too much resources. + Memory, +} + +/// Enumerates all possible *special* trap conditions. +/// +/// In this runtime traps used not only for signaling about errors but also +/// to just terminate quickly in some cases. +enum SpecialTrap { + // TODO: Can we pass wrapped memory instance instead of copying? + /// Signals that trap was generated in response to call `ext_return` host function. + Return(Vec), +} + +pub(crate) struct Runtime<'a, E: Ext + 'a> { + ext: &'a mut E, + config: &'a Config, + memory: sandbox::Memory, + gas_meter: &'a mut GasMeter, + special_trap: Option, +} +impl<'a, E: Ext + 'a> Runtime<'a, E> { + fn memory(&self) -> &sandbox::Memory { + &self.memory + } + /// Save a data buffer as a result of the execution. + /// + /// This function also charges gas for the returning. + /// + /// Returns `Err` if there is not enough gas. + fn store_return_data(&mut self, data: Vec) -> Result<(), ()> { + let data_len = <<::T as Trait>::Gas as As>::sa(data.len() as u64); + let price = (self.config.return_data_per_byte_cost) + .checked_mul(&data_len) + .ok_or_else(|| ())?; + + match self.gas_meter.charge(price) { + GasMeterResult::Proceed => { + self.special_trap = Some(SpecialTrap::Return(data)); + Ok(()) + } + GasMeterResult::OutOfGas => Err(()), + } + } +} + +fn to_execution_result( + runtime: Runtime, + run_err: Option, +) -> Result { + // Check the exact type of the error. It could be plain trap or + // special runtime trap the we must recognize. + let return_data = match (run_err, runtime.special_trap) { + // No traps were generated. Proceed normally. + (None, None) => Vec::new(), + // Special case. The trap was the result of the execution `return` host function. + (Some(sandbox::Error::Execution), Some(SpecialTrap::Return(rd))) => rd, + // Any other kind of a trap should result in a failure. + (Some(_), _) => return Err(Error::Invoke), + // Any other case (such as special trap flag without actual trap) signifies + // a logic error. + _ => unreachable!(), + }; + + Ok(ExecutionResult { return_data }) +} + +/// The result of execution of a smart-contract. +#[derive(PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct ExecutionResult { + /// The result produced by the execution of the contract. + /// + /// The contract can designate some buffer at the execution time via a special function. + /// If contract called this function with non-empty buffer it will be copied here. + /// + /// Note that gas is already charged for returning the data. + pub return_data: Vec, +} + +/// Execute the given code as a contract. +pub fn execute<'a, E: Ext>( + code: &[u8], + ext: &'a mut E, + gas_meter: &mut GasMeter, +) -> Result { + // TODO: Receive data as an argument + + let config = Config::default(); + let env = env_def::init_env(); + + let PreparedContract { + instrumented_code, + memory, + } = prepare_contract(code, &config, &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()); + } + imports.add_memory("env", "memory", memory.clone()); + + let mut runtime = Runtime { + ext, + config: &config, + memory, + gas_meter, + special_trap: None, + }; + + let mut instance = sandbox::Instance::new(&instrumented_code, &imports, &mut runtime) + .map_err(|_| Error::Instantiate)?; + + let run_result = instance.invoke(b"call", &[], &mut runtime); + + to_execution_result(runtime, run_result.err()) +} + +// TODO: Extract it to the root of the crate +#[derive(Clone)] +struct Config { + /// Gas cost of a growing memory by single page. + grow_mem_cost: T::Gas, + + /// Gas cost of a regular operation. + regular_op_cost: T::Gas, + + /// Gas cost per one byte returned. + return_data_per_byte_cost: T::Gas, + + /// How tall the stack is allowed to grow? + /// + /// See https://wiki.parity.io/WebAssembly-StackHeight to find out + /// how the stack frame cost is calculated. + max_stack_height: u32, + + //// What is the maximal memory pages amount is allowed to have for + /// a contract. + max_memory_pages: u32, +} + +impl Default for Config { + fn default() -> Config { + Config { + grow_mem_cost: T::Gas::sa(1), + regular_op_cost: T::Gas::sa(1), + return_data_per_byte_cost: T::Gas::sa(1), + max_stack_height: 64 * 1024, + max_memory_pages: 16, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use gas::GasMeter; + use std::collections::HashMap; + use tests::Test; + use wabt; + + #[derive(Debug, PartialEq, Eq)] + struct CreateEntry { + code: Vec, + endowment: u64, + data: Vec, + } + #[derive(Debug, PartialEq, Eq)] + struct TransferEntry { + to: u64, + value: u64, + } + #[derive(Default)] + pub struct MockExt { + storage: HashMap, Vec>, + creates: Vec, + transfers: Vec, + next_account_id: u64, + } + impl Ext for MockExt { + type T = Test; + + fn get_storage(&self, key: &[u8]) -> Option> { + self.storage.get(key).cloned() + } + 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( + &mut self, + code: &[u8], + endowment: u64, + _gas_meter: &mut GasMeter, + data: &[u8], + ) -> Result, ()> { + self.creates.push(CreateEntry { + code: code.to_vec(), + endowment, + data: data.to_vec(), + }); + let address = self.next_account_id; + self.next_account_id += 1; + + Ok(CreateReceipt { address }) + } + fn call( + &mut self, + to: &u64, + value: u64, + _gas_meter: &mut GasMeter, + _data: &[u8], + ) -> Result { + self.transfers.push(TransferEntry { to: *to, value }); + // Assume for now that it was just a plain transfer. + // TODO: Add tests for different call outcomes. + Ok(CallReceipt { + return_data: Vec::new(), + }) + } + } + + const CODE_TRANSFER: &str = r#" +(module + ;; ext_transfer(transfer_to: u32, transfer_to_len: u32, value_ptr: u32, value_len: u32) + (import "env" "ext_transfer" (func $ext_transfer (param i32 i32 i32 i32))) + + (import "env" "memory" (memory 1 1)) + + (func (export "call") + (call $ext_transfer + (i32.const 4) ;; Pointer to "Transfer to" address. + (i32.const 8) ;; Length of "Transfer to" address. + (i32.const 12) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Length of the buffer with value to transfer. + ) + ) + + ;; Destination AccountId to transfer the funds. + ;; Represented by u64 (8 bytes long) in little endian. + (data (i32.const 4) "\02\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() { + let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap(); + + let mut mock_ext = MockExt::default(); + execute( + &code_transfer, + &mut mock_ext, + &mut GasMeter::with_limit(50_000, 1), + ).unwrap(); + + assert_eq!(&mock_ext.transfers, &[TransferEntry { to: 2, value: 6 }]); + } + + 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 mock_ext, + &mut GasMeter::with_limit(100_000, 1) + ), + Err(_) + ); + } +} diff --git a/substrate/runtime/contract/src/vm/prepare.rs b/substrate/runtime/contract/src/vm/prepare.rs new file mode 100644 index 0000000000000..4e77fef6b0a5a --- /dev/null +++ b/substrate/runtime/contract/src/vm/prepare.rs @@ -0,0 +1,285 @@ +// 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::{Config, Error, Ext}; +use parity_wasm::elements::{self, External, MemoryType, Type}; +use pwasm_utils; +use pwasm_utils::rules; +use runtime_primitives::traits::As; +use sandbox; +use Trait; + +struct ContractModule<'a, T: Trait + '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, + config: &'a Config, +} + +impl<'a, T: Trait> ContractModule<'a, T> { + fn new(original_code: &[u8], config: &'a Config) -> Result, Error> { + let module = + elements::deserialize_buffer(original_code).map_err(|_| Error::Deserialization)?; + Ok(ContractModule { + module: Some(module), + config, + }) + } + + /// 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.config.regular_op_cost.as_(), Default::default()) + .with_grow_cost(self.config.grow_mem_cost.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.config.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()) + .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 `config`, +/// - 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], + config: &Config, + env: &HostFunctionSet, +) -> Result { + let mut contract_module = ContractModule::new(original_code, config)?; + 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 > config.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 tests::Test; + 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 config = Config::::default(); + let env = ::vm::env_def::init_env(); + prepare_contract::(wasm.as_ref(), &config, &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!(Config::::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/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm b/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm index 0f5f06b577b23..adee6b02cf463 100644 Binary files a/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm and b/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm differ diff --git a/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.wasm b/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.wasm index cd40d8e582a6e..223765126290d 100755 Binary files a/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.wasm and b/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.wasm differ