mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 20:21:03 +00:00
Smart-contract inspection and instrumentation. (#165)
* Extract mock module. * Wasm smart-contract instrumentation * Rebuild binaries. * Memory limits and tests.
This commit is contained in:
committed by
Gav Wood
parent
d97110733d
commit
7312a26d09
Generated
+23
@@ -1129,6 +1129,14 @@ dependencies = [
|
||||
"parking_lot 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parity-wasm"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parity-wordlist"
|
||||
version = "1.2.0"
|
||||
@@ -1499,6 +1507,16 @@ dependencies = [
|
||||
name = "pwasm-libc"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "pwasm-utils"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parity-wasm 0.30.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.1"
|
||||
@@ -2179,7 +2197,10 @@ 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.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"substrate-codec 0.1.0",
|
||||
@@ -2824,6 +2845,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
"checksum ole32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2c49021782e5233cd243168edfa8037574afed4eba4bbaf538b3d8d1789d8c"
|
||||
"checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37"
|
||||
"checksum parity-wasm 0.27.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1ba1ceaec13865445bcf05117867e4c6456d91c3617cdff2f3ef77b92b18cd12"
|
||||
"checksum parity-wasm 0.30.0 (registry+https://github.com/rust-lang/crates.io-index)" = "41083957b80abb8a01fac4d2773d5f92653aed8f0b740c8d3da1da62c7857abe"
|
||||
"checksum parity-wordlist 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d0dec124478845b142f68b446cbee953d14d4b41f1bc0425024417720dce693"
|
||||
"checksum parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "149d8f5b97f3c1133e3cfcd8886449959e856b557ff281e292b733d7c69e005e"
|
||||
"checksum parking_lot 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3e7f7c9857874e54afeb950eebeae662b1e51a2493666d2ea4c0a5d91dcf0412"
|
||||
@@ -2837,6 +2859,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
"checksum proc-macro-hack 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ba8d4f9257b85eb6cdf13f055cea3190520aab1409ca2ab43493ea4820c25f0"
|
||||
"checksum proc-macro-hack-impl 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d5cb6f960ad471404618e9817c0e5d10b1ae74cfdf01fab89ea0641fe7fb2892"
|
||||
"checksum pulldown-cmark 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8361e81576d2e02643b04950e487ec172b687180da65c731c03cf336784e6c07"
|
||||
"checksum pwasm-utils 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a3a822d2a1624b10c46572c231c149575bcc261c37d84fd3f1a2f5ae1f65515"
|
||||
"checksum quick-error 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eda5fe9b71976e62bc81b781206aaa076401769b2143379d3eb2118388babac4"
|
||||
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
|
||||
"checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1"
|
||||
|
||||
Generated
+22
@@ -389,6 +389,14 @@ dependencies = [
|
||||
"parking_lot 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parity-wasm"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.4.8"
|
||||
@@ -469,6 +477,16 @@ dependencies = [
|
||||
name = "pwasm-libc"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "pwasm-utils"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parity-wasm 0.30.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "0.3.15"
|
||||
@@ -813,6 +831,8 @@ 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",
|
||||
@@ -1087,6 +1107,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
"checksum odds 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)" = "4eae0151b9dacf24fcc170d9995e511669a082856a91f958a2fe380bfab3fb22"
|
||||
"checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37"
|
||||
"checksum parity-wasm 0.27.6 (registry+https://github.com/rust-lang/crates.io-index)" = "bd4dc02a80a0315b109e48992c46942c79bcdb8fac416dd575d330ed9ced6cbd"
|
||||
"checksum parity-wasm 0.30.0 (registry+https://github.com/rust-lang/crates.io-index)" = "41083957b80abb8a01fac4d2773d5f92653aed8f0b740c8d3da1da62c7857abe"
|
||||
"checksum parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "149d8f5b97f3c1133e3cfcd8886449959e856b557ff281e292b733d7c69e005e"
|
||||
"checksum parking_lot 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9fd9d732f2de194336fb02fe11f9eed13d9e76f13f4315b4d88a14ca411750cd"
|
||||
"checksum parking_lot_core 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "9f35048d735bb93dd115a0030498785971aab3234d311fbe273d020084d26bd8"
|
||||
@@ -1094,6 +1115,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
"checksum plain_hasher 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "83ae80873992f511142c07d0ec6c44de5636628fdb7e204abd655932ea79d995"
|
||||
"checksum proc-macro-hack 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ba8d4f9257b85eb6cdf13f055cea3190520aab1409ca2ab43493ea4820c25f0"
|
||||
"checksum proc-macro-hack-impl 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d5cb6f960ad471404618e9817c0e5d10b1ae74cfdf01fab89ea0641fe7fb2892"
|
||||
"checksum pwasm-utils 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a3a822d2a1624b10c46572c231c149575bcc261c37d84fd3f1a2f5ae1f65515"
|
||||
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
|
||||
"checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1"
|
||||
"checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5"
|
||||
|
||||
BIN
Binary file not shown.
Binary file not shown.
+22
@@ -354,6 +354,14 @@ dependencies = [
|
||||
"parking_lot 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parity-wasm"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.4.8"
|
||||
@@ -469,6 +477,16 @@ dependencies = [
|
||||
name = "pwasm-libc"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "pwasm-utils"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parity-wasm 0.30.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "0.3.15"
|
||||
@@ -813,6 +831,8 @@ 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",
|
||||
@@ -1087,6 +1107,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
"checksum odds 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)" = "4eae0151b9dacf24fcc170d9995e511669a082856a91f958a2fe380bfab3fb22"
|
||||
"checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37"
|
||||
"checksum parity-wasm 0.27.6 (registry+https://github.com/rust-lang/crates.io-index)" = "bd4dc02a80a0315b109e48992c46942c79bcdb8fac416dd575d330ed9ced6cbd"
|
||||
"checksum parity-wasm 0.30.0 (registry+https://github.com/rust-lang/crates.io-index)" = "41083957b80abb8a01fac4d2773d5f92653aed8f0b740c8d3da1da62c7857abe"
|
||||
"checksum parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "149d8f5b97f3c1133e3cfcd8886449959e856b557ff281e292b733d7c69e005e"
|
||||
"checksum parking_lot 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9fd9d732f2de194336fb02fe11f9eed13d9e76f13f4315b4d88a14ca411750cd"
|
||||
"checksum parking_lot_core 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "9f35048d735bb93dd115a0030498785971aab3234d311fbe273d020084d26bd8"
|
||||
@@ -1094,6 +1115,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
"checksum plain_hasher 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "83ae80873992f511142c07d0ec6c44de5636628fdb7e204abd655932ea79d995"
|
||||
"checksum proc-macro-hack 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ba8d4f9257b85eb6cdf13f055cea3190520aab1409ca2ab43493ea4820c25f0"
|
||||
"checksum proc-macro-hack-impl 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d5cb6f960ad471404618e9817c0e5d10b1ae74cfdf01fab89ea0641fe7fb2892"
|
||||
"checksum pwasm-utils 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a3a822d2a1624b10c46572c231c149575bcc261c37d84fd3f1a2f5ae1f65515"
|
||||
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
|
||||
"checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1"
|
||||
"checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5"
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -18,9 +18,12 @@ 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"]
|
||||
@@ -37,4 +40,6 @@ std = [
|
||||
"substrate-runtime-primitives/std",
|
||||
"substrate-runtime-session/std",
|
||||
"substrate-runtime-system/std",
|
||||
"pwasm-utils/std",
|
||||
"parity-wasm/std",
|
||||
]
|
||||
|
||||
@@ -16,16 +16,64 @@
|
||||
|
||||
//! Smart-contract execution module.
|
||||
|
||||
// TODO: Extract to it's own crate?
|
||||
|
||||
use codec::Slicable;
|
||||
use primitives::traits::As;
|
||||
use rstd::prelude::*;
|
||||
use sandbox;
|
||||
use {AccountDb, Module, OverlayAccountDb, Trait};
|
||||
|
||||
use parity_wasm::elements::{self, External, MemoryType};
|
||||
use pwasm_utils;
|
||||
use pwasm_utils::rules;
|
||||
|
||||
/// Error that can occur while preparing or executing wasm smart-contract.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Error {
|
||||
/// Error happened while serializing the module.
|
||||
Serialization,
|
||||
|
||||
/// Error happened while deserializing the module.
|
||||
Deserialization,
|
||||
|
||||
/// Internal memory declaration has been found in the module.
|
||||
InternalMemoryDeclared,
|
||||
|
||||
/// Gas instrumentation failed.
|
||||
///
|
||||
/// This most likely indicates the module isn't valid.
|
||||
GasInstrumentation,
|
||||
|
||||
/// Stack instrumentation failed.
|
||||
///
|
||||
/// This most likely indicates the module isn't valid.
|
||||
StackHeightInstrumentation,
|
||||
|
||||
/// Error happened during invocation of the contract's entrypoint.
|
||||
///
|
||||
/// Most likely because of trap.
|
||||
Invoke,
|
||||
|
||||
/// Error happened during instantiation.
|
||||
///
|
||||
/// This might indicate that `start` function trapped, or module isn't
|
||||
/// instantiable and/or unlinkable.
|
||||
Instantiate,
|
||||
|
||||
/// Memory creation error.
|
||||
///
|
||||
/// This might happen when the memory import has invalid descriptor or
|
||||
/// requested too much resources.
|
||||
Memory,
|
||||
}
|
||||
|
||||
struct ExecutionExt<'a, 'b: 'a, T: Trait + 'b> {
|
||||
account_db: &'a mut OverlayAccountDb<'b, T>,
|
||||
account: T::AccountId,
|
||||
memory: sandbox::Memory,
|
||||
gas_used: u64,
|
||||
gas_limit: u64,
|
||||
}
|
||||
impl<'a, 'b: 'a, T: Trait> ExecutionExt<'a, 'b, T> {
|
||||
fn account(&self) -> &T::AccountId {
|
||||
@@ -40,13 +88,44 @@ impl<'a, 'b: 'a, T: Trait> ExecutionExt<'a, 'b, T> {
|
||||
fn memory(&self) -> &sandbox::Memory {
|
||||
&self.memory
|
||||
}
|
||||
/// Account for used gas.
|
||||
///
|
||||
/// Returns `false` if there is not enough gas or addition of the specified
|
||||
/// amount of gas has lead to overflow. On success returns `true`.
|
||||
///
|
||||
/// Intuition about the return value sense is to answer the question 'are we allowed to continue?'
|
||||
fn charge_gas(&mut self, amount: u64) -> bool {
|
||||
match self.gas_used.checked_add(amount) {
|
||||
None => false,
|
||||
Some(val) if val > self.gas_limit => false,
|
||||
Some(val) => {
|
||||
self.gas_used = val;
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn execute<'a, 'b: 'a, T: Trait>(
|
||||
code: &[u8],
|
||||
account: &T::AccountId,
|
||||
account_db: &'a mut OverlayAccountDb<'b, T>,
|
||||
) -> bool {
|
||||
gas_limit: u64,
|
||||
) -> Result<(), Error> {
|
||||
// ext_gas(amount: u32)
|
||||
//
|
||||
// Account for used gas. Traps if gas used is greater than gas limit.
|
||||
//
|
||||
// - amount: How much gas is used.
|
||||
fn ext_gas<T: Trait>(e: &mut ExecutionExt<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)
|
||||
} else {
|
||||
Err(sandbox::HostError)
|
||||
}
|
||||
}
|
||||
|
||||
// ext_put_storage(location_ptr: u32, value_non_null: u32, value_ptr: u32);
|
||||
//
|
||||
// Change the value at the given location in storage or remove it.
|
||||
@@ -153,13 +232,13 @@ pub(crate) fn execute<'a, 'b: 'a, T: Trait>(
|
||||
Ok(sandbox::ReturnValue::Unit)
|
||||
}
|
||||
|
||||
// TODO: Inspect the binary to extract the initial page count.
|
||||
let memory = match sandbox::Memory::new(1, None) {
|
||||
Ok(memory) => memory,
|
||||
Err(_) => return false,
|
||||
};
|
||||
let PreparedContract {
|
||||
instrumented_code,
|
||||
memory,
|
||||
} = prepare_contract(code)?;
|
||||
|
||||
let mut imports = sandbox::EnvironmentDefinitionBuilder::new();
|
||||
imports.add_host_func("env", "gas", ext_gas::<T>);
|
||||
imports.add_host_func("env", "ext_set_storage", ext_set_storage::<T>);
|
||||
imports.add_host_func("env", "ext_get_storage", ext_get_storage::<T>);
|
||||
imports.add_host_func("env", "ext_transfer", ext_transfer::<T>);
|
||||
@@ -171,11 +250,514 @@ pub(crate) fn execute<'a, 'b: 'a, T: Trait>(
|
||||
account: account.clone(),
|
||||
account_db,
|
||||
memory,
|
||||
gas_limit,
|
||||
gas_used: 0,
|
||||
};
|
||||
|
||||
let mut instance = match sandbox::Instance::new(code, &imports, &mut exec_ext) {
|
||||
Ok(instance) => instance,
|
||||
Err(_err) => return false,
|
||||
};
|
||||
instance.invoke(b"call", &[], &mut exec_ext).is_ok()
|
||||
let mut instance =
|
||||
sandbox::Instance::new(&instrumented_code, &imports, &mut exec_ext)
|
||||
.map_err(|_| Error::Instantiate)?;
|
||||
instance
|
||||
.invoke(b"call", &[], &mut exec_ext)
|
||||
.map(|_| ())
|
||||
.map_err(|_| Error::Invoke)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Config {
|
||||
/// Gas cost of a growing memory by single page.
|
||||
grow_mem_cost: u32,
|
||||
|
||||
/// Gas cost of a regular operation.
|
||||
regular_op_cost: u32,
|
||||
|
||||
/// How tall the stack is allowed to grow?
|
||||
///
|
||||
/// See https://wiki.parity.io/WebAssembly-StackHeight to find out
|
||||
/// how the stack frame cost is calculated.
|
||||
max_stack_height: u32,
|
||||
|
||||
//// What is the maximal memory pages amount is allowed to have for
|
||||
/// a contract.
|
||||
max_memory_pages: u32,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Config {
|
||||
Config {
|
||||
grow_mem_cost: 1,
|
||||
regular_op_cost: 1,
|
||||
max_stack_height: 64 * 1024,
|
||||
max_memory_pages: 16,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ContractModule {
|
||||
// An `Option` is used here for loaning (`take()`-ing) the module.
|
||||
// Invariant: Can't be `None` (i.e. on enter and on exit from the function
|
||||
// the value *must* be `Some`).
|
||||
module: Option<elements::Module>,
|
||||
config: Config,
|
||||
}
|
||||
|
||||
impl ContractModule {
|
||||
fn new(original_code: &[u8], config: Config) -> Result<ContractModule, Error> {
|
||||
let module =
|
||||
elements::deserialize_buffer(original_code).map_err(|_| Error::Deserialization)?;
|
||||
Ok(ContractModule {
|
||||
module: Some(module),
|
||||
config,
|
||||
})
|
||||
}
|
||||
|
||||
/// Ensures that module doesn't declare internal memories.
|
||||
///
|
||||
/// In this runtime we only allow wasm module to import memory from the environment.
|
||||
/// Memory section contains declarations of internal linear memories, so if we find one
|
||||
/// we reject such a module.
|
||||
fn ensure_no_internal_memory(&self) -> Result<(), Error> {
|
||||
let module = self.module
|
||||
.as_ref()
|
||||
.expect("On entry to the function `module` can't be None; qed");
|
||||
if module
|
||||
.memory_section()
|
||||
.map_or(false, |ms| ms.entries().len() > 0)
|
||||
{
|
||||
return Err(Error::InternalMemoryDeclared);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn inject_gas_metering(&mut self) -> Result<(), Error> {
|
||||
let gas_rules = rules::Set::new(self.config.regular_op_cost, Default::default())
|
||||
.with_grow_cost(self.config.grow_mem_cost)
|
||||
.with_forbidden_floats();
|
||||
|
||||
let module = self.module
|
||||
.take()
|
||||
.expect("On entry to the function `module` can't be `None`; qed");
|
||||
|
||||
let contract_module = pwasm_utils::inject_gas_counter(module, &gas_rules)
|
||||
.map_err(|_| Error::GasInstrumentation)?;
|
||||
|
||||
self.module = Some(contract_module);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn inject_stack_height_metering(&mut self) -> Result<(), Error> {
|
||||
let module = self.module
|
||||
.take()
|
||||
.expect("On entry to the function `module` can't be `None`; qed");
|
||||
|
||||
let contract_module =
|
||||
pwasm_utils::stack_height::inject_limiter(module, self.config.max_stack_height)
|
||||
.map_err(|_| Error::StackHeightInstrumentation)?;
|
||||
|
||||
self.module = Some(contract_module);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Find the memory import entry and return it's descriptor.
|
||||
fn find_mem_import(&self) -> Option<&MemoryType> {
|
||||
let import_section = self.module
|
||||
.as_ref()
|
||||
.expect("On entry to the function `module` can't be `None`; qed")
|
||||
.import_section()?;
|
||||
for import in import_section.entries() {
|
||||
if let ("env", "memory", &External::Memory(ref memory_type)) =
|
||||
(import.module(), import.field(), import.external())
|
||||
{
|
||||
return Some(memory_type);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn into_wasm_code(mut self) -> Result<Vec<u8>, Error> {
|
||||
elements::serialize(
|
||||
self.module
|
||||
.take()
|
||||
.expect("On entry to the function `module` can't be `None`; qed"),
|
||||
).map_err(|_| Error::Serialization)
|
||||
}
|
||||
}
|
||||
|
||||
struct PreparedContract {
|
||||
instrumented_code: Vec<u8>,
|
||||
memory: sandbox::Memory,
|
||||
}
|
||||
|
||||
fn prepare_contract(original_code: &[u8]) -> Result<PreparedContract, Error> {
|
||||
let config = Config::default();
|
||||
|
||||
let mut contract_module = ContractModule::new(original_code, config.clone())?;
|
||||
contract_module.ensure_no_internal_memory()?;
|
||||
contract_module.inject_gas_metering()?;
|
||||
contract_module.inject_stack_height_metering()?;
|
||||
|
||||
// Inspect the module to extract the initial and maximum page count.
|
||||
let memory = match contract_module.find_mem_import() {
|
||||
Some(memory_type) => {
|
||||
let limits = memory_type.limits();
|
||||
match (limits.initial(), limits.maximum()) {
|
||||
(initial, Some(maximum)) if initial > maximum => {
|
||||
// Requested initial number of pages should not exceed the requested maximum.
|
||||
return Err(Error::Memory);
|
||||
}
|
||||
(_, Some(maximum)) if maximum > config.max_memory_pages => {
|
||||
// Maximum number of pages should not exceed the configured maximum.
|
||||
return Err(Error::Memory);
|
||||
}
|
||||
(_, None) => {
|
||||
// Maximum number of pages should be always declared.
|
||||
// This isn't a hard requirement and can be treated as a maxiumum set
|
||||
// to configured maximum.
|
||||
return Err(Error::Memory)
|
||||
}
|
||||
(initial, maximum) => sandbox::Memory::new(
|
||||
initial,
|
||||
maximum,
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
// If none memory imported then just crate an empty placeholder.
|
||||
// Any access to it will lead to out of bounds trap.
|
||||
None => sandbox::Memory::new(0, Some(0)),
|
||||
}.map_err(|_| Error::Memory)?;
|
||||
|
||||
Ok(PreparedContract {
|
||||
instrumented_code: contract_module.into_wasm_code()?,
|
||||
memory,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
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};
|
||||
|
||||
impl fmt::Debug for PreparedContract {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "PreparedContract {{ .. }}")
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_and_prepare_wat(wat: &str) -> Result<PreparedContract, Error> {
|
||||
let wasm = wabt::Wat2Wasm::new()
|
||||
.validate(false)
|
||||
.convert(wat)
|
||||
.unwrap();
|
||||
prepare_contract(wasm.as_ref())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn internal_memory_declaration() {
|
||||
let r = parse_and_prepare_wat(
|
||||
r#"(module (memory 1 1))"#,
|
||||
);
|
||||
assert_matches!(r, Err(Error::InternalMemoryDeclared));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memory() {
|
||||
// This test assumes that maximum page number is configured to a certain number.
|
||||
assert_eq!(Config::default().max_memory_pages, 16);
|
||||
|
||||
let r = parse_and_prepare_wat(
|
||||
r#"(module (import "env" "memory" (memory 1 1)))"#,
|
||||
);
|
||||
assert_matches!(r, Ok(_));
|
||||
|
||||
// No memory import
|
||||
let r = parse_and_prepare_wat(
|
||||
r#"(module)"#,
|
||||
);
|
||||
assert_matches!(r, Ok(_));
|
||||
|
||||
// incorrect import name. That's kinda ok, since this will fail
|
||||
// at later stage when imports will be resolved.
|
||||
let r = parse_and_prepare_wat(
|
||||
r#"(module (import "vne" "memory" (memory 1 1)))"#,
|
||||
);
|
||||
assert_matches!(r, Ok(_));
|
||||
|
||||
// initial exceed maximum
|
||||
let r = parse_and_prepare_wat(
|
||||
r#"(module (import "env" "memory" (memory 16 1)))"#,
|
||||
);
|
||||
assert_matches!(r, Err(Error::Memory));
|
||||
|
||||
// no maximum
|
||||
let r = parse_and_prepare_wat(
|
||||
r#"(module (import "env" "memory" (memory 1)))"#,
|
||||
);
|
||||
assert_matches!(r, Err(Error::Memory));
|
||||
|
||||
// requested maximum exceed configured maximum
|
||||
let r = parse_and_prepare_wat(
|
||||
r#"(module (import "env" "memory" (memory 1 17)))"#,
|
||||
);
|
||||
assert_matches!(r, Err(Error::Memory));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contract_transfer() {
|
||||
let code_transfer = wabt::wat2wasm(
|
||||
r#"
|
||||
(module
|
||||
;; ext_transfer(transfer_to: u32, transfer_to_len: u32, value: u32)
|
||||
(import "env" "ext_transfer" (func $ext_transfer (param 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 6) ;; 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")
|
||||
)
|
||||
"#,
|
||||
).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());
|
||||
|
||||
Staking::transfer(&0, 1, 11);
|
||||
|
||||
assert_eq!(Staking::balance(&0), 100);
|
||||
assert_eq!(Staking::balance(&1), 5);
|
||||
assert_eq!(Staking::balance(&2), 36);
|
||||
});
|
||||
}
|
||||
|
||||
fn escaped_bytestring(bytes: &[u8]) -> String {
|
||||
use std::fmt::Write;
|
||||
let mut result = String::new();
|
||||
for b in bytes {
|
||||
write!(result, "\\{:02x}", b).unwrap();
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contract_create() {
|
||||
let code_transfer = wabt::wat2wasm(
|
||||
r#"
|
||||
(module
|
||||
;; ext_transfer(transfer_to: u32, transfer_to_len: u32, value: u32)
|
||||
(import "env" "ext_transfer" (func $ext_transfer (param 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 6) ;; 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")
|
||||
)
|
||||
"#,
|
||||
).unwrap();
|
||||
|
||||
let code_create = wabt::wat2wasm(format!(
|
||||
r#"
|
||||
(module
|
||||
;; ext_create(code_ptr: u32, code_len: u32, value: u32)
|
||||
(import "env" "ext_create" (func $ext_create (param i32 i32 i32)))
|
||||
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
(func (export "call")
|
||||
(call $ext_create
|
||||
(i32.const 4) ;; Pointer to `code`
|
||||
(i32.const {code_length}) ;; Length of `code`
|
||||
(i32.const 3) ;; Value to transfer
|
||||
)
|
||||
)
|
||||
(data (i32.const 4) "{escaped_code_transfer}")
|
||||
)
|
||||
"#,
|
||||
escaped_code_transfer = escaped_bytestring(&code_transfer),
|
||||
code_length = code_transfer.len(),
|
||||
)).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_create.to_vec());
|
||||
|
||||
// When invoked, the contract at address `1` must create a contract with 'transfer' code.
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contract_adder() {
|
||||
let code_adder = wabt::wat2wasm(r#"
|
||||
(module
|
||||
;; ext_set_storage(location_ptr: i32, value_non_null: bool, value_ptr: i32)
|
||||
(import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32)))
|
||||
;; ext_get_storage(location_ptr: i32, value_ptr: i32)
|
||||
(import "env" "ext_get_storage" (func $ext_get_storage (param i32 i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
(func (export "call")
|
||||
(call $ext_get_storage
|
||||
(i32.const 4) ;; Point to a location of the storage.
|
||||
(i32.const 36) ;; The result will be written at this address.
|
||||
)
|
||||
(i32.store
|
||||
(i32.const 36)
|
||||
(i32.add
|
||||
(i32.load
|
||||
(i32.const 36)
|
||||
)
|
||||
(i32.const 1)
|
||||
)
|
||||
)
|
||||
|
||||
(call $ext_set_storage
|
||||
(i32.const 4) ;; Pointer to a location of the storage.
|
||||
(i32.const 1) ;; Value is not null.
|
||||
(i32.const 36) ;; Pointer to a data we want to put in the storage.
|
||||
)
|
||||
)
|
||||
|
||||
;; Location of storage to put the data. 32 bytes.
|
||||
(data (i32.const 4) "\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01")
|
||||
)
|
||||
"#).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);
|
||||
|
||||
Staking::transfer(&0, 1, 1);
|
||||
Staking::transfer(&0, 1, 1);
|
||||
|
||||
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,
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contract_out_of_gas() {
|
||||
// This code should make 100_000 iterations so it should
|
||||
// consume more than 100_000 units of gas.
|
||||
let code_loop = wabt::wat2wasm(
|
||||
r#"
|
||||
(module
|
||||
(func (export "call")
|
||||
(local $i i32)
|
||||
|
||||
loop $l
|
||||
;; $i = $i + 1
|
||||
(set_local $i
|
||||
(i32.add
|
||||
(get_local $i)
|
||||
(i32.const 1)
|
||||
)
|
||||
)
|
||||
|
||||
;; if $i < 100_000u32: goto $l
|
||||
(br_if $l
|
||||
(i32.lt_u
|
||||
(get_local $i)
|
||||
(i32.const 100000)
|
||||
)
|
||||
)
|
||||
end
|
||||
)
|
||||
)
|
||||
"#,
|
||||
).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_loop.to_vec());
|
||||
|
||||
// Transfer some balance from 0 to 1. This will trigger execution
|
||||
// of the smart-contract code at address 1.
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contract_internal_mem() {
|
||||
let code_mem = wabt::wat2wasm(
|
||||
r#"
|
||||
(module
|
||||
;; Internal memory is not allowed.
|
||||
(memory 1 1)
|
||||
|
||||
(func (export "call")
|
||||
nop
|
||||
)
|
||||
)
|
||||
"#,
|
||||
).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.
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,10 @@ 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;
|
||||
|
||||
@@ -38,6 +42,8 @@ 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::*;
|
||||
@@ -49,6 +55,8 @@ use runtime_support::{StorageValue, StorageMap, Parameter};
|
||||
use primitives::traits::{Zero, One, Bounded, RefInto, SimpleArithmetic, Executable, MakePayment};
|
||||
|
||||
mod contract;
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
|
||||
#[cfg(test)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
@@ -564,6 +572,8 @@ impl<T: Trait> Module<T> {
|
||||
assert!(to_balance + value > to_balance); // no overflow
|
||||
|
||||
// TODO: a fee, based upon gaslimit/gasprice.
|
||||
let gas_limit = 100_000;
|
||||
|
||||
// TODO: consider storing upper-bound for contract's gas limit in fixed-length runtime
|
||||
// code in contract itself and use that.
|
||||
|
||||
@@ -581,7 +591,7 @@ 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)
|
||||
contract::execute(&dest_code, dest, &mut overlay, gas_limit).is_ok()
|
||||
};
|
||||
|
||||
if should_commit {
|
||||
@@ -703,61 +713,7 @@ impl<T: Trait> primitives::BuildExternalities for GenesisConfig<T> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use runtime_io::with_externalities;
|
||||
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_fee: 0,
|
||||
}.build_externalities());
|
||||
t
|
||||
}
|
||||
|
||||
type System = system::Module<Test>;
|
||||
type Session = session::Module<Test>;
|
||||
type Staking = Module<Test>;
|
||||
use mock::*;
|
||||
|
||||
#[test]
|
||||
fn staking_should_work() {
|
||||
@@ -1043,179 +999,4 @@ mod tests {
|
||||
assert_eq!(Staking::free_balance(&2), 42);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contract_transfer() {
|
||||
let code_transfer = wabt::wat2wasm(
|
||||
r#"
|
||||
(module
|
||||
;; ext_transfer(transfer_to: u32, transfer_to_len: u32, value: u32)
|
||||
(import "env" "ext_transfer" (func $ext_transfer (param i32 i32 i32)))
|
||||
|
||||
(import "env" "memory" (memory 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 6) ;; 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")
|
||||
)
|
||||
"#,
|
||||
).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());
|
||||
|
||||
Staking::transfer(&0, 1, 11);
|
||||
|
||||
assert_eq!(Staking::balance(&0), 100);
|
||||
assert_eq!(Staking::balance(&1), 5);
|
||||
assert_eq!(Staking::balance(&2), 36);
|
||||
});
|
||||
}
|
||||
|
||||
fn escaped_bytestring(bytes: &[u8]) -> String {
|
||||
use std::fmt::Write;
|
||||
let mut result = String::new();
|
||||
for b in bytes {
|
||||
write!(result, "\\{:02x}", b).unwrap();
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contract_create() {
|
||||
let code_transfer = wabt::wat2wasm(
|
||||
r#"
|
||||
(module
|
||||
;; ext_transfer(transfer_to: u32, transfer_to_len: u32, value: u32)
|
||||
(import "env" "ext_transfer" (func $ext_transfer (param i32 i32 i32)))
|
||||
|
||||
(import "env" "memory" (memory 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 6) ;; 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")
|
||||
)
|
||||
"#,
|
||||
).unwrap();
|
||||
|
||||
let code_create = wabt::wat2wasm(format!(
|
||||
r#"
|
||||
(module
|
||||
;; ext_create(code_ptr: u32, code_len: u32, value: u32)
|
||||
(import "env" "ext_create" (func $ext_create (param i32 i32 i32)))
|
||||
|
||||
(import "env" "memory" (memory 1))
|
||||
|
||||
(func (export "call")
|
||||
(call $ext_create
|
||||
(i32.const 4) ;; Pointer to `code`
|
||||
(i32.const {code_length}) ;; Length of `code`
|
||||
(i32.const 3) ;; Value to transfer
|
||||
)
|
||||
)
|
||||
(data (i32.const 4) "{escaped_code_transfer}")
|
||||
)
|
||||
"#,
|
||||
escaped_code_transfer = escaped_bytestring(&code_transfer),
|
||||
code_length = code_transfer.len(),
|
||||
)).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_create.to_vec());
|
||||
|
||||
// When invoked, the contract at address `1` must create a contract with 'transfer' code.
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contract_adder() {
|
||||
let code_adder = wabt::wat2wasm(r#"
|
||||
(module
|
||||
;; ext_set_storage(location_ptr: i32, value_non_null: bool, value_ptr: i32)
|
||||
(import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32)))
|
||||
;; ext_get_storage(location_ptr: i32, value_ptr: i32)
|
||||
(import "env" "ext_get_storage" (func $ext_get_storage (param i32 i32)))
|
||||
(import "env" "memory" (memory 1))
|
||||
|
||||
(func (export "call")
|
||||
(call $ext_get_storage
|
||||
(i32.const 4) ;; Point to a location of the storage.
|
||||
(i32.const 36) ;; The result will be written at this address.
|
||||
)
|
||||
(i32.store
|
||||
(i32.const 36)
|
||||
(i32.add
|
||||
(i32.load
|
||||
(i32.const 36)
|
||||
)
|
||||
(i32.const 1)
|
||||
)
|
||||
)
|
||||
|
||||
(call $ext_set_storage
|
||||
(i32.const 4) ;; Pointer to a location of the storage.
|
||||
(i32.const 1) ;; Value is not null.
|
||||
(i32.const 36) ;; Pointer to a data we want to put in the storage.
|
||||
)
|
||||
)
|
||||
|
||||
;; Location of storage to put the data. 32 bytes.
|
||||
(data (i32.const 4) "\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01")
|
||||
)
|
||||
"#).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);
|
||||
|
||||
Staking::transfer(&0, 1, 1);
|
||||
Staking::transfer(&0, 1, 1);
|
||||
|
||||
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,
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
// 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_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