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

Commit

Permalink
feat: add abi code trait impls (#531)
Browse files Browse the repository at this point in the history
* feat: use const generics for array tokenize

* feat: add abi encode decode impls

* test: add some tests

* chore: move abi codec to core

* update changelog
  • Loading branch information
mattsse authored Oct 27, 2021
1 parent e089ad7 commit eede86d
Show file tree
Hide file tree
Showing 14 changed files with 356 additions and 170 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Unreleased

- move `AbiEncode` `AbiDecode` trait to ethers-core and implement for core types [#531](https://github.com/gakonst/ethers-rs/pull/531)
- add `EthCall` trait and derive macro which generates matching structs for contract calls [#517](https://github.com/gakonst/ethers-rs/pull/517)
- `abigen!` now generates `Display` for all events using the new `EthDisplay` macro [#513](https://github.com/gakonst/ethers-rs/pull/513)
- `abigen!` now supports overloaded functions natively [#501](https://github.com/gakonst/ethers-rs/pull/501)
Expand Down
10 changes: 5 additions & 5 deletions ethers-contract/ethers-contract-abigen/src/contract/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,19 +111,19 @@ impl Context {
#(#variant_names(#struct_names)),*
}

impl #ethers_contract::AbiDecode for #enum_name {
fn decode(data: impl AsRef<[u8]>) -> Result<Self, #ethers_contract::AbiError> {
impl #ethers_core::abi::AbiDecode for #enum_name {
fn decode(data: impl AsRef<[u8]>) -> Result<Self, #ethers_core::abi::AbiError> {
#(
if let Ok(decoded) = <#struct_names as #ethers_contract::AbiDecode>::decode(data.as_ref()) {
if let Ok(decoded) = <#struct_names as #ethers_core::abi::AbiDecode>::decode(data.as_ref()) {
return Ok(#enum_name::#variant_names(decoded))
}
)*
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> {
impl #ethers_core::abi::AbiEncode for #enum_name {
fn encode(self) -> Vec<u8> {
match self {
#(
#enum_name::#variant_names(element) => element.encode()
Expand Down
19 changes: 16 additions & 3 deletions ethers-contract/ethers-contract-derive/src/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,27 @@ pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> TokenStream {
fn abi_signature() -> ::std::borrow::Cow<'static, str> {
#abi.into()
}

}

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

impl #core_crate::abi::AbiEncode for #name {
fn encode(self) -> ::std::vec::Vec<u8> {
let tokens = #core_crate::abi::Tokenize::into_tokens(self);
let selector = <Self as #contract_crate::EthCall>::selector();
let encoded = #core_crate::abi::encode(&tokens);
selector
.iter()
.copied()
.chain(encoded.into_iter())
.collect()
}
}

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

Expand Down
20 changes: 2 additions & 18 deletions ethers-contract/src/base.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use crate::Contract;

pub use ethers_core::abi::AbiError;
use ethers_core::{
abi::{
Abi, Detokenize, Error, Event, Function, FunctionExt, InvalidOutputType, RawLog, Tokenize,
},
abi::{Abi, Detokenize, Error, Event, Function, FunctionExt, RawLog, Tokenize},
types::{Address, Bytes, Selector, H256},
};
use ethers_providers::Middleware;
Expand All @@ -14,21 +13,6 @@ use std::{
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.
Expand Down
29 changes: 6 additions & 23 deletions ethers-contract/src/call.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
use super::base::{decode_function_data, AbiError};
use ethers_core::{
abi::{Detokenize, Function, InvalidOutputType},
abi::{AbiDecode, AbiEncode, Detokenize, Function, InvalidOutputType, Tokenizable},
types::{
transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, TransactionRequest, U256,
transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, Selector,
TransactionRequest, U256,
},
utils::id,
};
use ethers_providers::{Middleware, PendingTransaction, ProviderError};

use std::borrow::Cow;
use std::{fmt::Debug, marker::PhantomData, sync::Arc};
use std::{borrow::Cow, fmt::Debug, marker::PhantomData, sync::Arc};

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 + AbiDecode + Send + Sync {
pub trait EthCall: Tokenizable + AbiDecode + AbiEncode + Send + Sync {
/// The name of the function
fn function_name() -> Cow<'static, str>;

Expand All @@ -30,20 +27,6 @@ pub trait EthCall: Tokenizable + AbiDecode + Send + Sync {
}
}

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)]
/// An Error which is thrown when interacting with a smart contract
pub enum ContractError<M: Middleware> {
Expand Down
14 changes: 0 additions & 14 deletions ethers-contract/src/codec.rs

This file was deleted.

3 changes: 0 additions & 3 deletions ethers-contract/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ pub use event::EthEvent;
mod log;
pub use log::{decode_logs, EthLogDecode, LogMeta};

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

mod stream;

mod multicall;
Expand Down
26 changes: 13 additions & 13 deletions ethers-contract/tests/abigen.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![cfg(feature = "abigen")]
//! Test cases to validate the `abigen!` macro
use ethers_contract::{abigen, AbiDecode, AbiEncode, EthEvent};
use ethers_core::abi::{Address, Tokenizable};
use ethers_contract::{abigen, EthEvent};
use ethers_core::abi::{AbiDecode, AbiEncode, Address, Tokenizable};
use ethers_core::types::{transaction::eip2718::TypedTransaction, Eip1559TransactionRequest, U256};
use ethers_core::utils::Solc;
use ethers_providers::Provider;
Expand Down Expand Up @@ -183,26 +183,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());
assert_eq!(encoded_call, call.clone().encode().into());
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());
assert_eq!(encoded_call, contract_call.encode().into());

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());
assert_eq!(encoded_call, call.clone().encode().into());
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());
assert_eq!(encoded_call, contract_call.encode().into());
}

#[test]
Expand All @@ -227,14 +227,14 @@ fn can_handle_overloaded_functions() {
let call = GetValueCall;

let encoded_call = contract.encode("getValue", ()).unwrap();
assert_eq!(encoded_call, call.clone().encode().unwrap());
assert_eq!(encoded_call, call.clone().encode().into());
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());
assert_eq!(encoded_call, contract_call.encode().into());

let call = GetValueWithOtherValueCall {
other_value: 420u64.into(),
Expand All @@ -243,14 +243,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());
assert_eq!(encoded_call, call.clone().encode().into());
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());
assert_eq!(encoded_call, contract_call.encode().into());

