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
This commit is contained in:
Matthias Seitz 2021-10-31 12:24:02 +01:00 committed by GitHub
parent 46bedb3282
commit 4123823383
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 84 additions and 26 deletions

View File

@ -4,6 +4,7 @@
### Unreleased ### 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) - 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) - 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) - `abigen!` now generates `Display` for all events using the new `EthDisplay` macro [#513](https://github.com/gakonst/ethers-rs/pull/513)

View File

@ -282,15 +282,27 @@ impl Context {
// sort functions by number of inputs asc // sort functions by number of inputs asc
let mut functions = functions.iter().collect::<Vec<_>>(); let mut functions = functions.iter().collect::<Vec<_>>();
functions.sort_by(|f1, f2| f1.inputs.len().cmp(&f2.inputs.len())); functions.sort_by(|f1, f2| f1.inputs.len().cmp(&f2.inputs.len()));
let prev = functions[0]; let first = functions[0];
for duplicate in functions.into_iter().skip(1) { // 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 // attempt to find diff in the input arguments
let diff = duplicate let mut diff = Vec::new();
.inputs let mut same_params = true;
.iter() for (idx, i1) in duplicate.inputs.iter().enumerate() {
.filter(|i1| prev.inputs.iter().all(|i2| *i1 != i2)) if first.inputs.iter().all(|i2| i1 != i2) {
.collect::<Vec<_>>(); 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() { let alias = match diff.len() {
0 => { 0 => {
// this should not happen since functions with same name and input are // this should not happen since functions with same name and input are
@ -302,13 +314,22 @@ impl Context {
} }
1 => { 1 => {
// single additional input params // single additional input params
if diff[0].name.is_empty() {
add_alias_for_first_with_idx = true;
format!("{}1", duplicate.name.to_snake_case())
} else {
format!( format!(
"{}_with_{}", "{}_with_{}",
duplicate.name.to_snake_case(), duplicate.name.to_snake_case(),
diff[0].name.to_snake_case() diff[0].name.to_snake_case()
) )
} }
}
_ => { _ => {
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 // 1 + n additional input params
let and = diff let and = diff
.iter() .iter()
@ -323,9 +344,16 @@ impl Context {
and and
) )
} }
}
}; };
aliases.insert(duplicate.abi_signature(), util::safe_ident(&alias)); 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) Ok(aliases)
} }

View File

@ -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 `;` /// `abigen!` supports multiple abigen definitions separated by a semicolon `;`
/// This is useful if the contracts use ABIEncoderV2 structs. In which case /// This is useful if the contracts use ABIEncoderV2 structs. In which case
/// `abigen!` bundles all type duplicates so that all rust contracts also use /// `abigen!` bundles all type duplicates so that all rust contracts also use

View File

@ -191,6 +191,8 @@ fn can_handle_overloaded_functions() {
getValue() (uint256) getValue() (uint256)
getValue(uint256 otherValue) (uint256) getValue(uint256 otherValue) (uint256)
getValue(uint256 otherValue, address addr) (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(); let decoded_enum = SimpleContractCalls::decode(encoded_call.as_ref()).unwrap();
assert_eq!(contract_call, decoded_enum); assert_eq!(contract_call, decoded_enum);
assert_eq!(encoded_call, contract_call.encode().into()); 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] #[tokio::test]