chore: move proc macro implementation to separate modules (#510)
This commit is contained in:
parent
4996fa2ca4
commit
4608ddd9fa
|
@ -0,0 +1,171 @@
|
||||||
|
//! Helper functions for deriving `EthAbiType`
|
||||||
|
|
||||||
|
use ethers_contract_abigen::ethers_core_crate;
|
||||||
|
use quote::{quote, quote_spanned};
|
||||||
|
use syn::spanned::Spanned as _;
|
||||||
|
use syn::{parse::Error, Data, DeriveInput, Fields};
|
||||||
|
|
||||||
|
/// Generates the tokenize implementation
|
||||||
|
pub fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream {
|
||||||
|
let core_crate = ethers_core_crate();
|
||||||
|
let name = &input.ident;
|
||||||
|
let generic_params = input.generics.params.iter().map(|p| quote! { #p });
|
||||||
|
let generic_params = quote! { #(#generic_params,)* };
|
||||||
|
|
||||||
|
let generic_args = input.generics.type_params().map(|p| {
|
||||||
|
let name = &p.ident;
|
||||||
|
quote_spanned! { p.ident.span() => #name }
|
||||||
|
});
|
||||||
|
|
||||||
|
let generic_args = quote! { #(#generic_args,)* };
|
||||||
|
|
||||||
|
let generic_predicates = match input.generics.where_clause {
|
||||||
|
Some(ref clause) => {
|
||||||
|
let predicates = clause.predicates.iter().map(|p| quote! { #p });
|
||||||
|
quote! { #(#predicates,)* }
|
||||||
|
}
|
||||||
|
None => quote! {},
|
||||||
|
};
|
||||||
|
|
||||||
|
let (tokenize_predicates, params_len, init_struct_impl, into_token_impl) = match input.data {
|
||||||
|
Data::Struct(ref data) => match data.fields {
|
||||||
|
Fields::Named(ref fields) => {
|
||||||
|
let tokenize_predicates = fields.named.iter().map(|f| {
|
||||||
|
let ty = &f.ty;
|
||||||
|
quote_spanned! { f.span() => #ty: #core_crate::abi::Tokenize }
|
||||||
|
});
|
||||||
|
let tokenize_predicates = quote! { #(#tokenize_predicates,)* };
|
||||||
|
|
||||||
|
let assignments = fields.named.iter().map(|f| {
|
||||||
|
let name = f.ident.as_ref().expect("Named fields have names");
|
||||||
|
quote_spanned! { f.span() => #name: #core_crate::abi::Tokenizable::from_token(iter.next().expect("tokens size is sufficient qed").into_token())? }
|
||||||
|
});
|
||||||
|
let init_struct_impl = quote! { Self { #(#assignments,)* } };
|
||||||
|
|
||||||
|
let into_token = fields.named.iter().map(|f| {
|
||||||
|
let name = f.ident.as_ref().expect("Named fields have names");
|
||||||
|
quote_spanned! { f.span() => self.#name.into_token() }
|
||||||
|
});
|
||||||
|
let into_token_impl = quote! { #(#into_token,)* };
|
||||||
|
|
||||||
|
(
|
||||||
|
tokenize_predicates,
|
||||||
|
fields.named.len(),
|
||||||
|
init_struct_impl,
|
||||||
|
into_token_impl,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Fields::Unnamed(ref fields) => {
|
||||||
|
let tokenize_predicates = fields.unnamed.iter().map(|f| {
|
||||||
|
let ty = &f.ty;
|
||||||
|
quote_spanned! { f.span() => #ty: #core_crate::abi::Tokenize }
|
||||||
|
});
|
||||||
|
let tokenize_predicates = quote! { #(#tokenize_predicates,)* };
|
||||||
|
|
||||||
|
let assignments = fields.unnamed.iter().map(|f| {
|
||||||
|
quote_spanned! { f.span() => #core_crate::abi::Tokenizable::from_token(iter.next().expect("tokens size is sufficient qed").into_token())? }
|
||||||
|
});
|
||||||
|
let init_struct_impl = quote! { Self(#(#assignments,)* ) };
|
||||||
|
|
||||||
|
let into_token = fields.unnamed.iter().enumerate().map(|(i, f)| {
|
||||||
|
let idx = syn::Index::from(i);
|
||||||
|
quote_spanned! { f.span() => self.#idx.into_token() }
|
||||||
|
});
|
||||||
|
let into_token_impl = quote! { #(#into_token,)* };
|
||||||
|
|
||||||
|
(
|
||||||
|
tokenize_predicates,
|
||||||
|
fields.unnamed.len(),
|
||||||
|
init_struct_impl,
|
||||||
|
into_token_impl,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Fields::Unit => {
|
||||||
|
return Error::new(
|
||||||
|
input.span(),
|
||||||
|
"EthAbiType cannot be derived for empty structs and unit",
|
||||||
|
)
|
||||||
|
.to_compile_error();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Data::Enum(_) => {
|
||||||
|
return Error::new(input.span(), "EthAbiType cannot be derived for enums")
|
||||||
|
.to_compile_error();
|
||||||
|
}
|
||||||
|
Data::Union(_) => {
|
||||||
|
return Error::new(input.span(), "EthAbiType cannot be derived for unions")
|
||||||
|
.to_compile_error();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// there might be the case that the event has only 1 params, which is not a
|
||||||
|
// tuple
|
||||||
|
let (from_token_impl, into_token_impl) = match params_len {
|
||||||
|
0 => (
|
||||||
|
quote! {
|
||||||
|
Ok(#init_struct_impl)
|
||||||
|
},
|
||||||
|
// can't encode an empty struct
|
||||||
|
// TODO: panic instead?
|
||||||
|
quote! {
|
||||||
|
#core_crate::abi::Token::Tuple(Vec::new())
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_ => {
|
||||||
|
let from_token = quote! {
|
||||||
|
if let #core_crate::abi::Token::Tuple(tokens) = token {
|
||||||
|
if tokens.len() != #params_len {
|
||||||
|
return Err(#core_crate::abi::InvalidOutputType(::std::format!(
|
||||||
|
"Expected {} tokens, got {}: {:?}",
|
||||||
|
#params_len,
|
||||||
|
tokens.len(),
|
||||||
|
tokens
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut iter = tokens.into_iter();
|
||||||
|
|
||||||
|
Ok(#init_struct_impl)
|
||||||
|
} else {
|
||||||
|
Err(#core_crate::abi::InvalidOutputType(::std::format!(
|
||||||
|
"Expected Tuple, got {:?}",
|
||||||
|
token
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let into_token = quote! {
|
||||||
|
#core_crate::abi::Token::Tuple(
|
||||||
|
::std::vec![
|
||||||
|
#into_token_impl
|
||||||
|
]
|
||||||
|
)
|
||||||
|
};
|
||||||
|
(from_token, into_token)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
impl<#generic_params> #core_crate::abi::Tokenizable for #name<#generic_args>
|
||||||
|
where
|
||||||
|
#generic_predicates
|
||||||
|
#tokenize_predicates
|
||||||
|
{
|
||||||
|
|
||||||
|
fn from_token(token: #core_crate::abi::Token) -> Result<Self, #core_crate::abi::InvalidOutputType> where
|
||||||
|
Self: Sized {
|
||||||
|
#from_token_impl
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_token(self) -> #core_crate::abi::Token {
|
||||||
|
#into_token_impl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<#generic_params> #core_crate::abi::TokenizableItem for #name<#generic_args>
|
||||||
|
where
|
||||||
|
#generic_predicates
|
||||||
|
#tokenize_predicates
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,594 @@
|
||||||
|
//! Helper functions for deriving `EthEvent`
|
||||||
|
|
||||||
|
use ethers_contract_abigen::{ethers_contract_crate, ethers_core_crate, Source};
|
||||||
|
use proc_macro2::{Span, TokenStream};
|
||||||
|
use quote::quote;
|
||||||
|
use syn::spanned::Spanned as _;
|
||||||
|
use syn::{parse::Error, AttrStyle, Data, DeriveInput, Field, Fields, Lit, Meta, NestedMeta};
|
||||||
|
|
||||||
|
use ethers_core::abi::{param_type::Reader, AbiParser, Event, EventExt, EventParam, ParamType};
|
||||||
|
use hex::FromHex;
|
||||||
|
|
||||||
|
use crate::abi_ty;
|
||||||
|
use crate::utils;
|
||||||
|
|
||||||
|
/// Generates the `EthEvent` trait support
|
||||||
|
pub(crate) fn derive_eth_event_impl(input: DeriveInput) -> TokenStream {
|
||||||
|
// the ethers crates to use
|
||||||
|
let core_crate = ethers_core_crate();
|
||||||
|
let contract_crate = ethers_contract_crate();
|
||||||
|
|
||||||
|
let name = &input.ident;
|
||||||
|
let attributes = match parse_event_attributes(&input) {
|
||||||
|
Ok(attributes) => attributes,
|
||||||
|
Err(errors) => return errors,
|
||||||
|
};
|
||||||
|
|
||||||
|
let event_name = attributes
|
||||||
|
.name
|
||||||
|
.map(|(s, _)| s)
|
||||||
|
.unwrap_or_else(|| input.ident.to_string());
|
||||||
|
|
||||||
|
let mut event = if let Some((src, span)) = attributes.abi {
|
||||||
|
// try to parse as solidity event
|
||||||
|
if let Ok(event) = parse_event(&src) {
|
||||||
|
event
|
||||||
|
} else {
|
||||||
|
// try as tuple
|
||||||
|
if let Some(inputs) = Reader::read(
|
||||||
|
src.trim_start_matches("event ")
|
||||||
|
.trim_start()
|
||||||
|
.trim_start_matches(&event_name),
|
||||||
|
)
|
||||||
|
.ok()
|
||||||
|
.and_then(|param| match param {
|
||||||
|
ParamType::Tuple(params) => Some(
|
||||||
|
params
|
||||||
|
.into_iter()
|
||||||
|
.map(|kind| EventParam {
|
||||||
|
name: "".to_string(),
|
||||||
|
indexed: false,
|
||||||
|
kind,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
_ => None,
|
||||||
|
}) {
|
||||||
|
Event {
|
||||||
|
name: event_name.clone(),
|
||||||
|
inputs,
|
||||||
|
anonymous: false,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match src.parse::<Source>().and_then(|s| s.get()) {
|
||||||
|
Ok(abi) => {
|
||||||
|
// try to derive the signature from the abi from the parsed abi
|
||||||
|
// TODO(mattsse): this will fail for events that contain other non
|
||||||
|
// elementary types in their abi because the parser
|
||||||
|
// doesn't know how to substitute the types
|
||||||
|
// this could be mitigated by getting the ABI of each non elementary type
|
||||||
|
// at runtime and computing the the signature as
|
||||||
|
// `static Lazy::...`
|
||||||
|
match parse_event(&abi) {
|
||||||
|
Ok(event) => event,
|
||||||
|
Err(err) => return Error::new(span, err).to_compile_error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => return Error::new(span, err).to_compile_error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// try to determine the abi from the fields
|
||||||
|
match derive_abi_event_from_fields(&input) {
|
||||||
|
Ok(event) => event,
|
||||||
|
Err(err) => return err.to_compile_error(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
event.name = event_name.clone();
|
||||||
|
if let Some((anon, _)) = attributes.anonymous.as_ref() {
|
||||||
|
event.anonymous = *anon;
|
||||||
|
}
|
||||||
|
|
||||||
|
let decode_log_impl = match derive_decode_from_log_impl(&input, &event) {
|
||||||
|
Ok(log) => log,
|
||||||
|
Err(err) => return err.to_compile_error(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (abi, hash) = (event.abi_signature(), event.signature());
|
||||||
|
|
||||||
|
let signature = if let Some((hash, _)) = attributes.signature_hash {
|
||||||
|
utils::signature(&hash)
|
||||||
|
} else {
|
||||||
|
utils::signature(hash.as_bytes())
|
||||||
|
};
|
||||||
|
|
||||||
|
let anon = attributes.anonymous.map(|(b, _)| b).unwrap_or_default();
|
||||||
|
|
||||||
|
let ethevent_impl = quote! {
|
||||||
|
impl #contract_crate::EthEvent for #name {
|
||||||
|
|
||||||
|
fn name() -> ::std::borrow::Cow<'static, str> {
|
||||||
|
#event_name.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature() -> #core_crate::types::H256 {
|
||||||
|
#signature
|
||||||
|
}
|
||||||
|
|
||||||
|
fn abi_signature() -> ::std::borrow::Cow<'static, str> {
|
||||||
|
#abi.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_log(log: &#core_crate::abi::RawLog) -> Result<Self, #core_crate::abi::Error> where Self: Sized {
|
||||||
|
#decode_log_impl
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_anonymous() -> bool {
|
||||||
|
#anon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let tokenize_impl = abi_ty::derive_tokenizeable_impl(&input);
|
||||||
|
|
||||||
|
// parse attributes abi into source
|
||||||
|
quote! {
|
||||||
|
#tokenize_impl
|
||||||
|
#ethevent_impl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal helper type for an event/log
|
||||||
|
struct EventField {
|
||||||
|
topic_name: Option<String>,
|
||||||
|
index: usize,
|
||||||
|
param: EventParam,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventField {
|
||||||
|
fn is_indexed(&self) -> bool {
|
||||||
|
self.topic_name.is_some()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn derive_decode_from_log_impl(
|
||||||
|
input: &DeriveInput,
|
||||||
|
event: &Event,
|
||||||
|
) -> Result<proc_macro2::TokenStream, Error> {
|
||||||
|
let core_crate = ethers_core_crate();
|
||||||
|
|
||||||
|
let fields: Vec<_> = match input.data {
|
||||||
|
Data::Struct(ref data) => match data.fields {
|
||||||
|
Fields::Named(ref fields) => {
|
||||||
|
if fields.named.len() != event.inputs.len() {
|
||||||
|
return Err(Error::new(
|
||||||
|
fields.span(),
|
||||||
|
format!(
|
||||||
|
"EthEvent {}'s fields length don't match with signature inputs {}",
|
||||||
|
event.name,
|
||||||
|
event.abi_signature()
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
fields.named.iter().collect()
|
||||||
|
}
|
||||||
|
Fields::Unnamed(ref fields) => {
|
||||||
|
if fields.unnamed.len() != event.inputs.len() {
|
||||||
|
return Err(Error::new(
|
||||||
|
fields.span(),
|
||||||
|
format!(
|
||||||
|
"EthEvent {}'s fields length don't match with signature inputs {}",
|
||||||
|
event.name,
|
||||||
|
event.abi_signature()
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
fields.unnamed.iter().collect()
|
||||||
|
}
|
||||||
|
Fields::Unit => {
|
||||||
|
return Err(Error::new(
|
||||||
|
input.span(),
|
||||||
|
"EthEvent cannot be derived for empty structs and unit",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Data::Enum(_) => {
|
||||||
|
return Err(Error::new(
|
||||||
|
input.span(),
|
||||||
|
"EthEvent cannot be derived for enums",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Data::Union(_) => {
|
||||||
|
return Err(Error::new(
|
||||||
|
input.span(),
|
||||||
|
"EthEvent cannot be derived for unions",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut event_fields = Vec::with_capacity(fields.len());
|
||||||
|
for (index, field) in fields.iter().enumerate() {
|
||||||
|
let mut param = event.inputs[index].clone();
|
||||||
|
|
||||||
|
let (topic_name, indexed) = parse_field_attributes(field)?;
|
||||||
|
if indexed {
|
||||||
|
param.indexed = true;
|
||||||
|
}
|
||||||
|
let topic_name = param
|
||||||
|
.indexed
|
||||||
|
.then(|| topic_name.or_else(|| Some(param.name.clone())))
|
||||||
|
.flatten();
|
||||||
|
|
||||||
|
event_fields.push(EventField {
|
||||||
|
topic_name,
|
||||||
|
index,
|
||||||
|
param,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert fields to params list
|
||||||
|
let topic_types = event_fields
|
||||||
|
.iter()
|
||||||
|
.filter(|f| f.is_indexed())
|
||||||
|
.map(|f| utils::topic_param_type_quote(&f.param.kind));
|
||||||
|
|
||||||
|
let topic_types_init = quote! {let topic_types = ::std::vec![#( #topic_types ),*];};
|
||||||
|
|
||||||
|
let data_types = event_fields
|
||||||
|
.iter()
|
||||||
|
.filter(|f| !f.is_indexed())
|
||||||
|
.map(|f| utils::param_type_quote(&f.param.kind));
|
||||||
|
|
||||||
|
let data_types_init = quote! {let data_types = [#( #data_types ),*];};
|
||||||
|
|
||||||
|
// decode
|
||||||
|
let (signature_check, flat_topics_init, topic_tokens_len_check) = if event.anonymous {
|
||||||
|
(
|
||||||
|
quote! {},
|
||||||
|
quote! {
|
||||||
|
let flat_topics = topics.iter().flat_map(|t| t.as_ref().to_vec()).collect::<Vec<u8>>();
|
||||||
|
},
|
||||||
|
quote! {
|
||||||
|
if topic_tokens.len() != topics.len() {
|
||||||
|
return Err(#core_crate::abi::Error::InvalidData);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
quote! {
|
||||||
|
let event_signature = topics.get(0).ok_or(#core_crate::abi::Error::InvalidData)?;
|
||||||
|
if event_signature != &Self::signature() {
|
||||||
|
return Err(#core_crate::abi::Error::InvalidData);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
quote! {
|
||||||
|
let flat_topics = topics.iter().skip(1).flat_map(|t| t.as_ref().to_vec()).collect::<Vec<u8>>();
|
||||||
|
},
|
||||||
|
quote! {
|
||||||
|
if topic_tokens.len() != topics.len() - 1 {
|
||||||
|
return Err(#core_crate::abi::Error::InvalidData);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
// check if indexed are sorted
|
||||||
|
let tokens_init = if event_fields
|
||||||
|
.iter()
|
||||||
|
.filter(|f| f.is_indexed())
|
||||||
|
.enumerate()
|
||||||
|
.all(|(idx, f)| f.index == idx)
|
||||||
|
{
|
||||||
|
quote! {
|
||||||
|
let topic_tokens = #core_crate::abi::decode(&topic_types, &flat_topics)?;
|
||||||
|
#topic_tokens_len_check
|
||||||
|
let data_tokens = #core_crate::abi::decode(&data_types, data)?;
|
||||||
|
let tokens:Vec<_> = topic_tokens.into_iter().chain(data_tokens.into_iter()).collect();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let swap_tokens = event_fields.iter().map(|field| {
|
||||||
|
if field.is_indexed() {
|
||||||
|
quote! { topic_tokens.remove(0) }
|
||||||
|
} else {
|
||||||
|
quote! { data_tokens.remove(0) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
let mut topic_tokens = #core_crate::abi::decode(&topic_types, &flat_topics)?;
|
||||||
|
#topic_tokens_len_check
|
||||||
|
let mut data_tokens = #core_crate::abi::decode(&data_types, &data)?;
|
||||||
|
let mut tokens = Vec::with_capacity(topics.len() + data_tokens.len());
|
||||||
|
#( tokens.push(#swap_tokens); )*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(quote! {
|
||||||
|
|
||||||
|
let #core_crate::abi::RawLog {data, topics} = log;
|
||||||
|
|
||||||
|
#signature_check
|
||||||
|
|
||||||
|
#topic_types_init
|
||||||
|
#data_types_init
|
||||||
|
|
||||||
|
#flat_topics_init
|
||||||
|
|
||||||
|
#tokens_init
|
||||||
|
|
||||||
|
#core_crate::abi::Tokenizable::from_token(#core_crate::abi::Token::Tuple(tokens)).map_err(|_|#core_crate::abi::Error::InvalidData)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn derive_abi_event_from_fields(input: &DeriveInput) -> Result<Event, 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 => {
|
||||||
|
return Err(Error::new(
|
||||||
|
input.span(),
|
||||||
|
"EthEvent cannot be derived for empty structs and unit",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Data::Enum(_) => {
|
||||||
|
return Err(Error::new(
|
||||||
|
input.span(),
|
||||||
|
"EthEvent cannot be derived for enums",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Data::Union(_) => {
|
||||||
|
return Err(Error::new(
|
||||||
|
input.span(),
|
||||||
|
"EthEvent cannot be derived for unions",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let inputs = fields
|
||||||
|
.iter()
|
||||||
|
.map(|f| {
|
||||||
|
let name = f
|
||||||
|
.ident
|
||||||
|
.as_ref()
|
||||||
|
.map(|name| name.to_string())
|
||||||
|
.unwrap_or_else(|| "".to_string());
|
||||||
|
utils::find_parameter_type(&f.ty).map(|ty| (name, ty))
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
let event = Event {
|
||||||
|
name: "".to_string(),
|
||||||
|
inputs: inputs
|
||||||
|
.into_iter()
|
||||||
|
.map(|(name, kind)| EventParam {
|
||||||
|
name,
|
||||||
|
kind,
|
||||||
|
indexed: false,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
anonymous: false,
|
||||||
|
};
|
||||||
|
Ok(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_field_attributes(field: &Field) -> Result<(Option<String>, bool), Error> {
|
||||||
|
let mut indexed = false;
|
||||||
|
let mut topic_name = None;
|
||||||
|
for a in field.attrs.iter() {
|
||||||
|
if let AttrStyle::Outer = a.style {
|
||||||
|
if let Ok(Meta::List(meta)) = a.parse_meta() {
|
||||||
|
if meta.path.is_ident("ethevent") {
|
||||||
|
for n in meta.nested.iter() {
|
||||||
|
if let NestedMeta::Meta(meta) = n {
|
||||||
|
match meta {
|
||||||
|
Meta::Path(path) => {
|
||||||
|
if path.is_ident("indexed") {
|
||||||
|
indexed = true;
|
||||||
|
} else {
|
||||||
|
return Err(Error::new(
|
||||||
|
path.span(),
|
||||||
|
"unrecognized ethevent parameter",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Meta::List(meta) => {
|
||||||
|
return Err(Error::new(
|
||||||
|
meta.path.span(),
|
||||||
|
"unrecognized ethevent parameter",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Meta::NameValue(meta) => {
|
||||||
|
if meta.path.is_ident("name") {
|
||||||
|
if let Lit::Str(ref lit_str) = meta.lit {
|
||||||
|
topic_name = Some(lit_str.value());
|
||||||
|
} else {
|
||||||
|
return Err(Error::new(
|
||||||
|
meta.span(),
|
||||||
|
"name attribute must be a string",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((topic_name, indexed))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_event(abi: &str) -> Result<Event, String> {
|
||||||
|
let abi = if !abi.trim_start().starts_with("event ") {
|
||||||
|
format!("event {}", abi)
|
||||||
|
} else {
|
||||||
|
abi.to_string()
|
||||||
|
};
|
||||||
|
AbiParser::default()
|
||||||
|
.parse_event(&abi)
|
||||||
|
.map_err(|err| format!("Failed to parse the event ABI: {:?}", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// All the attributes the `EthEvent` macro supports
|
||||||
|
#[derive(Default)]
|
||||||
|
struct EthEventAttributes {
|
||||||
|
name: Option<(String, Span)>,
|
||||||
|
abi: Option<(String, Span)>,
|
||||||
|
signature_hash: Option<(Vec<u8>, Span)>,
|
||||||
|
anonymous: Option<(bool, Span)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// extracts the attributes from the struct annotated with `EthEvent`
|
||||||
|
fn parse_event_attributes(
|
||||||
|
input: &DeriveInput,
|
||||||
|
) -> Result<EthEventAttributes, proc_macro2::TokenStream> {
|
||||||
|
let mut result = EthEventAttributes::default();
|
||||||
|
for a in input.attrs.iter() {
|
||||||
|
if let AttrStyle::Outer = a.style {
|
||||||
|
if let Ok(Meta::List(meta)) = a.parse_meta() {
|
||||||
|
if meta.path.is_ident("ethevent") {
|
||||||
|
for n in meta.nested.iter() {
|
||||||
|
if let NestedMeta::Meta(meta) = n {
|
||||||
|
match meta {
|
||||||
|
Meta::Path(path) => {
|
||||||
|
if let Some(name) = path.get_ident() {
|
||||||
|
if &*name.to_string() == "anonymous" {
|
||||||
|
if result.anonymous.is_none() {
|
||||||
|
result.anonymous = Some((true, name.span()));
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return Err(Error::new(
|
||||||
|
name.span(),
|
||||||
|
"anonymous already specified",
|
||||||
|
)
|
||||||
|
.to_compile_error());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Err(Error::new(
|
||||||
|
path.span(),
|
||||||
|
"unrecognized ethevent parameter",
|
||||||
|
)
|
||||||
|
.to_compile_error());
|
||||||
|
}
|
||||||
|
Meta::List(meta) => {
|
||||||
|
return Err(Error::new(
|
||||||
|
meta.path.span(),
|
||||||
|
"unrecognized ethevent parameter",
|
||||||
|
)
|
||||||
|
.to_compile_error());
|
||||||
|
}
|
||||||
|
Meta::NameValue(meta) => {
|
||||||
|
if meta.path.is_ident("anonymous") {
|
||||||
|
if let Lit::Bool(ref bool_lit) = meta.lit {
|
||||||
|
if result.anonymous.is_none() {
|
||||||
|
result.anonymous =
|
||||||
|
Some((bool_lit.value, bool_lit.span()));
|
||||||
|
} else {
|
||||||
|
return Err(Error::new(
|
||||||
|
meta.span(),
|
||||||
|
"anonymous already specified",
|
||||||
|
)
|
||||||
|
.to_compile_error());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::new(
|
||||||
|
meta.span(),
|
||||||
|
"name must be a string",
|
||||||
|
)
|
||||||
|
.to_compile_error());
|
||||||
|
}
|
||||||
|
} else if meta.path.is_ident("name") {
|
||||||
|
if let Lit::Str(ref lit_str) = meta.lit {
|
||||||
|
if result.name.is_none() {
|
||||||
|
result.name =
|
||||||
|
Some((lit_str.value(), lit_str.span()));
|
||||||
|
} else {
|
||||||
|
return Err(Error::new(
|
||||||
|
meta.span(),
|
||||||
|
"name already specified",
|
||||||
|
)
|
||||||
|
.to_compile_error());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::new(
|
||||||
|
meta.span(),
|
||||||
|
"name must be a string",
|
||||||
|
)
|
||||||
|
.to_compile_error());
|
||||||
|
}
|
||||||
|
} else if meta.path.is_ident("abi") {
|
||||||
|
if let Lit::Str(ref lit_str) = meta.lit {
|
||||||
|
if result.abi.is_none() {
|
||||||
|
result.abi =
|
||||||
|
Some((lit_str.value(), lit_str.span()));
|
||||||
|
} else {
|
||||||
|
return Err(Error::new(
|
||||||
|
meta.span(),
|
||||||
|
"abi already specified",
|
||||||
|
)
|
||||||
|
.to_compile_error());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::new(
|
||||||
|
meta.span(),
|
||||||
|
"abi must be a string",
|
||||||
|
)
|
||||||
|
.to_compile_error());
|
||||||
|
}
|
||||||
|
} else if meta.path.is_ident("signature") {
|
||||||
|
if let Lit::Str(ref lit_str) = meta.lit {
|
||||||
|
if result.signature_hash.is_none() {
|
||||||
|
match Vec::from_hex(lit_str.value()) {
|
||||||
|
Ok(sig) => {
|
||||||
|
result.signature_hash =
|
||||||
|
Some((sig, lit_str.span()))
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
return Err(Error::new(
|
||||||
|
meta.span(),
|
||||||
|
format!(
|
||||||
|
"Expected hex signature: {:?}",
|
||||||
|
err
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.to_compile_error());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::new(
|
||||||
|
meta.span(),
|
||||||
|
"signature already specified",
|
||||||
|
)
|
||||||
|
.to_compile_error());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::new(
|
||||||
|
meta.span(),
|
||||||
|
"signature must be a hex string",
|
||||||
|
)
|
||||||
|
.to_compile_error());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::new(
|
||||||
|
meta.span(),
|
||||||
|
"unrecognized ethevent parameter",
|
||||||
|
)
|
||||||
|
.to_compile_error());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
|
@ -1,27 +1,21 @@
|
||||||
//! 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)]
|
#![deny(missing_docs, unsafe_code, unused)]
|
||||||
|
|
||||||
use ethers_contract_abigen::{ethers_contract_crate, ethers_core_crate, Source};
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::{Literal, Span};
|
use syn::{parse_macro_input, DeriveInput};
|
||||||
use quote::{quote, quote_spanned};
|
|
||||||
use syn::spanned::Spanned as _;
|
|
||||||
use syn::{
|
|
||||||
parse::Error, parse_macro_input, AttrStyle, Data, DeriveInput, Expr, Field, Fields,
|
|
||||||
GenericArgument, Lit, Meta, NestedMeta, PathArguments, Type,
|
|
||||||
};
|
|
||||||
|
|
||||||
use abigen::Contracts;
|
use abigen::Contracts;
|
||||||
use ethers_core::abi::{param_type::Reader, AbiParser, Event, EventExt, EventParam, ParamType};
|
|
||||||
use hex::FromHex;
|
|
||||||
|
|
||||||
|
pub(crate) mod abi_ty;
|
||||||
mod abigen;
|
mod abigen;
|
||||||
|
mod event;
|
||||||
mod spanned;
|
mod spanned;
|
||||||
|
pub(crate) mod utils;
|
||||||
|
|
||||||
/// Proc macro to generate type-safe bindings to a contract(s). This macro accepts
|
/// Proc macro to generate type-safe bindings to a contract(s). This macro
|
||||||
/// one or more Ethereum contract ABI or a path. Note that this path is rooted in
|
/// accepts one or more Ethereum contract ABI or a path. Note that this path is
|
||||||
/// the crate's root `CARGO_MANIFEST_DIR`.
|
/// rooted in the crate's root `CARGO_MANIFEST_DIR`.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
|
@ -68,7 +62,9 @@ mod spanned;
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// `abigen!` supports multiple abigen definitions separated by a semicolon `;`
|
/// `abigen!` supports multiple abigen definitions separated by a semicolon `;`
|
||||||
/// This is useful if the contracts use ABIEncoderV2 structs. In which case `abigen!` bundles all type duplicates so that all rust contracts also use the same rust types.
|
/// This is useful if the contracts use ABIEncoderV2 structs. In which case
|
||||||
|
/// `abigen!` bundles all type duplicates so that all rust contracts also use
|
||||||
|
/// the same rust types.
|
||||||
///
|
///
|
||||||
/// # Example Multiple contracts
|
/// # Example Multiple contracts
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
|
@ -95,25 +91,41 @@ pub fn abigen(input: TokenStream) -> TokenStream {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Derives the `Tokenizable` trait for the labeled type.
|
||||||
|
///
|
||||||
|
/// This derive macro automatically adds a type bound `field: Tokenizable` for
|
||||||
|
/// each field type.
|
||||||
|
#[proc_macro_derive(EthAbiType)]
|
||||||
|
pub fn derive_abi_type(input: TokenStream) -> TokenStream {
|
||||||
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
TokenStream::from(abi_ty::derive_tokenizeable_impl(&input))
|
||||||
|
}
|
||||||
|
|
||||||
/// 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(...)]` attribute:
|
/// Additional arguments can be specified using the `#[ethevent(...)]`
|
||||||
|
/// attribute:
|
||||||
///
|
///
|
||||||
/// For the struct:
|
/// For the struct:
|
||||||
///
|
///
|
||||||
/// - `name`, `name = "..."`: Overrides the generated `EthEvent` name, default is the
|
/// - `name`, `name = "..."`: Overrides the generated `EthEvent` name, default
|
||||||
|
/// is the
|
||||||
/// struct's name.
|
/// struct's name.
|
||||||
/// - `signature`, `signature = "..."`: The signature as hex string to override the
|
/// - `signature`, `signature = "..."`: The signature as hex string to override
|
||||||
|
/// the
|
||||||
/// event's signature.
|
/// event's signature.
|
||||||
/// - `abi`, `abi = "..."`: The ABI signature for the event this event's data corresponds to.
|
/// - `abi`, `abi = "..."`: The ABI signature for the event this event's data
|
||||||
/// The `abi` should be solidity event definition or a tuple of the event's types in case the
|
/// corresponds to.
|
||||||
/// event has non elementary (other `EthAbiType`) types as members
|
/// The `abi` should be solidity event definition or a tuple of the event's
|
||||||
|
/// types in case the event has non elementary (other `EthAbiType`) types as
|
||||||
|
/// members
|
||||||
/// - `anonymous`: A flag to mark this as an anonymous event
|
/// - `anonymous`: A flag to mark this as an anonymous event
|
||||||
///
|
///
|
||||||
/// For fields:
|
/// For fields:
|
||||||
///
|
///
|
||||||
/// - `indexed`: flag to mark a field as an indexed event input
|
/// - `indexed`: flag to mark a field as an indexed event input
|
||||||
/// - `name`: override the name of an indexed event input, default is the rust field name
|
/// - `name`: override the name of an indexed event input, default is the rust
|
||||||
|
/// field name
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
|
@ -137,905 +149,5 @@ pub fn abigen(input: TokenStream) -> TokenStream {
|
||||||
#[proc_macro_derive(EthEvent, attributes(ethevent))]
|
#[proc_macro_derive(EthEvent, attributes(ethevent))]
|
||||||
pub fn derive_abi_event(input: TokenStream) -> TokenStream {
|
pub fn derive_abi_event(input: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
TokenStream::from(event::derive_eth_event_impl(input))
|
||||||
// the ethers crates to use
|
|
||||||
let core_crate = ethers_core_crate();
|
|
||||||
let contract_crate = ethers_contract_crate();
|
|
||||||
|
|
||||||
let name = &input.ident;
|
|
||||||
let attributes = match parse_attributes(&input) {
|
|
||||||
Ok(attributes) => attributes,
|
|
||||||
Err(errors) => return TokenStream::from(errors),
|
|
||||||
};
|
|
||||||
|
|
||||||
let event_name = attributes
|
|
||||||
.name
|
|
||||||
.map(|(s, _)| s)
|
|
||||||
.unwrap_or_else(|| input.ident.to_string());
|
|
||||||
|
|
||||||
let mut event = if let Some((src, span)) = attributes.abi {
|
|
||||||
// try to parse as solidity event
|
|
||||||
if let Ok(event) = parse_event(&src) {
|
|
||||||
event
|
|
||||||
} else {
|
|
||||||
// try as tuple
|
|
||||||
if let Some(inputs) = Reader::read(
|
|
||||||
src.trim_start_matches("event ")
|
|
||||||
.trim_start()
|
|
||||||
.trim_start_matches(&event_name),
|
|
||||||
)
|
|
||||||
.ok()
|
|
||||||
.and_then(|param| match param {
|
|
||||||
ParamType::Tuple(params) => Some(
|
|
||||||
params
|
|
||||||
.into_iter()
|
|
||||||
.map(|kind| EventParam {
|
|
||||||
name: "".to_string(),
|
|
||||||
indexed: false,
|
|
||||||
kind,
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
),
|
|
||||||
_ => None,
|
|
||||||
}) {
|
|
||||||
Event {
|
|
||||||
name: event_name.clone(),
|
|
||||||
inputs,
|
|
||||||
anonymous: false,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
match src.parse::<Source>().and_then(|s| s.get()) {
|
|
||||||
Ok(abi) => {
|
|
||||||
// try to derive the signature from the abi from the parsed abi
|
|
||||||
// TODO(mattsse): this will fail for events that contain other non elementary types in their abi
|
|
||||||
// because the parser doesn't know how to substitute the types
|
|
||||||
// this could be mitigated by getting the ABI of each non elementary type at runtime
|
|
||||||
// and computing the the signature as `static Lazy::...`
|
|
||||||
match parse_event(&abi) {
|
|
||||||
Ok(event) => event,
|
|
||||||
Err(err) => {
|
|
||||||
return TokenStream::from(Error::new(span, err).to_compile_error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => return TokenStream::from(Error::new(span, err).to_compile_error()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// try to determine the abi from the fields
|
|
||||||
match derive_abi_event_from_fields(&input) {
|
|
||||||
Ok(event) => event,
|
|
||||||
Err(err) => return TokenStream::from(err.to_compile_error()),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
event.name = event_name.clone();
|
|
||||||
if let Some((anon, _)) = attributes.anonymous.as_ref() {
|
|
||||||
event.anonymous = *anon;
|
|
||||||
}
|
|
||||||
|
|
||||||
let decode_log_impl = match derive_decode_from_log_impl(&input, &event) {
|
|
||||||
Ok(log) => log,
|
|
||||||
Err(err) => return TokenStream::from(err.to_compile_error()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let (abi, hash) = (event.abi_signature(), event.signature());
|
|
||||||
|
|
||||||
let signature = if let Some((hash, _)) = attributes.signature_hash {
|
|
||||||
signature(&hash)
|
|
||||||
} else {
|
|
||||||
signature(hash.as_bytes())
|
|
||||||
};
|
|
||||||
|
|
||||||
let anon = attributes.anonymous.map(|(b, _)| b).unwrap_or_default();
|
|
||||||
|
|
||||||
let ethevent_impl = quote! {
|
|
||||||
impl #contract_crate::EthEvent for #name {
|
|
||||||
|
|
||||||
fn name() -> ::std::borrow::Cow<'static, str> {
|
|
||||||
#event_name.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature() -> #core_crate::types::H256 {
|
|
||||||
#signature
|
|
||||||
}
|
|
||||||
|
|
||||||
fn abi_signature() -> ::std::borrow::Cow<'static, str> {
|
|
||||||
#abi.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decode_log(log: &#core_crate::abi::RawLog) -> Result<Self, #core_crate::abi::Error> where Self: Sized {
|
|
||||||
#decode_log_impl
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_anonymous() -> bool {
|
|
||||||
#anon
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let tokenize_impl = derive_tokenizeable_impl(&input);
|
|
||||||
|
|
||||||
// parse attributes abi into source
|
|
||||||
TokenStream::from(quote! {
|
|
||||||
#tokenize_impl
|
|
||||||
#ethevent_impl
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
struct EventField {
|
|
||||||
topic_name: Option<String>,
|
|
||||||
index: usize,
|
|
||||||
param: EventParam,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventField {
|
|
||||||
fn is_indexed(&self) -> bool {
|
|
||||||
self.topic_name.is_some()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Converts param types for indexed parameters to bytes32 where appropriate
|
|
||||||
// This applies to strings, arrays, structs and bytes to follow the encoding of
|
|
||||||
// these indexed param types according to
|
|
||||||
// https://solidity.readthedocs.io/en/develop/abi-spec.html#encoding-of-indexed-event-parameters
|
|
||||||
fn topic_param_type_quote(kind: &ParamType) -> proc_macro2::TokenStream {
|
|
||||||
let core_crate = ethers_core_crate();
|
|
||||||
match kind {
|
|
||||||
ParamType::String
|
|
||||||
| ParamType::Bytes
|
|
||||||
| ParamType::Array(_)
|
|
||||||
| ParamType::FixedArray(_, _)
|
|
||||||
| ParamType::Tuple(_) => quote! {#core_crate::abi::ParamType::FixedBytes(32)},
|
|
||||||
ty => param_type_quote(ty),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn param_type_quote(kind: &ParamType) -> proc_macro2::TokenStream {
|
|
||||||
let core_crate = ethers_core_crate();
|
|
||||||
match kind {
|
|
||||||
ParamType::Address => {
|
|
||||||
quote! {#core_crate::abi::ParamType::Address}
|
|
||||||
}
|
|
||||||
ParamType::Bytes => {
|
|
||||||
quote! {#core_crate::abi::ParamType::Bytes}
|
|
||||||
}
|
|
||||||
ParamType::Int(size) => {
|
|
||||||
let size = Literal::usize_suffixed(*size);
|
|
||||||
quote! {#core_crate::abi::ParamType::Int(#size)}
|
|
||||||
}
|
|
||||||
ParamType::Uint(size) => {
|
|
||||||
let size = Literal::usize_suffixed(*size);
|
|
||||||
quote! {#core_crate::abi::ParamType::Uint(#size)}
|
|
||||||
}
|
|
||||||
ParamType::Bool => {
|
|
||||||
quote! {#core_crate::abi::ParamType::Bool}
|
|
||||||
}
|
|
||||||
ParamType::String => {
|
|
||||||
quote! {#core_crate::abi::ParamType::String}
|
|
||||||
}
|
|
||||||
ParamType::Array(ty) => {
|
|
||||||
let ty = param_type_quote(&*ty);
|
|
||||||
quote! {#core_crate::abi::ParamType::Array(Box::new(#ty))}
|
|
||||||
}
|
|
||||||
ParamType::FixedBytes(size) => {
|
|
||||||
let size = Literal::usize_suffixed(*size);
|
|
||||||
quote! {#core_crate::abi::ParamType::FixedBytes(#size)}
|
|
||||||
}
|
|
||||||
ParamType::FixedArray(ty, size) => {
|
|
||||||
let ty = param_type_quote(&*ty);
|
|
||||||
let size = Literal::usize_suffixed(*size);
|
|
||||||
quote! {#core_crate::abi::ParamType::FixedArray(Box::new(#ty),#size)}
|
|
||||||
}
|
|
||||||
ParamType::Tuple(tuple) => {
|
|
||||||
let elements = tuple.iter().map(param_type_quote);
|
|
||||||
quote! {
|
|
||||||
#core_crate::abi::ParamType::Tuple(
|
|
||||||
::std::vec![
|
|
||||||
#( #elements ),*
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn derive_decode_from_log_impl(
|
|
||||||
input: &DeriveInput,
|
|
||||||
event: &Event,
|
|
||||||
) -> Result<proc_macro2::TokenStream, Error> {
|
|
||||||
let core_crate = ethers_core_crate();
|
|
||||||
|
|
||||||
let fields: Vec<_> = match input.data {
|
|
||||||
Data::Struct(ref data) => match data.fields {
|
|
||||||
Fields::Named(ref fields) => {
|
|
||||||
if fields.named.len() != event.inputs.len() {
|
|
||||||
return Err(Error::new(
|
|
||||||
fields.span(),
|
|
||||||
format!(
|
|
||||||
"EthEvent {}'s fields length don't match with signature inputs {}",
|
|
||||||
event.name,
|
|
||||||
event.abi_signature()
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
fields.named.iter().collect()
|
|
||||||
}
|
|
||||||
Fields::Unnamed(ref fields) => {
|
|
||||||
if fields.unnamed.len() != event.inputs.len() {
|
|
||||||
return Err(Error::new(
|
|
||||||
fields.span(),
|
|
||||||
format!(
|
|
||||||
"EthEvent {}'s fields length don't match with signature inputs {}",
|
|
||||||
event.name,
|
|
||||||
event.abi_signature()
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
fields.unnamed.iter().collect()
|
|
||||||
}
|
|
||||||
Fields::Unit => {
|
|
||||||
return Err(Error::new(
|
|
||||||
input.span(),
|
|
||||||
"EthEvent cannot be derived for empty structs and unit",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Data::Enum(_) => {
|
|
||||||
return Err(Error::new(
|
|
||||||
input.span(),
|
|
||||||
"EthEvent cannot be derived for enums",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Data::Union(_) => {
|
|
||||||
return Err(Error::new(
|
|
||||||
input.span(),
|
|
||||||
"EthEvent cannot be derived for unions",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut event_fields = Vec::with_capacity(fields.len());
|
|
||||||
for (index, field) in fields.iter().enumerate() {
|
|
||||||
let mut param = event.inputs[index].clone();
|
|
||||||
|
|
||||||
let (topic_name, indexed) = parse_field_attributes(field)?;
|
|
||||||
if indexed {
|
|
||||||
param.indexed = true;
|
|
||||||
}
|
|
||||||
let topic_name = if param.indexed {
|
|
||||||
if topic_name.is_none() {
|
|
||||||
Some(param.name.clone())
|
|
||||||
} else {
|
|
||||||
topic_name
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
event_fields.push(EventField {
|
|
||||||
topic_name,
|
|
||||||
index,
|
|
||||||
param,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert fields to params list
|
|
||||||
let topic_types = event_fields
|
|
||||||
.iter()
|
|
||||||
.filter(|f| f.is_indexed())
|
|
||||||
.map(|f| topic_param_type_quote(&f.param.kind));
|
|
||||||
|
|
||||||
let topic_types_init = quote! {let topic_types = ::std::vec![#( #topic_types ),*];};
|
|
||||||
|
|
||||||
let data_types = event_fields
|
|
||||||
.iter()
|
|
||||||
.filter(|f| !f.is_indexed())
|
|
||||||
.map(|f| param_type_quote(&f.param.kind));
|
|
||||||
|
|
||||||
let data_types_init = quote! {let data_types = [#( #data_types ),*];};
|
|
||||||
|
|
||||||
// decode
|
|
||||||
let (signature_check, flat_topics_init, topic_tokens_len_check) = if event.anonymous {
|
|
||||||
(
|
|
||||||
quote! {},
|
|
||||||
quote! {
|
|
||||||
let flat_topics = topics.iter().flat_map(|t| t.as_ref().to_vec()).collect::<Vec<u8>>();
|
|
||||||
},
|
|
||||||
quote! {
|
|
||||||
if topic_tokens.len() != topics.len() {
|
|
||||||
return Err(#core_crate::abi::Error::InvalidData);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
quote! {
|
|
||||||
let event_signature = topics.get(0).ok_or(#core_crate::abi::Error::InvalidData)?;
|
|
||||||
if event_signature != &Self::signature() {
|
|
||||||
return Err(#core_crate::abi::Error::InvalidData);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
quote! {
|
|
||||||
let flat_topics = topics.iter().skip(1).flat_map(|t| t.as_ref().to_vec()).collect::<Vec<u8>>();
|
|
||||||
},
|
|
||||||
quote! {
|
|
||||||
if topic_tokens.len() != topics.len() - 1 {
|
|
||||||
return Err(#core_crate::abi::Error::InvalidData);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
// check if indexed are sorted
|
|
||||||
let tokens_init = if event_fields
|
|
||||||
.iter()
|
|
||||||
.filter(|f| f.is_indexed())
|
|
||||||
.enumerate()
|
|
||||||
.all(|(idx, f)| f.index == idx)
|
|
||||||
{
|
|
||||||
quote! {
|
|
||||||
let topic_tokens = #core_crate::abi::decode(&topic_types, &flat_topics)?;
|
|
||||||
#topic_tokens_len_check
|
|
||||||
let data_tokens = #core_crate::abi::decode(&data_types, data)?;
|
|
||||||
let tokens:Vec<_> = topic_tokens.into_iter().chain(data_tokens.into_iter()).collect();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let swap_tokens = event_fields.iter().map(|field| {
|
|
||||||
if field.is_indexed() {
|
|
||||||
quote! { topic_tokens.remove(0) }
|
|
||||||
} else {
|
|
||||||
quote! { data_tokens.remove(0) }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
let mut topic_tokens = #core_crate::abi::decode(&topic_types, &flat_topics)?;
|
|
||||||
#topic_tokens_len_check
|
|
||||||
let mut data_tokens = #core_crate::abi::decode(&data_types, &data)?;
|
|
||||||
let mut tokens = Vec::with_capacity(topics.len() + data_tokens.len());
|
|
||||||
#( tokens.push(#swap_tokens); )*
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(quote! {
|
|
||||||
|
|
||||||
let #core_crate::abi::RawLog {data, topics} = log;
|
|
||||||
|
|
||||||
#signature_check
|
|
||||||
|
|
||||||
#topic_types_init
|
|
||||||
#data_types_init
|
|
||||||
|
|
||||||
#flat_topics_init
|
|
||||||
|
|
||||||
#tokens_init
|
|
||||||
|
|
||||||
#core_crate::abi::Tokenizable::from_token(#core_crate::abi::Token::Tuple(tokens)).map_err(|_|#core_crate::abi::Error::InvalidData)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn derive_abi_event_from_fields(input: &DeriveInput) -> Result<Event, 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 => {
|
|
||||||
return Err(Error::new(
|
|
||||||
input.span(),
|
|
||||||
"EthEvent cannot be derived for empty structs and unit",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Data::Enum(_) => {
|
|
||||||
return Err(Error::new(
|
|
||||||
input.span(),
|
|
||||||
"EthEvent cannot be derived for enums",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Data::Union(_) => {
|
|
||||||
return Err(Error::new(
|
|
||||||
input.span(),
|
|
||||||
"EthEvent cannot be derived for unions",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let inputs = fields
|
|
||||||
.iter()
|
|
||||||
.map(|f| {
|
|
||||||
let name = f
|
|
||||||
.ident
|
|
||||||
.as_ref()
|
|
||||||
.map(|name| name.to_string())
|
|
||||||
.unwrap_or_else(|| "".to_string());
|
|
||||||
find_parameter_type(&f.ty).map(|ty| (name, ty))
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
|
||||||
|
|
||||||
let event = Event {
|
|
||||||
name: "".to_string(),
|
|
||||||
inputs: inputs
|
|
||||||
.into_iter()
|
|
||||||
.map(|(name, kind)| EventParam {
|
|
||||||
name,
|
|
||||||
kind,
|
|
||||||
indexed: false,
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
anonymous: false,
|
|
||||||
};
|
|
||||||
Ok(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_field_attributes(field: &Field) -> Result<(Option<String>, bool), Error> {
|
|
||||||
let mut indexed = false;
|
|
||||||
let mut topic_name = None;
|
|
||||||
for a in field.attrs.iter() {
|
|
||||||
if let AttrStyle::Outer = a.style {
|
|
||||||
if let Ok(Meta::List(meta)) = a.parse_meta() {
|
|
||||||
if meta.path.is_ident("ethevent") {
|
|
||||||
for n in meta.nested.iter() {
|
|
||||||
if let NestedMeta::Meta(meta) = n {
|
|
||||||
match meta {
|
|
||||||
Meta::Path(path) => {
|
|
||||||
if path.is_ident("indexed") {
|
|
||||||
indexed = true;
|
|
||||||
} else {
|
|
||||||
return Err(Error::new(
|
|
||||||
path.span(),
|
|
||||||
"unrecognized ethevent parameter",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Meta::List(meta) => {
|
|
||||||
return Err(Error::new(
|
|
||||||
meta.path.span(),
|
|
||||||
"unrecognized ethevent parameter",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Meta::NameValue(meta) => {
|
|
||||||
if meta.path.is_ident("name") {
|
|
||||||
if let Lit::Str(ref lit_str) = meta.lit {
|
|
||||||
topic_name = Some(lit_str.value());
|
|
||||||
} else {
|
|
||||||
return Err(Error::new(
|
|
||||||
meta.span(),
|
|
||||||
"name attribute must be a string",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((topic_name, indexed))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_parameter_type(ty: &Type) -> Result<ParamType, Error> {
|
|
||||||
match ty {
|
|
||||||
Type::Array(ty) => {
|
|
||||||
let param = find_parameter_type(ty.elem.as_ref())?;
|
|
||||||
if let Expr::Lit(ref expr) = ty.len {
|
|
||||||
if let Lit::Int(ref len) = expr.lit {
|
|
||||||
if let Ok(size) = len.base10_parse::<usize>() {
|
|
||||||
return Ok(ParamType::FixedArray(Box::new(param), size));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(Error::new(
|
|
||||||
ty.span(),
|
|
||||||
"Failed to derive proper ABI from array field",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
Type::Path(ty) => {
|
|
||||||
if let Some(ident) = ty.path.get_ident() {
|
|
||||||
return match ident.to_string().to_lowercase().as_str() {
|
|
||||||
"address" => Ok(ParamType::Address),
|
|
||||||
"string" => Ok(ParamType::String),
|
|
||||||
"bool" => Ok(ParamType::Bool),
|
|
||||||
"int" | "uint" => Ok(ParamType::Uint(256)),
|
|
||||||
"h160" => Ok(ParamType::FixedBytes(20)),
|
|
||||||
"h256" | "secret" | "hash" => Ok(ParamType::FixedBytes(32)),
|
|
||||||
"h512" | "public" => Ok(ParamType::FixedBytes(64)),
|
|
||||||
s => parse_int_param_type(s).ok_or_else(|| {
|
|
||||||
Error::new(ty.span(), "Failed to derive proper ABI from fields")
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// check for `Vec`
|
|
||||||
if ty.path.segments.len() == 1 && ty.path.segments[0].ident == "Vec" {
|
|
||||||
if let PathArguments::AngleBracketed(ref args) = ty.path.segments[0].arguments {
|
|
||||||
if args.args.len() == 1 {
|
|
||||||
if let GenericArgument::Type(ref ty) = args.args.iter().next().unwrap() {
|
|
||||||
let kind = find_parameter_type(ty)?;
|
|
||||||
return Ok(ParamType::Array(Box::new(kind)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(Error::new(
|
|
||||||
ty.span(),
|
|
||||||
"Failed to derive proper ABI from fields",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
Type::Tuple(ty) => {
|
|
||||||
let params = ty
|
|
||||||
.elems
|
|
||||||
.iter()
|
|
||||||
.map(|t| find_parameter_type(t))
|
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
|
||||||
Ok(ParamType::Tuple(params))
|
|
||||||
}
|
|
||||||
_ => Err(Error::new(
|
|
||||||
ty.span(),
|
|
||||||
"Failed to derive proper ABI from fields",
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_int_param_type(s: &str) -> Option<ParamType> {
|
|
||||||
let size = s
|
|
||||||
.chars()
|
|
||||||
.skip(1)
|
|
||||||
.collect::<String>()
|
|
||||||
.parse::<usize>()
|
|
||||||
.ok()?;
|
|
||||||
if s.starts_with('u') {
|
|
||||||
Some(ParamType::Uint(size))
|
|
||||||
} else if s.starts_with('i') {
|
|
||||||
Some(ParamType::Int(size))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(hash: &[u8]) -> proc_macro2::TokenStream {
|
|
||||||
let core_crate = ethers_core_crate();
|
|
||||||
let bytes = hash.iter().copied().map(Literal::u8_unsuffixed);
|
|
||||||
quote! {#core_crate::types::H256([#( #bytes ),*])}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_event(abi: &str) -> Result<Event, String> {
|
|
||||||
let abi = if !abi.trim_start().starts_with("event ") {
|
|
||||||
format!("event {}", abi)
|
|
||||||
} else {
|
|
||||||
abi.to_string()
|
|
||||||
};
|
|
||||||
AbiParser::default()
|
|
||||||
.parse_event(&abi)
|
|
||||||
.map_err(|err| format!("Failed to parse the event ABI: {:?}", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Derives the `Tokenizable` trait for the labeled type.
|
|
||||||
///
|
|
||||||
/// This derive macro automatically adds a type bound `field: Tokenizable` for each
|
|
||||||
/// field type.
|
|
||||||
#[proc_macro_derive(EthAbiType)]
|
|
||||||
pub fn derive_abi_type(input: TokenStream) -> TokenStream {
|
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
|
||||||
TokenStream::from(derive_tokenizeable_impl(&input))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream {
|
|
||||||
let core_crate = ethers_core_crate();
|
|
||||||
let name = &input.ident;
|
|
||||||
let generic_params = input.generics.params.iter().map(|p| quote! { #p });
|
|
||||||
let generic_params = quote! { #(#generic_params,)* };
|
|
||||||
|
|
||||||
let generic_args = input.generics.type_params().map(|p| {
|
|
||||||
let name = &p.ident;
|
|
||||||
quote_spanned! { p.ident.span() => #name }
|
|
||||||
});
|
|
||||||
|
|
||||||
let generic_args = quote! { #(#generic_args,)* };
|
|
||||||
|
|
||||||
let generic_predicates = match input.generics.where_clause {
|
|
||||||
Some(ref clause) => {
|
|
||||||
let predicates = clause.predicates.iter().map(|p| quote! { #p });
|
|
||||||
quote! { #(#predicates,)* }
|
|
||||||
}
|
|
||||||
None => quote! {},
|
|
||||||
};
|
|
||||||
|
|
||||||
let (tokenize_predicates, params_len, init_struct_impl, into_token_impl) = match input.data {
|
|
||||||
Data::Struct(ref data) => match data.fields {
|
|
||||||
Fields::Named(ref fields) => {
|
|
||||||
let tokenize_predicates = fields.named.iter().map(|f| {
|
|
||||||
let ty = &f.ty;
|
|
||||||
quote_spanned! { f.span() => #ty: #core_crate::abi::Tokenize }
|
|
||||||
});
|
|
||||||
let tokenize_predicates = quote! { #(#tokenize_predicates,)* };
|
|
||||||
|
|
||||||
let assignments = fields.named.iter().map(|f| {
|
|
||||||
let name = f.ident.as_ref().expect("Named fields have names");
|
|
||||||
quote_spanned! { f.span() => #name: #core_crate::abi::Tokenizable::from_token(iter.next().expect("tokens size is sufficient qed").into_token())? }
|
|
||||||
});
|
|
||||||
let init_struct_impl = quote! { Self { #(#assignments,)* } };
|
|
||||||
|
|
||||||
let into_token = fields.named.iter().map(|f| {
|
|
||||||
let name = f.ident.as_ref().expect("Named fields have names");
|
|
||||||
quote_spanned! { f.span() => self.#name.into_token() }
|
|
||||||
});
|
|
||||||
let into_token_impl = quote! { #(#into_token,)* };
|
|
||||||
|
|
||||||
(
|
|
||||||
tokenize_predicates,
|
|
||||||
fields.named.len(),
|
|
||||||
init_struct_impl,
|
|
||||||
into_token_impl,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Fields::Unnamed(ref fields) => {
|
|
||||||
let tokenize_predicates = fields.unnamed.iter().map(|f| {
|
|
||||||
let ty = &f.ty;
|
|
||||||
quote_spanned! { f.span() => #ty: #core_crate::abi::Tokenize }
|
|
||||||
});
|
|
||||||
let tokenize_predicates = quote! { #(#tokenize_predicates,)* };
|
|
||||||
|
|
||||||
let assignments = fields.unnamed.iter().map(|f| {
|
|
||||||
quote_spanned! { f.span() => #core_crate::abi::Tokenizable::from_token(iter.next().expect("tokens size is sufficient qed").into_token())? }
|
|
||||||
});
|
|
||||||
let init_struct_impl = quote! { Self(#(#assignments,)* ) };
|
|
||||||
|
|
||||||
let into_token = fields.unnamed.iter().enumerate().map(|(i, f)| {
|
|
||||||
let idx = syn::Index::from(i);
|
|
||||||
quote_spanned! { f.span() => self.#idx.into_token() }
|
|
||||||
});
|
|
||||||
let into_token_impl = quote! { #(#into_token,)* };
|
|
||||||
|
|
||||||
(
|
|
||||||
tokenize_predicates,
|
|
||||||
fields.unnamed.len(),
|
|
||||||
init_struct_impl,
|
|
||||||
into_token_impl,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Fields::Unit => {
|
|
||||||
return Error::new(
|
|
||||||
input.span(),
|
|
||||||
"EthAbiType cannot be derived for empty structs and unit",
|
|
||||||
)
|
|
||||||
.to_compile_error();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Data::Enum(_) => {
|
|
||||||
return Error::new(input.span(), "EthAbiType cannot be derived for enums")
|
|
||||||
.to_compile_error();
|
|
||||||
}
|
|
||||||
Data::Union(_) => {
|
|
||||||
return Error::new(input.span(), "EthAbiType cannot be derived for unions")
|
|
||||||
.to_compile_error();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// there might be the case that the event has only 1 params, which is not a tuple
|
|
||||||
let (from_token_impl, into_token_impl) = match params_len {
|
|
||||||
0 => (
|
|
||||||
quote! {
|
|
||||||
Ok(#init_struct_impl)
|
|
||||||
},
|
|
||||||
// can't encode an empty struct
|
|
||||||
// TODO: panic instead?
|
|
||||||
quote! {
|
|
||||||
#core_crate::abi::Token::Tuple(Vec::new())
|
|
||||||
},
|
|
||||||
),
|
|
||||||
_ => {
|
|
||||||
let from_token = quote! {
|
|
||||||
if let #core_crate::abi::Token::Tuple(tokens) = token {
|
|
||||||
if tokens.len() != #params_len {
|
|
||||||
return Err(#core_crate::abi::InvalidOutputType(::std::format!(
|
|
||||||
"Expected {} tokens, got {}: {:?}",
|
|
||||||
#params_len,
|
|
||||||
tokens.len(),
|
|
||||||
tokens
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut iter = tokens.into_iter();
|
|
||||||
|
|
||||||
Ok(#init_struct_impl)
|
|
||||||
} else {
|
|
||||||
Err(#core_crate::abi::InvalidOutputType(::std::format!(
|
|
||||||
"Expected Tuple, got {:?}",
|
|
||||||
token
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let into_token = quote! {
|
|
||||||
#core_crate::abi::Token::Tuple(
|
|
||||||
::std::vec![
|
|
||||||
#into_token_impl
|
|
||||||
]
|
|
||||||
)
|
|
||||||
};
|
|
||||||
(from_token, into_token)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
impl<#generic_params> #core_crate::abi::Tokenizable for #name<#generic_args>
|
|
||||||
where
|
|
||||||
#generic_predicates
|
|
||||||
#tokenize_predicates
|
|
||||||
{
|
|
||||||
|
|
||||||
fn from_token(token: #core_crate::abi::Token) -> Result<Self, #core_crate::abi::InvalidOutputType> where
|
|
||||||
Self: Sized {
|
|
||||||
#from_token_impl
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_token(self) -> #core_crate::abi::Token {
|
|
||||||
#into_token_impl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<#generic_params> #core_crate::abi::TokenizableItem for #name<#generic_args>
|
|
||||||
where
|
|
||||||
#generic_predicates
|
|
||||||
#tokenize_predicates
|
|
||||||
{ }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct Attributes {
|
|
||||||
name: Option<(String, Span)>,
|
|
||||||
abi: Option<(String, Span)>,
|
|
||||||
signature_hash: Option<(Vec<u8>, Span)>,
|
|
||||||
anonymous: Option<(bool, Span)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_attributes(input: &DeriveInput) -> Result<Attributes, proc_macro2::TokenStream> {
|
|
||||||
let mut result = Attributes::default();
|
|
||||||
for a in input.attrs.iter() {
|
|
||||||
if let AttrStyle::Outer = a.style {
|
|
||||||
if let Ok(Meta::List(meta)) = a.parse_meta() {
|
|
||||||
if meta.path.is_ident("ethevent") {
|
|
||||||
for n in meta.nested.iter() {
|
|
||||||
if let NestedMeta::Meta(meta) = n {
|
|
||||||
match meta {
|
|
||||||
Meta::Path(path) => {
|
|
||||||
if let Some(name) = path.get_ident() {
|
|
||||||
if &*name.to_string() == "anonymous" {
|
|
||||||
if result.anonymous.is_none() {
|
|
||||||
result.anonymous = Some((true, name.span()));
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
return Err(Error::new(
|
|
||||||
name.span(),
|
|
||||||
"anonymous already specified",
|
|
||||||
)
|
|
||||||
.to_compile_error());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Err(Error::new(
|
|
||||||
path.span(),
|
|
||||||
"unrecognized ethevent parameter",
|
|
||||||
)
|
|
||||||
.to_compile_error());
|
|
||||||
}
|
|
||||||
Meta::List(meta) => {
|
|
||||||
return Err(Error::new(
|
|
||||||
meta.path.span(),
|
|
||||||
"unrecognized ethevent parameter",
|
|
||||||
)
|
|
||||||
.to_compile_error());
|
|
||||||
}
|
|
||||||
Meta::NameValue(meta) => {
|
|
||||||
if meta.path.is_ident("anonymous") {
|
|
||||||
if let Lit::Bool(ref bool_lit) = meta.lit {
|
|
||||||
if result.anonymous.is_none() {
|
|
||||||
result.anonymous =
|
|
||||||
Some((bool_lit.value, bool_lit.span()));
|
|
||||||
} else {
|
|
||||||
return Err(Error::new(
|
|
||||||
meta.span(),
|
|
||||||
"anonymous already specified",
|
|
||||||
)
|
|
||||||
.to_compile_error());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(Error::new(
|
|
||||||
meta.span(),
|
|
||||||
"name must be a string",
|
|
||||||
)
|
|
||||||
.to_compile_error());
|
|
||||||
}
|
|
||||||
} else if meta.path.is_ident("name") {
|
|
||||||
if let Lit::Str(ref lit_str) = meta.lit {
|
|
||||||
if result.name.is_none() {
|
|
||||||
result.name =
|
|
||||||
Some((lit_str.value(), lit_str.span()));
|
|
||||||
} else {
|
|
||||||
return Err(Error::new(
|
|
||||||
meta.span(),
|
|
||||||
"name already specified",
|
|
||||||
)
|
|
||||||
.to_compile_error());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(Error::new(
|
|
||||||
meta.span(),
|
|
||||||
"name must be a string",
|
|
||||||
)
|
|
||||||
.to_compile_error());
|
|
||||||
}
|
|
||||||
} else if meta.path.is_ident("abi") {
|
|
||||||
if let Lit::Str(ref lit_str) = meta.lit {
|
|
||||||
if result.abi.is_none() {
|
|
||||||
result.abi =
|
|
||||||
Some((lit_str.value(), lit_str.span()));
|
|
||||||
} else {
|
|
||||||
return Err(Error::new(
|
|
||||||
meta.span(),
|
|
||||||
"abi already specified",
|
|
||||||
)
|
|
||||||
.to_compile_error());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(Error::new(
|
|
||||||
meta.span(),
|
|
||||||
"abi must be a string",
|
|
||||||
)
|
|
||||||
.to_compile_error());
|
|
||||||
}
|
|
||||||
} else if meta.path.is_ident("signature") {
|
|
||||||
if let Lit::Str(ref lit_str) = meta.lit {
|
|
||||||
if result.signature_hash.is_none() {
|
|
||||||
match Vec::from_hex(lit_str.value()) {
|
|
||||||
Ok(sig) => {
|
|
||||||
result.signature_hash =
|
|
||||||
Some((sig, lit_str.span()))
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
return Err(Error::new(
|
|
||||||
meta.span(),
|
|
||||||
format!(
|
|
||||||
"Expected hex signature: {:?}",
|
|
||||||
err
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.to_compile_error());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(Error::new(
|
|
||||||
meta.span(),
|
|
||||||
"signature already specified",
|
|
||||||
)
|
|
||||||
.to_compile_error());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(Error::new(
|
|
||||||
meta.span(),
|
|
||||||
"signature must be a hex string",
|
|
||||||
)
|
|
||||||
.to_compile_error());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(Error::new(
|
|
||||||
meta.span(),
|
|
||||||
"unrecognized ethevent parameter",
|
|
||||||
)
|
|
||||||
.to_compile_error());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(result)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,159 @@
|
||||||
|
use ethers_contract_abigen::ethers_core_crate;
|
||||||
|
use ethers_core::abi::ParamType;
|
||||||
|
use proc_macro2::Literal;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::spanned::Spanned as _;
|
||||||
|
use syn::{parse::Error, Expr, GenericArgument, Lit, PathArguments, Type};
|
||||||
|
|
||||||
|
pub fn signature(hash: &[u8]) -> proc_macro2::TokenStream {
|
||||||
|
let core_crate = ethers_core_crate();
|
||||||
|
let bytes = hash.iter().copied().map(Literal::u8_unsuffixed);
|
||||||
|
quote! {#core_crate::types::H256([#( #bytes ),*])}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses an int type from its string representation
|
||||||
|
pub fn parse_int_param_type(s: &str) -> Option<ParamType> {
|
||||||
|
let size = s
|
||||||
|
.chars()
|
||||||
|
.skip(1)
|
||||||
|
.collect::<String>()
|
||||||
|
.parse::<usize>()
|
||||||
|
.ok()?;
|
||||||
|
if s.starts_with('u') {
|
||||||
|
Some(ParamType::Uint(size))
|
||||||
|
} else if s.starts_with('i') {
|
||||||
|
Some(ParamType::Int(size))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts param types for indexed parameters to bytes32 where appropriate
|
||||||
|
// This applies to strings, arrays, structs and bytes to follow the encoding of
|
||||||
|
// these indexed param types according to
|
||||||
|
// https://solidity.readthedocs.io/en/develop/abi-spec.html#encoding-of-indexed-event-parameters
|
||||||
|
pub fn topic_param_type_quote(kind: &ParamType) -> proc_macro2::TokenStream {
|
||||||
|
let core_crate = ethers_core_crate();
|
||||||
|
match kind {
|
||||||
|
ParamType::String
|
||||||
|
| ParamType::Bytes
|
||||||
|
| ParamType::Array(_)
|
||||||
|
| ParamType::FixedArray(_, _)
|
||||||
|
| ParamType::Tuple(_) => quote! {#core_crate::abi::ParamType::FixedBytes(32)},
|
||||||
|
ty => param_type_quote(ty),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the rust type for the given parameter
|
||||||
|
pub fn param_type_quote(kind: &ParamType) -> proc_macro2::TokenStream {
|
||||||
|
let core_crate = ethers_core_crate();
|
||||||
|
match kind {
|
||||||
|
ParamType::Address => {
|
||||||
|
quote! {#core_crate::abi::ParamType::Address}
|
||||||
|
}
|
||||||
|
ParamType::Bytes => {
|
||||||
|
quote! {#core_crate::abi::ParamType::Bytes}
|
||||||
|
}
|
||||||
|
ParamType::Int(size) => {
|
||||||
|
let size = Literal::usize_suffixed(*size);
|
||||||
|
quote! {#core_crate::abi::ParamType::Int(#size)}
|
||||||
|
}
|
||||||
|
ParamType::Uint(size) => {
|
||||||
|
let size = Literal::usize_suffixed(*size);
|
||||||
|
quote! {#core_crate::abi::ParamType::Uint(#size)}
|
||||||
|
}
|
||||||
|
ParamType::Bool => {
|
||||||
|
quote! {#core_crate::abi::ParamType::Bool}
|
||||||
|
}
|
||||||
|
ParamType::String => {
|
||||||
|
quote! {#core_crate::abi::ParamType::String}
|
||||||
|
}
|
||||||
|
ParamType::Array(ty) => {
|
||||||
|
let ty = param_type_quote(&*ty);
|
||||||
|
quote! {#core_crate::abi::ParamType::Array(Box::new(#ty))}
|
||||||
|
}
|
||||||
|
ParamType::FixedBytes(size) => {
|
||||||
|
let size = Literal::usize_suffixed(*size);
|
||||||
|
quote! {#core_crate::abi::ParamType::FixedBytes(#size)}
|
||||||
|
}
|
||||||
|
ParamType::FixedArray(ty, size) => {
|
||||||
|
let ty = param_type_quote(&*ty);
|
||||||
|
let size = Literal::usize_suffixed(*size);
|
||||||
|
quote! {#core_crate::abi::ParamType::FixedArray(Box::new(#ty),#size)}
|
||||||
|
}
|
||||||
|
ParamType::Tuple(tuple) => {
|
||||||
|
let elements = tuple.iter().map(param_type_quote);
|
||||||
|
quote! {
|
||||||
|
#core_crate::abi::ParamType::Tuple(
|
||||||
|
::std::vec![
|
||||||
|
#( #elements ),*
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to find the corresponding `ParamType` for the given type
|
||||||
|
pub fn find_parameter_type(ty: &Type) -> Result<ParamType, Error> {
|
||||||
|
match ty {
|
||||||
|
Type::Array(ty) => {
|
||||||
|
let param = find_parameter_type(ty.elem.as_ref())?;
|
||||||
|
if let Expr::Lit(ref expr) = ty.len {
|
||||||
|
if let Lit::Int(ref len) = expr.lit {
|
||||||
|
if let Ok(size) = len.base10_parse::<usize>() {
|
||||||
|
return Ok(ParamType::FixedArray(Box::new(param), size));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Error::new(
|
||||||
|
ty.span(),
|
||||||
|
"Failed to derive proper ABI from array field",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Type::Path(ty) => {
|
||||||
|
if let Some(ident) = ty.path.get_ident() {
|
||||||
|
return match ident.to_string().to_lowercase().as_str() {
|
||||||
|
"address" => Ok(ParamType::Address),
|
||||||
|
"string" => Ok(ParamType::String),
|
||||||
|
"bool" => Ok(ParamType::Bool),
|
||||||
|
"int" | "uint" => Ok(ParamType::Uint(256)),
|
||||||
|
"h160" => Ok(ParamType::FixedBytes(20)),
|
||||||
|
"h256" | "secret" | "hash" => Ok(ParamType::FixedBytes(32)),
|
||||||
|
"h512" | "public" => Ok(ParamType::FixedBytes(64)),
|
||||||
|
s => parse_int_param_type(s).ok_or_else(|| {
|
||||||
|
Error::new(ty.span(), "Failed to derive proper ABI from fields")
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// check for `Vec`
|
||||||
|
if ty.path.segments.len() == 1 && ty.path.segments[0].ident == "Vec" {
|
||||||
|
if let PathArguments::AngleBracketed(ref args) = ty.path.segments[0].arguments {
|
||||||
|
if args.args.len() == 1 {
|
||||||
|
if let GenericArgument::Type(ref ty) = args.args.iter().next().unwrap() {
|
||||||
|
let kind = find_parameter_type(ty)?;
|
||||||
|
return Ok(ParamType::Array(Box::new(kind)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::new(
|
||||||
|
ty.span(),
|
||||||
|
"Failed to derive proper ABI from fields",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Type::Tuple(ty) => {
|
||||||
|
let params = ty
|
||||||
|
.elems
|
||||||
|
.iter()
|
||||||
|
.map(find_parameter_type)
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
Ok(ParamType::Tuple(params))
|
||||||
|
}
|
||||||
|
_ => Err(Error::new(
|
||||||
|
ty.span(),
|
||||||
|
"Failed to derive proper ABI from fields",
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue