Decouple virtual machines (#6184)

* work in progress for splitting vms

* evm working

* Evm -> Vm

* wasm converted

* ethcore working

* test fixes
This commit is contained in:
Nikolay Volf
2017-08-01 13:37:57 +03:00
committed by GitHub
parent c4025622de
commit b7006034b1
65 changed files with 534 additions and 400 deletions

View File

@@ -0,0 +1,62 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Wasm evm call arguments helper
use util::{U256, H160};
/// Input part of the wasm call descriptor
pub struct CallArgs {
/// Receiver of the transaction
pub address: [u8; 20],
/// Sender of the transaction
pub sender: [u8; 20],
/// Original transaction initiator
pub origin: [u8; 20],
/// Transfer value
pub value: [u8; 32],
/// call/create params
pub data: Vec<u8>,
}
impl CallArgs {
/// New contract call payload with known parameters
pub fn new(address: H160, sender: H160, origin: H160, value: U256, data: Vec<u8>) -> Self {
let mut descriptor = CallArgs {
address: [0u8; 20],
sender: [0u8; 20],
origin: [0u8; 20],
value: [0u8; 32],
data: data,
};
descriptor.address.copy_from_slice(&*address);
descriptor.sender.copy_from_slice(&*sender);
descriptor.origin.copy_from_slice(&*origin);
value.to_big_endian(&mut descriptor.value);
descriptor
}
/// Total call payload length in linear memory
pub fn len(&self) -> u32 {
self.data.len() as u32 + 92
}
}

154
ethcore/wasm/src/env.rs Normal file
View File

@@ -0,0 +1,154 @@
// 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 <http://www.gnu.org/licenses/>.
//! Wasm env module bindings
use parity_wasm::elements::ValueType::*;
use parity_wasm::interpreter::UserFunctionDescriptor;
use parity_wasm::interpreter::UserFunctionDescriptor::*;
pub const SIGNATURES: &'static [UserFunctionDescriptor] = &[
Static(
"_storage_read",
&[I32; 2],
Some(I32),
),
Static(
"_storage_write",
&[I32; 2],
Some(I32),
),
Static(
"_malloc",
&[I32],
Some(I32),
),
Static(
"_free",
&[I32],
None,
),
Static(
"gas",
&[I32],
None,
),
Static(
"_debug",
&[I32; 2],
None,
),
Static(
"_suicide",
&[I32],
None,
),
Static(
"_create",
&[I32; 4],
Some(I32),
),
Static(
"abort",
&[I32],
None,
),
Static(
"_abort",
&[],
None,
),
Static(
"invoke_vii",
&[I32; 3],
None,
),
Static(
"invoke_vi",
&[I32; 2],
None,
),
Static(
"invoke_v",
&[I32],
None,
),
Static(
"invoke_iii",
&[I32; 3],
Some(I32),
),
Static(
"___resumeException",
&[I32],
None,
),
Static(
"_rust_begin_unwind",
&[I32; 4],
None,
),
Static(
"___cxa_find_matching_catch_2",
&[],
Some(I32),
),
Static(
"___gxx_personality_v0",
&[I32; 6],
Some(I32),
),
Static(
"_emscripten_memcpy_big",
&[I32; 3],
Some(I32),
),
Static(
"___syscall140",
&[I32; 2],
Some(I32)
),
Static(
"___syscall146",
&[I32; 2],
Some(I32)
),
Static(
"___syscall54",
&[I32; 2],
Some(I32)
),
Static(
"___syscall6",
&[I32; 2],
Some(I32)
),
Static(
"_llvm_trap",
&[I32; 0],
None
),
Static(
"abortOnCannotGrowMemory",
&[I32; 0],
Some(I32)
),
Static(
"___setErrNo",
&[I32; 1],
None
),
];

170
ethcore/wasm/src/lib.rs Normal file
View File

