feat(abigen): add EthAbiCodec proc macro (#704)

* feat(abigen): add EthAbiCodec proc macro

* rustfmt

* fix: tuple codec
This commit is contained in:
Matthias Seitz 2021-12-19 15:40:17 +01:00 committed by GitHub
parent 12334443eb
commit 3c164bc9bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 192 additions and 20 deletions

View File

@ -43,6 +43,8 @@
### 0.6.0
- add `EthAbiCodec` proc macro to derive `AbiEncode` `AbiDecode` implementation
[#704](https://github.com/gakonst/ethers-rs/pull/704)
- move `AbiEncode` `AbiDecode` trait to ethers-core and implement for core types
[#531](https://github.com/gakonst/ethers-rs/pull/531)
- Add EIP-712 `sign_typed_data` signer method; add ethers-core type `Eip712`

View File

@ -129,7 +129,7 @@ impl Context {
let ethers_contract = ethers_contract_crate();
Ok(quote! {
#abi_signature_doc
#[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthAbiType, #derives)]
#[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #derives)]
pub struct #name {
#( #fields ),*
}
@ -191,7 +191,7 @@ impl Context {
Ok(quote! {
#abi_signature_doc
#[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthAbiType, #derives)]
#[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #derives)]
pub struct #name {
#( #fields ),*
}

View File

@ -0,0 +1,33 @@
//! Helper functions for deriving `EthAbiType`
use ethers_core::macros::ethers_core_crate;
use quote::quote;
use syn::DeriveInput;
/// Generates the `AbiEncode` + `AbiDecode` implementation
pub fn derive_codec_impl(input: &DeriveInput) -> proc_macro2::TokenStream {
let name = &input.ident;
let core_crate = ethers_core_crate();
quote! {
impl #core_crate::abi::AbiDecode for #name {
fn decode(bytes: impl AsRef<[u8]>) -> Result<Self, #core_crate::abi::AbiError> {
if let #core_crate::abi::ParamType::Tuple(params) = <Self as #core_crate::abi::AbiType>::param_type() {
let tokens = #core_crate::abi::decode(&params, bytes.as_ref())?;
Ok(<Self as #core_crate::abi::Tokenizable>::from_token(#core_crate::abi::Token::Tuple(tokens))?)
} else {
Err(
#core_crate::abi::InvalidOutputType("Expected tuple".to_string()).into()
)
}
}
}
impl #core_crate::abi::AbiEncode for #name {
fn encode(self) -> ::std::vec::Vec<u8> {
let tokens = #core_crate::abi::Tokenize::into_tokens(self);
#core_crate::abi::encode(&tokens)
}
}
}
}

View File

@ -10,6 +10,7 @@ use abigen::Contracts;
pub(crate) mod abi_ty;
mod abigen;
mod call;
mod codec;
mod display;
mod event;
mod spanned;
@ -43,7 +44,7 @@ pub(crate) mod utils;
/// `ETHERSCAN_API_KEY` environment variable can be set. If it is, it will use
/// that API key when retrieving the contract ABI.
///
/// Currently the proc macro accepts additional parameters to configure some
/// Currently, the proc macro accepts additional parameters to configure some
/// aspects of the code generation. Specifically it accepts:
/// - `methods`: A list of mappings from method signatures to method names allowing methods names to
/// be explicitely set for contract methods. This also provides a workaround for generating code
@ -94,7 +95,7 @@ pub fn abigen(input: TokenStream) -> TokenStream {
contracts.expand().unwrap_or_else(|err| err.to_compile_error()).into()
}
/// Derives the `Tokenizable` trait for the labeled type.
/// Derives the `AbiType` and all `Tokenizable` traits for the labeled type.
///
/// This derive macro automatically adds a type bound `field: Tokenizable` for
/// each field type.
@ -104,6 +105,36 @@ pub fn derive_abi_type(input: TokenStream) -> TokenStream {
TokenStream::from(abi_ty::derive_tokenizeable_impl(&input))
}
/// Derives the `AbiEncode`, `AbiDecode` and traits for the labeled type.
///
/// This is an addition to `EthAbiType` that lacks the `AbiEncode`, `AbiDecode` implementation.
///
/// The reason why this is a separate macro is the `AbiEncode` / `AbiDecode` are `ethers`
/// generalized codec traits used for types, calls, etc. However, encoding/decoding a call differs
/// from the basic encoding/decoding, (`[selector + encode(self)]`)
///
/// # Example
///
/// ```ignore
/// use ethers_contract::{EthAbiCodec, EthAbiType};
/// use ethers_core::types::*;
///
/// #[derive(Debug, Clone, EthAbiType, EthAbiCodec)]
/// struct MyStruct {
/// addr: Address,
/// old_value: String,
/// new_value: String,
/// }
/// let val = MyStruct {..};
/// let bytes = val.encode();
/// let val = MyStruct::decode(&bytes).unwrap();
/// ```
#[proc_macro_derive(EthAbiCodec)]
pub fn derive_abi_codec(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
TokenStream::from(codec::derive_codec_impl(&input))
}
/// Derives `fmt::Display` trait and generates a convenient format for all the
/// underlying primitive types/tokens.
///
@ -113,7 +144,7 @@ pub fn derive_abi_type(input: TokenStream) -> TokenStream {
/// # Example
///
/// ```ignore
/// use ethers_contract::EthDisplay;
/// use ethers_contract::{EthDisplay, EthAbiType};
/// use ethers_core::types::*;
///
/// #[derive(Debug, Clone, EthAbiType, EthDisplay)]

View File

@ -35,7 +35,7 @@ pub use ethers_contract_abigen::Abigen;
#[cfg(any(test, feature = "abigen"))]
#[cfg_attr(docsrs, doc(cfg(feature = "abigen")))]
pub use ethers_contract_derive::{abigen, EthAbiType, EthCall, EthDisplay, EthEvent};
pub use ethers_contract_derive::{abigen, EthAbiCodec, EthAbiType, EthCall, EthDisplay, EthEvent};
// Hide the Lazy re-export, it's just for convenience
#[doc(hidden)]

View File

@ -9,6 +9,9 @@ use ethers_providers::Provider;
use ethers_solc::Solc;
use std::{convert::TryFrom, sync::Arc};
fn assert_codec<T: AbiDecode + AbiEncode>() {}
fn assert_tokenizeable<T: Tokenizable>() {}
#[test]
fn can_gen_human_readable() {
abigen!(
@ -54,18 +57,24 @@ fn can_gen_structs_readable() {
]"#,
event_derives(serde::Deserialize, serde::Serialize)
);
let value = Addresses {
let addr = Addresses {
addr: vec!["eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse().unwrap()],
s: "hello".to_string(),
};
let token = value.clone().into_token();
assert_eq!(value, Addresses::from_token(token).unwrap());
let token = addr.clone().into_token();
assert_eq!(addr, Addresses::from_token(token).unwrap());
assert_eq!("ValueChanged", ValueChangedFilter::name());
assert_eq!(
"ValueChanged((address,string),(address,string),(address[],string))",
ValueChangedFilter::abi_signature()
);
assert_codec::<Value>();
assert_codec::<Addresses>();
let encoded = addr.clone().encode();
let other = Addresses::decode(&encoded).unwrap();
assert_eq!(addr, other);
}
#[test]
@ -83,9 +92,10 @@ fn can_gen_structs_with_arrays_readable() {
"ValueChanged((address,string),(address,string),(address[],string)[])",
ValueChangedFilter::abi_signature()
);
}
fn assert_tokenizeable<T: Tokenizable>() {}
assert_codec::<Value>();
assert_codec::<Addresses>();
}
#[test]
fn can_generate_internal_structs() {
@ -97,6 +107,10 @@ fn can_generate_internal_structs() {
assert_tokenizeable::<VerifyingKey>();
assert_tokenizeable::<G1Point>();
assert_tokenizeable::<G2Point>();
assert_codec::<VerifyingKey>();
assert_codec::<G1Point>();
assert_codec::<G2Point>();
}
#[test]
@ -119,6 +133,10 @@ fn can_generate_internal_structs_multiple() {
assert_tokenizeable::<G1Point>();
assert_tokenizeable::<G2Point>();
assert_codec::<VerifyingKey>();
assert_codec::<G1Point>();
assert_codec::<G2Point>();
let (provider, _) = Provider::mocked();
let client = Arc::new(provider);
@ -153,6 +171,7 @@ fn can_gen_human_readable_with_structs() {
event_derives(serde::Deserialize, serde::Serialize)
);
assert_tokenizeable::<Foo>();
assert_codec::<Foo>();
let (client, _mock) = Provider::mocked();
let contract = SimpleContract::new(Address::default(), Arc::new(client));

View File

@ -1,6 +1,8 @@
use ethers_contract::{abigen, EthAbiType, EthCall, EthDisplay, EthEvent, EthLogDecode};
use ethers_contract::{
abigen, EthAbiCodec, EthAbiType, EthCall, EthDisplay, EthEvent, EthLogDecode,
};
use ethers_core::{
abi::{RawLog, Tokenizable},
abi::{AbiDecode, AbiEncode, RawLog, Tokenizable},
types::{Address, H160, H256, I256, U128, U256},
};
@ -478,3 +480,83 @@ fn can_derive_for_enum() {
let token = ActionChoices::GoLeft.into_token();
assert_eq!(ActionChoices::GoLeft, ActionChoices::from_token(token).unwrap());
}
#[test]
fn can_derive_abi_codec() {
#[derive(Debug, Clone, PartialEq, EthAbiType, EthAbiCodec)]
pub struct SomeType {
inner: Address,
msg: String,
}
let val = SomeType { inner: Default::default(), msg: "hello".to_string() };
let encoded = val.clone().encode();
let other = SomeType::decode(&encoded).unwrap();
assert_eq!(val, other);
}
#[test]
fn can_derive_abi_codec_single_field() {
#[derive(Debug, Clone, PartialEq, EthAbiType, EthAbiCodec)]
pub struct SomeType {
inner: Vec<U256>,
}
let val = SomeType { inner: Default::default() };
let encoded = val.clone().encode();
let decoded = SomeType::decode(&encoded).unwrap();
assert_eq!(val, decoded);
let encoded_tuple = (Vec::<U256>::default(),).encode();
assert_eq!(encoded_tuple, encoded);
let decoded_tuple = SomeType::decode(&encoded_tuple).unwrap();
assert_eq!(decoded_tuple, decoded);
let tuple = (val,);
let encoded = tuple.clone().encode();
let decoded = <(SomeType,)>::decode(&encoded).unwrap();
assert_eq!(tuple, decoded);
let wrapped =
ethers_core::abi::encode(&ethers_core::abi::Tokenize::into_tokens(tuple.clone())).to_vec();
assert_eq!(wrapped, encoded);
let decoded_wrapped = <(SomeType,)>::decode(&wrapped).unwrap();
assert_eq!(decoded_wrapped, tuple);
}
#[test]
fn can_derive_abi_codec_two_field() {
#[derive(Debug, Clone, PartialEq, EthAbiType, EthAbiCodec)]
pub struct SomeType {
inner: Vec<U256>,
addr: Address,
}
let val = SomeType { inner: Default::default(), addr: Default::default() };
let encoded = val.clone().encode();
let decoded = SomeType::decode(&encoded).unwrap();
assert_eq!(val, decoded);
let encoded_tuple = (Vec::<U256>::default(), Address::default()).encode();
assert_eq!(encoded_tuple, encoded);
let decoded_tuple = SomeType::decode(&encoded_tuple).unwrap();
assert_eq!(decoded_tuple, decoded);
let tuple = (val,);
let encoded = tuple.clone().encode();
let decoded = <(SomeType,)>::decode(&encoded).unwrap();
assert_eq!(tuple, decoded);
let wrapped =
ethers_core::abi::encode(&ethers_core::abi::Tokenize::into_tokens(tuple.clone())).to_vec();
assert_eq!(wrapped, encoded);
let decoded_wrapped = <(SomeType,)>::decode(&wrapped).unwrap();
assert_eq!(decoded_wrapped, tuple);
}

View File

@ -1,5 +1,7 @@
use crate::{
abi::{AbiArrayType, AbiError, AbiType, Detokenize, Tokenizable, TokenizableItem},
abi::{
AbiArrayType, AbiError, AbiType, Detokenize, Token, Tokenizable, TokenizableItem, Tokenize,
},
types::{Address, H256, U128, U256},
};
@ -109,8 +111,7 @@ macro_rules! impl_abi_codec_tuple {
)+
{
fn encode(self) -> Vec<u8> {
let token = self.into_token();
crate::abi::encode(&[token]).into()
crate::abi::encode(&self.into_tokens()).into()
}
}
@ -119,10 +120,14 @@ macro_rules! impl_abi_codec_tuple {
$ty: AbiType + Tokenizable,
)+ {
fn decode(bytes: impl AsRef<[u8]>) -> Result<Self, AbiError> {
let tokens = crate::abi::decode(
&[Self::param_type()], bytes.as_ref()
)?;
Ok(<Self as Detokenize>::from_tokens(tokens)?)
if let crate::abi::ParamType::Tuple(params) = <Self as AbiType>::param_type() {
let tokens = crate::abi::decode(&params, bytes.as_ref())?;
Ok(<Self as Tokenizable>::from_token(Token::Tuple(tokens))?)
} else {
Err(
crate::abi::InvalidOutputType("Expected tuple".to_string()).into()
)
}
}
}
}