use crate::Contract; use ethers_core::{ abi::{ Abi, Detokenize, Error, Event, Function, FunctionExt, InvalidOutputType, RawLog, Tokenize, }, types::{Address, Bytes, Selector, H256}, }; use ethers_providers::Middleware; use std::{collections::HashMap, fmt::Debug, hash::Hash, sync::Arc}; use thiserror::Error; #[derive(Error, Debug)] pub enum AbiError { /// Thrown when the ABI decoding fails #[error(transparent)] DecodingError(#[from] ethers_core::abi::Error), /// Thrown when detokenizing an argument #[error(transparent)] DetokenizationError(#[from] InvalidOutputType), #[error("missing or wrong function selector")] WrongSelector, } /// A reduced form of `Contract` which just takes the `abi` and produces /// ABI encoded data for its functions. #[derive(Debug, Clone)] pub struct BaseContract { pub(crate) abi: Abi, /// A mapping from method signature to a name-index pair for accessing /// functions in the contract ABI. This is used to avoid allocation when /// searching for matching functions by signature. // Adapted from: https://github.com/gnosis/ethcontract-rs/blob/master/src/contract.rs pub methods: HashMap, } impl From for BaseContract { /// Creates a new `BaseContract` from the abi. fn from(abi: Abi) -> Self { let methods = create_mapping(&abi.functions, |function| function.selector()); Self { abi, methods } } } impl BaseContract { /// Returns the ABI encoded data for the provided function and arguments /// /// If the function exists multiple times and you want to use one of the overloaded /// versions, consider using `encode_with_selector` pub fn encode(&self, name: &str, args: T) -> Result { let function = self.abi.function(name)?; encode_fn(function, args) } /// Returns the ABI encoded data for the provided function selector and arguments pub fn encode_with_selector( &self, signature: Selector, args: T, ) -> Result { let function = self.get_from_signature(signature)?; encode_fn(function, args) } /// Decodes the provided ABI encoded function arguments with the selected function name. /// /// If the function exists multiple times and you want to use one of the overloaded /// versions, consider using `decode_with_selector` pub fn decode>( &self, name: &str, bytes: T, ) -> Result { let function = self.abi.function(name)?; decode_fn(function, bytes, true) } /// Decodes for a given event name, given the `log.topics` and /// `log.data` fields from the transaction receipt pub fn decode_event( &self, name: &str, topics: Vec, data: Bytes, ) -> Result { let event = self.abi.event(name)?; decode_event(event, topics, data) } /// Decodes the provided ABI encoded bytes with the selected function selector pub fn decode_with_selector>( &self, signature: Selector, bytes: T, ) -> Result { let function = self.get_from_signature(signature)?; decode_fn(function, bytes, true) } fn get_from_signature(&self, signature: Selector) -> Result<&Function, AbiError> { Ok(self .methods .get(&signature) .map(|(name, index)| &self.abi.functions[name][*index]) .ok_or_else(|| Error::InvalidName(hex::encode(signature)))?) } /// Returns a reference to the contract's ABI pub fn abi(&self) -> &Abi { &self.abi } /// Upgrades a `BaseContract` into a full fledged contract with an address and middleware. pub fn into_contract( self, address: Address, client: impl Into>, ) -> Contract { Contract::new(address, self, client) } } impl AsRef for BaseContract { fn as_ref(&self) -> &Abi { self.abi() } } pub(crate) fn decode_event( event: &Event, topics: Vec, data: Bytes, ) -> Result { let tokens = event .parse_log(RawLog { topics, data: data.to_vec(), })? .params .into_iter() .map(|param| param.value) .collect::>(); Ok(D::from_tokens(tokens)?) } // Helper for encoding arguments for a specific function pub(crate) fn encode_fn(function: &Function, args: T) -> Result { let tokens = args.into_tokens(); Ok(function.encode_input(&tokens).map(Into::into)?) } // Helper for decoding bytes from a specific function pub fn decode_fn>( function: &Function, bytes: T, is_input: bool, ) -> Result { let bytes = bytes.as_ref(); let tokens = if is_input { if bytes.len() < 4 || bytes[..4] != function.selector() { return Err(AbiError::WrongSelector); } function.decode_input(&bytes[4..])? } else { function.decode_output(bytes)? }; Ok(D::from_tokens(tokens)?) } /// Utility function for creating a mapping between a unique signature and a /// name-index pair for accessing contract ABI items. fn create_mapping( elements: &HashMap>, signature: F, ) -> HashMap where S: Hash + Eq, F: Fn(&T) -> S, { let signature = &signature; elements .iter() .flat_map(|(name, sub_elements)| { sub_elements .iter() .enumerate() .map(move |(index, element)| (signature(element), (name.to_owned(), index))) }) .collect() } #[cfg(test)] mod tests { use super::*; use ethers_core::{abi::parse_abi, types::U256}; #[test] fn can_parse_function_inputs() { let abi = BaseContract::from(parse_abi(&[ "function approve(address _spender, uint256 value) external view returns (bool, bool)" ]).unwrap()); let spender = "7a250d5630b4cf539739df2c5dacb4c659f2488d" .parse::
() .unwrap(); let amount = U256::MAX; let encoded = abi.encode("approve", (spender, amount)).unwrap(); assert_eq!(hex::encode(&encoded), "095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); let (spender2, amount2): (Address, U256) = abi.decode("approve", encoded).unwrap(); assert_eq!(spender, spender2); assert_eq!(amount, amount2); } #[test] fn can_parse_events() { let abi = BaseContract::from( parse_abi(&[ "event Approval(address indexed owner, address indexed spender, uint256 value)", ]) .unwrap(), ); let topics = vec![ "8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", "000000000000000000000000e4e60fdf9bf188fa57b7a5022230363d5bd56d08", "0000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d", ] .into_iter() .map(|hash| hash.parse::().unwrap()) .collect::>(); let data = Bytes::from( hex::decode("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") .unwrap(), ); let (owner, spender, value): (Address, Address, U256) = abi.decode_event("Approval", topics, data).unwrap(); assert_eq!(value, U256::MAX); assert_eq!( owner, "e4e60fdf9bf188fa57b7a5022230363d5bd56d08" .parse::
() .unwrap() ); assert_eq!( spender, "7a250d5630b4cf539739df2c5dacb4c659f2488d" .parse::
() .unwrap() ); } }