diff --git a/ethcore/src/client/evm_test_client.rs b/ethcore/src/client/evm_test_client.rs new file mode 100644 index 000000000..2fd071062 --- /dev/null +++ b/ethcore/src/client/evm_test_client.rs @@ -0,0 +1,117 @@ +// 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 . + +//! Simple Client used for EVM tests. + +use std::fmt; +use std::sync::Arc; +use util::{self, U256, journaldb, trie}; +use util::kvdb::{self, KeyValueDB}; +use {state, state_db, client, executive, trace, db, spec}; +use factory::Factories; +use evm::{self, VMType}; +use action_params::ActionParams; + +/// EVM test Error. +#[derive(Debug)] +pub enum EvmTestError { + /// Trie integrity error. + Trie(util::TrieError), + /// EVM error. + Evm(evm::Error), + /// Initialization error. + Initialization(::error::Error), + /// Low-level database error. + Database(String), +} + +impl fmt::Display for EvmTestError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + use self::EvmTestError::*; + + match *self { + Trie(ref err) => write!(fmt, "Trie: {}", err), + Evm(ref err) => write!(fmt, "EVM: {}", err), + Initialization(ref err) => write!(fmt, "Initialization: {}", err), + Database(ref err) => write!(fmt, "DB: {}", err), + } + } +} + +/// Simplified, single-block EVM test client. +pub struct EvmTestClient { + state_db: state_db::StateDB, + factories: Factories, + spec: spec::Spec, +} + +impl EvmTestClient { + /// Creates new EVM test client with in-memory DB initialized with genesis of given Spec. + pub fn new(spec: spec::Spec) -> Result { + let factories = Factories { + vm: evm::Factory::new(VMType::Interpreter, 5 * 1024), + trie: trie::TrieFactory::new(trie::TrieSpec::Secure), + accountdb: Default::default(), + }; + let db = Arc::new(kvdb::in_memory(db::NUM_COLUMNS.expect("We use column-based DB; qed"))); + let journal_db = journaldb::new(db.clone(), journaldb::Algorithm::EarlyMerge, db::COL_STATE); + let mut state_db = state_db::StateDB::new(journal_db, 5 * 1024 * 1024); + state_db = spec.ensure_db_good(state_db, &factories).map_err(EvmTestError::Initialization)?; + // Write DB + { + let mut batch = kvdb::DBTransaction::new(); + state_db.journal_under(&mut batch, 0, &spec.genesis_header().hash()).map_err(|e| EvmTestError::Initialization(e.into()))?; + db.write(batch).map_err(EvmTestError::Database)?; + } + + Ok(EvmTestClient { + state_db, + factories, + spec, + }) + } + + /// Call given contract. + pub fn call(&mut self, params: ActionParams, vm_tracer: &mut T) + -> Result<(U256, Vec), EvmTestError> + { + let genesis = self.spec.genesis_header(); + let mut state = state::State::from_existing(self.state_db.boxed_clone(), *genesis.state_root(), self.spec.engine.account_start_nonce(), self.factories.clone()) + .map_err(EvmTestError::Trie)?; + let info = client::EnvInfo { + number: genesis.number(), + author: *genesis.author(), + timestamp: genesis.timestamp(), + difficulty: *genesis.difficulty(), + last_hashes: Arc::new([util::H256::default(); 256].to_vec()), + gas_used: 0.into(), + gas_limit: *genesis.gas_limit(), + }; + let mut substate = state::Substate::new(); + let mut tracer = trace::NoopTracer; + let mut output = vec![]; + let mut executive = executive::Executive::new(&mut state, &info, &*self.spec.engine, &self.factories.vm); + let gas_left = executive.call( + params, + &mut substate, + util::BytesRef::Flexible(&mut output), + &mut tracer, + vm_tracer, + ).map_err(EvmTestError::Evm)?; + + Ok((gas_left, output)) + } +} diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index f768e8d43..cd5c867e5 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -19,6 +19,7 @@ mod ancient_import; mod config; mod error; +mod evm_test_client; mod test_client; mod trace; mod client; @@ -26,6 +27,7 @@ mod client; pub use self::client::*; pub use self::config::{Mode, ClientConfig, DatabaseCompactionProfile, BlockChainConfig, VMType}; pub use self::error::Error; +pub use self::evm_test_client::{EvmTestClient, EvmTestError}; pub use self::test_client::{TestBlockChainClient, EachBlockWith}; pub use self::chain_notify::ChainNotify; pub use self::traits::{BlockChainClient, MiningBlockChainClient, EngineClient}; diff --git a/ethcore/src/evm/ext.rs b/ethcore/src/evm/ext.rs index e6b644874..d2ef23159 100644 --- a/ethcore/src/evm/ext.rs +++ b/ethcore/src/evm/ext.rs @@ -131,7 +131,7 @@ pub trait Ext { fn inc_sstore_clears(&mut self); /// Prepare to trace an operation. Passthrough for the VM trace. - fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: &U256) -> bool { false } + fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _stack_pop: usize, _gas_cost: &U256) -> bool { false } /// Trace the finalised execution of a single instruction. fn trace_executed(&mut self, _gas_used: U256, _stack_push: &[U256], _mem_diff: Option<(usize, &[u8])>, _store_diff: Option<(U256, U256)>) {} diff --git a/ethcore/src/evm/interpreter/mod.rs b/ethcore/src/evm/interpreter/mod.rs index c0a151f49..fe3f34013 100644 --- a/ethcore/src/evm/interpreter/mod.rs +++ b/ethcore/src/evm/interpreter/mod.rs @@ -129,7 +129,7 @@ impl evm::Evm for Interpreter { // Calculate gas cost let requirements = gasometer.requirements(ext, instruction, info, &stack, self.mem.size())?; // TODO: make compile-time removable if too much of a performance hit. - let trace_executed = ext.trace_prepare_execute(reader.position - 1, instruction, &requirements.gas_cost.as_u256()); + let trace_executed = ext.trace_prepare_execute(reader.position - 1, instruction, info.args, &requirements.gas_cost.as_u256()); gasometer.verify_gas(&requirements.gas_cost)?; self.mem.expand(requirements.memory_required_size); diff --git a/ethcore/src/externalities.rs b/ethcore/src/externalities.rs index 0b849033e..d351f3cc4 100644 --- a/ethcore/src/externalities.rs +++ b/ethcore/src/externalities.rs @@ -333,8 +333,8 @@ impl<'a, T: 'a, V: 'a, B: 'a> Ext for Externalities<'a, T, V, B> self.substate.sstore_clears_count = self.substate.sstore_clears_count + U256::one(); } - fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, gas_cost: &U256) -> bool { - self.vm_tracer.trace_prepare_execute(pc, instruction, gas_cost) + fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, stack_pop: usize, gas_cost: &U256) -> bool { + self.vm_tracer.trace_prepare_execute(pc, instruction, stack_pop, gas_cost) } fn trace_executed(&mut self, gas_used: U256, stack_push: &[U256], mem_diff: Option<(usize, &[u8])>, store_diff: Option<(U256, U256)>) { diff --git a/ethcore/src/trace/executive_tracer.rs b/ethcore/src/trace/executive_tracer.rs index db92b0554..6cca0020c 100644 --- a/ethcore/src/trace/executive_tracer.rs +++ b/ethcore/src/trace/executive_tracer.rs @@ -192,7 +192,7 @@ impl ExecutiveVMTracer { } impl VMTracer for ExecutiveVMTracer { - fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, gas_cost: &U256) -> bool { + fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, _stack_pop: usize, gas_cost: &U256) -> bool { self.data.operations.push(VMOperation { pc: pc, instruction: instruction, diff --git a/ethcore/src/trace/mod.rs b/ethcore/src/trace/mod.rs index 2d13e3260..6cb9e47e9 100644 --- a/ethcore/src/trace/mod.rs +++ b/ethcore/src/trace/mod.rs @@ -89,7 +89,7 @@ pub trait Tracer: Send { pub trait VMTracer: Send { /// Trace the preparation to execute a single instruction. /// @returns true if `trace_executed` should be called. - fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: &U256) -> bool { false } + fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _stack_pop: usize, _gas_cost: &U256) -> bool { false } /// Trace the finalised execution of a single instruction. fn trace_executed(&mut self, _gas_used: U256, _stack_push: &[U256], _mem_diff: Option<(usize, &[u8])>, _store_diff: Option<(U256, U256)>) {} @@ -97,7 +97,7 @@ pub trait VMTracer: Send { /// Spawn subtracer which will be used to trace deeper levels of execution. fn prepare_subtrace(&self, code: &[u8]) -> Self where Self: Sized; - /// Spawn subtracer which will be used to trace deeper levels of execution. + /// Finalize subtracer. fn done_subtrace(&mut self, sub: Self) where Self: Sized; /// Consumes self and returns the VM trace. diff --git a/ethcore/src/trace/noop_tracer.rs b/ethcore/src/trace/noop_tracer.rs index d5d04696d..d69ed9a16 100644 --- a/ethcore/src/trace/noop_tracer.rs +++ b/ethcore/src/trace/noop_tracer.rs @@ -72,7 +72,7 @@ pub struct NoopVMTracer; impl VMTracer for NoopVMTracer { /// Trace the preparation to execute a single instruction. - fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: &U256) -> bool { false } + fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _stack_pop: usize, _gas_cost: &U256) -> bool { false } /// Trace the finalised execution of a single instruction. fn trace_executed(&mut self, _gas_used: U256, _stack_push: &[U256], _mem_diff: Option<(usize, &[u8])>, _store_diff: Option<(U256, U256)>) {} diff --git a/evmbin/src/display/json.rs b/evmbin/src/display/json.rs new file mode 100644 index 000000000..685579521 --- /dev/null +++ b/evmbin/src/display/json.rs @@ -0,0 +1,116 @@ +// 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 . + +//! JSON VM output. + +use ethcore::trace; +use std::collections::HashMap; +use util::{U256, H256, ToPretty}; + +use display; +use vm; + +/// JSON formatting informant. +#[derive(Default)] +pub struct Informant { + depth: usize, + pc: usize, + instruction: u8, + gas_cost: U256, + stack: Vec, + memory: Vec, + storage: HashMap, +} + +impl Informant { + fn memory(&self) -> String { + format!("\"0x{}\"", self.memory.to_hex()) + } + + fn stack(&self) -> String { + let items = self.stack.iter().map(display::u256_as_str).collect::>(); + format!("[{}]", items.join(",")) + } + + fn storage(&self) -> String { + let vals = self.storage.iter() + .map(|(k, v)| format!("\"0x{:?}\": \"0x{:?}\"", k, v)) + .collect::>(); + format!("{{{}}}", vals.join(",")) + } +} + +impl vm::Informant for Informant { + fn finish(&mut self, result: Result) { + match result { + Ok(success) => println!( + "{{\"output\":\"0x{output}\",\"gasUsed\":\"{gas:x}\",\"time\":\"{time}\"}}", + output = success.output.to_hex(), + gas = success.gas_used, + time = display::format_time(&success.time), + ), + Err(failure) => println!( + "{{\"error\":\"{error}\",\"time\":\"{time}\"}}", + error = failure.error, + time = display::format_time(&failure.time), + ), + } + } +} + +impl trace::VMTracer for Informant { + fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, stack_pop: usize, gas_cost: &U256) -> bool { + self.pc = pc; + self.instruction = instruction; + self.gas_cost = *gas_cost; + let len = self.stack.len(); + self.stack.truncate(len - stack_pop); + true + } + + fn trace_executed(&mut self, gas_used: U256, stack_push: &[U256], mem_diff: Option<(usize, &[u8])>, store_diff: Option<(U256, U256)>) { + self.stack.extend_from_slice(stack_push); + + if let Some((pos, data)) = mem_diff { + self.memory[pos..pos + data.len()].copy_from_slice(data); + } + + if let Some((pos, val)) = store_diff { + self.storage.insert(pos.into(), val.into()); + } + + println!( + "{{\"pc\":{pc},\"op\":{op},\"gas\":{gas},\"gasCost\":{gas_cost},\"memory\":{memory},\"stack\":{stack},\"storage\":{storage},\"depth\":{depth}}}", + pc = self.pc, + op = self.instruction, + gas = display::u256_as_str(&gas_used), + gas_cost = display::u256_as_str(&self.gas_cost), + memory = self.memory(), + stack = self.stack(), + storage = self.storage(), + depth = self.depth, + ); + } + + fn prepare_subtrace(&self, _code: &[u8]) -> Self where Self: Sized { + let mut vm = Informant::default(); + vm.depth = self.depth + 1; + vm + } + + fn done_subtrace(&mut self, _sub: Self) where Self: Sized {} + fn drain(self) -> Option { None } +} diff --git a/evmbin/src/display/mod.rs b/evmbin/src/display/mod.rs new file mode 100644 index 000000000..2d6179859 --- /dev/null +++ b/evmbin/src/display/mod.rs @@ -0,0 +1,38 @@ +// 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 . + +//! VM Output display utils. + +use std::time::Duration; +use util::U256; + +pub mod json; +pub mod simple; + +/// Formats duration into human readable format. +pub fn format_time(time: &Duration) -> String { + format!("{}.{:.9}s", time.as_secs(), time.subsec_nanos()) +} + +/// Converts U256 into string. +/// TODO Overcomes: https://github.com/paritytech/bigint/issues/13 +pub fn u256_as_str(v: &U256) -> String { + if v.is_zero() { + "\"0x0\"".into() + } else { + format!("\"{:x}\"", v) + } +} diff --git a/evmbin/src/display/simple.rs b/evmbin/src/display/simple.rs new file mode 100644 index 000000000..5dcefd92c --- /dev/null +++ b/evmbin/src/display/simple.rs @@ -0,0 +1,49 @@ +// 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 . + +//! Simple VM output. + +use ethcore::trace; +use util::ToPretty; + +use display; +use vm; + +/// Simple formatting informant. +#[derive(Default)] +pub struct Informant; + +impl vm::Informant for Informant { + fn finish(&mut self, result: Result) { + match result { + Ok(success) => { + println!("Output: 0x{}", success.output.to_hex()); + println!("Gas used: {:x}", success.gas_used); + println!("Time: {}", display::format_time(&success.time)); + }, + Err(failure) => { + println!("Error: {}", failure.error); + println!("Time: {}", display::format_time(&failure.time)); + }, + } + } +} + +impl trace::VMTracer for Informant { + fn prepare_subtrace(&self, _code: &[u8]) -> Self where Self: Sized { Default::default() } + fn done_subtrace(&mut self, _sub: Self) where Self: Sized {} + fn drain(self) -> Option { None } +} diff --git a/evmbin/src/ext.rs b/evmbin/src/ext.rs deleted file mode 100644 index 781120c36..000000000 --- a/evmbin/src/ext.rs +++ /dev/null @@ -1,123 +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 . - -//! Externalities implementation. - -use std::sync::Arc; -use std::collections::HashMap; -use util::{U256, H256, Address, Bytes, trie}; -use ethcore::client::EnvInfo; -use ethcore::evm::{self, Ext, ContractCreateResult, MessageCallResult, Schedule, CallType, CreateContractAddress}; - -pub struct FakeExt { - schedule: Schedule, - store: HashMap, - depth: usize, -} - -impl Default for FakeExt { - fn default() -> Self { - FakeExt { - schedule: Schedule::new_post_eip150(usize::max_value(), true, true, true, true), - store: HashMap::new(), - depth: 1, - } - } -} - -impl Ext for FakeExt { - fn storage_at(&self, key: &H256) -> trie::Result { - Ok(self.store.get(key).unwrap_or(&H256::new()).clone()) - } - - fn set_storage(&mut self, key: H256, value: H256) -> trie::Result<()> { - self.store.insert(key, value); - Ok(()) - } - - fn exists(&self, _address: &Address) -> trie::Result { - unimplemented!(); - } - - fn exists_and_not_null(&self, _address: &Address) -> trie::Result { - unimplemented!(); - } - - fn origin_balance(&self) -> trie::Result { - unimplemented!(); - } - - fn balance(&self, _address: &Address) -> trie::Result { - unimplemented!(); - } - - fn blockhash(&self, _number: &U256) -> H256 { - unimplemented!(); - } - - fn create(&mut self, _gas: &U256, _value: &U256, _code: &[u8], _address: CreateContractAddress) -> ContractCreateResult { - unimplemented!(); - } - - fn call(&mut self, - _gas: &U256, - _sender_address: &Address, - _receive_address: &Address, - _value: Option, - _data: &[u8], - _code_address: &Address, - _output: &mut [u8], - _call_type: CallType) -> MessageCallResult { - unimplemented!(); - } - - fn extcode(&self, _address: &Address) -> trie::Result> { - unimplemented!(); - } - - fn extcodesize(&self, _address: &Address) -> trie::Result { - unimplemented!(); - } - - fn log(&mut self, _topics: Vec, _data: &[u8]) { - unimplemented!(); - } - - fn ret(self, gas: &U256, _data: &[u8]) -> evm::Result { - Ok(*gas) - } - - fn suicide(&mut self, _refund_address: &Address) -> trie::Result<()> { - unimplemented!(); - } - - fn schedule(&self) -> &Schedule { - &self.schedule - } - - fn env_info(&self) -> &EnvInfo { - unimplemented!() - } - - fn depth(&self) -> usize { - self.depth - } - - fn inc_sstore_clears(&mut self) { - unimplemented!(); - // self.sstore_clears += 1; - } -} diff --git a/evmbin/src/main.rs b/evmbin/src/main.rs index 505eae86e..6f50249e0 100644 --- a/evmbin/src/main.rs +++ b/evmbin/src/main.rs @@ -23,31 +23,36 @@ extern crate rustc_serialize; extern crate docopt; extern crate ethcore_util as util; -mod ext; - use std::sync::Arc; -use std::time::{Instant, Duration}; -use std::fmt; -use std::str::FromStr; +use std::{fmt, fs}; use docopt::Docopt; -use util::{U256, FromHex, Bytes}; -use ethcore::evm::{self, Factory, VMType, Finalize}; +use util::{U256, FromHex, Bytes, Address}; +use ethcore::spec; use ethcore::action_params::ActionParams; +mod vm; +mod display; + +use vm::Informant; + const USAGE: &'static str = r#" EVM implementation for Parity. Copyright 2016, 2017 Parity Technologies (UK) Ltd Usage: evmbin stats [options] + evmbin [options] evmbin [-h | --help] Transaction options: - --code CODE Contract code as hex (without 0x) - --input DATA Input data as hex (without 0x) - --gas GAS Supplied gas as hex (without 0x) + --code CODE Contract code as hex (without 0x). + --from ADDRESS Sender address (without 0x). + --input DATA Input data as hex (without 0x). + --gas GAS Supplied gas as hex (without 0x). General options: + --json Display verbose results in JSON. + --chain CHAIN Chain spec file path. -h, --help Display this message and exit. "#; @@ -55,107 +60,93 @@ General options: fn main() { let args: Args = Docopt::new(USAGE).and_then(|d| d.decode()).unwrap_or_else(|e| e.exit()); + if args.flag_json { + run(args, display::json::Informant::default()) + } else { + run(args, display::simple::Informant::default()) + } +} + +fn run(args: Args, mut informant: T) { + let from = arg(args.from(), "--from"); + let code = arg(args.code(), "--code"); + let spec = arg(args.spec(), "--chain"); + let gas = arg(args.gas(), "--gas"); + let data = arg(args.data(), "--input"); + let mut params = ActionParams::default(); - params.gas = args.gas(); - params.code = Some(Arc::new(args.code())); - params.data = args.data(); + params.sender = from; + params.origin = from; + params.gas = gas; + params.code = Some(Arc::new(code)); + params.data = data; - let result = run_vm(params); - match result { - Ok(success) => println!("{}", success), - Err(failure) => println!("{}", failure), - } -} - -/// Execute VM with given `ActionParams` -pub fn run_vm(params: ActionParams) -> Result { - let initial_gas = params.gas; - let factory = Factory::new(VMType::Interpreter, 1024); - let mut vm = factory.create(params.gas); - let mut ext = ext::FakeExt::default(); - - let start = Instant::now(); - let res = vm.exec(params, &mut ext).finalize(ext); - let duration = start.elapsed(); - - match res { - Ok(res) => Ok(Success { - gas_used: initial_gas - res.gas_left, - // TODO [ToDr] get output from ext - output: Vec::new(), - time: duration, - }), - Err(e) => Err(Failure { - error: e, - time: duration, - }), - } -} - -/// Execution finished correctly -pub struct Success { - /// Used gas - gas_used: U256, - /// Output as bytes - output: Vec, - /// Time Taken - time: Duration, -} -impl fmt::Display for Success { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - writeln!(f, "Gas used: {:?}", self.gas_used)?; - writeln!(f, "Output: {:?}", self.output)?; - writeln!(f, "Time: {}.{:.9}s", self.time.as_secs(), self.time.subsec_nanos())?; - Ok(()) - } -} - -/// Execution failed -pub struct Failure { - /// Internal error - error: evm::Error, - /// Duration - time: Duration, -} -impl fmt::Display for Failure { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - writeln!(f, "Error: {:?}", self.error)?; - writeln!(f, "Time: {}.{:.9}s", self.time.as_secs(), self.time.subsec_nanos())?; - Ok(()) - } + let result = vm::run(&mut informant, spec, params); + informant.finish(result); } #[derive(Debug, RustcDecodable)] struct Args { cmd_stats: bool, + flag_from: Option, flag_code: Option, flag_gas: Option, flag_input: Option, + flag_spec: Option, + flag_json: bool, } impl Args { - pub fn gas(&self) -> U256 { - self.flag_gas - .clone() - .and_then(|g| U256::from_str(&g).ok()) - .unwrap_or_else(|| !U256::zero()) + pub fn gas(&self) -> Result { + match self.flag_gas { + Some(ref gas) => gas.parse().map_err(to_string), + None => Ok(!U256::zero()), + } } - pub fn code(&self) -> Bytes { - self.flag_code - .clone() - .and_then(|c| c.from_hex().ok()) - .unwrap_or_else(|| die("Code is required.")) + pub fn from(&self) -> Result { + match self.flag_from { + Some(ref from) => from.parse().map_err(to_string), + None => Ok(Address::default()), + } } - pub fn data(&self) -> Option { - self.flag_input - .clone() - .and_then(|d| d.from_hex().ok()) + pub fn code(&self) -> Result { + match self.flag_code { + Some(ref code) => code.from_hex().map_err(to_string), + None => Err("Code is required!".into()), + } + } + + pub fn data(&self) -> Result, String> { + match self.flag_input { + Some(ref input) => input.from_hex().map_err(to_string).map(Some), + None => Ok(None), + } + } + + pub fn spec(&self) -> Result { + Ok(match self.flag_spec { + Some(ref filename) => { + let file = fs::File::open(filename).map_err(|e| format!("{}", e))?; + spec::Spec::load(file)? + }, + None => { + spec::Spec::new_instant() + }, + }) } } -fn die(msg: &'static str) -> ! { +fn arg(v: Result, param: &str) -> T { + v.unwrap_or_else(|e| die(format!("Invalid {}: {}", param, e))) +} + +fn to_string(msg: T) -> String { + format!("{}", msg) +} + +fn die(msg: T) -> ! { println!("{}", msg); ::std::process::exit(-1) } diff --git a/evmbin/src/vm.rs b/evmbin/src/vm.rs new file mode 100644 index 000000000..6e45c5dc8 --- /dev/null +++ b/evmbin/src/vm.rs @@ -0,0 +1,72 @@ +// 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 . + +//! VM runner. + +use std::time::{Instant, Duration}; +use util::U256; +use ethcore::{trace, spec}; +use ethcore::client::{EvmTestClient, EvmTestError}; +use ethcore::action_params::ActionParams; + +/// VM execution informant +pub trait Informant: trace::VMTracer { + /// Display final result. + fn finish(&mut self, result: Result); +} + +/// Execution finished correctly +pub struct Success { + /// Used gas + pub gas_used: U256, + /// Output as bytes + pub output: Vec, + /// Time Taken + pub time: Duration, +} + +/// Execution failed +pub struct Failure { + /// Internal error + pub error: EvmTestError, + /// Duration + pub time: Duration, +} + +/// Execute VM with given `ActionParams` +pub fn run(vm_tracer: &mut T, spec: spec::Spec, params: ActionParams) -> Result { + let mut test_client = EvmTestClient::new(spec).map_err(|error| Failure { + error, + time: Duration::from_secs(0) + })?; + + let initial_gas = params.gas; + let start = Instant::now(); + let result = test_client.call(params, vm_tracer); + let duration = start.elapsed(); + + match result { + Ok((gas_left, output)) => Ok(Success { + gas_used: initial_gas - gas_left, + output: output, + time: duration, + }), + Err(e) => Err(Failure { + error: e, + time: duration, + }), + } +}