505 lines
21 KiB
Rust
505 lines
21 KiB
Rust
//! Helper functions for deriving `EthEvent`
|
|
|
|
use ethers_contract_abigen::Source;
|
|
use proc_macro2::{Span, TokenStream};
|
|
use quote::quote;
|
|
use syn::{
|
|
parse::Error, spanned::Spanned as _, AttrStyle, Data, DeriveInput, Field, Fields, Lit, Meta,
|
|
NestedMeta,
|
|
};
|
|
|
|
use ethers_core::{
|
|
abi::{Event, EventExt, EventParam, HumanReadableParser},
|
|
macros::{ethers_contract_crate, ethers_core_crate},
|
|
};
|
|
use hex::FromHex;
|
|
|
|
use crate::{abi_ty, 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) = HumanReadableParser::parse_event(&src) {
|
|
event
|
|
} 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 HumanReadableParser::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) -> ::std::result::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);
|
|
|
|
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)
|
|
})
|
|
}
|
|
|
|
/// Determine the event's ABI by parsing the AST
|
|
fn derive_abi_event_from_fields(input: &DeriveInput) -> Result<Event, Error> {
|
|
let event = Event {
|
|
name: "".to_string(),
|
|
inputs: utils::derive_abi_inputs_from_fields(input, "EthEvent")?
|
|
.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))
|
|
}
|
|
|
|
/// 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)
|
|
}
|