From ee14a3fb3108f51a0dd7f9830359dc5263b3ded9 Mon Sep 17 00:00:00 2001 From: Nikolay Volf Date: Sun, 10 Sep 2017 18:02:31 +0200 Subject: [PATCH] WASM runtime update (#6467) * refactor to new parity-wasm * more errors refactoring * final test * update tests * fix merge bugs --- Cargo.lock | 12 +- ethcore/evm/Cargo.toml | 3 +- ethcore/evm/src/lib.rs | 1 - ethcore/res/wasm-tests | 2 +- ethcore/wasm/Cargo.toml | 2 +- ethcore/wasm/src/env.rs | 44 +++++- ethcore/wasm/src/lib.rs | 55 ++++--- ethcore/wasm/src/ptr.rs | 14 +- ethcore/wasm/src/result.rs | 8 +- ethcore/wasm/src/runtime.rs | 302 +++++++++++++++++++++++++----------- ethcore/wasm/src/tests.rs | 134 +++++++++++++--- 11 files changed, 427 insertions(+), 150 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1bf522580..33d49894d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,7 +7,7 @@ dependencies = [ "ethcore-logger 1.8.0", "ethcore-util 1.8.0", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-wasm 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-wasm 0.14.3 (registry+https://github.com/rust-lang/crates.io-index)", "vm 0.1.0", "wasm-utils 0.1.0 (git+https://github.com/paritytech/wasm-utils)", ] @@ -959,7 +959,6 @@ dependencies = [ "heapsize 0.4.0 (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 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "rlp 0.2.0", "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2188,7 +2187,7 @@ dependencies = [ [[package]] name = "parity-wasm" -version = "0.12.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3278,13 +3277,14 @@ 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#9462bcc0680f0ec2c876abdf75bae981dd4344a5" +source = "git+https://github.com/paritytech/wasm-utils#95f9f04d1036c39de5af1c811c6e5dc488fb73d9" 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)", + "glob 0.2.11 (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 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-wasm 0.14.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -3500,7 +3500,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 (registry+https://github.com/rust-lang/crates.io-index)" = "51104c8b8da5cd0ebe0ab765dfab37bc1927b4a01a3d870b0fe09d9ee65e35ea" +"checksum parity-wasm 0.14.3 (registry+https://github.com/rust-lang/crates.io-index)" = "466c01423614bbf89a37b0fc081e1ed3523dfd9064497308ad3f9c7c9f0092bb" "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" diff --git a/ethcore/evm/Cargo.toml b/ethcore/evm/Cargo.toml index 89baaadde..46260c08c 100644 --- a/ethcore/evm/Cargo.toml +++ b/ethcore/evm/Cargo.toml @@ -16,11 +16,10 @@ lazy_static = "0.2" log = "0.3" rlp = { path = "../../util/rlp" } vm = { path = "../vm" } -parity-wasm = "0.12" -parking_lot = "0.4" ethcore-logger = { path = "../../logger" } wasm-utils = { git = "https://github.com/paritytech/wasm-utils" } hash = { path = "../../util/hash" } +parking_lot = "0.4" [dev-dependencies] rustc-hex = "1.0" diff --git a/ethcore/evm/src/lib.rs b/ethcore/evm/src/lib.rs index 1acb57400..8c48ad86b 100644 --- a/ethcore/evm/src/lib.rs +++ b/ethcore/evm/src/lib.rs @@ -23,7 +23,6 @@ extern crate ethcore_util as util; extern crate ethcore_bigint as bigint; extern crate ethjson; extern crate rlp; -extern crate parity_wasm; extern crate parking_lot; extern crate wasm_utils; extern crate ethcore_logger; diff --git a/ethcore/res/wasm-tests b/ethcore/res/wasm-tests index 519b0b967..5fd27564f 160000 --- a/ethcore/res/wasm-tests +++ b/ethcore/res/wasm-tests @@ -1 +1 @@ -Subproject commit 519b0b967cffd7d1236ef21698b1e6e415a048e9 +Subproject commit 5fd27564f1ab49b25bb419bfc0cc68137e1f12f2 diff --git a/ethcore/wasm/Cargo.toml b/ethcore/wasm/Cargo.toml index 7f3c41917..1b18a5c2a 100644 --- a/ethcore/wasm/Cargo.toml +++ b/ethcore/wasm/Cargo.toml @@ -8,7 +8,7 @@ byteorder = "1.0" ethcore-util = { path = "../../util" } ethcore-bigint = { path = "../../util/bigint" } log = "0.3" -parity-wasm = "0.12" +parity-wasm = "0.14" wasm-utils = { git = "https://github.com/paritytech/wasm-utils" } vm = { path = "../vm" } ethcore-logger = { path = "../../logger" } diff --git a/ethcore/wasm/src/env.rs b/ethcore/wasm/src/env.rs index 777016a1b..c32e3ed84 100644 --- a/ethcore/wasm/src/env.rs +++ b/ethcore/wasm/src/env.rs @@ -19,7 +19,7 @@ use parity_wasm::elements::ValueType::*; use parity_wasm::interpreter::{self, UserFunctionDescriptor}; use parity_wasm::interpreter::UserFunctionDescriptor::*; -use super::runtime::Runtime; +use super::runtime::{Runtime, UserTrap}; pub const SIGNATURES: &'static [UserFunctionDescriptor] = &[ Static( @@ -87,6 +87,41 @@ pub const SIGNATURES: &'static [UserFunctionDescriptor] = &[ &[I32; 3], Some(I32), ), + Static( + "_panic", + &[I32; 2], + None, + ), + Static( + "_blockhash", + &[I32; 3], + Some(I32), + ), + Static( + "_coinbase", + &[I32], + None, + ), + Static( + "_timestamp", + &[], + Some(I32), + ), + Static( + "_blocknumber", + &[], + Some(I32), + ), + Static( + "_difficulty", + &[I32], + None, + ), + Static( + "_gaslimit", + &[I32], + None, + ), // TODO: Get rid of it also somehow? Static( @@ -102,9 +137,10 @@ pub const SIGNATURES: &'static [UserFunctionDescriptor] = &[ ), ]; -pub fn native_bindings<'a>(runtime: &'a mut Runtime) -> interpreter::UserFunctions<'a> { - interpreter::UserFunctions { - executor: runtime, +pub fn native_bindings<'a>(runtime: &'a mut Runtime) -> interpreter::UserDefinedElements<'a, UserTrap> { + interpreter::UserDefinedElements { + executor: Some(runtime), + globals: ::std::collections::HashMap::new(), functions: ::std::borrow::Cow::from(SIGNATURES), } } \ No newline at end of file diff --git a/ethcore/wasm/src/lib.rs b/ethcore/wasm/src/lib.rs index fec270be2..8eb14a91a 100644 --- a/ethcore/wasm/src/lib.rs +++ b/ethcore/wasm/src/lib.rs @@ -39,21 +39,41 @@ use parity_wasm::{interpreter, elements}; use parity_wasm::interpreter::ModuleInstanceInterface; use vm::{GasLeft, ReturnData, ActionParams}; -use self::runtime::{Runtime, RuntimeContext}; +use self::runtime::{Runtime, RuntimeContext, UserTrap}; -pub use self::runtime::Error as RuntimeError; +pub use self::runtime::InterpreterError; const DEFAULT_RESULT_BUFFER: usize = 1024; +/// Wrapped interpreter error +#[derive(Debug)] +pub struct Error(InterpreterError); + +impl From for Error { + fn from(e: InterpreterError) -> Self { + Error(e) + } +} + +impl From for vm::Error { + fn from(e: Error) -> Self { + vm::Error::Wasm(format!("Wasm runtime error: {:?}", e.0)) + } +} + +impl From for vm::Error { + fn from(e: UserTrap) -> Self { e.into() } +} + /// Wasm interpreter instance pub struct WasmInterpreter { - program: interpreter::ProgramInstance, + program: runtime::InterpreterProgramInstance, result: Vec, } impl WasmInterpreter { /// New wasm interpreter instance - pub fn new() -> Result { + pub fn new() -> Result { Ok(WasmInterpreter { program: interpreter::ProgramInstance::new()?, result: Vec::with_capacity(DEFAULT_RESULT_BUFFER), @@ -109,7 +129,7 @@ impl vm::Vm for WasmInterpreter { params.value.value(), params.data.unwrap_or(Vec::with_capacity(0)), ) - )?; + ).map_err(|e| Error(e))?; { let execution_params = runtime.execution_params() @@ -118,27 +138,30 @@ impl vm::Vm for WasmInterpreter { 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); - vm::Error::from(RuntimeError::Interpreter(err)) + vm::Error::from(Error(err)) })?; - module_instance.execute_export("_call", execution_params) - .map_err(|err| { + match module_instance.execute_export("_call", execution_params) { + Ok(_) => { }, + Err(interpreter::Error::User(UserTrap::Suicide)) => { }, + Err(err) => { trace!(target: "wasm", "Error executing contract: {:?}", err); - vm::Error::from(RuntimeError::Interpreter(err)) - })?; + return Err(vm::Error::from(Error(err))) + } + } } let result = result::WasmResult::new(d_ptr); - if result.peek_empty(&*runtime.memory())? { + if result.peek_empty(&*runtime.memory()).map_err(|e| Error(e))? { 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())?); + self.result.extend(result.pop(&*runtime.memory()).map_err(|e| Error(e.into()))?); let len = self.result.len(); Ok(GasLeft::NeedsReturn { - gas_left: runtime.gas_left()?.into(), + gas_left: runtime.gas_left().map_err(|e| Error(e.into()))?.into(), data: ReturnData::new( ::std::mem::replace(&mut self.result, Vec::with_capacity(DEFAULT_RESULT_BUFFER)), 0, @@ -149,9 +172,3 @@ impl vm::Vm for WasmInterpreter { } } } - -impl From for vm::Error { - fn from(err: runtime::Error) -> vm::Error { - vm::Error::Wasm(format!("WASM runtime-error: {:?}", err)) - } -} diff --git a/ethcore/wasm/src/ptr.rs b/ethcore/wasm/src/ptr.rs index 11edbad70..8f7c15490 100644 --- a/ethcore/wasm/src/ptr.rs +++ b/ethcore/wasm/src/ptr.rs @@ -16,9 +16,9 @@ //! Wasm bound-checked ptr -use parity_wasm::interpreter; +use super::runtime::{InterpreterMemoryInstance, InterpreterError, UserTrap}; -/// Bound-checked wrapper for webassembly memory +/// Bound-checked wrapper for webassembly memory pub struct WasmPtr(u32); /// Error in bound check @@ -28,15 +28,21 @@ pub enum Error { } impl From for WasmPtr { - fn from(raw: u32) -> Self { + fn from(raw: u32) -> Self { WasmPtr(raw) } } +impl From for InterpreterError { + fn from(_e: Error) -> Self { + UserTrap::MemoryAccessViolation.into() + } +} + 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> { + pub fn slice(&self, len: u32, mem: &InterpreterMemoryInstance) -> Result, Error> { mem.get(self.0, len as usize).map_err(|_| Error::AccessViolation) } diff --git a/ethcore/wasm/src/result.rs b/ethcore/wasm/src/result.rs index 3d1e51f64..932bbafa6 100644 --- a/ethcore/wasm/src/result.rs +++ b/ethcore/wasm/src/result.rs @@ -18,10 +18,8 @@ use byteorder::{LittleEndian, ByteOrder}; -use parity_wasm::interpreter; - use super::ptr::WasmPtr; -use super::runtime::Error as RuntimeError; +use super::runtime::{InterpreterError, InterpreterMemoryInstance}; /// Wrapper for wasm contract call result pub struct WasmResult { @@ -35,13 +33,13 @@ impl WasmResult { } /// Check if the result contains any data - pub fn peek_empty(&self, mem: &interpreter::MemoryInstance) -> Result { + pub fn peek_empty(&self, mem: &InterpreterMemoryInstance) -> 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> { + pub fn pop(self, mem: &InterpreterMemoryInstance) -> Result, InterpreterError> { 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); diff --git a/ethcore/wasm/src/runtime.rs b/ethcore/wasm/src/runtime.rs index f7fb07473..6ddaa7a10 100644 --- a/ethcore/wasm/src/runtime.rs +++ b/ethcore/wasm/src/runtime.rs @@ -30,31 +30,68 @@ use vm::CallType; 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, +/// User trap in native code +#[derive(Debug, Clone, PartialEq)] +pub enum UserTrap { + /// Storage read error + StorageReadError, + /// Storage update error + StorageUpdateError, /// Memory access violation - AccessViolation, - /// Interpreter runtime error - Interpreter(interpreter::Error), + MemoryAccessViolation, + /// Native code resulted in suicide + Suicide, + /// Suicide was requested but coudn't complete + SuicideAbort, + /// Invalid gas state inside interpreter + InvalidGasState, + /// Query of the balance resulted in an error + BalanceQueryError, + /// Failed allocation + AllocationFailed, + /// Gas limit reached + GasLimit, + /// Unknown runtime function + Unknown, + /// Passed string had invalid utf-8 encoding + BadUtf8, + /// Other error in native code + Other, + /// Panic with message + Panic(String), } -impl From for Error { - fn from(err: interpreter::Error) -> Self { - Error::Interpreter(err) +impl ::std::fmt::Display for UserTrap { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { + match *self { + UserTrap::StorageReadError => write!(f, "Storage read error"), + UserTrap::StorageUpdateError => write!(f, "Storage update error"), + UserTrap::MemoryAccessViolation => write!(f, "Memory access violation"), + UserTrap::SuicideAbort => write!(f, "Attempt to suicide resulted in an error"), + UserTrap::InvalidGasState => write!(f, "Invalid gas state"), + UserTrap::BalanceQueryError => write!(f, "Balance query resulted in an error"), + UserTrap::Suicide => write!(f, "Suicide result"), + UserTrap::Unknown => write!(f, "Unknown runtime function invoked"), + UserTrap::AllocationFailed => write!(f, "Memory allocation failed (OOM)"), + UserTrap::BadUtf8 => write!(f, "String encoding is bad utf-8 sequence"), + UserTrap::GasLimit => write!(f, "Invocation resulted in gas limit violated"), + UserTrap::Other => write!(f, "Other unspecified error"), + UserTrap::Panic(ref msg) => write!(f, "Panic: {}", msg), + } } } -impl From for Error { +impl interpreter::UserError for UserTrap { } + +pub type InterpreterError = interpreter::Error; +pub type InterpreterMemoryInstance = interpreter::MemoryInstance; +pub type InterpreterProgramInstance = interpreter::ProgramInstance; +pub type InterpreterCallerContext<'a> = interpreter::CallerContext<'a, UserTrap>; + +impl From for UserTrap { fn from(err: PtrError) -> Self { match err { - PtrError::AccessViolation => Error::AccessViolation, + PtrError::AccessViolation => UserTrap::MemoryAccessViolation, } } } @@ -79,20 +116,20 @@ pub struct Runtime<'a, 'b> { gas_limit: u64, dynamic_top: u32, ext: &'a mut vm::Ext, - memory: Arc, + memory: Arc, context: RuntimeContext, - instance: &'b interpreter::ProgramInstance, + instance: &'b InterpreterProgramInstance, } impl<'a, 'b> Runtime<'a, 'b> { /// New runtime for wasm contract with specified params pub fn with_params<'c, 'd>( ext: &'c mut vm::Ext, - memory: Arc, + memory: Arc, stack_space: u32, gas_limit: u64, context: RuntimeContext, - program_instance: &'d interpreter::ProgramInstance, + program_instance: &'d InterpreterProgramInstance, ) -> Runtime<'c, 'd> { Runtime { gas_counter: 0, @@ -106,30 +143,28 @@ impl<'a, 'b> Runtime<'a, 'b> { } /// Write to the storage from wasm memory - pub fn storage_write(&mut self, context: interpreter::CallerContext) - -> Result, interpreter::Error> + pub fn storage_write(&mut self, context: InterpreterCallerContext) + -> Result, InterpreterError> { 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()))?; + self.ext.set_storage(key, val).map_err(|_| UserTrap::StorageUpdateError)?; Ok(Some(0i32.into())) } /// Read from the storage to wasm memory - pub fn storage_read(&mut self, context: interpreter::CallerContext) - -> Result, interpreter::Error> + pub fn storage_read(&mut self, context: InterpreterCallerContext) + -> Result, InterpreterError> { 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()))?; + let val = self.ext.storage_at(&key).map_err(|_| UserTrap::StorageReadError)?; self.memory.set(val_ptr as u32, &*val)?; @@ -137,21 +172,21 @@ impl<'a, 'b> Runtime<'a, 'b> { } /// Pass suicide to state runtime - pub fn suicide(&mut self, context: interpreter::CallerContext) - -> Result, interpreter::Error> + pub fn suicide(&mut self, context: InterpreterCallerContext) + -> Result, InterpreterError> { 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()))?; + self.ext.suicide(&refund_address).map_err(|_| UserTrap::SuicideAbort)?; - Ok(None) + // We send trap to interpreter so it should abort further execution + Err(UserTrap::Suicide.into()) } /// Invoke create in the state runtime - pub fn create(&mut self, context: interpreter::CallerContext) - -> Result, interpreter::Error> + pub fn create(&mut self, context: InterpreterCallerContext) + -> Result, InterpreterError> { // // method signature: @@ -172,7 +207,7 @@ impl<'a, 'b> Runtime<'a, 'b> { 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()))? + .map_err(|_| UserTrap::InvalidGasState)? .into(); match self.ext.create(&gas_left, &endowment, &code, vm::CreateContractAddress::FromSenderAndCodeHash) { @@ -189,8 +224,8 @@ impl<'a, 'b> Runtime<'a, 'b> { } } - pub fn call(&mut self, context: interpreter::CallerContext) - -> Result, interpreter::Error> + pub fn call(&mut self, context: InterpreterCallerContext) + -> Result, InterpreterError> { // // method signature: @@ -207,8 +242,8 @@ impl<'a, 'b> Runtime<'a, 'b> { } - fn call_code(&mut self, context: interpreter::CallerContext) - -> Result, interpreter::Error> + fn call_code(&mut self, context: InterpreterCallerContext) + -> Result, InterpreterError> { // // signature (same as static call): @@ -227,9 +262,9 @@ impl<'a, 'b> Runtime<'a, 'b> { &mut self, use_val: bool, call_type: CallType, - context: interpreter::CallerContext, + context: InterpreterCallerContext, ) - -> Result, interpreter::Error> + -> Result, InterpreterError> { trace!(target: "wasm", "runtime: call code"); @@ -255,7 +290,7 @@ impl<'a, 'b> Runtime<'a, 'b> { if let Some(ref val) = val { let address_balance = self.ext.balance(&self.context.address) - .map_err(|_| interpreter::Error::Trap("Gas state error".to_owned()))?; + .map_err(|_| UserTrap::BalanceQueryError)?; if &address_balance < val { trace!(target: "wasm", "runtime: call failed due to balance check"); @@ -266,7 +301,7 @@ impl<'a, 'b> Runtime<'a, 'b> { let mut result = Vec::with_capacity(result_alloc_len as usize); result.resize(result_alloc_len as usize, 0); let gas = self.gas_left() - .map_err(|_| interpreter::Error::Trap("Gas state error".to_owned()))? + .map_err(|_| UserTrap::InvalidGasState)? .into(); // todo: optimize to use memory views once it's in let payload = self.memory.get(input_ptr, input_len as usize)?; @@ -294,8 +329,8 @@ impl<'a, 'b> Runtime<'a, 'b> { } } - pub fn static_call(&mut self, context: interpreter::CallerContext) - -> Result, interpreter::Error> + pub fn static_call(&mut self, context: InterpreterCallerContext) + -> Result, InterpreterError> { // signature (same as code call): // fn ( @@ -311,8 +346,8 @@ impl<'a, 'b> Runtime<'a, 'b> { /// Allocate memory using the wasm stack params - pub fn malloc(&mut self, context: interpreter::CallerContext) - -> Result, interpreter::Error> + pub fn malloc(&mut self, context: InterpreterCallerContext) + -> Result, InterpreterError> { let amount = context.value_stack.pop_as::()? as u32; let previous_top = self.dynamic_top; @@ -321,21 +356,21 @@ impl<'a, 'b> Runtime<'a, 'b> { } /// Allocate memory in wasm memory instance - pub fn alloc(&mut self, amount: u32) -> Result { + 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> + fn gas(&mut self, context: InterpreterCallerContext) + -> Result, InterpreterError> { 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))) + Err(UserTrap::GasLimit.into()) } } @@ -350,50 +385,50 @@ impl<'a, 'b> Runtime<'a, 'b> { } } - fn h256_at(&self, ptr: WasmPtr) -> Result { + 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()))? + .map_err(|_| UserTrap::MemoryAccessViolation)? )) } - fn pop_h256(&self, context: &mut interpreter::CallerContext) -> Result { + fn pop_h256(&self, context: &mut InterpreterCallerContext) -> Result { let ptr = WasmPtr::from_i32(context.value_stack.pop_as::()?) - .map_err(|_| interpreter::Error::Trap("Memory access violation".to_owned()))?; + .map_err(|_| UserTrap::MemoryAccessViolation)?; self.h256_at(ptr) } - fn pop_u256(&self, context: &mut interpreter::CallerContext) -> Result { + fn pop_u256(&self, context: &mut InterpreterCallerContext) -> Result { let ptr = WasmPtr::from_i32(context.value_stack.pop_as::()?) - .map_err(|_| interpreter::Error::Trap("Memory access violation".to_owned()))?; + .map_err(|_| UserTrap::MemoryAccessViolation)?; self.h256_at(ptr).map(Into::into) } - fn address_at(&self, ptr: WasmPtr) -> Result { + 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()))? + .map_err(|_| UserTrap::MemoryAccessViolation)? )) } - fn pop_address(&self, context: &mut interpreter::CallerContext) -> Result { + fn pop_address(&self, context: &mut InterpreterCallerContext) -> Result { let ptr = WasmPtr::from_i32(context.value_stack.pop_as::()?) - .map_err(|_| interpreter::Error::Trap("Memory access violation".to_owned()))?; + .map_err(|_| UserTrap::MemoryAccessViolation)?; self.address_at(ptr) } - fn user_trap(&mut self, _context: interpreter::CallerContext) - -> Result, interpreter::Error> + fn unknown_trap(&mut self, _context: InterpreterCallerContext) + -> Result, UserTrap> { - Err(interpreter::Error::Trap("unknown trap".to_owned())) + Err(UserTrap::Unknown) } fn user_noop(&mut self, - _context: interpreter::CallerContext - ) -> Result, interpreter::Error> { + _context: InterpreterCallerContext + ) -> Result, InterpreterError> { Ok(None) } /// Write call descriptor to wasm memory - pub fn write_descriptor(&mut self, call_args: CallArgs) -> Result { + pub fn write_descriptor(&mut self, call_args: CallArgs) -> Result { let d_ptr = self.alloc(16)?; let args_len = call_args.len(); @@ -417,14 +452,14 @@ impl<'a, 'b> Runtime<'a, 'b> { Ok(d_ptr.into()) } - fn debug_log(&mut self, context: interpreter::CallerContext) - -> Result, interpreter::Error> + fn debug_log(&mut self, context: InterpreterCallerContext) + -> Result, InterpreterError> { 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()))?; + .map_err(|_| UserTrap::BadUtf8)?; trace!(target: "wasm", "Contract debug message: {}", msg); @@ -432,18 +467,18 @@ impl<'a, 'b> Runtime<'a, 'b> { } /// Query current gas left for execution - pub fn gas_left(&self) -> Result { - if self.gas_counter > self.gas_limit { return Err(Error::InvalidGasState); } + pub fn gas_left(&self) -> Result { + if self.gas_counter > self.gas_limit { return Err(UserTrap::InvalidGasState); } Ok(self.gas_limit - self.gas_counter) } /// Shared memory reference - pub fn memory(&self) -> &interpreter::MemoryInstance { + pub fn memory(&self) -> &InterpreterMemoryInstance { &*self.memory } - fn mem_copy(&self, context: interpreter::CallerContext) - -> Result, interpreter::Error> + fn mem_copy(&self, context: InterpreterCallerContext) + -> Result, InterpreterError> { let len = context.value_stack.pop_as::()? as u32; let dst = context.value_stack.pop_as::()? as u32; @@ -459,8 +494,8 @@ impl<'a, 'b> Runtime<'a, 'b> { x >> 24 | x >> 8 & 0xff00 | x << 8 & 0xff0000 | x << 24 } - fn bitswap_i64(&mut self, context: interpreter::CallerContext) - -> Result, interpreter::Error> + fn bitswap_i64(&mut self, context: InterpreterCallerContext) + -> Result, InterpreterError> { let x1 = context.value_stack.pop_as::()?; let x2 = context.value_stack.pop_as::()?; @@ -471,13 +506,83 @@ impl<'a, 'b> Runtime<'a, 'b> { self.return_i64(result) } - fn return_i64(&mut self, val: i64) -> Result, interpreter::Error> { + fn user_panic(&mut self, context: InterpreterCallerContext) + -> Result, InterpreterError> + { + 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(|_| UserTrap::BadUtf8)?; + + trace!(target: "wasm", "Contract custom panic message: {}", msg); + + Err(UserTrap::Panic(msg).into()) + } + + fn block_hash(&mut self, context: InterpreterCallerContext) + -> Result, InterpreterError> + { + let return_ptr = context.value_stack.pop_as::()? as u32; + let block_hi = context.value_stack.pop_as::()? as u32; + let block_lo = context.value_stack.pop_as::()? as u32; + + let block_num = (block_hi as u64) << 32 | block_lo as u64; + + trace!("Requesting block hash for block #{}", block_num); + let hash = self.ext.blockhash(&U256::from(block_num)); + + self.memory.set(return_ptr, &*hash)?; + + Ok(Some(0i32.into())) + } + + fn coinbase(&mut self, context: InterpreterCallerContext) + -> Result, InterpreterError> + { + let return_ptr = context.value_stack.pop_as::()? as u32; + self.memory.set(return_ptr, &*self.ext.env_info().author)?; + Ok(None) + } + + fn timestamp(&mut self, _context: InterpreterCallerContext) + -> Result, InterpreterError> + { + let timestamp = self.ext.env_info().timestamp as i64; + self.return_i64(timestamp) + } + + fn block_number(&mut self, _context: InterpreterCallerContext) + -> Result, InterpreterError> + { + let block_number: u64 = self.ext.env_info().number.into(); + self.return_i64(block_number as i64) + } + + fn difficulty(&mut self, context: InterpreterCallerContext) + -> Result, InterpreterError> + { + let return_ptr = context.value_stack.pop_as::()? as u32; + let difficulty: H256 = self.ext.env_info().difficulty.into(); + self.memory.set(return_ptr, &*difficulty)?; + Ok(None) + } + + fn ext_gas_limit(&mut self, context: InterpreterCallerContext) + -> Result, InterpreterError> + { + let return_ptr = context.value_stack.pop_as::()? as u32; + let gas_limit: H256 = self.ext.env_info().gas_limit.into(); + self.memory.set(return_ptr, &*gas_limit)?; + Ok(None) + } + + fn return_i64(&mut self, val: i64) -> Result, InterpreterError> { let uval = val as u64; let hi = (uval >> 32) as i32; let lo = (uval << 32 >> 32) as i32; - let target = self.instance.module("contract") - .ok_or(interpreter::Error::Trap("Error locating main execution entry".to_owned()))?; + let target = self.instance.module("contract").ok_or(UserTrap::Other)?; target.execute_export( "setTempRet0", self.execution_params().add_argument( @@ -489,7 +594,7 @@ impl<'a, 'b> Runtime<'a, 'b> { )) } - pub fn execution_params(&mut self) -> interpreter::ExecutionParams { + pub fn execution_params(&mut self) -> interpreter::ExecutionParams { use super::env; let env_instance = self.instance.module("env") @@ -505,9 +610,9 @@ impl<'a, 'b> Runtime<'a, 'b> { } } -impl<'a, 'b> interpreter::UserFunctionExecutor for Runtime<'a, 'b> { - fn execute(&mut self, name: &str, context: interpreter::CallerContext) - -> Result, interpreter::Error> +impl<'a, 'b> interpreter::UserFunctionExecutor for Runtime<'a, 'b> { + fn execute(&mut self, name: &str, context: InterpreterCallerContext) + -> Result, InterpreterError> { match name { "_malloc" => { @@ -551,10 +656,31 @@ impl<'a, 'b> interpreter::UserFunctionExecutor for Runtime<'a, 'b> { "_llvm_bswap_i64" => { self.bitswap_i64(context) }, + "_panic" => { + self.user_panic(context) + }, + "_blockhash" => { + self.block_hash(context) + }, + "_coinbase" => { + self.coinbase(context) + }, + "_timestamp" => { + self.timestamp(context) + }, + "_blocknumber" => { + self.block_number(context) + }, + "_difficulty" => { + self.difficulty(context) + }, + "_gaslimit" => { + self.ext_gas_limit(context) + }, _ => { trace!(target: "wasm", "Trapped due to unhandled function: '{}'", name); - self.user_trap(context) - } + Ok(self.unknown_trap(context)?) + }, } } } diff --git a/ethcore/wasm/src/tests.rs b/ethcore/wasm/src/tests.rs index f0a828394..b79f38bdc 100644 --- a/ethcore/wasm/src/tests.rs +++ b/ethcore/wasm/src/tests.rs @@ -15,6 +15,7 @@ // along with Parity. If not, see . use std::sync::Arc; +use std::collections::HashMap; use byteorder::{LittleEndian, ByteOrder}; use bigint::prelude::U256; use bigint::hash::H256; @@ -87,7 +88,7 @@ fn logger() { }; println!("ext.store: {:?}", ext.store); - assert_eq!(gas_left, U256::from(99327)); + assert_eq!(gas_left, U256::from(99529)); let address_val: H256 = address.into(); assert_eq!( ext.store.get(&"0100000000000000000000000000000000000000000000000000000000000000".parse().unwrap()).expect("storage key to exist"), @@ -138,7 +139,7 @@ fn identity() { } }; - assert_eq!(gas_left, U256::from(99_672)); + assert_eq!(gas_left, U256::from(99_762)); assert_eq!( Address::from_slice(&result), @@ -172,7 +173,7 @@ fn dispersion() { } }; - assert_eq!(gas_left, U256::from(99_270)); + assert_eq!(gas_left, U256::from(99_360)); assert_eq!( result, @@ -201,7 +202,7 @@ fn suicide_not() { } }; - assert_eq!(gas_left, U256::from(99_578)); + assert_eq!(gas_left, U256::from(99_668)); assert_eq!( result, @@ -235,7 +236,7 @@ fn suicide() { } }; - assert_eq!(gas_left, U256::from(99_621)); + assert_eq!(gas_left, U256::from(99_699)); assert!(ext.suicides.contains(&refund)); } @@ -266,7 +267,7 @@ fn create() { assert!(ext.calls.contains( &FakeCall { call_type: FakeCallType::Create, - gas: U256::from(99_674), + gas: U256::from(99_734), sender_address: None, receive_address: None, value: Some(1_000_000_000.into()), @@ -274,7 +275,7 @@ fn create() { code_address: None, } )); - assert_eq!(gas_left, U256::from(99_596)); + assert_eq!(gas_left, U256::from(99_686)); } @@ -308,7 +309,7 @@ fn call_code() { assert!(ext.calls.contains( &FakeCall { call_type: FakeCallType::Call, - gas: U256::from(99_069), + gas: U256::from(99_129), sender_address: Some(sender), receive_address: Some(receiver), value: None, @@ -316,7 +317,7 @@ fn call_code() { code_address: Some("0d13710000000000000000000000000000000000".parse().unwrap()), } )); - assert_eq!(gas_left, U256::from(94144)); + assert_eq!(gas_left, U256::from(94262)); // siphash result let res = LittleEndian::read_u32(&result[..]); @@ -353,7 +354,7 @@ fn call_static() { assert!(ext.calls.contains( &FakeCall { call_type: FakeCallType::Call, - gas: U256::from(99_069), + gas: U256::from(99_129), sender_address: Some(sender), receive_address: Some(receiver), value: None, @@ -361,7 +362,7 @@ fn call_static() { code_address: Some("13077bfb00000000000000000000000000000000".parse().unwrap()), } )); - assert_eq!(gas_left, U256::from(94144)); + assert_eq!(gas_left, U256::from(94262)); // siphash result let res = LittleEndian::read_u32(&result[..]); @@ -387,7 +388,7 @@ fn realloc() { GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()), } }; - assert_eq!(gas_left, U256::from(99432)); + assert_eq!(gas_left, U256::from(99522)); assert_eq!(result, vec![0u8; 2]); } @@ -413,12 +414,15 @@ fn storage_read() { } }; - assert_eq!(gas_left, U256::from(99682)); + assert_eq!(gas_left, U256::from(99800)); assert_eq!(Address::from(&result[12..32]), address); } macro_rules! reqrep_test { ($name: expr, $input: expr) => { + reqrep_test!($name, $input, vm::EnvInfo::default(), HashMap::new()) + }; + ($name: expr, $input: expr, $info: expr, $block_hashes: expr) => { { ::ethcore_logger::init_log(); let code = load_sample!($name); @@ -428,9 +432,13 @@ macro_rules! reqrep_test { params.code = Some(Arc::new(code)); params.data = Some($input); + let mut fake_ext = FakeExt::new(); + fake_ext.info = $info; + fake_ext.blockhashes = $block_hashes; + let (gas_left, result) = { let mut interpreter = wasm_interpreter(); - let result = interpreter.exec(params, &mut FakeExt::new()).expect("Interpreter to execute without any errors"); + let result = interpreter.exec(params, &mut fake_ext).expect("Interpreter to execute without any errors"); match result { GasLeft::Known(_) => { panic!("Test is expected to return payload to check"); }, GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()), @@ -439,7 +447,7 @@ macro_rules! reqrep_test { (gas_left, result) } - } + }; } // math_* tests check the ability of wasm contract to perform big integer operations @@ -464,7 +472,7 @@ fn math_add() { } ); - assert_eq!(gas_left, U256::from(98087)); + assert_eq!(gas_left, U256::from(98177)); assert_eq!( U256::from_dec_str("1888888888888888888888888888887").unwrap(), (&result[..]).into() @@ -486,7 +494,7 @@ fn math_mul() { } ); - assert_eq!(gas_left, U256::from(97236)); + assert_eq!(gas_left, U256::from(97326)); assert_eq!( U256::from_dec_str("888888888888888888888888888887111111111111111111111111111112").unwrap(), (&result[..]).into() @@ -508,7 +516,7 @@ fn math_sub() { } ); - assert_eq!(gas_left, U256::from(98131)); + assert_eq!(gas_left, U256::from(98221)); assert_eq!( U256::from_dec_str("111111111111111111111111111111").unwrap(), (&result[..]).into() @@ -529,9 +537,97 @@ fn math_div() { } ); - assert_eq!(gas_left, U256::from(91420)); + assert_eq!(gas_left, U256::from(91510)); assert_eq!( U256::from_dec_str("1125000").unwrap(), (&result[..]).into() ); } + +// This test checks the ability of wasm contract to invoke +// varios blockchain runtime methods +#[test] +fn externs() { + let (gas_left, result) = reqrep_test!( + "externs.wasm", + Vec::new(), + vm::EnvInfo { + number: 0x9999999999u64.into(), + author: "efefefefefefefefefefefefefefefefefefefef".parse().unwrap(), + timestamp: 0x8888888888u64.into(), + difficulty: H256::from("0f1f2f3f4f5f6f7f8f9fafbfcfdfefff0d1d2d3d4d5d6d7d8d9dadbdcdddedfd").into(), + gas_limit: 0x777777777777u64.into(), + last_hashes: Default::default(), + gas_used: 0.into(), + }, + { + let mut hashes = HashMap::new(); + hashes.insert( + U256::from(0), + H256::from("9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d") + ); + hashes.insert( + U256::from(1), + H256::from("7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b") + ); + hashes + } + ); + + assert_eq!( + &result[0..64].to_vec(), + &vec![ + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b,0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, + ], + "Block hashes requested and returned do not match" + ); + + assert_eq!( + &result[64..84].to_vec(), + &vec![ + 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, + ], + "Coinbase requested and returned does not match" + ); + + assert_eq!( + &result[84..92].to_vec(), + &vec![ + 0x88, 0x88, 0x88, 0x88, 0x88, 0x00, 0x00, 0x00 + ], + "Timestamp requested and returned does not match" + ); + + assert_eq!( + &result[92..100].to_vec(), + &vec![ + 0x99, 0x99, 0x99, 0x99, 0x99, 0x00, 0x00, 0x00 + ], + "Block number requested and returned does not match" + ); + + assert_eq!( + &result[100..132].to_vec(), + &vec![ + 0x0f, 0x1f, 0x2f, 0x3f, 0x4f, 0x5f, 0x6f, 0x7f, + 0x8f, 0x9f, 0xaf, 0xbf, 0xcf, 0xdf, 0xef, 0xff, + 0x0d, 0x1d, 0x2d, 0x3d, 0x4d, 0x5d, 0x6d, 0x7d, + 0x8d, 0x9d, 0xad, 0xbd, 0xcd, 0xdd, 0xed, 0xfd, + ], + "Difficulty requested and returned does not match" + ); + + assert_eq!( + &result[132..164].to_vec(), + &vec![ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, + ], + "Gas limit requested and returned does not match" + ); + + assert_eq!(gas_left, U256::from(97588)); +} \ No newline at end of file