Std-json format for VM traces (#7262)

* Std-json logging.

* fixed merge with master
This commit is contained in:
Tomasz Drwięga 2018-01-18 10:32:22 +01:00 committed by Marek Kotewicz
parent 30737fe580
commit 709fbff067
13 changed files with 408 additions and 104 deletions

View File

@ -132,7 +132,9 @@ impl<Cost: CostType> vm::Vm for Interpreter<Cost> {
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)?;

View File

@ -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) {

View File

@ -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 {

View File

@ -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) {}

View File

@ -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) {}

View File

@ -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) {}

View File

@ -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
}
}

View File

@ -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<String>;
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<Vec<String>>, expected: &str) {
let expected = expected.split("\n")
.map(|x| x.trim())
.map(|x| x.to_owned())
.filter(|x| !x.is_empty())
.collect::<Vec<_>>();
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}
"#,
)
}
}

View File

@ -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.

View File

@ -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);
}

View File

@ -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 <http://www.gnu.org/licenses/>.
//! 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<T: Writer = io::Stdout> {
code: Vec<u8>,
instruction: u8,
depth: usize,
stack: Vec<U256>,
storage: HashMap<H256, H256>,
sink: T,
}
impl Default for Informant {
fn default() -> Self {
Self::new(io::stdout())
}
}
impl<T: Writer> Informant<T> {
pub fn new(sink: T) -> Self {
Informant {
code: Default::default(),
instruction: Default::default(),
depth: Default::default(),
stack: Default::default(),
storage: Default::default(),
sink,
}
}
}
impl<T: Writer> Informant<T> {
fn stack(&self) -> String {
let items = self.stack.iter().map(display::u256_as_str).collect::<Vec<_>>();
format!("[{}]", items.join(","))
}
fn storage(&self) -> String {
let vals = self.storage.iter()
.map(|(k, v)| format!("\"0x{:?}\": \"0x{:?}\"", k, v))
.collect::<Vec<_>>();
format!("{{{}}}", vals.join(","))
}
}
impl<T: Writer> vm::Informant for Informant<T> {
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<Self::Output>) {
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<T: Writer> trace::VMTracer for Informant<T> {
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(&current_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<Self::Output> { 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<Mutex<Vec<u8>>>);
impl Writer for TestWriter {
fn clone(&self) -> Self { Clone::clone(self) }
}
impl io::Write for TestWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.lock().unwrap().write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.0.lock().unwrap().flush()
}
}
fn informant() -> (Informant<TestWriter>, Arc<Mutex<Vec<u8>>>) {
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}
"#,
)
}
}

View File

@ -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<T> {
/// State root
pub state_root: H256,
/// Used gas
pub gas_used: U256,
/// Output as bytes
@ -73,7 +75,7 @@ pub fn run_action<T: Informant>(
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<T: Informant>(
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<X> where
F: FnOnce(EvmTestClient) -> (Result<(U256, Vec<u8>), EvmTestError>, Option<X>),
F: FnOnce(EvmTestClient) -> (Result<(H256, U256, Vec<u8>), EvmTestError>, Option<X>),
T: Into<Option<&'a pod_state::PodState>>,
{
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<T: Into<U256>>(
pub fn run_test<T, I, F>(
informant: I,
compare: F,
code: &str,
gas: T,
expected: &str,
) {
) where
T: Into<U256>,
I: Informant,
F: FnOnce(Option<I::Output>, &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::<Vec<_>>();
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);
}
}
}
}
}

View File

@ -56,7 +56,7 @@ EVM implementation for Parity.
Copyright 2016, 2017 Parity Technologies (UK) Ltd
Usage:
parity-evm state-test <file> [--json --only NAME --chain CHAIN]
parity-evm state-test <file> [--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<String>,
flag_chain: Option<String>,
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()));
}