Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

feat: function call enums EthCall macro and more #517

Merged
merged 27 commits into from
Oct 18, 2021
Merged
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
db51b38
fix: do not sort event variants
mattsse Oct 16, 2021
90b2c1b
style: use deref over clone
mattsse Oct 16, 2021
42bb563
style: refactor some stuff
mattsse Oct 16, 2021
28bd693
feat: add ethcall trait
mattsse Oct 16, 2021
7ec449c
feat: include in abigen
mattsse Oct 16, 2021
21b79ce
feat: add bare bones eth call derive
mattsse Oct 16, 2021
748a0a2
feat: impl EthCall derive
mattsse Oct 16, 2021
053b311
feat: support enums
mattsse Oct 16, 2021
00f8ed9
feat: use abigen enum derive
mattsse Oct 16, 2021
c6af805
fix: concrete abi and map errors
mattsse Oct 16, 2021
36e62a7
test: first call test
mattsse Oct 16, 2021
12fa801
rustfmt
mattsse Oct 16, 2021
62a53cc
chore: use correct trait name on error
mattsse Oct 16, 2021
3a1047f
feat: derive display for call structs
mattsse Oct 16, 2021
1deadab
feat: add from conversion
mattsse Oct 17, 2021
a011619
test: add convert test
mattsse Oct 17, 2021
f37fe20
chore: docs and test
mattsse Oct 17, 2021
cc12a49
chore: update changelog
mattsse Oct 17, 2021
d862a75
cargo fix
mattsse Oct 17, 2021
091d9cc
feat: add unit type derive support and more test
mattsse Oct 17, 2021
4897b0b
chore: patch ethabi
mattsse Oct 17, 2021
449f988
chore: rm ethabi patch
mattsse Oct 17, 2021
3ffb8be
feat: add encode/decode trait impls
mattsse Oct 17, 2021
7be4b59
style: use AsRef<[u8]>
mattsse Oct 17, 2021
68a8acb
Update ethers-contract/ethers-contract-abigen/src/contract/methods.rs
mattsse Oct 18, 2021
ee9aa89
style: reindent macro body
mattsse Oct 18, 2021
9277ebc
test: add tuple event test
mattsse Oct 18, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: add encode/decode trait impls
mattsse committed Oct 17, 2021
commit 3ffb8be3631cd05476146dc57f3cd9560700fea0
20 changes: 14 additions & 6 deletions ethers-contract/ethers-contract-abigen/src/contract/methods.rs
Original file line number Diff line number Diff line change
@@ -111,16 +111,24 @@ impl Context {
#(#variant_names(#struct_names)),*
}

impl #enum_name {

/// Decodes the provided ABI encoded function arguments with the selected function name.
pub fn decode(data: &[u8]) -> Result<Self, ethers_core::abi::Error> {
impl #ethers_contract::AbiDecode for #enum_name {
fn decode(data: &[u8]) -> Result<Self, #ethers_contract::AbiError> {
#(
if let Ok(decoded) = <#struct_names as #ethers_contract::EthCall>::decode(data) {
if let Ok(decoded) = <#struct_names as #ethers_contract::AbiDecode>::decode(data) {
return Ok(#enum_name::#variant_names(decoded))
}
)*
Err(#ethers_core::abi::Error::InvalidData)
Err(#ethers_core::abi::Error::InvalidData.into())
}
}

impl #ethers_contract::AbiEncode for #enum_name {
fn encode(self) -> Result<#ethers_core::types::Bytes, #ethers_contract::AbiError> {
match self {
#(
#enum_name::#variant_names(element) => element.encode()
),*
}
}
}

15 changes: 9 additions & 6 deletions ethers-contract/ethers-contract-derive/src/call.rs
Original file line number Diff line number Diff line change
@@ -96,12 +96,14 @@ pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> TokenStream {
#abi.into()
}

fn decode(bytes: &[u8]) -> Result<Self, #core_crate::abi::Error> where Self: Sized {
}

impl #contract_crate::AbiDecode for #name {
fn decode(bytes: &[u8]) -> Result<Self, #contract_crate::AbiError> {
#decode_impl
}
}
};

