Extract smart-contract runtime into the separate crate (#179)

* Apply inversion of control to contract module.

* Extract contract to it's own crate.

* Rebuild binaries.
This commit is contained in:
Sergey Pepyakin
2018-06-05 15:18:08 +03:00
committed by Gav Wood
parent 68e468f59c
commit ff0d9c3359
17 changed files with 405 additions and 240 deletions
+14 -3
View File
@@ -2071,6 +2071,19 @@ dependencies = [
"substrate-runtime-system 0.1.0", "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]] [[package]]
name = "substrate-runtime-council" name = "substrate-runtime-council"
version = "0.1.0" version = "0.1.0"
@@ -2193,16 +2206,14 @@ dependencies = [
name = "substrate-runtime-staking" name = "substrate-runtime-staking"
version = "0.1.0" version = "0.1.0"
dependencies = [ 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)", "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", "safe-mix 0.1.0",
"serde 1.0.63 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.63 (registry+https://github.com/rust-lang/crates.io-index)",
"substrate-codec 0.1.0", "substrate-codec 0.1.0",
"substrate-keyring 0.1.0", "substrate-keyring 0.1.0",
"substrate-primitives 0.1.0", "substrate-primitives 0.1.0",
"substrate-runtime-consensus 0.1.0", "substrate-runtime-consensus 0.1.0",
"substrate-runtime-contract 0.1.0",
"substrate-runtime-io 0.1.0", "substrate-runtime-io 0.1.0",
"substrate-runtime-primitives 0.1.0", "substrate-runtime-primitives 0.1.0",
"substrate-runtime-sandbox 0.1.0", "substrate-runtime-sandbox 0.1.0",
+1
View File
@@ -43,6 +43,7 @@ members = [
"substrate/runtime-std", "substrate/runtime-std",
"substrate/runtime-support", "substrate/runtime-support",
"substrate/runtime/consensus", "substrate/runtime/consensus",
"substrate/runtime/contract",
"substrate/runtime/council", "substrate/runtime/council",
"substrate/runtime/democracy", "substrate/runtime/democracy",
"substrate/runtime/executive", "substrate/runtime/executive",
+12 -2
View File
@@ -548,6 +548,17 @@ dependencies = [
"substrate-runtime-system 0.1.0", "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]] [[package]]
name = "substrate-runtime-council" name = "substrate-runtime-council"
version = "0.1.0" version = "0.1.0"
@@ -667,14 +678,13 @@ name = "substrate-runtime-staking"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"hex-literal 0.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", "safe-mix 0.1.0",
"serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
"substrate-codec 0.1.0", "substrate-codec 0.1.0",
"substrate-keyring 0.1.0", "substrate-keyring 0.1.0",
"substrate-primitives 0.1.0", "substrate-primitives 0.1.0",
"substrate-runtime-consensus 0.1.0", "substrate-runtime-consensus 0.1.0",
"substrate-runtime-contract 0.1.0",
"substrate-runtime-io 0.1.0", "substrate-runtime-io 0.1.0",
"substrate-runtime-primitives 0.1.0", "substrate-runtime-primitives 0.1.0",
"substrate-runtime-sandbox 0.1.0", "substrate-runtime-sandbox 0.1.0",
+12 -2
View File
@@ -548,6 +548,17 @@ dependencies = [
"substrate-runtime-system 0.1.0", "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]] [[package]]
name = "substrate-runtime-council" name = "substrate-runtime-council"
version = "0.1.0" version = "0.1.0"
@@ -667,14 +678,13 @@ name = "substrate-runtime-staking"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"hex-literal 0.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", "safe-mix 0.1.0",
"serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
"substrate-codec 0.1.0", "substrate-codec 0.1.0",
"substrate-keyring 0.1.0", "substrate-keyring 0.1.0",
"substrate-primitives 0.1.0", "substrate-primitives 0.1.0",
"substrate-runtime-consensus 0.1.0", "substrate-runtime-consensus 0.1.0",
"substrate-runtime-contract 0.1.0",
"substrate-runtime-io 0.1.0", "substrate-runtime-io 0.1.0",
"substrate-runtime-primitives 0.1.0", "substrate-runtime-primitives 0.1.0",
"substrate-runtime-sandbox 0.1.0", "substrate-runtime-sandbox 0.1.0",
@@ -0,0 +1,25 @@
[package]
name = "substrate-runtime-contract"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
[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",
]
@@ -14,19 +14,62 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>. // along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! 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 rstd::prelude::*;
use sandbox; use codec::Slicable;
use {AccountDb, Module, OverlayAccountDb, Trait};
use parity_wasm::elements::{self, External, MemoryType}; use parity_wasm::elements::{self, External, MemoryType};
use pwasm_utils;
use pwasm_utils::rules; 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<Vec<u8>>;
/// Sets the storage entry by the given key to the specified value.
fn set_storage(&mut self, key: &[u8], value: Option<Vec<u8>>);
// 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. /// Error that can occur while preparing or executing wasm smart-contract.
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum Error { pub enum Error {
@@ -67,26 +110,22 @@ pub enum Error {
Memory, Memory,
} }
struct ExecutionExt<'a, 'b: 'a, T: Trait + 'b> { struct Runtime<'a, T: Ext + 'a> {
account_db: &'a mut OverlayAccountDb<'b, T>, ext: &'a mut T,
account: T::AccountId,
memory: sandbox::Memory, memory: sandbox::Memory,
gas_used: u64, gas_used: u64,
gas_limit: u64, gas_limit: u64,
} }
impl<'a, 'b: 'a, T: Trait> ExecutionExt<'a, 'b, T> { impl<'a, T: Ext + 'a> Runtime<'a, T> {
fn account(&self) -> &T::AccountId {
&self.account
}
fn account_db(&self) -> &OverlayAccountDb<T> {
self.account_db
}
fn account_db_mut(&mut self) -> &mut OverlayAccountDb<'b, T> {
self.account_db
}
fn memory(&self) -> &sandbox::Memory { fn memory(&self) -> &sandbox::Memory {
&self.memory &self.memory
} }
fn ext(&self) -> &T {
self.ext
}
fn ext_mut(&mut self) -> &mut T {
self.ext
}
/// Account for used gas. /// Account for used gas.
/// ///
/// Returns `false` if there is not enough gas or addition of the specified /// 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], code: &[u8],
account: &T::AccountId, ext: &'a mut T,
account_db: &'a mut OverlayAccountDb<'b, T>,
gas_limit: u64, gas_limit: u64,
) -> Result<(), Error> { ) -> Result<(), Error> {
// ext_gas(amount: u32) // 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. // Account for used gas. Traps if gas used is greater than gas limit.
// //
// - amount: How much gas is used. // - amount: How much gas is used.
fn ext_gas<T: Trait>(e: &mut ExecutionExt<T>, args: &[sandbox::TypedValue]) -> Result<sandbox::ReturnValue, sandbox::HostError> { fn ext_gas<T: Ext>(e: &mut Runtime<T>, args: &[sandbox::TypedValue]) -> Result<sandbox::ReturnValue, sandbox::HostError> {
let amount = args[0].as_i32().unwrap() as u32; let amount = args[0].as_i32().unwrap() as u32;
if e.charge_gas(amount as u64) { if e.charge_gas(amount as u64) {
Ok(sandbox::ReturnValue::Unit) Ok(sandbox::ReturnValue::Unit)
@@ -135,7 +174,10 @@ pub(crate) fn execute<'a, 'b: 'a, T: Trait>(
// at the given location will be removed. // at the given location will be removed.
// - value_ptr: pointer into the linear memory // - 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. // where the value to set is placed. If `value_non_null` is set to 0, then this parameter is ignored.
fn ext_set_storage<T: Trait>(e: &mut ExecutionExt<T>, args: &[sandbox::TypedValue]) -> Result<sandbox::ReturnValue, sandbox::HostError> { fn ext_set_storage<T: Ext>(
e: &mut Runtime<T>,
args: &[sandbox::TypedValue],
) -> Result<sandbox::ReturnValue, sandbox::HostError> {
let location_ptr = args[0].as_i32().unwrap() as u32; let location_ptr = args[0].as_i32().unwrap() as u32;
let value_non_null = args[1].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 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]; let mut location = [0; 32];
e.memory().get(location_ptr, &mut location)?; 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]; let mut value = [0; 32];
e.memory().get(value_ptr, &mut value)?; e.memory().get(value_ptr, &mut value)?;
e.account_db_mut() Some(value.to_vec())
.set_storage(&account, location.to_vec(), Some(value.to_vec()));
} else { } else {
e.account_db_mut() None
.set_storage(&account, location.to_vec(), None); };
} e.ext_mut().set_storage(
&location,
value,
);
Ok(sandbox::ReturnValue::Unit) 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. // memory where the location of the requested value is placed.
// - dest_ptr: pointer where contents of the specified storage location // - dest_ptr: pointer where contents of the specified storage location
// should be placed. // should be placed.
fn ext_get_storage<T: Trait>(e: &mut ExecutionExt<T>, args: &[sandbox::TypedValue]) -> Result<sandbox::ReturnValue, sandbox::HostError> { fn ext_get_storage<T: Ext>(e: &mut Runtime<T>, args: &[sandbox::TypedValue]) -> Result<sandbox::ReturnValue, sandbox::HostError> {
let location_ptr = args[0].as_i32().unwrap() as u32; let location_ptr = args[0].as_i32().unwrap() as u32;
let dest_ptr = args[1].as_i32().unwrap() as u32; let dest_ptr = args[1].as_i32().unwrap() as u32;
let mut location = [0; 32]; let mut location = [0; 32];
e.memory().get(location_ptr, &mut location)?; e.memory().get(location_ptr, &mut location)?;
let account = e.account().clone(); if let Some(value) = e.ext().get_storage(&location) {
if let Some(value) = e.account_db_mut().get_storage(&account, &location) {
e.memory().set(dest_ptr, &value)?; e.memory().set(dest_ptr, &value)?;
} else { } else {
e.memory().set(dest_ptr, &[0u8; 32])?; e.memory().set(dest_ptr, &[0u8; 32])?;
@@ -185,8 +227,8 @@ pub(crate) fn execute<'a, 'b: 'a, T: Trait>(
Ok(sandbox::ReturnValue::Unit) Ok(sandbox::ReturnValue::Unit)
} }
// ext_transfer(transfer_to: u32, transfer_to_len: u32, value: u32) // ext_transfer(transfer_to: u32, transfer_to_len: u32, value_ptr: u32, value_len: u32)
fn ext_transfer<T: Trait>(e: &mut ExecutionExt<T>, args: &[sandbox::TypedValue]) -> Result<sandbox::ReturnValue, sandbox::HostError> { fn ext_transfer<T: Ext>(e: &mut Runtime<T>, args: &[sandbox::TypedValue]) -> Result<sandbox::ReturnValue, sandbox::HostError> {
let transfer_to_ptr = args[0].as_i32().unwrap() as u32; let transfer_to_ptr = args[0].as_i32().unwrap() as u32;
let transfer_to_len = args[1].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_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)?; e.memory().get(value_ptr, &mut value_buf)?;
let value = T::Balance::decode(&mut &value_buf[..]).unwrap(); let value = T::Balance::decode(&mut &value_buf[..]).unwrap();
let account = e.account().clone(); e.ext_mut().transfer(&transfer_to, value);
if let Some(commit_state) =
Module::<T>::effect_transfer(&account, &transfer_to, value, e.account_db())
.map_err(|_| sandbox::Error::Execution)?
{
e.account_db_mut().merge(commit_state);
}
Ok(sandbox::ReturnValue::Unit) Ok(sandbox::ReturnValue::Unit)
} }
// ext_create(code_ptr: u32, code_len: u32, value: u32) // ext_create(code_ptr: u32, code_len: u32, value_ptr: u32, value_len: u32)
fn ext_create<T: Trait>(e: &mut ExecutionExt<T>, args: &[sandbox::TypedValue]) -> Result<sandbox::ReturnValue, sandbox::HostError> { fn ext_create<T: Ext>(e: &mut Runtime<T>, args: &[sandbox::TypedValue]) -> Result<sandbox::ReturnValue, sandbox::HostError> {
let code_ptr = args[0].as_i32().unwrap() as u32; let code_ptr = args[0].as_i32().unwrap() as u32;
let code_len = args[1].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_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); code.resize(code_len as usize, 0u8);
e.memory().get(code_ptr, &mut code)?; e.memory().get(code_ptr, &mut code)?;
let account = e.account().clone(); e.ext_mut().create(&code, value);
if let Some(commit_state) =
Module::<T>::effect_create(&account, &code, value, e.account_db())
.map_err(|_| sandbox::Error::Execution)?
{
e.account_db_mut().merge(commit_state);
}
Ok(sandbox::ReturnValue::Unit) 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. // TODO: ext_balance, ext_address, ext_callvalue, etc.
imports.add_memory("env", "memory", memory.clone()); imports.add_memory("env", "memory", memory.clone());
let mut exec_ext = ExecutionExt { let mut runtime = Runtime {
account: account.clone(), ext,
account_db,
memory, memory,
gas_limit, gas_limit,
gas_used: 0, gas_used: 0,
}; };
let mut instance = let mut instance =
sandbox::Instance::new(&instrumented_code, &imports, &mut exec_ext) sandbox::Instance::new(&instrumented_code, &imports, &mut runtime)
.map_err(|_| Error::Instantiate)?; .map_err(|_| Error::Instantiate)?;
instance instance
.invoke(b"call", &[], &mut exec_ext) .invoke(b"call", &[], &mut runtime)
.map(|_| ()) .map(|_| ())
.map_err(|_| Error::Invoke) .map_err(|_| Error::Invoke)
} }
@@ -446,9 +475,51 @@ mod tests {
use super::*; use super::*;
use std::fmt; use std::fmt;
use wabt; use wabt;
use runtime_io::with_externalities; use std::collections::HashMap;
use mock::{Staking, Test, new_test_ext};
use ::{CodeOf, ContractAddressFor, DirectAccountDb, FreeBalance, StorageMap}; #[derive(Debug, PartialEq, Eq)]
struct CreateEntry {
code: Vec<u8>,
endownment: u64,
}
#[derive(Debug, PartialEq, Eq)]
struct TransferEntry {
to: u64,
value: u64,
}
#[derive(Default)]
struct MockExt {
storage: HashMap<Vec<u8>, Vec<u8>>,
creates: Vec<CreateEntry>,
transfers: Vec<TransferEntry>,
}
impl Ext for MockExt {
type AccountId = u64;
type Balance = u64;
fn get_storage(&self, key: &[u8]) -> Option<Vec<u8>> {
self.storage.get(key).cloned()
}
fn set_storage(&mut self, key: &[u8], value: Option<Vec<u8>>) {
*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 { impl fmt::Debug for PreparedContract {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@@ -544,19 +615,13 @@ mod tests {
fn contract_transfer() { fn contract_transfer() {
let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap(); let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap();
with_externalities(&mut new_test_ext(1, 3, 1, false), || { let mut mock_ext = MockExt::default();
<FreeBalance<Test>>::insert(0, 111); execute(&code_transfer, &mut mock_ext, 50_000).unwrap();
<FreeBalance<Test>>::insert(1, 0);
<FreeBalance<Test>>::insert(2, 30);
<CodeOf<Test>>::insert(1, code_transfer.to_vec()); assert_eq!(&mock_ext.transfers, &[TransferEntry {
to: 2,
assert_ok!(Staking::transfer(&0, 1, 11)); value: 6,
}]);
assert_eq!(Staking::balance(&0), 100);
assert_eq!(Staking::balance(&1), 5);
assert_eq!(Staking::balance(&2), 36);
});
} }
/// Returns code that uses `ext_create` runtime call. /// Returns code that uses `ext_create` runtime call.
@@ -609,22 +674,15 @@ r#"
let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap(); let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap();
let code_create = wabt::wat2wasm(&code_create(&code_transfer)).unwrap(); let code_create = wabt::wat2wasm(&code_create(&code_transfer)).unwrap();
with_externalities(&mut new_test_ext(1, 3, 1, false), || { let mut mock_ext = MockExt::default();
<FreeBalance<Test>>::insert(0, 111); execute(&code_create, &mut mock_ext, 50_000).unwrap();
<FreeBalance<Test>>::insert(1, 0);
<CodeOf<Test>>::insert(1, code_create.to_vec()); assert_eq!(&mock_ext.creates, &[
CreateEntry {
// When invoked, the contract at address `1` must create a contract with 'transfer' code. code: code_transfer,
assert_ok!(Staking::transfer(&0, 1, 11)); endownment: 3,
}
let derived_address = ]);
<Test as Trait>::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);
});
} }
/// This code a value from the storage, increment it's first byte /// This code a value from the storage, increment it's first byte
@@ -669,32 +727,25 @@ r#"
fn contract_adder() { fn contract_adder() {
let code_adder = wabt::wat2wasm(CODE_ADDER).unwrap(); let code_adder = wabt::wat2wasm(CODE_ADDER).unwrap();
with_externalities(&mut new_test_ext(1, 3, 1, false), || { let mut mock_ext = MockExt::default();
<FreeBalance<Test>>::insert(0, 111);
<FreeBalance<Test>>::insert(1, 0);
<CodeOf<Test>>::insert(1, code_adder);
assert_ok!(Staking::transfer(&0, 1, 1)); // Execute the test twice.
assert_ok!(Staking::transfer(&0, 1, 1)); execute(&code_adder, &mut mock_ext, 50_000).unwrap();
execute(&code_adder, &mut mock_ext, 50_000).unwrap();
let storage_addr = [0x01u8; 32]; let storage_addr = [0x01u8; 32];
let value =
AccountDb::<Test>::get_storage(&DirectAccountDb, &1, &storage_addr).unwrap();
assert_eq!( assert_eq!(
&value, &mock_ext.storage.get(&storage_addr[..]).unwrap()[..],
&[ &[
2, 0, 0, 0, 0, 0, 0, 0, 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, 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 // This code should make 100_000 iterations.
// consume more than 100_000 units of gas.
const CODE_LOOP: &str = const CODE_LOOP: &str =
r#" r#"
(module (module
@@ -726,21 +777,16 @@ r#"
fn contract_out_of_gas() { fn contract_out_of_gas() {
let code_loop = wabt::wat2wasm(CODE_LOOP).unwrap(); let code_loop = wabt::wat2wasm(CODE_LOOP).unwrap();
with_externalities(&mut new_test_ext(1, 3, 1, false), || { let mut mock_ext = MockExt::default();
// Set initial balances.
<FreeBalance<Test>>::insert(0, 111);
<FreeBalance<Test>>::insert(1, 0);
<CodeOf<Test>>::insert(1, code_loop.to_vec()); assert_matches!(
execute(&code_loop, &mut mock_ext, 900_000),
// Transfer some balance from 0 to 1. This will trigger execution Err(_)
// of the smart-contract code at address 1. );
assert_ok!(Staking::transfer(&0, 1, 11)); assert_matches!(
execute(&code_loop, &mut mock_ext, 937_000),
// The balance should remain unchanged since we are expecting Ok(_)
// out-of-gas error which will revert transfer. );
assert_eq!(Staking::balance(&0), 111);
});
} }
const CODE_MEM: &str = const CODE_MEM: &str =
@@ -759,19 +805,11 @@ r#"
fn contract_internal_mem() { fn contract_internal_mem() {
let code_mem = wabt::wat2wasm(CODE_MEM).unwrap(); let code_mem = wabt::wat2wasm(CODE_MEM).unwrap();
with_externalities(&mut new_test_ext(1, 3, 1, false), || { let mut mock_ext = MockExt::default();
// Set initial balances.
<FreeBalance<Test>>::insert(0, 111);
<FreeBalance<Test>>::insert(1, 0);
<CodeOf<Test>>::insert(1, code_mem.to_vec()); assert_matches!(
execute(&code_mem, &mut mock_ext, 100_000),
// Transfer some balance from 0 to 1. Err(_)
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);
});
} }
} }
@@ -10,6 +10,7 @@ safe-mix = { path = "../../../safe-mix", default_features = false}
substrate-keyring = { path = "../../keyring", optional = true } substrate-keyring = { path = "../../keyring", optional = true }
substrate-codec = { path = "../../codec", default_features = false } substrate-codec = { path = "../../codec", default_features = false }
substrate-primitives = { path = "../../primitives", 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-std = { path = "../../runtime-std", default_features = false }
substrate-runtime-io = { path = "../../runtime-io", default_features = false } substrate-runtime-io = { path = "../../runtime-io", default_features = false }
substrate-runtime-sandbox = { path = "../../runtime-sandbox", 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-consensus = { path = "../consensus", default_features = false }
substrate-runtime-system = { path = "../system", default_features = false } substrate-runtime-system = { path = "../system", default_features = false }
substrate-runtime-session = { path = "../session", 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] [dev-dependencies]
wabt = "0.1.7" wabt = "0.1.7"
assert_matches = "1.1"
[features] [features]
default = ["std"] default = ["std"]
@@ -33,6 +31,7 @@ std = [
"substrate-keyring", "substrate-keyring",
"substrate-codec/std", "substrate-codec/std",
"substrate-primitives/std", "substrate-primitives/std",
"substrate-runtime-contract/std",
"substrate-runtime-std/std", "substrate-runtime-std/std",
"substrate-runtime-io/std", "substrate-runtime-io/std",
"substrate-runtime-sandbox/std", "substrate-runtime-sandbox/std",
@@ -40,6 +39,4 @@ std = [
"substrate-runtime-primitives/std", "substrate-runtime-primitives/std",
"substrate-runtime-session/std", "substrate-runtime-session/std",
"substrate-runtime-system/std", "substrate-runtime-system/std",
"pwasm-utils/std",
"parity-wasm/std",
] ]
+164 -12
View File
@@ -24,10 +24,6 @@ extern crate serde;
#[cfg(test)] #[cfg(test)]
extern crate wabt; extern crate wabt;
#[cfg(test)]
#[macro_use]
extern crate assert_matches;
#[macro_use] #[macro_use]
extern crate substrate_runtime_support as runtime_support; 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_codec as codec;
extern crate substrate_primitives; extern crate substrate_primitives;
extern crate substrate_runtime_contract as contract;
extern crate substrate_runtime_io as runtime_io; extern crate substrate_runtime_io as runtime_io;
extern crate substrate_runtime_primitives as primitives; extern crate substrate_runtime_primitives as primitives;
extern crate substrate_runtime_consensus as consensus; extern crate substrate_runtime_consensus as consensus;
extern crate substrate_runtime_sandbox as sandbox; extern crate substrate_runtime_sandbox as sandbox;
extern crate substrate_runtime_session as session; extern crate substrate_runtime_session as session;
extern crate substrate_runtime_system as system; extern crate substrate_runtime_system as system;
extern crate pwasm_utils;
extern crate parity_wasm;
#[cfg(test)] use std::fmt::Debug; #[cfg(test)] use std::fmt::Debug;
use rstd::prelude::*; use rstd::prelude::*;
@@ -55,10 +50,6 @@ use runtime_support::{StorageValue, StorageMap, Parameter};
use runtime_support::dispatch::Result; use runtime_support::dispatch::Result;
use primitives::traits::{Zero, One, Bounded, RefInto, SimpleArithmetic, Executable, MakePayment, As}; use primitives::traits::{Zero, One, Bounded, RefInto, SimpleArithmetic, Executable, MakePayment, As};
mod contract;
#[cfg(test)]
mod mock;
#[cfg(test)] #[cfg(test)]
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum LockStatus<BlockNumber: Debug + PartialEq + Clone> { pub enum LockStatus<BlockNumber: Debug + PartialEq + Clone> {
@@ -629,7 +620,11 @@ impl<T: Trait> Module<T> {
} else { } else {
// TODO: logging (logs are just appended into a notable storage-based vector and cleared every // TODO: logging (logs are just appended into a notable storage-based vector and cleared every
// block). // 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 { Ok(if should_commit {
@@ -640,6 +635,36 @@ impl<T: Trait> Module<T> {
} }
} }
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<Vec<u8>> {
self.account_db.get_storage(&self.account, key)
}
fn set_storage(&mut self, key: &[u8], value: Option<Vec<u8>>) {
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::<T>::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::<T>::effect_transfer(&self.account, to, value, self.account_db)
{
self.account_db.merge(commit_state);
}
}
}
impl<T: Trait> MakePayment<T::AccountId> for Module<T> { impl<T: Trait> MakePayment<T::AccountId> for Module<T> {
fn make_payment(transactor: &T::AccountId, encoded_len: usize) -> bool { fn make_payment(transactor: &T::AccountId, encoded_len: usize) -> bool {
let b = Self::free_balance(transactor); let b = Self::free_balance(transactor);
@@ -759,7 +784,62 @@ impl<T: Trait> primitives::BuildExternalities for GenesisConfig<T> {
mod tests { mod tests {
use super::*; use super::*;
use runtime_io::with_externalities; 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 = <Self as HasPublicAux>::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::<Test>::default().build_externalities();
t.extend(consensus::GenesisConfig::<Test>{
code: vec![],
authorities: vec![],
}.build_externalities());
t.extend(session::GenesisConfig::<Test>{
session_length,
validators: vec![10, 20],
}.build_externalities());
t.extend(GenesisConfig::<Test>{
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<Test>;
type Session = session::Module<Test>;
type Staking = Module<Test>;
#[test] #[test]
fn staking_should_work() { fn staking_should_work() {
@@ -1043,4 +1123,76 @@ mod tests {
assert_eq!(Staking::free_balance(&2), 42); 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), || {
<FreeBalance<Test>>::insert(0, 111);
<FreeBalance<Test>>::insert(1, 0);
<FreeBalance<Test>>::insert(2, 30);
<CodeOf<Test>>::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.
<FreeBalance<Test>>::insert(0, 111);
<FreeBalance<Test>>::insert(1, 0);
<CodeOf<Test>>::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);
});
}
} }
@@ -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 <http://www.gnu.org/licenses/>.
//! 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 = <Self as HasPublicAux>::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::<Test>::default().build_externalities();
t.extend(consensus::GenesisConfig::<Test>{
code: vec![],
authorities: vec![],
}.build_externalities());
t.extend(session::GenesisConfig::<Test>{
session_length,
validators: vec![10, 20],
}.build_externalities());
t.extend(GenesisConfig::<Test>{
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<Test>;
pub type Session = session::Module<Test>;
pub type Staking = Module<Test>;