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 events;
mod methods;
mod structs;
mod types;
use super::util;
use super::Abigen;
use anyhow::{anyhow, Context as _, Result};
use ethers_core::abi::AbiParser;
use ethers_core::{
abi::{parse_abi, Abi},
types::Address,
@ -25,6 +27,9 @@ pub(crate) struct Context {
/// The parsed ABI.
abi: Abi,
/// The parser used for human readable format
abi_parser: AbiParser,
/// Was the ABI in human readable format?
human_readable: bool,
@ -59,11 +64,14 @@ impl Context {
let events_decl = cx.events_declaration()?;
// 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
let contract_methods = cx.methods()?;
// 5. Declare the structs parsed from the human readable abi
let abi_structs_decl = cx.abi_structs()?;
Ok(quote! {
// export all the created data types
pub use #name_mod::*;
@ -91,6 +99,8 @@ impl Context {
}
#events_decl
#abi_structs_decl
}
})
}
@ -99,22 +109,14 @@ impl Context {
fn from_abigen(args: Abigen) -> Result<Self> {
// get the actual ABI string
let abi_str = args.abi_source.get().context("failed to get ABI JSON")?;
let mut abi_parser = AbiParser::default();
// parse it
let (abi, human_readable): (Abi, _) = if let Ok(abi) = serde_json::from_str(&abi_str) {
// normal abi format
(abi, false)
} else {
// heuristic for parsing the human readable format
// 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)
(abi_parser.parse_str(&abi_str)?, true)
};
let contract_name = util::ident(&args.contract_name);
@ -144,6 +146,7 @@ impl Context {
abi,
human_readable,
abi_str: Literal::string(&abi_str),
abi_parser,
contract_name,
method_aliases,
event_derives,

View File

@ -15,7 +15,7 @@ pub(crate) fn imports(name: &str) -> TokenStream {
use std::sync::Arc;
use ethers::{
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
},
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 {
let name = &cx.contract_name;
let abi = &cx.abi_str;
@ -35,16 +36,8 @@ pub(crate) fn struct_declaration(cx: &Context, abi_name: &proc_macro2::Ident) ->
}
} else {
quote! {
pub static #abi_name: Lazy<Abi> = Lazy::new(|| {
let abi_str = #abi.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).expect("invalid abi")
});
pub static #abi_name: Lazy<Abi> = Lazy::new(|| ethers::core::abi::parse_abi_str(#abi)
.expect("invalid abi"));
}
};

View File

@ -1,6 +1,6 @@
use super::{types, util, Context};
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 proc_macro2::{Literal, TokenStream};
use quote::quote;
@ -14,7 +14,7 @@ impl Context {
let data_types = sorted_events
.values()
.flatten()
.map(|event| expand_event(event, &self.event_derives))
.map(|event| self.expand_event(event))
.collect::<Result<Vec<_>>>()?;
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 data_types = sorted_events
.values()
.flatten()
.map(|event| expand_filter(event))
.map(|event| self.expand_filter(event))
.collect::<Vec<_>>();
if data_types.is_empty() {
@ -42,214 +43,201 @@ impl Context {
#( #data_types )*
})
}
}
/// Expands into a single method for contracting an event stream.
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);
/// 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.
/// 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 }
}
(ParamType::FixedArray(ty, size), true) => {
if let ParamType::Tuple(..) = **ty {
// represents a fixed array of a struct
if let Some(ty) = self
.abi_parser
.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)?,
})
}
let ev_name = Literal::string(&event.name);
/// Expands an ABI event into name-type pairs for each of its parameters.
fn expand_params(&self, event: &Event) -> Result<Vec<(TokenStream, TokenStream)>> {
event
.inputs
.iter()
.enumerate()
.map(|(i, input)| {
// NOTE: Events can contain nameless values.
let name = util::expand_input_name(i, &input.name);
let ty = self.expand_input_type(&input)?;
let doc = util::expand_doc(&format!("Gets the contract's `{}` event", event.name));
quote! {
Ok((name, ty))
})
.collect()
}
#doc
pub fn #name(&self) -> Event<M, #result> {
self.0.event(#ev_name).expect("event not found (this should never happen)")
/// Expands into a single method for contracting an event stream.
fn expand_filter(&self, 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);
/// 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(&self, event: &Event) -> Result<TokenStream> {
let event_name = expand_struct_name(event);
let signature = expand_hash(event.signature());
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)
};
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 derives = expand_derives(&self.event_derives);
let abi_signature = event.abi_signature();
let event_abi_name = &event.name;
let params = expand_params(event)?;
Ok(quote! {
#[derive(Clone, Debug, Default, Eq, PartialEq, ethers::contract::EthEvent, #derives)]
#[ethevent( name = #event_abi_name, abi = #abi_signature )]
pub #data_type_definition
})
}
// 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)
};
/// Expands a event parameter into an event builder filter method for the
/// specified topic index.
fn expand_builder_topic_filter(
&self,
topic_index: usize,
param: &EventParam,
) -> Result<TokenStream> {
let doc = util::expand_doc(&format!(
"Adds a filter for the `{}` event parameter.",
param.name,
));
let topic = util::ident(&format!("topic{}", topic_index));
let name = if param.name.is_empty() {
topic.clone()
} else {
util::safe_ident(&param.name.to_snake_case())
};
let ty = self.expand_input_type(&param)?;
// 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"))?;
Ok(quote! {
#doc
pub fn #name(mut self, topic: Topic<#ty>) -> Self {
self.0 = (self.0).#topic(topic);
self
}
})
.collect::<Vec<_>>();
}
let derives = expand_derives(event_derives);
/// 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! {
#[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
pub const fn abi_signature() -> &'static str {
#abi_signature_lit
}
}
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
)));
}
#[allow(unused_mut)]
let mut tokens = tokens.into_iter();
#( #read_param_token )*
Ok(#data_type_construction)
}
}
})
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 ABI event into name-type pairs for each of its parameters.
fn expand_params(event: &Event) -> Result<Vec<(TokenStream, TokenStream)>> {
event
.inputs
.iter()
.enumerate()
.map(|(i, input)| {
// NOTE: Events can contain nameless values.
let name = util::expand_input_name(i, &input.name);
let ty = expand_input_type(&input)?;
Ok((name, ty))
})
.collect()
}
/// 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, TokenStream) {
fn expand_data_struct(name: &TokenStream, params: &[(TokenStream, TokenStream)]) -> TokenStream {
let fields = params
.iter()
.map(|(name, ty)| quote! { pub #name: #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)
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, TokenStream) {
fn expand_data_tuple(name: &TokenStream, params: &[(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! {
#( #topic_filters )*
})
}
/// Expands a event parameter into an event builder filter method for the
/// specified topic index.
fn expand_builder_topic_filter(topic_index: usize, param: &EventParam) -> Result<TokenStream> {
let doc = util::expand_doc(&format!(
"Adds a filter for the `{}` event parameter.",
param.name,
));
let topic = util::ident(&format!("topic{}", topic_index));
let name = if param.name.is_empty() {
topic.clone()
} else {
util::safe_ident(&param.name.to_snake_case())
};
let ty = expand_input_type(&param)?;
Ok(quote! {
#doc
pub fn #name(mut self, topic: Topic<#ty>) -> Self {
self.0 = (self.0).#topic(topic);
self
}
})
quote! { struct #name( #( #fields ),* ); }
}
/// Expands an ABI event into an identifier for its event data type.
@ -262,24 +250,6 @@ fn expand_derives(derives: &[Path]) -> TokenStream {
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
/// quasi-quoting for code generation. We do this to avoid allocating at runtime
fn expand_hash(hash: Hash) -> TokenStream {
@ -293,8 +263,13 @@ fn expand_hash(hash: Hash) -> TokenStream {
#[cfg(test)]
mod tests {
use super::*;
use crate::Abigen;
use ethers_core::abi::{EventParam, ParamType};
fn test_context() -> Context {
Context::from_abigen(Abigen::new("TestToken", "[]").unwrap()).unwrap()
}
#[test]
fn expand_transfer_filter() {
let event = Event {
@ -318,8 +293,8 @@ mod tests {
],
anonymous: false,
};
assert_quote!(expand_filter(&event), {
let cx = test_context();
assert_quote!(cx.expand_filter(&event), {
#[doc = "Gets the contract's `Transfer` event"]
pub fn transfer_filter(&self) -> Event<M, TransferFilter> {
self.0
@ -348,9 +323,10 @@ mod tests {
anonymous: false,
};
let cx = test_context();
let params = cx.expand_params(&event).unwrap();
let name = expand_struct_name(&event);
let params = expand_params(&event).unwrap();
let (definition, construction) = expand_data_struct(&name, &params);
let definition = expand_data_struct(&name, &params);
assert_quote!(definition, {
struct FooFilter {
@ -358,7 +334,6 @@ mod tests {
pub p1: Address,
}
});
assert_quote!(construction, { FooFilter { a, p1 } });
}
#[test]
@ -380,14 +355,14 @@ mod tests {
anonymous: false,
};
let cx = test_context();
let params = cx.expand_params(&event).unwrap();
let name = expand_struct_name(&event);
let params = expand_params(&event).unwrap();
let (definition, construction) = expand_data_tuple(&name, &params);
let definition = expand_data_tuple(&name, &params);
assert_quote!(definition, {
struct FooFilter(pub bool, pub Address);
});
assert_quote!(construction, { FooFilter(p0, p1) });
}
#[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! {
impl ethers_contract::EthEvent for #name {
fn name(&self) -> ::std::borrow::Cow<'static, str> {
fn name() -> ::std::borrow::Cow<'static, str> {
#event_name.into()
}

View File

@ -11,7 +11,7 @@ use std::marker::PhantomData;
/// A trait for implementing event bindings
pub trait EthEvent: Detokenize {
/// 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.
/// 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(),
};
assert_eq!("ValueChangedEvent", value.name());
assert_eq!("ValueChangedEvent", ValueChangedEvent::name());
assert_eq!(
"ValueChangedEvent(address,address,string,string)",
ValueChangedEvent::abi_signature()
@ -119,14 +119,7 @@ fn can_set_eth_event_name_attribute() {
new_value: String,
}
let value = ValueChangedEvent {
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!("MyEvent", ValueChangedEvent::name());
assert_eq!(
"MyEvent(address,address,string,string)",
ValueChangedEvent::abi_signature()

View File

@ -9,17 +9,33 @@ use crate::abi::{
/// A parser that turns a "human readable abi" into a `Abi`
pub struct AbiParser {
abi: Abi,
/// solidity structs
structs: HashMap<String, SolStruct>,
pub structs: HashMap<String, SolStruct>,
/// solidity structs as tuples
struct_tuples: HashMap<String, Vec<ParamType>>,
pub struct_tuples: HashMap<String, Vec<ParamType>>,
}
impl AbiParser {
/// 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
@ -32,11 +48,21 @@ impl AbiParser {
/// "function x() external view returns (uint256)",
/// ]).unwrap();
/// ```
pub fn parse(mut self, input: &[&str]) -> Result<Abi> {
pub fn parse(&mut self, input: &[&str]) -> Result<Abi> {
// 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
.iter()
.map(|s| escape_quotes(s))
.map(str::trim)
.filter(|s| !s.is_empty())
.partition(|s| s.starts_with("struct"));
for sol in structs {
@ -52,25 +78,23 @@ impl AbiParser {
line = line.trim_start();
if line.starts_with("function") {
let function = self.parse_function(&line)?;
self.abi
.functions
abi.functions
.entry(function.name.clone())
.or_default()
.push(function);
} else if line.starts_with("event") {
let event = self.parse_event(line)?;
self.abi
.events
abi.events
.entry(event.name.clone())
.or_default()
.push(event);
} else if line.starts_with("constructor") {
self.abi.constructor = Some(self.parse_constructor(line)?);
abi.constructor = Some(self.parse_constructor(line)?);
} else {
bail!("Illegal abi `{}`", line)
}
}
Ok(self.abi)
Ok(abi)
}
/// Substitutes any other struct references within structs with tuples
@ -136,13 +160,6 @@ impl AbiParser {
/// Link additional structs for parsing
pub fn with_structs(structs: Vec<SolStruct>) -> Self {
Self {
abi: Abi {
constructor: None,
functions: HashMap::new(),
events: HashMap::new(),
receive: false,
fallback: false,
},
structs: structs
.into_iter()
.map(|s| (s.name().to_string(), s))
@ -392,6 +409,13 @@ pub fn parse(input: &[&str]) -> Result<Abi> {
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
pub(crate) fn parse_identifier(input: &mut &str) -> Result<String> {
let mut chars = input.trim_start().chars();

View File

@ -15,7 +15,7 @@ mod error;
pub use error::ParseError;
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`.
pub trait FunctionExt {