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

feat: add abi code trait impls #531

Merged
merged 5 commits into from
Oct 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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