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
|
||||
//! 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_macro2::{Literal, Span};
|
||||
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 syn::{parse_macro_input, DeriveInput};
|
||||
|
||||
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 event;
|
||||
mod spanned;
|
||||
pub(crate) mod utils;
|
||||
|
||||
/// Proc macro to generate type-safe bindings to a contract(s). This macro accepts
|
||||
/// one or more Ethereum contract ABI or a path. Note that this path is rooted in
|
||||
/// the crate's root `CARGO_MANIFEST_DIR`.
|
||||
/// Proc macro to generate type-safe bindings to a contract(s). This macro
|
||||
/// accepts one or more Ethereum contract ABI or a path. Note that this path is
|
||||
/// rooted in the crate's root `CARGO_MANIFEST_DIR`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -68,7 +62,9 @@ mod spanned;
|
|||
/// ```
|
||||
///
|
||||
/// `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
|
||||
/// ```ignore
|
||||
|
@ -95,25 +91,41 @@ pub fn abigen(input: TokenStream) -> TokenStream {
|
|||
.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.
|
||||
///
|
||||
/// Additional arguments can be specified using the `#[ethevent(...)]` attribute:
|
||||
/// Additional arguments can be specified using the `#[ethevent(...)]`
|
||||
/// attribute:
|
||||
///
|
||||
/// 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.
|
||||
/// - `signature`, `signature = "..."`: The signature as hex string to override the
|
||||
/// - `signature`, `signature = "..."`: The signature as hex string to override
|
||||
/// the
|
||||
/// event's signature.
|
||||
/// - `abi`, `abi = "..."`: The ABI signature for the event this event's data corresponds to.
|
||||
/// 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
|
||||
/// - `abi`, `abi = "..."`: The ABI signature for the event this event's data
|
||||
/// corresponds to.
|
||||
/// 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
|
||||
///
|
||||
/// For fields:
|
||||
///
|
||||
/// - `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
|
||||
/// ```ignore
|
||||
|
@ -137,905 +149,5 @@ pub fn abigen(input: TokenStream) -> TokenStream {
|
|||
#[proc_macro_derive(EthEvent, attributes(ethevent))]
|
||||
pub fn derive_abi_event(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
|
||||
// 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)
|
||||
TokenStream::from(event::derive_eth_event_impl(input))
|
||||
}
|
||||
|
|
|
@ -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