Repository: gakonst/ethers-rs (rust)

Issue:
Overloaded Events are not supported by abigen
this will currently result in events with the same name


```solidity

    event ActionPaused(string action, bool pauseState);

    event ActionPaused(CToken cToken, string action, bool pauseState);
```

https://github.com/compound-finance/compound-protocol/blob/3affca87636eecd901eb43f81a4813186393905d/contracts/ComptrollerG2.sol#L61-L69

similar to calls, this should just numerate them.

Pull request description:
feat(abigen): support overloaded events
<!--
Thank you for your Pull Request. Please provide a description above and review
the requirements below.

Bug fixes and new features should include tests.

Contributors guide: https://github.com/gakonst/ethers-rs/blob/master/CONTRIBUTING.md

The contributors guide includes instructions for running rustfmt and building the
documentation.
-->

## Motivation
Closes #1135

<!--
Explain the context and why you're making that change. What is the problem
you're trying to solve? In some cases there is not a problem and this can be
thought of as being the motivation for your change.
-->

## Solution
Auto aliase overloaded events
<!--
Summarize the solution and provide any necessary context needed to understand
the code change.
-->

## PR Checklist

- [x] Added Tests
- [x] Added Documentation
- [x] Updated the changelog


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 810d78da..6d20c21b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -68,6 +68,8 @@
 
 ### Unreleased
 