let call = GetValueWithOtherValueAndAddrCall {
other_value: 420u64.into(),
Expand All @@ -266,7 +266,7 @@ 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());
assert_eq!(encoded_call, contract_call.encode().into());
}

#[tokio::test]
Expand All @@ -283,7 +283,7 @@ async fn can_handle_underscore_functions() {

// launcht the network & connect to it
let ganache = ethers_core::utils::Ganache::new().spawn();
let from = ganache.addresses()[0].clone();
let from = ganache.addresses()[0];
let provider = Provider::try_from(ganache.endpoint())
.unwrap()
.with_sender(from)
Expand Down Expand Up @@ -330,7 +330,7 @@ async fn can_handle_underscore_functions() {
// Manual call construction
use ethers_providers::Middleware;
// TODO: How do we handle underscores for calls here?
let data = simplestorage_mod::HashPuzzleCall.encode().unwrap();
let data = simplestorage_mod::HashPuzzleCall.encode();
let tx = Eip1559TransactionRequest::new().data(data).to(addr);
let tx = TypedTransaction::Eip1559(tx);
let res5 = client.call(&tx, None).await.unwrap();
Expand Down
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, AbiDecode, EthAbiType, EthCall, EthDisplay, EthEvent};
use ethers_contract::{abigen, EthAbiType, EthCall, EthDisplay, EthEvent};
use ethers_core::abi::{RawLog, Tokenizable};
use ethers_core::types::Address;
use ethers_core::types::{H160, H256, I256, U128, U256};
Expand Down
1 change: 1 addition & 0 deletions ethers-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ bincode = { version = "1.3.3", default-features = false }
once_cell = { version = "1.8.0" }
hex-literal = "0.3.3"
futures-util = { version = "0.3.17" }
rand = "0.8.4"

[features]
celo = ["legacy"] # celo support extends the transaction format with extra fields
Expand Down
Loading

0 comments on commit eede86d

Please sign in to comment.