diff --git a/Cargo.lock b/Cargo.lock index 5b8d05d04ef5a..8209509e4fab5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2071,6 +2071,19 @@ dependencies = [ "substrate-runtime-system 0.1.0", ] +[[package]] +name = "substrate-runtime-contract" +version = "0.1.0" +dependencies = [ + "assert_matches 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-wasm 0.30.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pwasm-utils 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-codec 0.1.0", + "substrate-runtime-sandbox 0.1.0", + "substrate-runtime-std 0.1.0", + "wabt 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "substrate-runtime-council" version = "0.1.0" @@ -2193,16 +2206,14 @@ dependencies = [ name = "substrate-runtime-staking" version = "0.1.0" dependencies = [ - "assert_matches 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-wasm 0.30.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pwasm-utils 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "safe-mix 0.1.0", "serde 1.0.63 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", "substrate-keyring 0.1.0", "substrate-primitives 0.1.0", "substrate-runtime-consensus 0.1.0", + "substrate-runtime-contract 0.1.0", "substrate-runtime-io 0.1.0", "substrate-runtime-primitives 0.1.0", "substrate-runtime-sandbox 0.1.0", diff --git a/Cargo.toml b/Cargo.toml index d58205e310b19..b3042535e5e71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ members = [ "substrate/runtime-std", "substrate/runtime-support", "substrate/runtime/consensus", + "substrate/runtime/contract", "substrate/runtime/council", "substrate/runtime/democracy", "substrate/runtime/executive", diff --git a/demo/runtime/wasm/Cargo.lock b/demo/runtime/wasm/Cargo.lock index fe80ff221c789..f8c0de993c7d2 100644 --- a/demo/runtime/wasm/Cargo.lock +++ b/demo/runtime/wasm/Cargo.lock @@ -548,6 +548,17 @@ dependencies = [ "substrate-runtime-system 0.1.0", ] +[[package]] +name = "substrate-runtime-contract" +version = "0.1.0" +dependencies = [ + "parity-wasm 0.30.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pwasm-utils 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-codec 0.1.0", + "substrate-runtime-sandbox 0.1.0", + "substrate-runtime-std 0.1.0", +] + [[package]] name = "substrate-runtime-council" version = "0.1.0" @@ -667,14 +678,13 @@ name = "substrate-runtime-staking" version = "0.1.0" dependencies = [ "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-wasm 0.30.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pwasm-utils 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "safe-mix 0.1.0", "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", "substrate-keyring 0.1.0", "substrate-primitives 0.1.0", "substrate-runtime-consensus 0.1.0", + "substrate-runtime-contract 0.1.0", "substrate-runtime-io 0.1.0", "substrate-runtime-primitives 0.1.0", "substrate-runtime-sandbox 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 b7748f8357327..1b85c4a9d6c8e 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 d12bcd785aaf9..00fdb71071422 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/polkadot/runtime/wasm/Cargo.lock b/polkadot/runtime/wasm/Cargo.lock index af427fbd6db4d..9d8f465e4acfb 100644 --- a/polkadot/runtime/wasm/Cargo.lock +++ b/polkadot/runtime/wasm/Cargo.lock @@ -548,6 +548,17 @@ dependencies = [ "substrate-runtime-system 0.1.0", ] +[[package]] +name = "substrate-runtime-contract" +version = "0.1.0" +dependencies = [ + "parity-wasm 0.30.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pwasm-utils 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-codec 0.1.0", + "substrate-runtime-sandbox 0.1.0", + "substrate-runtime-std 0.1.0", +] + [[package]] name = "substrate-runtime-council" version = "0.1.0" @@ -667,14 +678,13 @@ name = "substrate-runtime-staking" version = "0.1.0" dependencies = [ "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-wasm 0.30.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pwasm-utils 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "safe-mix 0.1.0", "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", "substrate-keyring 0.1.0", "substrate-primitives 0.1.0", "substrate-runtime-consensus 0.1.0", + "substrate-runtime-contract 0.1.0", "substrate-runtime-io 0.1.0", "substrate-runtime-primitives 0.1.0", "substrate-runtime-sandbox 0.1.0", diff --git a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm index 6e012c3b0b4e8..7ca1577099c72 100644 Binary files a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm and b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm differ diff --git a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm index 3eabfef169955..1a254a365da3d 100755 Binary files a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm and b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm differ diff --git a/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm b/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm index e0b13073a4f25..ddce0fcdfb80e 100644 Binary files a/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm and b/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm differ diff --git a/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.wasm b/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.wasm index 7cca19d08801d..d222a1f6b1200 100755 Binary files a/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.wasm and b/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.wasm differ diff --git a/substrate/runtime/contract/Cargo.toml b/substrate/runtime/contract/Cargo.toml new file mode 100644 index 0000000000000..30c3564063b02 --- /dev/null +++ b/substrate/runtime/contract/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "substrate-runtime-contract" +version = "0.1.0" +authors = ["Parity Technologies "] + +[dependencies] +substrate-codec = { path = "../../codec", default_features = false } +substrate-runtime-std = { path = "../../runtime-std", default_features = false } +substrate-runtime-sandbox = { path = "../../runtime-sandbox", default_features = false } +parity-wasm = { version = "0.30", default_features = false } +pwasm-utils = { version = "0.2", default_features = false } + +[dev-dependencies] +wabt = "0.1.7" +assert_matches = "1.1" + +[features] +default = ["std"] +std = [ + "substrate-codec/std", + "substrate-runtime-std/std", + "substrate-runtime-sandbox/std", + "parity-wasm/std", + "pwasm-utils/std", +] diff --git a/substrate/runtime/staking/src/contract.rs b/substrate/runtime/contract/src/lib.rs similarity index 76% rename from substrate/runtime/staking/src/contract.rs rename to substrate/runtime/contract/src/lib.rs index ed6353dd461a4..6b8f63b974cc6 100644 --- a/substrate/runtime/staking/src/contract.rs +++ b/substrate/runtime/contract/src/lib.rs @@ -12,21 +12,64 @@ // 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 . +// along with Substrate. If not, see . -//! Smart-contract execution module. +//! 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. -// TODO: Extract to it's own crate? +#![cfg_attr(not(feature = "std"), no_std)] +#![warn(missing_docs)] + +extern crate parity_wasm; +extern crate pwasm_utils; + +extern crate substrate_runtime_std as rstd; +extern crate substrate_runtime_sandbox as sandbox; +extern crate substrate_codec as codec; + +#[cfg(test)] +#[macro_use] +extern crate assert_matches; + +#[cfg(test)] +extern crate wabt; -use codec::Slicable; use rstd::prelude::*; -use sandbox; -use {AccountDb, Module, OverlayAccountDb, Trait}; +use codec::Slicable; use parity_wasm::elements::{self, External, MemoryType}; -use pwasm_utils; use pwasm_utils::rules; +/// 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 { + /// The indentifier of an account. + type AccountId: Slicable + Clone; + /// The balance of an account. + type Balance: Slicable; + + /// 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: Self::Balance); + + /// Transfer some funds to the specified account. + fn transfer(&mut self, to: &Self::AccountId, value: Self::Balance); +} + /// Error that can occur while preparing or executing wasm smart-contract. #[derive(Debug, PartialEq, Eq)] pub enum Error { @@ -67,26 +110,22 @@ pub enum Error { Memory, } -struct ExecutionExt<'a, 'b: 'a, T: Trait + 'b> { - account_db: &'a mut OverlayAccountDb<'b, T>, - account: T::AccountId, +struct Runtime<'a, T: Ext + 'a> { + ext: &'a mut T, memory: sandbox::Memory, gas_used: u64, gas_limit: u64, } -impl<'a, 'b: 'a, T: Trait> ExecutionExt<'a, 'b, T> { - fn account(&self) -> &T::AccountId { - &self.account - } - fn account_db(&self) -> &OverlayAccountDb { - self.account_db - } - fn account_db_mut(&mut self) -> &mut OverlayAccountDb<'b, T> { - self.account_db - } +impl<'a, T: Ext + 'a> Runtime<'a, T> { fn memory(&self) -> &sandbox::Memory { &self.memory } + fn ext(&self) -> &T { + self.ext + } + fn ext_mut(&mut self) -> &mut T { + self.ext + } /// Account for used gas. /// /// Returns `false` if there is not enough gas or addition of the specified @@ -105,10 +144,10 @@ impl<'a, 'b: 'a, T: Trait> ExecutionExt<'a, 'b, T> { } } -pub(crate) fn execute<'a, 'b: 'a, T: Trait>( +/// Execute the given code as a contract. +pub fn execute<'a, T: Ext>( code: &[u8], - account: &T::AccountId, - account_db: &'a mut OverlayAccountDb<'b, T>, + ext: &'a mut T, gas_limit: u64, ) -> Result<(), Error> { // ext_gas(amount: u32) @@ -116,7 +155,7 @@ pub(crate) fn execute<'a, 'b: 'a, T: Trait>( // Account for used gas. Traps if gas used is greater than gas limit. // // - amount: How much gas is used. - fn ext_gas(e: &mut ExecutionExt, args: &[sandbox::TypedValue]) -> Result { + fn ext_gas(e: &mut Runtime, args: &[sandbox::TypedValue]) -> Result { let amount = args[0].as_i32().unwrap() as u32; if e.charge_gas(amount as u64) { Ok(sandbox::ReturnValue::Unit) @@ -135,7 +174,10 @@ pub(crate) fn execute<'a, 'b: 'a, T: Trait>( // 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 ExecutionExt, args: &[sandbox::TypedValue]) -> Result { + 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; @@ -143,17 +185,18 @@ pub(crate) fn execute<'a, 'b: 'a, T: Trait>( let mut location = [0; 32]; e.memory().get(location_ptr, &mut location)?; - let account = e.account().clone(); - if value_non_null != 0 { + let value = if value_non_null != 0 { let mut value = [0; 32]; e.memory().get(value_ptr, &mut value)?; - e.account_db_mut() - .set_storage(&account, location.to_vec(), Some(value.to_vec())); + Some(value.to_vec()) } else { - e.account_db_mut() - .set_storage(&account, location.to_vec(), None); - } + None + }; + e.ext_mut().set_storage( + &location, + value, + ); Ok(sandbox::ReturnValue::Unit) } @@ -168,15 +211,14 @@ pub(crate) fn execute<'a, 'b: 'a, T: Trait>( // 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 ExecutionExt, args: &[sandbox::TypedValue]) -> Result { + 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)?; - let account = e.account().clone(); - if let Some(value) = e.account_db_mut().get_storage(&account, &location) { + if let Some(value) = e.ext().get_storage(&location) { e.memory().set(dest_ptr, &value)?; } else { e.memory().set(dest_ptr, &[0u8; 32])?; @@ -185,8 +227,8 @@ pub(crate) fn execute<'a, 'b: 'a, T: Trait>( Ok(sandbox::ReturnValue::Unit) } - // ext_transfer(transfer_to: u32, transfer_to_len: u32, value: u32) - fn ext_transfer(e: &mut ExecutionExt, args: &[sandbox::TypedValue]) -> Result { + // 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; @@ -202,19 +244,13 @@ pub(crate) fn execute<'a, 'b: 'a, T: Trait>( e.memory().get(value_ptr, &mut value_buf)?; let value = T::Balance::decode(&mut &value_buf[..]).unwrap(); - let account = e.account().clone(); - if let Some(commit_state) = - Module::::effect_transfer(&account, &transfer_to, value, e.account_db()) - .map_err(|_| sandbox::Error::Execution)? - { - e.account_db_mut().merge(commit_state); - } + e.ext_mut().transfer(&transfer_to, value); Ok(sandbox::ReturnValue::Unit) } - // ext_create(code_ptr: u32, code_len: u32, value: u32) - fn ext_create(e: &mut ExecutionExt, args: &[sandbox::TypedValue]) -> Result { + // 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; @@ -229,13 +265,7 @@ pub(crate) fn execute<'a, 'b: 'a, T: Trait>( code.resize(code_len as usize, 0u8); e.memory().get(code_ptr, &mut code)?; - let account = e.account().clone(); - if let Some(commit_state) = - Module::::effect_create(&account, &code, value, e.account_db()) - .map_err(|_| sandbox::Error::Execution)? - { - e.account_db_mut().merge(commit_state); - } + e.ext_mut().create(&code, value); Ok(sandbox::ReturnValue::Unit) } @@ -254,19 +284,18 @@ pub(crate) fn execute<'a, 'b: 'a, T: Trait>( // TODO: ext_balance, ext_address, ext_callvalue, etc. imports.add_memory("env", "memory", memory.clone()); - let mut exec_ext = ExecutionExt { - account: account.clone(), - account_db, + let mut runtime = Runtime { + ext, memory, gas_limit, gas_used: 0, }; let mut instance = - sandbox::Instance::new(&instrumented_code, &imports, &mut exec_ext) + sandbox::Instance::new(&instrumented_code, &imports, &mut runtime) .map_err(|_| Error::Instantiate)?; instance - .invoke(b"call", &[], &mut exec_ext) + .invoke(b"call", &[], &mut runtime) .map(|_| ()) .map_err(|_| Error::Invoke) } @@ -446,9 +475,51 @@ mod tests { use super::*; use std::fmt; use wabt; - use runtime_io::with_externalities; - use mock::{Staking, Test, new_test_ext}; - use ::{CodeOf, ContractAddressFor, DirectAccountDb, FreeBalance, StorageMap}; + use std::collections::HashMap; + + #[derive(Debug, PartialEq, Eq)] + struct CreateEntry { + code: Vec, + endownment: u64, + } + #[derive(Debug, PartialEq, Eq)] + struct TransferEntry { + to: u64, + value: u64, + } + #[derive(Default)] + struct MockExt { + storage: HashMap, Vec>, + creates: Vec, + transfers: Vec, + } + impl Ext for MockExt { + type AccountId = u64; + type Balance = u64; + + 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], value: Self::Balance) { + self.creates.push( + CreateEntry { + code: code.to_vec(), + endownment: value, + } + ); + } + fn transfer(&mut self, to: &Self::AccountId, value: Self::Balance) { + self.transfers.push( + TransferEntry { + to: *to, + value, + } + ); + } + } impl fmt::Debug for PreparedContract { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -544,19 +615,13 @@ mod tests { fn contract_transfer() { let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap(); - with_externalities(&mut new_test_ext(1, 3, 1, false), || { - >::insert(0, 111); - >::insert(1, 0); - >::insert(2, 30); - - >::insert(1, code_transfer.to_vec()); - - assert_ok!(Staking::transfer(&0, 1, 11)); + let mut mock_ext = MockExt::default(); + execute(&code_transfer, &mut mock_ext, 50_000).unwrap(); - assert_eq!(Staking::balance(&0), 100); - assert_eq!(Staking::balance(&1), 5); - assert_eq!(Staking::balance(&2), 36); - }); + assert_eq!(&mock_ext.transfers, &[TransferEntry { + to: 2, + value: 6, + }]); } /// Returns code that uses `ext_create` runtime call. @@ -609,22 +674,15 @@ r#" let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap(); let code_create = wabt::wat2wasm(&code_create(&code_transfer)).unwrap(); - with_externalities(&mut new_test_ext(1, 3, 1, false), || { - >::insert(0, 111); - >::insert(1, 0); + let mut mock_ext = MockExt::default(); + execute(&code_create, &mut mock_ext, 50_000).unwrap(); - >::insert(1, code_create.to_vec()); - - // When invoked, the contract at address `1` must create a contract with 'transfer' code. - assert_ok!(Staking::transfer(&0, 1, 11)); - - let derived_address = - ::DetermineContractAddress::contract_address_for(&code_transfer, &1); - - assert_eq!(Staking::balance(&0), 100); - assert_eq!(Staking::balance(&1), 8); - assert_eq!(Staking::balance(&derived_address), 3); - }); + assert_eq!(&mock_ext.creates, &[ + CreateEntry { + code: code_transfer, + endownment: 3, + } + ]); } /// This code a value from the storage, increment it's first byte @@ -669,32 +727,25 @@ r#" fn contract_adder() { let code_adder = wabt::wat2wasm(CODE_ADDER).unwrap(); - with_externalities(&mut new_test_ext(1, 3, 1, false), || { - >::insert(0, 111); - >::insert(1, 0); - >::insert(1, code_adder); - - assert_ok!(Staking::transfer(&0, 1, 1)); - assert_ok!(Staking::transfer(&0, 1, 1)); - - let storage_addr = [0x01u8; 32]; - let value = - AccountDb::::get_storage(&DirectAccountDb, &1, &storage_addr).unwrap(); - - assert_eq!( - &value, - &[ - 2, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - ] - ); - }); + let mut mock_ext = MockExt::default(); + + // Execute the test twice. + execute(&code_adder, &mut mock_ext, 50_000).unwrap(); + execute(&code_adder, &mut mock_ext, 50_000).unwrap(); + + let storage_addr = [0x01u8; 32]; + assert_eq!( + &mock_ext.storage.get(&storage_addr[..]).unwrap()[..], + &[ + 2, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + ][..], + ); } - // This code should make 100_000 iterations so it should - // consume more than 100_000 units of gas. + // This code should make 100_000 iterations. const CODE_LOOP: &str = r#" (module @@ -726,21 +777,16 @@ r#" fn contract_out_of_gas() { let code_loop = wabt::wat2wasm(CODE_LOOP).unwrap(); - with_externalities(&mut new_test_ext(1, 3, 1, false), || { - // Set initial balances. - >::insert(0, 111); - >::insert(1, 0); - - >::insert(1, code_loop.to_vec()); + let mut mock_ext = MockExt::default(); - // Transfer some balance from 0 to 1. This will trigger execution - // of the smart-contract code at address 1. - assert_ok!(Staking::transfer(&0, 1, 11)); - - // The balance should remain unchanged since we are expecting - // out-of-gas error which will revert transfer. - assert_eq!(Staking::balance(&0), 111); - }); + assert_matches!( + execute(&code_loop, &mut mock_ext, 900_000), + Err(_) + ); + assert_matches!( + execute(&code_loop, &mut mock_ext, 937_000), + Ok(_) + ); } const CODE_MEM: &str = @@ -759,19 +805,11 @@ r#" fn contract_internal_mem() { let code_mem = wabt::wat2wasm(CODE_MEM).unwrap(); - with_externalities(&mut new_test_ext(1, 3, 1, false), || { - // Set initial balances. - >::insert(0, 111); - >::insert(1, 0); + let mut mock_ext = MockExt::default(); - >::insert(1, code_mem.to_vec()); - - // Transfer some balance from 0 to 1. - assert_ok!(Staking::transfer(&0, 1, 11)); - - // The balance should remain unchanged since we are expecting - // validation error caused by internal memory declaration. - assert_eq!(Staking::balance(&0), 111); - }); + assert_matches!( + execute(&code_mem, &mut mock_ext, 100_000), + Err(_) + ); } } diff --git a/substrate/runtime/staking/Cargo.toml b/substrate/runtime/staking/Cargo.toml index 9c81d072fee73..0f9274198a544 100644 --- a/substrate/runtime/staking/Cargo.toml +++ b/substrate/runtime/staking/Cargo.toml @@ -10,6 +10,7 @@ safe-mix = { path = "../../../safe-mix", default_features = false} substrate-keyring = { path = "../../keyring", optional = true } substrate-codec = { path = "../../codec", default_features = false } substrate-primitives = { path = "../../primitives", default_features = false } +substrate-runtime-contract = { path = "../contract", default_features = false } substrate-runtime-std = { path = "../../runtime-std", default_features = false } substrate-runtime-io = { path = "../../runtime-io", default_features = false } substrate-runtime-sandbox = { path = "../../runtime-sandbox", default_features = false } @@ -18,12 +19,9 @@ substrate-runtime-primitives = { path = "../primitives", default_features = fals substrate-runtime-consensus = { path = "../consensus", default_features = false } substrate-runtime-system = { path = "../system", default_features = false } substrate-runtime-session = { path = "../session", default_features = false } -parity-wasm = { version = "0.30", default_features = false } -pwasm-utils = { version = "0.2", default_features = false } [dev-dependencies] wabt = "0.1.7" -assert_matches = "1.1" [features] default = ["std"] @@ -33,6 +31,7 @@ std = [ "substrate-keyring", "substrate-codec/std", "substrate-primitives/std", + "substrate-runtime-contract/std", "substrate-runtime-std/std", "substrate-runtime-io/std", "substrate-runtime-sandbox/std", @@ -40,6 +39,4 @@ std = [ "substrate-runtime-primitives/std", "substrate-runtime-session/std", "substrate-runtime-system/std", - "pwasm-utils/std", - "parity-wasm/std", ] diff --git a/substrate/runtime/staking/src/lib.rs b/substrate/runtime/staking/src/lib.rs index e63b3de7d7e14..418ed28132446 100644 --- a/substrate/runtime/staking/src/lib.rs +++ b/substrate/runtime/staking/src/lib.rs @@ -24,10 +24,6 @@ extern crate serde; #[cfg(test)] extern crate wabt; -#[cfg(test)] -#[macro_use] -extern crate assert_matches; - #[macro_use] extern crate substrate_runtime_support as runtime_support; @@ -36,14 +32,13 @@ extern crate substrate_runtime_std as rstd; extern crate substrate_codec as codec; extern crate substrate_primitives; +extern crate substrate_runtime_contract as contract; extern crate substrate_runtime_io as runtime_io; extern crate substrate_runtime_primitives as primitives; extern crate substrate_runtime_consensus as consensus; extern crate substrate_runtime_sandbox as sandbox; extern crate substrate_runtime_session as session; extern crate substrate_runtime_system as system; -extern crate pwasm_utils; -extern crate parity_wasm; #[cfg(test)] use std::fmt::Debug; use rstd::prelude::*; @@ -55,10 +50,6 @@ use runtime_support::{StorageValue, StorageMap, Parameter}; use runtime_support::dispatch::Result; use primitives::traits::{Zero, One, Bounded, RefInto, SimpleArithmetic, Executable, MakePayment, As}; -mod contract; -#[cfg(test)] -mod mock; - #[cfg(test)] #[derive(Debug, PartialEq, Clone)] pub enum LockStatus { @@ -629,7 +620,11 @@ impl Module { } else { // TODO: logging (logs are just appended into a notable storage-based vector and cleared every // block). - contract::execute(&dest_code, dest, &mut overlay, gas_limit).is_ok() + let mut staking_ext = StakingExt { + account_db: &mut overlay, + account: dest.clone(), + }; + contract::execute(&dest_code, &mut staking_ext, gas_limit).is_ok() }; Ok(if should_commit { @@ -640,6 +635,36 @@ impl Module { } } +struct StakingExt<'a, 'b: 'a, T: Trait + 'b> { + account_db: &'a mut OverlayAccountDb<'b, T>, + account: T::AccountId, +} +impl<'a, 'b: 'a, T: Trait> contract::Ext for StakingExt<'a, 'b, T> { + type AccountId = T::AccountId; + type Balance = T::Balance; + + fn get_storage(&self, key: &[u8]) -> Option> { + self.account_db.get_storage(&self.account, key) + } + fn set_storage(&mut self, key: &[u8], value: Option>) { + self.account_db.set_storage(&self.account, key.to_vec(), value); + } + fn create(&mut self, code: &[u8], value: Self::Balance) { + if let Ok(Some(commit_state)) = + Module::::effect_create(&self.account, code, value, self.account_db) + { + self.account_db.merge(commit_state); + } + } + fn transfer(&mut self, to: &Self::AccountId, value: Self::Balance) { + if let Ok(Some(commit_state)) = + Module::::effect_transfer(&self.account, to, value, self.account_db) + { + self.account_db.merge(commit_state); + } + } +} + impl MakePayment for Module { fn make_payment(transactor: &T::AccountId, encoded_len: usize) -> bool { let b = Self::free_balance(transactor); @@ -759,7 +784,62 @@ impl primitives::BuildExternalities for GenesisConfig { mod tests { use super::*; use runtime_io::with_externalities; - use mock::*; + use substrate_primitives::H256; + use primitives::BuildExternalities; + use primitives::traits::{HasPublicAux, Identity}; + use primitives::testing::{Digest, Header}; + + pub struct Test; + impl HasPublicAux for Test { + type PublicAux = u64; + } + impl consensus::Trait for Test { + type PublicAux = ::PublicAux; + type SessionKey = u64; + } + impl system::Trait for Test { + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = runtime_io::BlakeTwo256; + type Digest = Digest; + type AccountId = u64; + type Header = Header; + } + impl session::Trait for Test { + type ConvertAccountIdToSessionKey = Identity; + } + impl Trait for Test { + type Balance = u64; + type DetermineContractAddress = DummyContractAddressFor; + } + + fn new_test_ext(session_length: u64, sessions_per_era: u64, current_era: u64, monied: bool) -> runtime_io::TestExternalities { + let mut t = system::GenesisConfig::::default().build_externalities(); + t.extend(consensus::GenesisConfig::{ + code: vec![], + authorities: vec![], + }.build_externalities()); + t.extend(session::GenesisConfig::{ + session_length, + validators: vec![10, 20], + }.build_externalities()); + t.extend(GenesisConfig::{ + sessions_per_era, + current_era, + balances: if monied { vec![(1, 10), (2, 20), (3, 30), (4, 40)] } else { vec![] }, + intentions: vec![], + validator_count: 2, + bonding_duration: 3, + transaction_base_fee: 0, + transaction_byte_fee: 0, + }.build_externalities()); + t + } + + type System = system::Module; + type Session = session::Module; + type Staking = Module; #[test] fn staking_should_work() { @@ -1043,4 +1123,76 @@ mod tests { assert_eq!(Staking::free_balance(&2), 42); }); } + + 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(); + + with_externalities(&mut new_test_ext(1, 3, 1, false), || { + >::insert(0, 111); + >::insert(1, 0); + >::insert(2, 30); + + >::insert(1, code_transfer.to_vec()); + + assert_ok!(Staking::transfer(&0, 1, 11)); + + assert_eq!(Staking::balance(&0), 100); + assert_eq!(Staking::balance(&1), 5); + assert_eq!(Staking::balance(&2), 36); + }); + } + + 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(); + + with_externalities(&mut new_test_ext(1, 3, 1, false), || { + // Set initial balances. + >::insert(0, 111); + >::insert(1, 0); + + >::insert(1, code_mem.to_vec()); + + // Transfer some balance from 0 to 1. + assert_ok!(Staking::transfer(&0, 1, 11)); + + // The balance should remain unchanged since we are expecting + // validation error caused by internal memory declaration. + assert_eq!(Staking::balance(&0), 111); + }); + } } diff --git a/substrate/runtime/staking/src/mock.rs b/substrate/runtime/staking/src/mock.rs deleted file mode 100644 index 7a805a6c02a72..0000000000000 --- a/substrate/runtime/staking/src/mock.rs +++ /dev/null @@ -1,79 +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 . - -//! Test utilities - -#![cfg(test)] - - -use primitives::BuildExternalities; -use primitives::traits::{HasPublicAux, Identity}; -use primitives::testing::{Digest, Header}; -use substrate_primitives::H256; -use runtime_io; -use {DummyContractAddressFor, GenesisConfig, Module, Trait, consensus, session, system}; - -pub struct Test; -impl HasPublicAux for Test { - type PublicAux = u64; -} -impl consensus::Trait for Test { - type PublicAux = ::PublicAux; - type SessionKey = u64; -} -impl system::Trait for Test { - type Index = u64; - type BlockNumber = u64; - type Hash = H256; - type Hashing = runtime_io::BlakeTwo256; - type Digest = Digest; - type AccountId = u64; - type Header = Header; -} -impl session::Trait for Test { - type ConvertAccountIdToSessionKey = Identity; -} -impl Trait for Test { - type Balance = u64; - type DetermineContractAddress = DummyContractAddressFor; -} - -pub fn new_test_ext(session_length: u64, sessions_per_era: u64, current_era: u64, monied: bool) -> runtime_io::TestExternalities { - let mut t = system::GenesisConfig::::default().build_externalities(); - t.extend(consensus::GenesisConfig::{ - code: vec![], - authorities: vec![], - }.build_externalities()); - t.extend(session::GenesisConfig::{ - session_length, - validators: vec![10, 20], - }.build_externalities()); - t.extend(GenesisConfig::{ - sessions_per_era, - current_era, - balances: if monied { vec![(1, 10), (2, 20), (3, 30), (4, 40)] } else { vec![] }, - intentions: vec![], - validator_count: 2, - bonding_duration: 3, - transaction_base_fee: 0, - transaction_byte_fee: 0, - }.build_externalities()); - t -} - -pub type System = system::Module; -pub type Session = session::Module; -pub type Staking = Module; 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 3151358586870..bb95320a5f13b 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 fd76280533232..43b67db529907 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