From 902221c6e18df0a7b1b3c9c8b46c60e61bb3ef3e Mon Sep 17 00:00:00 2001 From: Nikolay Volf Date: Mon, 5 Feb 2018 22:59:27 +0300 Subject: [PATCH] wasmi interpreter (#7796) --- Cargo.lock | 22 +- ethcore/res/wasm-tests | 2 +- ethcore/src/executive.rs | 6 +- ethcore/vm/src/schedule.rs | 28 +- ethcore/wasm/Cargo.toml | 3 +- ethcore/wasm/run/src/runner.rs | 2 +- ethcore/wasm/src/env.rs | 392 ++++++----- ethcore/wasm/src/lib.rs | 202 +++--- ethcore/wasm/src/parser.rs | 81 +++ ethcore/wasm/src/ptr.rs | 58 -- ethcore/wasm/src/result.rs | 49 -- ethcore/wasm/src/runtime.rs | 1113 ++++++++++++-------------------- ethcore/wasm/src/tests.rs | 190 ++---- 13 files changed, 901 insertions(+), 1247 deletions(-) create mode 100644 ethcore/wasm/src/parser.rs delete mode 100644 ethcore/wasm/src/ptr.rs delete mode 100644 ethcore/wasm/src/result.rs diff --git a/Cargo.lock b/Cargo.lock index cc5aba3ea0a..4b1e0c9318b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2286,12 +2286,12 @@ dependencies = [ [[package]] name = "parity-wasm" -version = "0.15.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -3479,15 +3479,16 @@ dependencies = [ "ethcore-util 1.9.0", "libc 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-wasm 0.15.3 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-wasm 0.23.0 (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)", + "wasmi 0.0.0 (git+https://github.com/pepyakin/wasmi)", ] [[package]] name = "wasm-utils" version = "0.1.0" -source = "git+https://github.com/paritytech/wasm-utils#3d59f7ca0661317bc66894a26b2a5a319fa5d229" +source = "git+https://github.com/paritytech/wasm-utils#6fdc1c4ed47a6acb0a4774da505a416dd637bc6d" dependencies = [ "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.29.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3495,7 +3496,16 @@ dependencies = [ "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.8 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-wasm 0.15.3 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-wasm 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasmi" +version = "0.0.0" +source = "git+https://github.com/pepyakin/wasmi#551c99273042deaad869c17798060e2212deacab" +dependencies = [ + "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-wasm 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -3714,6 +3724,7 @@ dependencies = [ "checksum parity-ui-old-precompiled 1.9.0 (git+https://github.com/js-dist-paritytech/parity-beta-1-9-v1.git)" = "" "checksum parity-ui-precompiled 1.9.0 (git+https://github.com/js-dist-paritytech/parity-beta-1-9-shell.git)" = "" "checksum parity-wasm 0.15.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8431a184ad88cfbcd71a792aaca319cc7203a94300c26b8dce2d0df0681ea87d" +"checksum parity-wasm 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1ba4b1d4236b76694f6ab8d8d00cdbe1e37c6dd1b5c803d26721f27e097d4d9" "checksum parity-wordlist 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d0dec124478845b142f68b446cbee953d14d4b41f1bc0425024417720dce693" "checksum parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "149d8f5b97f3c1133e3cfcd8886449959e856b557ff281e292b733d7c69e005e" "checksum parking_lot_core 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "4f610cb9664da38e417ea3225f23051f589851999535290e077939838ab7a595" @@ -3837,6 +3848,7 @@ dependencies = [ "checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d" "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 wasmi 0.0.0 (git+https://github.com/pepyakin/wasmi)" = "" "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/res/wasm-tests b/ethcore/res/wasm-tests index f25e60e300a..ff8504a7f3b 160000 --- a/ethcore/res/wasm-tests +++ b/ethcore/res/wasm-tests @@ -1 +1 @@ -Subproject commit f25e60e300ade96aba2adffb3ebca8e5b4965720 +Subproject commit ff8504a7f3b3fe78af47fa4e0c24e4a75a7306fd diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index 2c01772b2b1..2d7d9ae7ffd 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -156,11 +156,7 @@ impl TransactOptions { pub fn executor(machine: &Machine, vm_factory: &Factory, params: &ActionParams) -> Box { if machine.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") - ) + Box::new(wasm::WasmInterpreter) } else { vm_factory.create(params.gas) } diff --git a/ethcore/vm/src/schedule.rs b/ethcore/vm/src/schedule.rs index 53e585d3770..11a049dfc30 100644 --- a/ethcore/vm/src/schedule.rs +++ b/ethcore/vm/src/schedule.rs @@ -127,20 +127,19 @@ pub struct WasmCosts { pub mul: u32, /// Memory (load/store) operations multiplier. pub mem: u32, - /// Memory copy operation, per byte. - pub mem_cmp: u32, - /// Memory copy operation, per byte. - pub mem_copy: u32, - /// Memory move operation, per byte. - pub mem_move: u32, - /// Memory set operation, per byte. - pub mem_set: u32, - /// Static region charge, per byte. - pub static_region: u32, /// General static query of U256 value from env-info pub static_u256: u32, /// General static query of Address value from env-info pub static_address: u32, + /// Memory stipend. Amount of free memory (in 64kb pages) each contract can use for stack. + pub initial_mem: u32, + /// Grow memory cost, per page (64kb) + pub grow_mem: u32, + /// Cost of wasm opcode is calculated as TABLE_ENTRY_COST * `opcodes_mul` / `opcodes_div` + pub opcodes_mul: u32, + /// Cost of wasm opcode is calculated as TABLE_ENTRY_COST * `opcodes_mul` / `opcodes_div` + pub opcodes_div: u32, + } impl Default for WasmCosts { @@ -150,13 +149,12 @@ impl Default for WasmCosts { div: 16, mul: 4, mem: 2, - mem_cmp: 1, - mem_copy: 1, - mem_move: 1, - mem_set: 1, - static_region: 1, static_u256: 64, static_address: 40, + initial_mem: 4096, + grow_mem: 8192, + opcodes_mul: 3, + opcodes_div: 8, } } } diff --git a/ethcore/wasm/Cargo.toml b/ethcore/wasm/Cargo.toml index 0d55179aefd..be4eecd5d14 100644 --- a/ethcore/wasm/Cargo.toml +++ b/ethcore/wasm/Cargo.toml @@ -8,8 +8,9 @@ byteorder = "1.0" ethcore-util = { path = "../../util" } ethcore-bigint = { path = "../../util/bigint" } log = "0.3" -parity-wasm = "0.15" +parity-wasm = "0.23" libc = "0.2" wasm-utils = { git = "https://github.com/paritytech/wasm-utils" } vm = { path = "../vm" } ethcore-logger = { path = "../../logger" } +wasmi = { git = "https://github.com/pepyakin/wasmi" } \ No newline at end of file diff --git a/ethcore/wasm/run/src/runner.rs b/ethcore/wasm/run/src/runner.rs index 06a6b046906..10b627fbcaf 100644 --- a/ethcore/wasm/run/src/runner.rs +++ b/ethcore/wasm/run/src/runner.rs @@ -16,7 +16,7 @@ fn load_code>(p: P) -> io::Result> { } fn wasm_interpreter() -> WasmInterpreter { - WasmInterpreter::new().expect("wasm interpreter to create without errors") + WasmInterpreter } #[derive(Debug)] diff --git a/ethcore/wasm/src/env.rs b/ethcore/wasm/src/env.rs index 27c7c93a946..c5b30df842a 100644 --- a/ethcore/wasm/src/env.rs +++ b/ethcore/wasm/src/env.rs @@ -14,183 +14,279 @@ // 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::{self, UserFunctionDescriptor}; -use parity_wasm::interpreter::UserFunctionDescriptor::*; -use super::runtime::{Runtime, UserTrap}; - -pub const SIGNATURES: &'static [UserFunctionDescriptor] = &[ - Static( - "_storage_read", - &[I32; 2], - None, - ), - Static( - "_storage_write", - &[I32; 2], - None, - ), - Static( - "_balance", - &[I32; 2], - None, - ), - Static( - "_ext_malloc", - &[I32], - Some(I32), - ), - Static( - "_ext_free", - &[I32], +//! Env module glue for wasmi interpreter + +use std::cell::RefCell; +use wasmi::{ + self, Signature, Error, FuncRef, FuncInstance, MemoryDescriptor, + MemoryRef, MemoryInstance, +}; + +/// Internal ids all functions runtime supports. This is just a glue for wasmi interpreter +/// that lacks high-level api and later will be factored out +pub mod ids { + pub const STORAGE_WRITE_FUNC: usize = 0; + pub const STORAGE_READ_FUNC: usize = 10; + pub const RET_FUNC: usize = 20; + pub const GAS_FUNC: usize = 30; + pub const FETCH_INPUT_FUNC: usize = 40; + pub const INPUT_LENGTH_FUNC: usize = 50; + pub const CCALL_FUNC: usize = 60; + pub const SCALL_FUNC: usize = 70; + pub const DCALL_FUNC: usize = 80; + pub const VALUE_FUNC: usize = 90; + pub const CREATE_FUNC: usize = 100; + pub const SUICIDE_FUNC: usize = 110; + pub const BLOCKHASH_FUNC: usize = 120; + pub const BLOCKNUMBER_FUNC: usize = 130; + pub const COINBASE_FUNC: usize = 140; + pub const DIFFICULTY_FUNC: usize = 150; + pub const GASLIMIT_FUNC: usize = 160; + pub const TIMESTAMP_FUNC: usize = 170; + pub const ADDRESS_FUNC: usize = 180; + pub const SENDER_FUNC: usize = 190; + pub const ORIGIN_FUNC: usize = 200; + pub const ELOG_FUNC: usize = 210; + + pub const PANIC_FUNC: usize = 1000; + pub const DEBUG_FUNC: usize = 1010; +} + +/// Signatures of all functions runtime supports. The actual dispatch happens at +/// impl runtime::Runtime methods. +pub mod signatures { + use wasmi::{self, ValueType}; + use wasmi::ValueType::*; + + pub struct StaticSignature(pub &'static [ValueType], pub Option); + + pub const STORAGE_READ: StaticSignature = StaticSignature( + &[I32, I32], None, - ), - Static( - "gas", - &[I32], + ); + + pub const STORAGE_WRITE: StaticSignature = StaticSignature( + &[I32, I32], None, - ), - Static( - "_debug", - &[I32; 2], + ); + + pub const RET: StaticSignature = StaticSignature( + &[I32, I32], None, - ), - Static( - "_suicide", + ); + + pub const GAS: StaticSignature = StaticSignature( &[I32], None, - ), - Static( - "_create", - &[I32; 4], + ); + + pub const FETCH_INPUT: StaticSignature = StaticSignature( + &[I32], + None, + ); + + pub const INPUT_LENGTH: StaticSignature = StaticSignature( + &[], Some(I32), - ), - Static( - "_ccall", + ); + + pub const CCALL: StaticSignature = StaticSignature( &[I64, I32, I32, I32, I32, I32, I32], Some(I32), - ), - Static( - "_dcall", + ); + + pub const DCALL: StaticSignature = StaticSignature( &[I64, I32, I32, I32, I32, I32], Some(I32), - ), - Static( - "_scall", + ); + + pub const SCALL: StaticSignature = StaticSignature( &[I64, I32, I32, I32, I32, I32], Some(I32), - ), - Static( - "abort", - &[I32], + ); + + pub const PANIC: StaticSignature = StaticSignature( + &[I32, I32], None, - ), - Static( - "_emscripten_memcpy_big", - &[I32; 3], - Some(I32), - ), - Static( - "_ext_memcmp", - &[I32; 3], - Some(I32), - ), - Static( - "_ext_memcpy", - &[I32; 3], - Some(I32), - ), - Static( - "_ext_memset", - &[I32; 3], - Some(I32), - ), - Static( - "_ext_memmove", - &[I32; 3], - Some(I32), - ), - Static( - "_panic", - &[I32; 2], - None, - ), - Static( - "_blockhash", - &[I64, I32], + ); + + pub const DEBUG: StaticSignature = StaticSignature( + &[I32, I32], None, - ), - Static( - "_coinbase", + ); + + pub const VALUE: StaticSignature = StaticSignature( &[I32], None, - ), - Static( - "_sender", + ); + + pub const CREATE: StaticSignature = StaticSignature( + &[I32, I32, I32, I32], + Some(I32), + ); + + pub const SUICIDE: StaticSignature = StaticSignature( &[I32], None, - ), - Static( - "_origin", + ); + + pub const BLOCKHASH: StaticSignature = StaticSignature( + &[I64, I32], + None, + ); + + pub const BLOCKNUMBER: StaticSignature = StaticSignature( + &[], + Some(I64), + ); + + pub const COINBASE: StaticSignature = StaticSignature( &[I32], None, - ), - Static( - "_address", + ); + + pub const DIFFICULTY: StaticSignature = StaticSignature( &[I32], None, - ), - Static( - "_value", + ); + + pub const GASLIMIT: StaticSignature = StaticSignature( &[I32], None, - ), - Static( - "_timestamp", - &[], - Some(I64), - ), - Static( - "_blocknumber", + ); + + pub const TIMESTAMP: StaticSignature = StaticSignature( &[], Some(I64), - ), - Static( - "_difficulty", + ); + + pub const ADDRESS: StaticSignature = StaticSignature( &[I32], None, - ), - Static( - "_gaslimit", + ); + + pub const SENDER: StaticSignature = StaticSignature( &[I32], None, - ), - Static( - "_elog", - &[I32; 4], - None, - ), - - // TODO: Get rid of it also somehow? - Static( - "_llvm_trap", - &[I32; 0], - None - ), - - Static( - "_llvm_bswap_i64", - &[I64], - Some(I64) - ), -]; - -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), + ); + + pub const ORIGIN: StaticSignature = StaticSignature( + &[I32], + None, + ); + + pub const ELOG: StaticSignature = StaticSignature( + &[I32, I32, I32, I32], + None, + ); + + impl Into for StaticSignature { + fn into(self) -> wasmi::Signature { + wasmi::Signature::new(self.0, self.1) + } + } +} + +fn host(signature: signatures::StaticSignature, idx: usize) -> FuncRef { + FuncInstance::alloc_host(signature.into(), idx) +} + +/// Import resolver for wasmi +/// Maps all functions that runtime support to the corresponding contract import +/// entries. +/// Also manages initial memory request from the runtime. +#[derive(Default)] +pub struct ImportResolver { + max_memory: u32, + memory: RefCell>, +} + +impl ImportResolver { + /// New import resolver with specifed maximum amount of inital memory (in wasm pages = 64kb) + pub fn with_limit(max_memory: u32) -> ImportResolver { + ImportResolver { + max_memory: max_memory, + memory: RefCell::new(None), + } + } + + /// Returns memory that was instantiated during the contract module + /// start. If contract does not use memory at all, the dummy memory of length (0, 0) + /// will be created instead. So this method always returns memory instance + /// unless errored. + pub fn memory_ref(&self) -> MemoryRef { + { + let mut mem_ref = self.memory.borrow_mut(); + if mem_ref.is_none() { + *mem_ref = Some( + MemoryInstance::alloc(0, Some(0)).expect("Memory allocation (0, 0) should not fail; qed") + ); + } + } + + self.memory.borrow().clone().expect("it is either existed or was created as (0, 0) above; qed") + } + + /// Returns memory size module initially requested + pub fn memory_size(&self) -> Result { + Ok(self.memory_ref().size()) } } + +impl wasmi::ModuleImportResolver for ImportResolver { + fn resolve_func(&self, field_name: &str, _signature: &Signature) -> Result { + let func_ref = match field_name { + "storage_read" => host(signatures::STORAGE_READ, ids::STORAGE_READ_FUNC), + "storage_write" => host(signatures::STORAGE_WRITE, ids::STORAGE_WRITE_FUNC), + "ret" => host(signatures::RET, ids::RET_FUNC), + "gas" => host(signatures::GAS, ids::GAS_FUNC), + "input_length" => host(signatures::INPUT_LENGTH, ids::INPUT_LENGTH_FUNC), + "fetch_input" => host(signatures::FETCH_INPUT, ids::FETCH_INPUT_FUNC), + "panic" => host(signatures::PANIC, ids::PANIC_FUNC), + "debug" => host(signatures::DEBUG, ids::DEBUG_FUNC), + "ccall" => host(signatures::CCALL, ids::CCALL_FUNC), + "dcall" => host(signatures::DCALL, ids::DCALL_FUNC), + "scall" => host(signatures::SCALL, ids::SCALL_FUNC), + "value" => host(signatures::VALUE, ids::VALUE_FUNC), + "create" => host(signatures::CREATE, ids::CREATE_FUNC), + "suicide" => host(signatures::SUICIDE, ids::SUICIDE_FUNC), + "blockhash" => host(signatures::BLOCKHASH, ids::BLOCKHASH_FUNC), + "blocknumber" => host(signatures::BLOCKNUMBER, ids::BLOCKNUMBER_FUNC), + "coinbase" => host(signatures::COINBASE, ids::COINBASE_FUNC), + "difficulty" => host(signatures::DIFFICULTY, ids::DIFFICULTY_FUNC), + "gaslimit" => host(signatures::GASLIMIT, ids::GASLIMIT_FUNC), + "timestamp" => host(signatures::TIMESTAMP, ids::TIMESTAMP_FUNC), + "address" => host(signatures::ADDRESS, ids::ADDRESS_FUNC), + "sender" => host(signatures::SENDER, ids::SENDER_FUNC), + "origin" => host(signatures::ORIGIN, ids::ORIGIN_FUNC), + "elog" => host(signatures::ELOG, ids::ELOG_FUNC), + _ => { + return Err(wasmi::Error::Instantiation( + format!("Export {} not found", field_name), + )) + } + }; + + Ok(func_ref) + } + + fn resolve_memory( + &self, + field_name: &str, + descriptor: &MemoryDescriptor, + ) -> Result { + if field_name == "memory" { + let effective_max = descriptor.maximum().unwrap_or(self.max_memory + 1); + if descriptor.initial() > self.max_memory || effective_max > self.max_memory + { + Err(Error::Instantiation("Module requested too much memory".to_owned())) + } else { + let mem = MemoryInstance::alloc(descriptor.initial(), descriptor.maximum())?; + *self.memory.borrow_mut() = Some(mem.clone()); + Ok(mem) + } + } else { + Err(Error::Instantiation("Memory imported under unknown name".to_owned())) + } + } +} \ No newline at end of file diff --git a/ethcore/wasm/src/lib.rs b/ethcore/wasm/src/lib.rs index 7979269e2bc..e48f2263486 100644 --- a/ethcore/wasm/src/lib.rs +++ b/ethcore/wasm/src/lib.rs @@ -16,35 +16,30 @@ //! Wasm Interpreter -extern crate vm; +extern crate byteorder; +extern crate ethcore_logger; extern crate ethcore_util as util; extern crate ethcore_bigint as bigint; #[macro_use] extern crate log; -extern crate ethcore_logger; -extern crate byteorder; -extern crate parity_wasm; extern crate libc; +extern crate parity_wasm; +extern crate vm; extern crate wasm_utils; +extern crate wasmi; mod runtime; -mod ptr; -mod result; #[cfg(test)] mod tests; mod env; mod panic_payload; - -const DEFAULT_STACK_SPACE: u32 = 5 * 1024 * 1024; - -use parity_wasm::{interpreter, elements}; -use parity_wasm::interpreter::ModuleInstanceInterface; +mod parser; use vm::{GasLeft, ReturnData, ActionParams}; -use self::runtime::{Runtime, RuntimeContext, UserTrap}; +use wasmi::Error as InterpreterError; -pub use self::runtime::InterpreterError; +use runtime::{Runtime, RuntimeContext}; -const DEFAULT_RESULT_BUFFER: usize = 1024; +use bigint::U256; /// Wrapped interpreter error #[derive(Debug)] @@ -62,139 +57,110 @@ impl From for vm::Error { } } -impl From for vm::Error { - fn from(e: UserTrap) -> Self { e.into() } -} - /// Wasm interpreter instance -pub struct WasmInterpreter { - program: runtime::InterpreterProgramInstance, - result: Vec, -} +pub struct WasmInterpreter; -impl WasmInterpreter { - /// New wasm interpreter instance - pub fn new() -> Result { - Ok(WasmInterpreter { - program: interpreter::ProgramInstance::new()?, - result: Vec::with_capacity(DEFAULT_RESULT_BUFFER), - }) +impl From for vm::Error { + fn from(e: runtime::Error) -> Self { + vm::Error::Wasm(format!("Wasm runtime error: {:?}", e)) } } impl vm::Vm for WasmInterpreter { fn exec(&mut self, params: ActionParams, ext: &mut vm::Ext) -> vm::Result { - use parity_wasm::elements::Deserialize; + let (module, data) = parser::payload(¶ms, ext.schedule())?; - let code = params.code.expect("exec is only called on contract with code; qed"); + let loaded_module = wasmi::Module::from_parity_wasm_module(module).map_err(Error)?; - trace!(target: "wasm", "Started wasm interpreter with code.len={:?}", code.len()); + let instantiation_resolover = env::ImportResolver::with_limit(16); - let env_instance = self.program.module("env") - // prefer explicit panic here - .expect("Wasm program to contain env module"); + let module_instance = wasmi::ModuleInstance::new( + &loaded_module, + &wasmi::ImportsBuilder::new().with_resolver("env", &instantiation_resolover) + ).map_err(Error)?; - let env_memory = env_instance.memory(interpreter::ItemIndex::Internal(0)) - // prefer explicit panic here - .expect("Linear memory to exist in wasm runtime"); + let adjusted_gas = params.gas * U256::from(ext.schedule().wasm.opcodes_div) / + U256::from(ext.schedule().wasm.opcodes_mul); - if params.gas > ::std::u64::MAX.into() { - return Err(vm::Error::Wasm("Wasm interpreter cannot run contracts with gas >= 2^64".to_owned())); + if adjusted_gas > ::std::u64::MAX.into() + { + return Err(vm::Error::Wasm("Wasm interpreter cannot run contracts with gas (wasm adjusted) >= 2^64".to_owned())); } - let mut runtime = Runtime::with_params( - ext, - env_memory, - DEFAULT_STACK_SPACE, - params.gas.low_u64(), - RuntimeContext { - address: params.address, - sender: params.sender, - origin: params.origin, - code_address: params.code_address, - value: params.value.value(), - }, - &self.program, - ); - - let (mut cursor, data_position) = match params.params_type { - vm::ParamsType::Embedded => { - let module_size = parity_wasm::peek_size(&*code); - ( - ::std::io::Cursor::new(&code[..module_size]), - module_size - ) - }, - vm::ParamsType::Separate => { - (::std::io::Cursor::new(&code[..]), 0) - }, - }; - - let contract_module = wasm_utils::inject_gas_counter( - elements::Module::deserialize( - &mut cursor - ).map_err(|err| { - vm::Error::Wasm(format!("Error deserializing contract code ({:?})", err)) - })?, - runtime.gas_rules(), - ); - - let data_section_length = contract_module.data_section() - .map(|section| section.entries().iter().fold(0, |sum, entry| sum + entry.value().len())) - .unwrap_or(0) - as u64; - - let static_segment_cost = data_section_length * runtime.ext().schedule().wasm.static_region as u64; - runtime.charge(|_| static_segment_cost).map_err(Error)?; - - let d_ptr = { - match params.params_type { - vm::ParamsType::Embedded => { - runtime.write_descriptor( - if data_position < code.len() { &code[data_position..] } else { &[] } - ).map_err(Error)? + let initial_memory = instantiation_resolover.memory_size().map_err(Error)?; + trace!(target: "wasm", "Contract requested {:?} pages of initial memory", initial_memory); + + let (gas_left, result) = { + let mut runtime = Runtime::with_params( + ext, + instantiation_resolover.memory_ref(), + // cannot overflow, checked above + adjusted_gas.low_u64(), + data.to_vec(), + RuntimeContext { + address: params.address, + sender: params.sender, + origin: params.origin, + code_address: params.code_address, + value: params.value.value(), }, - vm::ParamsType::Separate => { - runtime.write_descriptor(¶ms.data.unwrap_or_default()) - .map_err(Error)? - } - } - }; + ); - { - let execution_params = runtime.execution_params() - .add_argument(interpreter::RuntimeValue::I32(d_ptr.as_raw() as i32)); + // cannot overflow if static_region < 2^16, + // initial_memory ∈ [0..2^32) + // total_charge <- static_region * 2^32 * 2^16 + // total_charge ∈ [0..2^64) if static_region ∈ [0..2^16) + // qed + assert!(runtime.schedule().wasm.initial_mem < 1 << 16); + runtime.charge(|s| initial_memory as u64 * s.wasm.initial_mem as u64)?; - 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(Error(err)) - })?; + let module_instance = module_instance.run_start(&mut runtime).map_err(Error)?; - match module_instance.execute_export("_call", execution_params) { + match module_instance.invoke_export("call", &[], &mut runtime) { Ok(_) => { }, - Err(interpreter::Error::User(UserTrap::Suicide)) => { }, + Err(InterpreterError::Host(boxed)) => { + match boxed.downcast_ref::() { + None => { + return Err(vm::Error::Wasm("Invalid user error used in interpreter".to_owned())); + } + Some(runtime_err) => { + match *runtime_err { + runtime::Error::Suicide => { + // Suicide uses trap to break execution + } + ref any_err => { + trace!(target: "wasm", "Error executing contract: {:?}", boxed); + return Err(vm::Error::from(Error::from(InterpreterError::Host(Box::new(any_err.clone()))))); + } + } + } + } + }, Err(err) => { trace!(target: "wasm", "Error executing contract: {:?}", err); - return Err(vm::Error::from(Error(err))) + return Err(vm::Error::from(Error::from(err))) } } - } + ( + runtime.gas_left().expect("Cannot fail since it was not updated since last charge"), + runtime.into_result(), + ) + }; + + let gas_left = + U256::from(gas_left) * U256::from(ext.schedule().wasm.opcodes_mul) + / U256::from(ext.schedule().wasm.opcodes_div); - let result = result::WasmResult::new(d_ptr); - if result.peek_empty(&*runtime.memory()).map_err(|e| Error(e))? { + if result.is_empty() { trace!(target: "wasm", "Contract execution result is empty."); - Ok(GasLeft::Known(runtime.gas_left()?.into())) + Ok(GasLeft::Known(gas_left)) } else { - self.result.clear(); - // todo: use memory views to avoid copy - self.result.extend(result.pop(&*runtime.memory()).map_err(|e| Error(e.into()))?); - let len = self.result.len(); + let len = result.len(); Ok(GasLeft::NeedsReturn { - gas_left: runtime.gas_left().map_err(|e| Error(e.into()))?.into(), + gas_left: gas_left, data: ReturnData::new( - ::std::mem::replace(&mut self.result, Vec::with_capacity(DEFAULT_RESULT_BUFFER)), + result, 0, len, ), diff --git a/ethcore/wasm/src/parser.rs b/ethcore/wasm/src/parser.rs new file mode 100644 index 00000000000..f705bf13127 --- /dev/null +++ b/ethcore/wasm/src/parser.rs @@ -0,0 +1,81 @@ +// 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 . + +//! ActionParams parser for wasm + +use vm; +use wasm_utils::{self, rules}; +use parity_wasm::elements::{self, Deserialize}; +use parity_wasm::peek_size; + +fn gas_rules(schedule: &vm::Schedule) -> rules::Set { + rules::Set::new({ + let mut vals = ::std::collections::HashMap::with_capacity(4); + vals.insert(rules::InstructionType::Load, schedule.wasm.mem as u32); + vals.insert(rules::InstructionType::Store, schedule.wasm.mem as u32); + vals.insert(rules::InstructionType::Div, schedule.wasm.div as u32); + vals.insert(rules::InstructionType::Mul, schedule.wasm.mul as u32); + vals + }).with_grow_cost(schedule.wasm.grow_mem) +} + +/// Splits payload to code and data according to params.params_type, also +/// loads the module instance from payload and injects gas counter according +/// to schedule. +pub fn payload<'a>(params: &'a vm::ActionParams, schedule: &vm::Schedule) + -> Result<(elements::Module, &'a [u8]), vm::Error> +{ + let code = match params.code { + Some(ref code) => &code[..], + None => { return Err(vm::Error::Wasm("Invalid wasm call".to_owned())); } + }; + + let (mut cursor, data_position) = match params.params_type { + vm::ParamsType::Embedded => { + let module_size = peek_size(&*code); + ( + ::std::io::Cursor::new(&code[..module_size]), + module_size + ) + }, + vm::ParamsType::Separate => { + (::std::io::Cursor::new(&code[..]), 0) + }, + }; + + let contract_module = wasm_utils::inject_gas_counter( + elements::Module::deserialize( + &mut cursor + ).map_err(|err| { + vm::Error::Wasm(format!("Error deserializing contract code ({:?})", err)) + })?, + &gas_rules(schedule), + ); + + let data = match params.params_type { + vm::ParamsType::Embedded => { + if data_position < code.len() { &code[data_position..] } else { &[] } + }, + vm::ParamsType::Separate => { + match params.data { + Some(ref s) => &s[..], + None => &[] + } + } + }; + + Ok((contract_module, data)) +} \ No newline at end of file diff --git a/ethcore/wasm/src/ptr.rs b/ethcore/wasm/src/ptr.rs deleted file mode 100644 index 8f7c1549017..00000000000 --- a/ethcore/wasm/src/ptr.rs +++ /dev/null @@ -1,58 +0,0 @@ -// 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 super::runtime::{InterpreterMemoryInstance, InterpreterError, UserTrap}; - -/// 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 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: &InterpreterMemoryInstance) -> 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/wasm/src/result.rs b/ethcore/wasm/src/result.rs deleted file mode 100644 index 932bbafa623..00000000000 --- a/ethcore/wasm/src/result.rs +++ /dev/null @@ -1,49 +0,0 @@ -// 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 super::ptr::WasmPtr; -use super::runtime::{InterpreterError, InterpreterMemoryInstance}; - -/// 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: &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: &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); - - Ok(mem.get(result_ptr, result_len as usize)?) - } -} \ No newline at end of file diff --git a/ethcore/wasm/src/runtime.rs b/ethcore/wasm/src/runtime.rs index 2e8112666eb..6601e040a31 100644 --- a/ethcore/wasm/src/runtime.rs +++ b/ethcore/wasm/src/runtime.rs @@ -16,25 +16,34 @@ //! Wasm evm program runtime intstance -use std::sync::Arc; - -use byteorder::{LittleEndian, ByteOrder}; -use libc::{memcmp, c_void}; - -use vm; -use panic_payload; -use parity_wasm::interpreter; -use wasm_utils::rules; +use util::Address; use bigint::prelude::U256; use bigint::hash::H256; -use util::Address; +use vm::{self, CallType}; +use wasmi::{self, MemoryRef, RuntimeArgs, RuntimeValue, Error as InterpreterError}; +use super::panic_payload; + +pub struct RuntimeContext { + pub address: Address, + pub sender: Address, + pub origin: Address, + pub code_address: Address, + pub value: U256, +} -use vm::CallType; -use super::ptr::{WasmPtr, Error as PtrError}; +pub struct Runtime<'a> { + gas_counter: u64, + gas_limit: u64, + ext: &'a mut vm::Ext, + context: RuntimeContext, + memory: MemoryRef, + args: Vec, + result: Vec, +} /// User trap in native code #[derive(Debug, Clone, PartialEq)] -pub enum UserTrap { +pub enum Error { /// Storage read error StorageReadError, /// Storage update error @@ -61,326 +70,304 @@ pub enum UserTrap { Log, /// Other error in native code Other, + /// Syscall signature mismatch + InvalidSyscall, /// Panic with message Panic(String), } -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::Log => write!(f, "Error occured while logging an event"), - UserTrap::Other => write!(f, "Other unspecified error"), - UserTrap::Panic(ref msg) => write!(f, "Panic: {}", msg), +impl wasmi::HostError for Error { } + +impl From for Error { + fn from(interpreter_err: InterpreterError) -> Self { + match interpreter_err { + InterpreterError::Value(_) => Error::InvalidSyscall, + InterpreterError::Memory(_) => Error::MemoryAccessViolation, + _ => Error::Other, } } } -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 => UserTrap::MemoryAccessViolation, +impl ::std::fmt::Display for Error { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::result::Result<(), ::std::fmt::Error> { + match *self { + Error::StorageReadError => write!(f, "Storage read error"), + Error::StorageUpdateError => write!(f, "Storage update error"), + Error::MemoryAccessViolation => write!(f, "Memory access violation"), + Error::SuicideAbort => write!(f, "Attempt to suicide resulted in an error"), + Error::InvalidGasState => write!(f, "Invalid gas state"), + Error::BalanceQueryError => write!(f, "Balance query resulted in an error"), + Error::Suicide => write!(f, "Suicide result"), + Error::Unknown => write!(f, "Unknown runtime function invoked"), + Error::AllocationFailed => write!(f, "Memory allocation failed (OOM)"), + Error::BadUtf8 => write!(f, "String encoding is bad utf-8 sequence"), + Error::GasLimit => write!(f, "Invocation resulted in gas limit violated"), + Error::Log => write!(f, "Error occured while logging an event"), + Error::InvalidSyscall => write!(f, "Invalid syscall signature encountered at runtime"), + Error::Other => write!(f, "Other unspecified error"), + Error::Panic(ref msg) => write!(f, "Panic: {}", msg), } } } -pub struct RuntimeContext { - pub address: Address, - pub sender: Address, - pub origin: Address, - pub code_address: Address, - pub value: U256, -} +type Result = ::std::result::Result; -/// Runtime enviroment data for wasm contract execution -pub struct Runtime<'a, 'b> { - gas_counter: u64, - gas_limit: u64, - dynamic_top: u32, - ext: &'a mut vm::Ext, - memory: Arc, - context: RuntimeContext, - instance: &'b InterpreterProgramInstance, - gas_rules: rules::Set, -} +impl<'a> Runtime<'a> { -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, - stack_space: u32, + pub fn with_params( + ext: &mut vm::Ext, + memory: MemoryRef, gas_limit: u64, + args: Vec, context: RuntimeContext, - program_instance: &'d InterpreterProgramInstance, - ) -> Runtime<'c, 'd> { - - let rules = { - let schedule = ext.schedule(); - - rules::Set::new({ - let mut vals = ::std::collections::HashMap::with_capacity(4); - vals.insert(rules::InstructionType::Load, schedule.wasm.mem as u32); - vals.insert(rules::InstructionType::Store, schedule.wasm.mem as u32); - vals.insert(rules::InstructionType::Div, schedule.wasm.div as u32); - vals.insert(rules::InstructionType::Mul, schedule.wasm.mul as u32); - vals - }) - }; - + ) -> Runtime { Runtime { gas_counter: 0, gas_limit: gas_limit, - dynamic_top: stack_space, memory: memory, ext: ext, context: context, - instance: program_instance, - gas_rules: rules, + args: args, + result: Vec::new(), } } - /// Write to the storage from wasm memory - 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.charge(|schedule| schedule.sstore_set_gas as u64)?; - - self.ext.set_storage(key, val).map_err(|_| UserTrap::StorageUpdateError)?; + /// Loads 256-bit hash from the specifed sandboxed memory pointer + fn h256_at(&self, ptr: u32) -> Result { + let mut buf = [0u8; 32]; + self.memory.get_into(ptr, &mut buf[..])?; - Ok(None) + Ok(H256::from(&buf[..])) } - /// Read from the storage to wasm memory - 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(|_| UserTrap::StorageReadError)?; - - self.charge(|schedule| schedule.sload_gas as u64)?; + /// Loads 160-bit hash (Ethereum address) from the specified sandboxed memory pointer + fn address_at(&self, ptr: u32) -> Result
{ + let mut buf = [0u8; 20]; + self.memory.get_into(ptr, &mut buf[..])?; - self.memory.set(val_ptr as u32, &*val)?; - - Ok(None) + Ok(Address::from(&buf[..])) } - /// Fetches balance for address - pub fn balance(&mut self, context: InterpreterCallerContext) - -> Result, InterpreterError> - { - let mut context = context; - let return_ptr = context.value_stack.pop_as::()? as u32; - let address = self.pop_address(&mut context)?; - - self.charge(|schedule| schedule.balance_gas as u64)?; + /// Loads 256-bit integer represented with bigendian from the specified sandboxed memory pointer + fn u256_at(&self, ptr: u32) -> Result { + let mut buf = [0u8; 32]; + self.memory.get_into(ptr, &mut buf[..])?; - let balance = self.ext.balance(&address).map_err(|_| UserTrap::BalanceQueryError)?; - let value: H256 = balance.into(); - self.memory.set(return_ptr, &*value)?; - Ok(None) + Ok(U256::from_big_endian(&buf[..])) } - /// Pass suicide to state runtime - pub fn suicide(&mut self, context: InterpreterCallerContext) - -> Result, InterpreterError> - { - let mut context = context; - let refund_address = self.pop_address(&mut context)?; - - if self.ext.exists(&refund_address).map_err(|_| UserTrap::SuicideAbort)? { - trace!(target: "wasm", "Suicide: refund to existing address {}", refund_address); - self.charge(|schedule| schedule.suicide_gas as u64)?; + /// Charge specified amount of gas, returning false is gas limit exceeded, true if not. + /// Intuition about the return value sense is to aswer the question 'are we allowed to continue?' + fn charge_gas(&mut self, amount: u64) -> bool { + let prev = self.gas_counter; + if prev + amount > self.gas_limit { + // exceeds gas + false } else { - trace!(target: "wasm", "Suicide: refund to new address {}", refund_address); - self.charge(|schedule| schedule.suicide_to_new_account_cost as u64)?; + self.gas_counter = prev + amount; + true } - - self.ext.suicide(&refund_address).map_err(|_| UserTrap::SuicideAbort)?; - - // We send trap to interpreter so it should abort further execution - Err(UserTrap::Suicide.into()) } /// Charge gas according to closure - pub fn charge(&mut self, f: F) -> Result<(), InterpreterError> + pub fn charge(&mut self, f: F) -> Result<()> where F: FnOnce(&vm::Schedule) -> u64 { let amount = f(self.ext.schedule()); if !self.charge_gas(amount as u64) { - Err(UserTrap::GasLimit.into()) + Err(Error::GasLimit) } else { Ok(()) } } - pub fn overflow_charge(&mut self, f: F) -> Result<(), InterpreterError> + /// Adjusted charge of gas which scales actual charge according to the wasm opcode counting coefficient + pub fn adjusted_charge(&mut self, f: F) -> Result<()> + where F: FnOnce(&vm::Schedule) -> u64 + { + self.charge(|schedule| f(schedule) * schedule.wasm.opcodes_div as u64 / schedule.wasm.opcodes_mul as u64) + } + + /// Charge gas provided by the closure, and closure also can return overflowing + /// flag as None in gas cost. + pub fn overflow_charge(&mut self, f: F) -> Result<()> where F: FnOnce(&vm::Schedule) -> Option { let amount = match f(self.ext.schedule()) { Some(amount) => amount, - None => { return Err(UserTrap::GasLimit.into()); } + None => { return Err(Error::GasLimit.into()); } }; if !self.charge_gas(amount as u64) { - Err(UserTrap::GasLimit.into()) + Err(Error::GasLimit.into()) } else { Ok(()) } } - /// Invoke create in the state runtime - pub fn create(&mut self, context: InterpreterCallerContext) - -> Result, InterpreterError> + /// Same as overflow_charge, but with amount adjusted by wasm opcodes coeff + pub fn adjusted_overflow_charge(&mut self, f: F) -> Result<()> + where F: FnOnce(&vm::Schedule) -> Option { - // - // method signature: - // fn create(endowment: *const u8, code_ptr: *const u8, code_len: u32, result_ptr: *mut u8) -> i32; - // + self.overflow_charge(|schedule| + f(schedule) + .and_then(|x| x.checked_mul(schedule.wasm.opcodes_div as u64)) + .map(|x| x / schedule.wasm.opcodes_mul as u64) + ) + } - 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); + /// Read from the storage to wasm memory. + pub fn storage_read(&mut self, args: RuntimeArgs) -> Result<()> + { + let key = self.h256_at(args.nth(0)?)?; + let val_ptr: u32 = args.nth(1)?; - let code = self.memory.get(code_ptr, code_len as usize)?; + let val = self.ext.storage_at(&key).map_err(|_| Error::StorageReadError)?; - self.charge(|schedule| schedule.create_gas as u64)?; - self.charge(|schedule| schedule.create_data_gas as u64 * code.len() as u64)?; + self.adjusted_charge(|schedule| schedule.sload_gas as u64)?; - let gas_left = self.gas_left() - .map_err(|_| UserTrap::InvalidGasState)? - .into(); + self.memory.set(val_ptr as u32, &*val)?; - match self.ext.create(&gas_left, &endowment, &code, vm::CreateContractAddress::FromSenderAndCodeHash) { - vm::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())) - }, - vm::ContractCreateResult::Failed => { - trace!(target: "wasm", "runtime: create contract fail"); - Ok(Some((-1i32).into())) - }, - vm::ContractCreateResult::Reverted(gas_left, _) => { - trace!(target: "wasm", "runtime: create contract reverted"); - self.gas_counter = self.gas_limit - gas_left.low_u64(); - Ok(Some((-1i32).into())) - }, - } + Ok(()) } - pub fn call(&mut self, context: InterpreterCallerContext) - -> Result, InterpreterError> + /// Write to storage from wasm memory. + pub fn storage_write(&mut self, args: RuntimeArgs) -> Result<()> { - // - // method signature: - // fn ( - // gas: i64, - // address: *const u8, - // val_ptr: *const u8, - // input_ptr: *const u8, - // input_len: u32, - // result_ptr: *mut u8, - // result_len: u32, - // ) -> i32 + let key = self.h256_at(args.nth(0)?)?; + let val_ptr: u32 = args.nth(1)?; + + self.adjusted_charge(|schedule| schedule.sstore_set_gas as u64)?; + + let val = self.h256_at(val_ptr)?; + self.ext.set_storage(key, val).map_err(|_| Error::StorageUpdateError)?; + + Ok(()) + } + + /// Return currently used schedule. + pub fn schedule(&self) -> &vm::Schedule { + self.ext.schedule() + } + + /// Return. Syscall takes 2 arguments - pointer in sandboxed memory where result is and + /// the length of the result. + pub fn ret(&mut self, args: RuntimeArgs) -> Result<()> { + let ptr: u32 = args.nth(0)?; + let len: u32 = args.nth(1)?; + + trace!(target: "wasm", "Contract ret: {} bytes @ {}", len, ptr); + + self.result = self.memory.get(ptr, len as usize)?; + + Ok(()) + } + + /// Destroy the runtime, returning currently recorded result of the execution. + pub fn into_result(self) -> Vec { + self.result + } + + /// 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) + } + + /// Report gas cost with the params passed in wasm stack + fn gas(&mut self, args: RuntimeArgs) -> Result<()> { + let amount: u32 = args.nth(0)?; + if self.charge_gas(amount as u64) { + Ok(()) + } else { + Err(Error::GasLimit.into()) + } + } - self.do_call(true, CallType::Call, context) + /// Query the length of the input bytes + fn input_legnth(&mut self) -> RuntimeValue { + RuntimeValue::I32(self.args.len() as i32) } + /// Write input bytes to the memory location using the passed pointer + fn fetch_input(&mut self, args: RuntimeArgs) -> Result<()> { + let ptr: u32 = args.nth(0)?; + self.memory.set(ptr, &self.args[..])?; + Ok(()) + } - fn call_code(&mut self, context: InterpreterCallerContext) - -> Result, InterpreterError> + /// User panic. Contract can invoke this when he encounters unrecoverable error. + fn panic(&mut self, args: RuntimeArgs) -> Result<()> { - // - // signature (same as static call): - // fn ( - // gas: i64, - // address: *const u8, - // input_ptr: *const u8, - // input_len: u32, - // result_ptr: *mut u8, - // result_len: u32, - // ) -> i32 + let payload_ptr: u32 = args.nth(0)?; + let payload_len: u32 = args.nth(1)?; + + let raw_payload = self.memory.get(payload_ptr, payload_len as usize)?; + let payload = panic_payload::decode(&raw_payload); + let msg = format!( + "{msg}, {file}:{line}:{col}", + msg = payload + .msg + .as_ref() + .map(String::as_ref) + .unwrap_or(""), + file = payload + .file + .as_ref() + .map(String::as_ref) + .unwrap_or(""), + line = payload.line.unwrap_or(0), + col = payload.col.unwrap_or(0) + ); + trace!(target: "wasm", "Contract custom panic message: {}", msg); - self.do_call(false, CallType::DelegateCall, context) + Err(Error::Panic(msg).into()) } fn do_call( &mut self, use_val: bool, call_type: CallType, - context: InterpreterCallerContext, + args: RuntimeArgs, ) - -> Result, InterpreterError> + -> Result { + trace!(target: "wasm", "runtime: CALL({:?})", call_type); - trace!(target: "wasm", "runtime: call code"); - let mut context = context; - let result_alloc_len = context.value_stack.pop_as::()? as u32; - trace!(target: "wasm", " result_len: {:?}", result_alloc_len); + let gas: u64 = args.nth(0)?; + trace!(target: "wasm", " gas: {:?}", gas); - let result_ptr = context.value_stack.pop_as::()? as u32; - trace!(target: "wasm", " result_ptr: {:?}", result_ptr); + let address = self.address_at(args.nth(1)?)?; + trace!(target: "wasm", " address: {:?}", address); - let input_len = context.value_stack.pop_as::()? as u32; - trace!(target: "wasm", " input_len: {:?}", input_len); + let vofs = if use_val { 1 } else { 0 }; + let val = if use_val { Some(self.u256_at(args.nth(2)?)?) } else { None }; + trace!(target: "wasm", " val: {:?}", val); - let input_ptr = context.value_stack.pop_as::()? as u32; + let input_ptr: u32 = args.nth(2 + vofs)?; trace!(target: "wasm", " input_ptr: {:?}", input_ptr); - let val = if use_val { Some(self.pop_u256(&mut context)?) } - else { None }; - trace!(target: "wasm", " val: {:?}", val); + let input_len: u32 = args.nth(3 + vofs)?; + trace!(target: "wasm", " input_len: {:?}", input_len); - let address = self.pop_address(&mut context)?; - trace!(target: "wasm", " address: {:?}", address); + let result_ptr: u32 = args.nth(4 + vofs)?; + trace!(target: "wasm", " result_ptr: {:?}", result_ptr); - let gas = context.value_stack.pop_as::()? as u64; - trace!(target: "wasm", " gas: {:?}", gas); + let result_alloc_len: u32 = args.nth(5 + vofs)?; + trace!(target: "wasm", " result_len: {:?}", result_alloc_len); if let Some(ref val) = val { let address_balance = self.ext.balance(&self.context.address) - .map_err(|_| UserTrap::BalanceQueryError)?; + .map_err(|_| Error::BalanceQueryError)?; if &address_balance < val { trace!(target: "wasm", "runtime: call failed due to balance check"); - return Ok(Some((-1i32).into())); + return Ok((-1i32).into()); } } - self.charge(|schedule| schedule.call_gas as u64)?; + self.adjusted_charge(|schedule| schedule.call_gas as u64)?; let mut result = Vec::with_capacity(result_alloc_len as usize); result.resize(result_alloc_len as usize, 0); @@ -388,7 +375,17 @@ impl<'a, 'b> Runtime<'a, 'b> { // todo: optimize to use memory views once it's in let payload = self.memory.get(input_ptr, input_len as usize)?; - self.charge(|_| gas.into())?; + let adjusted_gas = match gas.checked_mul(self.ext.schedule().wasm.opcodes_div as u64) + .map(|x| x / self.ext.schedule().wasm.opcodes_mul as u64) + { + Some(x) => x, + None => { + trace!("CALL overflowed gas, call aborted with error returned"); + return Ok(RuntimeValue::I32(-1)) + }, + }; + + self.charge(|_| adjusted_gas)?; let call_result = self.ext.call( &gas.into(), @@ -404,449 +401,208 @@ impl<'a, 'b> Runtime<'a, 'b> { match call_result { vm::MessageCallResult::Success(gas_left, _) => { // cannot overflow, before making call gas_counter was incremented with gas, and gas_left < gas - self.gas_counter = self.gas_counter - gas_left.low_u64(); + self.gas_counter = self.gas_counter - + gas_left.low_u64() * self.ext.schedule().wasm.opcodes_div as u64 + / self.ext.schedule().wasm.opcodes_mul as u64; self.memory.set(result_ptr, &result)?; - Ok(Some(0i32.into())) + Ok(0i32.into()) }, vm::MessageCallResult::Reverted(gas_left, _) => { // cannot overflow, before making call gas_counter was incremented with gas, and gas_left < gas - self.gas_counter = self.gas_counter - gas_left.low_u64(); + self.gas_counter = self.gas_counter - + gas_left.low_u64() * self.ext.schedule().wasm.opcodes_div as u64 + / self.ext.schedule().wasm.opcodes_mul as u64; self.memory.set(result_ptr, &result)?; - Ok(Some((-1i32).into())) + Ok((-1i32).into()) }, vm::MessageCallResult::Failed => { - Ok(Some((-1i32).into())) + Ok((-1i32).into()) } } } - pub fn static_call(&mut self, context: InterpreterCallerContext) - -> Result, InterpreterError> - { - // signature (same as code call): - // fn ( - // gas: i64, - // address: *const u8, - // input_ptr: *const u8, - // input_len: u32, - // result_ptr: *mut u8, - // result_len: u32, - // ) -> i32 - - self.do_call(false, CallType::StaticCall, context) - } - - - /// Allocate memory using the wasm stack params - pub fn malloc(&mut self, context: InterpreterCallerContext) - -> Result, InterpreterError> - { - let amount = context.value_stack.pop_as::()? as u32; - - self.charge(|schedule| schedule.wasm.alloc as u64 * amount as u64)?; - - 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: InterpreterCallerContext) - -> Result, InterpreterError> - { - let amount = context.value_stack.pop_as::()? as u64; - if self.charge_gas(amount) { - Ok(None) - } else { - Err(UserTrap::GasLimit.into()) - } - } - - 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(|_| UserTrap::MemoryAccessViolation)? - )) + /// Message call. + fn ccall(&mut self, args: RuntimeArgs) -> Result { + self.do_call(true, CallType::Call, args) } - fn pop_h256(&self, context: &mut InterpreterCallerContext) -> Result { - let ptr = WasmPtr::from_i32(context.value_stack.pop_as::()?) - .map_err(|_| UserTrap::MemoryAccessViolation)?; - self.h256_at(ptr) + /// Delegate call. + fn dcall(&mut self, args: RuntimeArgs) -> Result { + self.do_call(false, CallType::DelegateCall, args) } - fn pop_u256(&self, context: &mut InterpreterCallerContext) -> Result { - let ptr = WasmPtr::from_i32(context.value_stack.pop_as::()?) - .map_err(|_| UserTrap::MemoryAccessViolation)?; - self.h256_at(ptr).map(Into::into) + /// STatic call. + fn scall(&mut self, args: RuntimeArgs) -> Result { + self.do_call(false, CallType::StaticCall, args) } - fn address_at(&self, ptr: WasmPtr) -> Result { - Ok(Address::from_slice(&ptr.slice(20, &*self.memory) - .map_err(|_| UserTrap::MemoryAccessViolation)? - )) - } - - fn pop_address(&self, context: &mut InterpreterCallerContext) -> Result { - let ptr = WasmPtr::from_i32(context.value_stack.pop_as::()?) - .map_err(|_| UserTrap::MemoryAccessViolation)?; - self.address_at(ptr) - } - - fn unknown_trap(&mut self, _context: InterpreterCallerContext) - -> Result, UserTrap> - { - Err(UserTrap::Unknown) - } - - fn user_noop(&mut self, - _context: InterpreterCallerContext - ) -> Result, InterpreterError> { - Ok(None) - } - - /// Write call descriptor to wasm memory - pub fn write_descriptor(&mut self, input: &[u8]) -> Result { - let d_ptr = self.alloc(16)?; - - let args_len = input.len() as u32; - 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, input)?; - - Ok(d_ptr.into()) - } - - fn debug_log(&mut self, context: InterpreterCallerContext) - -> Result, InterpreterError> + fn return_address_ptr(&mut self, ptr: u32, val: Address) -> Result<()> { - 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 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(UserTrap::InvalidGasState); } - Ok(self.gas_limit - self.gas_counter) + self.charge(|schedule| schedule.wasm.static_address as u64)?; + self.memory.set(ptr, &*val)?; + Ok(()) } - /// Shared memory reference - pub fn memory(&self) -> &InterpreterMemoryInstance { - &*self.memory + fn return_u256_ptr(&mut self, ptr: u32, val: U256) -> Result<()> { + let value: H256 = val.into(); + self.charge(|schedule| schedule.wasm.static_u256 as u64)?; + self.memory.set(ptr, &*value)?; + Ok(()) } - fn mem_cmp(&mut self, context: InterpreterCallerContext) - -> Result, InterpreterError> - { - // - // method signature: - // fn memcmp(cx: *const u8, ct: *const u8, n: usize) -> i32; - // - - let len = context.value_stack.pop_as::()? as u32; - let ct = context.value_stack.pop_as::()? as u32; - let cx = context.value_stack.pop_as::()? as u32; - - self.charge(|schedule| schedule.wasm.mem_cmp as u64 * len as u64)?; - - let ct = self.memory.get(ct, len as usize)?; - let cx = self.memory.get(cx, len as usize)?; - - let result = unsafe { - memcmp(cx.as_ptr() as *const c_void, ct.as_ptr() as *const c_void, len as usize) - }; - - Ok(Some(Into::into(result))) + pub fn value(&mut self, args: RuntimeArgs) -> Result<()> { + let val = self.context.value; + self.return_u256_ptr(args.nth(0)?, val) } - fn mem_copy(&mut self, context: InterpreterCallerContext) - -> Result, InterpreterError> + pub fn create(&mut self, args: RuntimeArgs) -> Result { // // method signature: - // fn memcpy(dest: *const u8, src: *const u8, len: u32) -> *mut u8; - // - - let len = context.value_stack.pop_as::()? as u32; - let src = context.value_stack.pop_as::()? as u32; - let dst = context.value_stack.pop_as::()? as u32; - - self.charge(|schedule| schedule.wasm.mem_copy as u64 * len as u64)?; - - self.memory().copy_nonoverlapping(src as usize, dst as usize, len as usize)?; - - Ok(Some(Into::into(dst as i32))) - } - - fn mem_move(&mut self, context: InterpreterCallerContext) - -> Result, InterpreterError> - { - // - // method signature: - // fn memmove(dest: *const u8, src: *const u8, len: u32) -> *mut u8; + // fn create(endowment: *const u8, code_ptr: *const u8, code_len: u32, result_ptr: *mut u8) -> i32; // + trace!(target: "wasm", "runtime: CREATE"); + let endowment = self.u256_at(args.nth(0)?)?; + trace!(target: "wasm", " val: {:?}", endowment); + let code_ptr: u32 = args.nth(1)?; + trace!(target: "wasm", " code_ptr: {:?}", code_ptr); + let code_len: u32 = args.nth(2)?; + trace!(target: "wasm", " code_len: {:?}", code_len); + let result_ptr: u32 = args.nth(3)?; + trace!(target: "wasm", "result_ptr: {:?}", result_ptr); - let len = context.value_stack.pop_as::()? as u32; - let src = context.value_stack.pop_as::()? as u32; - let dst = context.value_stack.pop_as::()? as u32; - - self.charge(|schedule| schedule.wasm.mem_move as u64 * len as u64)?; - - self.memory().copy(src as usize, dst as usize, len as usize)?; - - Ok(Some(Into::into(dst as i32))) - } - - fn mem_set(&mut self, context: InterpreterCallerContext) - -> Result, InterpreterError> - { - // - // method signature: - // fn memset(dest: *const u8, c: u32, len: u32) -> *mut u8; - // + let code = self.memory.get(code_ptr, code_len as usize)?; - let len = context.value_stack.pop_as::()? as u32; - let c = context.value_stack.pop_as::()? as u32; - let dst = context.value_stack.pop_as::()? as u32; + self.adjusted_charge(|schedule| schedule.create_gas as u64)?; + self.adjusted_charge(|schedule| schedule.create_data_gas as u64 * code.len() as u64)?; - self.charge(|schedule| schedule.wasm.mem_set as u64 * len as u64)?; + let gas_left: U256 = U256::from(self.gas_left()?) + * U256::from(self.ext.schedule().wasm.opcodes_mul) + / U256::from(self.ext.schedule().wasm.opcodes_div); - self.memory().clear(dst as usize, c as u8, len as usize)?; + match self.ext.create(&gas_left, &endowment, &code, vm::CreateContractAddress::FromSenderAndCodeHash) { + vm::ContractCreateResult::Created(address, gas_left) => { + self.memory.set(result_ptr, &*address)?; + self.gas_counter = self.gas_limit - + // this cannot overflow, since initial gas is in [0..u64::max) range, + // and gas_left cannot be bigger + gas_left.low_u64() * self.ext.schedule().wasm.opcodes_div as u64 + / self.ext.schedule().wasm.opcodes_mul as u64; + trace!(target: "wasm", "runtime: create contract success (@{:?})", address); + Ok(0i32.into()) + }, + vm::ContractCreateResult::Failed => { + trace!(target: "wasm", "runtime: create contract fail"); + Ok((-1i32).into()) + }, + vm::ContractCreateResult::Reverted(gas_left, _) => { + trace!(target: "wasm", "runtime: create contract reverted"); + self.gas_counter = self.gas_limit - + // this cannot overflow, since initial gas is in [0..u64::max) range, + // and gas_left cannot be bigger + gas_left.low_u64() * self.ext.schedule().wasm.opcodes_div as u64 + / self.ext.schedule().wasm.opcodes_mul as u64; - Ok(Some(Into::into(dst as i32))) + Ok((-1i32).into()) + }, + } } - fn bitswap_i64(&mut self, context: InterpreterCallerContext) - -> Result, InterpreterError> + fn debug(&mut self, args: RuntimeArgs) -> Result<()> { - let x = context.value_stack.pop_as::()?; - let result = x.swap_bytes(); - - Ok(Some(result.into())) - } + let msg_ptr: u32 = args.nth(0)?; + let msg_len: u32 = args.nth(1)?; - fn user_panic(&mut self, context: InterpreterCallerContext) - -> Result, InterpreterError> - { - let payload_len = context.value_stack.pop_as::()? as u32; - let payload_ptr = context.value_stack.pop_as::()? as u32; + let msg = String::from_utf8(self.memory.get(msg_ptr, msg_len as usize)?) + .map_err(|_| Error::BadUtf8)?; - let raw_payload = self.memory.get(payload_ptr, payload_len as usize)?; - let payload = panic_payload::decode(&raw_payload); - let msg = format!( - "{msg}, {file}:{line}:{col}", - msg = payload - .msg - .as_ref() - .map(String::as_ref) - .unwrap_or(""), - file = payload - .file - .as_ref() - .map(String::as_ref) - .unwrap_or(""), - line = payload.line.unwrap_or(0), - col = payload.col.unwrap_or(0) - ); - trace!(target: "wasm", "Contract custom panic message: {}", msg); + trace!(target: "wasm", "Contract debug message: {}", msg); - Err(UserTrap::Panic(msg).into()) + Ok(()) } - fn block_hash(&mut self, context: InterpreterCallerContext) - -> Result, InterpreterError> + /// Pass suicide to state runtime + pub fn suicide(&mut self, args: RuntimeArgs) -> Result<()> { - let return_ptr = context.value_stack.pop_as::()? as u32; - let block_num = context.value_stack.pop_as::()? as u64; - - self.charge(|schedule| schedule.blockhash_gas as u64)?; + let refund_address = self.address_at(args.nth(0)?)?; - trace!("Requesting block hash for block #{}", block_num); - let hash = self.ext.blockhash(&U256::from(block_num)); + if self.ext.exists(&refund_address).map_err(|_| Error::SuicideAbort)? { + trace!(target: "wasm", "Suicide: refund to existing address {}", refund_address); + self.adjusted_charge(|schedule| schedule.suicide_gas as u64)?; + } else { + trace!(target: "wasm", "Suicide: refund to new address {}", refund_address); + self.adjusted_charge(|schedule| schedule.suicide_to_new_account_cost as u64)?; + } - self.memory.set(return_ptr, &*hash)?; + self.ext.suicide(&refund_address).map_err(|_| Error::SuicideAbort)?; - Ok(None) + // We send trap to interpreter so it should abort further execution + Err(Error::Suicide.into()) } - fn return_address_ptr(&mut self, ptr: u32, val: Address) -> Result<(), InterpreterError> - { - self.charge(|schedule| schedule.wasm.static_address as u64)?; - self.memory.set(ptr, &*val)?; - Ok(()) - } + pub fn blockhash(&mut self, args: RuntimeArgs) -> Result<()> { + self.adjusted_charge(|schedule| schedule.blockhash_gas as u64)?; + let hash = self.ext.blockhash(&U256::from(args.nth::(0)?)); + self.memory.set(args.nth(1)?, &*hash)?; - fn return_u256_ptr(&mut self, ptr: u32, val: U256) -> Result<(), InterpreterError> { - let value: H256 = val.into(); - self.charge(|schedule| schedule.wasm.static_u256 as u64)?; - self.memory.set(ptr, &*value)?; Ok(()) } - fn coinbase(&mut self, context: InterpreterCallerContext) - -> Result, InterpreterError> - { - let author = self.ext.env_info().author; - self.return_address_ptr( - context.value_stack.pop_as::()? as u32, - author, - )?; - Ok(None) - } - - fn sender(&mut self, context: InterpreterCallerContext) - -> Result, InterpreterError> - { - let sender = self.context.sender; - self.return_address_ptr( - context.value_stack.pop_as::()? as u32, - sender, - )?; - Ok(None) - } - - fn address(&mut self, context: InterpreterCallerContext) - -> Result, InterpreterError> - { - let addr = self.context.address; - self.return_address_ptr( - context.value_stack.pop_as::()? as u32, - addr, - )?; - Ok(None) - } - - fn origin(&mut self, context: InterpreterCallerContext) - -> Result, InterpreterError> - { - let origin = self.context.origin; - self.return_address_ptr( - context.value_stack.pop_as::()? as u32, - origin, - )?; - Ok(None) - } - - fn value(&mut self, context: InterpreterCallerContext) - -> Result, InterpreterError> - { - let value = self.context.value; - self.return_u256_ptr( - context.value_stack.pop_as::()? as u32, - value, - )?; - Ok(None) - } - - fn timestamp(&mut self, _context: InterpreterCallerContext) - -> Result, InterpreterError> - { - let timestamp = self.ext.env_info().timestamp as i64; - Ok(Some(timestamp.into())) + pub fn blocknumber(&mut self) -> Result { + Ok(RuntimeValue::from(self.ext.env_info().number)) } - fn block_number(&mut self, _context: InterpreterCallerContext) - -> Result, InterpreterError> - { - let block_number = self.ext.env_info().number as i64; - Ok(Some(block_number.into())) + pub fn coinbase(&mut self, args: RuntimeArgs) -> Result<()> { + let coinbase = self.ext.env_info().author; + self.return_address_ptr(args.nth(0)?, coinbase) } - fn difficulty(&mut self, context: InterpreterCallerContext) - -> Result, InterpreterError> - { + pub fn difficulty(&mut self, args: RuntimeArgs) -> Result<()> { let difficulty = self.ext.env_info().difficulty; - self.return_u256_ptr( - context.value_stack.pop_as::()? as u32, - difficulty, - )?; - Ok(None) + self.return_u256_ptr(args.nth(0)?, difficulty) } - fn ext_gas_limit(&mut self, context: InterpreterCallerContext) - -> Result, InterpreterError> - { + pub fn gaslimit(&mut self, args: RuntimeArgs) -> Result<()> { let gas_limit = self.ext.env_info().gas_limit; - self.return_u256_ptr( - context.value_stack.pop_as::()? as u32, - gas_limit, - )?; - Ok(None) + self.return_u256_ptr(args.nth(0)?, gas_limit) } - pub fn execution_params(&mut self) -> interpreter::ExecutionParams { - use super::env; - - let env_instance = self.instance.module("env") - .expect("Env module always exists; qed"); + pub fn address(&mut self, args: RuntimeArgs) -> Result<()> { + let address = self.context.address; + self.return_address_ptr(args.nth(0)?, address) + } - interpreter::ExecutionParams::with_external( - "env".into(), - Arc::new( - interpreter::env_native_module(env_instance, env::native_bindings(self)) - .expect("Env module always exists; qed") - ) - ) + pub fn sender(&mut self, args: RuntimeArgs) -> Result<()> { + let sender = self.context.sender; + self.return_address_ptr(args.nth(0)?, sender) } - pub fn gas_rules(&self) -> &rules::Set { - &self.gas_rules + pub fn origin(&mut self, args: RuntimeArgs) -> Result<()> { + let origin = self.context.origin; + self.return_address_ptr(args.nth(0)?, origin) } - pub fn ext(&mut self) -> &mut vm::Ext { - self.ext + pub fn timestamp(&mut self) -> Result { + let timestamp = self.ext.env_info().timestamp; + Ok(RuntimeValue::from(timestamp)) } - pub fn log(&mut self, context: InterpreterCallerContext) - -> Result, InterpreterError> + pub fn elog(&mut self, args: RuntimeArgs) -> Result<()> { // signature is: // pub fn elog(topic_ptr: *const u8, topic_count: u32, data_ptr: *const u8, data_len: u32); - let data_len = context.value_stack.pop_as::()? as u32; - let data_ptr = context.value_stack.pop_as::()? as u32; - let topic_count = context.value_stack.pop_as::()? as u32; - let topic_ptr = context.value_stack.pop_as::()? as u32; + let topic_ptr: u32 = args.nth(0)?; + let topic_count: u32 = args.nth(1)?; + let data_ptr: u32 = args.nth(2)?; + let data_len: u32 = args.nth(3)?; if topic_count > 4 { - return Err(UserTrap::Log.into()); + return Err(Error::Log.into()); } - self.overflow_charge(|schedule| + self.adjusted_overflow_charge(|schedule| { let topics_gas = schedule.log_gas as u64 + schedule.log_topic_gas as u64 * topic_count as u64; (schedule.log_data_gas as u64) @@ -858,120 +614,69 @@ impl<'a, 'b> Runtime<'a, 'b> { let mut topics: Vec = Vec::with_capacity(topic_count as usize); topics.resize(topic_count as usize, H256::zero()); for i in 0..topic_count { - let offset = i.checked_mul(32).ok_or(UserTrap::MemoryAccessViolation)? - .checked_add(topic_ptr).ok_or(UserTrap::MemoryAccessViolation)?; + let offset = i.checked_mul(32).ok_or(Error::MemoryAccessViolation)? + .checked_add(topic_ptr).ok_or(Error::MemoryAccessViolation)?; *topics.get_mut(i as usize) .expect("topics is resized to `topic_count`, i is in 0..topic count iterator, get_mut uses i as an indexer, get_mut cannot fail; qed") = H256::from(&self.memory.get(offset, 32)?[..]); } - self.ext.log(topics, &self.memory.get(data_ptr, data_len as usize)?).map_err(|_| UserTrap::Log)?; + self.ext.log(topics, &self.memory.get(data_ptr, data_len as usize)?).map_err(|_| Error::Log)?; - Ok(None) + Ok(()) } } -impl<'a, 'b> interpreter::UserFunctionExecutor for Runtime<'a, 'b> { - fn execute(&mut self, name: &str, context: InterpreterCallerContext) - -> Result, InterpreterError> - { - match name { - "_ext_malloc" => { - self.malloc(context) - }, - "_ext_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) - }, - "_balance" => { - self.balance(context) - }, - "_suicide" => { - self.suicide(context) - }, - "_create" => { - self.create(context) - }, - "_ccall" => { - self.call(context) - }, - "_dcall" => { - self.call_code(context) - }, - "_scall" => { - self.static_call(context) - }, - "_debug" => { - self.debug_log(context) - }, - "gas" => { - self.gas(context) - }, - "_emscripten_memcpy_big" => { - self.mem_copy(context) - }, - "_ext_memcmp" => { - self.mem_cmp(context) - }, - "_ext_memcpy" => { - self.mem_copy(context) - }, - "_ext_memmove" => { - self.mem_move(context) - }, - "_ext_memset" => { - self.mem_set(context) - }, - "_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) - }, - "_sender" => { - self.sender(context) - }, - "_address" => { - self.address(context) - }, - "_origin" => { - self.origin(context) - }, - "_value" => { - self.value(context) - }, - "_elog" => { - self.log(context) - }, - _ => { - trace!(target: "wasm", "Trapped due to unhandled function: '{}'", name); - Ok(self.unknown_trap(context)?) - }, +mod ext_impl { + + use wasmi::{Externals, RuntimeArgs, RuntimeValue, Error}; + use env::ids::*; + + macro_rules! void { + { $e: expr } => { { $e?; Ok(None) } } + } + + macro_rules! some { + { $e: expr } => { { Ok(Some($e?)) } } + } + + macro_rules! cast { + { $e: expr } => { { Ok(Some($e)) } } + } + + impl<'a> Externals for super::Runtime<'a> { + fn invoke_index( + &mut self, + index: usize, + args: RuntimeArgs, + ) -> Result, Error> { + match index { + STORAGE_WRITE_FUNC => void!(self.storage_write(args)), + STORAGE_READ_FUNC => void!(self.storage_read(args)), + RET_FUNC => void!(self.ret(args)), + GAS_FUNC => void!(self.gas(args)), + INPUT_LENGTH_FUNC => cast!(self.input_legnth()), + FETCH_INPUT_FUNC => void!(self.fetch_input(args)), + PANIC_FUNC => void!(self.panic(args)), + DEBUG_FUNC => void!(self.debug(args)), + CCALL_FUNC => some!(self.ccall(args)), + DCALL_FUNC => some!(self.dcall(args)), + SCALL_FUNC => some!(self.scall(args)), + VALUE_FUNC => void!(self.value(args)), + CREATE_FUNC => some!(self.create(args)), + SUICIDE_FUNC => void!(self.suicide(args)), + BLOCKHASH_FUNC => void!(self.blockhash(args)), + BLOCKNUMBER_FUNC => some!(self.blocknumber()), + COINBASE_FUNC => void!(self.coinbase(args)), + DIFFICULTY_FUNC => void!(self.difficulty(args)), + GASLIMIT_FUNC => void!(self.gaslimit(args)), + TIMESTAMP_FUNC => some!(self.timestamp()), + ADDRESS_FUNC => void!(self.address(args)), + SENDER_FUNC => void!(self.sender(args)), + ORIGIN_FUNC => void!(self.origin(args)), + ELOG_FUNC => void!(self.elog(args)), + _ => panic!("env module doesn't provide function at index {}", index), + } } } } diff --git a/ethcore/wasm/src/tests.rs b/ethcore/wasm/src/tests.rs index a4973598433..96f338460ea 100644 --- a/ethcore/wasm/src/tests.rs +++ b/ethcore/wasm/src/tests.rs @@ -68,7 +68,7 @@ fn test_finalize(res: Result) -> Result { } fn wasm_interpreter() -> WasmInterpreter { - WasmInterpreter::new().expect("wasm interpreter to create without errors") + WasmInterpreter } /// Empty contract does almost nothing except producing 1 (one) local node debug log message @@ -88,7 +88,7 @@ fn empty() { test_finalize(interpreter.exec(params, &mut ext)).unwrap() }; - assert_eq!(gas_left, U256::from(96_678)); + assert_eq!(gas_left, U256::from(98462)); } // This test checks if the contract deserializes payload header properly. @@ -140,7 +140,7 @@ fn logger() { U256::from(1_000_000_000), "Logger sets 0x04 key to the trasferred value" ); - assert_eq!(gas_left, U256::from(15_860)); + assert_eq!(gas_left, U256::from(17_578)); } // This test checks if the contract can allocate memory and pass pointer to the result stream properly. @@ -175,7 +175,7 @@ fn identity() { sender, "Idenity test contract does not return the sender passed" ); - assert_eq!(gas_left, U256::from(96_540)); + assert_eq!(gas_left, U256::from(98_408)); } // Dispersion test sends byte array and expect the contract to 'disperse' the original elements with @@ -184,6 +184,8 @@ fn identity() { // This also tests byte-perfect memory allocation and in/out ptr lifecycle. #[test] fn dispersion() { + ::ethcore_logger::init_log(); + let code = load_sample!("dispersion.wasm"); let mut params = ActionParams::default(); @@ -203,12 +205,11 @@ fn dispersion() { } }; - assert_eq!( result, vec![0u8, 0, 125, 11, 197, 7, 255, 8, 19, 0] ); - assert_eq!(gas_left, U256::from(96_116)); + assert_eq!(gas_left, U256::from(93_972)); } #[test] @@ -236,7 +237,7 @@ fn suicide_not() { result, vec![0u8] ); - assert_eq!(gas_left, U256::from(96_461)); + assert_eq!(gas_left, U256::from(94_970)); } #[test] @@ -268,7 +269,7 @@ fn suicide() { }; assert!(ext.suicides.contains(&refund)); - assert_eq!(gas_left, U256::from(96_429)); + assert_eq!(gas_left, U256::from(94_933)); } #[test] @@ -298,7 +299,7 @@ fn create() { assert!(ext.calls.contains( &FakeCall { call_type: FakeCallType::Create, - gas: U256::from(62_545), + gas: U256::from(60_917), sender_address: None, receive_address: None, value: Some(1_000_000_000.into()), @@ -306,7 +307,7 @@ fn create() { code_address: None, } )); - assert_eq!(gas_left, U256::from(62_538)); + assert_eq!(gas_left, U256::from(60_903)); } #[test] @@ -350,7 +351,7 @@ fn call_msg() { } )); - assert_eq!(gas_left, U256::from(95_699)); + assert_eq!(gas_left, U256::from(93_511)); } #[test] @@ -395,7 +396,7 @@ fn call_code() { // siphash result let res = LittleEndian::read_u32(&result[..]); assert_eq!(res, 4198595614); - assert_eq!(gas_left, U256::from(90_550)); + assert_eq!(gas_left, U256::from(92_381)); } #[test] @@ -443,7 +444,7 @@ fn call_static() { let res = LittleEndian::read_u32(&result[..]); assert_eq!(res, 317632590); - assert_eq!(gas_left, U256::from(90_550)); + assert_eq!(gas_left, U256::from(92_381)); } // Realloc test @@ -466,178 +467,83 @@ fn realloc() { } }; assert_eq!(result, vec![0u8; 2]); - assert_eq!(gas_left, U256::from(96_445)); + assert_eq!(gas_left, U256::from(94_352)); } -// Tests that contract's ability to read from a storage -// Test prepopulates address into storage, than executes a contract which read that address from storage and write this address into result #[test] -fn storage_read() { - let code = load_sample!("storage_read.wasm"); - let address: Address = "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6".parse().unwrap(); +fn alloc() { + let code = load_sample!("alloc.wasm"); let mut params = ActionParams::default(); - params.gas = U256::from(100_000); + params.gas = U256::from(10_000_000); params.code = Some(Arc::new(code)); - let mut ext = FakeExt::new(); - ext.store.insert("0100000000000000000000000000000000000000000000000000000000000000".into(), address.into()); - - 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!("storage_read should return payload"); }, - GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()), - } - }; - - assert_eq!(Address::from(&result[12..32]), address); - assert_eq!(gas_left, U256::from(96_463)); -} - -// Tests keccak calculation -// keccak.wasm runs wasm-std::keccak function on data param and returns hash -#[test] -fn keccak() { - ::ethcore_logger::init_log(); - let code = load_sample!("keccak.wasm"); - - let mut params = ActionParams::default(); - params.gas = U256::from(100_000); - params.code = Some(Arc::new(code)); - params.data = Some(b"something".to_vec()); + 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!("keccak should return payload"); }, + GasLeft::Known(_) => { panic!("alloc test should return payload"); }, GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()), } }; - - assert_eq!(H256::from_slice(&result), H256::from("68371d7e884c168ae2022c82bd837d51837718a7f7dfb7aa3f753074a35e1d87")); - assert_eq!(gas_left, U256::from(81_067)); + assert_eq!(result, vec![5u8; 1024*450]); + assert_eq!(gas_left, U256::from(6_506_844)); } -// memcmp test. -#[test] -fn memcmp() { - ::ethcore_logger::init_log(); - - let (gas_left, result) = reqrep_test! { - "memcmp.wasm", - vec![1u8, 1, 1] - }.expect("Interpreter to execute without any errors"); - - assert_eq!(0i32, LittleEndian::read_i32(&result)); - assert_eq!(gas_left, U256::from(96610)); - - let (gas_left, result) = reqrep_test! { - "memcmp.wasm", - vec![1u8, 1, 3, 1] - }.expect("Interpreter to execute without any errors"); - - assert_eq!(2i32, LittleEndian::read_i32(&result)); - assert_eq!(gas_left, U256::from(96610)); - - let (gas_left, result) = reqrep_test! { - "memcmp.wasm", - vec![1u8, 1, 0] - }.expect("Interpreter to execute without any errors"); - - assert_eq!(-1i32, LittleEndian::read_i32(&result)); - assert_eq!(gas_left, U256::from(96610)); -} - -// memcpy test. -#[test] -fn memcpy() { - ::ethcore_logger::init_log(); - let code = load_sample!("mem.wasm"); - - let mut test_payload = Vec::with_capacity(8192); - for i in 0..8192 { - test_payload.push((i % 255) as u8); - } - let mut data = vec![0u8]; - data.extend(&test_payload); - - let mut params = ActionParams::default(); - params.gas = U256::from(100_000); - params.code = Some(Arc::new(code)); - params.data = Some(data); - 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!("mem should return payload"); }, - GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()), - } - }; - - assert_eq!(result, test_payload); - assert_eq!(gas_left, U256::from(71_940)); -} - -// memmove test. +// Tests that contract's ability to read from a storage +// Test prepopulates address into storage, than executes a contract which read that address from storage and write this address into result #[test] -fn memmove() { +fn storage_read() { ::ethcore_logger::init_log(); - let code = load_sample!("mem.wasm"); - let mut test_payload = Vec::with_capacity(8192); - for i in 0..8192 { - test_payload.push((i % 255) as u8); - } - let mut data = vec![1u8]; - data.extend(&test_payload); + let code = load_sample!("storage_read.wasm"); + let address: Address = "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6".parse().unwrap(); let mut params = ActionParams::default(); params.gas = U256::from(100_000); params.code = Some(Arc::new(code)); - params.data = Some(data); let mut ext = FakeExt::new(); + ext.store.insert("0100000000000000000000000000000000000000000000000000000000000000".into(), address.into()); 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!("mem should return payload"); }, - GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()), + GasLeft::Known(_) => { panic!("storage_read should return payload"); }, + GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()), } }; - assert_eq!(result, test_payload); - assert_eq!(gas_left, U256::from(71_940)); + assert_eq!(Address::from(&result[12..32]), address); + assert_eq!(gas_left, U256::from(98_298)); } -// memset test +// Tests keccak calculation +// keccak.wasm runs wasm-std::keccak function on data param and returns hash #[test] -fn memset() { +fn keccak() { ::ethcore_logger::init_log(); - let code = load_sample!("mem.wasm"); + let code = load_sample!("keccak.wasm"); let mut params = ActionParams::default(); params.gas = U256::from(100_000); params.code = Some(Arc::new(code)); - params.data = Some(vec![2u8, 228u8]); + params.data = Some(b"something".to_vec()); 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!("mem should return payload"); }, - GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()), + GasLeft::Known(_) => { panic!("keccak should return payload"); }, + GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()), } }; - assert_eq!(result, vec![228u8; 8192]); - assert_eq!(gas_left, U256::from(71_921)); + assert_eq!(H256::from_slice(&result), H256::from("68371d7e884c168ae2022c82bd837d51837718a7f7dfb7aa3f753074a35e1d87")); + assert_eq!(gas_left, U256::from(84_223)); } // math_* tests check the ability of wasm contract to perform big integer operations @@ -666,7 +572,7 @@ fn math_add() { U256::from_dec_str("1888888888888888888888888888887").unwrap(), (&result[..]).into() ); - assert_eq!(gas_left, U256::from(95_384)); + assert_eq!(gas_left, U256::from(93_818)); } // multiplication @@ -688,7 +594,7 @@ fn math_mul() { U256::from_dec_str("888888888888888888888888888887111111111111111111111111111112").unwrap(), (&result[..]).into() ); - assert_eq!(gas_left, U256::from(94_374)); + assert_eq!(gas_left, U256::from(93_304)); } // subtraction @@ -710,7 +616,7 @@ fn math_sub() { U256::from_dec_str("111111111111111111111111111111").unwrap(), (&result[..]).into() ); - assert_eq!(gas_left, U256::from(95_372)); + assert_eq!(gas_left, U256::from(93_831)); } // subtraction with overflow @@ -752,7 +658,7 @@ fn math_div() { U256::from_dec_str("1125000").unwrap(), (&result[..]).into() ); - assert_eq!(gas_left, U256::from(88_356)); + assert_eq!(gas_left, U256::from(90_607)); } // This test checks the ability of wasm contract to invoke @@ -840,7 +746,7 @@ fn externs() { "Gas limit requested and returned does not match" ); - assert_eq!(gas_left, U256::from(95_321)); + assert_eq!(gas_left, U256::from(92_089)); } #[test] @@ -866,7 +772,7 @@ fn embedded_keccak() { }; assert_eq!(H256::from_slice(&result), H256::from("68371d7e884c168ae2022c82bd837d51837718a7f7dfb7aa3f753074a35e1d87")); - assert_eq!(gas_left, U256::from(81_067)); + assert_eq!(gas_left, U256::from(84_223)); } /// This test checks the correctness of log extern @@ -901,5 +807,5 @@ fn events() { assert_eq!(&log_entry.data, b"gnihtemos"); assert_eq!(&result, b"gnihtemos"); - assert_eq!(gas_left, U256::from(79_206)); + assert_eq!(gas_left, U256::from(81_235)); }