use alloc::{vec, vec::Vec};
use alloy_primitives::{address, b256, Address, Log, LogData, B256, U256};
use alloy_sol_types::SolValue;
use revm::{
context::JournalTr,
context_interface::ContextTr,
interpreter::{CallInputs, CallOutcome, CreateInputs, CreateOutcome, CreateScheme},
Database, Inspector,
};
pub const TRANSFER_LOG_EMITTER: Address = address!("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee");
pub const TRANSFER_EVENT_TOPIC: B256 =
b256!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef");
#[derive(Debug, Default, Clone)]
pub struct TransferInspector {
internal_only: bool,
transfers: Vec<TransferOperation>,
insert_logs: bool,
}
impl TransferInspector {
pub fn new(internal_only: bool) -> Self {
Self { internal_only, transfers: Vec::new(), insert_logs: false }
}
pub fn internal_only() -> Self {
Self::new(true)
}
pub fn into_transfers(self) -> Vec<TransferOperation> {
self.transfers
}
pub fn with_logs(mut self, insert_logs: bool) -> Self {
self.insert_logs = insert_logs;
self
}
pub fn transfers(&self) -> &[TransferOperation] {
&self.transfers
}
pub fn iter(&self) -> impl Iterator<Item = &TransferOperation> {
self.transfers.iter()
}
fn on_transfer<DB: Database, JOURNAL: JournalTr<Database = DB>>(
&mut self,
from: Address,
to: Address,
value: U256,
kind: TransferKind,
journaled_state: &mut JOURNAL,
) {
if self.internal_only && journaled_state.depth() == 0 {
return;
}
if value.is_zero() {
return;
}
self.transfers.push(TransferOperation { kind, from, to, value });
if self.insert_logs {
let from = B256::from_slice(&from.abi_encode());
let to = B256::from_slice(&to.abi_encode());
let data = value.abi_encode();
journaled_state.log(Log {
address: TRANSFER_LOG_EMITTER,
data: LogData::new_unchecked(vec![TRANSFER_EVENT_TOPIC, from, to], data.into()),
});
}
}
}
impl<CTX> Inspector<CTX> for TransferInspector
where
CTX: ContextTr,
{
fn call(&mut self, context: &mut CTX, inputs: &mut CallInputs) -> Option<CallOutcome> {
if let Some(value) = inputs.transfer_value() {
self.on_transfer(
inputs.transfer_from(),
inputs.transfer_to(),
value,
TransferKind::Call,
context.journal_mut(),
);
}
None
}
fn create(&mut self, context: &mut CTX, inputs: &mut CreateInputs) -> Option<CreateOutcome> {
let nonce = context.journal_mut().load_account(inputs.caller()).ok()?.data.info.nonce;
let address = inputs.created_address(nonce);
let kind = match inputs.scheme() {
CreateScheme::Create => TransferKind::Create,
CreateScheme::Create2 { .. } => TransferKind::Create2,
CreateScheme::Custom { .. } => return None,
};
self.on_transfer(inputs.caller(), address, inputs.value(), kind, context.journal_mut());
None
}
fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) {
self.transfers.push(TransferOperation {
kind: TransferKind::SelfDestruct,
from: contract,
to: target,
value,
});
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TransferOperation {
pub kind: TransferKind,
pub from: Address,
pub to: Address,
pub value: U256,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TransferKind {
Call,
Create,
Create2,
SelfDestruct,
}