From 4123823383728037b23c18038f9a107902d67756 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sun, 31 Oct 2021 12:24:02 +0100 Subject: [PATCH] feat: enumerate overloaded functions if they are nameless (#545) * fix: overloaded with same type arguments * fix: numerate overloaded functions * docs: add note * chore: update changelog * style: rename vars --- CHANGELOG.md | 1 + .../src/contract/methods.rs | 80 +++++++++++++------ .../ethers-contract-derive/src/lib.rs | 3 + ethers-contract/tests/abigen.rs | 26 ++++++ 4 files changed, 84 insertions(+), 26 deletions(-) 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::>(); 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::>(); - + 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::>() - .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::>() + .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 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]