use alloy_dyn_abi::{DynSolType, DynSolValue, FunctionExt, JsonAbiExt};
use alloy_json_abi::{Error, Event, Function, Param};
use alloy_primitives::{hex, Address, LogData};
use eyre::{Context, ContextCompat, Result};
use foundry_block_explorers::{contract::ContractMetadata, errors::EtherscanError, Client};
use foundry_config::Chain;
use std::{future::Future, pin::Pin};
pub fn encode_args<I, S>(inputs: &[Param], args: I) -> Result<Vec<DynSolValue>>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
std::iter::zip(inputs, args)
.map(|(input, arg)| coerce_value(&input.selector_type(), arg.as_ref()))
.collect()
}
pub fn encode_function_args<I, S>(func: &Function, args: I) -> Result<Vec<u8>>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
Ok(func.abi_encode_input(&encode_args(&func.inputs, args)?)?)
}
pub fn encode_function_args_packed<I, S>(func: &Function, args: I) -> Result<Vec<u8>>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let params: Vec<Vec<u8>> = std::iter::zip(&func.inputs, args)
.map(|(input, arg)| coerce_value(&input.selector_type(), arg.as_ref()))
.collect::<Result<Vec<_>>>()?
.into_iter()
.map(|v| v.abi_encode_packed())
.collect();
Ok(params.concat())
}
pub fn abi_decode_calldata(
sig: &str,
calldata: &str,
input: bool,
fn_selector: bool,
) -> Result<Vec<DynSolValue>> {
let func = get_func(sig)?;
let calldata = hex::decode(calldata)?;
let mut calldata = calldata.as_slice();
if input && fn_selector && calldata.len() >= 4 {
calldata = &calldata[4..];
}
let res = if input {
func.abi_decode_input(calldata, false)
} else {
func.abi_decode_output(calldata, false)
}?;
if res.is_empty() {
eyre::bail!("no data was decoded")
}
Ok(res)
}
pub fn get_func(sig: &str) -> Result<Function> {
Function::parse(sig).wrap_err("could not parse function signature")
}
pub fn get_event(sig: &str) -> Result<Event> {
Event::parse(sig).wrap_err("could not parse event signature")
}
pub fn get_error(sig: &str) -> Result<Error> {
Error::parse(sig).wrap_err("could not parse event signature")
}
pub fn get_indexed_event(mut event: Event, raw_log: &LogData) -> Event {
if !event.anonymous && raw_log.topics().len() > 1 {
let indexed_params = raw_log.topics().len() - 1;
let num_inputs = event.inputs.len();
let num_address_params = event.inputs.iter().filter(|p| p.ty == "address").count();
event.inputs.iter_mut().enumerate().for_each(|(index, param)| {
if param.name.is_empty() {
param.name = format!("param{index}");
}
if num_inputs == indexed_params ||
(num_address_params == indexed_params && param.ty == "address")
{
param.indexed = true;
}
})
}
event
}
pub async fn get_func_etherscan(
function_name: &str,
contract: Address,
args: &[String],
chain: Chain,
etherscan_api_key: &str,
) -> Result<Function> {
let client = Client::new(chain, etherscan_api_key)?;
let source = find_source(client, contract).await?;
let metadata = source.items.first().wrap_err("etherscan returned empty metadata")?;
let mut abi = metadata.abi()?;
let funcs = abi.functions.remove(function_name).unwrap_or_default();
for func in funcs {
let res = encode_function_args(&func, args);
if res.is_ok() {
return Ok(func)
}
}
Err(eyre::eyre!("Function not found in abi"))
}
pub fn find_source(
client: Client,
address: Address,
) -> Pin<Box<dyn Future<Output = Result<ContractMetadata>>>> {
Box::pin(async move {
trace!(%address, "find Etherscan source");
let source = client.contract_source_code(address).await?;
let metadata = source.items.first().wrap_err("Etherscan returned no data")?;
if metadata.proxy == 0 {
Ok(source)
} else {
let implementation = metadata.implementation.unwrap();
sh_println!(
"Contract at {address} is a proxy, trying to fetch source at {implementation}..."
)?;
match find_source(client, implementation).await {
impl_source @ Ok(_) => impl_source,
Err(e) => {
let err = EtherscanError::ContractCodeNotVerified(address).to_string();
if e.to_string() == err {
error!(%err);
Ok(source)
} else {
Err(e)
}
}
}
}
})
}
pub fn coerce_value(ty: &str, arg: &str) -> Result<DynSolValue> {
let ty = DynSolType::parse(ty)?;
Ok(DynSolType::coerce_str(&ty, arg)?)
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_dyn_abi::EventExt;
use alloy_primitives::{B256, U256};
#[test]
fn test_get_func() {
let func = get_func("function foo(uint256 a, uint256 b) returns (uint256)");
assert!(func.is_ok());
let func = func.unwrap();
assert_eq!(func.name, "foo");
assert_eq!(func.inputs.len(), 2);
assert_eq!(func.inputs[0].ty, "uint256");
assert_eq!(func.inputs[1].ty, "uint256");
let func = get_func("foo(bytes4 a, uint8 b)(bytes4)");
assert!(func.is_ok());
let func = func.unwrap();
assert_eq!(func.name, "foo");
assert_eq!(func.inputs.len(), 2);
assert_eq!(func.inputs[0].ty, "bytes4");
assert_eq!(func.inputs[1].ty, "uint8");
assert_eq!(func.outputs[0].ty, "bytes4");
}
#[test]
fn test_indexed_only_address() {
let event = get_event("event Ev(address,uint256,address)").unwrap();
let param0 = B256::random();
let param1 = vec![3; 32];
let param2 = B256::random();
let log = LogData::new_unchecked(vec![event.selector(), param0, param2], param1.into());
let event = get_indexed_event(event, &log);
assert_eq!(event.inputs.len(), 3);
let parsed = event.decode_log(&log, false).unwrap();
assert_eq!(event.inputs.iter().filter(|param| param.indexed).count(), 2);
assert_eq!(parsed.indexed[0], DynSolValue::Address(Address::from_word(param0)));
assert_eq!(parsed.body[0], DynSolValue::Uint(U256::from_be_bytes([3; 32]), 256));
assert_eq!(parsed.indexed[1], DynSolValue::Address(Address::from_word(param2)));
}
#[test]
fn test_indexed_all() {
let event = get_event("event Ev(address,uint256,address)").unwrap();
let param0 = B256::random();
let param1 = vec![3; 32];
let param2 = B256::random();
let log = LogData::new_unchecked(
vec![event.selector(), param0, B256::from_slice(¶m1), param2],
vec![].into(),
);
let event = get_indexed_event(event, &log);
assert_eq!(event.inputs.len(), 3);
assert_eq!(event.inputs.iter().filter(|param| param.indexed).count(), 3);
let parsed = event.decode_log(&log, false).unwrap();
assert_eq!(parsed.indexed[0], DynSolValue::Address(Address::from_word(param0)));
assert_eq!(parsed.indexed[1], DynSolValue::Uint(U256::from_be_bytes([3; 32]), 256));
assert_eq!(parsed.indexed[2], DynSolValue::Address(Address::from_word(param2)));
}
}