Repository: gakonst/ethers-rs (rust)

Issue:
abigen! fails for functions with same name, different args
**Version**

ethers v0.5.3 (https://github.com/gakonst/ethers-rs#5ab0b7e0)

**Platform**
`Darwin odys-mbp-68.local 20.6.0 Darwin Kernel Version 20.6.0: Mon Aug 30 06:12:21 PDT 2021; root:xnu-7195.141.6~3/RELEASE_X86_64 x86_64`

**Description**

- `abigen!` fails to handle multiple functions that have the same name but different arguments
- It fails in a non-consistent manner. The first example works, the second produces an error:

```
log(address)
log(string, string)
log(string)
```
```
log(string, string)
log(string)
```

### Error 
```
error: Function with same name and parameter types defined twice: log
  --> evm-adapters/src/sputnik/cheatcodes/mod.rs:72:5
   |
72 | /     r#"[
73 | |             log(string, string)
74 | |             log(string)
75 | |     ]"#,
   | |_______^


```

### Code 
```
ethers::contract::abigen!(
    ConsoleLog,
    r#"[
            log(address)
            log(string, string)
            log(string)
    ]"#,
);

pub use ConsoleLogCalls;
```

The above is needed so that we can port `console.log` functionality to dapptools-rs. 


Pull request description:
feat: enumerate overloaded functions if they are nameless
<!--
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
Close #543 
<!--
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
generate aliases using the call's indices if the function params are nameless like

```rust
    abigen!(
        ConsoleLog,
        r#"[
            log()
            log(string, string)
            log(string)
    ]"#
    );
```
becomes

```rust
enum    ConsoleLogCalls {
    Log0,
    Log1(...),
    Log2(...),
}
```

<!--
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 93974d6d..ec383fb7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
 
 ### Unreleased
 
+- use enumerated aliases for overloaded functions [#545](https://github.com/gakonst/ethers-rs/pull/545)
 - move `AbiEncode` `AbiDecode` trait to ethers-core and implement for core types [#531](https://github.com/gakonst/ethers-rs/pull/531)
 - add `EthCall` trait and derive macro which generates matching structs for contract calls [#517](https://github.com/gakonst/ethers-rs/pull/517)
 - `abigen!` now generates `Display` for all events using the new `EthDisplay` macro [#513](https://github.com/gakonst/ethers-rs/pull/513)
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
index c8e9cb55..cf88ed50 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
@@ -282,15 +282,27 @@ impl Context {
             // sort functions by number of inputs asc
             let mut functions = functions.iter().collect::<Vec<_>>();
             functions.sort_by(|f1, f2| f1.inputs.len().cmp(&f2.inputs.len()));
-            let prev = functions[0];
-            for duplicate in functions.into_iter().skip(1) {
+            let first = functions[0];
+            // assuming here that if there are overloaded functions with nameless params like `log;,
+            // log(string); log(string, string)` `log()` should also be aliased with its
+            // index to `log0`
+            let mut add_alias_for_first_with_idx = false;
+            for (idx, duplicate) in functions.into_iter().enumerate().skip(1) {
                 // attempt to find diff in the input arguments
-                let diff = duplicate
-                    .inputs
-                    .iter()
-                    .filter(|i1| prev.inputs.iter().all(|i2| *i1 != i2))
-                    .collect::<Vec<_>>();
-
+                let mut diff = Vec::new();
+                let mut same_params = true;
+                for (idx, i1) in duplicate.inputs.iter().enumerate() {
+                    if first.inputs.iter().all(|i2| i1 != i2) {
+                        diff.push(i1);
+                        same_params = false;
+                    } else {
+                        // check for cases like `log(string); log(string, string)` by keep track of
+                        // same order
+                        if same_params && idx + 1 > first.inputs.len() {
+                            diff.push(i1);
+                        }
+                    }
+                }
                 let alias = match diff.len() {
                     0 => {
                         // this should not happen since functions with same name and input are
@@ -302,30 +314,46 @@ impl Context {
                     }
                     1 => {
                         // single additional input params
-                        format!(
-                            "{}_with_{}",
-                            duplicate.name.to_snake_case(),
-                            diff[0].name.to_snake_case()
-                        )
+                        if diff[0].name.is_empty() {
+                            add_alias_for_first_with_idx = true;
+                            format!("{}1", duplicate.name.to_snake_case())
+                        } else {
+                            format!(
+                                "{}_with_{}",
+                                duplicate.name.to_snake_case(),
+                                diff[0].name.to_snake_case()
+                            )
+                        }
                     }
                     _ => {
-                        // 1 + n additional input params
-                        let and = diff
-                            .iter()
-                            .skip(1)
-                            .map(|i| i.name.to_snake_case())
-                            .collect::<Vec<_>>()
-                            .join("_and_");
-                        format!(
-                            "{}_with_{}_and_{}",
-                            duplicate.name.to_snake_case(),
-                            diff[0].name.to_snake_case(),
-                            and
-                        )
+                        if diff.iter().any(|d| d.name.is_empty()) {
+                            add_alias_for_first_with_idx = true;
+                            format!("{}{}", duplicate.name.to_snake_case(), idx)
+                        } else {
+                            // 1 + n additional input params
+                            let and = diff
+                                .iter()
+                                .skip(1)
+                                .map(|i| i.name.to_snake_case())
+                                .collect::<Vec<_>>()
+                                .join("_and_");
+                            format!(
+                                "{}_with_{}_and_{}",
+                                duplicate.name.to_snake_case(),
+                                diff[0].name.to_snake_case(),
+                                and
+                            )
+                        }
                     }
                 };
                 aliases.insert(duplicate.abi_signature(), util::safe_ident(&alias));
             }
+
+            if add_alias_for_first_with_idx {
+                // insert an alias for the root duplicated call
+                let prev_alias = format!("{}0", first.name.to_snake_case());
+                aliases.insert(first.abi_signature(), util::safe_ident(&prev_alias));
+            }
         }
         Ok(aliases)
     }
diff --git a/ethers-contract/ethers-contract-derive/src/lib.rs b/ethers-contract/ethers-contract-derive/src/lib.rs
index 1393b6fb..d343be2c 100644
--- a/ethers-contract/ethers-contract-derive/src/lib.rs
+++ b/ethers-contract/ethers-contract-derive/src/lib.rs
@@ -62,6 +62,9 @@ pub(crate) mod utils;
 /// );
 /// ```
 ///
+/// Aliases for overloaded functions with no aliases provided in the `method` section are derived
+/// automatically.
+///
 /// `abigen!` supports multiple abigen definitions separated by a semicolon `;`
 /// This is useful if the contracts use ABIEncoderV2 structs. In which case
 /// `abigen!` bundles all type duplicates so that all rust contracts also use


Test patch:
diff --git a/ethers-contract/tests/abigen.rs b/ethers-contract/tests/abigen.rs
index 449b52b7..e8ab939e 100644
--- a/ethers-contract/tests/abigen.rs
+++ b/ethers-contract/tests/abigen.rs
@@ -191,6 +191,8 @@ fn can_handle_overloaded_functions() {
         getValue() (uint256)
         getValue(uint256 otherValue) (uint256)
         getValue(uint256 otherValue, address addr) (uint256)
+        setValue(string, string)
+        setValue(string)
     ]"#
     );
 
@@ -238,6 +240,30 @@ fn can_handle_overloaded_functions() {
     let decoded_enum = SimpleContractCalls::decode(encoded_call.as_ref()).unwrap();
     assert_eq!(contract_call, decoded_enum);
     assert_eq!(encoded_call, contract_call.encode().into());
+
+    let call = SetValue0Call("message".to_string());
+    let _contract_call = SimpleContractCalls::SetValue0(call);
+    let call = SetValue1Call("message".to_string(), "message".to_string());
+    let _contract_call = SimpleContractCalls::SetValue1(call);
+}
+
+#[test]
+fn can_handle_even_more_overloaded_functions() {
+    abigen!(
+        ConsoleLog,
+        r#"[
+            log()
+            log(string, string)
+            log(string)
+    ]"#
+    );
+
+    let _call = Log0Call;
+    let _contract_call = ConsoleLogCalls::Log0;
+    let call = Log1Call("message".to_string());
+    let _contract_call = ConsoleLogCalls::Log1(call);
+    let call = Log2Call("message".to_string(), "message".to_string());
+    let _contract_call = ConsoleLogCalls::Log2(call);
 }
 
 #[tokio::test]