@@ -0,0 +1,170 @@
// 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 <http://www.gnu.org/licenses/>.
//! Wasm Interpreter
extern crate vm;
extern crate ethcore_util as util;
#[macro_use] extern crate log;
extern crate byteorder;
extern crate parity_wasm;
extern crate wasm_utils;
mod runtime;
mod ptr;
mod call_args;
mod result;
#[cfg(test)]
mod tests;
mod env;
use std::sync::Arc;
const DEFAULT_STACK_SPACE: u32 = 5 * 1024 * 1024;
use parity_wasm::{interpreter, elements};
use parity_wasm::interpreter::ModuleInstanceInterface;
use vm::{GasLeft, ReturnData, ActionParams};
use self::runtime::Runtime;
pub use self::runtime::Error as RuntimeError;
const DEFAULT_RESULT_BUFFER: usize = 1024;
/// Wasm interpreter instance
pub struct WasmInterpreter {
program: interpreter::ProgramInstance,
result: Vec<u8>,
}
impl WasmInterpreter {
/// New wasm interpreter instance
pub fn new() -> Result<WasmInterpreter, RuntimeError> {
Ok(WasmInterpreter {
program: interpreter::ProgramInstance::new()?,
result: Vec::with_capacity(DEFAULT_RESULT_BUFFER),
})
}
}
impl vm::Vm for WasmInterpreter {
fn exec(&mut self, params: ActionParams, ext: &mut vm::Ext) -> vm::Result<GasLeft> {
use parity_wasm::elements::Deserialize;
let code = params.code.expect("exec is only called on contract with code; qed");
trace!(target: "wasm", "Started wasm interpreter with code.len={:?}", code.len());
let env_instance = self.program.module("env")
// prefer explicit panic here
.expect("Wasm program to contain env module");
let env_memory = env_instance.memory(interpreter::ItemIndex::Internal(0))
// prefer explicit panic here
.expect("Linear memory to exist in wasm runtime");
if params.gas > ::std::u64::MAX.into() {
return Err(vm::Error::Wasm("Wasm interpreter cannot run contracts with gas >= 2^64".to_owned()));
}
let mut runtime = Runtime::with_params(
ext,
env_memory,
DEFAULT_STACK_SPACE,
params.gas.low_u64(),
);
let mut cursor = ::std::io::Cursor::new(&*code);
let contract_module = wasm_utils::inject_gas_counter(
elements::Module::deserialize(
&mut cursor
).map_err(|err| {
vm::Error::Wasm(format!("Error deserializing contract code ({:?})", err))
})?
);
let d_ptr = runtime.write_descriptor(
call_args::CallArgs::new(
params.address,
params.sender,
params.origin,
params.value.value(),
params.data.unwrap_or(Vec::with_capacity(0)),
)
)?;
{
let execution_params = interpreter::ExecutionParams::with_external(
"env".into(),
Arc::new(
interpreter::env_native_module(env_instance, native_bindings(&mut runtime))
.map_err(|err| {
// todo: prefer explicit panic here also?
vm::Error::Wasm(format!("Error instantiating native bindings: {:?}", err))
})?
)
).add_argument(interpreter::RuntimeValue::I32(d_ptr.as_raw() as i32));
let module_instance = self.program.add_module("contract", contract_module, Some(&execution_params.externals))
.map_err(|err| {
trace!(target: "wasm", "Error adding contract module: {:?}", err);
vm::Error::from(RuntimeError::Interpreter(err))
})?;
module_instance.execute_export("_call", execution_params)
.map_err(|err| {
trace!(target: "wasm", "Error executing contract: {:?}", err);
vm::Error::from(RuntimeError::Interpreter(err))
})?;
}
let result = result::WasmResult::new(d_ptr);
if result.peek_empty(&*runtime.memory())? {
trace!(target: "wasm", "Contract execution result is empty.");
Ok(GasLeft::Known(runtime.gas_left()?.into()))
} else {
self.result.clear();
// todo: use memory views to avoid copy
self.result.extend(result.pop(&*runtime.memory())?);
let len = self.result.len();
Ok(GasLeft::NeedsReturn {
gas_left: runtime.gas_left()?.into(),
data: ReturnData::new(
::std::mem::replace(&mut self.result, Vec::with_capacity(DEFAULT_RESULT_BUFFER)),
0,
len,
),
apply_state: true,
})
}
}
}
fn native_bindings<'a>(runtime: &'a mut Runtime) -> interpreter::UserFunctions<'a> {
interpreter::UserFunctions {
executor: runtime,
functions: ::std::borrow::Cow::from(env::SIGNATURES),
}
}
impl From<runtime::Error> for vm::Error {
fn from(err: runtime::Error) -> vm::Error {
vm::Error::Wasm(format!("WASM runtime-error: {:?}", err))
}
}

