feat(abigen): subsitute structs in event bindings (#1674)
* fix(abigen): handle event defaults * feat(abigen): subsitute structs in event bindings * update changelog * chore: rustfmt * fix broken tests * chore(clippy): make clippy happy
This commit is contained in:
parent
8c3e9985d5
commit
0e7f46b03d
|
@ -93,6 +93,7 @@
|
||||||
|
|
||||||
### Unreleased
|
### Unreleased
|
||||||
|
|
||||||
|
- Use corresponding rust structs for event fields if they're solidity structs [#1674](https://github.com/gakonst/ethers-rs/pull/1674)
|
||||||
- Add `ContractFilter` to filter contracts in `MultiAbigen` [#1564](https://github.com/gakonst/ethers-rs/pull/1564)
|
- Add `ContractFilter` to filter contracts in `MultiAbigen` [#1564](https://github.com/gakonst/ethers-rs/pull/1564)
|
||||||
- generate error bindings for custom errors [#1549](https://github.com/gakonst/ethers-rs/pull/1549)
|
- generate error bindings for custom errors [#1549](https://github.com/gakonst/ethers-rs/pull/1549)
|
||||||
- Support overloaded events
|
- Support overloaded events
|
||||||
|
|
|
@ -207,6 +207,7 @@ impl Context {
|
||||||
.rust_type_names
|
.rust_type_names
|
||||||
.extend(abi_parser.function_params.values().map(|ty| (ty.clone(), ty.clone())));
|
.extend(abi_parser.function_params.values().map(|ty| (ty.clone(), ty.clone())));
|
||||||
internal_structs.function_params = abi_parser.function_params.clone();
|
internal_structs.function_params = abi_parser.function_params.clone();
|
||||||
|
internal_structs.event_params = abi_parser.event_params.clone();
|
||||||
internal_structs.outputs = abi_parser.outputs.clone();
|
internal_structs.outputs = abi_parser.outputs.clone();
|
||||||
|
|
||||||
internal_structs
|
internal_structs
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use super::{types, util, Context};
|
use super::{types, util, Context};
|
||||||
|
use crate::util::can_derive_defaults;
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
abi::{Event, EventExt, EventParam, ParamType, SolStruct},
|
abi::{Event, EventExt, EventParam, Param, ParamType},
|
||||||
macros::{ethers_contract_crate, ethers_core_crate},
|
macros::{ethers_contract_crate, ethers_core_crate},
|
||||||
};
|
};
|
||||||
use eyre::Result;
|
use eyre::Result;
|
||||||
|
@ -140,55 +141,60 @@ impl Context {
|
||||||
/// Note that this is slightly different from expanding a Solidity type as
|
/// Note that this is slightly different from expanding a Solidity type as
|
||||||
/// complex types like arrays and strings get emitted as hashes when they are
|
/// complex types like arrays and strings get emitted as hashes when they are
|
||||||
/// indexed.
|
/// indexed.
|
||||||
/// If a complex types matches with a struct previously parsed by the AbiParser,
|
/// If a complex types matches with a struct previously parsed by the internal structs,
|
||||||
/// we can replace it
|
/// we can replace it
|
||||||
fn expand_input_type(&self, input: &EventParam) -> Result<TokenStream> {
|
fn expand_input_type(
|
||||||
|
&self,
|
||||||
|
event: &Event,
|
||||||
|
input: &EventParam,
|
||||||
|
idx: usize,
|
||||||
|
) -> Result<TokenStream> {
|
||||||
let ethers_core = ethers_core_crate();
|
let ethers_core = ethers_core_crate();
|
||||||
Ok(match (&input.kind, input.indexed) {
|
Ok(match (&input.kind, input.indexed) {
|
||||||
(ParamType::Array(ty), true) => {
|
(ParamType::Array(_), 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! { #ethers_core::types::H256 }
|
quote! { #ethers_core::types::H256 }
|
||||||
}
|
}
|
||||||
(ParamType::FixedArray(ty, size), true) => {
|
(ParamType::FixedArray(_, _), 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! { #ethers_core::types::H256 }
|
quote! { #ethers_core::types::H256 }
|
||||||
}
|
}
|
||||||
(ParamType::Tuple(..), true) => {
|
(ParamType::Tuple(..), true) => {
|
||||||
// represents a struct
|
quote! { #ethers_core::types::H256 }
|
||||||
if let Some(ty) =
|
|
||||||
self.abi_parser.structs.get(&input.name).map(SolStruct::name).map(util::ident)
|
|
||||||
{
|
|
||||||
quote! {#ty}
|
|
||||||
} else {
|
|
||||||
quote! { #ethers_core::types::H256 }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
(ParamType::Bytes, true) | (ParamType::String, true) => {
|
(ParamType::Bytes, true) | (ParamType::String, true) => {
|
||||||
quote! { #ethers_core::types::H256 }
|
quote! { #ethers_core::types::H256 }
|
||||||
}
|
}
|
||||||
|
(ParamType::Tuple(_), false) => {
|
||||||
|
let ty = if let Some(rust_struct_name) =
|
||||||
|
self.internal_structs.get_event_input_struct_type(&event.name, idx)
|
||||||
|
{
|
||||||
|
let ident = util::ident(rust_struct_name);
|
||||||
|
quote! {#ident}
|
||||||
|
} else {
|
||||||
|
types::expand(&input.kind)?
|
||||||
|
};
|
||||||
|
ty
|
||||||
|
}
|
||||||
|
(ParamType::Array(_), _) => {
|
||||||
|
// represents an array of a struct
|
||||||
|
if let Some(rust_struct_name) =
|
||||||
|
self.internal_structs.get_event_input_struct_type(&event.name, idx)
|
||||||
|
{
|
||||||
|
let ty = util::ident(rust_struct_name);
|
||||||
|
return Ok(quote! {::std::vec::Vec<#ty>})
|
||||||
|
}
|
||||||
|
types::expand(&input.kind)?
|
||||||
|
}
|
||||||
|
(ParamType::FixedArray(_, size), _) => {
|
||||||
|
// represents a fixed array of a struct
|
||||||
|
if let Some(rust_struct_name) =
|
||||||
|
self.internal_structs.get_event_input_struct_type(&event.name, idx)
|
||||||
|
{
|
||||||
|
let ty = util::ident(rust_struct_name);
|
||||||
|
let size = Literal::usize_unsuffixed(*size);
|
||||||
|
return Ok(quote! {[#ty; #size]})
|
||||||
|
}
|
||||||
|
types::expand(&input.kind)?
|
||||||
|
}
|
||||||
(kind, _) => types::expand(kind)?,
|
(kind, _) => types::expand(kind)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -199,10 +205,10 @@ impl Context {
|
||||||
.inputs
|
.inputs
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, input)| {
|
.map(|(idx, input)| {
|
||||||
// NOTE: Events can contain nameless values.
|
// NOTE: Events can contain nameless values.
|
||||||
let name = util::expand_input_name(i, &input.name);
|
let name = util::expand_input_name(idx, &input.name);
|
||||||
let ty = self.expand_input_type(input)?;
|
let ty = self.expand_input_type(event, input, idx)?;
|
||||||
|
|
||||||
Ok((name, ty, input.indexed))
|
Ok((name, ty, input.indexed))
|
||||||
})
|
})
|
||||||
|
@ -255,10 +261,31 @@ impl Context {
|
||||||
|
|
||||||
let derives = util::expand_derives(&self.event_derives);
|
let derives = util::expand_derives(&self.event_derives);
|
||||||
|
|
||||||
|
// rust-std only derives default automatically for arrays len <= 32
|
||||||
|
// for large array types we skip derive(Default) <https://github.com/gakonst/ethers-rs/issues/1640>
|
||||||
|
let derive_default = if can_derive_defaults(
|
||||||
|
&event
|
||||||
|
.inputs
|
||||||
|
.iter()
|
||||||
|
.map(|param| Param {
|
||||||
|
name: param.name.clone(),
|
||||||
|
kind: param.kind.clone(),
|
||||||
|
internal_type: None,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
) {
|
||||||
|
quote! {
|
||||||
|
#[derive(Default)]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {}
|
||||||
|
};
|
||||||
|
|
||||||
let ethers_contract = ethers_contract_crate();
|
let ethers_contract = ethers_contract_crate();
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthEvent, #ethers_contract::EthDisplay, #derives)]
|
#[derive(Clone, Debug, Eq, PartialEq, #ethers_contract::EthEvent, #ethers_contract::EthDisplay, #derives)]
|
||||||
|
#derive_default
|
||||||
#[ethevent( name = #event_abi_name, abi = #abi_signature )]
|
#[ethevent( name = #event_abi_name, abi = #abi_signature )]
|
||||||
pub #data_type_definition
|
pub #data_type_definition
|
||||||
})
|
})
|
||||||
|
|
|
@ -229,6 +229,12 @@ pub struct InternalStructs {
|
||||||
/// (function name) -> Vec<structs> all structs the function returns
|
/// (function name) -> Vec<structs> all structs the function returns
|
||||||
pub(crate) outputs: HashMap<String, Vec<String>>,
|
pub(crate) outputs: HashMap<String, Vec<String>>,
|
||||||
|
|
||||||
|
/// (event name, idx) -> struct which are the identifying properties we get the name
|
||||||
|
/// from ethabi.
|
||||||
|
///
|
||||||
|
/// Note: we need to map the index of the event here because events can contain nameless inputs
|
||||||
|
pub(crate) event_params: HashMap<(String, usize), String>,
|
||||||
|
|
||||||
/// All the structs extracted from the abi with their identifier as key
|
/// All the structs extracted from the abi with their identifier as key
|
||||||
pub(crate) structs: HashMap<String, SolStruct>,
|
pub(crate) structs: HashMap<String, SolStruct>,
|
||||||
|
|
||||||
|
@ -245,23 +251,37 @@ impl InternalStructs {
|
||||||
let mut top_level_internal_types = HashMap::new();
|
let mut top_level_internal_types = HashMap::new();
|
||||||
let mut function_params = HashMap::new();
|
let mut function_params = HashMap::new();
|
||||||
let mut outputs = HashMap::new();
|
let mut outputs = HashMap::new();
|
||||||
|
let mut event_params = HashMap::new();
|
||||||
let mut structs = HashMap::new();
|
let mut structs = HashMap::new();
|
||||||
for item in abi
|
for item in abi
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|item| item.type_field == "constructor" || item.type_field == "function")
|
.filter(|item| matches!(item.type_field.as_str(), "constructor" | "function" | "event"))
|
||||||
{
|
{
|
||||||
|
let is_event = item.type_field == "event";
|
||||||
|
|
||||||
if let Some(name) = item.name {
|
if let Some(name) = item.name {
|
||||||
for input in item.inputs {
|
for (idx, input) in item.inputs.into_iter().enumerate() {
|
||||||
if let Some(ty) = input
|
if let Some(ty) = input
|
||||||
.internal_type
|
.internal_type
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.filter(|ty| ty.starts_with("struct "))
|
.filter(|ty| ty.starts_with("struct "))
|
||||||
.map(struct_type_identifier)
|
.map(struct_type_identifier)
|
||||||
{
|
{
|
||||||
function_params.insert((name.clone(), input.name.clone()), ty.to_string());
|
if is_event {
|
||||||
|
event_params.insert((name.clone(), idx), ty.to_string());
|
||||||
|
} else {
|
||||||
|
function_params
|
||||||
|
.insert((name.clone(), input.name.clone()), ty.to_string());
|
||||||
|
}
|
||||||
top_level_internal_types.insert(ty.to_string(), input);
|
top_level_internal_types.insert(ty.to_string(), input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if is_event {
|
||||||
|
// no outputs in an event
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
let mut output_structs = Vec::new();
|
let mut output_structs = Vec::new();
|
||||||
for output in item.outputs {
|
for output in item.outputs {
|
||||||
if let Some(ty) = output
|
if let Some(ty) = output
|
||||||
|
@ -300,6 +320,7 @@ impl InternalStructs {
|
||||||
function_params,
|
function_params,
|
||||||
outputs,
|
outputs,
|
||||||
structs,
|
structs,
|
||||||
|
event_params,
|
||||||
struct_tuples,
|
struct_tuples,
|
||||||
rust_type_names: type_names
|
rust_type_names: type_names
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -318,6 +339,15 @@ impl InternalStructs {
|
||||||
.map(String::as_str)
|
.map(String::as_str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the name of the rust type that will be generated if the given input is a struct
|
||||||
|
/// This takes the index of event's parameter instead of the parameter's name like
|
||||||
|
/// [`Self::get_function_input_struct_type`] does because we can't rely on the name since events
|
||||||
|
/// support nameless parameters NOTE: this does not account for arrays or fixed arrays
|
||||||
|
pub fn get_event_input_struct_type(&self, event: &str, idx: usize) -> Option<&str> {
|
||||||
|
let key = (event.to_string(), idx);
|
||||||
|
self.event_params.get(&key).and_then(|id| self.rust_type_names.get(id)).map(String::as_str)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the name of the rust type that will be generated if the given output is a struct
|
/// Returns the name of the rust type that will be generated if the given output is a struct
|
||||||
/// NOTE: this does not account for arrays or fixed arrays
|
/// NOTE: this does not account for arrays or fixed arrays
|
||||||
pub fn get_function_output_struct_type(
|
pub fn get_function_output_struct_type(
|
||||||
|
|
|
@ -828,12 +828,12 @@ mod tests {
|
||||||
|
|
||||||
let single_file = false;
|
let single_file = false;
|
||||||
|
|
||||||
multi_gen.clone().build().unwrap().write_to_module(&mod_root, single_file).unwrap();
|
multi_gen.clone().build().unwrap().write_to_module(mod_root, single_file).unwrap();
|
||||||
multi_gen
|
multi_gen
|
||||||
.clone()
|
.clone()
|
||||||
.build()
|
.build()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.ensure_consistent_module(&mod_root, single_file)
|
.ensure_consistent_module(mod_root, single_file)
|
||||||
.expect("Inconsistent bindings");
|
.expect("Inconsistent bindings");
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -845,12 +845,12 @@ mod tests {
|
||||||
|
|
||||||
let single_file = true;
|
let single_file = true;
|
||||||
|
|
||||||
multi_gen.clone().build().unwrap().write_to_module(&mod_root, single_file).unwrap();
|
multi_gen.clone().build().unwrap().write_to_module(mod_root, single_file).unwrap();
|
||||||
multi_gen
|
multi_gen
|
||||||
.clone()
|
.clone()
|
||||||
.build()
|
.build()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.ensure_consistent_module(&mod_root, single_file)
|
.ensure_consistent_module(mod_root, single_file)
|
||||||
.expect("Inconsistent bindings");
|
.expect("Inconsistent bindings");
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -868,13 +868,13 @@ mod tests {
|
||||||
.clone()
|
.clone()
|
||||||
.build()
|
.build()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.write_to_crate(name, version, &mod_root, single_file)
|
.write_to_crate(name, version, mod_root, single_file)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
multi_gen
|
multi_gen
|
||||||
.clone()
|
.clone()
|
||||||
.build()
|
.build()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.ensure_consistent_crate(name, version, &mod_root, single_file, true)
|
.ensure_consistent_crate(name, version, mod_root, single_file, true)
|
||||||
.expect("Inconsistent bindings");
|
.expect("Inconsistent bindings");
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -892,13 +892,13 @@ mod tests {
|
||||||
.clone()
|
.clone()
|
||||||
.build()
|
.build()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.write_to_crate(name, version, &mod_root, single_file)
|
.write_to_crate(name, version, mod_root, single_file)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
multi_gen
|
multi_gen
|
||||||
.clone()
|
.clone()
|
||||||
.build()
|
.build()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.ensure_consistent_crate(name, version, &mod_root, single_file, true)
|
.ensure_consistent_crate(name, version, mod_root, single_file, true)
|
||||||
.expect("Inconsistent bindings");
|
.expect("Inconsistent bindings");
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -910,7 +910,7 @@ mod tests {
|
||||||
|
|
||||||
let single_file = false;
|
let single_file = false;
|
||||||
|
|
||||||
multi_gen.clone().build().unwrap().write_to_module(&mod_root, single_file).unwrap();
|
multi_gen.clone().build().unwrap().write_to_module(mod_root, single_file).unwrap();
|
||||||
|
|
||||||
let mut cloned = multi_gen.clone();
|
let mut cloned = multi_gen.clone();
|
||||||
cloned.push(
|
cloned.push(
|
||||||
|
@ -924,7 +924,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
let result =
|
let result =
|
||||||
cloned.build().unwrap().ensure_consistent_module(&mod_root, single_file).is_err();
|
cloned.build().unwrap().ensure_consistent_module(mod_root, single_file).is_err();
|
||||||
|
|
||||||
// ensure inconsistent bindings are detected
|
// ensure inconsistent bindings are detected
|
||||||
assert!(result, "Inconsistent bindings wrongly approved");
|
assert!(result, "Inconsistent bindings wrongly approved");
|
||||||
|
@ -938,7 +938,7 @@ mod tests {
|
||||||
|
|
||||||
let single_file = true;
|
let single_file = true;
|
||||||
|
|
||||||
multi_gen.clone().build().unwrap().write_to_module(&mod_root, single_file).unwrap();
|
multi_gen.clone().build().unwrap().write_to_module(mod_root, single_file).unwrap();
|
||||||
|
|
||||||
let mut cloned = multi_gen.clone();
|
let mut cloned = multi_gen.clone();
|
||||||
cloned.push(
|
cloned.push(
|
||||||
|
@ -952,7 +952,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
let result =
|
let result =
|
||||||
cloned.build().unwrap().ensure_consistent_module(&mod_root, single_file).is_err();
|
cloned.build().unwrap().ensure_consistent_module(mod_root, single_file).is_err();
|
||||||
|
|
||||||
// ensure inconsistent bindings are detected
|
// ensure inconsistent bindings are detected
|
||||||
assert!(result, "Inconsistent bindings wrongly approved");
|
assert!(result, "Inconsistent bindings wrongly approved");
|
||||||
|
@ -972,7 +972,7 @@ mod tests {
|
||||||
.clone()
|
.clone()
|
||||||
.build()
|
.build()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.write_to_crate(name, version, &mod_root, single_file)
|
.write_to_crate(name, version, mod_root, single_file)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut cloned = multi_gen.clone();
|
let mut cloned = multi_gen.clone();
|
||||||
|
@ -989,7 +989,7 @@ mod tests {
|
||||||
let result = cloned
|
let result = cloned
|
||||||
.build()
|
.build()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.ensure_consistent_crate(name, version, &mod_root, single_file, true)
|
.ensure_consistent_crate(name, version, mod_root, single_file, true)
|
||||||
.is_err();
|
.is_err();
|
||||||
|
|
||||||
// ensure inconsistent bindings are detected
|
// ensure inconsistent bindings are detected
|
||||||
|
@ -1010,7 +1010,7 @@ mod tests {
|
||||||
.clone()
|
.clone()
|
||||||
.build()
|
.build()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.write_to_crate(name, version, &mod_root, single_file)
|
.write_to_crate(name, version, mod_root, single_file)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut cloned = multi_gen.clone();
|
let mut cloned = multi_gen.clone();
|
||||||
|
@ -1027,7 +1027,7 @@ mod tests {
|
||||||
let result = cloned
|
let result = cloned
|
||||||
.build()
|
.build()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.ensure_consistent_crate(name, version, &mod_root, single_file, true)
|
.ensure_consistent_crate(name, version, mod_root, single_file, true)
|
||||||
.is_err();
|
.is_err();
|
||||||
|
|
||||||
// ensure inconsistent bindings are detected
|
// ensure inconsistent bindings are detected
|
||||||
|
|
|
@ -19,6 +19,7 @@ use std::{
|
||||||
fn assert_codec<T: AbiDecode + AbiEncode>() {}
|
fn assert_codec<T: AbiDecode + AbiEncode>() {}
|
||||||
fn assert_tokenizeable<T: Tokenizable>() {}
|
fn assert_tokenizeable<T: Tokenizable>() {}
|
||||||
fn assert_call<T: AbiEncode + AbiDecode + Default + Tokenizable>() {}
|
fn assert_call<T: AbiEncode + AbiDecode + Default + Tokenizable>() {}
|
||||||
|
fn assert_event<T: EthEvent>() {}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_gen_human_readable() {
|
fn can_gen_human_readable() {
|
||||||
|
@ -720,3 +721,18 @@ fn can_gen_large_tuple_array() {
|
||||||
let _call = CallWithLongArrayCall::default();
|
let _call = CallWithLongArrayCall::default();
|
||||||
assert_call::<CallWithLongArrayCall>();
|
assert_call::<CallWithLongArrayCall>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_generate_event_with_structs() {
|
||||||
|
/*
|
||||||
|
contract MyContract {
|
||||||
|
struct MyStruct {uint256 a; uint256 b; }
|
||||||
|
event MyEvent(MyStruct, uint256);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
abigen!(MyContract, "ethers-contract/tests/solidity-contracts/EventWithStruct.json");
|
||||||
|
|
||||||
|
let _filter = MyEventFilter { p0: MyStruct::default(), c: U256::zero() };
|
||||||
|
assert_eq!("MyEvent((uint256,uint256),uint256)", MyEventFilter::abi_signature());
|
||||||
|
assert_event::<MyEventFilter>();
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"internalType": "uint256",
|
||||||
|
"name": "a",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"internalType": "uint256",
|
||||||
|
"name": "b",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"indexed": false,
|
||||||
|
"internalType": "struct MyContract.MyStruct",
|
||||||
|
"name": "",
|
||||||
|
"type": "tuple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indexed": false,
|
||||||
|
"internalType": "uint256",
|
||||||
|
"name": "c",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "MyEvent",
|
||||||
|
"type": "event"
|
||||||
|
}
|
||||||
|
]
|
|
@ -18,6 +18,11 @@ pub struct AbiParser {
|
||||||
/// (function name, param name) -> struct which are the identifying properties we get the name
|
/// (function name, param name) -> struct which are the identifying properties we get the name
|
||||||
/// from ethabi.
|
/// from ethabi.
|
||||||
pub function_params: HashMap<(String, String), String>,
|
pub function_params: HashMap<(String, String), String>,
|
||||||
|
/// (event name, idx) -> struct which are the identifying properties we get the name
|
||||||
|
/// from ethabi.
|
||||||
|
///
|
||||||
|
/// Note: we need to map the index of the event here because events can contain nameless inputs
|
||||||
|
pub event_params: HashMap<(String, usize), String>,
|
||||||
/// (function name) -> Vec<structs> all structs the function returns
|
/// (function name) -> Vec<structs> all structs the function returns
|
||||||
pub outputs: HashMap<String, Vec<String>>,
|
pub outputs: HashMap<String, Vec<String>>,
|
||||||
}
|
}
|
||||||
|
@ -170,12 +175,13 @@ impl AbiParser {
|
||||||
structs: structs.into_iter().map(|s| (s.name().to_string(), s)).collect(),
|
structs: structs.into_iter().map(|s| (s.name().to_string(), s)).collect(),
|
||||||
struct_tuples: HashMap::new(),
|
struct_tuples: HashMap::new(),
|
||||||
function_params: Default::default(),
|
function_params: Default::default(),
|
||||||
|
event_params: Default::default(),
|
||||||
outputs: Default::default(),
|
outputs: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses a solidity event declaration from `event <name> (args*) anonymous?`
|
/// Parses a solidity event declaration from `event <name> (args*) anonymous?`
|
||||||
pub fn parse_event(&self, s: &str) -> Result<Event> {
|
pub fn parse_event(&mut self, s: &str) -> Result<Event> {
|
||||||
let mut event = s.trim();
|
let mut event = s.trim();
|
||||||
if !event.starts_with("event ") {
|
if !event.starts_with("event ") {
|
||||||
bail!("Not an event `{}`", s)
|
bail!("Not an event `{}`", s)
|
||||||
|
@ -208,8 +214,20 @@ impl AbiParser {
|
||||||
.split(',')
|
.split(',')
|
||||||
.map(|e| self.parse_event_arg(e))
|
.map(|e| self.parse_event_arg(e))
|
||||||
.collect::<Result<Vec<_>, _>>()?
|
.collect::<Result<Vec<_>, _>>()?
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(idx, (input, struct_name))| {
|
||||||
|
if let Some(struct_name) = struct_name {
|
||||||
|
// keep track of the user defined struct of that param
|
||||||
|
self.event_params.insert((name.clone(), idx), struct_name);
|
||||||
|
}
|
||||||
|
input
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
};
|
};
|
||||||
return Ok(Event { name, inputs, anonymous })
|
|
||||||
|
let event = Event { name, inputs, anonymous };
|
||||||
|
return Ok(event)
|
||||||
}
|
}
|
||||||
Some(' ') | Some('\t') => continue,
|
Some(' ') | Some('\t') => continue,
|
||||||
Some(c) => {
|
Some(c) => {
|
||||||
|
@ -220,7 +238,9 @@ impl AbiParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a single event param
|
/// Parse a single event param
|
||||||
fn parse_event_arg(&self, input: &str) -> Result<EventParam> {
|
///
|
||||||
|
/// See [`Self::parse_type`]
|
||||||
|
fn parse_event_arg(&self, input: &str) -> Result<(EventParam, Option<String>)> {
|
||||||
let mut iter = input.trim().rsplitn(3, is_whitespace);
|
let mut iter = input.trim().rsplitn(3, is_whitespace);
|
||||||
let mut indexed = false;
|
let mut indexed = false;
|
||||||
let mut name =
|
let mut name =
|
||||||
|
@ -246,7 +266,8 @@ impl AbiParser {
|
||||||
name = "";
|
name = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(EventParam { name: name.to_string(), indexed, kind: self.parse_type(type_str)?.0 })
|
let (kind, user_ty) = self.parse_type(type_str)?;
|
||||||
|
Ok((EventParam { name: name.to_string(), indexed, kind }, user_ty))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the parsed function from the input string
|
/// Returns the parsed function from the input string
|
||||||
|
@ -665,12 +686,12 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_event_input() {
|
fn parse_event_input() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
AbiParser::default().parse_event_arg("address indexed x").unwrap(),
|
AbiParser::default().parse_event_arg("address indexed x").unwrap().0,
|
||||||
EventParam { name: "x".to_string(), kind: ParamType::Address, indexed: true }
|
EventParam { name: "x".to_string(), kind: ParamType::Address, indexed: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
AbiParser::default().parse_event_arg("address x").unwrap(),
|
AbiParser::default().parse_event_arg("address x").unwrap().0,
|
||||||
EventParam { name: "x".to_string(), kind: ParamType::Address, indexed: false }
|
EventParam { name: "x".to_string(), kind: ParamType::Address, indexed: false }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue