diff --git a/src/tracing/types.rs b/src/tracing/types.rs index 4f33abc..fde1348 100644 --- a/src/tracing/types.rs +++ b/src/tracing/types.rs @@ -32,8 +32,12 @@ pub struct CallTrace { /// /// Note: This is an Option because not all tracers make use of this pub maybe_precompile: Option, - /// Holds the target for the selfdestruct refund target if `status` is - /// [InstructionResult::SelfDestruct] + /// Holds the target for the __selfdestruct__ refund target + /// + /// This is only set if a selfdestruct was executed. + /// + /// Note: This not necessarily guarantees that the status is [InstructionResult::SelfDestruct] + /// There's an edge case where a new created contract is immediately selfdestructed. pub selfdestruct_refund_target: Option
, /// The kind of call this is pub kind: CallKind, @@ -197,9 +201,16 @@ impl CallTraceNode { } /// Returns true if the call was a selfdestruct + /// + /// A selfdestruct is marked by the refund target being set. + /// + /// See also `TracingInspector::selfdestruct` + /// + /// Note: We can't rely in the [Self::status] being [InstructionResult::SelfDestruct] because + /// there's an edge case where a new created contract (CREATE) is immediately selfdestructed. #[inline] - pub fn is_selfdestruct(&self) -> bool { - self.status() == InstructionResult::SelfDestruct + pub const fn is_selfdestruct(&self) -> bool { + self.trace.selfdestruct_refund_target.is_some() } /// Converts this node into a parity `TransactionTrace` diff --git a/tests/it/main.rs b/tests/it/main.rs new file mode 100644 index 0000000..342f6a4 --- /dev/null +++ b/tests/it/main.rs @@ -0,0 +1,2 @@ +mod parity; +pub mod utils; diff --git a/tests/it/parity.rs b/tests/it/parity.rs new file mode 100644 index 0000000..e8b5660 --- /dev/null +++ b/tests/it/parity.rs @@ -0,0 +1,164 @@ +//! Parity tests + +use crate::utils::inspect; +use alloy_primitives::{hex, Address}; +use alloy_rpc_types::TransactionInfo; +use revm::{ + db::{CacheDB, EmptyDB}, + interpreter::CreateScheme, + primitives::{ + BlockEnv, CfgEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, ExecutionResult, Output, SpecId, + TransactTo, TxEnv, + }, + DatabaseCommit, +}; +use revm_inspectors::tracing::{TracingInspector, TracingInspectorConfig}; + +#[test] +fn test_parity_selfdestruct() { + /* + contract DummySelfDestruct { + function close() public { + selfdestruct(payable(msg.sender)); + } + } + */ + + // simple contract that selfdestructs when a function is called + let code = hex!("6080604052348015600f57600080fd5b50606a80601d6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806343d726d614602d575b600080fd5b603233ff5b00fea2646970667358221220e52c8372ad24b20a6f7e4a13772dbc1d00f7eb5d0934ed6d635d3f5bf47dbc9364736f6c634300080d0033"); + + let deployer = Address::ZERO; + + let mut db = CacheDB::new(EmptyDB::default()); + + let cfg = CfgEnvWithHandlerCfg::new(CfgEnv::default(), SpecId::LONDON); + + let env = EnvWithHandlerCfg::new_with_cfg_env( + cfg.clone(), + BlockEnv::default(), + TxEnv { + caller: deployer, + gas_limit: 1000000, + transact_to: TransactTo::Create(CreateScheme::Create), + data: code.into(), + ..Default::default() + }, + ); + + let mut insp = TracingInspector::new(TracingInspectorConfig::default_parity()); + + let (res, _) = inspect(&mut db, env, &mut insp).unwrap(); + let addr = match res.result { + ExecutionResult::Success { output, .. } => match output { + Output::Create(_, addr) => addr.unwrap(), + _ => panic!("Create failed"), + }, + _ => panic!("Execution failed"), + }; + db.commit(res.state); + + let mut insp = TracingInspector::new(TracingInspectorConfig::default_parity()); + + let env = EnvWithHandlerCfg::new_with_cfg_env( + cfg, + BlockEnv::default(), + TxEnv { + caller: deployer, + gas_limit: 1000000, + transact_to: TransactTo::Call(addr), + data: hex!("43d726d6").into(), + ..Default::default() + }, + ); + + let (res, _) = inspect(&mut db, env, &mut insp).unwrap(); + assert!(res.result.is_success()); + + let traces = insp + .with_transaction_gas_used(res.result.gas_used()) + .into_parity_builder() + .into_localized_transaction_traces(TransactionInfo::default()); + + assert_eq!(traces.len(), 2); + assert!(traces[1].trace.action.is_selfdestruct()) +} + +// Minimal example of +#[test] +fn test_parity_constructor_selfdestruct() { + // simple contract that selfdestructs when a function is called + + /* + contract DummySelfDestruct { + function close() public { + new Noop(); + } + } + contract Noop { + constructor() { + selfdestruct(payable(msg.sender)); + } + } + */ + + let code = hex!("6080604052348015600f57600080fd5b5060b48061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806343d726d614602d575b600080fd5b60336035565b005b604051603f90605e565b604051809103906000f080158015605a573d6000803e3d6000fd5b5050565b60148061006b8339019056fe6080604052348015600f57600080fd5b5033fffea264697066735822122087fcd1ed364913e41107ea336facf7b7f5972695b3e3abcf55dbb2452e124ea964736f6c634300080d0033"); + + let deployer = Address::ZERO; + + let mut db = CacheDB::new(EmptyDB::default()); + + let cfg = CfgEnvWithHandlerCfg::new(CfgEnv::default(), SpecId::LONDON); + + let env = EnvWithHandlerCfg::new_with_cfg_env( + cfg.clone(), + BlockEnv::default(), + TxEnv { + caller: deployer, + gas_limit: 1000000, + transact_to: TransactTo::Create(CreateScheme::Create), + data: code.into(), + ..Default::default() + }, + ); + + let mut insp = TracingInspector::new(TracingInspectorConfig::default_parity()); + + let (res, _) = inspect(&mut db, env, &mut insp).unwrap(); + let addr = match res.result { + ExecutionResult::Success { output, .. } => match output { + Output::Create(_, addr) => addr.unwrap(), + _ => panic!("Create failed"), + }, + _ => panic!("Execution failed"), + }; + db.commit(res.state); + + let mut insp = TracingInspector::new(TracingInspectorConfig::default_parity()); + + let env = EnvWithHandlerCfg::new_with_cfg_env( + cfg, + BlockEnv::default(), + TxEnv { + caller: deployer, + gas_limit: 1000000, + transact_to: TransactTo::Call(addr), + data: hex!("43d726d6").into(), + ..Default::default() + }, + ); + + let (res, _) = inspect(&mut db, env, &mut insp).unwrap(); + assert!(res.result.is_success()); + + let traces = insp + .with_transaction_gas_used(res.result.gas_used()) + .into_parity_builder() + .into_localized_transaction_traces(TransactionInfo::default()); + + assert_eq!(traces.len(), 3); + assert!(traces[1].trace.action.is_create()); + assert_eq!(traces[1].trace.trace_address, vec![0]); + assert_eq!(traces[1].trace.subtraces, 1); + assert!(traces[2].trace.action.is_selfdestruct()); + assert_eq!(traces[2].trace.trace_address, vec![0, 0]); +} diff --git a/tests/it/utils.rs b/tests/it/utils.rs new file mode 100644 index 0000000..861e3ba --- /dev/null +++ b/tests/it/utils.rs @@ -0,0 +1,26 @@ +use revm::{ + inspector_handle_register, + primitives::{EVMError, EnvWithHandlerCfg, ResultAndState}, + Database, GetInspector, +}; + +/// Executes the [EnvWithHandlerCfg] against the given [Database] without committing state changes. +pub fn inspect( + db: DB, + env: EnvWithHandlerCfg, + inspector: I, +) -> Result<(ResultAndState, EnvWithHandlerCfg), EVMError> +where + DB: Database, + I: GetInspector, +{ + let mut evm = revm::Evm::builder() + .with_db(db) + .with_external_context(inspector) + .with_env_with_handler_cfg(env) + .append_handler_register(inspector_handle_register) + .build(); + let res = evm.transact()?; + let (_, env) = evm.into_db_and_env_with_handler_cfg(); + Ok((res, env)) +}