use std::collections::HashMap;
use alloy_primitives::{Address, B256, I256, Log, U256};
use revm::Inspector;
use revm::interpreter::InterpreterTypes;
const TRANSFER_EVENT_SIGNATURE: B256 = B256::new([
0xdd, 0xf2, 0x52, 0xad, 0x1b, 0xe2, 0xc8, 0x9b, 0x69, 0xc2, 0xb0, 0x68, 0xfc, 0x37, 0x8d, 0xaa,
0x95, 0x2b, 0xa7, 0xf1, 0x63, 0xc4, 0xa1, 0x16, 0x28, 0xf5, 0x5a, 0x4d, 0xf5, 0x23, 0xb3, 0xef,
]);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TokenTransfer {
pub token: Address,
pub from: Address,
pub to: Address,
pub value: U256,
}
#[derive(Clone, Debug, Default)]
pub struct TransferInspector {
pub transfers: Vec<TokenTransfer>,
pub logs: Vec<Log>,
}
impl TransferInspector {
pub fn new() -> Self {
Self {
transfers: Vec::new(),
logs: Vec::new(),
}
}
pub fn balance_deltas(&self, owner: Address) -> HashMap<Address, I256> {
let mut deltas: HashMap<Address, I256> = HashMap::new();
for transfer in &self.transfers {
if transfer.from == owner {
let entry = deltas.entry(transfer.token).or_insert(I256::ZERO);
*entry -= I256::from_raw(transfer.value);
}
if transfer.to == owner {
let entry = deltas.entry(transfer.token).or_insert(I256::ZERO);
*entry += I256::from_raw(transfer.value);
}
}
deltas
}
pub fn balance_deltas_for_tokens(
&self,
owner: Address,
tokens: impl IntoIterator<Item = Address>,
) -> HashMap<Address, I256> {
let token_set: std::collections::HashSet<Address> = tokens.into_iter().collect();
let all_deltas = self.balance_deltas(owner);
all_deltas
.into_iter()
.filter(|(token, _)| token_set.contains(token))
.collect()
}
pub fn clear(&mut self) {
self.transfers.clear();
self.logs.clear();
}
pub fn parse_transfer(log: &Log) -> Option<TokenTransfer> {
let topics = log.topics();
if topics.len() < 3 {
return None;
}
if topics[0] != TRANSFER_EVENT_SIGNATURE {
return None;
}
let from = Address::from_word(topics[1]);
let to = Address::from_word(topics[2]);
let data = log.data.data.as_ref();
if data.len() < 32 {
return None;
}
let value = U256::from_be_slice(&data[..32]);
Some(TokenTransfer {
token: log.address,
from,
to,
value,
})
}
}
impl<CTX, INTR> Inspector<CTX, INTR> for TransferInspector
where
INTR: InterpreterTypes,
{
fn log(&mut self, _context: &mut CTX, log: Log) {
if let Some(transfer) = Self::parse_transfer(&log) {
self.transfers.push(transfer);
}
self.logs.push(log);
}
}
pub fn parse_receipt_deltas(receipt_logs: &[Log], owner: Address) -> HashMap<Address, I256> {
let mut deltas: HashMap<Address, I256> = HashMap::new();
for log in receipt_logs {
if let Some(transfer) = TransferInspector::parse_transfer(log) {
if transfer.from == owner {
let entry = deltas.entry(transfer.token).or_insert(I256::ZERO);
*entry -= I256::from_raw(transfer.value);
}
if transfer.to == owner {
let entry = deltas.entry(transfer.token).or_insert(I256::ZERO);
*entry += I256::from_raw(transfer.value);
}
}
}
deltas
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::{address, bytes};
fn make_transfer_log(token: Address, from: Address, to: Address, value: U256) -> Log {
Log::new(
token,
vec![TRANSFER_EVENT_SIGNATURE, from.into_word(), to.into_word()],
value.to_be_bytes_vec().into(),
)
.unwrap()
}
#[test]
fn test_parse_single_transfer() {
let token = address!("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); let from = address!("1111111111111111111111111111111111111111");
let to = address!("2222222222222222222222222222222222222222");
let value = U256::from(1000000u64);
let log = make_transfer_log(token, from, to, value);
let transfer = TransferInspector::parse_transfer(&log).expect("should parse");
assert_eq!(transfer.token, token);
assert_eq!(transfer.from, from);
assert_eq!(transfer.to, to);
assert_eq!(transfer.value, value);
}
#[test]
fn test_balance_deltas_outgoing() {
let mut inspector = TransferInspector::new();
let token = address!("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
let owner = address!("1111111111111111111111111111111111111111");
let recipient = address!("2222222222222222222222222222222222222222");
let value = U256::from(1000000u64);
inspector.transfers.push(TokenTransfer {
token,
from: owner,
to: recipient,
value,
});
let deltas = inspector.balance_deltas(owner);
assert_eq!(deltas.get(&token), Some(&(-I256::from_raw(value))));
}
#[test]
fn test_balance_deltas_incoming() {
let mut inspector = TransferInspector::new();
let token = address!("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
let sender = address!("1111111111111111111111111111111111111111");
let owner = address!("2222222222222222222222222222222222222222");
let value = U256::from(1000000u64);
inspector.transfers.push(TokenTransfer {
token,
from: sender,
to: owner,
value,
});
let deltas = inspector.balance_deltas(owner);
assert_eq!(deltas.get(&token), Some(&I256::from_raw(value)));
}
#[test]
fn test_balance_deltas_multiple_transfers() {
let mut inspector = TransferInspector::new();
let token_a = address!("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
let token_b = address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
let owner = address!("1111111111111111111111111111111111111111");
let other = address!("2222222222222222222222222222222222222222");
inspector.transfers.push(TokenTransfer {
token: token_a,
from: owner,
to: other,
value: U256::from(100u64),
});
inspector.transfers.push(TokenTransfer {
token: token_b,
from: other,
to: owner,
value: U256::from(50u64),
});
inspector.transfers.push(TokenTransfer {
token: token_a,
from: other,
to: owner,
value: U256::from(25u64),
});
let deltas = inspector.balance_deltas(owner);
assert_eq!(deltas.get(&token_a), Some(&I256::try_from(-75i64).unwrap()));
assert_eq!(deltas.get(&token_b), Some(&I256::try_from(50i64).unwrap()));
}
#[test]
fn test_balance_deltas_for_tokens_filter() {
let mut inspector = TransferInspector::new();
let token_a = address!("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
let token_b = address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
let token_c = address!("6B175474E89094C44Da98b954EeecdE504D571D1");
let owner = address!("1111111111111111111111111111111111111111");
let other = address!("2222222222222222222222222222222222222222");
inspector.transfers.push(TokenTransfer {
token: token_a,
from: owner,
to: other,
value: U256::from(100u64),
});
inspector.transfers.push(TokenTransfer {
token: token_b,
from: other,
to: owner,
value: U256::from(50u64),
});
inspector.transfers.push(TokenTransfer {
token: token_c,
from: other,
to: owner,
value: U256::from(200u64),
});
let deltas = inspector.balance_deltas_for_tokens(owner, vec![token_a, token_b]);
assert_eq!(deltas.len(), 2);
assert!(deltas.contains_key(&token_a));
assert!(deltas.contains_key(&token_b));
assert!(!deltas.contains_key(&token_c));
}
#[test]
fn test_non_transfer_log_ignored() {
let log = Log::new(
address!("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
vec![
B256::ZERO, address!("1111111111111111111111111111111111111111").into_word(),
address!("2222222222222222222222222222222222222222").into_word(),
],
U256::from(100u64).to_be_bytes_vec().into(),
)
.unwrap();
assert!(TransferInspector::parse_transfer(&log).is_none());
}
#[test]
fn test_inspector_clear() {
let mut inspector = TransferInspector::new();
inspector.transfers.push(TokenTransfer {
token: Address::ZERO,
from: Address::ZERO,
to: Address::ZERO,
value: U256::ZERO,
});
inspector
.logs
.push(Log::new_unchecked(Address::ZERO, vec![], bytes!("")));
assert!(!inspector.transfers.is_empty());
assert!(!inspector.logs.is_empty());
inspector.clear();
assert!(inspector.transfers.is_empty());
assert!(inspector.logs.is_empty());
}
#[test]
fn test_parse_receipt_deltas() {
let token_a = address!("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
let token_b = address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
let owner = address!("1111111111111111111111111111111111111111");
let other = address!("2222222222222222222222222222222222222222");
let logs = vec![
make_transfer_log(token_a, owner, other, U256::from(100u64)),
make_transfer_log(token_b, other, owner, U256::from(50u64)),
Log::new(
token_a,
vec![B256::ZERO, owner.into_word(), other.into_word()],
U256::from(999u64).to_be_bytes_vec().into(),
)
.unwrap(),
make_transfer_log(token_a, other, owner, U256::from(25u64)),
];
let deltas = super::parse_receipt_deltas(&logs, owner);
assert_eq!(deltas.get(&token_a), Some(&I256::try_from(-75i64).unwrap()));
assert_eq!(deltas.get(&token_b), Some(&I256::try_from(50i64).unwrap()));
}
#[test]
fn test_parse_receipt_deltas_empty() {
let owner = address!("1111111111111111111111111111111111111111");
let deltas = super::parse_receipt_deltas(&[], owner);
assert!(deltas.is_empty());
}
}