use alloy_dyn_abi::{DynSolType, DynSolValue, JsonAbiExt};
use alloy_json_abi::Function;
use anyhow::{Context, Result};
use hypersync_format::Data;
use std::collections::HashMap;
#[derive(Debug, Hash, Eq, PartialEq)]
struct FunctionKey {
signature: Vec<u8>,
}
type DecoderMap = HashMap<FunctionKey, Function>;
pub struct CallDecoder {
map: DecoderMap,
}
impl CallDecoder {
pub fn from_signatures<S: AsRef<str>>(signatures: &[S]) -> Result<Self> {
let map: DecoderMap = signatures
.iter()
.map(|sig| {
let function = Function::parse(sig.as_ref()).context("parse event signature")?;
let signature = function.selector().to_vec();
let event_key = FunctionKey { signature };
Ok((event_key, function))
})
.collect::<Result<DecoderMap>>()
.context("construct function decoder map")?;
Ok(Self { map })
}
pub fn decode_input(&self, data: &Data) -> Result<Option<Vec<DynSolValue>>> {
let function_key = FunctionKey {
signature: data[0..4].to_vec(),
};
let function = match self.map.get(&function_key) {
Some(function) => function,
None => return Ok(None),
};
let decoded = function
.abi_decode_input(data.as_ref())
.context("decoding input data")?;
Ok(Some(decoded))
}
pub fn decode_output(
&self,
data: &Data,
function_signature: &str,
) -> Result<Option<Vec<DynSolValue>>> {
let function = Function::parse(function_signature).context("parsing function signature")?;
let output_types = function.outputs;
let output_types: Vec<DynSolType> = output_types
.into_iter()
.map(|param| param.ty.parse::<DynSolType>())
.collect::<Result<_, _>>() .context("parsing output types")?;
let tuple_type = DynSolType::Tuple(output_types);
match tuple_type.abi_decode(data.as_ref()) {
Ok(decoded) => {
if let DynSolValue::Tuple(values) = decoded {
Ok(Some(values)) } else {
Ok(Some(vec![decoded])) }
}
Err(_) => {
Ok(None) }
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use hypersync_format::{Data, Hex};
#[test]
fn test_decode_input_with_single_signature() {
let function =
alloy_json_abi::Function::parse("transfer(address dst, uint256 wad)").unwrap();
let input = "0xa9059cbb000000000000000000000000dc4bde73fa35b7478a574f78d5dfd57a0b2e22810000000000000000000000000000000000000000000000004710ca26d3eeae0a";
let input = Data::decode_hex(input).unwrap();
let expected = function.abi_decode_input(input.as_ref()).unwrap();
let decoder =
CallDecoder::from_signatures(&["transfer(address dst, uint256 wad)"]).unwrap();
let got = decoder.decode_input(&input).unwrap().unwrap();
for (expected, got) in expected.iter().zip(got.iter()) {
assert_eq!(expected, got, "Checking that decodes are the same");
}
}
#[test]
fn test_decode_input_with_multiple_signature() {
let function =
alloy_json_abi::Function::parse("transfer(address dst, uint256 wad)").unwrap();
let input = "0xa9059cbb000000000000000000000000dc4bde73fa35b7478a574f78d5dfd57a0b2e22810000000000000000000000000000000000000000000000004710ca26d3eeae0a";
let input = Data::decode_hex(input).unwrap();
let expected = function.abi_decode_input(input.as_ref()).unwrap();
let decoder = CallDecoder::from_signatures(&[
"transfer(address dst, uint256 wad)",
"approve(address usr, uint256 wad)",
])
.unwrap();
let got = decoder.decode_input(&input).unwrap().unwrap();
for (expected, got) in expected.iter().zip(got.iter()) {
assert_eq!(expected, got, "Checking that decodes are the same");
}
}
#[test]
#[should_panic]
fn test_decode_input_with_incorrect_signature() {
let _function = alloy_json_abi::Function::parse("incorrect signature").unwrap();
}
#[test]
fn test_decode_output_single_value() {
let output = "0x0000000000000000000000000000000000000000000000056bc75e2d63100000";
let output = Data::decode_hex(output).unwrap();
let function_signature = "balanceOf(address)(uint256)";
let decoder = CallDecoder::from_signatures(&["balanceOf(address)"]).unwrap();
let result = decoder
.decode_output(&output, function_signature)
.unwrap()
.unwrap();
assert_eq!(result.len(), 1, "Should return a single value");
assert!(
matches!(result[0], DynSolValue::Uint(..)),
"Should be a uint value"
);
}
#[test]
fn test_decode_output_multiple_values() {
let output = "0x000000000000000000000000dc4bde73fa35b7478a574f78d5dfd57a0b2e22810000000000000000000000000000000000000000000000004710ca26d3eeae0a";
let output = Data::decode_hex(output).unwrap();
let function_signature = "someFunction()(address,uint256)";
let decoder = CallDecoder::from_signatures(&["someFunction()"]).unwrap();
let result = decoder
.decode_output(&output, function_signature)
.unwrap()
.unwrap();
assert_eq!(result.len(), 2, "Should return two values");
assert!(
matches!(result[0], DynSolValue::Address(..)),
"First value should be an address"
);
assert!(
matches!(result[1], DynSolValue::Uint(..)),
"Second value should be a uint"
);
}
#[test]
fn test_decode_output_invalid_data() {
let output = "invalid_data";
let _output = Data::decode_hex(output).unwrap_err();
let function_signature = "balanceOf(address)(uint256)";
let decoder = CallDecoder::from_signatures(&["balanceOf(address)"]).unwrap();
let result = decoder
.decode_output(&Data::default(), function_signature)
.unwrap();
assert!(result.is_none(), "Should return None for invalid data");
}
#[test]
#[should_panic(expected = "parsing function signature")]
fn test_decode_output_invalid_signature() {
let output = "0x0000000000000000000000000000000000000000000000056bc75e2d63100000";
let output = Data::decode_hex(output).unwrap();
let function_signature = "invalid signature";
let decoder = CallDecoder::from_signatures(&["balanceOf(address)"]).unwrap();
decoder.decode_output(&output, function_signature).unwrap();
}
}