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

feat: add display support for events #513

Merged
merged 10 commits into from
Oct 16, 2021
Merged
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

- `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 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)
Expand Down
15 changes: 12 additions & 3 deletions ethers-contract/ethers-contract-abigen/src/contract/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@ impl Context {
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)
),*
}
}
}
}
}

Expand Down Expand Up @@ -150,7 +160,6 @@ impl Context {
/// we can replace it
fn expand_input_type(&self, input: &EventParam) -> Result<TokenStream> {
let ethers_core = util::ethers_core_crate();

Ok(match (&input.kind, input.indexed) {
(ParamType::Array(ty), true) => {
if let ParamType::Tuple(..) = **ty {
Expand Down Expand Up @@ -184,7 +193,7 @@ impl Context {
quote! { #ethers_core::types::H256 }
}
(ParamType::Tuple(..), true) => {
// represents an struct
// represents a struct
if let Some(ty) = self
.abi_parser
.structs
Expand Down Expand Up @@ -269,7 +278,7 @@ impl Context {
let ethers_contract = util::ethers_contract_crate();

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 )]
pub #data_type_definition
})
Expand Down
112 changes: 112 additions & 0 deletions ethers-contract/ethers-contract-derive/src/display.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
}
})
}
34 changes: 33 additions & 1 deletion ethers-contract/ethers-contract-derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Implementation of procedural macro for generating type-safe bindings to an
//! ethereum smart contract.
#![deny(missing_docs, unsafe_code, unused)]
#![deny(missing_docs, unsafe_code)]

use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};
Expand All @@ -9,6 +9,7 @@ use abigen::Contracts;

pub(crate) mod abi_ty;
mod abigen;
mod display;
mod event;
mod spanned;
pub(crate) mod utils;
Expand Down Expand Up @@ -101,6 +102,37 @@ pub fn derive_abi_type(input: TokenStream) -> TokenStream {
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.
///
/// Additional arguments can be specified using the `#[ethevent(...)]`
Expand Down
3 changes: 2 additions & 1 deletion ethers-contract/ethers-contract-derive/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
match ty {
Type::Array(ty) => {
Expand Down
2 changes: 1 addition & 1 deletion ethers-contract/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,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, EthEvent};
pub use ethers_contract_derive::{abigen, EthAbiType, EthDisplay, EthEvent};

// Hide the Lazy re-export, it's just for convenience
#[doc(hidden)]
Expand Down
38 changes: 37 additions & 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, EthEvent};
use ethers_contract::{abigen, EthAbiType, EthDisplay, EthEvent};
use ethers_core::abi::{RawLog, Tokenizable};
use ethers_core::types::Address;
use ethers_core::types::{H160, H256, I256, U128, U256};
Expand Down Expand Up @@ -335,3 +335,39 @@ fn can_decode_event_with_no_params() {

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));
}
3 changes: 3 additions & 0 deletions ethers-core/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ pub use units::Units;
/// Re-export RLP
pub use rlp;

/// Re-export hex
pub use hex;

use crate::types::{Address, Bytes, U256};
use k256::{ecdsa::SigningKey, EncodedPoint as K256PublicKey};
use std::ops::Neg;
Expand Down