feat(abigen): include ethevent proc macro in abigen code gen workflow (#232)

* fix: make EthEvent name method a trait method

* refactor: make expand methods members of Context

* fix: make AbiParser parsing non consumeable

* feat: add struct expanding

* feat: use derive(EthEvent) in abigen workflow

* test: check EthEvent in abigen macro

* test: make test compile again

* refactor: simplify and optimize abi parsing from single str

* test: add human readable abigen tests
This commit is contained in:
Matthias Seitz 2021-03-16 20:37:19 +01:00 committed by GitHub
parent 0c18f9b32c
commit 57010c1c60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 412 additions and 249 deletions

View File

@ -2,11 +2,13 @@
mod common; mod common;
mod events; mod events;
mod methods; mod methods;
mod structs;
mod types; mod types;
use super::util; use super::util;
use super::Abigen; use super::Abigen;
use anyhow::{anyhow, Context as _, Result}; use anyhow::{anyhow, Context as _, Result};
use ethers_core::abi::AbiParser;
use ethers_core::{ use ethers_core::{
abi::{parse_abi, Abi}, abi::{parse_abi, Abi},
types::Address, types::Address,
@ -25,6 +27,9 @@ pub(crate) struct Context {
/// The parsed ABI. /// The parsed ABI.
abi: Abi, abi: Abi,
/// The parser used for human readable format
abi_parser: AbiParser,
/// Was the ABI in human readable format? /// Was the ABI in human readable format?
human_readable: bool, human_readable: bool,
@ -59,11 +64,14 @@ impl Context {
let events_decl = cx.events_declaration()?; let events_decl = cx.events_declaration()?;
// 3. impl block for the event functions // 3. impl block for the event functions
let contract_events = cx.events()?; let contract_events = cx.event_methods()?;
// 4. impl block for the contract methods // 4. impl block for the contract methods
let contract_methods = cx.methods()?; let contract_methods = cx.methods()?;
// 5. Declare the structs parsed from the human readable abi
let abi_structs_decl = cx.abi_structs()?;
Ok(quote! { Ok(quote! {
// export all the created data types // export all the created data types
pub use #name_mod::*; pub use #name_mod::*;
@ -91,6 +99,8 @@ impl Context {
} }
#events_decl #events_decl
#abi_structs_decl
} }
}) })
} }
@ -99,22 +109,14 @@ impl Context {
fn from_abigen(args: Abigen) -> Result<Self> { fn from_abigen(args: Abigen) -> Result<Self> {
// get the actual ABI string // get the actual ABI string
let abi_str = args.abi_source.get().context("failed to get ABI JSON")?; let abi_str = args.abi_source.get().context("failed to get ABI JSON")?;
let mut abi_parser = AbiParser::default();
// parse it // parse it
let (abi, human_readable): (Abi, _) = if let Ok(abi) = serde_json::from_str(&abi_str) { let (abi, human_readable): (Abi, _) = if let Ok(abi) = serde_json::from_str(&abi_str) {
// normal abi format // normal abi format
(abi, false) (abi, false)
} else { } else {
// heuristic for parsing the human readable format // heuristic for parsing the human readable format
(abi_parser.parse_str(&abi_str)?, true)
// replace bad chars
let abi_str = abi_str.replace('[', "").replace(']', "");
// split lines and get only the non-empty things
let split: Vec<&str> = abi_str
.split('\n')
.map(|x| x.trim())
.filter(|x| !x.is_empty())
.collect();
(parse_abi(&split)?, true)
}; };
let contract_name = util::ident(&args.contract_name); let contract_name = util::ident(&args.contract_name);
@ -144,6 +146,7 @@ impl Context {
abi, abi,
human_readable, human_readable,
abi_str: Literal::string(&abi_str), abi_str: Literal::string(&abi_str),
abi_parser,
contract_name, contract_name,
method_aliases, method_aliases,
event_derives, event_derives,

View File

@ -15,7 +15,7 @@ pub(crate) fn imports(name: &str) -> TokenStream {
use std::sync::Arc; use std::sync::Arc;
use ethers::{ use ethers::{
core::{ core::{
abi::{Abi, Token, Detokenize, InvalidOutputType, Tokenizable, parse_abi}, abi::{Abi, Token, Detokenize, InvalidOutputType, Tokenizable},
types::*, // import all the types so that we can codegen for everything types::*, // import all the types so that we can codegen for everything
}, },
contract::{Contract, builders::{ContractCall, Event}, Lazy}, contract::{Contract, builders::{ContractCall, Event}, Lazy},
@ -24,6 +24,7 @@ pub(crate) fn imports(name: &str) -> TokenStream {
} }
} }
/// Generates the static `Abi` constants and the contract struct
pub(crate) fn struct_declaration(cx: &Context, abi_name: &proc_macro2::Ident) -> TokenStream { pub(crate) fn struct_declaration(cx: &Context, abi_name: &proc_macro2::Ident) -> TokenStream {
let name = &cx.contract_name; let name = &cx.contract_name;
let abi = &cx.abi_str; let abi = &cx.abi_str;
@ -35,16 +36,8 @@ pub(crate) fn struct_declaration(cx: &Context, abi_name: &proc_macro2::Ident) ->
} }
} else { } else {
quote! { quote! {
pub static #abi_name: Lazy<Abi> = Lazy::new(|| { pub static #abi_name: Lazy<Abi> = Lazy::new(|| ethers::core::abi::parse_abi_str(#abi)
let abi_str = #abi.replace('[', "").replace(']', ""); .expect("invalid abi"));
// split lines and get only the non-empty things
let split: Vec<&str> = abi_str
.split("\n")
.map(|x| x.trim())
.filter(|x| !x.is_empty())
.collect();
parse_abi(&split).expect("invalid abi")
});
} }
}; };

