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:
parent
0c18f9b32c
commit
57010c1c60
|
@ -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,
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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, ¶ms)
|
||||
} else {
|
||||
expand_data_struct(&event_name, ¶ms)
|
||||
};
|
||||
|
||||
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, ¶ms)
|
||||
} else {
|
||||
expand_data_struct(&event_name, ¶ms)
|
||||
};
|
||||
/// 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(¶m.name.to_snake_case())
|
||||
};
|
||||
let ty = self.expand_input_type(¶m)?;
|
||||
|
||||
// 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(¶m.name.to_snake_case())
|
||||
};
|
||||
let ty = expand_input_type(¶m)?;
|
||||
|
||||
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, ¶ms);
|
||||
let definition = expand_data_struct(&name, ¶ms);
|
||||
|
||||
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, ¶ms);
|
||||
let definition = expand_data_tuple(&name, ¶ms);
|
||||
|
||||
assert_quote!(definition, {
|
||||
struct FooFilter(pub bool, pub Address);
|
||||
});
|
||||
assert_quote!(construction, { FooFilter(p0, p1) });
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -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 )*})
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
// );
|
||||
// }
|
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue