diff --git a/ethcore/src/externalities.rs b/ethcore/src/externalities.rs index 493b21611..6dea52037 100644 --- a/ethcore/src/externalities.rs +++ b/ethcore/src/externalities.rs @@ -263,6 +263,7 @@ impl<'a, T, V> Ext for Externalities<'a, T, V> where T: 'a + Tracer, V: 'a + VMT self.state.transfer_balance(&address, refund_address, &balance); } + self.tracer.trace_suicide(address, balance, refund_address.clone(), self.depth + 1); self.substate.suicides.insert(address); } diff --git a/ethcore/src/state.rs b/ethcore/src/state.rs index ca84d4ec1..effda5f0f 100644 --- a/ethcore/src/state.rs +++ b/ethcore/src/state.rs @@ -222,7 +222,7 @@ impl State { /// Reset the code of account `a` so that it is `code`. pub fn reset_code(&mut self, a: &Address, code: Bytes) { self.require_or_from(a, true, || Account::new_contract(0.into(), self.account_start_nonce), |_|{}).reset_code(code); - } + } /// Execute a given transaction. /// This will change the state accordingly. @@ -1154,6 +1154,58 @@ fn should_trace_failed_subcall_with_subcall_transaction() { assert_eq!(result.trace, expected_trace); } +#[test] +fn should_trace_suicide() { + init_log(); + + let temp = RandomTempPath::new(); + let mut state = get_temp_state_in(temp.as_path()); + + let mut info = EnvInfo::default(); + info.gas_limit = 1_000_000.into(); + let engine = TestEngine::new(5); + + let t = Transaction { + nonce: 0.into(), + gas_price: 0.into(), + gas: 100_000.into(), + action: Action::Call(0xa.into()), + value: 100.into(), + data: vec![], + }.sign(&"".sha3()); + + state.init_code(&0xa.into(), FromHex::from_hex("73000000000000000000000000000000000000000bff").unwrap()); + state.add_balance(&0xa.into(), &50.into()); + state.add_balance(t.sender().as_ref().unwrap(), &100.into()); + let vm_factory = Default::default(); + let result = state.apply(&info, &engine, &vm_factory, &t, true).unwrap(); + let expected_trace = Some(Trace { + depth: 0, + action: trace::Action::Call(trace::Call { + from: "9cce34f7ab185c7aba1b7c8140d620b4bda941d6".into(), + to: 0xa.into(), + value: 100.into(), + gas: 79000.into(), + input: vec![], + }), + result: trace::Res::Call(trace::CallResult { + gas_used: 3.into(), + output: vec![] + }), + subs: vec![Trace { + depth: 1, + action: trace::Action::Suicide(trace::Suicide { + address: 0xa.into(), + refund_address: 0xb.into(), + balance: 150.into(), + }), + result: trace::Res::None, + subs: vec![] + }] + }); + assert_eq!(result.trace, expected_trace); +} + #[test] fn code_from_database() { let a = Address::zero(); diff --git a/ethcore/src/trace/executive_tracer.rs b/ethcore/src/trace/executive_tracer.rs index ff7c6a62f..af8183c0a 100644 --- a/ethcore/src/trace/executive_tracer.rs +++ b/ethcore/src/trace/executive_tracer.rs @@ -18,7 +18,7 @@ use util::{Bytes, Address, U256}; use action_params::ActionParams; -use trace::trace::{Trace, Call, Create, Action, Res, CreateResult, CallResult, VMTrace, VMOperation, VMExecutedOperation, MemoryDiff, StorageDiff}; +use trace::trace::{Trace, Call, Create, Action, Res, CreateResult, CallResult, VMTrace, VMOperation, VMExecutedOperation, MemoryDiff, StorageDiff, Suicide}; use trace::{Tracer, VMTracer}; /// Simple executive tracer. Traces all calls and creates. Ignores delegatecalls. @@ -97,6 +97,20 @@ impl Tracer for ExecutiveTracer { self.traces.push(trace); } + fn trace_suicide(&mut self, address: Address, balance: U256, refund_address: Address, depth: usize) { + let trace = Trace { + depth: depth, + subs: vec![], + action: Action::Suicide(Suicide { + address: address, + refund_address: refund_address, + balance: balance, + }), + result: Res::None, + }; + self.traces.push(trace); + } + fn subtracer(&self) -> Self { ExecutiveTracer::default() } diff --git a/ethcore/src/trace/mod.rs b/ethcore/src/trace/mod.rs index 8f6c0b3f6..67fec2b97 100644 --- a/ethcore/src/trace/mod.rs +++ b/ethcore/src/trace/mod.rs @@ -81,6 +81,9 @@ pub trait Tracer: Send { /// Stores failed create trace. fn trace_failed_create(&mut self, create: Option, depth: usize, subs: Vec); + /// Stores suicide info. + fn trace_suicide(&mut self, address: Address, balance: U256, refund_address: Address, depth: usize); + /// Spawn subtracer which will be used to trace deeper levels of execution. fn subtracer(&self) -> Self where Self: Sized; diff --git a/ethcore/src/trace/noop_tracer.rs b/ethcore/src/trace/noop_tracer.rs index b6e6ca9fd..290fb2367 100644 --- a/ethcore/src/trace/noop_tracer.rs +++ b/ethcore/src/trace/noop_tracer.rs @@ -55,6 +55,9 @@ impl Tracer for NoopTracer { assert!(create.is_none(), "self.prepare_trace_create().is_none(): so we can't be tracing: qed"); } + fn trace_suicide(&mut self, _address: Address, _balance: U256, _refund_address: Address, _depth: usize) { + } + fn subtracer(&self) -> Self { NoopTracer } diff --git a/ethcore/src/types/trace_types/filter.rs b/ethcore/src/types/trace_types/filter.rs index d57efa5f3..91d1c421d 100644 --- a/ethcore/src/types/trace_types/filter.rs +++ b/ethcore/src/types/trace_types/filter.rs @@ -121,15 +121,20 @@ impl Filter { let to_matches = self.to_address.matches_all(); from_matches && to_matches } + Action::Suicide(ref suicide) => { + let from_matches = self.from_address.matches(&suicide.address); + let to_matches = self.to_address.matches(&suicide.refund_address); + from_matches && to_matches + } } } } #[cfg(test)] mod tests { - use util::{FixedHash, Address, U256}; + use util::{FixedHash, Address}; use util::sha3::Hashable; - use trace::trace::{Action, Call, Res}; + use trace::trace::{Action, Call, Res, Suicide}; use trace::flat::FlatTrace; use trace::{Filter, AddressesFilter}; use basic_types::LogBloom; @@ -270,10 +275,10 @@ mod tests { let trace = FlatTrace { action: Action::Call(Call { - from: Address::from(1), - to: Address::from(2), - value: U256::from(3), - gas: U256::from(4), + from: 1.into(), + to: 2.into(), + value: 3.into(), + gas: 4.into(), input: vec![0x5], }), result: Res::FailedCall, @@ -288,5 +293,24 @@ mod tests { assert!(f4.matches(&trace)); assert!(f5.matches(&trace)); assert!(!f6.matches(&trace)); + + let trace = FlatTrace { + action: Action::Suicide(Suicide { + address: 1.into(), + refund_address: 2.into(), + balance: 3.into(), + }), + result: Res::None, + trace_address: vec![], + subtraces: 0 + }; + + assert!(f0.matches(&trace)); + assert!(f1.matches(&trace)); + assert!(f2.matches(&trace)); + assert!(f3.matches(&trace)); + assert!(f4.matches(&trace)); + assert!(f5.matches(&trace)); + assert!(!f6.matches(&trace)); } } diff --git a/ethcore/src/types/trace_types/trace.rs b/ethcore/src/types/trace_types/trace.rs index 243acaf64..db90e068c 100644 --- a/ethcore/src/types/trace_types/trace.rs +++ b/ethcore/src/types/trace_types/trace.rs @@ -205,6 +205,48 @@ impl Create { } } +/// Suicide action. +#[derive(Debug, Clone, PartialEq, Binary)] +pub struct Suicide { + /// Suicided address. + pub address: Address, + /// Suicided contract heir. + pub refund_address: Address, + /// Balance of the contract just before suicide. + pub balance: U256, +} + +impl Suicide { + /// Return suicide action bloom. + pub fn bloom(&self) -> LogBloom { + LogBloom::from_bloomed(&self.address.sha3()) + .with_bloomed(&self.refund_address.sha3()) + } +} + +impl Encodable for Suicide { + fn rlp_append(&self, s: &mut RlpStream) { + s.begin_list(3); + s.append(&self.address); + s.append(&self.refund_address); + s.append(&self.balance); + } +} + +impl Decodable for Suicide { + fn decode(decoder: &D) -> Result where D: Decoder { + let d = decoder.as_rlp(); + let res = Suicide { + address: try!(d.val_at(0)), + refund_address: try!(d.val_at(1)), + balance: try!(d.val_at(3)), + }; + + Ok(res) + } +} + + /// Description of an action that we trace; will be either a call or a create. #[derive(Debug, Clone, PartialEq, Binary)] pub enum Action { @@ -212,6 +254,8 @@ pub enum Action { Call(Call), /// It's a create action. Create(Create), + /// Suicide. + Suicide(Suicide), } impl Encodable for Action { @@ -225,6 +269,10 @@ impl Encodable for Action { Action::Create(ref create) => { s.append(&1u8); s.append(create); + }, + Action::Suicide(ref suicide) => { + s.append(&2u8); + s.append(suicide); } } } @@ -237,6 +285,7 @@ impl Decodable for Action { match action_type { 0 => d.val_at(1).map(Action::Call), 1 => d.val_at(1).map(Action::Create), + 2 => d.val_at(2).map(Action::Suicide), _ => Err(DecoderError::Custom("Invalid action type.")), } } @@ -248,6 +297,7 @@ impl Action { match *self { Action::Call(ref call) => call.bloom(), Action::Create(ref create) => create.bloom(), + Action::Suicide(ref suicide) => suicide.bloom(), } } } @@ -263,6 +313,8 @@ pub enum Res { FailedCall, /// Failed create. FailedCreate, + /// None + None, } impl Encodable for Res { @@ -285,6 +337,10 @@ impl Encodable for Res { Res::FailedCreate => { s.begin_list(1); s.append(&3u8); + }, + Res::None => { + s.begin_list(1); + s.append(&4u8); } } } @@ -299,6 +355,7 @@ impl Decodable for Res { 1 => d.val_at(1).map(Res::Create), 2 => Ok(Res::FailedCall), 3 => Ok(Res::FailedCreate), + 4 => Ok(Res::None), _ => Err(DecoderError::Custom("Invalid result type.")), } } @@ -518,7 +575,7 @@ mod tests { use util::{Address, U256, FixedHash}; use util::rlp::{encode, decode}; use util::sha3::Hashable; - use trace::trace::{Call, CallResult, Create, Res, Action, Trace}; + use trace::trace::{Call, CallResult, Create, Res, Action, Trace, Suicide}; #[test] fn traces_rlp() { @@ -577,6 +634,16 @@ mod tests { }), subs: vec![], result: Res::FailedCreate + }, + Trace { + depth: 3, + action: Action::Suicide(Suicide { + address: 101.into(), + refund_address: 102.into(), + balance: 0.into(), + }), + subs: vec![], + result: Res::None, } ], result: Res::Call(CallResult { @@ -592,5 +659,8 @@ mod tests { assert!(bloom.contains_bloomed(&Address::from(2).sha3())); assert!(!bloom.contains_bloomed(&Address::from(20).sha3())); assert!(bloom.contains_bloomed(&Address::from(6).sha3())); + assert!(bloom.contains_bloomed(&Address::from(101).sha3())); + assert!(bloom.contains_bloomed(&Address::from(102).sha3())); + assert!(!bloom.contains_bloomed(&Address::from(103).sha3())); } } diff --git a/rpc/src/v1/types/trace.rs b/rpc/src/v1/types/trace.rs index 59da9be80..187a9a9b0 100644 --- a/rpc/src/v1/types/trace.rs +++ b/rpc/src/v1/types/trace.rs @@ -260,6 +260,28 @@ impl From for Call { } } +/// Suicide +#[derive(Debug, Serialize)] +pub struct Suicide { + /// Address. + pub address: H160, + /// Refund address. + #[serde(rename="refundAddress")] + pub refund_address: H160, + /// Balance. + pub balance: U256, +} + +impl From for Suicide { + fn from(s: trace::Suicide) -> Self { + Suicide { + address: s.address.into(), + refund_address: s.refund_address.into(), + balance: s.balance.into(), + } + } +} + /// Action #[derive(Debug, Serialize)] pub enum Action { @@ -269,13 +291,17 @@ pub enum Action { /// Create #[serde(rename="create")] Create(Create), + /// Suicide + #[serde(rename="suicide")] + Suicide(Suicide), } impl From for Action { fn from(c: trace::Action) -> Self { match c { - trace::Action::Call(call) => Action::Call(Call::from(call)), - trace::Action::Create(create) => Action::Create(Create::from(create)), + trace::Action::Call(call) => Action::Call(call.into()), + trace::Action::Create(create) => Action::Create(create.into()), + trace::Action::Suicide(suicide) => Action::Suicide(suicide.into()), } } } @@ -336,6 +362,9 @@ pub enum Res { /// Creation failure #[serde(rename="failedCreate")] FailedCreate, + /// None + #[serde(rename="none")] + None, } impl From for Res { @@ -345,6 +374,7 @@ impl From for Res { trace::Res::Create(create) => Res::Create(CreateResult::from(create)), trace::Res::FailedCall => Res::FailedCall, trace::Res::FailedCreate => Res::FailedCreate, + trace::Res::None => Res::None, } } }