View File

@ -1,6 +1,6 @@
use super::{types, util, Context}; use super::{types, util, Context};
use anyhow::Result; use anyhow::Result;
use ethers_core::abi::{Event, EventExt, EventParam, Hash, ParamType}; use ethers_core::abi::{Event, EventExt, EventParam, Hash, ParamType, SolStruct};
use inflector::Inflector; use inflector::Inflector;
use proc_macro2::{Literal, TokenStream}; use proc_macro2::{Literal, TokenStream};
use quote::quote; use quote::quote;
@ -14,7 +14,7 @@ impl Context {
let data_types = sorted_events let data_types = sorted_events
.values() .values()
.flatten() .flatten()
.map(|event| expand_event(event, &self.event_derives)) .map(|event| self.expand_event(event))
.collect::<Result<Vec<_>>>()?; .collect::<Result<Vec<_>>>()?;
if data_types.is_empty() { if data_types.is_empty() {
@ -26,12 +26,13 @@ impl Context {
}) })
} }
pub fn events(&self) -> Result<TokenStream> { /// Generate the event filter methods for the contract
pub fn event_methods(&self) -> Result<TokenStream> {
let sorted_events: BTreeMap<_, _> = self.abi.events.clone().into_iter().collect(); let sorted_events: BTreeMap<_, _> = self.abi.events.clone().into_iter().collect();
let data_types = sorted_events let data_types = sorted_events
.values() .values()
.flatten() .flatten()
.map(|event| expand_filter(event)) .map(|event| self.expand_filter(event))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if data_types.is_empty() { if data_types.is_empty() {
@ -42,116 +43,70 @@ impl Context {
#( #data_types )* #( #data_types )*
}) })
} }
}
/// Expands into a single method for contracting an event stream. /// Expands an event property type.
fn expand_filter(event: &Event) -> TokenStream {
// append `filter` to disambiguate with potentially conflicting
// function names
let name = util::safe_ident(&format!("{}_filter", event.name.to_snake_case()));
// let result = util::ident(&event.name.to_pascal_case());
let result = expand_struct_name(event);
let ev_name = Literal::string(&event.name);
let doc = util::expand_doc(&format!("Gets the contract's `{}` event", event.name));
quote! {
#doc
pub fn #name(&self) -> Event<M, #result> {
self.0.event(#ev_name).expect("event not found (this should never happen)")
}
}
}
/// Expands an ABI event into a single event data type. This can expand either
/// into a structure or a tuple in the case where all event parameters (topics
/// and data) are anonymous.
fn expand_event(event: &Event, event_derives: &[Path]) -> Result<TokenStream> {
let event_name = expand_struct_name(event);
let signature = expand_hash(event.signature());
let abi_signature = event.abi_signature();
let abi_signature_lit = Literal::string(&abi_signature);
let abi_signature_doc = util::expand_doc(&format!("`{}`", abi_signature));
let params = expand_params(event)?;
// expand as a tuple if all fields are anonymous
let all_anonymous_fields = event.inputs.iter().all(|input| input.name.is_empty());
let (data_type_definition, data_type_construction) = if all_anonymous_fields {
expand_data_tuple(&event_name, &params)
} else {
expand_data_struct(&event_name, &params)
};
// read each token parameter as the required data type
let params_len = Literal::usize_unsuffixed(params.len());
let read_param_token = params
.iter()
.map(|(name, _)| {
quote! {
let #name = Tokenizable::from_token(tokens.next().expect("this should never happen"))?;
}
})
.collect::<Vec<_>>();
let derives = expand_derives(event_derives);
Ok(quote! {
#[derive(Clone, Debug, Default, Eq, PartialEq, #derives)]
pub #data_type_definition
impl #event_name {
/// Retrieves the signature for the event this data corresponds to.
/// This signature is the Keccak-256 hash of the ABI signature of
/// this event.
pub const fn signature() -> H256 {
#signature
}
/// Retrieves the ABI signature for the event this data corresponds
/// to. For this event the value should always be:
/// ///
#abi_signature_doc /// Note that this is slightly different than an expanding a Solidity type as
pub const fn abi_signature() -> &'static str { /// complex types like arrays and strings get emited as hashes when they are
#abi_signature_lit /// indexed.
/// If a complex types matches with a struct previously parsed by the AbiParser,
/// we can replace it
fn expand_input_type(&self, input: &EventParam) -> Result<TokenStream> {
Ok(match (&input.kind, input.indexed) {
(ParamType::Array(ty), true) => {
if let ParamType::Tuple(..) = **ty {
// represents an array of a struct
if let Some(ty) = self
.abi_parser
.structs
.get(&input.name)
.map(SolStruct::name)
.map(util::ident)
{
return Ok(quote! {::std::vec::Vec<#ty>});
} }
} }
quote! { H256 }
impl Detokenize for #event_name {
fn from_tokens(
tokens: Vec<Token>,
) -> Result<Self, InvalidOutputType> {
if tokens.len() != #params_len {
return Err(InvalidOutputType(format!(
"Expected {} tokens, got {}: {:?}",
#params_len,
tokens.len(),
tokens
)));
} }
(ParamType::FixedArray(ty, size), true) => {
#[allow(unused_mut)] if let ParamType::Tuple(..) = **ty {
let mut tokens = tokens.into_iter(); // represents a fixed array of a struct
#( #read_param_token )* if let Some(ty) = self
.abi_parser
Ok(#data_type_construction) .structs
.get(&input.name)
.map(SolStruct::name)
.map(util::ident)
{
let size = Literal::usize_unsuffixed(*size);
return Ok(quote! {[#ty; #size]});
} }
} }
quote! { H256 }
}
(ParamType::Tuple(..), true) => {
// represents an struct
if let Some(ty) = self
.abi_parser
.structs
.get(&input.name)
.map(SolStruct::name)
.map(util::ident)
{
quote! {#ty}
} else {
quote! { H256 }
}
}
(ParamType::Bytes, true) | (ParamType::String, true) => {
quote! { H256 }
}
(kind, _) => types::expand(kind)?,
}) })
} }
/// Expands an ABI event into an identifier for its event data type. /// Expands an ABI event into name-type pairs for each of its parameters.
fn expand_struct_name(event: &Event) -> TokenStream { fn expand_params(&self, event: &Event) -> Result<Vec<(TokenStream, TokenStream)>> {
let name = format!("{}Filter", event.name.to_pascal_case());
let event_name = util::ident(&name);
quote! { #event_name }
}
/// Expands an ABI event into name-type pairs for each of its parameters.
fn expand_params(event: &Event) -> Result<Vec<(TokenStream, TokenStream)>> {
event event
.inputs .inputs
.iter() .iter()
@ -159,78 +114,64 @@ fn expand_params(event: &Event) -> Result<Vec<(TokenStream, TokenStream)>> {
.map(|(i, input)| { .map(|(i, input)| {
// NOTE: Events can contain nameless values. // NOTE: Events can contain nameless values.
let name = util::expand_input_name(i, &input.name); let name = util::expand_input_name(i, &input.name);
let ty = expand_input_type(&input)?; let ty = self.expand_input_type(&input)?;
Ok((name, ty)) Ok((name, ty))
}) })
.collect() .collect()
} }
/// Expands an event data structure from its name-type parameter pairs. Returns /// Expands into a single method for contracting an event stream.
/// a tuple with the type definition (i.e. the struct declaration) and fn expand_filter(&self, event: &Event) -> TokenStream {
/// construction (i.e. code for creating an instance of the event data). // append `filter` to disambiguate with potentially conflicting
fn expand_data_struct( // function names
name: &TokenStream, let name = util::safe_ident(&format!("{}_filter", event.name.to_snake_case()));
params: &[(TokenStream, TokenStream)], // let result = util::ident(&event.name.to_pascal_case());
) -> (TokenStream, TokenStream) { let result = expand_struct_name(event);
let fields = params let ev_name = Literal::string(&event.name);
.iter()
.map(|(name, ty)| quote! { pub #name: #ty })
.collect::<Vec<_>>();
let param_names = params let doc = util::expand_doc(&format!("Gets the contract's `{}` event", event.name));
.iter() quote! {
.map(|(name, _)| name) #doc
.cloned() pub fn #name(&self) -> Event<M, #result> {
.collect::<Vec<_>>(); self.0.event(#ev_name).expect("event not found (this should never happen)")
}
}
}
let definition = quote! { struct #name { #( #fields, )* } }; /// Expands an ABI event into a single event data type. This can expand either
let construction = quote! { #name { #( #param_names ),* } }; /// into a structure or a tuple in the case where all event parameters (topics
/// and data) are anonymous.
fn expand_event(&self, event: &Event) -> Result<TokenStream> {
let event_name = expand_struct_name(event);
(definition, construction) let params = self.expand_params(event)?;
} // expand as a tuple if all fields are anonymous
let all_anonymous_fields = event.inputs.iter().all(|input| input.name.is_empty());
let data_type_definition = if all_anonymous_fields {
expand_data_tuple(&event_name, &params)
} else {
expand_data_struct(&event_name, &params)
};
/// Expands an event data named tuple from its name-type parameter pairs. let derives = expand_derives(&self.event_derives);
/// Returns a tuple with the type definition and construction. let abi_signature = event.abi_signature();
fn expand_data_tuple( let event_abi_name = &event.name;
name: &TokenStream,
params: &[(TokenStream, TokenStream)],
) -> (TokenStream, TokenStream) {
let fields = params
.iter()
.map(|(_, ty)| quote! { pub #ty })
.collect::<Vec<_>>();
let param_names = params
.iter()
.map(|(name, _)| name)
.cloned()
.collect::<Vec<_>>();
let definition = quote! { struct #name( #( #fields ),* ); };
let construction = quote! { #name( #( #param_names ),* ) };
(definition, construction)
}
/// Expands an ABI event into filter methods for its indexed parameters.
fn expand_builder_topic_filters(event: &Event) -> Result<TokenStream> {
let topic_filters = event
.inputs
.iter()
.filter(|input| input.indexed)
.enumerate()
.map(|(topic_index, input)| expand_builder_topic_filter(topic_index, input))
.collect::<Result<Vec<_>>>()?;
Ok(quote! { Ok(quote! {
#( #topic_filters )* #[derive(Clone, Debug, Default, Eq, PartialEq, ethers::contract::EthEvent, #derives)]
#[ethevent( name = #event_abi_name, abi = #abi_signature )]
pub #data_type_definition
}) })
} }
/// Expands a event parameter into an event builder filter method for the /// Expands a event parameter into an event builder filter method for the
/// specified topic index. /// specified topic index.
fn expand_builder_topic_filter(topic_index: usize, param: &EventParam) -> Result<TokenStream> { fn expand_builder_topic_filter(
&self,
topic_index: usize,
param: &EventParam,
) -> Result<TokenStream> {
let doc = util::expand_doc(&format!( let doc = util::expand_doc(&format!(
"Adds a filter for the `{}` event parameter.", "Adds a filter for the `{}` event parameter.",
param.name, param.name,
@ -241,7 +182,7 @@ fn expand_builder_topic_filter(topic_index: usize, param: &EventParam) -> Result
} else { } else {
util::safe_ident(&param.name.to_snake_case()) util::safe_ident(&param.name.to_snake_case())
}; };
let ty = expand_input_type(&param)?; let ty = self.expand_input_type(&param)?;
Ok(quote! { Ok(quote! {
#doc #doc
@ -250,6 +191,53 @@ fn expand_builder_topic_filter(topic_index: usize, param: &EventParam) -> Result
self self
} }
}) })
}
/// Expands an ABI event into filter methods for its indexed parameters.
fn expand_builder_topic_filters(&self, event: &Event) -> Result<TokenStream> {
let topic_filters = event
.inputs
.iter()
.filter(|input| input.indexed)
.enumerate()
.map(|(topic_index, input)| self.expand_builder_topic_filter(topic_index, input))
.collect::<Result<Vec<_>>>()?;
Ok(quote! {
#( #topic_filters )*
})
}
}
/// Expands an ABI event into an identifier for its event data type.
fn expand_struct_name(event: &Event) -> TokenStream {
// TODO: get rid of `Filter` suffix?
let name = format!("{}Filter", event.name.to_pascal_case());
let event_name = util::ident(&name);
quote! { #event_name }
}
/// Expands an event data structure from its name-type parameter pairs. Returns
/// a tuple with the type definition (i.e. the struct declaration) and
/// construction (i.e. code for creating an instance of the event data).
fn expand_data_struct(name: &TokenStream, params: &[(TokenStream, TokenStream)]) -> TokenStream {
let fields = params
.iter()
.map(|(name, ty)| quote! { pub #name: #ty })
.collect::<Vec<_>>();
quote! { struct #name { #( #fields, )* } }
}
/// Expands an event data named tuple from its name-type parameter pairs.
/// Returns a tuple with the type definition and construction.
fn expand_data_tuple(name: &TokenStream, params: &[(TokenStream, TokenStream)]) -> TokenStream {
let fields = params
.iter()
.map(|(_, ty)| quote! { pub #ty })
.collect::<Vec<_>>();
quote! { struct #name( #( #fields ),* ); }
} }
/// Expands an ABI event into an identifier for its event data type. /// Expands an ABI event into an identifier for its event data type.
@ -262,24 +250,6 @@ fn expand_derives(derives: &[Path]) -> TokenStream {
quote! {#(#derives),*} quote! {#(#derives),*}
} }
/// Expands an event property type.
///
/// Note that this is slightly different than an expanding a Solidity type as
/// complex types like arrays and strings get emited as hashes when they are
/// indexed.
fn expand_input_type(input: &EventParam) -> Result<TokenStream> {
Ok(match (&input.kind, input.indexed) {
(ParamType::Array(..), true)
| (ParamType::Bytes, true)
| (ParamType::FixedArray(..), true)
| (ParamType::String, true)
| (ParamType::Tuple(..), true) => {
quote! { H256 }
}
(kind, _) => types::expand(kind)?,
})
}
/// Expands a 256-bit `Hash` into a literal representation that can be used with /// Expands a 256-bit `Hash` into a literal representation that can be used with
/// quasi-quoting for code generation. We do this to avoid allocating at runtime /// quasi-quoting for code generation. We do this to avoid allocating at runtime
fn expand_hash(hash: Hash) -> TokenStream { fn expand_hash(hash: Hash) -> TokenStream {
@ -293,8 +263,13 @@ fn expand_hash(hash: Hash) -> TokenStream {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::Abigen;
use ethers_core::abi::{EventParam, ParamType}; use ethers_core::abi::{EventParam, ParamType};
fn test_context() -> Context {
Context::from_abigen(Abigen::new("TestToken", "[]").unwrap()).unwrap()
}
#[test] #[test]
fn expand_transfer_filter() { fn expand_transfer_filter() {
let event = Event { let event = Event {
@ -318,8 +293,8 @@ mod tests {
], ],
anonymous: false, anonymous: false,
}; };
let cx = test_context();
assert_quote!(expand_filter(&event), { assert_quote!(cx.expand_filter(&event), {
#[doc = "Gets the contract's `Transfer` event"] #[doc = "Gets the contract's `Transfer` event"]
pub fn transfer_filter(&self) -> Event<M, TransferFilter> { pub fn transfer_filter(&self) -> Event<M, TransferFilter> {
self.0 self.0
@ -348,9 +323,10 @@ mod tests {
anonymous: false, anonymous: false,
}; };
let cx = test_context();
let params = cx.expand_params(&event).unwrap();
let name = expand_struct_name(&event); let name = expand_struct_name(&event);
let params = expand_params(&event).unwrap(); let definition = expand_data_struct(&name, &params);
let (definition, construction) = expand_data_struct(&name, &params);
assert_quote!(definition, { assert_quote!(definition, {
struct FooFilter { struct FooFilter {
@ -358,7 +334,6 @@ mod tests {
pub p1: Address, pub p1: Address,
} }
}); });
assert_quote!(construction, { FooFilter { a, p1 } });
} }
#[test] #[test]
@ -380,14 +355,14 @@ mod tests {
anonymous: false, anonymous: false,
}; };
let cx = test_context();
let params = cx.expand_params(&event).unwrap();
let name = expand_struct_name(&event); let name = expand_struct_name(&event);
let params = expand_params(&event).unwrap(); let definition = expand_data_tuple(&name, &params);
let (definition, construction) = expand_data_tuple(&name, &params);
assert_quote!(definition, { assert_quote!(definition, {
struct FooFilter(pub bool, pub Address); struct FooFilter(pub bool, pub Address);
}); });
assert_quote!(construction, { FooFilter(p0, p1) });
} }
#[test] #[test]

View File

@ -0,0 +1,109 @@
//! Methods for expanding structs
use anyhow::{Context as _, Result};
use inflector::Inflector;
use proc_macro2::{Literal, TokenStream};
use quote::quote;
use ethers_core::abi::{struct_def::FieldType, ParamType};
use crate::contract::{types, Context};
use crate::util;
impl Context {
/// Generate corresponding types for structs parsed from a human readable ABI
///
/// NOTE: This assumes that all structs that are potentially used as type for variable are
/// in fact present in the `AbiParser`, this is sound because `AbiParser::parse` would have
/// failed already
pub fn abi_structs(&self) -> Result<TokenStream> {
let mut structs = Vec::with_capacity(self.abi_parser.structs.len());
for (name, sol_struct) in &self.abi_parser.structs {
let mut fields = Vec::with_capacity(sol_struct.fields().len());
let mut param_types = Vec::with_capacity(sol_struct.fields().len());
for field in sol_struct.fields() {
let field_name = util::ident(&field.name().to_snake_case());
match field.r#type() {
FieldType::Elementary(ty) => {
param_types.push(ty.clone());
let ty = types::expand(ty)?;
fields.push(quote! { pub #field_name: #ty });
}
FieldType::Struct(struct_ty) => {
let ty = util::ident(struct_ty.name());
fields.push(quote! { pub #field_name: #ty });
let tuple = self
.abi_parser
.struct_tuples
.get(struct_ty.name())
.context(format!("No types found for {}", struct_ty.name()))?
.clone();
param_types.push(ParamType::Tuple(tuple));
}
FieldType::StructArray(struct_ty) => {
let ty = util::ident(struct_ty.name());
fields.push(quote! { pub #field_name: ::std::vec::Vec<#ty> });
let tuple = self
.abi_parser
.struct_tuples
.get(struct_ty.name())
.context(format!("No types found for {}", struct_ty.name()))?
.clone();
param_types.push(ParamType::Array(Box::new(ParamType::Tuple(tuple))));
}
FieldType::FixedStructArray(struct_ty, len) => {
let ty = util::ident(struct_ty.name());
let size = Literal::usize_unsuffixed(*len);
fields.push(quote! { pub #field_name: [#ty; #size] });
let tuple = self
.abi_parser
.struct_tuples
.get(struct_ty.name())
.context(format!("No types found for {}", struct_ty.name()))?
.clone();
param_types.push(ParamType::FixedArray(
Box::new(ParamType::Tuple(tuple)),
*len,
));
}
FieldType::Mapping(_) => {
return Err(anyhow::anyhow!(
"Mapping types in struct `{}` are not supported {:?}",
name,
field
));
}
}
}
let abi_signature = format!(
"{}({})",
name,
param_types
.iter()
.map(|kind| kind.to_string())
.collect::<Vec<_>>()
.join(","),
);
let abi_signature_doc = util::expand_doc(&format!("`{}`", abi_signature));
let name = util::ident(name);
// use the same derives as for events
let derives = &self.event_derives;
let derives = quote! {#(#derives),*};
structs.push(quote! {
#abi_signature_doc
#[derive(Clone, Debug, Default, Eq, PartialEq, ethers::contract::EthAbiType, #derives)]
pub struct #name {
#( #fields ),*
}
});
}
Ok(quote! {#( #structs )*})
}
}

View File

@ -190,7 +190,7 @@ pub fn derive_abi_event(input: TokenStream) -> TokenStream {
let ethevent_impl = quote! { let ethevent_impl = quote! {
impl ethers_contract::EthEvent for #name { impl ethers_contract::EthEvent for #name {
fn name(&self) -> ::std::borrow::Cow<'static, str> { fn name() -> ::std::borrow::Cow<'static, str> {
#event_name.into() #event_name.into()
} }

View File

@ -11,7 +11,7 @@ use std::marker::PhantomData;
/// A trait for implementing event bindings /// A trait for implementing event bindings
pub trait EthEvent: Detokenize { pub trait EthEvent: Detokenize {
/// The name of the event this type represents /// The name of the event this type represents
fn name(&self) -> Cow<'static, str>; fn name() -> Cow<'static, str>;
/// Retrieves the signature for the event this data corresponds to. /// Retrieves the signature for the event this data corresponds to.
/// This signature is the Keccak-256 hash of the ABI signature of /// This signature is the Keccak-256 hash of the ABI signature of

View File

@ -0,0 +1,66 @@
//! Test cases to validate the `abigen!` macro
use ethers_contract::{abigen, EthEvent};
use ethers_core::abi::Tokenizable;
#[test]
fn can_gen_human_readable() {
abigen!(
SimpleContract,
r#"[
event ValueChanged(address indexed author, string oldValue, string newValue)
]"#,
event_derives(serde::Deserialize, serde::Serialize)
);
assert_eq!("ValueChanged", ValueChangedFilter::name());
assert_eq!(
"ValueChanged(address,string,string)",
ValueChangedFilter::abi_signature()
);
}
#[test]
fn can_gen_structs_readable() {
abigen!(
SimpleContract,
r#"[
struct Value {address addr; string value;}
struct Addresses {address[] addr; string s;}
event ValueChanged(Value indexed old, Value newValue, Addresses _a)
]"#,
event_derives(serde::Deserialize, serde::Serialize)
);
let value = Addresses {
addr: vec!["eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse().unwrap()],
s: "hello".to_string(),
};
let token = value.clone().into_token();
assert_eq!(value, Addresses::from_token(token).unwrap());
assert_eq!("ValueChanged", ValueChangedFilter::name());
assert_eq!(
"ValueChanged((address,string),(address,string),(address[],string))",
ValueChangedFilter::abi_signature()
);
}
// NOTE(mattsse): There is currently a limitation with the `ethabi` crate's `Reader`
// that doesn't support arrays of tuples; https://github.com/gakonst/ethabi/pull/1 should fix this
// See also https://github.com/rust-ethereum/ethabi/issues/178 and
// https://github.com/rust-ethereum/ethabi/pull/186
// #[test]
// fn can_gen_structs_with_arrays_readable() {
// abigen!(
// SimpleContract,
// r#"[
// struct Value {address addr; string value;}
// struct Addresses {address[] addr; string s;}
// event ValueChanged(Value indexed old, Value newValue, Addresses[] _a)
// ]"#,
// event_derives(serde::Deserialize, serde::Serialize)
// );
// assert_eq!(
// "ValueChanged((address,string),(address,string),(address[],string)[])",
// ValueChangedFilter::abi_signature()
// );
// }

View File

@ -98,7 +98,7 @@ fn can_derive_eth_event() {
new_value: "100".to_string(), new_value: "100".to_string(),
}; };
assert_eq!("ValueChangedEvent", value.name()); assert_eq!("ValueChangedEvent", ValueChangedEvent::name());
assert_eq!( assert_eq!(
"ValueChangedEvent(address,address,string,string)", "ValueChangedEvent(address,address,string,string)",
ValueChangedEvent::abi_signature() ValueChangedEvent::abi_signature()
@ -119,14 +119,7 @@ fn can_set_eth_event_name_attribute() {
new_value: String, new_value: String,
} }
let value = ValueChangedEvent { assert_eq!("MyEvent", ValueChangedEvent::name());
old_author: "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse().unwrap(),
new_author: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".parse().unwrap(),
old_value: "50".to_string(),
new_value: "100".to_string(),
};
assert_eq!("MyEvent", value.name());
assert_eq!( assert_eq!(
"MyEvent(address,address,string,string)", "MyEvent(address,address,string,string)",
ValueChangedEvent::abi_signature() ValueChangedEvent::abi_signature()

View File

@ -9,17 +9,33 @@ use crate::abi::{
/// A parser that turns a "human readable abi" into a `Abi` /// A parser that turns a "human readable abi" into a `Abi`
pub struct AbiParser { pub struct AbiParser {
abi: Abi,
/// solidity structs /// solidity structs
structs: HashMap<String, SolStruct>, pub structs: HashMap<String, SolStruct>,
/// solidity structs as tuples /// solidity structs as tuples
struct_tuples: HashMap<String, Vec<ParamType>>, pub struct_tuples: HashMap<String, Vec<ParamType>>,
} }
impl AbiParser { impl AbiParser {
/// Parses a "human readable abi" string /// Parses a "human readable abi" string
pub fn parse_str(self, s: &str) -> Result<Abi> { ///
self.parse(&s.lines().collect::<Vec<_>>()) /// # Example
///
/// ```
/// # use ethers::abi::AbiParser;
/// let abi = AbiParser::default().parse_str(r#"[
/// function setValue(string)
/// function getValue() external view returns (string)
/// event ValueChanged(address indexed author, string oldValue, string newValue)
/// ]"#).unwrap();
/// ```
pub fn parse_str(&mut self, s: &str) -> Result<Abi> {
self.parse(
&s.trim()
.trim_start_matches('[')
.trim_end_matches(']')
.lines()
.collect::<Vec<_>>(),
)
} }
/// Parses a "human readable abi" string vector /// Parses a "human readable abi" string vector
@ -32,11 +48,21 @@ impl AbiParser {
/// "function x() external view returns (uint256)", /// "function x() external view returns (uint256)",
/// ]).unwrap(); /// ]).unwrap();
/// ``` /// ```
pub fn parse(mut self, input: &[&str]) -> Result<Abi> { pub fn parse(&mut self, input: &[&str]) -> Result<Abi> {
// parse struct first // parse struct first
let mut abi = Abi {
constructor: None,
functions: HashMap::new(),
events: HashMap::new(),
receive: false,
fallback: false,
};
let (structs, types): (Vec<_>, Vec<_>) = input let (structs, types): (Vec<_>, Vec<_>) = input
.iter() .iter()
.map(|s| escape_quotes(s)) .map(|s| escape_quotes(s))
.map(str::trim)
.filter(|s| !s.is_empty())
.partition(|s| s.starts_with("struct")); .partition(|s| s.starts_with("struct"));
for sol in structs { for sol in structs {
@ -52,25 +78,23 @@ impl AbiParser {
line = line.trim_start(); line = line.trim_start();
if line.starts_with("function") { if line.starts_with("function") {
let function = self.parse_function(&line)?; let function = self.parse_function(&line)?;
self.abi abi.functions
.functions
.entry(function.name.clone()) .entry(function.name.clone())
.or_default() .or_default()
.push(function); .push(function);
} else if line.starts_with("event") { } else if line.starts_with("event") {
let event = self.parse_event(line)?; let event = self.parse_event(line)?;
self.abi abi.events
.events
.entry(event.name.clone()) .entry(event.name.clone())
.or_default() .or_default()
.push(event); .push(event);
} else if line.starts_with("constructor") { } else if line.starts_with("constructor") {
self.abi.constructor = Some(self.parse_constructor(line)?); abi.constructor = Some(self.parse_constructor(line)?);
} else { } else {
bail!("Illegal abi `{}`", line) bail!("Illegal abi `{}`", line)
} }
} }
Ok(self.abi) Ok(abi)
} }
/// Substitutes any other struct references within structs with tuples /// Substitutes any other struct references within structs with tuples
@ -136,13 +160,6 @@ impl AbiParser {
/// Link additional structs for parsing /// Link additional structs for parsing
pub fn with_structs(structs: Vec<SolStruct>) -> Self { pub fn with_structs(structs: Vec<SolStruct>) -> Self {
Self { Self {
abi: Abi {
constructor: None,
functions: HashMap::new(),
events: HashMap::new(),
receive: false,
fallback: false,
},
structs: structs structs: structs
.into_iter() .into_iter()
.map(|s| (s.name().to_string(), s)) .map(|s| (s.name().to_string(), s))
@ -392,6 +409,13 @@ pub fn parse(input: &[&str]) -> Result<Abi> {
AbiParser::default().parse(input) AbiParser::default().parse(input)
} }
/// Parses a "human readable abi" string
///
/// See also `AbiParser::parse_str`
pub fn parse_str(input: &str) -> Result<Abi> {
AbiParser::default().parse_str(input)
}
/// Parses an identifier like event or function name /// Parses an identifier like event or function name
pub(crate) fn parse_identifier(input: &mut &str) -> Result<String> { pub(crate) fn parse_identifier(input: &mut &str) -> Result<String> {
let mut chars = input.trim_start().chars(); let mut chars = input.trim_start().chars();

View File

@ -15,7 +15,7 @@ mod error;
pub use error::ParseError; pub use error::ParseError;
mod human_readable; mod human_readable;
pub use human_readable::{parse as parse_abi, AbiParser}; pub use human_readable::{parse as parse_abi, parse_str as parse_abi_str, AbiParser};
/// Extension trait for `ethabi::Function`. /// Extension trait for `ethabi::Function`.
pub trait FunctionExt { pub trait FunctionExt {