52
ethcore/wasm/src/ptr.rs Normal file
View File

@@ -0,0 +1,52 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Wasm bound-checked ptr
use parity_wasm::interpreter;
/// Bound-checked wrapper for webassembly memory
pub struct WasmPtr(u32);
/// Error in bound check
#[derive(Debug)]
pub enum Error {
AccessViolation,
}
impl From<u32> for WasmPtr {
fn from(raw: u32) -> Self {
WasmPtr(raw)
}
}
impl WasmPtr {
// todo: use memory view when they are on
/// Check memory range and return data with given length starting from the current pointer value
pub fn slice(&self, len: u32, mem: &interpreter::MemoryInstance) -> Result<Vec<u8>, 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<Self, Error> {
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 }
}

View File

@@ -0,0 +1,51 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Wasm evm results helper
use byteorder::{LittleEndian, ByteOrder};
use parity_wasm::interpreter;
use super::ptr::WasmPtr;
use super::runtime::Error as RuntimeError;
/// Wrapper for wasm contract call result
pub struct WasmResult {
ptr: WasmPtr,
}
impl WasmResult {
/// New call result from given ptr
pub fn new(descriptor_ptr: WasmPtr) -> WasmResult {
WasmResult { ptr: descriptor_ptr }
}
/// Check if the result contains any data
pub fn peek_empty(&self, mem: &interpreter::MemoryInstance) -> Result<bool, RuntimeError> {
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<Vec<u8>, RuntimeError> {
let result_ptr = LittleEndian::read_u32(&self.ptr.slice(16, mem)?[8..12]);
let result_len = LittleEndian::read_u32(&self.ptr.slice(16, mem)?[12..16]);
trace!(target: "wasm", "contract result: {} bytes at @{}", result_len, result_ptr);
Ok(mem.get(result_ptr, result_len as usize)?)
}
}

355
ethcore/wasm/src/runtime.rs Normal file
View File

@@ -0,0 +1,355 @@
// 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 <http://www.gnu.org/licenses/>.
//! Wasm evm program runtime intstance
use std::sync::Arc;
use byteorder::{LittleEndian, ByteOrder};
use vm;
use parity_wasm::interpreter;
use util::{Address, H256, U256};
use super::ptr::{WasmPtr, Error as PtrError};
use super::call_args::CallArgs;
/// Wasm runtime error
#[derive(Debug)]
pub enum Error {
/// Storage error
Storage,
/// Allocator error
Allocator,
/// Invalid gas state during the call
InvalidGasState,
/// Memory access violation
AccessViolation,
/// Interpreter runtime error
Interpreter(interpreter::Error),
}
impl From<interpreter::Error> for Error {
fn from(err: interpreter::Error) -> Self {
Error::Interpreter(err)
}
}
impl From<PtrError> for Error {
fn from(err: PtrError) -> Self {
match err {
PtrError::AccessViolation => Error::AccessViolation,
}
}
}
/// Runtime enviroment data for wasm contract execution
pub struct Runtime<'a> {
gas_counter: u64,
gas_limit: u64,
dynamic_top: u32,
ext: &'a mut vm::Ext,
memory: Arc<interpreter::MemoryInstance>,
}
impl<'a> Runtime<'a> {
/// New runtime for wasm contract with specified params
pub fn with_params<'b>(
ext: &'b mut vm::Ext,
memory: Arc<interpreter::MemoryInstance>,
stack_space: u32,
gas_limit: u64,
) -> Runtime<'b> {
Runtime {
gas_counter: 0,
gas_limit: gas_limit,
dynamic_top: stack_space,
memory: memory,
ext: ext,
}
}
/// Write to the storage from wasm memory
pub fn storage_write(&mut self, context: interpreter::CallerContext)
-> Result<Option<interpreter::RuntimeValue>, interpreter::Error>
{
let mut context = context;
let val = self.pop_h256(&mut context)?;
let key = self.pop_h256(&mut context)?;
trace!(target: "wasm", "storage_write: value {} at @{}", &val, &key);
self.ext.set_storage(key, val)
.map_err(|_| interpreter::Error::Trap("Storage update error".to_owned()))?;
Ok(Some(0i32.into()))
}
/// Read from the storage to wasm memory
pub fn storage_read(&mut self, context: interpreter::CallerContext)
-> Result<Option<interpreter::RuntimeValue>, interpreter::Error>
{
let mut context = context;
let val_ptr = context.value_stack.pop_as::<i32>()?;
let key = self.pop_h256(&mut context)?;
let val = self.ext.storage_at(&key)
.map_err(|_| interpreter::Error::Trap("Storage read error".to_owned()))?;
self.memory.set(val_ptr as u32, &*val)?;
Ok(Some(0.into()))
}
/// Pass suicide to state runtime
pub fn suicide(&mut self, context: interpreter::CallerContext)
-> Result<Option<interpreter::RuntimeValue>, interpreter::Error>
{
let mut context = context;
let refund_address = self.pop_address(&mut context)?;
self.ext.suicide(&refund_address)
.map_err(|_| interpreter::Error::Trap("Suicide error".to_owned()))?;
Ok(None)
}
/// Invoke create in the state runtime
pub fn create(&mut self, context: interpreter::CallerContext)
-> Result<Option<interpreter::RuntimeValue>, interpreter::Error>
{
//
// method signature:
// fn create(endowment: *const u8, code_ptr: *const u8, code_len: u32, result_ptr: *mut u8) -> i32;
//
trace!(target: "wasm", "runtime: create contract");
let mut context = context;
let result_ptr = context.value_stack.pop_as::<i32>()? as u32;
trace!(target: "wasm", " result_ptr: {:?}", result_ptr);
let code_len = context.value_stack.pop_as::<i32>()? as u32;
trace!(target: "wasm", " code_len: {:?}", code_len);
let code_ptr = context.value_stack.pop_as::<i32>()? as u32;
trace!(target: "wasm", " code_ptr: {:?}", code_ptr);
let endowment = self.pop_u256(&mut context)?;
trace!(target: "wasm", " val: {:?}", endowment);
let code = self.memory.get(code_ptr, code_len as usize)?;
let gas_left = self.gas_left()
.map_err(|_| interpreter::Error::Trap("Gas state error".to_owned()))?
.into();
match self.ext.create(&gas_left, &endowment, &code, 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()))
}
}
}
/// Allocate memory using the wasm stack params
pub fn malloc(&mut self, context: interpreter::CallerContext)
-> Result<Option<interpreter::RuntimeValue>, interpreter::Error>
{
let amount = context.value_stack.pop_as::<i32>()? as u32;
let previous_top = self.dynamic_top;
self.dynamic_top = previous_top + amount;
Ok(Some((previous_top as i32).into()))
}
/// Allocate memory in wasm memory instance
pub fn alloc(&mut self, amount: u32) -> Result<u32, Error> {
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<Option<interpreter::RuntimeValue>, interpreter::Error>
{
let amount = context.value_stack.pop_as::<i32>()? as u64;
if self.charge_gas(amount) {
Ok(None)
} else {
Err(interpreter::Error::Trap(format!("Gas exceeds limits of {}", self.gas_limit)))
}
}
fn charge_gas(&mut self, amount: u64) -> bool {
let prev = self.gas_counter;
if prev + amount > self.gas_limit {
// exceeds gas
false
} else {
self.gas_counter = prev + amount;
true
}
}
fn h256_at(&self, ptr: WasmPtr) -> Result<H256, interpreter::Error> {
Ok(H256::from_slice(&ptr.slice(32, &*self.memory)
.map_err(|_| interpreter::Error::Trap("Memory access violation".to_owned()))?
))
}
fn pop_h256(&self, context: &mut interpreter::CallerContext) -> Result<H256, interpreter::Error> {
let ptr = WasmPtr::from_i32(context.value_stack.pop_as::<i32>()?)
.map_err(|_| interpreter::Error::Trap("Memory access violation".to_owned()))?;
self.h256_at(ptr)
}
fn pop_u256(&self, context: &mut interpreter::CallerContext) -> Result<U256, interpreter::Error> {
let ptr = WasmPtr::from_i32(context.value_stack.pop_as::<i32>()?)
.map_err(|_| interpreter::Error::Trap("Memory access violation".to_owned()))?;
self.h256_at(ptr).map(Into::into)
}
fn address_at(&self, ptr: WasmPtr) -> Result<Address, interpreter::Error> {
Ok(Address::from_slice(&ptr.slice(20, &*self.memory)
.map_err(|_| interpreter::Error::Trap("Memory access violation".to_owned()))?
))
}
fn pop_address(&self, context: &mut interpreter::CallerContext) -> Result<Address, interpreter::Error> {
let ptr = WasmPtr::from_i32(context.value_stack.pop_as::<i32>()?)
.map_err(|_| interpreter::Error::Trap("Memory access violation".to_owned()))?;
self.address_at(ptr)
}
fn user_trap(&mut self, _context: interpreter::CallerContext)
-> Result<Option<interpreter::RuntimeValue>, interpreter::Error>
{
Err(interpreter::Error::Trap("unknown trap".to_owned()))
}
fn user_noop(&mut self,
_context: interpreter::CallerContext
) -> Result<Option<interpreter::RuntimeValue>, interpreter::Error> {
Ok(None)
}
/// Write call descriptor to wasm memory
pub fn write_descriptor(&mut self, call_args: CallArgs) -> Result<WasmPtr, Error> {
let d_ptr = self.alloc(16)?;
let args_len = call_args.len();
let args_ptr = self.alloc(args_len)?;
// write call descriptor
// call descriptor is [args_ptr, args_len, return_ptr, return_len]
// all are 4 byte length, last 2 are zeroed
let mut d_buf = [0u8; 16];
LittleEndian::write_u32(&mut d_buf[0..4], args_ptr);
LittleEndian::write_u32(&mut d_buf[4..8], args_len);
self.memory.set(d_ptr, &d_buf)?;
// write call args to memory
self.memory.set(args_ptr, &call_args.address)?;
self.memory.set(args_ptr+20, &call_args.sender)?;
self.memory.set(args_ptr+40, &call_args.origin)?;
self.memory.set(args_ptr+60, &call_args.value)?;
self.memory.set(args_ptr+92, &call_args.data)?;
Ok(d_ptr.into())
}
fn debug_log(&mut self, context: interpreter::CallerContext)
-> Result<Option<interpreter::RuntimeValue>, interpreter::Error>
{
let msg_len = context.value_stack.pop_as::<i32>()? as u32;
let msg_ptr = context.value_stack.pop_as::<i32>()? as u32;
let msg = String::from_utf8(self.memory.get(msg_ptr, msg_len as usize)?)
.map_err(|_| interpreter::Error::Trap("Debug log utf-8 decoding error".to_owned()))?;
trace!(target: "wasm", "Contract debug message: {}", msg);
Ok(None)
}
/// Query current gas left for execution
pub fn gas_left(&self) -> Result<u64, Error> {
if self.gas_counter > self.gas_limit { return Err(Error::InvalidGasState); }
Ok(self.gas_limit - self.gas_counter)
}
/// Shared memory reference
pub fn memory(&self) -> &interpreter::MemoryInstance {
&*self.memory
}
fn mem_copy(&self, context: interpreter::CallerContext)
-> Result<Option<interpreter::RuntimeValue>, interpreter::Error>
{
let len = context.value_stack.pop_as::<i32>()? as u32;
let dst = context.value_stack.pop_as::<i32>()? as u32;
let src = context.value_stack.pop_as::<i32>()? as u32;
let mem = self.memory().get(src, len as usize)?;
self.memory().set(dst, &mem)?;
Ok(Some(0i32.into()))
}
}
impl<'a> interpreter::UserFunctionExecutor for Runtime<'a> {
fn execute(&mut self, name: &str, context: interpreter::CallerContext)
-> Result<Option<interpreter::RuntimeValue>, interpreter::Error>
{
match name {
"_malloc" => {
self.malloc(context)
},
"_free" => {
// Since it is arena allocator, free does nothing
// todo: update if changed
self.user_noop(context)
},
"_storage_read" => {
self.storage_read(context)
},
"_storage_write" => {
self.storage_write(context)
},
"_suicide" => {
self.suicide(context)
},
"_create" => {
self.create(context)
},
"_debug" => {
self.debug_log(context)
},
"gas" => {
self.gas(context)
},
"_emscripten_memcpy_big" => {
self.mem_copy(context)
},
_ => {
trace!("Unknown env func: '{}'", name);
self.user_trap(context)
}
}
}
}

