Merge pull request #6842 from paritytech/td-evm-json
Fix JSON tracing for sub-calls.
This commit is contained in:
commit
39e27076ad
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -913,6 +913,7 @@ dependencies = [
|
||||
"ethjson 0.1.0",
|
||||
"evm 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)",
|
||||
"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)",
|
||||
|
@ -1142,7 +1142,7 @@ impl Client {
|
||||
state_diff: bool,
|
||||
transaction: &SignedTransaction,
|
||||
options: TransactOptions<T, V>,
|
||||
) -> Result<Executed, CallError> where
|
||||
) -> Result<Executed<T::Output, V::Output>, CallError> where
|
||||
T: trace::Tracer,
|
||||
V: trace::VMTracer,
|
||||
{
|
||||
|
@ -197,7 +197,7 @@ impl<'a> EvmTestClient<'a> {
|
||||
env_info: &client::EnvInfo,
|
||||
transaction: transaction::SignedTransaction,
|
||||
vm_tracer: T,
|
||||
) -> TransactResult {
|
||||
) -> TransactResult<T::Output> {
|
||||
let initial_gas = transaction.gas;
|
||||
// Verify transaction
|
||||
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 {
|
||||
state_root: *self.state.root(),
|
||||
gas_left: initial_gas - result.receipt.gas_used,
|
||||
output: result.output
|
||||
output: result.output,
|
||||
vm_trace: result.vm_trace,
|
||||
}
|
||||
},
|
||||
Err(error) => TransactResult::Err {
|
||||
@ -230,7 +231,7 @@ impl<'a> EvmTestClient<'a> {
|
||||
}
|
||||
|
||||
/// A result of applying transaction to the state.
|
||||
pub enum TransactResult {
|
||||
pub enum TransactResult<T> {
|
||||
/// Successful execution
|
||||
Ok {
|
||||
/// State root
|
||||
@ -239,6 +240,8 @@ pub enum TransactResult {
|
||||
gas_left: U256,
|
||||
/// Output
|
||||
output: Vec<u8>,
|
||||
/// VM Traces
|
||||
vm_trace: Option<T>,
|
||||
},
|
||||
/// Transaction failed to run
|
||||
Err {
|
||||
|
@ -29,7 +29,7 @@ use std::fmt;
|
||||
|
||||
/// Transaction execution receipt.
|
||||
#[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.
|
||||
pub exception: Option<vm::Error>,
|
||||
|
||||
@ -63,9 +63,9 @@ pub struct Executed {
|
||||
/// Transaction output.
|
||||
pub output: Bytes,
|
||||
/// The trace of this transaction.
|
||||
pub trace: Vec<FlatTrace>,
|
||||
pub trace: Vec<T>,
|
||||
/// The VM trace of this transaction.
|
||||
pub vm_trace: Option<VMTrace>,
|
||||
pub vm_trace: Option<V>,
|
||||
/// The state diff, if we traced it.
|
||||
pub state_diff: Option<StateDiff>,
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ use evm::{CallType, Factory, Finalize, FinalizationResult};
|
||||
use vm::{self, Ext, CreateContractAddress, ReturnData, CleanDustMode, ActionParams, ActionValue};
|
||||
use wasm;
|
||||
use externalities::*;
|
||||
use trace::{self, FlatTrace, VMTrace, Tracer, VMTracer};
|
||||
use trace::{self, Tracer, VMTracer};
|
||||
use transaction::{Action, SignedTransaction};
|
||||
use crossbeam;
|
||||
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.
|
||||
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)
|
||||
}
|
||||
@ -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.
|
||||
/// 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>)
|
||||
-> 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 balance = self.state.balance(&sender)?;
|
||||
@ -244,7 +244,7 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> {
|
||||
output_from_create: bool,
|
||||
mut tracer: T,
|
||||
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 nonce = self.state.nonce(&sender)?;
|
||||
|
||||
@ -587,15 +587,15 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> {
|
||||
}
|
||||
|
||||
/// Finalizes the transaction (does refunds and suicides).
|
||||
fn finalize(
|
||||
fn finalize<T, V>(
|
||||
&mut self,
|
||||
t: &SignedTransaction,
|
||||
mut substate: Substate,
|
||||
result: vm::Result<FinalizationResult>,
|
||||
output: Bytes,
|
||||
trace: Vec<FlatTrace>,
|
||||
vm_trace: Option<VMTrace>
|
||||
) -> ExecutionResult {
|
||||
trace: Vec<T>,
|
||||
vm_trace: Option<V>
|
||||
) -> Result<Executed<T, V>, ExecutionError> {
|
||||
let schedule = self.machine.schedule(self.info.number);
|
||||
|
||||
// refunds from SSTORE nonzero -> zero
|
||||
|
@ -62,19 +62,19 @@ pub use self::backend::Backend;
|
||||
pub use self::substate::Substate;
|
||||
|
||||
/// Used to return information about an `State::apply` operation.
|
||||
pub struct ApplyOutcome {
|
||||
pub struct ApplyOutcome<T, V> {
|
||||
/// The receipt for the applied transaction.
|
||||
pub receipt: Receipt,
|
||||
/// The output of the applied transaction.
|
||||
pub output: Bytes,
|
||||
/// 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.
|
||||
pub vm_trace: Option<VMTrace>
|
||||
pub vm_trace: Option<V>
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[derive(Debug, Clone)]
|
||||
@ -668,7 +668,7 @@ impl<B: Backend> State<B> {
|
||||
|
||||
/// Execute a given transaction, producing a receipt and an optional trace.
|
||||
/// 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 {
|
||||
let options = TransactOptions::with_tracing();
|
||||
self.apply_with_tracing(env_info, machine, t, options.tracer, options.vm_tracer)
|
||||
@ -687,7 +687,7 @@ impl<B: Backend> State<B> {
|
||||
t: &SignedTransaction,
|
||||
tracer: T,
|
||||
vm_tracer: V,
|
||||
) -> ApplyResult where
|
||||
) -> ApplyResult<T::Output, V::Output> where
|
||||
T: trace::Tracer,
|
||||
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
|
||||
// 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)
|
||||
-> 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);
|
||||
|
||||
|
@ -83,6 +83,8 @@ fn should_prefix_address_properly() {
|
||||
}
|
||||
|
||||
impl Tracer for ExecutiveTracer {
|
||||
type Output = FlatTrace;
|
||||
|
||||
fn prepare_trace_call(&self, params: &ActionParams) -> Option<Call> {
|
||||
Some(Call::from(params.clone()))
|
||||
}
|
||||
@ -201,6 +203,8 @@ impl ExecutiveVMTracer {
|
||||
}
|
||||
|
||||
impl VMTracer for ExecutiveVMTracer {
|
||||
type Output = VMTrace;
|
||||
|
||||
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) {
|
||||
|
@ -48,6 +48,9 @@ use header::BlockNumber;
|
||||
|
||||
/// This trait is used by executive to build traces.
|
||||
pub trait Tracer: Send {
|
||||
/// Data returned when draining the Tracer.
|
||||
type Output;
|
||||
|
||||
/// Prepares call trace for given params. Noop tracer should return None.
|
||||
fn prepare_trace_call(&self, params: &ActionParams) -> Option<Call>;
|
||||
|
||||
@ -63,7 +66,7 @@ pub trait Tracer: Send {
|
||||
call: Option<Call>,
|
||||
gas_used: U256,
|
||||
output: Option<Bytes>,
|
||||
subs: Vec<FlatTrace>,
|
||||
subs: Vec<Self::Output>,
|
||||
);
|
||||
|
||||
/// Stores trace create info.
|
||||
@ -73,14 +76,14 @@ pub trait Tracer: Send {
|
||||
gas_used: U256,
|
||||
code: Option<Bytes>,
|
||||
address: Address,
|
||||
subs: Vec<FlatTrace>
|
||||
subs: Vec<Self::Output>
|
||||
);
|
||||
|
||||
/// 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.
|
||||
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.
|
||||
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;
|
||||
|
||||
/// Consumes self and returns all traces.
|
||||
fn drain(self) -> Vec<FlatTrace>;
|
||||
fn drain(self) -> Vec<Self::Output>;
|
||||
}
|
||||
|
||||
/// Used by executive to build VM traces.
|
||||
pub trait VMTracer: Send {
|
||||
|
||||
/// Data returned when draining the VMTracer.
|
||||
type Output;
|
||||
|
||||
/// 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.
|
||||
@ -116,7 +122,7 @@ pub trait VMTracer: Send {
|
||||
fn done_subtrace(&mut self, sub: Self) where Self: Sized;
|
||||
|
||||
/// 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,
|
||||
|
@ -27,6 +27,8 @@ use trace::trace::{Call, Create, VMTrace, RewardType};
|
||||
pub struct NoopTracer;
|
||||
|
||||
impl Tracer for NoopTracer {
|
||||
type Output = FlatTrace;
|
||||
|
||||
fn prepare_trace_call(&self, _: &ActionParams) -> Option<Call> {
|
||||
None
|
||||
}
|
||||
@ -76,6 +78,8 @@ impl Tracer for NoopTracer {
|
||||
pub struct NoopVMTracer;
|
||||
|
||||
impl VMTracer for NoopVMTracer {
|
||||
type Output = VMTrace;
|
||||
|
||||
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) {}
|
||||
|
@ -22,5 +22,8 @@ evm = { path = "../ethcore/evm" }
|
||||
vm = { path = "../ethcore/vm" }
|
||||
panic_hook = { path = "../panic_hook" }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.1"
|
||||
|
||||
[features]
|
||||
evm-debug = ["ethcore/evm-debug-tests"]
|
||||
|
@ -16,11 +16,13 @@
|
||||
|
||||
//! JSON VM output.
|
||||
|
||||
use ethcore::trace;
|
||||
use std::collections::HashMap;
|
||||
use bigint::prelude::U256;
|
||||
use std::mem;
|
||||
|
||||
use bigint::hash::H256;
|
||||
use bigint::prelude::U256;
|
||||
use bytes::ToPretty;
|
||||
use ethcore::trace;
|
||||
|
||||
use display;
|
||||
use info as vm;
|
||||
@ -37,6 +39,9 @@ pub struct Informant {
|
||||
stack: Vec<U256>,
|
||||
memory: Vec<u8>,
|
||||
storage: HashMap<H256, H256>,
|
||||
traces: Vec<String>,
|
||||
subtraces: Vec<String>,
|
||||
unmatched: bool,
|
||||
}
|
||||
|
||||
impl Informant {
|
||||
@ -70,28 +75,43 @@ impl vm::Informant for Informant {
|
||||
self.gas_used = gas;
|
||||
}
|
||||
|
||||
fn finish(result: Result<vm::Success, vm::Failure>) {
|
||||
fn finish(result: vm::RunResult<Self::Output>) {
|
||||
match result {
|
||||
Ok(success) => 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),
|
||||
),
|
||||
Ok(success) => {
|
||||
for trace in success.traces.unwrap_or_else(Vec::new) {
|
||||
println!("{}", trace);
|
||||
}
|
||||
|
||||
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) => {
|
||||
for trace in failure.traces.unwrap_or_else(Vec::new) {
|
||||
println!("{}", trace);
|
||||
}
|
||||
|
||||
println!(
|
||||
"{{\"error\":\"{error}\",\"gasUsed\":\"{gas:x}\",\"time\":{time}}}",
|
||||
error = failure.error,
|
||||
gas = failure.gas_used,
|
||||
time = display::as_micros(&failure.time),
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl trace::VMTracer for Informant {
|
||||
type Output = Vec<String>;
|
||||
|
||||
fn trace_next_instruction(&mut self, pc: usize, instruction: u8) -> bool {
|
||||
self.pc = pc;
|
||||
self.instruction = instruction;
|
||||
self.unmatched = 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)>) {
|
||||
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 = self.pc,
|
||||
op = self.instruction,
|
||||
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),
|
||||
memory = self.memory(),
|
||||
stack = self.stack(),
|
||||
storage = self.storage(),
|
||||
depth = self.depth,
|
||||
);
|
||||
self.traces.push(trace);
|
||||
|
||||
self.unmatched = false;
|
||||
self.gas_used = gas_used;
|
||||
|
||||
let len = self.stack.len();
|
||||
@ -133,6 +155,11 @@ impl trace::VMTracer for Informant {
|
||||
if let Some((pos, val)) = store_diff {
|
||||
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 {
|
||||
@ -143,14 +170,21 @@ impl trace::VMTracer for Informant {
|
||||
vm
|
||||
}
|
||||
|
||||
fn done_subtrace(&mut self, mut sub: Self) {
|
||||
if sub.depth == 1 {
|
||||
// print last line with final state:
|
||||
sub.gas_cost = 0.into();
|
||||
let gas_used = sub.gas_used;
|
||||
trace::VMTracer::trace_executed(&mut sub, gas_used, &[], None, None);
|
||||
fn done_subtrace(&mut self, sub: Self) {
|
||||
if let Some(subtraces) = sub.drain() {
|
||||
self.subtraces.extend(subtraces);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ impl vm::Informant for Informant {
|
||||
println!("Test: {} ({})", name, action);
|
||||
}
|
||||
|
||||
fn finish(result: Result<vm::Success, vm::Failure>) {
|
||||
fn finish(result: vm::RunResult<Self::Output>) {
|
||||
match result {
|
||||
Ok(success) => {
|
||||
println!("Output: 0x{}", success.output.to_hex());
|
||||
@ -47,7 +47,9 @@ impl vm::Informant for Informant {
|
||||
}
|
||||
|
||||
impl trace::VMTracer for Informant {
|
||||
type Output = ();
|
||||
|
||||
fn prepare_subtrace(&self, _code: &[u8]) -> Self where Self: Sized { Default::default() }
|
||||
fn done_subtrace(&mut self, _sub: Self) {}
|
||||
fn drain(self) -> Option<trace::VMTrace> { None }
|
||||
fn drain(self) -> Option<()> { None }
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ use bigint::hash::H256;
|
||||
use ethcore::{trace, spec, transaction, pod_state};
|
||||
use ethcore::client::{self, EvmTestClient, EvmTestError, TransactResult};
|
||||
use ethjson;
|
||||
use vm::ActionParams;
|
||||
|
||||
/// VM execution informant
|
||||
pub trait Informant: trace::VMTracer {
|
||||
@ -30,27 +31,51 @@ pub trait Informant: trace::VMTracer {
|
||||
/// Set initial gas.
|
||||
fn set_gas(&mut self, _gas: U256) {}
|
||||
/// Display final result.
|
||||
fn finish(result: Result<Success, Failure>);
|
||||
fn finish(result: RunResult<Self::Output>);
|
||||
}
|
||||
|
||||
/// Execution finished correctly
|
||||
pub struct Success {
|
||||
#[derive(Debug)]
|
||||
pub struct Success<T> {
|
||||
/// Used gas
|
||||
pub gas_used: U256,
|
||||
/// Output as bytes
|
||||
pub output: Vec<u8>,
|
||||
/// Time Taken
|
||||
pub time: Duration,
|
||||
/// Traces
|
||||
pub traces: Option<T>,
|
||||
}
|
||||
|
||||
/// Execution failed
|
||||
pub struct Failure {
|
||||
#[derive(Debug)]
|
||||
pub struct Failure<T> {
|
||||
/// Used gas
|
||||
pub gas_used: U256,
|
||||
/// Internal error
|
||||
pub error: EvmTestError,
|
||||
/// 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.
|
||||
@ -82,19 +107,19 @@ pub fn run_transaction<T: Informant>(
|
||||
let result = client.transact(env_info, transaction, informant);
|
||||
match result {
|
||||
TransactResult::Ok { state_root, .. } if state_root != post_root => {
|
||||
Err(EvmTestError::PostCondition(format!(
|
||||
(Err(EvmTestError::PostCondition(format!(
|
||||
"State root mismatch (got: {}, expected: {})",
|
||||
state_root,
|
||||
post_root,
|
||||
)))
|
||||
))), None)
|
||||
},
|
||||
TransactResult::Ok { gas_left, output, .. } => {
|
||||
Ok((gas_left, output))
|
||||
TransactResult::Ok { gas_left, output, vm_trace, .. } => {
|
||||
(Ok((gas_left, output)), vm_trace)
|
||||
},
|
||||
TransactResult::Err { error, .. } => {
|
||||
Err(EvmTestError::PostCondition(format!(
|
||||
(Err(EvmTestError::PostCondition(format!(
|
||||
"Unexpected execution error: {:?}", error
|
||||
)))
|
||||
))), None)
|
||||
},
|
||||
}
|
||||
});
|
||||
@ -103,8 +128,13 @@ pub fn run_transaction<T: Informant>(
|
||||
}
|
||||
|
||||
/// 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
|
||||
F: FnOnce(EvmTestClient) -> Result<(U256, Vec<u8>), EvmTestError>,
|
||||
pub fn run<'a, F, T, X>(
|
||||
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>>,
|
||||
{
|
||||
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 {
|
||||
gas_used: 0.into(),
|
||||
error,
|
||||
time: Duration::from_secs(0)
|
||||
time: Duration::from_secs(0),
|
||||
traces: None,
|
||||
})?;
|
||||
|
||||
let start = Instant::now();
|
||||
let result = run(test_client);
|
||||
let duration = start.elapsed();
|
||||
let time = start.elapsed();
|
||||
|
||||
match result {
|
||||
Ok((gas_left, output)) => Ok(Success {
|
||||
(Ok((gas_left, output)), traces) => Ok(Success {
|
||||
gas_used: initial_gas - gas_left,
|
||||
output: output,
|
||||
time: duration,
|
||||
output,
|
||||
time,
|
||||
traces,
|
||||
}),
|
||||
Err(e) => Err(Failure {
|
||||
(Err(error), traces) => Err(Failure {
|
||||
gas_used: initial_gas,
|
||||
error: e,
|
||||
time: duration,
|
||||
error,
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,10 @@ extern crate vm;
|
||||
extern crate evm;
|
||||
extern crate panic_hook;
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate pretty_assertions;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, fs};
|
||||
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 to = arg(args.to(), "--to");
|
||||
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.data = data;
|
||||
|
||||
informant.set_gas(gas);
|
||||
let result = info::run(&spec, gas, None, |mut client| {
|
||||
client.call(params, &mut informant).map(|r| (r.gas_left, r.return_data.to_vec()))
|
||||
});
|
||||
let result = info::run_action(&spec, params, informant);
|
||||
T::finish(result);
|
||||
}
|
||||
|
||||
@ -187,7 +188,7 @@ impl Args {
|
||||
pub fn gas(&self) -> Result<U256, String> {
|
||||
match self.flag_gas {
|
||||
Some(ref gas) => gas.parse().map_err(to_string),
|
||||
None => Ok(!U256::zero()),
|
||||
None => Ok(U256::from(u64::max_value())),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,9 +60,9 @@ hash = { path = "../util/hash" }
|
||||
hardware-wallet = { path = "../hw" }
|
||||
|
||||
clippy = { version = "0.0.103", optional = true}
|
||||
pretty_assertions = "0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.1"
|
||||
macros = { path = "../util/macros" }
|
||||
ethcore-network = { path = "../util/network" }
|
||||
kvdb-memorydb = { path = "../util/kvdb-memorydb" }
|
||||
|
Loading…
Reference in New Issue
Block a user