let tokenize_impl = abi_ty::derive_tokenizeable_impl(&input);

quote! {
@@ -112,6 +114,7 @@ pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> TokenStream {

fn derive_decode_impl(function: &Function) -> TokenStream {
let core_crate = ethers_core_crate();
let contract_crate = ethers_contract_crate();
let data_types = function
.inputs
.iter()
@@ -120,12 +123,12 @@ fn derive_decode_impl(function: &Function) -> TokenStream {
let data_types_init = quote! {let data_types = [#( #data_types ),*];};

quote! {
if bytes.len() < 4 || bytes[..4] != Self::selector() {
return Err(#core_crate::abi::Error::InvalidData);
if bytes.len() < 4 || bytes[..4] != <Self as #contract_crate::EthCall>::selector() {
return Err(#contract_crate::AbiError::WrongSelector);
}
#data_types_init
let data_tokens = #core_crate::abi::decode(&data_types, &bytes[4..]).map_err(|_|#core_crate::abi::Error::InvalidData)?;
<Self as #core_crate::abi::Tokenizable>::from_token( #core_crate::abi::Token::Tuple(data_tokens)).map_err(|_|#core_crate::abi::Error::InvalidData)
let data_tokens = #core_crate::abi::decode(&data_types, &bytes[4..])?;
Ok(<Self as #core_crate::abi::Tokenizable>::from_token( #core_crate::abi::Token::Tuple(data_tokens))?)
}
}

22 changes: 16 additions & 6 deletions ethers-contract/src/call.rs
Original file line number Diff line number Diff line change
@@ -10,13 +10,14 @@ use ethers_providers::{Middleware, PendingTransaction, ProviderError};
use std::borrow::Cow;
use std::{fmt::Debug, marker::PhantomData, sync::Arc};

use ethers_core::abi::Tokenizable;
use crate::{AbiDecode, AbiEncode};
use ethers_core::abi::{Tokenizable, Tokenize};
use ethers_core::types::Selector;
use ethers_core::utils::id;
use thiserror::Error as ThisError;

/// A helper trait for types that represent all call input parameters of a specific function
pub trait EthCall: Tokenizable + Send + Sync {
pub trait EthCall: Tokenizable + AbiDecode + Send + Sync {
/// The name of the function
fn function_name() -> Cow<'static, str>;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the advantage of returning a Cow instead of a 'static str here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currently none, I think, but I wasn't sure whether this also allows others to implement calls that construct the function name dynamically, since this is a type function, &'static str should be sufficient.


@@ -27,11 +28,20 @@ pub trait EthCall: Tokenizable + Send + Sync {
fn selector() -> Selector {
id(Self::abi_signature())
}
}

/// Decodes the provided ABI encoded function arguments with the selected function name.
fn decode(bytes: &[u8]) -> Result<Self, ethers_core::abi::Error>
where
Self: Sized;
impl<T: EthCall> AbiEncode for T {
fn encode(self) -> Result<Bytes, AbiError> {
let tokens = self.into_tokens();
let selector = Self::selector();
let encoded = ethers_core::abi::encode(&tokens);
let encoded: Vec<_> = selector
.iter()
.copied()
.chain(encoded.into_iter())
.collect();
Ok(encoded.into())
}
}

#[derive(ThisError, Debug)]
14 changes: 14 additions & 0 deletions ethers-contract/src/codec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use crate::AbiError;
use ethers_core::types::Bytes;

/// Trait for ABI encoding
pub trait AbiEncode {
/// ABI encode the type
fn encode(self) -> Result<Bytes, AbiError>;
}

/// Trait for ABI decoding
pub trait AbiDecode: Sized {
/// Decodes the ABI encoded data
fn decode(bytes: &[u8]) -> Result<Self, AbiError>;
}
3 changes: 3 additions & 0 deletions ethers-contract/src/lib.rs
Original file line number Diff line number Diff line change
@@ -31,6 +31,9 @@ pub use event::EthEvent;
mod log;
pub use log::{decode_logs, EthLogDecode, LogMeta};

mod codec;
pub use codec::{AbiDecode, AbiEncode};

mod stream;

mod multicall;
11 changes: 10 additions & 1 deletion ethers-contract/tests/abigen.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![cfg(feature = "abigen")]
//! Test cases to validate the `abigen!` macro
use ethers_contract::{abigen, EthCall, EthEvent};
use ethers_contract::{abigen, AbiDecode, AbiEncode, EthEvent};
use ethers_core::abi::{Address, Tokenizable};
use ethers_core::types::U256;
use ethers_providers::Provider;
@@ -181,22 +181,26 @@ fn can_gen_human_readable_with_structs() {
addr: Address::random(),
};
let encoded_call = contract.encode("bar", (call.x, call.y, call.addr)).unwrap();
assert_eq!(encoded_call, call.clone().encode().unwrap());
let decoded_call = BarCall::decode(encoded_call.as_ref()).unwrap();
assert_eq!(call, decoded_call);

let contract_call = SimpleContractCalls::Bar(call);
let decoded_enum = SimpleContractCalls::decode(encoded_call.as_ref()).unwrap();
assert_eq!(contract_call, decoded_enum);
assert_eq!(encoded_call, contract_call.encode().unwrap());

let call = YeetCall(1u64.into(), 0u64.into(), Address::zero());
let encoded_call = contract.encode("yeet", (call.0, call.1, call.2)).unwrap();
assert_eq!(encoded_call, call.clone().encode().unwrap());
let decoded_call = YeetCall::decode(encoded_call.as_ref()).unwrap();
assert_eq!(call, decoded_call);

let contract_call = SimpleContractCalls::Yeet(call.clone());
let decoded_enum = SimpleContractCalls::decode(encoded_call.as_ref()).unwrap();
assert_eq!(contract_call, decoded_enum);
assert_eq!(contract_call, call.into());
assert_eq!(encoded_call, contract_call.encode().unwrap());
}

#[test]
@@ -221,12 +225,14 @@ fn can_handle_overloaded_functions() {
let call = GetValueCall;

let encoded_call = contract.encode("getValue", ()).unwrap();
assert_eq!(encoded_call, call.clone().encode().unwrap());
let decoded_call = GetValueCall::decode(encoded_call.as_ref()).unwrap();
assert_eq!(call, decoded_call);

let contract_call = SimpleContractCalls::GetValue(call);
let decoded_enum = SimpleContractCalls::decode(encoded_call.as_ref()).unwrap();
assert_eq!(contract_call, decoded_enum);
assert_eq!(encoded_call, contract_call.encode().unwrap());

let call = GetValueWithOtherValueCall {
other_value: 420u64.into(),
@@ -235,12 +241,14 @@ fn can_handle_overloaded_functions() {
let encoded_call = contract
.encode_with_selector([15, 244, 201, 22], call.other_value)
.unwrap();
assert_eq!(encoded_call, call.clone().encode().unwrap());
let decoded_call = GetValueWithOtherValueCall::decode(encoded_call.as_ref()).unwrap();
assert_eq!(call, decoded_call);

let contract_call = SimpleContractCalls::GetValueWithOtherValue(call);
let decoded_enum = SimpleContractCalls::decode(encoded_call.as_ref()).unwrap();
assert_eq!(contract_call, decoded_enum);
assert_eq!(encoded_call, contract_call.encode().unwrap());

let call = GetValueWithOtherValueAndAddrCall {
other_value: 420u64.into(),
@@ -256,4 +264,5 @@ fn can_handle_overloaded_functions() {
let contract_call = SimpleContractCalls::GetValueWithOtherValueAndAddr(call);
let decoded_enum = SimpleContractCalls::decode(encoded_call.as_ref()).unwrap();
assert_eq!(contract_call, decoded_enum);
assert_eq!(encoded_call, contract_call.encode().unwrap());
}
2 changes: 1 addition & 1 deletion ethers-contract/tests/common/derive.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use ethers_contract::EthLogDecode;
use ethers_contract::{abigen, EthAbiType, EthCall, EthDisplay, EthEvent};
use ethers_contract::{abigen, AbiDecode, EthAbiType, EthCall, EthDisplay, EthEvent};
use ethers_core::abi::{RawLog, Tokenizable};
use ethers_core::types::Address;
use ethers_core::types::{H160, H256, I256, U128, U256};