From da2c2e5fc67010dbd6e6300d856b0dfa06f0b569 Mon Sep 17 00:00:00 2001 From: Marek Kotewicz Date: Mon, 5 Sep 2016 11:56:44 +0200 Subject: [PATCH] facelift for traces, added errors (#2042) * evm errors facelift * facelift for traces, added errors with description * additional tests for traces json serialization --- ethcore/src/evm/evm.rs | 115 +++++----- ethcore/src/executive.rs | 6 +- ethcore/src/state/mod.rs | 11 +- ethcore/src/trace/db.rs | 6 +- ethcore/src/trace/executive_tracer.rs | 10 +- ethcore/src/trace/mod.rs | 7 +- ethcore/src/trace/noop_tracer.rs | 6 +- ethcore/src/types/trace_types/error.rs | 99 +++++++++ ethcore/src/types/trace_types/filter.rs | 4 +- ethcore/src/types/trace_types/mod.rs | 1 + ethcore/src/types/trace_types/trace.rs | 21 +- rpc/src/v1/types/trace.rs | 265 +++++++++++++++++------- 12 files changed, 391 insertions(+), 160 deletions(-) create mode 100644 ethcore/src/types/trace_types/error.rs diff --git a/ethcore/src/evm/evm.rs b/ethcore/src/evm/evm.rs index 2bda9c488..813819250 100644 --- a/ethcore/src/evm/evm.rs +++ b/ethcore/src/evm/evm.rs @@ -16,11 +16,13 @@ //! Evm interface. -use common::*; +use std::{ops, cmp, fmt}; +use util::{U128, U256, U512, Uint}; +use action_params::ActionParams; use evm::Ext; /// Evm errors. -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub enum Error { /// `OutOfGas` is returned when transaction execution runs out of gas. /// The state should be reverted to the state from before the @@ -63,6 +65,21 @@ pub enum Error { Internal, } +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::Error::*; + let message = match *self { + OutOfGas => "Out of gas", + BadJumpDestination { .. } => "Bad jump destination", + BadInstruction { .. } => "Bad instruction", + StackUnderflow { .. } => "Stack underflow", + OutOfStack { .. } => "Out of stack", + Internal => "Internal error", + }; + message.fmt(f) + } +} + /// A specialized version of Result over EVM errors. pub type Result = ::std::result::Result; @@ -193,53 +210,55 @@ pub trait Evm { fn exec(&mut self, params: ActionParams, ext: &mut Ext) -> Result; } - -#[test] #[cfg(test)] -fn should_calculate_overflow_mul_shr_without_overflow() { - // given - let num = 1048576; +mod tests { + use util::{U256, Uint}; + use super::CostType; - // when - let (res1, o1) = U256::from(num).overflow_mul_shr(U256::from(num), 20); - let (res2, o2) = num.overflow_mul_shr(num, 20); + #[test] + fn should_calculate_overflow_mul_shr_without_overflow() { + // given + let num = 1048576; - // then - assert_eq!(res1, U256::from(num)); - assert!(!o1); - assert_eq!(res2, num); - assert!(!o2); -} - -#[test] -#[cfg(test)] -fn should_calculate_overflow_mul_shr_with_overflow() { - // given - let max = ::std::u64::MAX; - let num1 = U256([max, max, max, max]); - let num2 = ::std::usize::MAX; - - // when - let (res1, o1) = num1.overflow_mul_shr(num1, 256); - let (res2, o2) = num2.overflow_mul_shr(num2, 64); - - // then - assert_eq!(res2, num2 - 1); - assert!(o2); - - assert_eq!(res1, !U256::zero() - U256::one()); - assert!(o1); -} - -#[test] -#[cfg(test)] -fn should_validate_u256_to_usize_conversion() { - // given - let v = U256::from(::std::usize::MAX) + U256::from(1); - - // when - let res = usize::from_u256(v); - - // then - assert!(res.is_err()); + // when + let (res1, o1) = U256::from(num).overflow_mul_shr(U256::from(num), 20); + let (res2, o2) = num.overflow_mul_shr(num, 20); + + // then + assert_eq!(res1, U256::from(num)); + assert!(!o1); + assert_eq!(res2, num); + assert!(!o2); + } + + #[test] + fn should_calculate_overflow_mul_shr_with_overflow() { + // given + let max = u64::max_value(); + let num1 = U256([max, max, max, max]); + let num2 = usize::max_value(); + + // when + let (res1, o1) = num1.overflow_mul_shr(num1, 256); + let (res2, o2) = num2.overflow_mul_shr(num2, 64); + + // then + assert_eq!(res2, num2 - 1); + assert!(o2); + + assert_eq!(res1, !U256::zero() - U256::one()); + assert!(o1); + } + + #[test] + fn should_validate_u256_to_usize_conversion() { + // given + let v = U256::from(usize::max_value()) + U256::from(1); + + // when + let res = usize::from_u256(v); + + // then + assert!(res.is_err()); + } } diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index 64771f95d..332eda190 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -286,7 +286,7 @@ impl<'a> Executive<'a> { // just drain the whole gas self.state.revert_snapshot(); - tracer.trace_failed_call(trace_info, vec![]); + tracer.trace_failed_call(trace_info, vec![], evm::Error::OutOfGas.into()); Err(evm::Error::OutOfGas) } @@ -320,7 +320,7 @@ impl<'a> Executive<'a> { trace_output, traces ), - _ => tracer.trace_failed_call(trace_info, traces), + Err(e) => tracer.trace_failed_call(trace_info, traces, e.into()), }; trace!(target: "executive", "substate={:?}; unconfirmed_substate={:?}\n", substate, unconfirmed_substate); @@ -385,7 +385,7 @@ impl<'a> Executive<'a> { created, subtracer.traces() ), - _ => tracer.trace_failed_create(trace_info, subtracer.traces()) + Err(e) => tracer.trace_failed_create(trace_info, subtracer.traces(), e.into()) }; self.enact_result(&res, substate, unconfirmed_substate); diff --git a/ethcore/src/state/mod.rs b/ethcore/src/state/mod.rs index 46e77cd34..7a3b7b7ee 100644 --- a/ethcore/src/state/mod.rs +++ b/ethcore/src/state/mod.rs @@ -444,8 +444,7 @@ use env_info::*; use spec::*; use transaction::*; use util::log::init_log; -use trace::trace; -use trace::FlatTrace; +use trace::{FlatTrace, TraceError, trace}; use types::executed::CallType; #[test] @@ -538,7 +537,7 @@ fn should_trace_failed_create_transaction() { gas: 78792.into(), init: vec![91, 96, 0, 86], }), - result: trace::Res::FailedCreate, + result: trace::Res::FailedCreate(TraceError::OutOfGas), subtraces: 0 }]; @@ -869,7 +868,7 @@ fn should_trace_failed_call_transaction() { input: vec![], call_type: CallType::Call, }), - result: trace::Res::FailedCall, + result: trace::Res::FailedCall(TraceError::OutOfGas), subtraces: 0, }]; @@ -1084,7 +1083,7 @@ fn should_trace_failed_subcall_transaction() { input: vec![], call_type: CallType::Call, }), - result: trace::Res::FailedCall, + result: trace::Res::FailedCall(TraceError::OutOfGas), }]; assert_eq!(result.trace, expected_trace); @@ -1217,7 +1216,7 @@ fn should_trace_failed_subcall_with_subcall_transaction() { input: vec![], call_type: CallType::Call, }), - result: trace::Res::FailedCall, + result: trace::Res::FailedCall(TraceError::OutOfGas), }, FlatTrace { trace_address: vec![0, 0].into_iter().collect(), subtraces: 0, diff --git a/ethcore/src/trace/db.rs b/ethcore/src/trace/db.rs index d295e084c..e7bd7c825 100644 --- a/ethcore/src/trace/db.rs +++ b/ethcore/src/trace/db.rs @@ -420,7 +420,7 @@ mod tests { use devtools::RandomTempPath; use header::BlockNumber; use trace::{Config, Switch, TraceDB, Database as TraceDatabase, DatabaseExtras, ImportRequest}; - use trace::{Filter, LocalizedTrace, AddressesFilter}; + use trace::{Filter, LocalizedTrace, AddressesFilter, TraceError}; use trace::trace::{Call, Action, Res}; use trace::flat::{FlatTrace, FlatBlockTraces, FlatTransactionTraces}; use types::executed::CallType; @@ -560,7 +560,7 @@ mod tests { input: vec![], call_type: CallType::Call, }), - result: Res::FailedCall, + result: Res::FailedCall(TraceError::OutOfGas), }])]), block_hash: block_hash.clone(), block_number: block_number, @@ -579,7 +579,7 @@ mod tests { input: vec![], call_type: CallType::Call, }), - result: Res::FailedCall, + result: Res::FailedCall(TraceError::OutOfGas), trace_address: vec![], subtraces: 0, transaction_number: 0, diff --git a/ethcore/src/trace/executive_tracer.rs b/ethcore/src/trace/executive_tracer.rs index 9963a9f27..b086b2378 100644 --- a/ethcore/src/trace/executive_tracer.rs +++ b/ethcore/src/trace/executive_tracer.rs @@ -19,7 +19,7 @@ use util::{Bytes, Address, U256}; use action_params::ActionParams; use trace::trace::{Call, Create, Action, Res, CreateResult, CallResult, VMTrace, VMOperation, VMExecutedOperation, MemoryDiff, StorageDiff, Suicide}; -use trace::{Tracer, VMTracer, FlatTrace}; +use trace::{Tracer, VMTracer, FlatTrace, TraceError}; /// Simple executive tracer. Traces all calls and creates. Ignores delegatecalls. #[derive(Default)] @@ -112,23 +112,23 @@ impl Tracer for ExecutiveTracer { self.traces.extend(update_trace_address(subs)); } - fn trace_failed_call(&mut self, call: Option, subs: Vec) { + fn trace_failed_call(&mut self, call: Option, subs: Vec, error: TraceError) { let trace = FlatTrace { trace_address: Default::default(), subtraces: top_level_subtraces(&subs), action: Action::Call(call.expect("self.prepare_trace_call().is_some(): so we must be tracing: qed")), - result: Res::FailedCall, + result: Res::FailedCall(error), }; debug!(target: "trace", "Traced failed call {:?}", trace); self.traces.push(trace); self.traces.extend(update_trace_address(subs)); } - fn trace_failed_create(&mut self, create: Option, subs: Vec) { + fn trace_failed_create(&mut self, create: Option, subs: Vec, error: TraceError) { let trace = FlatTrace { subtraces: top_level_subtraces(&subs), action: Action::Create(create.expect("self.prepare_trace_create().is_some(): so we must be tracing: qed")), - result: Res::FailedCreate, + result: Res::FailedCreate(error), trace_address: Default::default(), }; debug!(target: "trace", "Traced failed create {:?}", trace); diff --git a/ethcore/src/trace/mod.rs b/ethcore/src/trace/mod.rs index 4b6b4ad92..06604450f 100644 --- a/ethcore/src/trace/mod.rs +++ b/ethcore/src/trace/mod.rs @@ -24,7 +24,8 @@ mod executive_tracer; mod import; mod noop_tracer; -pub use types::trace_types::*; +pub use types::trace_types::{filter, flat, localized, trace}; +pub use types::trace_types::error::Error as TraceError; pub use self::config::{Config, Switch}; pub use self::db::TraceDB; pub use self::error::Error; @@ -71,10 +72,10 @@ pub trait Tracer: Send { ); /// Stores failed call trace. - fn trace_failed_call(&mut self, call: Option, subs: Vec); + fn trace_failed_call(&mut self, call: Option, subs: Vec, error: TraceError); /// Stores failed create trace. - fn trace_failed_create(&mut self, create: Option, subs: Vec); + fn trace_failed_create(&mut self, create: Option, subs: Vec, error: TraceError); /// Stores suicide info. fn trace_suicide(&mut self, address: Address, balance: U256, refund_address: Address); diff --git a/ethcore/src/trace/noop_tracer.rs b/ethcore/src/trace/noop_tracer.rs index 9ae8e2561..f126d85aa 100644 --- a/ethcore/src/trace/noop_tracer.rs +++ b/ethcore/src/trace/noop_tracer.rs @@ -18,7 +18,7 @@ use util::{Bytes, Address, U256}; use action_params::ActionParams; -use trace::{Tracer, VMTracer, FlatTrace}; +use trace::{Tracer, VMTracer, FlatTrace, TraceError}; use trace::trace::{Call, Create, VMTrace}; /// Nonoperative tracer. Does not trace anything. @@ -47,11 +47,11 @@ impl Tracer for NoopTracer { assert!(code.is_none(), "self.prepare_trace_output().is_none(): so we can't be tracing: qed"); } - fn trace_failed_call(&mut self, call: Option, _: Vec) { + fn trace_failed_call(&mut self, call: Option, _: Vec, _: TraceError) { assert!(call.is_none(), "self.prepare_trace_call().is_none(): so we can't be tracing: qed"); } - fn trace_failed_create(&mut self, create: Option, _: Vec) { + fn trace_failed_create(&mut self, create: Option, _: Vec, _: TraceError) { assert!(create.is_none(), "self.prepare_trace_create().is_none(): so we can't be tracing: qed"); } diff --git a/ethcore/src/types/trace_types/error.rs b/ethcore/src/types/trace_types/error.rs new file mode 100644 index 000000000..5b2510c81 --- /dev/null +++ b/ethcore/src/types/trace_types/error.rs @@ -0,0 +1,99 @@ +// Copyright 2015, 2016 Ethcore (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 . + +//! Trace errors. + +use std::fmt; +use rlp::{Encodable, RlpStream, Decodable, Decoder, DecoderError, Stream, View}; +use evm::Error as EvmError; + +/// Trace evm errors. +#[derive(Debug, PartialEq, Clone, Binary)] +pub enum Error { + /// `OutOfGas` is returned when transaction execution runs out of gas. + OutOfGas, + /// `BadJumpDestination` is returned when execution tried to move + /// to position that wasn't marked with JUMPDEST instruction + BadJumpDestination, + /// `BadInstructions` is returned when given instruction is not supported + BadInstruction, + /// `StackUnderflow` when there is not enough stack elements to execute instruction + StackUnderflow, + /// When execution would exceed defined Stack Limit + OutOfStack, + /// Returned on evm internal error. Should never be ignored during development. + /// Likely to cause consensus issues. + Internal, +} + +impl From for Error { + fn from(e: EvmError) -> Self { + match e { + EvmError::OutOfGas => Error::OutOfGas, + EvmError::BadJumpDestination { .. } => Error::BadJumpDestination, + EvmError::BadInstruction { .. } => Error::BadInstruction, + EvmError::StackUnderflow { .. } => Error::StackUnderflow, + EvmError::OutOfStack { .. } => Error::OutOfStack, + EvmError::Internal => Error::Internal, + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::Error::*; + let message = match *self { + OutOfGas => "Out of gas", + BadJumpDestination => "Bad jump destination", + BadInstruction => "Bad instruction", + StackUnderflow => "Stack underflow", + OutOfStack => "Out of stack", + Internal => "Internal error", + }; + message.fmt(f) + } +} + +impl Encodable for Error { + fn rlp_append(&self, s: &mut RlpStream) { + use self::Error::*; + let value = match *self { + OutOfGas => 0u8, + BadJumpDestination => 1, + BadInstruction => 2, + StackUnderflow => 3, + OutOfStack => 4, + Internal => 5, + }; + s.append(&value); + } +} + +impl Decodable for Error { + fn decode(decoder: &D) -> Result where D: Decoder { + use self::Error::*; + let value: u8 = try!(decoder.as_rlp().as_val()); + match value { + 0 => Ok(OutOfGas), + 1 => Ok(BadJumpDestination), + 2 => Ok(BadInstruction), + 3 => Ok(StackUnderflow), + 4 => Ok(OutOfStack), + 5 => Ok(Internal), + _ => Err(DecoderError::Custom("Invalid error type")), + } + } +} diff --git a/ethcore/src/types/trace_types/filter.rs b/ethcore/src/types/trace_types/filter.rs index 89457bcb3..2d35718c0 100644 --- a/ethcore/src/types/trace_types/filter.rs +++ b/ethcore/src/types/trace_types/filter.rs @@ -140,7 +140,7 @@ mod tests { use util::bloom::Bloomable; use trace::trace::{Action, Call, Res, Create, CreateResult, Suicide}; use trace::flat::FlatTrace; - use trace::{Filter, AddressesFilter}; + use trace::{Filter, AddressesFilter, TraceError}; use types::executed::CallType; #[test] @@ -286,7 +286,7 @@ mod tests { input: vec![0x5], call_type: CallType::Call, }), - result: Res::FailedCall, + result: Res::FailedCall(TraceError::OutOfGas), trace_address: vec![0].into_iter().collect(), subtraces: 0, }; diff --git a/ethcore/src/types/trace_types/mod.rs b/ethcore/src/types/trace_types/mod.rs index 7b5c93790..f940263b2 100644 --- a/ethcore/src/types/trace_types/mod.rs +++ b/ethcore/src/types/trace_types/mod.rs @@ -16,6 +16,7 @@ //! Types used in the public api +pub mod error; pub mod filter; pub mod flat; pub mod trace; diff --git a/ethcore/src/types/trace_types/trace.rs b/ethcore/src/types/trace_types/trace.rs index 7261107e6..9efeaa001 100644 --- a/ethcore/src/types/trace_types/trace.rs +++ b/ethcore/src/types/trace_types/trace.rs @@ -24,6 +24,7 @@ use rlp::*; use action_params::ActionParams; use basic_types::LogBloom; use types::executed::CallType; +use super::error::Error; /// `Call` result. #[derive(Debug, Clone, PartialEq, Default, Binary)] @@ -322,9 +323,9 @@ pub enum Res { /// Successful create action result. Create(CreateResult), /// Failed call. - FailedCall, + FailedCall(Error), /// Failed create. - FailedCreate, + FailedCreate(Error), /// None None, } @@ -342,13 +343,15 @@ impl Encodable for Res { s.append(&1u8); s.append(create); }, - Res::FailedCall => { - s.begin_list(1); + Res::FailedCall(ref err) => { + s.begin_list(2); s.append(&2u8); + s.append(err); }, - Res::FailedCreate => { - s.begin_list(1); + Res::FailedCreate(ref err) => { + s.begin_list(2); s.append(&3u8); + s.append(err); }, Res::None => { s.begin_list(1); @@ -365,8 +368,8 @@ impl Decodable for Res { match action_type { 0 => d.val_at(1).map(Res::Call), 1 => d.val_at(1).map(Res::Create), - 2 => Ok(Res::FailedCall), - 3 => Ok(Res::FailedCreate), + 2 => d.val_at(1).map(Res::FailedCall), + 3 => d.val_at(1).map(Res::FailedCreate), 4 => Ok(Res::None), _ => Err(DecoderError::Custom("Invalid result type.")), } @@ -378,7 +381,7 @@ impl Res { pub fn bloom(&self) -> LogBloom { match *self { Res::Create(ref create) => create.bloom(), - Res::Call(_) | Res::FailedCall | Res::FailedCreate | Res::None => Default::default(), + Res::Call(_) | Res::FailedCall(_) | Res::FailedCreate(_) | Res::None => Default::default(), } } } diff --git a/rpc/src/v1/types/trace.rs b/rpc/src/v1/types/trace.rs index e401a6cee..f66d8e0c1 100644 --- a/rpc/src/v1/types/trace.rs +++ b/rpc/src/v1/types/trace.rs @@ -16,8 +16,7 @@ use std::collections::BTreeMap; use serde::{Serialize, Serializer}; -use ethcore::trace::trace; -use ethcore::trace::{FlatTrace, LocalizedTrace as EthLocalizedTrace}; +use ethcore::trace::{FlatTrace, LocalizedTrace as EthLocalizedTrace, trace, TraceError}; use ethcore::trace as et; use ethcore::state_diff; use ethcore::account_diff; @@ -319,16 +318,13 @@ impl From for Suicide { } /// Action -#[derive(Debug, Serialize)] +#[derive(Debug)] pub enum Action { /// Call - #[serde(rename="call")] Call(Call), /// Create - #[serde(rename="create")] Create(Create), /// Suicide - #[serde(rename="suicide")] Suicide(Suicide), } @@ -384,22 +380,17 @@ impl From for CreateResult { } /// Response -#[derive(Debug, Serialize)] +#[derive(Debug)] pub enum Res { /// Call - #[serde(rename="call")] Call(CallResult), /// Create - #[serde(rename="create")] Create(CreateResult), /// Call failure - #[serde(rename="failedCall")] - FailedCall, + FailedCall(TraceError), /// Creation failure - #[serde(rename="failedCreate")] - FailedCreate, + FailedCreate(TraceError), /// None - #[serde(rename="none")] None, } @@ -408,39 +399,73 @@ impl From for Res { match t { trace::Res::Call(call) => Res::Call(CallResult::from(call)), trace::Res::Create(create) => Res::Create(CreateResult::from(create)), - trace::Res::FailedCall => Res::FailedCall, - trace::Res::FailedCreate => Res::FailedCreate, + trace::Res::FailedCall(error) => Res::FailedCall(error), + trace::Res::FailedCreate(error) => Res::FailedCreate(error), trace::Res::None => Res::None, } } } /// Trace -#[derive(Debug, Serialize)] +#[derive(Debug)] pub struct LocalizedTrace { /// Action action: Action, /// Result result: Res, /// Trace address - #[serde(rename="traceAddress")] trace_address: Vec, /// Subtraces subtraces: U256, /// Transaction position - #[serde(rename="transactionPosition")] transaction_position: U256, /// Transaction hash - #[serde(rename="transactionHash")] transaction_hash: H256, /// Block Number - #[serde(rename="blockNumber")] block_number: U256, /// Block Hash - #[serde(rename="blockHash")] block_hash: H256, } +impl Serialize for LocalizedTrace { + fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> + where S: Serializer + { + let mut state = try!(serializer.serialize_struct("LocalizedTrace", 9)); + match self.action { + Action::Call(ref call) => { + try!(serializer.serialize_struct_elt(&mut state, "type", "call")); + try!(serializer.serialize_struct_elt(&mut state, "action", call)); + }, + Action::Create(ref create) => { + try!(serializer.serialize_struct_elt(&mut state, "type", "create")); + try!(serializer.serialize_struct_elt(&mut state, "action", create)); + }, + Action::Suicide(ref suicide) => { + try!(serializer.serialize_struct_elt(&mut state, "type", "suicide")); + try!(serializer.serialize_struct_elt(&mut state, "action", suicide)); + }, + } + + match self.result { + Res::Call(ref call) => try!(serializer.serialize_struct_elt(&mut state, "result", call)), + Res::Create(ref create) => try!(serializer.serialize_struct_elt(&mut state, "result", create)), + Res::FailedCall(ref error) => try!(serializer.serialize_struct_elt(&mut state, "error", error.to_string())), + Res::FailedCreate(ref error) => try!(serializer.serialize_struct_elt(&mut state, "error", error.to_string())), + Res::None => try!(serializer.serialize_struct_elt(&mut state, "result", None as Option)), + } + + try!(serializer.serialize_struct_elt(&mut state, "traceAddress", &self.trace_address)); + try!(serializer.serialize_struct_elt(&mut state, "subtraces", &self.subtraces)); + try!(serializer.serialize_struct_elt(&mut state, "transactionPosition", &self.transaction_position)); + try!(serializer.serialize_struct_elt(&mut state, "transactionHash", &self.transaction_hash)); + try!(serializer.serialize_struct_elt(&mut state, "blockNumber", &self.block_number)); + try!(serializer.serialize_struct_elt(&mut state, "blockHash", &self.block_hash)); + + serializer.serialize_struct_end(state) + } +} + impl From for LocalizedTrace { fn from(t: EthLocalizedTrace) -> Self { LocalizedTrace { @@ -457,10 +482,9 @@ impl From for LocalizedTrace { } /// Trace -#[derive(Debug, Serialize)] +#[derive(Debug)] pub struct Trace { /// Trace address - #[serde(rename="traceAddress")] trace_address: Vec, /// Subtraces subtraces: U256, @@ -470,6 +494,41 @@ pub struct Trace { result: Res, } +impl Serialize for Trace { + fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> + where S: Serializer + { + let mut state = try!(serializer.serialize_struct("Trace", 4)); + match self.action { + Action::Call(ref call) => { + try!(serializer.serialize_struct_elt(&mut state, "type", "call")); + try!(serializer.serialize_struct_elt(&mut state, "action", call)); + }, + Action::Create(ref create) => { + try!(serializer.serialize_struct_elt(&mut state, "type", "create")); + try!(serializer.serialize_struct_elt(&mut state, "action", create)); + }, + Action::Suicide(ref suicide) => { + try!(serializer.serialize_struct_elt(&mut state, "type", "suicide")); + try!(serializer.serialize_struct_elt(&mut state, "action", suicide)); + }, + } + + match self.result { + Res::Call(ref call) => try!(serializer.serialize_struct_elt(&mut state, "result", call)), + Res::Create(ref create) => try!(serializer.serialize_struct_elt(&mut state, "result", create)), + Res::FailedCall(ref error) => try!(serializer.serialize_struct_elt(&mut state, "error", error.to_string())), + Res::FailedCreate(ref error) => try!(serializer.serialize_struct_elt(&mut state, "error", error.to_string())), + Res::None => try!(serializer.serialize_struct_elt(&mut state, "result", None as Option)), + } + + try!(serializer.serialize_struct_elt(&mut state, "traceAddress", &self.trace_address)); + try!(serializer.serialize_struct_elt(&mut state, "subtraces", &self.subtraces)); + + serializer.serialize_struct_end(state) + } +} + impl From for Trace { fn from(t: FlatTrace) -> Self { Trace { @@ -511,7 +570,8 @@ impl From for TraceResults { mod tests { use serde_json; use std::collections::BTreeMap; - use v1::types::{Bytes, U256, H256, H160}; + use v1::types::Bytes; + use ethcore::trace::TraceError; use super::*; #[test] @@ -527,29 +587,118 @@ mod tests { } #[test] - fn test_trace_serialize() { + fn test_trace_call_serialize() { let t = LocalizedTrace { action: Action::Call(Call { - from: H160::from(4), - to: H160::from(5), - value: U256::from(6), - gas: U256::from(7), + from: 4.into(), + to: 5.into(), + value: 6.into(), + gas: 7.into(), input: Bytes::new(vec![0x12, 0x34]), call_type: CallType::Call, }), result: Res::Call(CallResult { - gas_used: U256::from(8), + gas_used: 8.into(), output: vec![0x56, 0x78].into(), }), - trace_address: vec![U256::from(10)], - subtraces: U256::from(1), - transaction_position: U256::from(11), - transaction_hash: H256::from(12), - block_number: U256::from(13), - block_hash: H256::from(14), + trace_address: vec![10.into()], + subtraces: 1.into(), + transaction_position: 11.into(), + transaction_hash: 12.into(), + block_number: 13.into(), + block_hash: 14.into(), }; let serialized = serde_json::to_string(&t).unwrap(); - assert_eq!(serialized, r#"{"action":{"call":{"from":"0x0000000000000000000000000000000000000004","to":"0x0000000000000000000000000000000000000005","value":"0x6","gas":"0x7","input":"0x1234","callType":"call"}},"result":{"call":{"gasUsed":"0x8","output":"0x5678"}},"traceAddress":["0xa"],"subtraces":"0x1","transactionPosition":"0xb","transactionHash":"0x000000000000000000000000000000000000000000000000000000000000000c","blockNumber":"0xd","blockHash":"0x000000000000000000000000000000000000000000000000000000000000000e"}"#); + assert_eq!(serialized, r#"{"type":"call","action":{"from":"0x0000000000000000000000000000000000000004","to":"0x0000000000000000000000000000000000000005","value":"0x6","gas":"0x7","input":"0x1234","callType":"call"},"result":{"gasUsed":"0x8","output":"0x5678"},"traceAddress":["0xa"],"subtraces":"0x1","transactionPosition":"0xb","transactionHash":"0x000000000000000000000000000000000000000000000000000000000000000c","blockNumber":"0xd","blockHash":"0x000000000000000000000000000000000000000000000000000000000000000e"}"#); + } + + #[test] + fn test_trace_failed_call_serialize() { + let t = LocalizedTrace { + action: Action::Call(Call { + from: 4.into(), + to: 5.into(), + value: 6.into(), + gas: 7.into(), + input: Bytes::new(vec![0x12, 0x34]), + call_type: CallType::Call, + }), + result: Res::FailedCall(TraceError::OutOfGas), + trace_address: vec![10.into()], + subtraces: 1.into(), + transaction_position: 11.into(), + transaction_hash: 12.into(), + block_number: 13.into(), + block_hash: 14.into(), + }; + let serialized = serde_json::to_string(&t).unwrap(); + assert_eq!(serialized, r#"{"type":"call","action":{"from":"0x0000000000000000000000000000000000000004","to":"0x0000000000000000000000000000000000000005","value":"0x6","gas":"0x7","input":"0x1234","callType":"call"},"error":"Out of gas","traceAddress":["0xa"],"subtraces":"0x1","transactionPosition":"0xb","transactionHash":"0x000000000000000000000000000000000000000000000000000000000000000c","blockNumber":"0xd","blockHash":"0x000000000000000000000000000000000000000000000000000000000000000e"}"#); + } + + #[test] + fn test_trace_create_serialize() { + let t = LocalizedTrace { + action: Action::Create(Create { + from: 4.into(), + value: 6.into(), + gas: 7.into(), + init: Bytes::new(vec![0x12, 0x34]), + }), + result: Res::Create(CreateResult { + gas_used: 8.into(), + code: vec![0x56, 0x78].into(), + address: 0xff.into(), + }), + trace_address: vec![10.into()], + subtraces: 1.into(), + transaction_position: 11.into(), + transaction_hash: 12.into(), + block_number: 13.into(), + block_hash: 14.into(), + }; + let serialized = serde_json::to_string(&t).unwrap(); + assert_eq!(serialized, r#"{"type":"create","action":{"from":"0x0000000000000000000000000000000000000004","value":"0x6","gas":"0x7","init":"0x1234"},"result":{"gasUsed":"0x8","code":"0x5678","address":"0x00000000000000000000000000000000000000ff"},"traceAddress":["0xa"],"subtraces":"0x1","transactionPosition":"0xb","transactionHash":"0x000000000000000000000000000000000000000000000000000000000000000c","blockNumber":"0xd","blockHash":"0x000000000000000000000000000000000000000000000000000000000000000e"}"#); + } + + #[test] + fn test_trace_failed_create_serialize() { + let t = LocalizedTrace { + action: Action::Create(Create { + from: 4.into(), + value: 6.into(), + gas: 7.into(), + init: Bytes::new(vec![0x12, 0x34]), + }), + result: Res::FailedCreate(TraceError::OutOfGas), + trace_address: vec![10.into()], + subtraces: 1.into(), + transaction_position: 11.into(), + transaction_hash: 12.into(), + block_number: 13.into(), + block_hash: 14.into(), + }; + let serialized = serde_json::to_string(&t).unwrap(); + assert_eq!(serialized, r#"{"type":"create","action":{"from":"0x0000000000000000000000000000000000000004","value":"0x6","gas":"0x7","init":"0x1234"},"error":"Out of gas","traceAddress":["0xa"],"subtraces":"0x1","transactionPosition":"0xb","transactionHash":"0x000000000000000000000000000000000000000000000000000000000000000c","blockNumber":"0xd","blockHash":"0x000000000000000000000000000000000000000000000000000000000000000e"}"#); + } + + #[test] + fn test_trace_suicide_serialize() { + let t = LocalizedTrace { + action: Action::Suicide(Suicide { + address: 4.into(), + refund_address: 6.into(), + balance: 7.into(), + }), + result: Res::None, + trace_address: vec![10.into()], + subtraces: 1.into(), + transaction_position: 11.into(), + transaction_hash: 12.into(), + block_number: 13.into(), + block_hash: 14.into(), + }; + let serialized = serde_json::to_string(&t).unwrap(); + assert_eq!(serialized, r#"{"type":"suicide","action":{"address":"0x0000000000000000000000000000000000000004","refundAddress":"0x0000000000000000000000000000000000000006","balance":"0x7"},"result":null,"traceAddress":["0xa"],"subtraces":"0x1","transactionPosition":"0xb","transactionHash":"0x000000000000000000000000000000000000000000000000000000000000000c","blockNumber":"0xd","blockHash":"0x000000000000000000000000000000000000000000000000000000000000000e"}"#); } #[test] @@ -616,44 +765,4 @@ mod tests { let serialized = serde_json::to_string(&t).unwrap(); assert_eq!(serialized, r#"{"0x000000000000000000000000000000000000002a":{"balance":"=","nonce":{"+":"0x1"},"code":"=","storage":{"0x000000000000000000000000000000000000000000000000000000000000002a":"="}},"0x0000000000000000000000000000000000000045":{"balance":"=","nonce":{"*":{"from":"0x1","to":"0x0"}},"code":{"-":"0x60"},"storage":{}}}"#); } - - #[test] - fn test_action_serialize() { - let actions = vec![Action::Call(Call { - from: H160::from(1), - to: H160::from(2), - value: U256::from(3), - gas: U256::from(4), - input: vec![0x12, 0x34].into(), - call_type: CallType::Call, - }), Action::Create(Create { - from: H160::from(5), - value: U256::from(6), - gas: U256::from(7), - init: vec![0x56, 0x78].into(), - })]; - - let serialized = serde_json::to_string(&actions).unwrap(); - assert_eq!(serialized, r#"[{"call":{"from":"0x0000000000000000000000000000000000000001","to":"0x0000000000000000000000000000000000000002","value":"0x3","gas":"0x4","input":"0x1234","callType":"call"}},{"create":{"from":"0x0000000000000000000000000000000000000005","value":"0x6","gas":"0x7","init":"0x5678"}}]"#); - } - - #[test] - fn test_result_serialize() { - let results = vec![ - Res::Call(CallResult { - gas_used: U256::from(1), - output: vec![0x12, 0x34].into(), - }), - Res::Create(CreateResult { - gas_used: U256::from(2), - code: vec![0x45, 0x56].into(), - address: H160::from(3), - }), - Res::FailedCall, - Res::FailedCreate, - ]; - - let serialized = serde_json::to_string(&results).unwrap(); - assert_eq!(serialized, r#"[{"call":{"gasUsed":"0x1","output":"0x1234"}},{"create":{"gasUsed":"0x2","code":"0x4556","address":"0x0000000000000000000000000000000000000003"}},"failedCall","failedCreate"]"#); - } }