Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

feat(abigen): subsitute structs in event bindings #1674

Merged
merged 6 commits into from
Sep 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@

### 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)
- generate error bindings for custom errors [#1549](https://github.com/gakonst/ethers-rs/pull/1549)
- Support overloaded events
Expand Down
1 change: 1 addition & 0 deletions ethers-contract/ethers-contract-abigen/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ impl Context {
.rust_type_names
.extend(abi_parser.function_params.values().map(|ty| (ty.clone(), ty.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
Expand Down
109 changes: 68 additions & 41 deletions ethers-contract/ethers-contract-abigen/src/contract/events.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::{types, util, Context};
use crate::util::can_derive_defaults;
use ethers_core::{
abi::{Event, EventExt, EventParam, ParamType, SolStruct},
abi::{Event, EventExt, EventParam, Param, ParamType},
macros::{ethers_contract_crate, ethers_core_crate},
};
use eyre::Result;
Expand Down Expand Up @@ -140,54 +141,59 @@ impl Context {
/// 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
/// 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
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();
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>})
}
}
(ParamType::Array(_), true) => {
quote! { #ethers_core::types::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]})
}
}
(ParamType::FixedArray(_, _), true) => {
quote! { #ethers_core::types::H256 }
}
(ParamType::Tuple(..), true) => {
// represents a struct
if let Some(ty) =
self.abi_parser.structs.get(&input.name).map(SolStruct::name).map(util::ident)
quote! { #ethers_core::types::H256 }
}
(ParamType::Bytes, true) | (ParamType::String, true) => {
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)
{
quote! {#ty}
let ident = util::ident(rust_struct_name);
quote! {#ident}
} else {
quote! { #ethers_core::types::H256 }
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::Bytes, true) | (ParamType::String, true) => {
quote! { #ethers_core::types::H256 }
(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)?,
})
Expand All @@ -199,10 +205,10 @@ impl Context {
.inputs
.iter()
.enumerate()
.map(|(i, input)| {
.map(|(idx, input)| {
// NOTE: Events can contain nameless values.
let name = util::expand_input_name(i, &input.name);
let ty = self.expand_input_type(input)?;
let name = util::expand_input_name(idx, &input.name);
let ty = self.expand_input_type(event, input, idx)?;

Ok((name, ty, input.indexed))
})
Expand Down Expand Up @@ -255,10 +261,31 @@ impl Context {

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();

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 )]
pub #data_type_definition
})
Expand Down
36 changes: 33 additions & 3 deletions ethers-contract/ethers-contract-abigen/src/contract/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,12 @@ pub struct InternalStructs {
/// (function name) -> Vec<structs> all structs the function returns
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
pub(crate) structs: HashMap<String, SolStruct>,

Expand All @@ -245,23 +251,37 @@ impl InternalStructs {
let mut top_level_internal_types = HashMap::new();
let mut function_params = HashMap::new();
let mut outputs = HashMap::new();
let mut event_params = HashMap::new();
let mut structs = HashMap::new();
for item in abi
.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 {
for input in item.inputs {
for (idx, input) in item.inputs.into_iter().enumerate() {
if let Some(ty) = input
.internal_type
.as_deref()
.filter(|ty| ty.starts_with("struct "))
.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);
}
}

if is_event {
// no outputs in an event
continue
}

let mut output_structs = Vec::new();
for output in item.outputs {
if let Some(ty) = output
Expand Down Expand Up @@ -300,6 +320,7 @@ impl InternalStructs {
function_params,
outputs,
structs,
event_params,
struct_tuples,
rust_type_names: type_names
.into_iter()
Expand All @@ -318,6 +339,15 @@ impl InternalStructs {
.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
/// NOTE: this does not account for arrays or fixed arrays
pub fn get_function_output_struct_type(
Expand Down
32 changes: 16 additions & 16 deletions ethers-contract/ethers-contract-abigen/src/multi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -828,12 +828,12 @@ mod tests {

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
.clone()
.build()
.unwrap()
.ensure_consistent_module(&mod_root, single_file)
.ensure_consistent_module(mod_root, single_file)
.expect("Inconsistent bindings");
})
}
Expand All @@ -845,12 +845,12 @@ mod tests {

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
.clone()
.build()
.unwrap()
.ensure_consistent_module(&mod_root, single_file)
.ensure_consistent_module(mod_root, single_file)
.expect("Inconsistent bindings");
})
}
Expand All @@ -868,13 +868,13 @@ mod tests {
.clone()
.build()
.unwrap()
.write_to_crate(name, version, &mod_root, single_file)
.write_to_crate(name, version, mod_root, single_file)
.unwrap();
multi_gen
.clone()
.build()
.unwrap()
.ensure_consistent_crate(name, version, &mod_root, single_file, true)
.ensure_consistent_crate(name, version, mod_root, single_file, true)
.expect("Inconsistent bindings");
})
}
Expand All @@ -892,13 +892,13 @@ mod tests {
.clone()
.build()
.unwrap()
.write_to_crate(name, version, &mod_root, single_file)
.write_to_crate(name, version, mod_root, single_file)
.unwrap();
multi_gen
.clone()
.build()
.unwrap()
.ensure_consistent_crate(name, version, &mod_root, single_file, true)
.ensure_consistent_crate(name, version, mod_root, single_file, true)
.expect("Inconsistent bindings");
})
}
Expand All @@ -910,7 +910,7 @@ mod tests {

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();
cloned.push(
Expand All @@ -924,7 +924,7 @@ mod tests {
);

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
assert!(result, "Inconsistent bindings wrongly approved");
Expand All @@ -938,7 +938,7 @@ mod tests {

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();
cloned.push(
Expand All @@ -952,7 +952,7 @@ mod tests {
);

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
assert!(result, "Inconsistent bindings wrongly approved");
Expand All @@ -972,7 +972,7 @@ mod tests {
.clone()
.build()
.unwrap()
.write_to_crate(name, version, &mod_root, single_file)
.write_to_crate(name, version, mod_root, single_file)
.unwrap();

let mut cloned = multi_gen.clone();
Expand All @@ -989,7 +989,7 @@ mod tests {
let result = cloned
.build()
.unwrap()
.ensure_consistent_crate(name, version, &mod_root, single_file, true)
.ensure_consistent_crate(name, version, mod_root, single_file, true)
.is_err();

// ensure inconsistent bindings are detected
Expand All @@ -1010,7 +1010,7 @@ mod tests {
.clone()
.build()
.unwrap()
.write_to_crate(name, version, &mod_root, single_file)
.write_to_crate(name, version, mod_root, single_file)
.unwrap();

let mut cloned = multi_gen.clone();
Expand All @@ -1027,7 +1027,7 @@ mod tests {
let result = cloned
.build()
.unwrap()
.ensure_consistent_crate(name, version, &mod_root, single_file, true)
.ensure_consistent_crate(name, version, mod_root, single_file, true)
.is_err();

// ensure inconsistent bindings are detected
Expand Down
Loading