feat: add display support for events (#513)

* chore: move proc macro implementation to separate modules

* feat: add display derive macro

* chore: reexport hex

* feat: add EthDisplay

* test: add display test

* fix: use ? op

* feat: derive EthDisplay in abigen

* feat: derive display for event enum

* chore: update changelog
This commit is contained in:
Matthias Seitz 2021-10-16 15:42:17 +02:00 committed by GitHub
parent 4608ddd9fa
commit bef7960a2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 201 additions and 7 deletions

View File

@ -4,6 +4,7 @@
### Unreleased ### Unreleased
- `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) - `abigen!` now supports overloaded functions natively [#501](https://github.com/gakonst/ethers-rs/pull/501)
- `abigen!` now supports multiple contracts [#498](https://github.com/gakonst/ethers-rs/pull/498) - `abigen!` now supports multiple contracts [#498](https://github.com/gakonst/ethers-rs/pull/498)
- Use rust types as contract function inputs for human readable abi [#482](https://github.com/gakonst/ethers-rs/pull/482) - Use rust types as contract function inputs for human readable abi [#482](https://github.com/gakonst/ethers-rs/pull/482)

View File

@ -105,6 +105,16 @@ impl Context {
Err(#ethers_core::abi::Error::InvalidData) Err(#ethers_core::abi::Error::InvalidData)
} }
} }
impl ::std::fmt::Display for #enum_name {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match self {
#(
#enum_name::#variants(element) => element.fmt(f)
),*
}
}
}
} }
} }
@ -150,7 +160,6 @@ impl Context {
/// we can replace it /// we can replace it
fn expand_input_type(&self, input: &EventParam) -> Result<TokenStream> { fn expand_input_type(&self, input: &EventParam) -> Result<TokenStream> {
let ethers_core = util::ethers_core_crate(); let ethers_core = util::ethers_core_crate();
Ok(match (&input.kind, input.indexed) { Ok(match (&input.kind, input.indexed) {
(ParamType::Array(ty), true) => { (ParamType::Array(ty), true) => {
if let ParamType::Tuple(..) = **ty { if let ParamType::Tuple(..) = **ty {
@ -184,7 +193,7 @@ impl Context {
quote! { #ethers_core::types::H256 } quote! { #ethers_core::types::H256 }
} }
(ParamType::Tuple(..), true) => { (ParamType::Tuple(..), true) => {
// represents an struct // represents a struct
if let Some(ty) = self if let Some(ty) = self
.abi_parser .abi_parser
.structs .structs
@ -269,7 +278,7 @@ impl Context {
let ethers_contract = util::ethers_contract_crate(); let ethers_contract = util::ethers_contract_crate();
Ok(quote! { Ok(quote! {
#[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthEvent, #derives)] #[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthEvent, #ethers_contract::EthDisplay, #derives)]
#[ethevent( name = #event_abi_name, abi = #abi_signature )] #[ethevent( name = #event_abi_name, abi = #abi_signature )]
pub #data_type_definition pub #data_type_definition
}) })

View File

@ -0,0 +1,112 @@
//! Helper functions for deriving `Display`
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::spanned::Spanned as _;
use syn::{parse::Error, Data, DeriveInput, Fields};
use ethers_contract_abigen::ethers_core_crate;
use ethers_core::abi::ParamType;
use crate::utils;
/// Derive `fmt::Display` for the given type
pub(crate) fn derive_eth_display_impl(input: DeriveInput) -> Result<TokenStream, Error> {
let fields: Vec<_> = match input.data {
Data::Struct(ref data) => match data.fields {
Fields::Named(ref fields) => fields.named.iter().collect(),
Fields::Unnamed(ref fields) => fields.unnamed.iter().collect(),
Fields::Unit => {
vec![]
}
},
Data::Enum(_) => {
return Err(Error::new(
input.span(),
"Enum types are not supported by EthDisplay",
))
}
Data::Union(_) => {
return Err(Error::new(
input.span(),
"Union types are not supported by EthDisplay",
))
}
};
let core_crate = ethers_core_crate();
let hex_encode = quote! {#core_crate::utils::hex::encode};
let mut fmts = TokenStream::new();
for (idx, field) in fields.iter().enumerate() {
let ident = field
.ident
.clone()
.unwrap_or_else(|| format_ident!("{}", idx));
let tokens = if let Ok(param) = utils::find_parameter_type(&field.ty) {
match param {
ParamType::Address | ParamType::Uint(_) | ParamType::Int(_) => {
quote! {
write!(f, "{:?}", self.#ident)?;
}
}
ParamType::Bytes => {
quote! {
write!(f, "0x{}", #hex_encode(self.#ident))?;
}
}
ParamType::Bool | ParamType::String => {
quote! {
self.#ident.fmt(f)?;
}
}
ParamType::Tuple(_) => {
quote! {
write!(f, "{:?}", &self.#ident)?;
}
}
ParamType::Array(ty) | ParamType::FixedArray(ty, _) => {
if *ty == ParamType::Uint(8) {
// `u8`
quote! {
write!(f, "0x{}", #hex_encode(&self.#ident[..]))?;
}
} else {
// format as array with `[arr[0].display, arr[1].display,...]`
quote! {
write!(f, "[")?;
for (idx, val) in self.#ident.iter().enumerate() {
write!(f, "{}", val)?;
if idx < self.#ident.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, "]")?;
}
}
}
ParamType::FixedBytes(_) => {
quote! {
write!(f, "0x{}", #hex_encode(&self.#ident))?;
}
}
}
} else {
// could not detect the parameter type and rely on using debug fmt
quote! {
write!(f, "{:?}", &self.#ident)?;
}
};
fmts.extend(tokens);
if idx < fields.len() - 1 {
fmts.extend(quote! { write!(f, ", ")?;});
}
}
let name = &input.ident;
Ok(quote! {
impl ::std::fmt::Display for #name {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
#fmts
Ok(())
}
}
})
}

View File

@ -1,6 +1,6 @@
//! Implementation of procedural macro for generating type-safe bindings to an //! Implementation of procedural macro for generating type-safe bindings to an
//! ethereum smart contract. //! ethereum smart contract.
#![deny(missing_docs, unsafe_code, unused)] #![deny(missing_docs, unsafe_code)]
use proc_macro::TokenStream; use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput}; use syn::{parse_macro_input, DeriveInput};
@ -9,6 +9,7 @@ use abigen::Contracts;
pub(crate) mod abi_ty; pub(crate) mod abi_ty;
mod abigen; mod abigen;
mod display;
mod event; mod event;
mod spanned; mod spanned;
pub(crate) mod utils; pub(crate) mod utils;
@ -101,6 +102,37 @@ pub fn derive_abi_type(input: TokenStream) -> TokenStream {
TokenStream::from(abi_ty::derive_tokenizeable_impl(&input)) TokenStream::from(abi_ty::derive_tokenizeable_impl(&input))
} }
/// Derives `fmt::Display` trait and generates a convenient format for all the
/// underlying primitive types/tokens.
///
/// The fields of the structure are formatted comma separated, like `self.0,
/// self.1, self.2,...`
///
/// # Example
///
/// ```ignore
/// #[derive(Debug, Clone, EthAbiType, EthDisplay)]
/// struct MyStruct {
/// addr: Address,
/// old_value: String,
/// new_value: String,
/// h: H256,
/// arr_u8: [u8; 32],
/// arr_u16: [u16; 32],
/// v: Vec<u8>,
/// }
/// let val = MyStruct {..};
/// format!("{}", val);
/// ```
#[proc_macro_derive(EthDisplay, attributes(ethdisplay))]
pub fn derive_eth_display(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match display::derive_eth_display_impl(input) {
Ok(tokens) => TokenStream::from(tokens),
Err(err) => err.to_compile_error().into(),
}
}
/// Derives the `EthEvent` and `Tokenizeable` trait for the labeled type. /// Derives the `EthEvent` and `Tokenizeable` trait for the labeled type.
/// ///
/// Additional arguments can be specified using the `#[ethevent(...)]` /// Additional arguments can be specified using the `#[ethevent(...)]`

View File

@ -94,7 +94,8 @@ pub fn param_type_quote(kind: &ParamType) -> proc_macro2::TokenStream {
} }
} }
/// Tries to find the corresponding `ParamType` for the given type /// Tries to find the corresponding `ParamType` used for tokenization for the
/// given type
pub fn find_parameter_type(ty: &Type) -> Result<ParamType, Error> { pub fn find_parameter_type(ty: &Type) -> Result<ParamType, Error> {
match ty { match ty {
Type::Array(ty) => { Type::Array(ty) => {

View File

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

View File

@ -1,5 +1,5 @@
use ethers_contract::EthLogDecode; use ethers_contract::EthLogDecode;
use ethers_contract::{abigen, EthAbiType, EthEvent}; use ethers_contract::{abigen, EthAbiType, EthDisplay, EthEvent};
use ethers_core::abi::{RawLog, Tokenizable}; use ethers_core::abi::{RawLog, Tokenizable};
use ethers_core::types::Address; use ethers_core::types::Address;
use ethers_core::types::{H160, H256, I256, U128, U256}; use ethers_core::types::{H160, H256, I256, U128, U256};
@ -335,3 +335,39 @@ fn can_decode_event_with_no_params() {
let _ = <NoParam as EthLogDecode>::decode_log(&log).unwrap(); let _ = <NoParam as EthLogDecode>::decode_log(&log).unwrap();
} }
#[test]
fn eth_display_works() {
#[derive(Debug, Clone, EthAbiType, EthDisplay)]
struct MyStruct {
addr: Address,
old_value: String,
new_value: String,
h: H256,
i: I256,
arr_u8: [u8; 32],
arr_u16: [u16; 32],
v: Vec<u8>,
}
let item = MyStruct {
addr: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".parse().unwrap(),
old_value: "50".to_string(),
new_value: "100".to_string(),
h: H256::random(),
i: I256::zero(),
arr_u8: [0; 32],
arr_u16: [1; 32],
v: vec![0; 32],
};
let val = format!(
"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, 50, 100, 0x{}, {}, 0x{}, {:?}, 0x{}",
hex::encode(&item.h),
item.i,
hex::encode(&item.arr_u8),
item.arr_u16,
hex::encode(&item.v),
);
assert_eq!(val, format!("{}", item));
}

View File

@ -33,6 +33,9 @@ pub use units::Units;
/// Re-export RLP /// Re-export RLP
pub use rlp; pub use rlp;
/// Re-export hex
pub use hex;
use crate::types::{Address, Bytes, U256}; use crate::types::{Address, Bytes, U256};
use k256::{ecdsa::SigningKey, EncodedPoint as K256PublicKey}; use k256::{ecdsa::SigningKey, EncodedPoint as K256PublicKey};
use std::ops::Neg; use std::ops::Neg;