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 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,
|
||||||
|
|
|
@ -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")
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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,214 +43,201 @@ 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
|
/// Note that this is slightly different than an expanding a Solidity type as
|
||||||
// function names
|
/// complex types like arrays and strings get emited as hashes when they are
|
||||||
let name = util::safe_ident(&format!("{}_filter", event.name.to_snake_case()));
|
/// indexed.
|
||||||
// let result = util::ident(&event.name.to_pascal_case());
|
/// If a complex types matches with a struct previously parsed by the AbiParser,
|
||||||
let result = expand_struct_name(event);
|
/// 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));
|
Ok((name, ty))
|
||||||
quote! {
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
#doc
|
/// Expands into a single method for contracting an event stream.
|
||||||
pub fn #name(&self) -> Event<M, #result> {
|
fn expand_filter(&self, event: &Event) -> TokenStream {
|
||||||
self.0.event(#ev_name).expect("event not found (this should never happen)")
|
// 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
|
/// 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
|
/// into a structure or a tuple in the case where all event parameters (topics
|
||||||
/// and data) are anonymous.
|
/// and data) are anonymous.
|
||||||
fn expand_event(event: &Event, event_derives: &[Path]) -> Result<TokenStream> {
|
fn expand_event(&self, event: &Event) -> Result<TokenStream> {
|
||||||
let event_name = expand_struct_name(event);
|
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 derives = expand_derives(&self.event_derives);
|
||||||
let abi_signature_lit = Literal::string(&abi_signature);
|
let abi_signature = event.abi_signature();
|
||||||
let abi_signature_doc = util::expand_doc(&format!("`{}`", 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
|
/// Expands a event parameter into an event builder filter method for the
|
||||||
let all_anonymous_fields = event.inputs.iter().all(|input| input.name.is_empty());
|
/// specified topic index.
|
||||||
let (data_type_definition, data_type_construction) = if all_anonymous_fields {
|
fn expand_builder_topic_filter(
|
||||||
expand_data_tuple(&event_name, ¶ms)
|
&self,
|
||||||
} else {
|
topic_index: usize,
|
||||||
expand_data_struct(&event_name, ¶ms)
|
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
|
Ok(quote! {
|
||||||
let params_len = Literal::usize_unsuffixed(params.len());
|
#doc
|
||||||
let read_param_token = params
|
pub fn #name(mut self, topic: Topic<#ty>) -> Self {
|
||||||
.iter()
|
self.0 = (self.0).#topic(topic);
|
||||||
.map(|(name, _)| {
|
self
|
||||||
quote! {
|
|
||||||
let #name = Tokenizable::from_token(tokens.next().expect("this should never happen"))?;
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.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! {
|
Ok(quote! {
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq, #derives)]
|
#( #topic_filters )*
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expands an ABI event into an identifier for its event data type.
|
/// Expands an ABI event into an identifier for its event data type.
|
||||||
fn expand_struct_name(event: &Event) -> TokenStream {
|
fn expand_struct_name(event: &Event) -> TokenStream {
|
||||||
|
// TODO: get rid of `Filter` suffix?
|
||||||
let name = format!("{}Filter", event.name.to_pascal_case());
|
let name = format!("{}Filter", event.name.to_pascal_case());
|
||||||
let event_name = util::ident(&name);
|
let event_name = util::ident(&name);
|
||||||
quote! { #event_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
|
/// Expands an event data structure from its name-type parameter pairs. Returns
|
||||||
/// a tuple with the type definition (i.e. the struct declaration) and
|
/// a tuple with the type definition (i.e. the struct declaration) and
|
||||||
/// construction (i.e. code for creating an instance of the event data).
|
/// construction (i.e. code for creating an instance of the event data).
|
||||||
fn expand_data_struct(
|
fn expand_data_struct(name: &TokenStream, params: &[(TokenStream, TokenStream)]) -> TokenStream {
|
||||||
name: &TokenStream,
|
|
||||||
params: &[(TokenStream, TokenStream)],
|
|
||||||
) -> (TokenStream, TokenStream) {
|
|
||||||
let fields = params
|
let fields = params
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, ty)| quote! { pub #name: #ty })
|
.map(|(name, ty)| quote! { pub #name: #ty })
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let param_names = params
|
quote! { struct #name { #( #fields, )* } }
|
||||||
.iter()
|
|
||||||
.map(|(name, _)| name)
|
|
||||||
.cloned()
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let definition = quote! { struct #name { #( #fields, )* } };
|
|
||||||
let construction = quote! { #name { #( #param_names ),* } };
|
|
||||||
|
|
||||||
(definition, construction)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expands an event data named tuple from its name-type parameter pairs.
|
/// Expands an event data named tuple from its name-type parameter pairs.
|
||||||
/// Returns a tuple with the type definition and construction.
|
/// Returns a tuple with the type definition and construction.
|
||||||
fn expand_data_tuple(
|
fn expand_data_tuple(name: &TokenStream, params: &[(TokenStream, TokenStream)]) -> TokenStream {
|
||||||
name: &TokenStream,
|
|
||||||
params: &[(TokenStream, TokenStream)],
|
|
||||||
) -> (TokenStream, TokenStream) {
|
|
||||||
let fields = params
|
let fields = params
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(_, ty)| quote! { pub #ty })
|
.map(|(_, ty)| quote! { pub #ty })
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let param_names = params
|
quote! { struct #name( #( #fields ),* ); }
|
||||||
.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
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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, ¶ms);
|
||||||
let (definition, construction) = expand_data_struct(&name, ¶ms);
|
|
||||||
|
|
||||||
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, ¶ms);
|
||||||
let (definition, construction) = expand_data_tuple(&name, ¶ms);
|
|
||||||
|
|
||||||
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]
|
||||||
|
|
|
@ -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! {
|
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(),
|
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()
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue