use crate::{
errors::{EvmError, TokenError},
evm::TraceEvm,
types::{TokenInfo, ERC20_TRANSFER_EVENT_SIGNATURE},
};
use alloy::{
primitives::{Address, Bytes, FixedBytes, TxKind, U256},
sol,
sol_types::SolCall,
};
use anyhow::Result;
use revm::{
context::TxEnv,
context_interface::result::{ExecutionResult, Output},
database::Database,
ExecuteEvm,
};
sol! {
function name() public returns (string);
function symbol() public returns (string);
function decimals() public returns (uint8);
function balanceOf(address owner) public returns (uint256);
function totalSupply() public returns (uint256);
}
pub fn query_erc20_balance<DB, INSP>(
evm: &mut TraceEvm<DB, INSP>,
token_address: Address,
owner: Address,
) -> Result<U256>
where
DB: Database,
{
let data: Bytes = balanceOfCall { owner }.abi_encode().into();
let tx = TxEnv::builder()
.caller(Address::ZERO)
.kind(TxKind::Call(token_address))
.chain_id(Some(evm.cfg.chain_id))
.data(data)
.nonce(0) .build_fill();
let ref_tx = evm
.transact(tx)
.map_err(|e| anyhow::anyhow!("Failed to query ERC20 balance: {}", e))?;
let value = match ref_tx.result {
ExecutionResult::Success {
output: Output::Call(value),
..
} => value,
_ => return Err(anyhow::anyhow!("Failed to execute balanceOf call")),
};
let balance = balanceOfCall::abi_decode_returns(&value)?;
Ok(balance)
}
fn query_token_info<DB, INSP>(
evm: &mut TraceEvm<DB, INSP>,
token_address: Address,
name_encoded: Bytes,
symbol_encoded: Bytes,
decimals_encoded: Bytes,
total_supply_encoded: Bytes,
) -> Result<TokenInfo, TokenError>
where
DB: Database,
{
let tx_name = TxEnv {
caller: Address::ZERO,
kind: TxKind::Call(token_address),
data: name_encoded,
chain_id: Some(evm.cfg.chain_id),
nonce: 0,
..Default::default()
};
let ref_tx = evm
.transact(tx_name)
.map_err(|e| TokenError::AnyhowError(format!("Failed to query token name: {e}")))?;
let name = match ref_tx.result {
ExecutionResult::Success {
output: Output::Call(value),
..
} => nameCall::abi_decode_returns(&value).map_err(|_| TokenError::NameDecode {
address: token_address.to_string(),
reason: "Failed to decode name".to_string(),
})?,
_ => {
return Err(TokenError::CallReverted {
address: token_address.to_string(),
})
}
};
let tx_symbol = TxEnv {
caller: Address::ZERO,
kind: TxKind::Call(token_address),
chain_id: Some(evm.cfg.chain_id),
data: symbol_encoded,
..Default::default()
};
let ref_tx = evm
.transact(tx_symbol)
.map_err(|e| TokenError::AnyhowError(format!("Failed to query token symbol: {e}")))?;
let symbol = match ref_tx.result {
ExecutionResult::Success {
output: Output::Call(value),
..
} => symbolCall::abi_decode_returns(&value).map_err(|_| TokenError::SymbolDecode {
address: token_address.to_string(),
reason: "Failed to decode symbol".to_string(),
})?,
_ => {
return Err(TokenError::CallReverted {
address: token_address.to_string(),
})
}
};
let tx_decimals = TxEnv {
kind: TxKind::Call(token_address),
data: decimals_encoded,
chain_id: Some(evm.cfg.chain_id),
..Default::default()
};
let ref_tx = evm
.transact(tx_decimals)
.map_err(|e| TokenError::AnyhowError(format!("Failed to query token decimals: {e}")))?;
let decimals = match ref_tx.result {
ExecutionResult::Success {
output: Output::Call(value),
..
} => decimalsCall::abi_decode_returns(&value).map_err(|_| TokenError::DecimalsDecode {
address: token_address.to_string(),
reason: "Failed to decode decimals".to_string(),
})?,
_ => {
return Err(TokenError::CallReverted {
address: token_address.to_string(),
})
}
};
let tx_total_supply = TxEnv {
kind: TxKind::Call(token_address),
data: total_supply_encoded,
chain_id: Some(evm.cfg.chain_id),
..Default::default()
};
let ref_tx = evm
.transact(tx_total_supply)
.map_err(|e| TokenError::AnyhowError(format!("Failed to query token total supply: {e}")))?;
let total_supply = match ref_tx.result {
ExecutionResult::Success {
output: Output::Call(value),
..
} => totalSupplyCall::abi_decode_returns(&value).map_err(|_| {
TokenError::TotalSupplyDecode {
address: token_address.to_string(),
reason: "Failed to decode total supply".to_string(),
}
})?,
_ => {
return Err(TokenError::CallReverted {
address: token_address.to_string(),
})
}
};
Ok(TokenInfo {
name,
symbol,
decimals,
total_supply,
})
}
pub fn get_token_infos<DB, INSP>(
evm: &mut TraceEvm<DB, INSP>,
tokens: &[Address],
) -> Result<Vec<TokenInfo>, EvmError>
where
DB: Database,
{
let name_encoded: Bytes = nameCall {}.abi_encode().into();
let symbol_encoded: Bytes = symbolCall {}.abi_encode().into();
let decimals_encoded: Bytes = decimalsCall {}.abi_encode().into();
let total_supply_encoded: Bytes = totalSupplyCall {}.abi_encode().into();
let mut token_infos = Vec::with_capacity(tokens.len());
for token in tokens {
let token_info = query_token_info(
evm,
*token,
name_encoded.clone(),
symbol_encoded.clone(),
decimals_encoded.clone(),
total_supply_encoded.clone(),
)?;
token_infos.push(token_info);
}
Ok(token_infos)
}
pub fn parse_transfer_log(
topics: &[FixedBytes<32>],
data: &[u8],
) -> Option<(Address, Address, U256)> {
if topics.len() < 3 || topics[0] != ERC20_TRANSFER_EVENT_SIGNATURE {
return None;
}
let amount = U256::from_be_slice(data);
if !amount.is_zero() {
Some((
Address::from_slice(&topics[1].as_slice()[12..]),
Address::from_slice(&topics[2].as_slice()[12..]),
amount,
))
} else {
None
}
}