From 9626cc1335b624be4a8438f3082ef922f8abafe0 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Wed, 2 Mar 2022 15:58:18 +0200 Subject: [PATCH] feat: add contract interface helpers (#982) * feat(contract): add helpers for decoding as raw Token This allows decoding Events and Function data without knowing the return type * feat(contract): add helpers for decoding function outputs * chore(contract): remove redundant generic param --- ethers-contract/src/base.rs | 125 +++++++++++++++++++++++++++++++++--- 1 file changed, 116 insertions(+), 9 deletions(-) diff --git a/ethers-contract/src/base.rs b/ethers-contract/src/base.rs index 6bf51732..32db0c47 100644 --- a/ethers-contract/src/base.rs +++ b/ethers-contract/src/base.rs @@ -2,7 +2,7 @@ use crate::Contract; pub use ethers_core::abi::AbiError; use ethers_core::{ - abi::{Abi, Detokenize, Error, Event, Function, FunctionExt, RawLog, Tokenize}, + abi::{Abi, Detokenize, Error, Event, Function, FunctionExt, RawLog, Token, Tokenize}, types::{Address, Bytes, Selector, H256}, }; use ethers_providers::Middleware; @@ -68,6 +68,47 @@ impl BaseContract { decode_function_data(function, bytes, true) } + /// 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` + /// + /// Returns a [`Token`] vector, which lets you decode function arguments dynamically + /// without knowing the return type. + pub fn decode_raw>(&self, name: &str, bytes: T) -> Result, AbiError> { + let function = self.abi.function(name)?; + decode_function_data_raw(function, bytes, true) + } + + /// Decodes the provided ABI encoded function output 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_output>( + &self, + name: &str, + bytes: T, + ) -> Result { + let function = self.abi.function(name)?; + decode_function_data(function, bytes, false) + } + + /// Decodes the provided ABI encoded function output 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` + /// + /// Returns a [`Token`] vector, which lets you decode function arguments dynamically + /// without knowing the return type. + pub fn decode_output_raw>( + &self, + name: &str, + bytes: T, + ) -> Result, AbiError> { + let function = self.abi.function(name)?; + decode_function_data_raw(function, bytes, false) + } + /// Decodes for a given event name, given the `log.topics` and /// `log.data` fields from the transaction receipt pub fn decode_event( @@ -80,6 +121,34 @@ impl BaseContract { decode_event(event, topics, data) } + /// Decodes for a given event name, given the `log.topics` and + /// `log.data` fields from the transaction receipt + /// + /// Returns a [`Token`] vector, which lets you decode function arguments dynamically + /// without knowing the return type. + pub fn decode_event_raw( + &self, + name: &str, + topics: Vec, + data: Bytes, + ) -> Result, AbiError> { + let event = self.abi.event(name)?; + decode_event_raw(event, topics, data) + } + + /// Decodes the provided ABI encoded bytes with the selected function selector + /// + /// Returns a [`Token`] vector, which lets you decode function arguments dynamically + /// without knowing the return type. + pub fn decode_with_selector_raw>( + &self, + signature: Selector, + bytes: T, + ) -> Result, AbiError> { + let function = self.get_from_signature(signature)?; + decode_function_data_raw(function, bytes, true) + } + /// Decodes the provided ABI encoded bytes with the selected function selector pub fn decode_with_selector>( &self, @@ -90,6 +159,28 @@ impl BaseContract { decode_function_data(function, bytes, true) } + pub fn decode_output_with_selector>( + &self, + signature: Selector, + bytes: T, + ) -> Result { + let function = self.get_from_signature(signature)?; + decode_function_data(function, bytes, false) + } + + /// Decodes the provided ABI encoded bytes with the selected function selector + /// + /// Returns a [`Token`] vector, which lets you decode function arguments dynamically + /// without knowing the return type. + pub fn decode_output_with_selector_raw>( + &self, + signature: Selector, + bytes: T, + ) -> Result, AbiError> { + let function = self.get_from_signature(signature)?; + decode_function_data_raw(function, bytes, false) + } + fn get_from_signature(&self, signature: Selector) -> Result<&Function, AbiError> { Ok(self .methods @@ -119,17 +210,25 @@ impl AsRef for BaseContract { } } -pub(crate) fn decode_event( +pub fn decode_event_raw( event: &Event, topics: Vec, data: Bytes, -) -> Result { - let tokens = event +) -> Result, AbiError> { + Ok(event .parse_log(RawLog { topics, data: data.to_vec() })? .params .into_iter() .map(|param| param.value) - .collect::>(); + .collect::>()) +} + +pub fn decode_event( + event: &Event, + topics: Vec, + data: Bytes, +) -> Result { + let tokens = decode_event_raw(event, topics, data)?; Ok(D::from_tokens(tokens)?) } @@ -140,21 +239,29 @@ pub fn encode_function_data(function: &Function, args: T) -> Result } /// Helper for ABI decoding raw data based on a function's input or output. -pub fn decode_function_data>( +pub fn decode_function_data_raw>( function: &Function, bytes: T, is_input: bool, -) -> Result { +) -> Result, AbiError> { let bytes = bytes.as_ref(); - let tokens = if is_input { + Ok(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)? - }; + }) +} +/// Helper for ABI decoding raw data based on a function's input or output. +pub fn decode_function_data>( + function: &Function, + bytes: T, + is_input: bool, +) -> Result { + let tokens = decode_function_data_raw(function, bytes, is_input)?; Ok(D::from_tokens(tokens)?) }