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
|
### 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)
|
||||||
|
|
|
@ -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
|
||||||
})
|
})
|
||||||
|
|
|
@ -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
|
//! 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(...)]`
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue