diff --git a/ethcore/evm/src/interpreter/mod.rs b/ethcore/evm/src/interpreter/mod.rs index d6b3ff792..32d9dbee5 100644 --- a/ethcore/evm/src/interpreter/mod.rs +++ b/ethcore/evm/src/interpreter/mod.rs @@ -132,7 +132,9 @@ impl vm::Vm for Interpreter { reader.position += 1; // TODO: make compile-time removable if too much of a performance hit. - do_trace = do_trace && ext.trace_next_instruction(reader.position - 1, instruction); + do_trace = do_trace && ext.trace_next_instruction( + reader.position - 1, instruction, gasometer.current_gas.as_u256(), + ); let info = &infos[instruction as usize]; self.verify_instruction(ext, instruction, info, &stack)?; diff --git a/ethcore/src/externalities.rs b/ethcore/src/externalities.rs index 64d902b53..05c295e06 100644 --- a/ethcore/src/externalities.rs +++ b/ethcore/src/externalities.rs @@ -396,8 +396,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_next_instruction(&mut self, pc: usize, instruction: u8) -> bool { - self.vm_tracer.trace_next_instruction(pc, instruction) + fn trace_next_instruction(&mut self, pc: usize, instruction: u8, current_gas: U256) -> bool { + self.vm_tracer.trace_next_instruction(pc, instruction, current_gas) } fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, gas_cost: U256) { diff --git a/ethcore/src/trace/executive_tracer.rs b/ethcore/src/trace/executive_tracer.rs index 70ed981d7..b1d116d69 100644 --- a/ethcore/src/trace/executive_tracer.rs +++ b/ethcore/src/trace/executive_tracer.rs @@ -204,7 +204,7 @@ impl ExecutiveVMTracer { impl VMTracer for ExecutiveVMTracer { type Output = VMTrace; - fn trace_next_instruction(&mut self, _pc: usize, _instruction: u8) -> bool { true } + fn trace_next_instruction(&mut self, _pc: usize, _instruction: u8, _current_gas: U256) -> bool { true } fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, gas_cost: U256) { self.data.operations.push(VMOperation { diff --git a/ethcore/src/trace/mod.rs b/ethcore/src/trace/mod.rs index 4ef4bab7a..271bdf818 100644 --- a/ethcore/src/trace/mod.rs +++ b/ethcore/src/trace/mod.rs @@ -104,7 +104,7 @@ pub trait VMTracer: Send { /// Trace the progression of interpreter to next instruction. /// If tracer returns `false` it won't be called again. /// @returns true if `trace_prepare_execute` and `trace_executed` should be called. - fn trace_next_instruction(&mut self, _pc: usize, _instruction: u8) -> bool { false } + fn trace_next_instruction(&mut self, _pc: usize, _instruction: u8, _current_gas: U256) -> bool { false } /// Trace the preparation to execute a single valid instruction. fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: U256) {} diff --git a/ethcore/src/trace/noop_tracer.rs b/ethcore/src/trace/noop_tracer.rs index fa9e4ae41..ab0bf77ff 100644 --- a/ethcore/src/trace/noop_tracer.rs +++ b/ethcore/src/trace/noop_tracer.rs @@ -79,7 +79,7 @@ pub struct NoopVMTracer; impl VMTracer for NoopVMTracer { type Output = VMTrace; - fn trace_next_instruction(&mut self, _pc: usize, _instruction: u8) -> bool { false } + fn trace_next_instruction(&mut self, _pc: usize, _instruction: u8, _current_gas: U256) -> bool { false } fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: U256) {} diff --git a/ethcore/vm/src/ext.rs b/ethcore/vm/src/ext.rs index c1e12e600..98661e47e 100644 --- a/ethcore/vm/src/ext.rs +++ b/ethcore/vm/src/ext.rs @@ -138,7 +138,7 @@ pub trait Ext { fn inc_sstore_clears(&mut self); /// Decide if any more operations should be traced. Passthrough for the VM trace. - fn trace_next_instruction(&mut self, _pc: usize, _instruction: u8) -> bool { false } + fn trace_next_instruction(&mut self, _pc: usize, _instruction: u8, _current_gas: U256) -> bool { false } /// Prepare to trace an operation. Passthrough for the VM trace. fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: U256) {} diff --git a/ethcore/vm/src/tests.rs b/ethcore/vm/src/tests.rs index ef7ad2015..f141a0f96 100644 --- a/ethcore/vm/src/tests.rs +++ b/ethcore/vm/src/tests.rs @@ -198,7 +198,7 @@ impl Ext for FakeExt { self.sstore_clears += 1; } - fn trace_next_instruction(&mut self, _pc: usize, _instruction: u8) -> bool { + fn trace_next_instruction(&mut self, _pc: usize, _instruction: u8, _gas: U256) -> bool { self.tracing } } diff --git a/evmbin/src/display/json.rs b/evmbin/src/display/json.rs index fa5f4ffe0..fbfb46952 100644 --- a/evmbin/src/display/json.rs +++ b/evmbin/src/display/json.rs @@ -62,7 +62,7 @@ impl Informant { } impl vm::Informant for Informant { - fn before_test(&self, name: &str, action: &str) { + fn before_test(&mut self, name: &str, action: &str) { println!( "{{\"test\":\"{name}\",\"action\":\"{action}\"}}", name = name, @@ -107,7 +107,7 @@ impl vm::Informant for Informant { impl trace::VMTracer for Informant { type Output = Vec; - fn trace_next_instruction(&mut self, pc: usize, instruction: u8) -> bool { + fn trace_next_instruction(&mut self, pc: usize, instruction: u8, _current_gas: U256) -> bool { self.pc = pc; self.instruction = instruction; self.unmatched = true; @@ -144,6 +144,7 @@ impl trace::VMTracer for Informant { self.stack.truncate(if len > info.args { len - info.args } else { 0 }); self.stack.extend_from_slice(stack_push); + // TODO [ToDr] Align memory? if let Some((pos, data)) = mem_diff { if self.memory.len() < (pos + data.len()) { self.memory.resize(pos + data.len(), 0); @@ -187,3 +188,99 @@ impl trace::VMTracer for Informant { Some(self.traces) } } + +#[cfg(test)] +mod tests { + use super::*; + use info::tests::run_test; + + fn assert_traces_eq( + a: &[String], + b: &[String], + ) { + let mut ita = a.iter(); + let mut itb = b.iter(); + + loop { + match (ita.next(), itb.next()) { + (Some(a), Some(b)) => { + assert_eq!(a, b); + println!("{}", a); + }, + (None, None) => return, + e => { + panic!("Traces mismatch: {:?}", e); + } + } + } + } + + fn compare_json(traces: Option>, expected: &str) { + let expected = expected.split("\n") + .map(|x| x.trim()) + .map(|x| x.to_owned()) + .filter(|x| !x.is_empty()) + .collect::>(); + assert_traces_eq(&traces.unwrap(), &expected); + } + + #[test] + fn should_trace_failure() { + run_test( + Informant::default(), + &compare_json, + "60F8d6", + 0xffff, + r#" +{"pc":0,"op":96,"opName":"PUSH1","gas":"0xffff","gasCost":"0x3","memory":"0x","stack":[],"storage":{},"depth":1} +{"pc":2,"op":214,"opName":"","gas":"0xfffc","gasCost":"0x0","memory":"0x","stack":["0xf8"],"storage":{},"depth":1} + "#, + ); + + run_test( + Informant::default(), + &compare_json, + "F8d6", + 0xffff, + r#" +{"pc":0,"op":248,"opName":"","gas":"0xffff","gasCost":"0x0","memory":"0x","stack":[],"storage":{},"depth":1} + "#, + ); + } + + #[test] + fn should_trace_create_correctly() { + run_test( + Informant::default(), + &compare_json, + "32343434345830f138343438323439f0", + 0xffff, + r#" +{"pc":0,"op":50,"opName":"ORIGIN","gas":"0xffff","gasCost":"0x2","memory":"0x","stack":[],"storage":{},"depth":1} +{"pc":1,"op":52,"opName":"CALLVALUE","gas":"0xfffd","gasCost":"0x2","memory":"0x","stack":["0x0"],"storage":{},"depth":1} +{"pc":2,"op":52,"opName":"CALLVALUE","gas":"0xfffb","gasCost":"0x2","memory":"0x","stack":["0x0","0x0"],"storage":{},"depth":1} +{"pc":3,"op":52,"opName":"CALLVALUE","gas":"0xfff9","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0"],"storage":{},"depth":1} +{"pc":4,"op":52,"opName":"CALLVALUE","gas":"0xfff7","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0","0x0"],"storage":{},"depth":1} +{"pc":5,"op":88,"opName":"PC","gas":"0xfff5","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0","0x0","0x0"],"storage":{},"depth":1} +{"pc":6,"op":48,"opName":"ADDRESS","gas":"0xfff3","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0","0x0","0x0","0x5"],"storage":{},"depth":1} +{"pc":7,"op":241,"opName":"CALL","gas":"0xfff1","gasCost":"0x61d0","memory":"0x","stack":["0x0","0x0","0x0","0x0","0x0","0x5","0x0"],"storage":{},"depth":1} +{"pc":8,"op":56,"opName":"CODESIZE","gas":"0x9e21","gasCost":"0x2","memory":"0x","stack":["0x1"],"storage":{},"depth":1} +{"pc":9,"op":52,"opName":"CALLVALUE","gas":"0x9e1f","gasCost":"0x2","memory":"0x","stack":["0x1","0x10"],"storage":{},"depth":1} +{"pc":10,"op":52,"opName":"CALLVALUE","gas":"0x9e1d","gasCost":"0x2","memory":"0x","stack":["0x1","0x10","0x0"],"storage":{},"depth":1} +{"pc":11,"op":56,"opName":"CODESIZE","gas":"0x9e1b","gasCost":"0x2","memory":"0x","stack":["0x1","0x10","0x0","0x0"],"storage":{},"depth":1} +{"pc":12,"op":50,"opName":"ORIGIN","gas":"0x9e19","gasCost":"0x2","memory":"0x","stack":["0x1","0x10","0x0","0x0","0x10"],"storage":{},"depth":1} +{"pc":13,"op":52,"opName":"CALLVALUE","gas":"0x9e17","gasCost":"0x2","memory":"0x","stack":["0x1","0x10","0x0","0x0","0x10","0x0"],"storage":{},"depth":1} +{"pc":14,"op":57,"opName":"CODECOPY","gas":"0x9e15","gasCost":"0x9","memory":"0x","stack":["0x1","0x10","0x0","0x0","0x10","0x0","0x0"],"storage":{},"depth":1} +{"pc":15,"op":240,"opName":"CREATE","gas":"0x9e0c","gasCost":"0x9e0c","memory":"0x32343434345830f138343438323439f0","stack":["0x1","0x10","0x0","0x0"],"storage":{},"depth":1} +{"pc":0,"op":50,"opName":"ORIGIN","gas":"0x210c","gasCost":"0x2","memory":"0x","stack":[],"storage":{},"depth":2} +{"pc":1,"op":52,"opName":"CALLVALUE","gas":"0x210a","gasCost":"0x2","memory":"0x","stack":["0x0"],"storage":{},"depth":2} +{"pc":2,"op":52,"opName":"CALLVALUE","gas":"0x2108","gasCost":"0x2","memory":"0x","stack":["0x0","0x0"],"storage":{},"depth":2} +{"pc":3,"op":52,"opName":"CALLVALUE","gas":"0x2106","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0"],"storage":{},"depth":2} +{"pc":4,"op":52,"opName":"CALLVALUE","gas":"0x2104","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0","0x0"],"storage":{},"depth":2} +{"pc":5,"op":88,"opName":"PC","gas":"0x2102","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0","0x0","0x0"],"storage":{},"depth":2} +{"pc":6,"op":48,"opName":"ADDRESS","gas":"0x2100","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0","0x0","0x0","0x5"],"storage":{},"depth":2} +{"pc":7,"op":241,"opName":"CALL","gas":"0x20fe","gasCost":"0x0","memory":"0x","stack":["0x0","0x0","0x0","0x0","0x0","0x5","0xbd770416a3345f91e4b34576cb804a576fa48eb1"],"storage":{},"depth":2} + "#, + ) + } +} diff --git a/evmbin/src/display/mod.rs b/evmbin/src/display/mod.rs index 633e9e2e1..f5a18aa94 100644 --- a/evmbin/src/display/mod.rs +++ b/evmbin/src/display/mod.rs @@ -20,6 +20,7 @@ use std::time::Duration; use ethereum_types::U256; pub mod json; +pub mod std_json; pub mod simple; /// Formats duration into human readable format. diff --git a/evmbin/src/display/simple.rs b/evmbin/src/display/simple.rs index 6e9959a8e..30bb8ffcf 100644 --- a/evmbin/src/display/simple.rs +++ b/evmbin/src/display/simple.rs @@ -27,7 +27,7 @@ use info as vm; pub struct Informant; impl vm::Informant for Informant { - fn before_test(&self, name: &str, action: &str) { + fn before_test(&mut self, name: &str, action: &str) { println!("Test: {} ({})", name, action); } diff --git a/evmbin/src/display/std_json.rs b/evmbin/src/display/std_json.rs new file mode 100644 index 000000000..fe34c29a4 --- /dev/null +++ b/evmbin/src/display/std_json.rs @@ -0,0 +1,265 @@ +// 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 . + +//! Standardized JSON VM output. + +use std::collections::HashMap; +use std::io; + +use ethereum_types::{H256, U256}; +use bytes::ToPretty; +use ethcore::trace; + +use display; +use info as vm; + +pub trait Writer: io::Write + Send + Sized { + fn clone(&self) -> Self; +} + +impl Writer for io::Stdout { + fn clone(&self) -> Self { + io::stdout() + } +} + +/// JSON formatting informant. +pub struct Informant { + code: Vec, + instruction: u8, + depth: usize, + stack: Vec, + storage: HashMap, + sink: T, +} + +impl Default for Informant { + fn default() -> Self { + Self::new(io::stdout()) + } +} + +impl Informant { + pub fn new(sink: T) -> Self { + Informant { + code: Default::default(), + instruction: Default::default(), + depth: Default::default(), + stack: Default::default(), + storage: Default::default(), + sink, + } + } +} + +impl Informant { + 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 before_test(&mut self, name: &str, action: &str) { + writeln!( + &mut self.sink, + "{{\"test\":\"{name}\",\"action\":\"{action}\"}}", + name = name, + action = action, + ).expect("The sink must be writeable."); + } + + fn set_gas(&mut self, _gas: U256) {} + + fn finish(result: vm::RunResult) { + match result { + Ok(success) => { + println!("{{\"stateRoot\":\"{:?}\"}}", success.state_root); + println!( + "{{\"output\":\"0x{output}\",\"gasUsed\":\"{gas:x}\",\"time\":{time}}}", + output = success.output.to_hex(), + gas = success.gas_used, + time = display::as_micros(&success.time), + ); + }, + Err(failure) => { + println!( + "{{\"error\":\"{error}\",\"gasUsed\":\"{gas:x}\",\"time\":{time}}}", + error = failure.error, + gas = failure.gas_used, + time = display::as_micros(&failure.time), + ) + }, + } + } +} + +impl trace::VMTracer for Informant { + type Output = (); + + fn trace_next_instruction(&mut self, pc: usize, instruction: u8, current_gas: U256) -> bool { + let info = ::evm::INSTRUCTIONS[instruction as usize]; + self.instruction = instruction; + let storage = self.storage(); + let stack = self.stack(); + + writeln!( + &mut self.sink, + "{{\"pc\":{pc},\"op\":{op},\"opName\":\"{name}\",\"gas\":{gas},\"stack\":{stack},\"storage\":{storage},\"depth\":{depth}}}", + pc = pc, + op = instruction, + name = info.name, + gas = display::u256_as_str(¤t_gas), + stack = stack, + storage = storage, + depth = self.depth, + ).expect("The sink must be writeable."); + + true + } + + fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: U256) { + } + + fn trace_executed(&mut self, _gas_used: U256, stack_push: &[U256], _mem_diff: Option<(usize, &[u8])>, store_diff: Option<(U256, U256)>) { + let info = ::evm::INSTRUCTIONS[self.instruction as usize]; + + let len = self.stack.len(); + self.stack.truncate(if len > info.args { len - info.args } else { 0 }); + self.stack.extend_from_slice(stack_push); + + if let Some((pos, val)) = store_diff { + self.storage.insert(pos.into(), val.into()); + } + } + + fn prepare_subtrace(&self, code: &[u8]) -> Self where Self: Sized { + let mut vm = Informant::new(self.sink.clone()); + vm.depth = self.depth + 1; + vm.code = code.to_vec(); + vm + } + + fn done_subtrace(&mut self, _sub: Self) {} + + fn drain(self) -> Option { None } +} + +#[cfg(test)] +mod tests { + use std::sync::{Arc, Mutex}; + use super::*; + use info::tests::run_test; + + #[derive(Debug, Clone, Default)] + struct TestWriter(pub Arc>>); + + impl Writer for TestWriter { + fn clone(&self) -> Self { Clone::clone(self) } + } + + impl io::Write for TestWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.lock().unwrap().write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.0.lock().unwrap().flush() + } + } + + fn informant() -> (Informant, Arc>>) { + let writer = TestWriter::default(); + let res = writer.0.clone(); + (Informant::new(writer), res) + } + + #[test] + fn should_trace_failure() { + let (inf, res) = informant(); + run_test( + inf, + move |_, expected| { + let bytes = res.lock().unwrap(); + assert_eq!(expected, &String::from_utf8_lossy(&**bytes)) + }, + "60F8d6", + 0xffff, + r#"{"pc":0,"op":96,"opName":"PUSH1","gas":"0xffff","stack":[],"storage":{},"depth":1} +{"pc":2,"op":214,"opName":"","gas":"0xfffc","stack":["0xf8"],"storage":{},"depth":1} +"#, + ); + + let (inf, res) = informant(); + run_test( + inf, + move |_, expected| { + let bytes = res.lock().unwrap(); + assert_eq!(expected, &String::from_utf8_lossy(&**bytes)) + }, + "F8d6", + 0xffff, + r#"{"pc":0,"op":248,"opName":"","gas":"0xffff","stack":[],"storage":{},"depth":1} +"#, + ); + } + + #[test] + fn should_trace_create_correctly() { + let (informant, res) = informant(); + run_test( + informant, + move |_, expected| { + let bytes = res.lock().unwrap(); + assert_eq!(expected, &String::from_utf8_lossy(&**bytes)) + }, + "32343434345830f138343438323439f0", + 0xffff, + r#"{"pc":0,"op":50,"opName":"ORIGIN","gas":"0xffff","stack":[],"storage":{},"depth":1} +{"pc":1,"op":52,"opName":"CALLVALUE","gas":"0xfffd","stack":["0x0"],"storage":{},"depth":1} +{"pc":2,"op":52,"opName":"CALLVALUE","gas":"0xfffb","stack":["0x0","0x0"],"storage":{},"depth":1} +{"pc":3,"op":52,"opName":"CALLVALUE","gas":"0xfff9","stack":["0x0","0x0","0x0"],"storage":{},"depth":1} +{"pc":4,"op":52,"opName":"CALLVALUE","gas":"0xfff7","stack":["0x0","0x0","0x0","0x0"],"storage":{},"depth":1} +{"pc":5,"op":88,"opName":"PC","gas":"0xfff5","stack":["0x0","0x0","0x0","0x0","0x0"],"storage":{},"depth":1} +{"pc":6,"op":48,"opName":"ADDRESS","gas":"0xfff3","stack":["0x0","0x0","0x0","0x0","0x0","0x5"],"storage":{},"depth":1} +{"pc":7,"op":241,"opName":"CALL","gas":"0xfff1","stack":["0x0","0x0","0x0","0x0","0x0","0x5","0x0"],"storage":{},"depth":1} +{"pc":8,"op":56,"opName":"CODESIZE","gas":"0x9e21","stack":["0x1"],"storage":{},"depth":1} +{"pc":9,"op":52,"opName":"CALLVALUE","gas":"0x9e1f","stack":["0x1","0x10"],"storage":{},"depth":1} +{"pc":10,"op":52,"opName":"CALLVALUE","gas":"0x9e1d","stack":["0x1","0x10","0x0"],"storage":{},"depth":1} +{"pc":11,"op":56,"opName":"CODESIZE","gas":"0x9e1b","stack":["0x1","0x10","0x0","0x0"],"storage":{},"depth":1} +{"pc":12,"op":50,"opName":"ORIGIN","gas":"0x9e19","stack":["0x1","0x10","0x0","0x0","0x10"],"storage":{},"depth":1} +{"pc":13,"op":52,"opName":"CALLVALUE","gas":"0x9e17","stack":["0x1","0x10","0x0","0x0","0x10","0x0"],"storage":{},"depth":1} +{"pc":14,"op":57,"opName":"CODECOPY","gas":"0x9e15","stack":["0x1","0x10","0x0","0x0","0x10","0x0","0x0"],"storage":{},"depth":1} +{"pc":15,"op":240,"opName":"CREATE","gas":"0x9e0c","stack":["0x1","0x10","0x0","0x0"],"storage":{},"depth":1} +{"pc":0,"op":50,"opName":"ORIGIN","gas":"0x210c","stack":[],"storage":{},"depth":2} +{"pc":1,"op":52,"opName":"CALLVALUE","gas":"0x210a","stack":["0x0"],"storage":{},"depth":2} +{"pc":2,"op":52,"opName":"CALLVALUE","gas":"0x2108","stack":["0x0","0x0"],"storage":{},"depth":2} +{"pc":3,"op":52,"opName":"CALLVALUE","gas":"0x2106","stack":["0x0","0x0","0x0"],"storage":{},"depth":2} +{"pc":4,"op":52,"opName":"CALLVALUE","gas":"0x2104","stack":["0x0","0x0","0x0","0x0"],"storage":{},"depth":2} +{"pc":5,"op":88,"opName":"PC","gas":"0x2102","stack":["0x0","0x0","0x0","0x0","0x0"],"storage":{},"depth":2} +{"pc":6,"op":48,"opName":"ADDRESS","gas":"0x2100","stack":["0x0","0x0","0x0","0x0","0x0","0x5"],"storage":{},"depth":2} +{"pc":7,"op":241,"opName":"CALL","gas":"0x20fe","stack":["0x0","0x0","0x0","0x0","0x0","0x5","0xbd770416a3345f91e4b34576cb804a576fa48eb1"],"storage":{},"depth":2} +"#, + ) + } +} diff --git a/evmbin/src/info.rs b/evmbin/src/info.rs index 3f3a66a8c..532d9dd05 100644 --- a/evmbin/src/info.rs +++ b/evmbin/src/info.rs @@ -27,7 +27,7 @@ use vm::ActionParams; /// VM execution informant pub trait Informant: trace::VMTracer { /// Display a single run init message - fn before_test(&self, test: &str, action: &str); + fn before_test(&mut self, test: &str, action: &str); /// Set initial gas. fn set_gas(&mut self, _gas: U256) {} /// Display final result. @@ -37,6 +37,8 @@ pub trait Informant: trace::VMTracer { /// Execution finished correctly #[derive(Debug)] pub struct Success { + /// State root + pub state_root: H256, /// Used gas pub gas_used: U256, /// Output as bytes @@ -73,7 +75,7 @@ pub fn run_action( run(spec, params.gas, None, |mut client| { let result = client .call(params, &mut informant) - .map(|r| (r.gas_left, r.return_data.to_vec())); + .map(|r| (0.into(), r.gas_left, r.return_data.to_vec())); (result, informant.drain()) }) } @@ -113,8 +115,8 @@ pub fn run_transaction( post_root, ))), None) }, - TransactResult::Ok { gas_left, output, vm_trace, .. } => { - (Ok((gas_left, output)), vm_trace) + TransactResult::Ok { state_root, gas_left, output, vm_trace, .. } => { + (Ok((state_root, gas_left, output)), vm_trace) }, TransactResult::Err { error, .. } => { (Err(EvmTestError::PostCondition(format!( @@ -134,7 +136,7 @@ pub fn run<'a, F, T, X>( pre_state: T, run: F, ) -> RunResult where - F: FnOnce(EvmTestClient) -> (Result<(U256, Vec), EvmTestError>, Option), + F: FnOnce(EvmTestClient) -> (Result<(H256, U256, Vec), EvmTestError>, Option), T: Into>, { let test_client = match pre_state.into() { @@ -152,7 +154,8 @@ pub fn run<'a, F, T, X>( let time = start.elapsed(); match result { - (Ok((gas_left, output)), traces) => Ok(Success { + (Ok((state_root, gas_left, output)), traces) => Ok(Success { + state_root, gas_used: initial_gas - gas_left, output, time, @@ -168,110 +171,35 @@ pub fn run<'a, F, T, X>( } #[cfg(test)] -mod tests { +pub mod tests { use std::sync::Arc; use rustc_hex::FromHex; use super::*; - #[test] - fn should_trace_failure() { - run_test( - "60F8d6", - 0xffff, - r#" -{"pc":0,"op":96,"opName":"PUSH1","gas":"0xffff","gasCost":"0x3","memory":"0x","stack":[],"storage":{},"depth":1} -{"pc":2,"op":214,"opName":"","gas":"0xfffc","gasCost":"0x0","memory":"0x","stack":["0xf8"],"storage":{},"depth":1} - "#, - ); - - run_test( - "F8d6", - 0xffff, - r#" -{"pc":0,"op":248,"opName":"","gas":"0xffff","gasCost":"0x0","memory":"0x","stack":[],"storage":{},"depth":1} - "#, - ); - } - - #[test] - fn should_trace_create_correctly() { - run_test( - "32343434345830f138343438323439f0", - 0xffff, - r#" -{"pc":0,"op":50,"opName":"ORIGIN","gas":"0xffff","gasCost":"0x2","memory":"0x","stack":[],"storage":{},"depth":1} -{"pc":1,"op":52,"opName":"CALLVALUE","gas":"0xfffd","gasCost":"0x2","memory":"0x","stack":["0x0"],"storage":{},"depth":1} -{"pc":2,"op":52,"opName":"CALLVALUE","gas":"0xfffb","gasCost":"0x2","memory":"0x","stack":["0x0","0x0"],"storage":{},"depth":1} -{"pc":3,"op":52,"opName":"CALLVALUE","gas":"0xfff9","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0"],"storage":{},"depth":1} -{"pc":4,"op":52,"opName":"CALLVALUE","gas":"0xfff7","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0","0x0"],"storage":{},"depth":1} -{"pc":5,"op":88,"opName":"PC","gas":"0xfff5","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0","0x0","0x0"],"storage":{},"depth":1} -{"pc":6,"op":48,"opName":"ADDRESS","gas":"0xfff3","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0","0x0","0x0","0x5"],"storage":{},"depth":1} -{"pc":7,"op":241,"opName":"CALL","gas":"0xfff1","gasCost":"0x61d0","memory":"0x","stack":["0x0","0x0","0x0","0x0","0x0","0x5","0x0"],"storage":{},"depth":1} -{"pc":8,"op":56,"opName":"CODESIZE","gas":"0x9e21","gasCost":"0x2","memory":"0x","stack":["0x1"],"storage":{},"depth":1} -{"pc":9,"op":52,"opName":"CALLVALUE","gas":"0x9e1f","gasCost":"0x2","memory":"0x","stack":["0x1","0x10"],"storage":{},"depth":1} -{"pc":10,"op":52,"opName":"CALLVALUE","gas":"0x9e1d","gasCost":"0x2","memory":"0x","stack":["0x1","0x10","0x0"],"storage":{},"depth":1} -{"pc":11,"op":56,"opName":"CODESIZE","gas":"0x9e1b","gasCost":"0x2","memory":"0x","stack":["0x1","0x10","0x0","0x0"],"storage":{},"depth":1} -{"pc":12,"op":50,"opName":"ORIGIN","gas":"0x9e19","gasCost":"0x2","memory":"0x","stack":["0x1","0x10","0x0","0x0","0x10"],"storage":{},"depth":1} -{"pc":13,"op":52,"opName":"CALLVALUE","gas":"0x9e17","gasCost":"0x2","memory":"0x","stack":["0x1","0x10","0x0","0x0","0x10","0x0"],"storage":{},"depth":1} -{"pc":14,"op":57,"opName":"CODECOPY","gas":"0x9e15","gasCost":"0x9","memory":"0x","stack":["0x1","0x10","0x0","0x0","0x10","0x0","0x0"],"storage":{},"depth":1} -{"pc":15,"op":240,"opName":"CREATE","gas":"0x9e0c","gasCost":"0x9e0c","memory":"0x32343434345830f138343438323439f0","stack":["0x1","0x10","0x0","0x0"],"storage":{},"depth":1} -{"pc":0,"op":50,"opName":"ORIGIN","gas":"0x210c","gasCost":"0x2","memory":"0x","stack":[],"storage":{},"depth":2} -{"pc":1,"op":52,"opName":"CALLVALUE","gas":"0x210a","gasCost":"0x2","memory":"0x","stack":["0x0"],"storage":{},"depth":2} -{"pc":2,"op":52,"opName":"CALLVALUE","gas":"0x2108","gasCost":"0x2","memory":"0x","stack":["0x0","0x0"],"storage":{},"depth":2} -{"pc":3,"op":52,"opName":"CALLVALUE","gas":"0x2106","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0"],"storage":{},"depth":2} -{"pc":4,"op":52,"opName":"CALLVALUE","gas":"0x2104","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0","0x0"],"storage":{},"depth":2} -{"pc":5,"op":88,"opName":"PC","gas":"0x2102","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0","0x0","0x0"],"storage":{},"depth":2} -{"pc":6,"op":48,"opName":"ADDRESS","gas":"0x2100","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0","0x0","0x0","0x5"],"storage":{},"depth":2} -{"pc":7,"op":241,"opName":"CALL","gas":"0x20fe","gasCost":"0x0","memory":"0x","stack":["0x0","0x0","0x0","0x0","0x0","0x5","0xbd770416a3345f91e4b34576cb804a576fa48eb1"],"storage":{},"depth":2} - "#, - ) - } - - fn run_test>( + pub fn run_test( + informant: I, + compare: F, code: &str, gas: T, expected: &str, - ) { + ) where + T: Into, + I: Informant, + F: FnOnce(Option, &str), + { let mut params = ActionParams::default(); params.code = Some(Arc::new(code.from_hex().unwrap())); params.gas = gas.into(); let spec = ::ethcore::ethereum::new_foundation(&::std::env::temp_dir()); - let informant = ::display::json::Informant::default(); let result = run_action(&spec, params, informant); - let expected = expected.split("\n") - .map(|x| x.trim()) - .map(|x| x.to_owned()) - .filter(|x| !x.is_empty()) - .collect::>(); match result { Ok(Success { traces, .. }) => { - assert_traces_eq(&traces.unwrap(), &expected); + compare(traces, expected) }, Err(Failure { traces, .. }) => { - assert_traces_eq(&traces.unwrap(), &expected); + compare(traces, expected) }, } } - - fn assert_traces_eq( - a: &[String], - b: &[String], - ) { - let mut ita = a.iter(); - let mut itb = b.iter(); - - loop { - match (ita.next(), itb.next()) { - (Some(a), Some(b)) => { - assert_eq!(a, b); - println!("{}", a); - }, - (None, None) => return, - e => { - panic!("Traces mismatch: {:?}", e); - } - } - } - } } diff --git a/evmbin/src/main.rs b/evmbin/src/main.rs index 1a6bfdf22..1d46e57fb 100644 --- a/evmbin/src/main.rs +++ b/evmbin/src/main.rs @@ -56,7 +56,7 @@ EVM implementation for Parity. Copyright 2016, 2017 Parity Technologies (UK) Ltd Usage: - parity-evm state-test [--json --only NAME --chain CHAIN] + parity-evm state-test [--json --std-json --only NAME --chain CHAIN] parity-evm stats [options] parity-evm [options] parity-evm [-h | --help] @@ -75,6 +75,7 @@ State test options: General options: --json Display verbose results in JSON. + --std-json Display results in standardized JSON format. --chain CHAIN Chain spec file path. -h, --help Display this message and exit. "#; @@ -89,6 +90,8 @@ fn main() { run_state_test(args) } else if args.flag_json { run_call(args, display::json::Informant::default()) + } else if args.flag_std_json { + run_call(args, display::std_json::Informant::default()) } else { run_call(args, display::simple::Informant::default()) } @@ -130,6 +133,9 @@ fn run_state_test(args: Args) { if args.flag_json { let i = display::json::Informant::default(); info::run_transaction(&name, idx, &spec, &pre, post_root, &env_info, transaction, i) + } else if args.flag_std_json { + let i = display::std_json::Informant::default(); + info::run_transaction(&name, idx, &spec, &pre, post_root, &env_info, transaction, i) } else { let i = display::simple::Informant::default(); info::run_transaction(&name, idx, &spec, &pre, post_root, &env_info, transaction, i) @@ -181,6 +187,7 @@ struct Args { flag_input: Option, flag_chain: Option, flag_json: bool, + flag_std_json: bool, } impl Args { @@ -266,6 +273,7 @@ mod tests { let args = run(&[ "parity-evm", "--json", + "--std-json", "--gas", "1", "--gas-price", "2", "--from", "0000000000000000000000000000000000000003", @@ -276,6 +284,7 @@ mod tests { ]); assert_eq!(args.flag_json, true); + assert_eq!(args.flag_std_json, true); assert_eq!(args.gas(), Ok(1.into())); assert_eq!(args.gas_price(), Ok(2.into())); assert_eq!(args.from(), Ok(3.into())); @@ -294,11 +303,13 @@ mod tests { "--chain", "homestead", "--only=add11", "--json", + "--std-json" ]); assert_eq!(args.cmd_state_test, true); assert!(args.arg_file.is_some()); assert_eq!(args.flag_json, true); + assert_eq!(args.flag_std_json, true); assert_eq!(args.flag_chain, Some("homestead".to_owned())); assert_eq!(args.flag_only, Some("add11".to_owned())); }