Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add transferinspector #76

Merged
merged 1 commit into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,7 @@ pub mod opcode;
/// An inspector for recording traces
pub mod tracing;

/// An inspector for recording internal transfers.
pub mod transfer;

pub use colorchoice::ColorChoice;
130 changes: 130 additions & 0 deletions src/transfer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
use alloy_primitives::{Address, U256};
use revm::{
interpreter::{CallInputs, CallOutcome, CreateInputs, CreateOutcome, CreateScheme},
Database, EvmContext, Inspector,
};

/// An [Inspector] that collects internal ETH transfers.
///
/// This can be used to construct via `ots_getInternalOperations`
#[derive(Debug, Default)]
pub struct TransferInspector {
internal_only: bool,
transfers: Vec<TransferOperation>,
}

impl TransferInspector {
/// Creates a new transfer inspector.
///
/// If `internal_only` is set to `true`, only internal transfers are collected, in other words,
/// the top level call is ignored.
pub fn new(internal_only: bool) -> Self {
Self { internal_only, transfers: Vec::new() }
}

/// Creates a new transfer inspector that only collects internal transfers.
pub fn internal_only() -> Self {
Self::new(true)
}

/// Consumes the inspector and returns the collected transfers.
pub fn into_transfers(self) -> Vec<TransferOperation> {
self.transfers
}

/// Returns a reference to the collected transfers.
pub fn transfers(&self) -> &[TransferOperation] {
&self.transfers
}

/// Returns an iterator over the collected transfers.
pub fn iter(&self) -> impl Iterator<Item = &TransferOperation> {
self.transfers.iter()
}
}

impl<DB> Inspector<DB> for TransferInspector
where
DB: Database,
{
fn call(
&mut self,
context: &mut EvmContext<DB>,
inputs: &mut CallInputs,
) -> Option<CallOutcome> {
if self.internal_only && context.journaled_state.depth() == 0 {
// skip top level call
return None;
}

if !inputs.transfer.value.is_zero() {
self.transfers.push(TransferOperation {
kind: TransferKind::Call,
from: inputs.transfer.source,
to: inputs.transfer.target,
value: inputs.transfer.value,
});
}

None
}

fn create_end(
&mut self,
context: &mut EvmContext<DB>,
inputs: &CreateInputs,
outcome: CreateOutcome,
) -> CreateOutcome {
if self.internal_only && context.journaled_state.depth() == 0 {
return outcome;
}
if let Some(address) = outcome.address {
let kind = match inputs.scheme {
CreateScheme::Create => TransferKind::Create,
CreateScheme::Create2 { .. } => TransferKind::Create2,
};
self.transfers.push(TransferOperation {
kind,
from: inputs.caller,
to: address,
value: inputs.value,
});
}
outcome
}

fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) {
self.transfers.push(TransferOperation {
kind: TransferKind::SelfDestruct,
from: contract,
to: target,
value,
});
}
}

/// A transfer operation.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TransferOperation {
/// Source of the transfer call.
pub kind: TransferKind,
/// Sender of the transfer.
pub from: Address,
/// Receiver of the transfer.
pub to: Address,
/// Value of the transfer.
pub value: U256,
}

/// The kind of transfer operation.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TransferKind {
/// A non zero value transfer CALL
Call,
/// A CREATE operation
Create,
/// A CREATE2 operation
Create2,
/// A SELFDESTRUCT operation
SelfDestruct,
}
1 change: 1 addition & 0 deletions tests/it/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ pub mod utils;

mod geth;
mod parity;
mod transfer;
mod writer;
119 changes: 119 additions & 0 deletions tests/it/transfer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
//! Transfer tests

use alloy_primitives::{hex, Address, U256};
use revm::{
db::{CacheDB, EmptyDB},
interpreter::CreateScheme,
primitives::{
BlockEnv, CfgEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, ExecutionResult, HandlerCfg,
Output, SpecId, TransactTo, TxEnv,
},
DatabaseCommit,
};

use crate::utils::inspect;
use revm_inspectors::{
tracing::{TracingInspector, TracingInspectorConfig},
transfer::{TransferInspector, TransferKind, TransferOperation},
};

#[test]
fn test_internal_transfers() {
/*
contract Transfer {

function sendViaCall(address payable _to) public payable {
(bool sent, bytes memory data) = _to.call{value: msg.value}("");
}
}
*/

let code = hex!("608060405234801561001057600080fd5b5060ef8061001f6000396000f3fe608060405260043610601c5760003560e01c8063830c29ae146021575b600080fd5b6030602c366004608b565b6032565b005b600080826001600160a01b03163460405160006040518083038185875af1925050503d8060008114607e576040519150601f19603f3d011682016040523d82523d6000602084013e6083565b606091505b505050505050565b600060208284031215609c57600080fd5b81356001600160a01b038116811460b257600080fd5b939250505056fea26469706673582212201654bdbf09c088897c9b02f3ba9df280b136ef99c3a05ca5a21d9a10fd912d3364736f6c634300080d0033");
let deployer = Address::ZERO;

let mut db = CacheDB::new(EmptyDB::default());

let cfg = CfgEnvWithHandlerCfg::new(CfgEnv::default(), HandlerCfg::new(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_geth());

// Create contract
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 acc = db.load_account(deployer).unwrap();
acc.info.balance = U256::from(u64::MAX);

let tx_env = TxEnv {
caller: deployer,
gas_limit: 100000000,
transact_to: TransactTo::Call(addr),
data: hex!("830c29ae0000000000000000000000000000000000000000000000000000000000000000")
.into(),
value: U256::from(10),
..Default::default()
};

let mut insp = TransferInspector::new(false);

let env = EnvWithHandlerCfg::new_with_cfg_env(cfg.clone(), BlockEnv::default(), tx_env.clone());
let (res, _) = inspect(&mut db, env, &mut insp).unwrap();
assert!(res.result.is_success());

assert_eq!(insp.transfers().len(), 2);
assert_eq!(
insp.transfers()[0],
TransferOperation {
kind: TransferKind::Call,
from: deployer,
to: addr,
value: U256::from(10),
}
);
assert_eq!(
insp.transfers()[1],
TransferOperation {
kind: TransferKind::Call,
from: addr,
to: deployer,
value: U256::from(10),
}
);

let mut insp = TransferInspector::internal_only();

let env = EnvWithHandlerCfg::new_with_cfg_env(cfg, BlockEnv::default(), tx_env);

let (res, _) = inspect(&mut db, env, &mut insp).unwrap();
assert!(res.result.is_success());

assert_eq!(insp.transfers().len(), 1);
assert_eq!(
insp.transfers()[0],
TransferOperation {
kind: TransferKind::Call,
from: addr,
to: deployer,
value: U256::from(10),
}
);
}
Loading