298
ethcore/wasm/src/tests.rs Normal file
View File

@@ -0,0 +1,298 @@
// 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 <http://www.gnu.org/licenses/>.
use std::sync::Arc;
use super::super::tests::{FakeExt, FakeCall, FakeCallType};
use super::WasmInterpreter;
use evm::{self, Evm, GasLeft};
use action_params::{ActionParams, ActionValue};
use util::{U256, H256, Address};
macro_rules! load_sample {
($name: expr) => {
include_bytes!(concat!("../../../res/wasm-tests/compiled/", $name)).to_vec()
}
}
fn test_finalize(res: Result<GasLeft, vm::Error>) -> Result<U256, vm::Error> {
match res {
Ok(GasLeft::Known(gas)) => Ok(gas),
Ok(GasLeft::NeedsReturn{..}) => unimplemented!(), // since ret is unimplemented.
Err(e) => Err(e),
}
}
fn wasm_interpreter() -> WasmInterpreter {
WasmInterpreter::new().expect("wasm interpreter to create without errors")
}
/// Empty contract does almost nothing except producing 1 (one) local node debug log message
#[test]
fn empty() {
let code = load_sample!("empty.wasm");
let address: Address = "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6".parse().unwrap();
let mut params = ActionParams::default();
params.address = address.clone();
params.gas = U256::from(100_000);
params.code = Some(Arc::new(code));
let mut ext = FakeExt::new();
let gas_left = {
let mut interpreter = wasm_interpreter();
test_finalize(interpreter.exec(params, &mut ext)).unwrap()
};
assert_eq!(gas_left, U256::from(99_996));
}
// This test checks if the contract deserializes payload header properly.
// Contract is provided with receiver(address), sender, origin and transaction value
// logger.wasm writes all these provided fixed header fields to some arbitrary storage keys.
#[test]
fn logger() {
let code = load_sample!("logger.wasm");
let address: Address = "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6".parse().unwrap();
let sender: Address = "0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d".parse().unwrap();
let origin: Address = "0102030405060708090a0b0c0d0e0f1011121314".parse().unwrap();
let mut params = ActionParams::default();
params.address = address.clone();
params.sender = sender.clone();
params.origin = origin.clone();
params.gas = U256::from(100_000);
params.value = ActionValue::transfer(1_000_000_000);
params.code = Some(Arc::new(code));
let mut ext = FakeExt::new();
let gas_left = {
let mut interpreter = wasm_interpreter();
test_finalize(interpreter.exec(params, &mut ext)).unwrap()
};
println!("ext.store: {:?}", ext.store);
assert_eq!(gas_left, U256::from(99581));
let address_val: H256 = address.into();
assert_eq!(
ext.store.get(&"0100000000000000000000000000000000000000000000000000000000000000".parse().unwrap()).expect("storage key to exist"),
&address_val,
"Logger sets 0x01 key to the provided address"
);
let sender_val: H256 = sender.into();
assert_eq!(
ext.store.get(&"0200000000000000000000000000000000000000000000000000000000000000".parse().unwrap()).expect("storage key to exist"),
&sender_val,
"Logger sets 0x02 key to the provided sender"
);
let origin_val: H256 = origin.into();
assert_eq!(
ext.store.get(&"0300000000000000000000000000000000000000000000000000000000000000".parse().unwrap()).expect("storage key to exist"),
&origin_val,
"Logger sets 0x03 key to the provided origin"
);
assert_eq!(
U256::from(ext.store.get(&"0400000000000000000000000000000000000000000000000000000000000000".parse().unwrap()).expect("storage key to exist")),
U256::from(1_000_000_000),
"Logger sets 0x04 key to the trasferred value"
);
}
// This test checks if the contract can allocate memory and pass pointer to the result stream properly.
// 1. Contract is being provided with the call descriptor ptr
// 2. Descriptor ptr is 16 byte length
// 3. The last 8 bytes of call descriptor is the space for the contract to fill [result_ptr[4], result_len[4]]
// if it has any result.
#[test]
fn identity() {
let code = load_sample!("identity.wasm");
let sender: Address = "01030507090b0d0f11131517191b1d1f21232527".parse().unwrap();
let mut params = ActionParams::default();
params.sender = sender.clone();
params.gas = U256::from(100_000);
params.code = Some(Arc::new(code));
let mut ext = FakeExt::new();
let (gas_left, result) = {
let mut interpreter = wasm_interpreter();
let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors");
match result {
GasLeft::Known(_) => { panic!("Identity contract should return payload"); },
GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()),
}
};
assert_eq!(gas_left, U256::from(99_689));
assert_eq!(
Address::from_slice(&result),
sender,
"Idenity test contract does not return the sender passed"
);
}
// Dispersion test sends byte array and expect the contract to 'disperse' the original elements with
// their modulo 19 dopant.
// The result is always twice as long as the input.
// This also tests byte-perfect memory allocation and in/out ptr lifecycle.
#[test]
fn dispersion() {
let code = load_sample!("dispersion.wasm");
let mut params = ActionParams::default();
params.gas = U256::from(100_000);
params.code = Some(Arc::new(code));
params.data = Some(vec![
0u8, 125, 197, 255, 19
]);
let mut ext = FakeExt::new();
let (gas_left, result) = {
let mut interpreter = wasm_interpreter();
let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors");
match result {
GasLeft::Known(_) => { panic!("Dispersion routine should return payload"); },
GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()),
}
};
assert_eq!(gas_left, U256::from(99_402));
assert_eq!(
result,
vec![0u8, 0, 125, 11, 197, 7, 255, 8, 19, 0]
);
}
#[test]
fn suicide_not() {
let code = load_sample!("suicidal.wasm");
let mut params = ActionParams::default();
params.gas = U256::from(100_000);
params.code = Some(Arc::new(code));
params.data = Some(vec![
0u8
]);
let mut ext = FakeExt::new();
let (gas_left, result) = {
let mut interpreter = wasm_interpreter();
let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors");
match result {
GasLeft::Known(_) => { panic!("Suicidal contract should return payload when had not actualy killed himself"); },
GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()),
}
};
assert_eq!(gas_left, U256::from(99_703));
assert_eq!(
result,
vec![0u8]
);
}
#[test]
fn suicide() {
let code = load_sample!("suicidal.wasm");
let refund: Address = "01030507090b0d0f11131517191b1d1f21232527".parse().unwrap();
let mut params = ActionParams::default();
params.gas = U256::from(100_000);
params.code = Some(Arc::new(code));
let mut args = vec![127u8];
args.extend(refund.to_vec());
params.data = Some(args);
let mut ext = FakeExt::new();
let gas_left = {
let mut interpreter = wasm_interpreter();
let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors");
match result {
GasLeft::Known(gas) => gas,
GasLeft::NeedsReturn { .. } => {
panic!("Suicidal contract should not return anything when had killed itself");
},
}
};
assert_eq!(gas_left, U256::from(99_747));
assert!(ext.suicides.contains(&refund));
}
#[test]
fn create() {
let mut params = ActionParams::default();
params.gas = U256::from(100_000);
params.code = Some(Arc::new(load_sample!("creator.wasm")));
params.data = Some(vec![0u8, 2, 4, 8, 16, 32, 64, 128]);
params.value = ActionValue::transfer(1_000_000_000);
let mut ext = FakeExt::new();
let gas_left = {
let mut interpreter = wasm_interpreter();
let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors");
match result {
GasLeft::Known(gas) => gas,
GasLeft::NeedsReturn { .. } => {
panic!("Create contract should not return anthing because ext always fails on creation");
},
}
};
trace!(target: "wasm", "fake_calls: {:?}", &ext.calls);
assert!(ext.calls.contains(
&FakeCall {
call_type: FakeCallType::Create,
gas: U256::from(99_778),
sender_address: None,
receive_address: None,
value: Some(1_000_000_000.into()),
data: vec![0u8, 2, 4, 8, 16, 32, 64, 128],
code_address: None,
}
));
assert_eq!(gas_left, U256::from(99_768));
}
// Realloc test
#[test]
fn realloc() {
let code = load_sample!("realloc.wasm");
let mut params = ActionParams::default();
params.gas = U256::from(100_000);
params.code = Some(Arc::new(code));
params.data = Some(vec![0u8]);
let mut ext = FakeExt::new();
let (gas_left, result) = {
let mut interpreter = wasm_interpreter();
let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors");
match result {
GasLeft::Known(_) => { panic!("Realloc should return payload"); },
GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()),
}
};
assert_eq!(gas_left, U256::from(98326));
assert_eq!(result, vec![0u8; 2]);
}