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",
]
[[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",
+1
View File
@@ -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",
+12 -2
View File
@@ -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",
+12 -2
View File
@@ -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",
@@ -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",
]
@@ -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 <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 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<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.
#[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<T> {
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<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;
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<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 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<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 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<T: Trait>(e: &mut ExecutionExt<T>, args: &[sandbox::TypedValue]) -> Result<sandbox::ReturnValue, sandbox::HostError> {
// ext_transfer(transfer_to: u32, transfer_to_len: u32, value_ptr: u32, value_len: u32)
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_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::<T>::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<T: Trait>(e: &mut ExecutionExt<T>, args: &[sandbox::TypedValue]) -> Result<sandbox::ReturnValue, sandbox::HostError> {
// ext_create(code_ptr: u32, code_len: u32, value_ptr: u32, value_len: u32)
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_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::<T>::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<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 {
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), || {
<FreeBalance<Test>>::insert(0, 111);
<FreeBalance<Test>>::insert(1, 0);
<FreeBalance<Test>>::insert(2, 30);
let mut mock_ext = MockExt::default();
execute(&code_transfer, &mut mock_ext, 50_000).unwrap();
<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);
});
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), || {
<FreeBalance<Test>>::insert(0, 111);
<FreeBalance<Test>>::insert(1, 0);
let mut mock_ext = MockExt::default();
execute(&code_create, &mut mock_ext, 50_000).unwrap();
<CodeOf<Test>>::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 =
<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);
});
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), || {
<FreeBalance<Test>>::insert(0, 111);
<FreeBalance<Test>>::insert(1, 0);
<CodeOf<Test>>::insert(1, code_adder);
let mut mock_ext = MockExt::default();
assert_ok!(Staking::transfer(&0, 1, 1));
assert_ok!(Staking::transfer(&0, 1, 1));
// 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];
let value =
AccountDb::<Test>::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 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.
<FreeBalance<Test>>::insert(0, 111);
<FreeBalance<Test>>::insert(1, 0);
let mut mock_ext = MockExt::default();
<CodeOf<Test>>::insert(1, code_loop.to_vec());
// 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.
<FreeBalance<Test>>::insert(0, 111);
<FreeBalance<Test>>::insert(1, 0);
let mut mock_ext = MockExt::default();
<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);
});
assert_matches!(
execute(&code_mem, &mut mock_ext, 100_000),
Err(_)
);
}
}
@@ -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",
]
+164 -12
View File
@@ -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<BlockNumber: Debug + PartialEq + Clone> {
@@ -629,7 +620,11 @@ impl<T: Trait> Module<T> {
} 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<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> {
fn make_payment(transactor: &T::AccountId, encoded_len: usize) -> bool {
let b = Self::free_balance(transactor);
@@ -759,7 +784,62 @@ impl<T: Trait> primitives::BuildExternalities for GenesisConfig<T> {
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 = <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]
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), || {
<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>;