Skip to content

Commit f89f379

Browse files
committed
Overloaded functions with better MismatchedABI messaging
1 parent 3c412f8 commit f89f379

25 files changed

+1743
-486
lines changed

docs/web3.contract.rst

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1570,6 +1570,23 @@ You can interact with the web3.py contract API as follows:
15701570
Invoke Ambiguous Contract Functions
15711571
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
15721572

1573+
Calling overloaded functions can be done as you would expect. Passing arguments will
1574+
disambiguate which function you want to call.
1575+
1576+
For example, if you have a contract with two functions with the name ``identity`` that
1577+
accept different types of arguments, you can call them like this:
1578+
1579+
.. code-block:: python
1580+
1581+
>>> ambiguous_contract = w3.eth.contract(address=..., abi=...)
1582+
>>> ambiguous_contract.functions.identity(1, True).call()
1583+
1
1584+
>>> ambiguous_contract.functions.identity("one", 1, True).call()
1585+
1
1586+
1587+
If there is a need to first retrieve the function, you can use the contract instance's
1588+
``get_function_by_signature`` method to get the function you want to call.
1589+
15731590
Below is an example of a contract that has multiple functions of the same name,
15741591
and the arguments are ambiguous. You can use the :meth:`Contract.get_function_by_signature`
15751592
method to reference the intended function and call it with the correct arguments.
@@ -1588,7 +1605,6 @@ method to reference the intended function and call it with the correct arguments
15881605
}
15891606
"""
15901607
# fast forward all the steps of compiling and deploying the contract.
1591-
>>> ambiguous_contract.functions.identity(1, True) # raises Web3ValidationError
15921608
15931609
>>> identity_func = ambiguous_contract.get_function_by_signature('identity(uint256,bool)')
15941610
>>> identity_func(1, True)

newsfragments/3491.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Update the `ContractEvents` class to raise a `NoABIFound` exception if the `Contract` is initialized without an `ABI` and an attempt to access an event is made. This exception makes `ContractEvents` consistent with `ContractFunctions`.

newsfragments/3491.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Contracts with overloaded functions or events are now supported. The Contract initializes functions and events using an identifier to distinguish between them. The identifier is the function or event signature, which consists of the name and the parameter types.

newsfragments/3491.misc.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Raise MismatchedABI exceptions with detailed error messaging about potential matching elements. The arguments and expected types are included in the exception message for better debugging.

tests/core/contracts/conftest.py

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
from tests.utils import (
1111
async_partial,
1212
)
13+
from web3._utils.abi import (
14+
get_abi_element_signature,
15+
)
1316
from web3._utils.contract_sources.contract_data.arrays_contract import (
1417
ARRAYS_CONTRACT_DATA,
1518
)
@@ -22,6 +25,7 @@
2225
CONTRACT_CALLER_TESTER_DATA,
2326
)
2427
from web3._utils.contract_sources.contract_data.event_contracts import (
28+
AMBIGUOUS_EVENT_NAME_CONTRACT_DATA,
2529
EVENT_CONTRACT_DATA,
2630
INDEXED_EVENT_CONTRACT_DATA,
2731
)
@@ -61,6 +65,10 @@
6165
from web3.exceptions import (
6266
Web3ValueError,
6367
)
68+
from web3.utils.abi import (
69+
abi_to_signature,
70+
get_abi_element,
71+
)
6472

6573
# --- function name tester contract --- #
6674

@@ -283,6 +291,30 @@ def indexed_event_contract(
283291
return indexed_event_contract
284292

285293

294+
@pytest.fixture
295+
def ambiguous_event_contract(
296+
w3, wait_for_block, wait_for_transaction, address_conversion_func
297+
):
298+
wait_for_block(w3)
299+
300+
ambiguous_event_contract_factory = w3.eth.contract(
301+
**AMBIGUOUS_EVENT_NAME_CONTRACT_DATA
302+
)
303+
deploy_txn_hash = ambiguous_event_contract_factory.constructor().transact(
304+
{"gas": 1000000}
305+
)
306+
deploy_receipt = wait_for_transaction(w3, deploy_txn_hash)
307+
contract_address = address_conversion_func(deploy_receipt["contractAddress"])
308+
309+
bytecode = w3.eth.get_code(contract_address)
310+
assert bytecode == ambiguous_event_contract_factory.bytecode_runtime
311+
ambiguous_event_name_contract = ambiguous_event_contract_factory(
312+
address=contract_address
313+
)
314+
assert ambiguous_event_name_contract.address == contract_address
315+
return ambiguous_event_name_contract
316+
317+
286318
# --- arrays contract --- #
287319

288320

@@ -470,6 +502,11 @@ def invoke_contract(
470502
func_kwargs=None,
471503
tx_params=None,
472504
):
505+
function_signature = contract_function
506+
function_arg_count = len(func_args or ()) + len(func_kwargs or {})
507+
if function_arg_count == 0:
508+
function_signature = get_abi_element_signature(contract_function)
509+
473510
if func_args is None:
474511
func_args = []
475512
if func_kwargs is None:
@@ -482,7 +519,14 @@ def invoke_contract(
482519
f"allowable_invoke_method must be one of: {allowable_call_desig}"
483520
)
484521

485-
function = contract.functions[contract_function]
522+
fn_abi = get_abi_element(
523+
contract.abi,
524+
function_signature,
525+
*func_args,
526+
abi_codec=contract.w3.codec,
527+
**func_kwargs,
528+
)
529+
function = contract.functions[abi_to_signature(fn_abi)]
486530
result = getattr(function(*func_args, **func_kwargs), api_call_desig)(tx_params)
487531

488532
return result
@@ -744,6 +788,11 @@ async def async_invoke_contract(
744788
func_kwargs=None,
745789
tx_params=None,
746790
):
791+
function_signature = contract_function
792+
function_arg_count = len(func_args or ()) + len(func_kwargs or {})
793+
if function_arg_count == 0:
794+
function_signature = get_abi_element_signature(contract_function)
795+
747796
if func_args is None:
748797
func_args = []
749798
if func_kwargs is None:
@@ -756,7 +805,14 @@ async def async_invoke_contract(
756805
f"allowable_invoke_method must be one of: {allowable_call_desig}"
757806
)
758807

759-
function = contract.functions[contract_function]
808+
fn_abi = get_abi_element(
809+
contract.abi,
810+
function_signature,
811+
*func_args,
812+
abi_codec=contract.w3.codec,
813+
**func_kwargs,
814+
)
815+
function = contract.functions[abi_to_signature(fn_abi)]
760816
result = await getattr(function(*func_args, **func_kwargs), api_call_desig)(
761817
tx_params
762818
)

0 commit comments

Comments
 (0)