Merge pull request #6842 from paritytech/td-evm-json

Fix JSON tracing for sub-calls.
This commit is contained in:
Marek Kotewicz 2017-10-29 21:47:14 -07:00 committed by GitHub
commit 39e27076ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 280 additions and 80 deletions

1
Cargo.lock generated
View File

@ -913,6 +913,7 @@ dependencies = [
"ethjson 0.1.0", "ethjson 0.1.0",
"evm 0.1.0", "evm 0.1.0",
"panic_hook 0.1.0", "panic_hook 0.1.0",
"pretty_assertions 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -1142,7 +1142,7 @@ impl Client {
state_diff: bool, state_diff: bool,
transaction: &SignedTransaction, transaction: &SignedTransaction,
options: TransactOptions<T, V>, options: TransactOptions<T, V>,
) -> Result<Executed, CallError> where ) -> Result<Executed<T::Output, V::Output>, CallError> where
T: trace::Tracer, T: trace::Tracer,
V: trace::VMTracer, V: trace::VMTracer,
{ {

View File

@ -197,7 +197,7 @@ impl<'a> EvmTestClient<'a> {
env_info: &client::EnvInfo, env_info: &client::EnvInfo,
transaction: transaction::SignedTransaction, transaction: transaction::SignedTransaction,
vm_tracer: T, vm_tracer: T,
) -> TransactResult { ) -> TransactResult<T::Output> {
let initial_gas = transaction.gas; let initial_gas = transaction.gas;
// Verify transaction // Verify transaction
let is_ok = transaction.verify_basic(true, None, env_info.number >= self.spec.engine.params().eip86_transition); let is_ok = transaction.verify_basic(true, None, env_info.number >= self.spec.engine.params().eip86_transition);
@ -218,7 +218,8 @@ impl<'a> EvmTestClient<'a> {
TransactResult::Ok { TransactResult::Ok {
state_root: *self.state.root(), state_root: *self.state.root(),
gas_left: initial_gas - result.receipt.gas_used, gas_left: initial_gas - result.receipt.gas_used,
output: result.output output: result.output,
vm_trace: result.vm_trace,
} }
}, },
Err(error) => TransactResult::Err { Err(error) => TransactResult::Err {
@ -230,7 +231,7 @@ impl<'a> EvmTestClient<'a> {
} }
/// A result of applying transaction to the state. /// A result of applying transaction to the state.
pub enum TransactResult { pub enum TransactResult<T> {
/// Successful execution /// Successful execution
Ok { Ok {
/// State root /// State root
@ -239,6 +240,8 @@ pub enum TransactResult {
gas_left: U256, gas_left: U256,
/// Output /// Output
output: Vec<u8>, output: Vec<u8>,
/// VM Traces
vm_trace: Option<T>,
}, },
/// Transaction failed to run /// Transaction failed to run
Err { Err {

View File

@ -29,7 +29,7 @@ use std::fmt;
/// Transaction execution receipt. /// Transaction execution receipt.
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub struct Executed { pub struct Executed<T = FlatTrace, V = VMTrace> {
/// True if the outer call/create resulted in an exceptional exit. /// True if the outer call/create resulted in an exceptional exit.
pub exception: Option<vm::Error>, pub exception: Option<vm::Error>,
@ -63,9 +63,9 @@ pub struct Executed {
/// Transaction output. /// Transaction output.
pub output: Bytes, pub output: Bytes,
/// The trace of this transaction. /// The trace of this transaction.
pub trace: Vec<FlatTrace>, pub trace: Vec<T>,
/// The VM trace of this transaction. /// The VM trace of this transaction.
pub vm_trace: Option<VMTrace>, pub vm_trace: Option<V>,
/// The state diff, if we traced it. /// The state diff, if we traced it.
pub state_diff: Option<StateDiff>, pub state_diff: Option<StateDiff>,
} }

View File

@ -30,7 +30,7 @@ use evm::{CallType, Factory, Finalize, FinalizationResult};
use vm::{self, Ext, CreateContractAddress, ReturnData, CleanDustMode, ActionParams, ActionValue}; use vm::{self, Ext, CreateContractAddress, ReturnData, CleanDustMode, ActionParams, ActionValue};
use wasm; use wasm;
use externalities::*; use externalities::*;
use trace::{self, FlatTrace, VMTrace, Tracer, VMTracer}; use trace::{self, Tracer, VMTracer};
use transaction::{Action, SignedTransaction}; use transaction::{Action, SignedTransaction};
use crossbeam; use crossbeam;
pub use executed::{Executed, ExecutionResult}; pub use executed::{Executed, ExecutionResult};
@ -214,7 +214,7 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> {
/// This function should be used to execute transaction. /// This function should be used to execute transaction.
pub fn transact<T, V>(&'a mut self, t: &SignedTransaction, options: TransactOptions<T, V>) pub fn transact<T, V>(&'a mut self, t: &SignedTransaction, options: TransactOptions<T, V>)
-> Result<Executed, ExecutionError> where T: Tracer, V: VMTracer, -> Result<Executed<T::Output, V::Output>, ExecutionError> where T: Tracer, V: VMTracer,
{ {
self.transact_with_tracer(t, options.check_nonce, options.output_from_init_contract, options.tracer, options.vm_tracer) self.transact_with_tracer(t, options.check_nonce, options.output_from_init_contract, options.tracer, options.vm_tracer)
} }
@ -223,7 +223,7 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> {
/// This will ensure the caller has enough balance to execute the desired transaction. /// This will ensure the caller has enough balance to execute the desired transaction.
/// Used for extra-block executions for things like consensus contracts and RPCs /// Used for extra-block executions for things like consensus contracts and RPCs
pub fn transact_virtual<T, V>(&'a mut self, t: &SignedTransaction, options: TransactOptions<T, V>) pub fn transact_virtual<T, V>(&'a mut self, t: &SignedTransaction, options: TransactOptions<T, V>)
-> Result<Executed, ExecutionError> where T: Tracer, V: VMTracer, -> Result<Executed<T::Output, V::Output>, ExecutionError> where T: Tracer, V: VMTracer,
{ {
let sender = t.sender(); let sender = t.sender();
let balance = self.state.balance(&sender)?; let balance = self.state.balance(&sender)?;
@ -244,7 +244,7 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> {
output_from_create: bool, output_from_create: bool,
mut tracer: T, mut tracer: T,
mut vm_tracer: V mut vm_tracer: V
) -> Result<Executed, ExecutionError> where T: Tracer, V: VMTracer { ) -> Result<Executed<T::Output, V::Output>, ExecutionError> where T: Tracer, V: VMTracer {
let sender = t.sender(); let sender = t.sender();
let nonce = self.state.nonce(&sender)?; let nonce = self.state.nonce(&sender)?;
@ -587,15 +587,15 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> {
} }
/// Finalizes the transaction (does refunds and suicides). /// Finalizes the transaction (does refunds and suicides).
fn finalize( fn finalize<T, V>(
&mut self, &mut self,
t: &SignedTransaction, t: &SignedTransaction,
mut substate: Substate, mut substate: Substate,
result: vm::Result<FinalizationResult>, result: vm::Result<FinalizationResult>,
output: Bytes, output: Bytes,
trace: Vec<FlatTrace>, trace: Vec<T>,
vm_trace: Option<VMTrace> vm_trace: Option<V>
) -> ExecutionResult { ) -> Result<Executed<T, V>, ExecutionError> {
let schedule = self.machine.schedule(self.info.number); let schedule = self.machine.schedule(self.info.number);
// refunds from SSTORE nonzero -> zero // refunds from SSTORE nonzero -> zero

View File

@ -62,19 +62,19 @@ pub use self::backend::Backend;
pub use self::substate::Substate; pub use self::substate::Substate;
/// Used to return information about an `State::apply` operation. /// Used to return information about an `State::apply` operation.
pub struct ApplyOutcome { pub struct ApplyOutcome<T, V> {
/// The receipt for the applied transaction. /// The receipt for the applied transaction.
pub receipt: Receipt, pub receipt: Receipt,
/// The output of the applied transaction. /// The output of the applied transaction.
pub output: Bytes, pub output: Bytes,
/// The trace for the applied transaction, empty if tracing was not produced. /// The trace for the applied transaction, empty if tracing was not produced.
pub trace: Vec<FlatTrace>, pub trace: Vec<T>,
/// The VM trace for the applied transaction, None if tracing was not produced. /// The VM trace for the applied transaction, None if tracing was not produced.
pub vm_trace: Option<VMTrace> pub vm_trace: Option<V>
} }
/// Result type for the execution ("application") of a transaction. /// Result type for the execution ("application") of a transaction.
pub type ApplyResult = Result<ApplyOutcome, Error>; pub type ApplyResult<T, V> = Result<ApplyOutcome<T, V>, Error>;
/// Return type of proof validity check. /// Return type of proof validity check.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -668,7 +668,7 @@ impl<B: Backend> State<B> {
/// Execute a given transaction, producing a receipt and an optional trace. /// Execute a given transaction, producing a receipt and an optional trace.
/// This will change the state accordingly. /// This will change the state accordingly.
pub fn apply(&mut self, env_info: &EnvInfo, machine: &Machine, t: &SignedTransaction, tracing: bool) -> ApplyResult { pub fn apply(&mut self, env_info: &EnvInfo, machine: &Machine, t: &SignedTransaction, tracing: bool) -> ApplyResult<FlatTrace, VMTrace> {
if tracing { if tracing {
let options = TransactOptions::with_tracing(); let options = TransactOptions::with_tracing();
self.apply_with_tracing(env_info, machine, t, options.tracer, options.vm_tracer) self.apply_with_tracing(env_info, machine, t, options.tracer, options.vm_tracer)
@ -687,7 +687,7 @@ impl<B: Backend> State<B> {
t: &SignedTransaction, t: &SignedTransaction,
tracer: T, tracer: T,
vm_tracer: V, vm_tracer: V,
) -> ApplyResult where ) -> ApplyResult<T::Output, V::Output> where
T: trace::Tracer, T: trace::Tracer,
V: trace::VMTracer, V: trace::VMTracer,
{ {
@ -728,7 +728,7 @@ impl<B: Backend> State<B> {
// `virt` signals that we are executing outside of a block set and restrictions like // `virt` signals that we are executing outside of a block set and restrictions like
// gas limits and gas costs should be lifted. // gas limits and gas costs should be lifted.
fn execute<T, V>(&mut self, env_info: &EnvInfo, machine: &Machine, t: &SignedTransaction, options: TransactOptions<T, V>, virt: bool) fn execute<T, V>(&mut self, env_info: &EnvInfo, machine: &Machine, t: &SignedTransaction, options: TransactOptions<T, V>, virt: bool)
-> Result<Executed, ExecutionError> where T: trace::Tracer, V: trace::VMTracer, -> Result<Executed<T::Output, V::Output>, ExecutionError> where T: trace::Tracer, V: trace::VMTracer,
{ {
let mut e = Executive::new(self, env_info, machine); let mut e = Executive::new(self, env_info, machine);

View File

@ -83,6 +83,8 @@ fn should_prefix_address_properly() {
} }
impl Tracer for ExecutiveTracer { impl Tracer for ExecutiveTracer {
type Output = FlatTrace;
fn prepare_trace_call(&self, params: &ActionParams) -> Option<Call> { fn prepare_trace_call(&self, params: &ActionParams) -> Option<Call> {
Some(Call::from(params.clone())) Some(Call::from(params.clone()))
} }
@ -201,6 +203,8 @@ impl ExecutiveVMTracer {
} }
impl VMTracer for 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) -> bool { true }
fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, gas_cost: U256) { fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, gas_cost: U256) {

View File

@ -48,6 +48,9 @@ use header::BlockNumber;
/// This trait is used by executive to build traces. /// This trait is used by executive to build traces.
pub trait Tracer: Send { pub trait Tracer: Send {
/// Data returned when draining the Tracer.
type Output;
/// Prepares call trace for given params. Noop tracer should return None. /// Prepares call trace for given params. Noop tracer should return None.
fn prepare_trace_call(&self, params: &ActionParams) -> Option<Call>; fn prepare_trace_call(&self, params: &ActionParams) -> Option<Call>;
@ -63,7 +66,7 @@ pub trait Tracer: Send {
call: Option<Call>, call: Option<Call>,
gas_used: U256, gas_used: U256,
output: Option<Bytes>, output: Option<Bytes>,
subs: Vec<FlatTrace>, subs: Vec<Self::Output>,
); );
/// Stores trace create info. /// Stores trace create info.
@ -73,14 +76,14 @@ pub trait Tracer: Send {
gas_used: U256, gas_used: U256,
code: Option<Bytes>, code: Option<Bytes>,
address: Address, address: Address,
subs: Vec<FlatTrace> subs: Vec<Self::Output>
); );
/// Stores failed call trace. /// Stores failed call trace.
fn trace_failed_call(&mut self, call: Option<Call>, subs: Vec<FlatTrace>, error: TraceError); fn trace_failed_call(&mut self, call: Option<Call>, subs: Vec<Self::Output>, error: TraceError);
/// Stores failed create trace. /// Stores failed create trace.
fn trace_failed_create(&mut self, create: Option<Create>, subs: Vec<FlatTrace>, error: TraceError); fn trace_failed_create(&mut self, create: Option<Create>, subs: Vec<Self::Output>, error: TraceError);
/// Stores suicide info. /// Stores suicide info.
fn trace_suicide(&mut self, address: Address, balance: U256, refund_address: Address); fn trace_suicide(&mut self, address: Address, balance: U256, refund_address: Address);
@ -92,12 +95,15 @@ pub trait Tracer: Send {
fn subtracer(&self) -> Self where Self: Sized; fn subtracer(&self) -> Self where Self: Sized;
/// Consumes self and returns all traces. /// Consumes self and returns all traces.
fn drain(self) -> Vec<FlatTrace>; fn drain(self) -> Vec<Self::Output>;
} }
/// Used by executive to build VM traces. /// Used by executive to build VM traces.
pub trait VMTracer: Send { pub trait VMTracer: Send {
/// Data returned when draining the VMTracer.
type Output;
/// Trace the progression of interpreter to next instruction. /// Trace the progression of interpreter to next instruction.
/// If tracer returns `false` it won't be called again. /// If tracer returns `false` it won't be called again.
/// @returns true if `trace_prepare_execute` and `trace_executed` should be called. /// @returns true if `trace_prepare_execute` and `trace_executed` should be called.
@ -116,7 +122,7 @@ pub trait VMTracer: Send {
fn done_subtrace(&mut self, sub: Self) where Self: Sized; fn done_subtrace(&mut self, sub: Self) where Self: Sized;
/// Consumes self and returns the VM trace. /// Consumes self and returns the VM trace.
fn drain(self) -> Option<VMTrace>; fn drain(self) -> Option<Self::Output>;
} }
/// `DbExtras` provides an interface to query extra data which is not stored in tracesdb, /// `DbExtras` provides an interface to query extra data which is not stored in tracesdb,

View File

@ -27,6 +27,8 @@ use trace::trace::{Call, Create, VMTrace, RewardType};
pub struct NoopTracer; pub struct NoopTracer;
impl Tracer for NoopTracer { impl Tracer for NoopTracer {
type Output = FlatTrace;
fn prepare_trace_call(&self, _: &ActionParams) -> Option<Call> { fn prepare_trace_call(&self, _: &ActionParams) -> Option<Call> {
None None
} }
@ -76,6 +78,8 @@ impl Tracer for NoopTracer {
pub struct NoopVMTracer; pub struct NoopVMTracer;
impl VMTracer for 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) -> bool { false }
fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: U256) {} fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: U256) {}

View File

@ -22,5 +22,8 @@ evm = { path = "../ethcore/evm" }
vm = { path = "../ethcore/vm" } vm = { path = "../ethcore/vm" }
panic_hook = { path = "../panic_hook" } panic_hook = { path = "../panic_hook" }
[dev-dependencies]
pretty_assertions = "0.1"
[features] [features]
evm-debug = ["ethcore/evm-debug-tests"] evm-debug = ["ethcore/evm-debug-tests"]

View File

@ -16,11 +16,13 @@
//! JSON VM output. //! JSON VM output.
use ethcore::trace;
use std::collections::HashMap; use std::collections::HashMap;
use bigint::prelude::U256; use std::mem;
use bigint::hash::H256; use bigint::hash::H256;
use bigint::prelude::U256;
use bytes::ToPretty; use bytes::ToPretty;
use ethcore::trace;
use display; use display;
use info as vm; use info as vm;
@ -37,6 +39,9 @@ pub struct Informant {
stack: Vec<U256>, stack: Vec<U256>,
memory: Vec<u8>, memory: Vec<u8>,
storage: HashMap<H256, H256>, storage: HashMap<H256, H256>,
traces: Vec<String>,
subtraces: Vec<String>,
unmatched: bool,
} }
impl Informant { impl Informant {
@ -70,28 +75,43 @@ impl vm::Informant for Informant {
self.gas_used = gas; self.gas_used = gas;
} }
fn finish(result: Result<vm::Success, vm::Failure>) { fn finish(result: vm::RunResult<Self::Output>) {
match result { match result {
Ok(success) => println!( Ok(success) => {
for trace in success.traces.unwrap_or_else(Vec::new) {
println!("{}", trace);
}
println!(
"{{\"output\":\"0x{output}\",\"gasUsed\":\"{gas:x}\",\"time\":{time}}}", "{{\"output\":\"0x{output}\",\"gasUsed\":\"{gas:x}\",\"time\":{time}}}",
output = success.output.to_hex(), output = success.output.to_hex(),
gas = success.gas_used, gas = success.gas_used,
time = display::as_micros(&success.time), time = display::as_micros(&success.time),
), )
Err(failure) => println!( },
Err(failure) => {
for trace in failure.traces.unwrap_or_else(Vec::new) {
println!("{}", trace);
}
println!(
"{{\"error\":\"{error}\",\"gasUsed\":\"{gas:x}\",\"time\":{time}}}", "{{\"error\":\"{error}\",\"gasUsed\":\"{gas:x}\",\"time\":{time}}}",
error = failure.error, error = failure.error,
gas = failure.gas_used, gas = failure.gas_used,
time = display::as_micros(&failure.time), time = display::as_micros(&failure.time),
), )
},
} }
} }
} }
impl trace::VMTracer 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) -> bool {
self.pc = pc; self.pc = pc;
self.instruction = instruction; self.instruction = instruction;
self.unmatched = true;
true true
} }
@ -104,19 +124,21 @@ impl trace::VMTracer for Informant {
fn trace_executed(&mut self, gas_used: U256, stack_push: &[U256], mem_diff: Option<(usize, &[u8])>, store_diff: Option<(U256, 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 info = ::evm::INSTRUCTIONS[self.instruction as usize];
println!( let trace = format!(
"{{\"pc\":{pc},\"op\":{op},\"opName\":\"{name}\",\"gas\":{gas},\"gasCost\":{gas_cost},\"memory\":{memory},\"stack\":{stack},\"storage\":{storage},\"depth\":{depth}}}", "{{\"pc\":{pc},\"op\":{op},\"opName\":\"{name}\",\"gas\":{gas},\"gasCost\":{gas_cost},\"memory\":{memory},\"stack\":{stack},\"storage\":{storage},\"depth\":{depth}}}",
pc = self.pc, pc = self.pc,
op = self.instruction, op = self.instruction,
name = info.name, name = info.name,
gas = display::u256_as_str(&(gas_used + self.gas_cost)), gas = display::u256_as_str(&(gas_used.saturating_add(self.gas_cost))),
gas_cost = display::u256_as_str(&self.gas_cost), gas_cost = display::u256_as_str(&self.gas_cost),
memory = self.memory(), memory = self.memory(),
stack = self.stack(), stack = self.stack(),
storage = self.storage(), storage = self.storage(),
depth = self.depth, depth = self.depth,
); );
self.traces.push(trace);
self.unmatched = false;
self.gas_used = gas_used; self.gas_used = gas_used;
let len = self.stack.len(); let len = self.stack.len();
@ -133,6 +155,11 @@ impl trace::VMTracer for Informant {
if let Some((pos, val)) = store_diff { if let Some((pos, val)) = store_diff {
self.storage.insert(pos.into(), val.into()); self.storage.insert(pos.into(), val.into());
} }
if !self.subtraces.is_empty() {
self.traces.extend(mem::replace(&mut self.subtraces, vec![]));
}
} }
fn prepare_subtrace(&self, code: &[u8]) -> Self where Self: Sized { fn prepare_subtrace(&self, code: &[u8]) -> Self where Self: Sized {
@ -143,14 +170,21 @@ impl trace::VMTracer for Informant {
vm vm
} }
fn done_subtrace(&mut self, mut sub: Self) { fn done_subtrace(&mut self, sub: Self) {
if sub.depth == 1 { if let Some(subtraces) = sub.drain() {
// print last line with final state: self.subtraces.extend(subtraces);
sub.gas_cost = 0.into();
let gas_used = sub.gas_used;
trace::VMTracer::trace_executed(&mut sub, gas_used, &[], None, None);
} }
} }
fn drain(self) -> Option<trace::VMTrace> { None } fn drain(mut self) -> Option<Self::Output> {
if self.unmatched {
// print last line with final state:
self.gas_cost = 0.into();
let gas_used = self.gas_used;
self.trace_executed(gas_used, &[], None, None);
} else if !self.subtraces.is_empty() {
self.traces.extend(mem::replace(&mut self.subtraces, vec![]));
}
Some(self.traces)
}
} }

View File

@ -31,7 +31,7 @@ impl vm::Informant for Informant {
println!("Test: {} ({})", name, action); println!("Test: {} ({})", name, action);
} }
fn finish(result: Result<vm::Success, vm::Failure>) { fn finish(result: vm::RunResult<Self::Output>) {
match result { match result {
Ok(success) => { Ok(success) => {
println!("Output: 0x{}", success.output.to_hex()); println!("Output: 0x{}", success.output.to_hex());
@ -47,7 +47,9 @@ impl vm::Informant for Informant {
} }
impl trace::VMTracer for Informant { impl trace::VMTracer for Informant {
type Output = ();
fn prepare_subtrace(&self, _code: &[u8]) -> Self where Self: Sized { Default::default() } fn prepare_subtrace(&self, _code: &[u8]) -> Self where Self: Sized { Default::default() }
fn done_subtrace(&mut self, _sub: Self) {} fn done_subtrace(&mut self, _sub: Self) {}
fn drain(self) -> Option<trace::VMTrace> { None } fn drain(self) -> Option<()> { None }
} }

View File

@ -22,6 +22,7 @@ use bigint::hash::H256;
use ethcore::{trace, spec, transaction, pod_state}; use ethcore::{trace, spec, transaction, pod_state};
use ethcore::client::{self, EvmTestClient, EvmTestError, TransactResult}; use ethcore::client::{self, EvmTestClient, EvmTestError, TransactResult};
use ethjson; use ethjson;
use vm::ActionParams;
/// VM execution informant /// VM execution informant
pub trait Informant: trace::VMTracer { pub trait Informant: trace::VMTracer {
@ -30,27 +31,51 @@ pub trait Informant: trace::VMTracer {
/// Set initial gas. /// Set initial gas.
fn set_gas(&mut self, _gas: U256) {} fn set_gas(&mut self, _gas: U256) {}
/// Display final result. /// Display final result.
fn finish(result: Result<Success, Failure>); fn finish(result: RunResult<Self::Output>);
} }
/// Execution finished correctly /// Execution finished correctly
pub struct Success { #[derive(Debug)]
pub struct Success<T> {
/// Used gas /// Used gas
pub gas_used: U256, pub gas_used: U256,
/// Output as bytes /// Output as bytes
pub output: Vec<u8>, pub output: Vec<u8>,
/// Time Taken /// Time Taken
pub time: Duration, pub time: Duration,
/// Traces
pub traces: Option<T>,
} }
/// Execution failed /// Execution failed
pub struct Failure { #[derive(Debug)]
pub struct Failure<T> {
/// Used gas /// Used gas
pub gas_used: U256, pub gas_used: U256,
/// Internal error /// Internal error
pub error: EvmTestError, pub error: EvmTestError,
/// Duration /// Duration
pub time: Duration, pub time: Duration,
/// Traces
pub traces: Option<T>,
}
/// EVM Execution result
pub type RunResult<T> = Result<Success<T>, Failure<T>>;
/// Execute given `ActionParams` and return the result.
pub fn run_action<T: Informant>(
spec: &spec::Spec,
params: ActionParams,
mut informant: T,
) -> RunResult<T::Output> {
informant.set_gas(params.gas);
run(spec, params.gas, None, |mut client| {
let result = client
.call(params, &mut informant)
.map(|r| (r.gas_left, r.return_data.to_vec()));
(result, informant.drain())
})
} }
/// Execute given Transaction and verify resulting state root. /// Execute given Transaction and verify resulting state root.
@ -82,19 +107,19 @@ pub fn run_transaction<T: Informant>(
let result = client.transact(env_info, transaction, informant); let result = client.transact(env_info, transaction, informant);
match result { match result {
TransactResult::Ok { state_root, .. } if state_root != post_root => { TransactResult::Ok { state_root, .. } if state_root != post_root => {
Err(EvmTestError::PostCondition(format!( (Err(EvmTestError::PostCondition(format!(
"State root mismatch (got: {}, expected: {})", "State root mismatch (got: {}, expected: {})",
state_root, state_root,
post_root, post_root,
))) ))), None)
}, },
TransactResult::Ok { gas_left, output, .. } => { TransactResult::Ok { gas_left, output, vm_trace, .. } => {
Ok((gas_left, output)) (Ok((gas_left, output)), vm_trace)
}, },
TransactResult::Err { error, .. } => { TransactResult::Err { error, .. } => {
Err(EvmTestError::PostCondition(format!( (Err(EvmTestError::PostCondition(format!(
"Unexpected execution error: {:?}", error "Unexpected execution error: {:?}", error
))) ))), None)
}, },
} }
}); });
@ -103,8 +128,13 @@ pub fn run_transaction<T: Informant>(
} }
/// Execute VM with given `ActionParams` /// Execute VM with given `ActionParams`
pub fn run<'a, F, T>(spec: &'a spec::Spec, initial_gas: U256, pre_state: T, run: F) -> Result<Success, Failure> where pub fn run<'a, F, T, X>(
F: FnOnce(EvmTestClient) -> Result<(U256, Vec<u8>), EvmTestError>, spec: &'a spec::Spec,
initial_gas: U256,
pre_state: T,
run: F,
) -> RunResult<X> where
F: FnOnce(EvmTestClient) -> (Result<(U256, Vec<u8>), EvmTestError>, Option<X>),
T: Into<Option<&'a pod_state::PodState>>, T: Into<Option<&'a pod_state::PodState>>,
{ {
let test_client = match pre_state.into() { let test_client = match pre_state.into() {
@ -113,23 +143,135 @@ pub fn run<'a, F, T>(spec: &'a spec::Spec, initial_gas: U256, pre_state: T, run:
}.map_err(|error| Failure { }.map_err(|error| Failure {
gas_used: 0.into(), gas_used: 0.into(),
error, error,
time: Duration::from_secs(0) time: Duration::from_secs(0),
traces: None,
})?; })?;
let start = Instant::now(); let start = Instant::now();
let result = run(test_client); let result = run(test_client);
let duration = start.elapsed(); let time = start.elapsed();
match result { match result {
Ok((gas_left, output)) => Ok(Success { (Ok((gas_left, output)), traces) => Ok(Success {
gas_used: initial_gas - gas_left, gas_used: initial_gas - gas_left,
output: output, output,
time: duration, time,
traces,
}), }),
Err(e) => Err(Failure { (Err(error), traces) => Err(Failure {
gas_used: initial_gas, gas_used: initial_gas,
error: e, error,
time: duration, time,
traces,
}), }),
} }
} }
#[cfg(test)]
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>>(
code: &str,
gas: T,
expected: &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);
},
Err(Failure { traces, .. }) => {
assert_traces_eq(&traces.unwrap(), &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

@ -32,6 +32,10 @@ extern crate vm;
extern crate evm; extern crate evm;
extern crate panic_hook; extern crate panic_hook;
#[cfg(test)]
#[macro_use]
extern crate pretty_assertions;
use std::sync::Arc; use std::sync::Arc;
use std::{fmt, fs}; use std::{fmt, fs};
use std::path::PathBuf; use std::path::PathBuf;
@ -136,7 +140,7 @@ fn run_state_test(args: Args) {
} }
} }
fn run_call<T: Informant>(args: Args, mut informant: T) { fn run_call<T: Informant>(args: Args, informant: T) {
let from = arg(args.from(), "--from"); let from = arg(args.from(), "--from");
let to = arg(args.to(), "--to"); let to = arg(args.to(), "--to");
let code = arg(args.code(), "--code"); let code = arg(args.code(), "--code");
@ -160,10 +164,7 @@ fn run_call<T: Informant>(args: Args, mut informant: T) {
params.code = code.map(Arc::new); params.code = code.map(Arc::new);
params.data = data; params.data = data;
informant.set_gas(gas); let result = info::run_action(&spec, params, informant);
let result = info::run(&spec, gas, None, |mut client| {
client.call(params, &mut informant).map(|r| (r.gas_left, r.return_data.to_vec()))
});
T::finish(result); T::finish(result);
} }
@ -187,7 +188,7 @@ impl Args {
pub fn gas(&self) -> Result<U256, String> { pub fn gas(&self) -> Result<U256, String> {
match self.flag_gas { match self.flag_gas {
Some(ref gas) => gas.parse().map_err(to_string), Some(ref gas) => gas.parse().map_err(to_string),
None => Ok(!U256::zero()), None => Ok(U256::from(u64::max_value())),
} }
} }

View File

@ -60,9 +60,9 @@ hash = { path = "../util/hash" }
hardware-wallet = { path = "../hw" } hardware-wallet = { path = "../hw" }
clippy = { version = "0.0.103", optional = true} clippy = { version = "0.0.103", optional = true}
pretty_assertions = "0.1"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.1"
macros = { path = "../util/macros" } macros = { path = "../util/macros" }
ethcore-network = { path = "../util/network" } ethcore-network = { path = "../util/network" }
kvdb-memorydb = { path = "../util/kvdb-memorydb" } kvdb-memorydb = { path = "../util/kvdb-memorydb" }