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:
parent
4608ddd9fa
commit
bef7960a2b
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
),*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
})
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
|
@ -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};
|
||||
|
@ -9,6 +9,7 @@ use abigen::Contracts;
|
|||
|
||||
pub(crate) mod abi_ty;
|
||||
mod abigen;
|
||||
mod display;
|
||||
mod event;
|
||||
mod spanned;
|
||||
pub(crate) mod utils;
|
||||
|
@ -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(...)]`
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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};
|
||||
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue