mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 12:11:02 +00:00
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:
committed by
Gav Wood
parent
68e468f59c
commit
ff0d9c3359
Generated
+14
-3
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
Generated
+12
-2
@@ -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",
|
||||
|
||||
BIN
Binary file not shown.
Binary file not shown.
+12
-2
@@ -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",
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -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",
|
||||
]
|
||||
+166
-128
@@ -14,19 +14,62 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// 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,
|
||||
&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",
|
||||
]
|
||||
|
||||
@@ -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>;
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
Reference in New Issue
Block a user