+- Support overloaded events
+  [#1233](https://github.com/gakonst/ethers-rs/pull/1233)
 - Relax Clone requirements when Arc<Middleware> is used
   [#1183](https://github.com/gakonst/ethers-rs/pull/1183)
 - Generate a deploy function if bytecode is provided in the abigen! input (json artifact)
diff --git a/ethers-contract/ethers-contract-abigen/src/contract.rs b/ethers-contract/ethers-contract-abigen/src/contract.rs
index 288af663..ad4306d6 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract.rs
@@ -16,7 +16,7 @@ use eyre::{eyre, Context as _, Result};
 use crate::contract::methods::MethodAlias;
 
 use crate::rawabi::JsonAbi;
-use ethers_core::types::Bytes;
+use ethers_core::{abi::EventExt, types::Bytes};
 use proc_macro2::{Ident, Literal, TokenStream};
 use quote::quote;
 use serde::Deserialize;
@@ -227,6 +227,22 @@ impl Context {
             event_aliases.insert(signature, alias);
         }
 
+        // also check for overloaded functions not covered by aliases, in which case we simply
+        // numerate them
+        for events in abi.events.values() {
+            let not_aliased =
+                events.iter().filter(|ev| !event_aliases.contains_key(&ev.abi_signature()));
+            if not_aliased.clone().count() > 1 {
+                let mut aliases = Vec::new();
+                // overloaded events
+                for (idx, event) in not_aliased.enumerate() {
+                    let event_name = format!("{}{}", event.name, idx + 1);
+                    aliases.push((event.abi_signature(), events::event_struct_alias(&event_name)));
+                }
+                event_aliases.extend(aliases);
+            }
+        }
+
         let event_derives = args
             .event_derives
             .iter()
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/events.rs b/ethers-contract/ethers-contract-abigen/src/contract/events.rs
index 96946af8..d7fb7b5e 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/events.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/events.rs
@@ -58,7 +58,9 @@ impl Context {
             .events
             .values()
             .flatten()
-            .map(|e| expand_struct_name(e, self.event_aliases.get(&e.abi_signature()).cloned()))
+            .map(|e| {
+                event_struct_name(&e.name, self.event_aliases.get(&e.abi_signature()).cloned())
+            })
             .collect::<Vec<_>>();
 
         let ethers_core = ethers_core_crate();
@@ -116,7 +118,10 @@ impl Context {
             let ty = if iter.next().is_some() {
                 self.expand_event_enum_name()
             } else {
-                expand_struct_name(event, self.event_aliases.get(&event.abi_signature()).cloned())
+                event_struct_name(
+                    &event.name,
+                    self.event_aliases.get(&event.abi_signature()).cloned(),
+                )
             };
 
             quote! {
@@ -218,7 +223,7 @@ impl Context {
         // append `filter` to disambiguate with potentially conflicting
         // function names
 
-        let result = expand_struct_name(event, alias);
+        let result = event_struct_name(&event.name, alias);
 
         let doc = util::expand_doc(&format!("Gets the contract's `{}` event", event.name));
         quote! {
@@ -237,7 +242,7 @@ impl Context {
         let abi_signature = event.abi_signature();
         let event_abi_name = event.name.clone();
 
-        let event_name = expand_struct_name(event, sig);
+        let event_name = event_struct_name(&event.name, sig);
 
         let params = self.expand_event_params(event)?;
         // expand as a tuple if all fields are anonymous
@@ -261,17 +266,22 @@ impl Context {
 }
 
 /// Expands an ABI event into an identifier for its event data type.
-fn expand_struct_name(event: &Event, alias: Option<Ident>) -> Ident {
+fn event_struct_name(event_name: &str, alias: Option<Ident>) -> Ident {
     // TODO: get rid of `Filter` suffix?
 
     let name = if let Some(id) = alias {
         format!("{}Filter", id.to_string().to_pascal_case())
     } else {
-        format!("{}Filter", event.name.to_pascal_case())
+        format!("{}Filter", event_name.to_pascal_case())
     };
     util::ident(&name)
 }
 
+/// Returns the alias name for an event
+pub(crate) fn event_struct_alias(event_name: &str) -> Ident {
+    util::ident(&event_name.to_pascal_case())
+}
+
 /// 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).
@@ -406,7 +416,7 @@ mod tests {
 
         let cx = test_context();
         let params = cx.expand_event_params(&event).unwrap();
-        let name = expand_struct_name(&event, None);
+        let name = event_struct_name(&event.name, None);
         let definition = expand_data_struct(&name, &params);
 
         assert_quote!(definition, {
@@ -431,7 +441,7 @@ mod tests {
         let cx = test_context_with_alias("Foo(bool,address)", "FooAliased");
         let params = cx.expand_event_params(&event).unwrap();
         let alias = Some(util::ident("FooAliased"));
-        let name = expand_struct_name(&event, alias);
+        let name = event_struct_name(&event.name, alias);
         let definition = expand_data_struct(&name, &params);
 
         assert_quote!(definition, {
@@ -455,7 +465,7 @@ mod tests {
 
         let cx = test_context();
         let params = cx.expand_event_params(&event).unwrap();
-        let name = expand_struct_name(&event, None);
+        let name = event_struct_name(&event.name, None);
         let definition = expand_data_tuple(&name, &params);
 
         assert_quote!(definition, {
@@ -477,7 +487,7 @@ mod tests {
         let cx = test_context_with_alias("Foo(bool,address)", "FooAliased");
         let params = cx.expand_event_params(&event).unwrap();
         let alias = Some(util::ident("FooAliased"));
-        let name = expand_struct_name(&event, alias);
+        let name = event_struct_name(&event.name, alias);
         let definition = expand_data_tuple(&name, &params);
 
         assert_quote!(definition, {


Test patch:
diff --git a/ethers-contract/tests/abigen.rs b/ethers-contract/tests/abigen.rs
index 63faf133..1c5cd110 100644
--- a/ethers-contract/tests/abigen.rs
+++ b/ethers-contract/tests/abigen.rs
@@ -548,3 +548,21 @@ fn can_gen_reserved_word_field_names() {
 
     let _foo = Foo { ref_: U256::default() };
 }
+
+#[test]
+fn can_handle_overloaded_events() {
+    abigen!(
+        SimpleContract,
+        r#"[
+            event ActionPaused(string cToken, string action, bool pauseState)
+            event ActionPaused(string action, bool pauseState)
+    ]"#
+    );
+
+    let _ev1 = ActionPaused1Filter {
+        c_token: "ctoken".to_string(),
+        action: "action".to_string(),
+        pause_state: false,
+    };
+    let _ev2 = ActionPaused2Filter { action: "action".to_string(), pause_state: false };
+}