diff --git a/.gitmodules b/.gitmodules index 06b71f6ae..b49256b4d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,6 @@ path = ethcore/res/ethereum/tests url = https://github.com/ethereum/tests.git branch = develop +[submodule "ethcore/res/wasm-tests"] + path = ethcore/res/wasm-tests + url = https://github.com/paritytech/wasm-tests diff --git a/Cargo.lock b/Cargo.lock index b346a7cad..2c665a7de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,6 +57,16 @@ dependencies = [ "syntex_syntax 0.58.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "atty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "base-x" version = "0.2.2" @@ -119,6 +129,11 @@ name = "bitflags" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "bitflags" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "bitflags" version = "0.9.1" @@ -173,6 +188,21 @@ dependencies = [ "multihash 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "clap" +version = "2.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "clippy" version = "0.0.103" @@ -376,6 +406,7 @@ dependencies = [ "native-contracts 0.1.0", "num 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-wasm 0.12.1 (git+http://github.com/nikvolf/parity-wasm)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rlp 0.2.0", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", @@ -385,6 +416,7 @@ dependencies = [ "stats 0.1.0", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", "transient-hashmap 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-utils 0.1.0 (git+https://github.com/paritytech/wasm-utils)", ] [[package]] @@ -1848,6 +1880,16 @@ dependencies = [ "target_info 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parity-wasm" +version = "0.12.1" +source = "git+http://github.com/nikvolf/parity-wasm#98311ec7333e0d3dc9e45c9df673cc79e41acb83" +dependencies = [ + "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "parity-wordlist" version = "1.0.1" @@ -2523,6 +2565,16 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "term_size" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "termios" version = "0.2.2" @@ -2743,6 +2795,11 @@ name = "unicode-segmentation" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unicode-width" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "unicode-xid" version = "0.0.4" @@ -2784,6 +2841,11 @@ name = "utf8-ranges" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "vec_map" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "vecio" version = "0.1.0" @@ -2807,6 +2869,18 @@ name = "void" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "wasm-utils" +version = "0.1.0" +source = "git+https://github.com/paritytech/wasm-utils#357a5deed635938e79553227bfab976959ca3523" +dependencies = [ + "clap 2.24.2 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-wasm 0.12.1 (git+http://github.com/nikvolf/parity-wasm)", +] + [[package]] name = "winapi" version = "0.2.8" @@ -2882,6 +2956,7 @@ dependencies = [ "checksum app_dirs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b7d1c0d48a81bbb13043847f957971f4d87c81542d80ece5e84ba3cba4058fd4" "checksum arrayvec 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d89f1b0e242270b5b797778af0c8d182a1a2ccac5d8d6fadf414223cc0fab096" "checksum aster 0.41.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ccfdf7355d9db158df68f976ed030ab0f6578af811f5a7bb6dcf221ec24e0e0" +"checksum atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d912da0db7fa85514874458ca3651fe2cddace8d0b0505571dbdcd41ab490159" "checksum base-x 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2f59103b47307f76e03bef1633aec7fa9e29bfb5aa6daf5a334f94233c71f6c1" "checksum base32 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1b9605ba46d61df0410d8ac686b0007add8172eba90e8e909c347856fe794d8c" "checksum bigint 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d0673c930652d3d4d6dcd5c45b5db4fa5f8f33994d7323618c43c083b223e8c" @@ -2891,6 +2966,7 @@ dependencies = [ "checksum bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5b97c2c8e8bbb4251754f559df8af22fb264853c7d009084a576cdf12565089d" "checksum bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4f67931368edf3a9a51d29886d245f1c3db2f1ef0dcc9e35ff70341b78c10d23" "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" +"checksum bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1370e9fc2a6ae53aea8b7a5110edbd08836ed87c88736dfabccade1c2b44bff4" "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" "checksum blastfig 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "09640e0509d97d5cdff03a9f5daf087a8e04c735c3b113a75139634a19cfc7b2" "checksum bloomchain 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3f421095d2a76fc24cd3fb3f912b90df06be7689912b1bdb423caefae59c258d" @@ -2899,6 +2975,7 @@ dependencies = [ "checksum bytes 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8b24f16593f445422331a5eed46b72f7f171f910fead4f2ea8f17e727e9c5c14" "checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c" "checksum cid 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "34aa7da06f10541fbca6850719cdaa8fa03060a5d2fb33840f149cf8133a00c7" +"checksum clap 2.24.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b8f69e518f967224e628896b54e41ff6acfb4dcfefc5076325c36525dac900f" "checksum clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)" = "5b4fabf979ddf6419a313c1c0ada4a5b95cfd2049c56e8418d622d27b4b6ff32" "checksum clippy_lints 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)" = "ce96ec05bfe018a0d5d43da115e54850ea2217981ff0f2e462780ab9d594651a" "checksum cookie 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d53b80dde876f47f03cda35303e368a79b91c70b0d65ecba5fd5280944a08591" @@ -3003,6 +3080,7 @@ dependencies = [ "checksum parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e1d06f6ee0fda786df3784a96ee3f0629f529b91cbfb7d142f6410e6bcd1ce2c" "checksum parity-tokio-ipc 0.1.5 (git+https://github.com/nikvolf/parity-tokio-ipc)" = "" "checksum parity-ui-precompiled 1.4.0 (git+https://github.com/paritytech/js-precompiled.git)" = "" +"checksum parity-wasm 0.12.1 (git+http://github.com/nikvolf/parity-wasm)" = "" "checksum parity-wordlist 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "52142d717754f7ff7ef0fc8da1bdce4f302dd576fb9bf8b727d6a5fdef33348d" "checksum parking_lot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aebb68eebde2c99f89592d925288600fde220177e46b5c9a91ca218d245aeedf" "checksum parking_lot_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb1b97670a2ffadce7c397fb80a3d687c4f3060140b885621ef1653d0e5d5068" @@ -3080,6 +3158,7 @@ dependencies = [ "checksum target_info 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c63f48baada5c52e65a29eef93ab4f8982681b67f9e8d29c7b05abcfec2b9ffe" "checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6" "checksum term 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d168af3930b369cfe245132550579d47dfd873d69470755a19c2c6568dbbd989" +"checksum term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2b6b55df3198cc93372e85dd2ed817f0e38ce8cc0f22eb32391bfad9c4bf209" "checksum termios 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d5d9cf598a6d7ce700a4e6a9199da127e6819a61e64b68609683cc9a01b5683a" "checksum thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4437c97558c70d129e40629a5b385b3fb1ffac301e63941335e4d354081ec14a" "checksum thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c85048c6260d17cf486ceae3282d9fb6b90be220bf5b28c400f5485ffc29f0c7" @@ -3103,15 +3182,18 @@ dependencies = [ "checksum unicode-bidi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c1f7ceb96afdfeedee42bade65a0d585a6a0106f681b6749c8ff4daa8df30b3f" "checksum unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "26643a2f83bac55f1976fb716c10234485f9202dcd65cfbdf9da49867b271172" "checksum unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18127285758f0e2c6cf325bb3f3d138a12fee27de4f23e146cd6a179f26c2cf3" +"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" "checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91" "checksum untrusted 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6b65243989ef6aacd9c0d6bd2b822765c3361d8ed352185a6f3a41f3a718c673" "checksum url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "afe9ec54bc4db14bc8744b7fed060d785ac756791450959b2248443319d5b119" "checksum user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ef4711d107b21b410a3a974b1204d9accc8b10dad75d8324b5d755de1617d47" "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" +"checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c" "checksum vecio 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0795a11576d29ae80525a3fda315bf7b534f8feb9d34101e5fe63fb95bb2fd24" "checksum vergen 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "56b639f935488eb40f06d17c3e3bcc3054f6f75d264e187b1107c8d1cba8d31c" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +"checksum wasm-utils 0.1.0 (git+https://github.com/paritytech/wasm-utils)" = "" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" "checksum ws 0.7.1 (git+https://github.com/tomusdrw/ws-rs)" = "" diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index 0a2909dd2..cda69be57 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -52,6 +52,8 @@ semver = "0.6" stats = { path = "../util/stats" } time = "0.1" transient-hashmap = "0.4" +parity-wasm = { git = "https://github.com/nikvolf/parity-wasm" } +wasm-utils = { git = "https://github.com/paritytech/wasm-utils" } [dev-dependencies] native-contracts = { path = "native_contracts", features = ["test_contracts"] } diff --git a/ethcore/res/wasm-tests b/ethcore/res/wasm-tests new file mode 160000 index 000000000..9ed630431 --- /dev/null +++ b/ethcore/res/wasm-tests @@ -0,0 +1 @@ +Subproject commit 9ed6304313fa949ed92aa0570fb2bc759fb6dc58 diff --git a/ethcore/src/action_params.rs b/ethcore/src/action_params.rs index 96f7b1136..2ec71c1f3 100644 --- a/ethcore/src/action_params.rs +++ b/ethcore/src/action_params.rs @@ -39,6 +39,16 @@ impl ActionValue { ActionValue::Transfer(x) | ActionValue::Apparent(x) => x } } + + /// Returns the transfer action value of the U256-convertable raw value + pub fn transfer>(transfer_value: T) -> ActionValue { + ActionValue::Transfer(transfer_value.into()) + } + + /// Returns the apparent action value of the U256-convertable raw value + pub fn apparent>(apparent_value: T) -> ActionValue { + ActionValue::Apparent(apparent_value.into()) + } } // TODO: should be a trait, possible to avoid cloning everything from a Transaction(/View). diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 9a25db593..676bb1aae 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -376,6 +376,11 @@ pub trait Engine : Sync + Send { self.snapshot_components().is_some() } + /// If this engine supports wasm contracts. + fn supports_wasm(&self) -> bool { + self.params().wasm + } + /// Returns new contract address generation scheme at given block number. fn create_address_scheme(&self, number: BlockNumber) -> CreateContractAddress { if number >= self.params().eip86_transition { diff --git a/ethcore/src/evm/evm.rs b/ethcore/src/evm/evm.rs index 31ecb13f8..7a1b9952d 100644 --- a/ethcore/src/evm/evm.rs +++ b/ethcore/src/evm/evm.rs @@ -21,6 +21,7 @@ use util::{U128, U256, U512, trie}; use action_params::ActionParams; use evm::Ext; use builtin; +use super::wasm; /// Evm errors. #[derive(Debug, Clone, PartialEq)] @@ -66,6 +67,8 @@ pub enum Error { MutableCallInStaticContext, /// Likely to cause consensus issues. Internal(String), + /// Wasm runtime error + Wasm(String), } impl From> for Error { @@ -80,6 +83,12 @@ impl From for Error { } } +impl From for Error { + fn from(err: wasm::RuntimeError) -> Self { + Error::Wasm(format!("Runtime error: {:?}", err)) + } +} + impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::Error::*; @@ -92,6 +101,7 @@ impl fmt::Display for Error { BuiltIn(name) => write!(f, "Built-in failed: {}", name), Internal(ref msg) => write!(f, "Internal error: {}", msg), MutableCallInStaticContext => write!(f, "Mutable call in static context"), + Wasm(ref msg) => write!(f, "Internal error: {}", msg), } } } diff --git a/ethcore/src/evm/mod.rs b/ethcore/src/evm/mod.rs index a48004290..db334ffa0 100644 --- a/ethcore/src/evm/mod.rs +++ b/ethcore/src/evm/mod.rs @@ -22,6 +22,7 @@ pub mod interpreter; #[macro_use] pub mod factory; pub mod schedule; +pub mod wasm; mod vmtype; mod instructions; diff --git a/ethcore/src/evm/tests.rs b/ethcore/src/evm/tests.rs index b93361392..61e0b62be 100644 --- a/ethcore/src/evm/tests.rs +++ b/ethcore/src/evm/tests.rs @@ -39,13 +39,13 @@ pub enum FakeCallType { #[derive(PartialEq, Eq, Hash, Debug)] pub struct FakeCall { - call_type: FakeCallType, - gas: U256, - sender_address: Option
, - receive_address: Option
, - value: Option, - data: Bytes, - code_address: Option
, + pub call_type: FakeCallType, + pub gas: U256, + pub sender_address: Option
, + pub receive_address: Option
, + pub value: Option, + pub data: Bytes, + pub code_address: Option
, } /// Fake externalities test structure. @@ -53,17 +53,17 @@ pub struct FakeCall { /// Can't do recursive calls. #[derive(Default)] pub struct FakeExt { + pub store: HashMap, + pub suicides: HashSet
, + pub calls: HashSet, sstore_clears: usize, depth: usize, - store: HashMap, blockhashes: HashMap, codes: HashMap>, logs: Vec, - _suicides: HashSet
, info: EnvInfo, schedule: Schedule, balances: HashMap, - calls: HashSet, } // similar to the normal `finalize` function, but ignoring NeedsReturn. @@ -173,8 +173,9 @@ impl Ext for FakeExt { unimplemented!(); } - fn suicide(&mut self, _refund_address: &Address) -> evm::Result<()> { - unimplemented!(); + fn suicide(&mut self, refund_address: &Address) -> evm::Result<()> { + self.suicides.insert(refund_address.clone()); + Ok(()) } fn schedule(&self) -> &Schedule { diff --git a/ethcore/src/evm/wasm/call_args.rs b/ethcore/src/evm/wasm/call_args.rs new file mode 100644 index 000000000..b4cce4982 --- /dev/null +++ b/ethcore/src/evm/wasm/call_args.rs @@ -0,0 +1,62 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity 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. + +// Parity 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 Parity. If not, see . + +//! Wasm evm call arguments helper + +use util::{U256, H160}; + +/// Input part of the wasm call descriptor +pub struct CallArgs { + /// Receiver of the transaction + pub address: [u8; 20], + + /// Sender of the transaction + pub sender: [u8; 20], + + /// Original transaction initiator + pub origin: [u8; 20], + + /// Transfer value + pub value: [u8; 32], + + /// call/create params + pub data: Vec, +} + +impl CallArgs { + /// New contract call payload with known parameters + pub fn new(address: H160, sender: H160, origin: H160, value: U256, data: Vec) -> Self { + let mut descriptor = CallArgs { + address: [0u8; 20], + sender: [0u8; 20], + origin: [0u8; 20], + value: [0u8; 32], + data: data, + }; + + descriptor.address.copy_from_slice(&*address); + descriptor.sender.copy_from_slice(&*sender); + descriptor.origin.copy_from_slice(&*origin); + value.to_big_endian(&mut descriptor.value); + + descriptor + } + + /// Total call payload length in linear memory + pub fn len(&self) -> u32 { + self.data.len() as u32 + 92 + } +} \ No newline at end of file diff --git a/ethcore/src/evm/wasm/env.rs b/ethcore/src/evm/wasm/env.rs new file mode 100644 index 000000000..cabd38bd9 --- /dev/null +++ b/ethcore/src/evm/wasm/env.rs @@ -0,0 +1,119 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity 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. + +// Parity 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 Parity. If not, see . + +//! Wasm env module bindings + +use parity_wasm::elements::ValueType::*; +use parity_wasm::interpreter::UserFunctionDescriptor; +use parity_wasm::interpreter::UserFunctionDescriptor::*; + +pub const SIGNATURES: &'static [UserFunctionDescriptor] = &[ + Static( + "_storage_read", + &[I32; 2], + Some(I32), + ), + Static( + "_storage_write", + &[I32; 2], + Some(I32), + ), + Static( + "_malloc", + &[I32], + Some(I32), + ), + Static( + "_free", + &[I32], + None, + ), + Static( + "gas", + &[I32], + None, + ), + Static( + "_debug", + &[I32; 2], + None, + ), + Static( + "_suicide", + &[I32], + None, + ), + Static( + "_create", + &[I32; 4], + Some(I32), + ), + Static( + "abort", + &[I32], + None, + ), + Static( + "_abort", + &[], + None, + ), + Static( + "invoke_vii", + &[I32; 3], + None, + ), + Static( + "invoke_vi", + &[I32; 2], + None, + ), + Static( + "invoke_v", + &[I32], + None, + ), + Static( + "invoke_iii", + &[I32; 3], + Some(I32), + ), + Static( + "___resumeException", + &[I32], + None, + ), + Static( + "_rust_begin_unwind", + &[I32; 4], + None, + ), + Static( + "___cxa_find_matching_catch_2", + &[], + Some(I32), + ), + Static( + "___gxx_personality_v0", + &[I32; 6], + Some(I32), + ), + Static( + "_emscripten_memcpy_big", + &[I32; 3], + Some(I32), + ) +]; diff --git a/ethcore/src/evm/wasm/mod.rs b/ethcore/src/evm/wasm/mod.rs new file mode 100644 index 000000000..3965cc529 --- /dev/null +++ b/ethcore/src/evm/wasm/mod.rs @@ -0,0 +1,159 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity 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. + +// Parity 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 Parity. If not, see . + +//! Wasm Interpreter + +mod runtime; +mod ptr; +mod call_args; +mod result; +#[cfg(test)] +mod tests; +mod env; + +use std::sync::Arc; + +const DEFAULT_STACK_SPACE: u32 = 5 * 1024 * 1024; + +use parity_wasm::{interpreter, elements}; +use parity_wasm::interpreter::ModuleInstanceInterface; +use wasm_utils; + +use evm::{self, GasLeft, ReturnData}; +use action_params::ActionParams; +use self::runtime::Runtime; + +pub use self::runtime::Error as RuntimeError; + +const DEFAULT_RESULT_BUFFER: usize = 1024; + +/// Wasm interpreter instance +pub struct WasmInterpreter { + program: interpreter::ProgramInstance, + result: Vec, +} + +impl WasmInterpreter { + /// New wasm interpreter instance + pub fn new() -> Result { + Ok(WasmInterpreter { + program: interpreter::ProgramInstance::new()?, + result: Vec::with_capacity(DEFAULT_RESULT_BUFFER), + }) + } +} + +impl evm::Evm for WasmInterpreter { + + fn exec(&mut self, params: ActionParams, ext: &mut evm::Ext) -> evm::Result { + use parity_wasm::elements::Deserialize; + + let code = params.code.expect("exec is only called on contract with code; qed"); + + trace!(target: "wasm", "Started wasm interpreter with code.len={:?}", code.len()); + + let env_instance = self.program.module("env") + // prefer explicit panic here + .expect("Wasm program to contain env module"); + + let env_memory = env_instance.memory(interpreter::ItemIndex::Internal(0)) + // prefer explicit panic here + .expect("Linear memory to exist in wasm runtime"); + + if params.gas > ::std::u64::MAX.into() { + return Err(evm::Error::Wasm("Wasm interpreter cannot run contracts with gas >= 2^64".to_owned())); + } + + let mut runtime = Runtime::with_params( + ext, + env_memory, + DEFAULT_STACK_SPACE, + params.gas.low_u64(), + ); + + let mut cursor = ::std::io::Cursor::new(&*code); + + let contract_module = wasm_utils::inject_gas_counter( + elements::Module::deserialize( + &mut cursor + ).map_err(|err| { + evm::Error::Wasm(format!("Error deserializing contract code ({:?})", err)) + })? + ); + + let d_ptr = runtime.write_descriptor( + call_args::CallArgs::new( + params.address, + params.sender, + params.origin, + params.value.value(), + params.data.unwrap_or(Vec::with_capacity(0)), + ) + )?; + + { + let execution_params = interpreter::ExecutionParams::with_external( + "env".into(), + Arc::new( + interpreter::env_native_module(env_instance, native_bindings(&mut runtime)) + .map_err(|err| { + // todo: prefer explicit panic here also? + evm::Error::Wasm(format!("Error instantiating native bindings: {:?}", err)) + })? + ) + ).add_argument(interpreter::RuntimeValue::I32(d_ptr.as_raw() as i32)); + + let module_instance = self.program.add_module("contract", contract_module, Some(&execution_params.externals)) + .map_err(|err| { + trace!(target: "wasm", "Error adding contract module: {:?}", err); + evm::Error::from(RuntimeError::Interpreter(err)) + })?; + + module_instance.execute_export("_call", execution_params) + .map_err(|err| { + trace!(target: "wasm", "Error executing contract: {:?}", err); + evm::Error::from(RuntimeError::Interpreter(err)) + })?; + } + + let result = result::WasmResult::new(d_ptr); + if result.peek_empty(&*runtime.memory())? { + trace!(target: "wasm", "Contract execution result is empty."); + Ok(GasLeft::Known(runtime.gas_left()?.into())) + } else { + self.result.clear(); + // todo: use memory views to avoid copy + self.result.extend(result.pop(&*runtime.memory())?); + let len = self.result.len(); + Ok(GasLeft::NeedsReturn { + gas_left: runtime.gas_left()?.into(), + data: ReturnData::new( + ::std::mem::replace(&mut self.result, Vec::with_capacity(DEFAULT_RESULT_BUFFER)), + 0, + len, + ), + apply_state: true, + }) + } + } +} + +fn native_bindings<'a>(runtime: &'a mut Runtime) -> interpreter::UserFunctions<'a> { + interpreter::UserFunctions { + executor: runtime, + functions: ::std::borrow::Cow::from(env::SIGNATURES), + } +} \ No newline at end of file diff --git a/ethcore/src/evm/wasm/ptr.rs b/ethcore/src/evm/wasm/ptr.rs new file mode 100644 index 000000000..11edbad70 --- /dev/null +++ b/ethcore/src/evm/wasm/ptr.rs @@ -0,0 +1,52 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity 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. + +// Parity 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 Parity. If not, see . + +//! Wasm bound-checked ptr + +use parity_wasm::interpreter; + +/// Bound-checked wrapper for webassembly memory +pub struct WasmPtr(u32); + +/// Error in bound check +#[derive(Debug)] +pub enum Error { + AccessViolation, +} + +impl From for WasmPtr { + fn from(raw: u32) -> Self { + WasmPtr(raw) + } +} + +impl WasmPtr { + // todo: use memory view when they are on + /// Check memory range and return data with given length starting from the current pointer value + pub fn slice(&self, len: u32, mem: &interpreter::MemoryInstance) -> Result, Error> { + mem.get(self.0, len as usize).map_err(|_| Error::AccessViolation) + } + + // todo: maybe 2gb limit can be enhanced + /// Convert i32 from wasm stack to the wrapped pointer + pub fn from_i32(raw_ptr: i32) -> Result { + if raw_ptr < 0 { return Err(Error::AccessViolation); } + Ok(WasmPtr(raw_ptr as u32)) + } + + /// Return pointer raw value + pub fn as_raw(&self) -> u32 { self.0 } +} \ No newline at end of file diff --git a/ethcore/src/evm/wasm/result.rs b/ethcore/src/evm/wasm/result.rs new file mode 100644 index 000000000..3d1e51f64 --- /dev/null +++ b/ethcore/src/evm/wasm/result.rs @@ -0,0 +1,51 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity 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. + +// Parity 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 Parity. If not, see . + +//! Wasm evm results helper + +use byteorder::{LittleEndian, ByteOrder}; + +use parity_wasm::interpreter; + +use super::ptr::WasmPtr; +use super::runtime::Error as RuntimeError; + +/// Wrapper for wasm contract call result +pub struct WasmResult { + ptr: WasmPtr, +} + +impl WasmResult { + /// New call result from given ptr + pub fn new(descriptor_ptr: WasmPtr) -> WasmResult { + WasmResult { ptr: descriptor_ptr } + } + + /// Check if the result contains any data + pub fn peek_empty(&self, mem: &interpreter::MemoryInstance) -> Result { + let result_len = LittleEndian::read_u32(&self.ptr.slice(16, mem)?[12..16]); + Ok(result_len == 0) + } + + /// Consume the result ptr and return the actual data from wasm linear memory + pub fn pop(self, mem: &interpreter::MemoryInstance) -> Result, RuntimeError> { + let result_ptr = LittleEndian::read_u32(&self.ptr.slice(16, mem)?[8..12]); + let result_len = LittleEndian::read_u32(&self.ptr.slice(16, mem)?[12..16]); + trace!(target: "wasm", "contract result: {} bytes at @{}", result_len, result_ptr); + + Ok(mem.get(result_ptr, result_len as usize)?) + } +} \ No newline at end of file diff --git a/ethcore/src/evm/wasm/runtime.rs b/ethcore/src/evm/wasm/runtime.rs new file mode 100644 index 000000000..eb1fc55fb --- /dev/null +++ b/ethcore/src/evm/wasm/runtime.rs @@ -0,0 +1,356 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity 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. + +// Parity 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 Parity. If not, see . + +//! Wasm evm program runtime intstance + +use std::sync::Arc; + +use byteorder::{LittleEndian, ByteOrder}; + +use evm; + +use parity_wasm::interpreter; +use util::{Address, H256, U256}; + +use super::ptr::{WasmPtr, Error as PtrError}; +use super::call_args::CallArgs; + +/// Wasm runtime error +#[derive(Debug)] +pub enum Error { + /// Storage error + Storage, + /// Allocator error + Allocator, + /// Invalid gas state during the call + InvalidGasState, + /// Memory access violation + AccessViolation, + /// Interpreter runtime error + Interpreter(interpreter::Error), +} + +impl From for Error { + fn from(err: interpreter::Error) -> Self { + Error::Interpreter(err) + } +} + +impl From for Error { + fn from(err: PtrError) -> Self { + match err { + PtrError::AccessViolation => Error::AccessViolation, + } + } +} + +/// Runtime enviroment data for wasm contract execution +pub struct Runtime<'a> { + gas_counter: u64, + gas_limit: u64, + dynamic_top: u32, + ext: &'a mut evm::Ext, + memory: Arc, +} + +impl<'a> Runtime<'a> { + /// New runtime for wasm contract with specified params + pub fn with_params<'b>( + ext: &'b mut evm::Ext, + memory: Arc, + stack_space: u32, + gas_limit: u64, + ) -> Runtime<'b> { + Runtime { + gas_counter: 0, + gas_limit: gas_limit, + dynamic_top: stack_space, + memory: memory, + ext: ext, + } + } + + /// Write to the storage from wasm memory + pub fn storage_write(&mut self, context: interpreter::CallerContext) + -> Result, interpreter::Error> + { + let mut context = context; + let val = self.pop_h256(&mut context)?; + let key = self.pop_h256(&mut context)?; + trace!(target: "wasm", "storage_write: value {} at @{}", &val, &key); + + self.ext.set_storage(key, val) + .map_err(|_| interpreter::Error::Trap("Storage update error".to_owned()))?; + + Ok(Some(0i32.into())) + } + + /// Read from the storage to wasm memory + pub fn storage_read(&mut self, context: interpreter::CallerContext) + -> Result, interpreter::Error> + { + let mut context = context; + let val_ptr = context.value_stack.pop_as::()?; + let key = self.pop_h256(&mut context)?; + + let val = self.ext.storage_at(&key) + .map_err(|_| interpreter::Error::Trap("Storage read error".to_owned()))?; + + self.memory.set(val_ptr as u32, &*val)?; + + Ok(Some(0.into())) + } + + /// Pass suicide to state runtime + pub fn suicide(&mut self, context: interpreter::CallerContext) + -> Result, interpreter::Error> + { + let mut context = context; + let refund_address = self.pop_address(&mut context)?; + + self.ext.suicide(&refund_address) + .map_err(|_| interpreter::Error::Trap("Suicide error".to_owned()))?; + + Ok(None) + } + + /// Invoke create in the state runtime + pub fn create(&mut self, context: interpreter::CallerContext) + -> Result, interpreter::Error> + { + // + // method signature: + // fn create(endowment: *const u8, code_ptr: *const u8, code_len: u32, result_ptr: *mut u8) -> i32; + // + + trace!(target: "wasm", "runtime: create contract"); + let mut context = context; + let result_ptr = context.value_stack.pop_as::()? as u32; + trace!(target: "wasm", " result_ptr: {:?}", result_ptr); + let code_len = context.value_stack.pop_as::()? as u32; + trace!(target: "wasm", " code_len: {:?}", code_len); + let code_ptr = context.value_stack.pop_as::()? as u32; + trace!(target: "wasm", " code_ptr: {:?}", code_ptr); + let endowment = self.pop_u256(&mut context)?; + trace!(target: "wasm", " val: {:?}", endowment); + + let code = self.memory.get(code_ptr, code_len as usize)?; + + let gas_left = self.gas_left() + .map_err(|_| interpreter::Error::Trap("Gas state error".to_owned()))? + .into(); + + match self.ext.create(&gas_left, &endowment, &code, evm::CreateContractAddress::FromSenderAndCodeHash) { + evm::ContractCreateResult::Created(address, gas_left) => { + self.memory.set(result_ptr, &*address)?; + self.gas_counter = self.gas_limit - gas_left.low_u64(); + trace!(target: "wasm", "runtime: create contract success (@{:?})", address); + Ok(Some(0i32.into())) + }, + evm::ContractCreateResult::Failed => { + trace!(target: "wasm", "runtime: create contract fail"); + Ok(Some((-1i32).into())) + } + } + } + + /// Allocate memory using the wasm stack params + pub fn malloc(&mut self, context: interpreter::CallerContext) + -> Result, interpreter::Error> + { + let amount = context.value_stack.pop_as::()? as u32; + let previous_top = self.dynamic_top; + self.dynamic_top = previous_top + amount; + Ok(Some((previous_top as i32).into())) + } + + /// Allocate memory in wasm memory instance + pub fn alloc(&mut self, amount: u32) -> Result { + let previous_top = self.dynamic_top; + self.dynamic_top = previous_top + amount; + Ok(previous_top.into()) + } + + /// Report gas cost with the params passed in wasm stack + fn gas(&mut self, context: interpreter::CallerContext) + -> Result, interpreter::Error> + { + let amount = context.value_stack.pop_as::()? as u64; + if self.charge_gas(amount) { + Ok(None) + } else { + Err(interpreter::Error::Trap(format!("Gas exceeds limits of {}", self.gas_limit))) + } + } + + fn charge_gas(&mut self, amount: u64) -> bool { + let prev = self.gas_counter; + if prev + amount > self.gas_limit { + // exceeds gas + false + } else { + self.gas_counter = prev + amount; + true + } + } + + fn h256_at(&self, ptr: WasmPtr) -> Result { + Ok(H256::from_slice(&ptr.slice(32, &*self.memory) + .map_err(|_| interpreter::Error::Trap("Memory access violation".to_owned()))? + )) + } + + fn pop_h256(&self, context: &mut interpreter::CallerContext) -> Result { + let ptr = WasmPtr::from_i32(context.value_stack.pop_as::()?) + .map_err(|_| interpreter::Error::Trap("Memory access violation".to_owned()))?; + self.h256_at(ptr) + } + + fn pop_u256(&self, context: &mut interpreter::CallerContext) -> Result { + let ptr = WasmPtr::from_i32(context.value_stack.pop_as::()?) + .map_err(|_| interpreter::Error::Trap("Memory access violation".to_owned()))?; + self.h256_at(ptr).map(Into::into) + } + + fn address_at(&self, ptr: WasmPtr) -> Result { + Ok(Address::from_slice(&ptr.slice(20, &*self.memory) + .map_err(|_| interpreter::Error::Trap("Memory access violation".to_owned()))? + )) + } + + fn pop_address(&self, context: &mut interpreter::CallerContext) -> Result { + let ptr = WasmPtr::from_i32(context.value_stack.pop_as::()?) + .map_err(|_| interpreter::Error::Trap("Memory access violation".to_owned()))?; + self.address_at(ptr) + } + + fn user_trap(&mut self, _context: interpreter::CallerContext) + -> Result, interpreter::Error> + { + Err(interpreter::Error::Trap("unknown trap".to_owned())) + } + + fn user_noop(&mut self, + _context: interpreter::CallerContext + ) -> Result, interpreter::Error> { + Ok(None) + } + + /// Write call descriptor to wasm memory + pub fn write_descriptor(&mut self, call_args: CallArgs) -> Result { + let d_ptr = self.alloc(16)?; + + let args_len = call_args.len(); + let args_ptr = self.alloc(args_len)?; + + // write call descriptor + // call descriptor is [args_ptr, args_len, return_ptr, return_len] + // all are 4 byte length, last 2 are zeroed + let mut d_buf = [0u8; 16]; + LittleEndian::write_u32(&mut d_buf[0..4], args_ptr); + LittleEndian::write_u32(&mut d_buf[4..8], args_len); + self.memory.set(d_ptr, &d_buf)?; + + // write call args to memory + self.memory.set(args_ptr, &call_args.address)?; + self.memory.set(args_ptr+20, &call_args.sender)?; + self.memory.set(args_ptr+40, &call_args.origin)?; + self.memory.set(args_ptr+60, &call_args.value)?; + self.memory.set(args_ptr+92, &call_args.data)?; + + Ok(d_ptr.into()) + } + + fn debug_log(&mut self, context: interpreter::CallerContext) + -> Result, interpreter::Error> + { + let msg_len = context.value_stack.pop_as::()? as u32; + let msg_ptr = context.value_stack.pop_as::()? as u32; + + let msg = String::from_utf8(self.memory.get(msg_ptr, msg_len as usize)?) + .map_err(|_| interpreter::Error::Trap("Debug log utf-8 decoding error".to_owned()))?; + + trace!(target: "wasm", "Contract debug message: {}", msg); + + Ok(None) + } + + /// Query current gas left for execution + pub fn gas_left(&self) -> Result { + if self.gas_counter > self.gas_limit { return Err(Error::InvalidGasState); } + Ok(self.gas_limit - self.gas_counter) + } + + /// Shared memory reference + pub fn memory(&self) -> &interpreter::MemoryInstance { + &*self.memory + } + + fn mem_copy(&self, context: interpreter::CallerContext) + -> Result, interpreter::Error> + { + let len = context.value_stack.pop_as::()? as u32; + let dst = context.value_stack.pop_as::()? as u32; + let src = context.value_stack.pop_as::()? as u32; + + let mem = self.memory().get(src, len as usize)?; + self.memory().set(dst, &mem)?; + + Ok(Some(0i32.into())) + } +} + +impl<'a> interpreter::UserFunctionExecutor for Runtime<'a> { + fn execute(&mut self, name: &str, context: interpreter::CallerContext) + -> Result, interpreter::Error> + { + match name { + "_malloc" => { + self.malloc(context) + }, + "_free" => { + // Since it is arena allocator, free does nothing + // todo: update if changed + self.user_noop(context) + }, + "_storage_read" => { + self.storage_read(context) + }, + "_storage_write" => { + self.storage_write(context) + }, + "_suicide" => { + self.suicide(context) + }, + "_create" => { + self.create(context) + }, + "_debug" => { + self.debug_log(context) + }, + "gas" => { + self.gas(context) + }, + "_emscripten_memcpy_big" => { + self.mem_copy(context) + }, + _ => { + trace!("Unknown env func: '{}'", name); + self.user_trap(context) + } + } + } +} \ No newline at end of file diff --git a/ethcore/src/evm/wasm/tests.rs b/ethcore/src/evm/wasm/tests.rs new file mode 100644 index 000000000..7fd98db08 --- /dev/null +++ b/ethcore/src/evm/wasm/tests.rs @@ -0,0 +1,279 @@ +use std::path::PathBuf; +use std::fs::File; +use std::io::Read; +use std::sync::Arc; + +use ethcore_logger::init_log; +use super::super::tests::{FakeExt, FakeCall, FakeCallType}; +use super::WasmInterpreter; +use evm::{self, Evm, GasLeft}; +use action_params::{ActionParams, ActionValue}; +use util::{U256, H256, Address}; + +fn load_sample(name: &str) -> Vec { + let mut path = PathBuf::from("./res/wasm-tests/compiled"); + path.push(name); + let mut file = File::open(path).expect(&format!("File {} for test to exist", name)); + let mut data = vec![]; + file.read_to_end(&mut data).expect(&format!("Test {} to load ok", name)); + data +} + +fn test_finalize(res: Result) -> Result { + match res { + Ok(GasLeft::Known(gas)) => Ok(gas), + Ok(GasLeft::NeedsReturn{..}) => unimplemented!(), // since ret is unimplemented. + Err(e) => Err(e), + } +} + +fn wasm_interpreter() -> WasmInterpreter { + WasmInterpreter::new().expect("wasm interpreter to create without errors") +} + +/// Empty contract does almost nothing except producing 1 (one) local node debug log message +#[test] +fn empty() { + init_log(); + + let code = load_sample("empty.wasm"); + let address: Address = "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6".parse().unwrap(); + + let mut params = ActionParams::default(); + params.address = address.clone(); + params.gas = U256::from(100_000); + params.code = Some(Arc::new(code)); + let mut ext = FakeExt::new(); + + let gas_left = { + let mut interpreter = wasm_interpreter(); + test_finalize(interpreter.exec(params, &mut ext)).unwrap() + }; + + assert_eq!(gas_left, U256::from(99_996)); +} + +// This test checks if the contract deserializes payload header properly. +// Contract is provided with receiver(address), sender, origin and transaction value +// logger.wasm writes all these provided fixed header fields to some arbitrary storage keys. +#[test] +fn logger() { + init_log(); + + let code = load_sample("logger.wasm"); + let address: Address = "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6".parse().unwrap(); + let sender: Address = "0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d".parse().unwrap(); + let origin: Address = "0102030405060708090a0b0c0d0e0f1011121314".parse().unwrap(); + + let mut params = ActionParams::default(); + params.address = address.clone(); + params.sender = sender.clone(); + params.origin = origin.clone(); + params.gas = U256::from(100_000); + params.value = ActionValue::transfer(1_000_000_000); + params.code = Some(Arc::new(code)); + let mut ext = FakeExt::new(); + + let gas_left = { + let mut interpreter = wasm_interpreter(); + test_finalize(interpreter.exec(params, &mut ext)).unwrap() + }; + + println!("ext.store: {:?}", ext.store); + assert_eq!(gas_left, U256::from(99581)); + let address_val: H256 = address.into(); + assert_eq!( + ext.store.get(&"0100000000000000000000000000000000000000000000000000000000000000".parse().unwrap()).expect("storage key to exist"), + &address_val, + "Logger sets 0x01 key to the provided address" + ); + let sender_val: H256 = sender.into(); + assert_eq!( + ext.store.get(&"0200000000000000000000000000000000000000000000000000000000000000".parse().unwrap()).expect("storage key to exist"), + &sender_val, + "Logger sets 0x02 key to the provided sender" + ); + let origin_val: H256 = origin.into(); + assert_eq!( + ext.store.get(&"0300000000000000000000000000000000000000000000000000000000000000".parse().unwrap()).expect("storage key to exist"), + &origin_val, + "Logger sets 0x03 key to the provided origin" + ); + assert_eq!( + U256::from(ext.store.get(&"0400000000000000000000000000000000000000000000000000000000000000".parse().unwrap()).expect("storage key to exist")), + U256::from(1_000_000_000), + "Logger sets 0x04 key to the trasferred value" + ); +} + +// This test checks if the contract can allocate memory and pass pointer to the result stream properly. +// 1. Contract is being provided with the call descriptor ptr +// 2. Descriptor ptr is 16 byte length +// 3. The last 8 bytes of call descriptor is the space for the contract to fill [result_ptr[4], result_len[4]] +// if it has any result. +#[test] +fn identity() { + init_log(); + + let code = load_sample("identity.wasm"); + let sender: Address = "01030507090b0d0f11131517191b1d1f21232527".parse().unwrap(); + + let mut params = ActionParams::default(); + params.sender = sender.clone(); + params.gas = U256::from(100_000); + params.code = Some(Arc::new(code)); + let mut ext = FakeExt::new(); + + let (gas_left, result) = { + let mut interpreter = wasm_interpreter(); + let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors"); + match result { + GasLeft::Known(_) => { panic!("Identity contract should return payload"); }, + GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()), + } + }; + + assert_eq!(gas_left, U256::from(99_689)); + + assert_eq!( + Address::from_slice(&result), + sender, + "Idenity test contract does not return the sender passed" + ); +} + +// Dispersion test sends byte array and expect the contract to 'disperse' the original elements with +// their modulo 19 dopant. +// The result is always twice as long as the input. +// This also tests byte-perfect memory allocation and in/out ptr lifecycle. +#[test] +fn dispersion() { + init_log(); + + let code = load_sample("dispersion.wasm"); + + let mut params = ActionParams::default(); + params.gas = U256::from(100_000); + params.code = Some(Arc::new(code)); + params.data = Some(vec![ + 0u8, 125, 197, 255, 19 + ]); + let mut ext = FakeExt::new(); + + let (gas_left, result) = { + let mut interpreter = wasm_interpreter(); + let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors"); + match result { + GasLeft::Known(_) => { panic!("Dispersion routine should return payload"); }, + GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()), + } + }; + + assert_eq!(gas_left, U256::from(99_402)); + + assert_eq!( + result, + vec![0u8, 0, 125, 11, 197, 7, 255, 8, 19, 0] + ); +} + +#[test] +fn suicide_not() { + init_log(); + + let code = load_sample("suicidal.wasm"); + + let mut params = ActionParams::default(); + params.gas = U256::from(100_000); + params.code = Some(Arc::new(code)); + params.data = Some(vec![ + 0u8 + ]); + let mut ext = FakeExt::new(); + + let (gas_left, result) = { + let mut interpreter = wasm_interpreter(); + let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors"); + match result { + GasLeft::Known(_) => { panic!("Suicidal contract should return payload when had not actualy killed himself"); }, + GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()), + } + }; + + assert_eq!(gas_left, U256::from(99_703)); + + assert_eq!( + result, + vec![0u8] + ); +} + +#[test] +fn suicide() { + init_log(); + + let code = load_sample("suicidal.wasm"); + + let refund: Address = "01030507090b0d0f11131517191b1d1f21232527".parse().unwrap(); + let mut params = ActionParams::default(); + params.gas = U256::from(100_000); + params.code = Some(Arc::new(code)); + + let mut args = vec![127u8]; + args.extend(refund.to_vec()); + params.data = Some(args); + + let mut ext = FakeExt::new(); + + let gas_left = { + let mut interpreter = wasm_interpreter(); + let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors"); + match result { + GasLeft::Known(gas) => gas, + GasLeft::NeedsReturn { .. } => { + panic!("Suicidal contract should not return anything when had killed itself"); + }, + } + }; + + assert_eq!(gas_left, U256::from(99_747)); + assert!(ext.suicides.contains(&refund)); +} + +#[test] +fn create() { + init_log(); + + let mut params = ActionParams::default(); + params.gas = U256::from(100_000); + params.code = Some(Arc::new(load_sample("creator.wasm"))); + params.data = Some(vec![0u8, 2, 4, 8, 16, 32, 64, 128]); + params.value = ActionValue::transfer(1_000_000_000); + + let mut ext = FakeExt::new(); + + let gas_left = { + let mut interpreter = wasm_interpreter(); + let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors"); + match result { + GasLeft::Known(gas) => gas, + GasLeft::NeedsReturn { .. } => { + panic!("Create contract should not return anthing because ext always fails on creation"); + }, + } + }; + + trace!(target: "wasm", "fake_calls: {:?}", &ext.calls); + assert!(ext.calls.contains( + &FakeCall { + call_type: FakeCallType::Create, + gas: U256::from(99_778), + sender_address: None, + receive_address: None, + value: Some(1_000_000_000.into()), + data: vec![0u8, 2, 4, 8, 16, 32, 64, 128], + code_address: None, + } + )); + assert_eq!(gas_left, U256::from(99_768)); +} \ No newline at end of file diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index 07b7b67c3..d7a6fbc28 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -22,7 +22,7 @@ use engines::Engine; use types::executed::CallType; use env_info::EnvInfo; use error::ExecutionError; -use evm::{self, Ext, Finalize, CreateContractAddress, FinalizationResult, ReturnData, CleanDustMode}; +use evm::{self, wasm, Factory, Ext, Finalize, CreateContractAddress, FinalizationResult, ReturnData, CleanDustMode}; use externalities::*; use trace::{FlatTrace, Tracer, NoopTracer, ExecutiveTracer, VMTrace, VMTracer, ExecutiveVMTracer, NoopVMTracer}; use transaction::{Action, SignedTransaction}; @@ -34,6 +34,8 @@ pub use types::executed::{Executed, ExecutionResult}; /// Maybe something like here: `https://github.com/ethereum/libethereum/blob/4db169b8504f2b87f7d5a481819cfb959fc65f6c/libethereum/ExtVM.cpp` const STACK_SIZE_PER_DEPTH: usize = 24*1024; +const WASM_MAGIC_NUMBER: &'static [u8; 4] = b"\0asm"; + /// Returns new address created from address, nonce, and code hash pub fn contract_address(address_scheme: CreateContractAddress, sender: &Address, nonce: &U256, code: &[u8]) -> (Address, Option) { use rlp::RlpStream; @@ -72,6 +74,20 @@ pub struct TransactOptions { pub check_nonce: bool, } +pub fn executor(engine: &E, vm_factory: &Factory, params: &ActionParams) + -> Box where E: Engine + ?Sized +{ + if engine.supports_wasm() && params.code.as_ref().map_or(false, |code| code.len() > 4 && &code[0..4] == WASM_MAGIC_NUMBER) { + Box::new( + wasm::WasmInterpreter::new() + // prefer to fail fast + .expect("Failed to create wasm runtime") + ) + } else { + vm_factory.create(params.gas) + } +} + /// Transaction executor. pub struct Executive<'a, B: 'a + StateBackend, E: 'a + Engine + ?Sized> { state: &'a mut State, @@ -263,18 +279,19 @@ impl<'a, B: 'a + StateBackend, E: Engine + ?Sized> Executive<'a, B, E> { let vm_factory = self.state.vm_factory(); let mut ext = self.as_externalities(OriginInfo::from(¶ms), unconfirmed_substate, output_policy, tracer, vm_tracer, static_call); trace!(target: "executive", "ext.schedule.have_delegate_call: {}", ext.schedule().have_delegate_call); - return vm_factory.create(params.gas).exec(params, &mut ext).finalize(ext); + return executor(self.engine, &vm_factory, ¶ms).exec(params, &mut ext).finalize(ext); } // Start in new thread to reset stack // TODO [todr] No thread builder yet, so we need to reset once for a while // https://github.com/aturon/crossbeam/issues/16 crossbeam::scope(|scope| { + let engine = self.engine; let vm_factory = self.state.vm_factory(); let mut ext = self.as_externalities(OriginInfo::from(¶ms), unconfirmed_substate, output_policy, tracer, vm_tracer, static_call); scope.spawn(move || { - vm_factory.create(params.gas).exec(params, &mut ext).finalize(ext) + executor(engine, &vm_factory, ¶ms).exec(params, &mut ext).finalize(ext) }) }).join() } @@ -562,6 +579,7 @@ impl<'a, B: 'a + StateBackend, E: Engine + ?Sized> Executive<'a, B, E> { | Err(evm::Error::BadInstruction {.. }) | Err(evm::Error::StackUnderflow {..}) | Err(evm::Error::BuiltIn {..}) + | Err(evm::Error::Wasm {..}) | Err(evm::Error::OutOfStack {..}) | Err(evm::Error::MutableCallInStaticContext) | Ok(FinalizationResult { apply_state: false, .. }) => { diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index 746a737bf..7ffa61288 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -105,6 +105,8 @@ extern crate semver; extern crate stats; extern crate time; extern crate transient_hashmap; +extern crate parity_wasm; +extern crate wasm_utils; #[macro_use] extern crate log; diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index 3ad23505b..5bbf7f7c3 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -80,9 +80,11 @@ pub struct CommonParams { /// Number of first block where dust cleanup rules (EIP-168 and EIP169) begin. pub dust_protection_transition: BlockNumber, /// Nonce cap increase per block. Nonce cap is only checked if dust protection is enabled. - pub nonce_cap_increment : u64, + pub nonce_cap_increment: u64, /// Enable dust cleanup for contracts. - pub remove_dust_contracts : bool, + pub remove_dust_contracts: bool, + /// Wasm support + pub wasm: bool, } impl From for CommonParams { @@ -110,6 +112,7 @@ impl From for CommonParams { dust_protection_transition: p.dust_protection_transition.map_or(BlockNumber::max_value(), Into::into), nonce_cap_increment: p.nonce_cap_increment.map_or(64, Into::into), remove_dust_contracts: p.remove_dust_contracts.unwrap_or(false), + wasm: p.wasm.unwrap_or(false), } } } diff --git a/ethcore/src/types/trace_types/error.rs b/ethcore/src/types/trace_types/error.rs index 0348fc2a4..f10f2d1bb 100644 --- a/ethcore/src/types/trace_types/error.rs +++ b/ethcore/src/types/trace_types/error.rs @@ -42,6 +42,8 @@ pub enum Error { Internal, /// When execution tries to modify the state in static context MutableCallInStaticContext, + /// Wasm error + Wasm, } impl<'a> From<&'a EvmError> for Error { @@ -53,6 +55,7 @@ impl<'a> From<&'a EvmError> for Error { EvmError::StackUnderflow { .. } => Error::StackUnderflow, EvmError::OutOfStack { .. } => Error::OutOfStack, EvmError::BuiltIn { .. } => Error::BuiltIn, + EvmError::Wasm { .. } => Error::Wasm, EvmError::Internal(_) => Error::Internal, EvmError::MutableCallInStaticContext => Error::MutableCallInStaticContext, } @@ -75,6 +78,7 @@ impl fmt::Display for Error { StackUnderflow => "Stack underflow", OutOfStack => "Out of stack", BuiltIn => "Built-in failed", + Wasm => "Wasm runtime error", Internal => "Internal error", MutableCallInStaticContext => "Mutable Call In Static Context", }; @@ -94,6 +98,7 @@ impl Encodable for Error { Internal => 5, BuiltIn => 6, MutableCallInStaticContext => 7, + Wasm => 8, }; s.append_internal(&value); @@ -113,6 +118,7 @@ impl Decodable for Error { 5 => Ok(Internal), 6 => Ok(BuiltIn), 7 => Ok(MutableCallInStaticContext), + 8 => Ok(Wasm), _ => Err(DecoderError::Custom("Invalid error type")), } } diff --git a/json/src/spec/params.rs b/json/src/spec/params.rs index 8103515cb..0c0b1b01e 100644 --- a/json/src/spec/params.rs +++ b/json/src/spec/params.rs @@ -89,6 +89,8 @@ pub struct Params { pub nonce_cap_increment: Option, /// See `CommonParams` docs. pub remove_dust_contracts : Option, + /// Wasm support flag + pub wasm: Option, } #[cfg(test)]