Skip to content

Commit

Permalink
fix: record CREATE + SELFDESTRUCT (#28)
Browse files Browse the repository at this point in the history
Closes #21

TLDR selfdestruct in constructor is not recorded as
`InstructionResult::Selfdestruct`

this uses the refund target to identify selfdestructs
  • Loading branch information
mattsse authored Feb 10, 2024
1 parent b8bd5bc commit d48f86e
Show file tree
Hide file tree
Showing 4 changed files with 207 additions and 4 deletions.
19 changes: 15 additions & 4 deletions src/tracing/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,12 @@ pub struct CallTrace {
///
/// Note: This is an Option because not all tracers make use of this
pub maybe_precompile: Option<bool>,
/// 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<Address>,
/// The kind of call this is
pub kind: CallKind,
Expand Down Expand Up @@ -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`
Expand Down
2 changes: 2 additions & 0 deletions tests/it/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mod parity;
pub mod utils;
164 changes: 164 additions & 0 deletions tests/it/parity.rs
Original file line number Diff line number Diff line change
@@ -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 <https://etherscan.io/tx/0xd81725127173cf1095a722cbaec118052e2626ddb914d61967fb4bf117969be0>
#[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]);
}
26 changes: 26 additions & 0 deletions tests/it/utils.rs
Original file line number Diff line number Diff line change
@@ -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, I>(
db: DB,
env: EnvWithHandlerCfg,
inspector: I,
) -> Result<(ResultAndState, EnvWithHandlerCfg), EVMError<DB::Error>>
where
DB: Database,
I: GetInspector<DB>,
{
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))
}

0 comments on commit d48f86e

Please sign in to comment.