Std-json format for VM traces (#7262)
* Std-json logging. * fixed merge with master
This commit is contained in:
parent
30737fe580
commit
709fbff067
@ -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)?;
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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) {}
|
||||
|
@ -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) {}
|
||||
|
||||
|
@ -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) {}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
265
evmbin/src/display/std_json.rs
Normal file
265
evmbin/src/display/std_json.rs
Normal 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(¤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<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}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user