ethers-contract 0.5.3

Smart contract bindings for the ethers-rs crate
Documentation
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::{BTreeMap, 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<Selector, (String, usize)>,
}

impl From<Abi> 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<T: Tokenize>(&self, name: &str, args: T) -> Result<Bytes, AbiError> {
        let function = self.abi.function(name)?;
        encode_function_data(function, args)
    }

    /// Returns the ABI encoded data for the provided function selector and arguments
    pub fn encode_with_selector<T: Tokenize>(
        &self,
        signature: Selector,
        args: T,
    ) -> Result<Bytes, AbiError> {
        let function = self.get_from_signature(signature)?;
        encode_function_data(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<D: Detokenize, T: AsRef<[u8]>>(
        &self,
        name: &str,
        bytes: T,
    ) -> Result<D, AbiError> {
        let function = self.abi.function(name)?;
        decode_function_data(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<D: Detokenize>(
        &self,
        name: &str,
        topics: Vec<H256>,
        data: Bytes,
    ) -> Result<D, AbiError> {
        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<D: Detokenize, T: AsRef<[u8]>>(
        &self,
        signature: Selector,
        bytes: T,
    ) -> Result<D, AbiError> {
        let function = self.get_from_signature(signature)?;
        decode_function_data(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<M: Middleware>(
        self,
        address: Address,
        client: impl Into<Arc<M>>,
    ) -> Contract<M> {
        Contract::new(address, self, client)
    }
}

impl AsRef<Abi> for BaseContract {
    fn as_ref(&self) -> &Abi {
        self.abi()
    }
}

pub(crate) fn decode_event<D: Detokenize>(
    event: &Event,
    topics: Vec<H256>,
    data: Bytes,
) -> Result<D, AbiError> {
    let tokens = event
        .parse_log(RawLog {
            topics,
            data: data.to_vec(),
        })?
        .params
        .into_iter()
        .map(|param| param.value)
        .collect::<Vec<_>>();
    Ok(D::from_tokens(tokens)?)
}

/// Helper for ABI encoding arguments for a specific function
pub fn encode_function_data<T: Tokenize>(function: &Function, args: T) -> Result<Bytes, AbiError> {
    let tokens = args.into_tokens();
    Ok(function.encode_input(&tokens).map(Into::into)?)
}

/// Helper for ABI decoding raw data based on a function's input or output.
pub fn decode_function_data<D: Detokenize, T: AsRef<[u8]>>(
    function: &Function,
    bytes: T,
    is_input: bool,
) -> Result<D, AbiError> {
    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<T, S, F>(
    elements: &BTreeMap<String, Vec<T>>,
    signature: F,
) -> HashMap<S, (String, usize)>
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::<Address>()
            .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::<H256>().unwrap())
        .collect::<Vec<_>>();
        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::<Address>()
                .unwrap()
        );
        assert_eq!(
            spender,
            "7a250d5630b4cf539739df2c5dacb4c659f2488d"
                .parse::<Address>()
                .unwrap()
        );
    }
}