261 lines
8.0 KiB
Rust
261 lines
8.0 KiB
Rust
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<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_fn(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_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<D: Detokenize, T: AsRef<[u8]>>(
|
|
&self,
|
|
name: &str,
|
|
bytes: T,
|
|
) -> Result<D, AbiError> {
|
|
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<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_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<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 encoding arguments for a specific function
|
|
pub(crate) fn encode_fn<T: Tokenize>(function: &Function, args: T) -> Result<Bytes, AbiError> {
|
|
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<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: &HashMap<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()
|
|
);
|
|
}
|
|
}
|