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

feat: enumerate overloaded functions if they are nameless #545

Merged
merged 5 commits into from
Oct 31, 2021
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 @@ -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)
Expand Down
80 changes: 54 additions & 26 deletions ethers-contract/ethers-contract-abigen/src/contract/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
Expand Down
3 changes: 3 additions & 0 deletions ethers-contract/ethers-contract-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 26 additions & 0 deletions ethers-contract/tests/abigen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
]"#
);

Expand Down Expand Up @@ -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]
Expand Down