WASM contracts MVP (#5679)
* lifetime issues * refactor to new 'native env' * descriptors and such * wasm mvp continued * finalized env/ext bindings * descriptor -> call_args * inject gas counter * result processing and engine activation * tabify some source files * needs return new * wasm tests initial * erradicate warnings * origin in the descriptor * update test repo * payload verification tests * identity return payload test * some test description * dispersion test * check length here * suicidal contract * engine params * fix typo * review fixes * submodule update * update - purge reserved space * doc effort * more review fixes * fix error message * fix dependency url * reorg error handling * update submodule * update utils * update to latest parity-wasm * tabify * fix wasm magic header * update dependencies * external create and tests * update to latest tests * extra trace info * Update parity-wasm * update wasm-utils also * few traces and result handle change * alter trace content * fix issues with optimizer, update to latest parity with validator, etc * static initialization * license preamble * update wasm crates and gas costs * fix grumbles * bring back lifetime * fix compilation
This commit is contained in:
@@ -39,6 +39,16 @@ impl ActionValue {
|
||||
ActionValue::Transfer(x) | ActionValue::Apparent(x) => x
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the transfer action value of the U256-convertable raw value
|
||||
pub fn transfer<T: Into<U256>>(transfer_value: T) -> ActionValue {
|
||||
ActionValue::Transfer(transfer_value.into())
|
||||
}
|
||||
|
||||
/// Returns the apparent action value of the U256-convertable raw value
|
||||
pub fn apparent<T: Into<U256>>(apparent_value: T) -> ActionValue {
|
||||
ActionValue::Apparent(apparent_value.into())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: should be a trait, possible to avoid cloning everything from a Transaction(/View).
|
||||
|
||||
@@ -376,6 +376,11 @@ pub trait Engine : Sync + Send {
|
||||
self.snapshot_components().is_some()
|
||||
}
|
||||
|
||||
/// If this engine supports wasm contracts.
|
||||
fn supports_wasm(&self) -> bool {
|
||||
self.params().wasm
|
||||
}
|
||||
|
||||
/// Returns new contract address generation scheme at given block number.
|
||||
fn create_address_scheme(&self, number: BlockNumber) -> CreateContractAddress {
|
||||
if number >= self.params().eip86_transition {
|
||||
|
||||
@@ -21,6 +21,7 @@ use util::{U128, U256, U512, trie};
|
||||
use action_params::ActionParams;
|
||||
use evm::Ext;
|
||||
use builtin;
|
||||
use super::wasm;
|
||||
|
||||
/// Evm errors.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@@ -66,6 +67,8 @@ pub enum Error {
|
||||
MutableCallInStaticContext,
|
||||
/// Likely to cause consensus issues.
|
||||
Internal(String),
|
||||
/// Wasm runtime error
|
||||
Wasm(String),
|
||||
}
|
||||
|
||||
impl From<Box<trie::TrieError>> for Error {
|
||||
@@ -80,6 +83,12 @@ impl From<builtin::Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<wasm::RuntimeError> for Error {
|
||||
fn from(err: wasm::RuntimeError) -> Self {
|
||||
Error::Wasm(format!("Runtime error: {:?}", err))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::Error::*;
|
||||
@@ -92,6 +101,7 @@ impl fmt::Display for Error {
|
||||
BuiltIn(name) => write!(f, "Built-in failed: {}", name),
|
||||
Internal(ref msg) => write!(f, "Internal error: {}", msg),
|
||||
MutableCallInStaticContext => write!(f, "Mutable call in static context"),
|
||||
Wasm(ref msg) => write!(f, "Internal error: {}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ pub mod interpreter;
|
||||
#[macro_use]
|
||||
pub mod factory;
|
||||
pub mod schedule;
|
||||
pub mod wasm;
|
||||
|
||||
mod vmtype;
|
||||
mod instructions;
|
||||
|
||||
@@ -39,13 +39,13 @@ pub enum FakeCallType {
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Debug)]
|
||||
pub struct FakeCall {
|
||||
call_type: FakeCallType,
|
||||
gas: U256,
|
||||
sender_address: Option<Address>,
|
||||
receive_address: Option<Address>,
|
||||
value: Option<U256>,
|
||||
data: Bytes,
|
||||
code_address: Option<Address>,
|
||||
pub call_type: FakeCallType,
|
||||
pub gas: U256,
|
||||
pub sender_address: Option<Address>,
|
||||
pub receive_address: Option<Address>,
|
||||
pub value: Option<U256>,
|
||||
pub data: Bytes,
|
||||
pub code_address: Option<Address>,
|
||||
}
|
||||
|
||||
/// Fake externalities test structure.
|
||||
@@ -53,17 +53,17 @@ pub struct FakeCall {
|
||||
/// Can't do recursive calls.
|
||||
#[derive(Default)]
|
||||
pub struct FakeExt {
|
||||
pub store: HashMap<H256, H256>,
|
||||
pub suicides: HashSet<Address>,
|
||||
pub calls: HashSet<FakeCall>,
|
||||
sstore_clears: usize,
|
||||
depth: usize,
|
||||
store: HashMap<H256, H256>,
|
||||
blockhashes: HashMap<U256, H256>,
|
||||
codes: HashMap<Address, Arc<Bytes>>,
|
||||
logs: Vec<FakeLogEntry>,
|
||||
_suicides: HashSet<Address>,
|
||||
info: EnvInfo,
|
||||
schedule: Schedule,
|
||||
balances: HashMap<Address, U256>,
|
||||
calls: HashSet<FakeCall>,
|
||||
}
|
||||
|
||||
// similar to the normal `finalize` function, but ignoring NeedsReturn.
|
||||
@@ -173,8 +173,9 @@ impl Ext for FakeExt {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn suicide(&mut self, _refund_address: &Address) -> evm::Result<()> {
|
||||
unimplemented!();
|
||||
fn suicide(&mut self, refund_address: &Address) -> evm::Result<()> {
|
||||
self.suicides.insert(refund_address.clone());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn schedule(&self) -> &Schedule {
|
||||
|
||||
62
ethcore/src/evm/wasm/call_args.rs
Normal file
62
ethcore/src/evm/wasm/call_args.rs
Normal 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
|
||||
}
|
||||
}
|
||||
119
ethcore/src/evm/wasm/env.rs
Normal file
119
ethcore/src/evm/wasm/env.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <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),
|
||||
)
|
||||
];
|
||||
159
ethcore/src/evm/wasm/mod.rs
Normal file
159
ethcore/src/evm/wasm/mod.rs
Normal file
@@ -0,0 +1,159 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Wasm Interpreter
|
||||
|
||||
mod runtime;
|
||||
mod ptr;
|
||||
mod call_args;
|
||||
mod result;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
mod env;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
const DEFAULT_STACK_SPACE: u32 = 5 * 1024 * 1024;
|
||||
|
||||
use parity_wasm::{interpreter, elements};
|
||||
use parity_wasm::interpreter::ModuleInstanceInterface;
|
||||
use wasm_utils;
|
||||
|
||||
use evm::{self, GasLeft, ReturnData};
|
||||
use action_params::ActionParams;
|
||||
use self::runtime::Runtime;
|
||||
|
||||
pub use self::runtime::Error as RuntimeError;
|
||||
|
||||
const DEFAULT_RESULT_BUFFER: usize = 1024;
|
||||
|
||||
/// Wasm interpreter instance
|
||||
pub struct WasmInterpreter {
|
||||
program: interpreter::ProgramInstance,
|
||||
result: Vec<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 evm::Evm for WasmInterpreter {
|
||||
|
||||
fn exec(&mut self, params: ActionParams, ext: &mut evm::Ext) -> evm::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(evm::Error::Wasm("Wasm interpreter cannot run contracts with gas >= 2^64".to_owned()));
|
||||
}
|
||||
|
||||
let mut runtime = Runtime::with_params(
|
||||
ext,
|
||||
env_memory,
|
||||
DEFAULT_STACK_SPACE,
|
||||
params.gas.low_u64(),
|
||||
);
|
||||
|
||||
let mut cursor = ::std::io::Cursor::new(&*code);
|
||||
|
||||
let contract_module = wasm_utils::inject_gas_counter(
|
||||
elements::Module::deserialize(
|
||||
&mut cursor
|
||||
).map_err(|err| {
|
||||
evm::Error::Wasm(format!("Error deserializing contract code ({:?})", err))
|
||||
})?
|
||||
);
|
||||
|
||||
let d_ptr = runtime.write_descriptor(
|
||||
call_args::CallArgs::new(
|
||||
params.address,
|
||||
params.sender,
|
||||
params.origin,
|
||||
params.value.value(),
|
||||
params.data.unwrap_or(Vec::with_capacity(0)),
|
||||
)
|
||||
)?;
|
||||
|
||||
{
|
||||
let execution_params = interpreter::ExecutionParams::with_external(
|
||||
"env".into(),
|
||||
Arc::new(
|
||||
interpreter::env_native_module(env_instance, native_bindings(&mut runtime))
|
||||
.map_err(|err| {
|
||||
// todo: prefer explicit panic here also?
|
||||
evm::Error::Wasm(format!("Error instantiating native bindings: {:?}", err))
|
||||
})?
|
||||
)
|
||||
).add_argument(interpreter::RuntimeValue::I32(d_ptr.as_raw() as i32));
|
||||
|
||||
let module_instance = self.program.add_module("contract", contract_module, Some(&execution_params.externals))
|
||||
.map_err(|err| {
|
||||
trace!(target: "wasm", "Error adding contract module: {:?}", err);
|
||||
evm::Error::from(RuntimeError::Interpreter(err))
|
||||
})?;
|
||||
|
||||
module_instance.execute_export("_call", execution_params)
|
||||
.map_err(|err| {
|
||||
trace!(target: "wasm", "Error executing contract: {:?}", err);
|
||||
evm::Error::from(RuntimeError::Interpreter(err))
|
||||
})?;
|
||||
}
|
||||
|
||||
let result = result::WasmResult::new(d_ptr);
|
||||
if result.peek_empty(&*runtime.memory())? {
|
||||
trace!(target: "wasm", "Contract execution result is empty.");
|
||||
Ok(GasLeft::Known(runtime.gas_left()?.into()))
|
||||
} else {
|
||||
self.result.clear();
|
||||
// todo: use memory views to avoid copy
|
||||
self.result.extend(result.pop(&*runtime.memory())?);
|
||||
let len = self.result.len();
|
||||
Ok(GasLeft::NeedsReturn {
|
||||
gas_left: runtime.gas_left()?.into(),
|
||||
data: ReturnData::new(
|
||||
::std::mem::replace(&mut self.result, Vec::with_capacity(DEFAULT_RESULT_BUFFER)),
|
||||
0,
|
||||
len,
|
||||
),
|
||||
apply_state: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn native_bindings<'a>(runtime: &'a mut Runtime) -> interpreter::UserFunctions<'a> {
|
||||
interpreter::UserFunctions {
|
||||
executor: runtime,
|
||||
functions: ::std::borrow::Cow::from(env::SIGNATURES),
|
||||
}
|
||||
}
|
||||
52
ethcore/src/evm/wasm/ptr.rs
Normal file
52
ethcore/src/evm/wasm/ptr.rs
Normal 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 }
|
||||
}
|
||||
51
ethcore/src/evm/wasm/result.rs
Normal file
51
ethcore/src/evm/wasm/result.rs
Normal 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)?)
|
||||
}
|
||||
}
|
||||
356
ethcore/src/evm/wasm/runtime.rs
Normal file
356
ethcore/src/evm/wasm/runtime.rs
Normal file
@@ -0,0 +1,356 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Wasm evm program runtime intstance
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use byteorder::{LittleEndian, ByteOrder};
|
||||
|
||||
use evm;
|
||||
|
||||
use parity_wasm::interpreter;
|
||||
use util::{Address, H256, U256};
|
||||
|
||||
use super::ptr::{WasmPtr, Error as PtrError};
|
||||
use super::call_args::CallArgs;
|
||||
|
||||
/// Wasm runtime error
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Storage error
|
||||
Storage,
|
||||
/// Allocator error
|
||||
Allocator,
|
||||
/// Invalid gas state during the call
|
||||
InvalidGasState,
|
||||
/// Memory access violation
|
||||
AccessViolation,
|
||||
/// Interpreter runtime error
|
||||
Interpreter(interpreter::Error),
|
||||
}
|
||||
|
||||
impl From<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 evm::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 evm::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, evm::CreateContractAddress::FromSenderAndCodeHash) {
|
||||
evm::ContractCreateResult::Created(address, gas_left) => {
|
||||
self.memory.set(result_ptr, &*address)?;
|
||||
self.gas_counter = self.gas_limit - gas_left.low_u64();
|
||||
trace!(target: "wasm", "runtime: create contract success (@{:?})", address);
|
||||
Ok(Some(0i32.into()))
|
||||
},
|
||||
evm::ContractCreateResult::Failed => {
|
||||
trace!(target: "wasm", "runtime: create contract fail");
|
||||
Ok(Some((-1i32).into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocate memory using the wasm stack params
|
||||
pub fn malloc(&mut self, context: interpreter::CallerContext)
|
||||
-> Result<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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
279
ethcore/src/evm/wasm/tests.rs
Normal file
279
ethcore/src/evm/wasm/tests.rs
Normal file
@@ -0,0 +1,279 @@
|
||||
use std::path::PathBuf;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::sync::Arc;
|
||||
|
||||
use ethcore_logger::init_log;
|
||||
use super::super::tests::{FakeExt, FakeCall, FakeCallType};
|
||||
use super::WasmInterpreter;
|
||||
use evm::{self, Evm, GasLeft};
|
||||
use action_params::{ActionParams, ActionValue};
|
||||
use util::{U256, H256, Address};
|
||||
|
||||
fn load_sample(name: &str) -> Vec<u8> {
|
||||
let mut path = PathBuf::from("./res/wasm-tests/compiled");
|
||||
path.push(name);
|
||||
let mut file = File::open(path).expect(&format!("File {} for test to exist", name));
|
||||
let mut data = vec![];
|
||||
file.read_to_end(&mut data).expect(&format!("Test {} to load ok", name));
|
||||
data
|
||||
}
|
||||
|
||||
fn test_finalize(res: Result<GasLeft, evm::Error>) -> Result<U256, evm::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() {
|
||||
init_log();
|
||||
|
||||
let code = load_sample("empty.wasm");
|
||||
let address: Address = "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6".parse().unwrap();
|
||||
|
||||
let mut params = ActionParams::default();
|
||||
params.address = address.clone();
|
||||
params.gas = U256::from(100_000);
|
||||
params.code = Some(Arc::new(code));
|
||||
let mut ext = FakeExt::new();
|
||||
|
||||
let gas_left = {
|
||||
let mut interpreter = wasm_interpreter();
|
||||
test_finalize(interpreter.exec(params, &mut ext)).unwrap()
|
||||
};
|
||||
|
||||
assert_eq!(gas_left, U256::from(99_996));
|
||||
}
|
||||
|
||||
// This test checks if the contract deserializes payload header properly.
|
||||
// Contract is provided with receiver(address), sender, origin and transaction value
|
||||
// logger.wasm writes all these provided fixed header fields to some arbitrary storage keys.
|
||||
#[test]
|
||||
fn logger() {
|
||||
init_log();
|
||||
|
||||
let code = load_sample("logger.wasm");
|
||||
let address: Address = "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6".parse().unwrap();
|
||||
let sender: Address = "0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d".parse().unwrap();
|
||||
let origin: Address = "0102030405060708090a0b0c0d0e0f1011121314".parse().unwrap();
|
||||
|
||||
let mut params = ActionParams::default();
|
||||
params.address = address.clone();
|
||||
params.sender = sender.clone();
|
||||
params.origin = origin.clone();
|
||||
params.gas = U256::from(100_000);
|
||||
params.value = ActionValue::transfer(1_000_000_000);
|
||||
params.code = Some(Arc::new(code));
|
||||
let mut ext = FakeExt::new();
|
||||
|
||||
let gas_left = {
|
||||
let mut interpreter = wasm_interpreter();
|
||||
test_finalize(interpreter.exec(params, &mut ext)).unwrap()
|
||||
};
|
||||
|
||||
println!("ext.store: {:?}", ext.store);
|
||||
assert_eq!(gas_left, U256::from(99581));
|
||||
let address_val: H256 = address.into();
|
||||
assert_eq!(
|
||||
ext.store.get(&"0100000000000000000000000000000000000000000000000000000000000000".parse().unwrap()).expect("storage key to exist"),
|
||||
&address_val,
|
||||
"Logger sets 0x01 key to the provided address"
|
||||
);
|
||||
let sender_val: H256 = sender.into();
|
||||
assert_eq!(
|
||||
ext.store.get(&"0200000000000000000000000000000000000000000000000000000000000000".parse().unwrap()).expect("storage key to exist"),
|
||||
&sender_val,
|
||||
"Logger sets 0x02 key to the provided sender"
|
||||
);
|
||||
let origin_val: H256 = origin.into();
|
||||
assert_eq!(
|
||||
ext.store.get(&"0300000000000000000000000000000000000000000000000000000000000000".parse().unwrap()).expect("storage key to exist"),
|
||||
&origin_val,
|
||||
"Logger sets 0x03 key to the provided origin"
|
||||
);
|
||||
assert_eq!(
|
||||
U256::from(ext.store.get(&"0400000000000000000000000000000000000000000000000000000000000000".parse().unwrap()).expect("storage key to exist")),
|
||||
U256::from(1_000_000_000),
|
||||
"Logger sets 0x04 key to the trasferred value"
|
||||
);
|
||||
}
|
||||
|
||||
// This test checks if the contract can allocate memory and pass pointer to the result stream properly.
|
||||
// 1. Contract is being provided with the call descriptor ptr
|
||||
// 2. Descriptor ptr is 16 byte length
|
||||
// 3. The last 8 bytes of call descriptor is the space for the contract to fill [result_ptr[4], result_len[4]]
|
||||
// if it has any result.
|
||||
#[test]
|
||||
fn identity() {
|
||||
init_log();
|
||||
|
||||
let code = load_sample("identity.wasm");
|
||||
let sender: Address = "01030507090b0d0f11131517191b1d1f21232527".parse().unwrap();
|
||||
|
||||
let mut params = ActionParams::default();
|
||||
params.sender = sender.clone();
|
||||
params.gas = U256::from(100_000);
|
||||
params.code = Some(Arc::new(code));
|
||||
let mut ext = FakeExt::new();
|
||||
|
||||
let (gas_left, result) = {
|
||||
let mut interpreter = wasm_interpreter();
|
||||
let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors");
|
||||
match result {
|
||||
GasLeft::Known(_) => { panic!("Identity contract should return payload"); },
|
||||
GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()),
|
||||
}
|
||||
};
|
||||
|
||||
assert_eq!(gas_left, U256::from(99_689));
|
||||
|
||||
assert_eq!(
|
||||
Address::from_slice(&result),
|
||||
sender,
|
||||
"Idenity test contract does not return the sender passed"
|
||||
);
|
||||
}
|
||||
|
||||
// Dispersion test sends byte array and expect the contract to 'disperse' the original elements with
|
||||
// their modulo 19 dopant.
|
||||
// The result is always twice as long as the input.
|
||||
// This also tests byte-perfect memory allocation and in/out ptr lifecycle.
|
||||
#[test]
|
||||
fn dispersion() {
|
||||
init_log();
|
||||
|
||||
let code = load_sample("dispersion.wasm");
|
||||
|
||||
let mut params = ActionParams::default();
|
||||
params.gas = U256::from(100_000);
|
||||
params.code = Some(Arc::new(code));
|
||||
params.data = Some(vec![
|
||||
0u8, 125, 197, 255, 19
|
||||
]);
|
||||
let mut ext = FakeExt::new();
|
||||
|
||||
let (gas_left, result) = {
|
||||
let mut interpreter = wasm_interpreter();
|
||||
let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors");
|
||||
match result {
|
||||
GasLeft::Known(_) => { panic!("Dispersion routine should return payload"); },
|
||||
GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()),
|
||||
}
|
||||
};
|
||||
|
||||
assert_eq!(gas_left, U256::from(99_402));
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
vec![0u8, 0, 125, 11, 197, 7, 255, 8, 19, 0]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn suicide_not() {
|
||||
init_log();
|
||||
|
||||
let code = load_sample("suicidal.wasm");
|
||||
|
||||
let mut params = ActionParams::default();
|
||||
params.gas = U256::from(100_000);
|
||||
params.code = Some(Arc::new(code));
|
||||
params.data = Some(vec![
|
||||
0u8
|
||||
]);
|
||||
let mut ext = FakeExt::new();
|
||||
|
||||
let (gas_left, result) = {
|
||||
let mut interpreter = wasm_interpreter();
|
||||
let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors");
|
||||
match result {
|
||||
GasLeft::Known(_) => { panic!("Suicidal contract should return payload when had not actualy killed himself"); },
|
||||
GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()),
|
||||
}
|
||||
};
|
||||
|
||||
assert_eq!(gas_left, U256::from(99_703));
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
vec![0u8]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn suicide() {
|
||||
init_log();
|
||||
|
||||
let code = load_sample("suicidal.wasm");
|
||||
|
||||
let refund: Address = "01030507090b0d0f11131517191b1d1f21232527".parse().unwrap();
|
||||
let mut params = ActionParams::default();
|
||||
params.gas = U256::from(100_000);
|
||||
params.code = Some(Arc::new(code));
|
||||
|
||||
let mut args = vec![127u8];
|
||||
args.extend(refund.to_vec());
|
||||
params.data = Some(args);
|
||||
|
||||
let mut ext = FakeExt::new();
|
||||
|
||||
let gas_left = {
|
||||
let mut interpreter = wasm_interpreter();
|
||||
let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors");
|
||||
match result {
|
||||
GasLeft::Known(gas) => gas,
|
||||
GasLeft::NeedsReturn { .. } => {
|
||||
panic!("Suicidal contract should not return anything when had killed itself");
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
assert_eq!(gas_left, U256::from(99_747));
|
||||
assert!(ext.suicides.contains(&refund));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create() {
|
||||
init_log();
|
||||
|
||||
let mut params = ActionParams::default();
|
||||
params.gas = U256::from(100_000);
|
||||
params.code = Some(Arc::new(load_sample("creator.wasm")));
|
||||
params.data = Some(vec![0u8, 2, 4, 8, 16, 32, 64, 128]);
|
||||
params.value = ActionValue::transfer(1_000_000_000);
|
||||
|
||||
let mut ext = FakeExt::new();
|
||||
|
||||
let gas_left = {
|
||||
let mut interpreter = wasm_interpreter();
|
||||
let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors");
|
||||
match result {
|
||||
GasLeft::Known(gas) => gas,
|
||||
GasLeft::NeedsReturn { .. } => {
|
||||
panic!("Create contract should not return anthing because ext always fails on creation");
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
trace!(target: "wasm", "fake_calls: {:?}", &ext.calls);
|
||||
assert!(ext.calls.contains(
|
||||
&FakeCall {
|
||||
call_type: FakeCallType::Create,
|
||||
gas: U256::from(99_778),
|
||||
sender_address: None,
|
||||
receive_address: None,
|
||||
value: Some(1_000_000_000.into()),
|
||||
data: vec![0u8, 2, 4, 8, 16, 32, 64, 128],
|
||||
code_address: None,
|
||||
}
|
||||
));
|
||||
assert_eq!(gas_left, U256::from(99_768));
|
||||
}
|
||||
@@ -22,7 +22,7 @@ use engines::Engine;
|
||||
use types::executed::CallType;
|
||||
use env_info::EnvInfo;
|
||||
use error::ExecutionError;
|
||||
use evm::{self, Ext, Finalize, CreateContractAddress, FinalizationResult, ReturnData, CleanDustMode};
|
||||
use evm::{self, wasm, Factory, Ext, Finalize, CreateContractAddress, FinalizationResult, ReturnData, CleanDustMode};
|
||||
use externalities::*;
|
||||
use trace::{FlatTrace, Tracer, NoopTracer, ExecutiveTracer, VMTrace, VMTracer, ExecutiveVMTracer, NoopVMTracer};
|
||||
use transaction::{Action, SignedTransaction};
|
||||
@@ -34,6 +34,8 @@ pub use types::executed::{Executed, ExecutionResult};
|
||||
/// Maybe something like here: `https://github.com/ethereum/libethereum/blob/4db169b8504f2b87f7d5a481819cfb959fc65f6c/libethereum/ExtVM.cpp`
|
||||
const STACK_SIZE_PER_DEPTH: usize = 24*1024;
|
||||
|
||||
const WASM_MAGIC_NUMBER: &'static [u8; 4] = b"\0asm";
|
||||
|
||||
/// Returns new address created from address, nonce, and code hash
|
||||
pub fn contract_address(address_scheme: CreateContractAddress, sender: &Address, nonce: &U256, code: &[u8]) -> (Address, Option<H256>) {
|
||||
use rlp::RlpStream;
|
||||
@@ -72,6 +74,20 @@ pub struct TransactOptions {
|
||||
pub check_nonce: bool,
|
||||
}
|
||||
|
||||
pub fn executor<E>(engine: &E, vm_factory: &Factory, params: &ActionParams)
|
||||
-> Box<evm::Evm> where E: Engine + ?Sized
|
||||
{
|
||||
if engine.supports_wasm() && params.code.as_ref().map_or(false, |code| code.len() > 4 && &code[0..4] == WASM_MAGIC_NUMBER) {
|
||||
Box::new(
|
||||
wasm::WasmInterpreter::new()
|
||||
// prefer to fail fast
|
||||
.expect("Failed to create wasm runtime")
|
||||
)
|
||||
} else {
|
||||
vm_factory.create(params.gas)
|
||||
}
|
||||
}
|
||||
|
||||
/// Transaction executor.
|
||||
pub struct Executive<'a, B: 'a + StateBackend, E: 'a + Engine + ?Sized> {
|
||||
state: &'a mut State<B>,
|
||||
@@ -263,18 +279,19 @@ impl<'a, B: 'a + StateBackend, E: Engine + ?Sized> Executive<'a, B, E> {
|
||||
let vm_factory = self.state.vm_factory();
|
||||
let mut ext = self.as_externalities(OriginInfo::from(¶ms), unconfirmed_substate, output_policy, tracer, vm_tracer, static_call);
|
||||
trace!(target: "executive", "ext.schedule.have_delegate_call: {}", ext.schedule().have_delegate_call);
|
||||
return vm_factory.create(params.gas).exec(params, &mut ext).finalize(ext);
|
||||
return executor(self.engine, &vm_factory, ¶ms).exec(params, &mut ext).finalize(ext);
|
||||
}
|
||||
|
||||
// Start in new thread to reset stack
|
||||
// TODO [todr] No thread builder yet, so we need to reset once for a while
|
||||
// https://github.com/aturon/crossbeam/issues/16
|
||||
crossbeam::scope(|scope| {
|
||||
let engine = self.engine;
|
||||
let vm_factory = self.state.vm_factory();
|
||||
let mut ext = self.as_externalities(OriginInfo::from(¶ms), unconfirmed_substate, output_policy, tracer, vm_tracer, static_call);
|
||||
|
||||
scope.spawn(move || {
|
||||
vm_factory.create(params.gas).exec(params, &mut ext).finalize(ext)
|
||||
executor(engine, &vm_factory, ¶ms).exec(params, &mut ext).finalize(ext)
|
||||
})
|
||||
}).join()
|
||||
}
|
||||
@@ -562,6 +579,7 @@ impl<'a, B: 'a + StateBackend, E: Engine + ?Sized> Executive<'a, B, E> {
|
||||
| Err(evm::Error::BadInstruction {.. })
|
||||
| Err(evm::Error::StackUnderflow {..})
|
||||
| Err(evm::Error::BuiltIn {..})
|
||||
| Err(evm::Error::Wasm {..})
|
||||
| Err(evm::Error::OutOfStack {..})
|
||||
| Err(evm::Error::MutableCallInStaticContext)
|
||||
| Ok(FinalizationResult { apply_state: false, .. }) => {
|
||||
|
||||
@@ -105,6 +105,8 @@ extern crate semver;
|
||||
extern crate stats;
|
||||
extern crate time;
|
||||
extern crate transient_hashmap;
|
||||
extern crate parity_wasm;
|
||||
extern crate wasm_utils;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
@@ -80,9 +80,11 @@ pub struct CommonParams {
|
||||
/// Number of first block where dust cleanup rules (EIP-168 and EIP169) begin.
|
||||
pub dust_protection_transition: BlockNumber,
|
||||
/// Nonce cap increase per block. Nonce cap is only checked if dust protection is enabled.
|
||||
pub nonce_cap_increment : u64,
|
||||
pub nonce_cap_increment: u64,
|
||||
/// Enable dust cleanup for contracts.
|
||||
pub remove_dust_contracts : bool,
|
||||
pub remove_dust_contracts: bool,
|
||||
/// Wasm support
|
||||
pub wasm: bool,
|
||||
}
|
||||
|
||||
impl From<ethjson::spec::Params> for CommonParams {
|
||||
@@ -110,6 +112,7 @@ impl From<ethjson::spec::Params> for CommonParams {
|
||||
dust_protection_transition: p.dust_protection_transition.map_or(BlockNumber::max_value(), Into::into),
|
||||
nonce_cap_increment: p.nonce_cap_increment.map_or(64, Into::into),
|
||||
remove_dust_contracts: p.remove_dust_contracts.unwrap_or(false),
|
||||
wasm: p.wasm.unwrap_or(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ pub enum Error {
|
||||
Internal,
|
||||
/// When execution tries to modify the state in static context
|
||||
MutableCallInStaticContext,
|
||||
/// Wasm error
|
||||
Wasm,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a EvmError> for Error {
|
||||
@@ -53,6 +55,7 @@ impl<'a> From<&'a EvmError> for Error {
|
||||
EvmError::StackUnderflow { .. } => Error::StackUnderflow,
|
||||
EvmError::OutOfStack { .. } => Error::OutOfStack,
|
||||
EvmError::BuiltIn { .. } => Error::BuiltIn,
|
||||
EvmError::Wasm { .. } => Error::Wasm,
|
||||
EvmError::Internal(_) => Error::Internal,
|
||||
EvmError::MutableCallInStaticContext => Error::MutableCallInStaticContext,
|
||||
}
|
||||
@@ -75,6 +78,7 @@ impl fmt::Display for Error {
|
||||
StackUnderflow => "Stack underflow",
|
||||
OutOfStack => "Out of stack",
|
||||
BuiltIn => "Built-in failed",
|
||||
Wasm => "Wasm runtime error",
|
||||
Internal => "Internal error",
|
||||
MutableCallInStaticContext => "Mutable Call In Static Context",
|
||||
};
|
||||
@@ -94,6 +98,7 @@ impl Encodable for Error {
|
||||
Internal => 5,
|
||||
BuiltIn => 6,
|
||||
MutableCallInStaticContext => 7,
|
||||
Wasm => 8,
|
||||
};
|
||||
|
||||
s.append_internal(&value);
|
||||
@@ -113,6 +118,7 @@ impl Decodable for Error {
|
||||
5 => Ok(Internal),
|
||||
6 => Ok(BuiltIn),
|
||||
7 => Ok(MutableCallInStaticContext),
|
||||
8 => Ok(Wasm),
|
||||
_ => Err(DecoderError::Custom("Invalid error type")),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user