Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initialize contracts having function or event overrides #3476

Open
wants to merge 30 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
778b687
Ambiguous event contract test
reedsa Sep 4, 2024
c544463
Use ``get_abi_element`` instead of ``get_event_abi``
reedsa Sep 4, 2024
9fcbc23
Failing test for ``get_abi_element`` for retrieving an overloaded eve…
reedsa Sep 4, 2024
8ae0fee
Better filtering in ``get_abi_element``
reedsa Sep 4, 2024
ac7ca56
Use argument types to disambiguate event ABIs in ``_get_event_abi`` c…
reedsa Sep 4, 2024
8ae7321
Update ContractFunction methods to call ``_get_abi()`` and handle amb…
reedsa Sep 5, 2024
fbff55d
Handle ambiguity to get the correct function ABI in ``prepare_transac…
reedsa Sep 5, 2024
9bbebb7
Find ambiguous function without args in ``_find_matching_fn_abi``
reedsa Sep 5, 2024
f2aa792
Use ABI from Contract Event inside class methods.
reedsa Sep 5, 2024
65cf98f
Resolve ambiguous event references by storing a copy of each event wi…
reedsa Sep 5, 2024
b2db790
Use the event interface to get the correct instance
reedsa Sep 5, 2024
9c0bd8f
Newsfragment for #3476
reedsa Sep 5, 2024
59cbb9c
Updates based on feedback
reedsa Sep 6, 2024
bb40953
Get function by argument type to resolve ambiguities
reedsa Sep 9, 2024
08b49e5
All function classes initialize with the ABI
reedsa Sep 10, 2024
b50e22b
Additional test for ``get_function_by_signature``
reedsa Sep 11, 2024
284913b
Deprecate ``get_event_abi``
reedsa Sep 11, 2024
75efe10
Rename ``_find_abi_identifier_by_name`` -> ``_get_any_abi_signature_w…
reedsa Sep 11, 2024
01f126b
Compile with latest Solidity version (v0.8.27)
reedsa Sep 11, 2024
9ff0d2d
Add test case for ``abi`` used as function name in a contract
reedsa Sep 11, 2024
53c5ea9
Update event_contracts.py
reedsa Sep 12, 2024
e1d16e5
Revert changes made to ens test
reedsa Sep 12, 2024
233fa23
Add section to ambiguous section of the contract docs
reedsa Sep 13, 2024
a24b4a5
Improved docs formatting
reedsa Sep 13, 2024
24e4857
Add ``@combomethod`` to ``_parse_logs`` in base_contract
reedsa Sep 16, 2024
02de0a2
Initialize and use correct ``fallback`` or ``receive`` function
reedsa Sep 16, 2024
9fa8d04
Apply changes from #3479
reedsa Sep 16, 2024
ebd3092
Feedback improvements
reedsa Sep 16, 2024
3104111
Clean up logic in filters
reedsa Sep 19, 2024
f7ace61
Fix logic
reedsa Sep 19, 2024
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
18 changes: 17 additions & 1 deletion docs/web3.contract.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1450,6 +1450,23 @@ You can interact with the web3.py contract API as follows:
Invoke Ambiguous Contract Functions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Calling overloaded functions can be done as you would expect. Passing arguments will
disambiguate which function you want to call.

For example, if you have a contract with two functions with the name ``identity`` that
accept different types of arguments, you can call them like this:

.. code-block:: python

>>> ambiguous_contract = w3.eth.contract(address=..., abi=...)
>>> ambiguous_contract.functions.identity(1, True).call()
1
>>> ambiguous_contract.functions.identity("one", 1, True).call()
1

If there is a need to first retrieve the function, you can use the contract instance's
``get_function_by_signature`` method to get the function you want to call.

Below is an example of a contract that has multiple functions of the same name,
and the arguments are ambiguous. You can use the :meth:`Contract.get_function_by_signature`
method to reference the intended function and call it with the correct arguments.
Expand All @@ -1468,7 +1485,6 @@ method to reference the intended function and call it with the correct arguments
}
"""
# fast forward all the steps of compiling and deploying the contract.
>>> ambiguous_contract.functions.identity(1, True) # raises Web3ValidationError

>>> identity_func = ambiguous_contract.get_function_by_signature('identity(uint256,bool)')
>>> identity_func(1, True)
Expand Down
1 change: 1 addition & 0 deletions newsfragments/3476.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +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.
60 changes: 58 additions & 2 deletions tests/core/contracts/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
from tests.utils import (
async_partial,
)
from web3._utils.abi import (
get_abi_element_signature,
)
from web3._utils.contract_sources.contract_data.arrays_contract import (
ARRAYS_CONTRACT_DATA,
)
Expand All @@ -22,6 +25,7 @@
CONTRACT_CALLER_TESTER_DATA,
)
from web3._utils.contract_sources.contract_data.event_contracts import (
AMBIGUOUS_EVENT_NAME_CONTRACT_DATA,
EVENT_CONTRACT_DATA,
INDEXED_EVENT_CONTRACT_DATA,
)
Expand Down Expand Up @@ -61,6 +65,10 @@
from web3.exceptions import (
Web3ValueError,
)
from web3.utils.abi import (
abi_to_signature,
get_abi_element,
)

# --- function name tester contract --- #

Expand Down Expand Up @@ -261,6 +269,30 @@ def indexed_event_contract(
return indexed_event_contract


@pytest.fixture
def ambiguous_event_contract(
w3, wait_for_block, wait_for_transaction, address_conversion_func
):
wait_for_block(w3)

ambiguous_event_contract_factory = w3.eth.contract(
**AMBIGUOUS_EVENT_NAME_CONTRACT_DATA
)
deploy_txn_hash = ambiguous_event_contract_factory.constructor().transact(
{"gas": 1000000}
)
deploy_receipt = wait_for_transaction(w3, deploy_txn_hash)
contract_address = address_conversion_func(deploy_receipt["contractAddress"])

bytecode = w3.eth.get_code(contract_address)
assert bytecode == ambiguous_event_contract_factory.bytecode_runtime
ambiguous_event_name_contract = ambiguous_event_contract_factory(
address=contract_address
)
assert ambiguous_event_name_contract.address == contract_address
return ambiguous_event_name_contract


# --- arrays contract --- #


Expand Down Expand Up @@ -448,6 +480,11 @@ def invoke_contract(
func_kwargs=None,
tx_params=None,
):
function_signature = contract_function
function_arg_count = len(func_args or ()) + len(func_kwargs or {})
if function_arg_count == 0:
function_signature = get_abi_element_signature(contract_function)

if func_args is None:
func_args = []
if func_kwargs is None:
Expand All @@ -460,7 +497,14 @@ def invoke_contract(
f"allowable_invoke_method must be one of: {allowable_call_desig}"
)

function = contract.functions[contract_function]
fn_abi = get_abi_element(
contract.abi,
function_signature,
*func_args,
abi_codec=contract.w3.codec,
**func_kwargs,
)
function = contract.functions[abi_to_signature(fn_abi)]
result = getattr(function(*func_args, **func_kwargs), api_call_desig)(tx_params)

return result
Expand Down Expand Up @@ -722,6 +766,11 @@ async def async_invoke_contract(
func_kwargs=None,
tx_params=None,
):
function_signature = contract_function
function_arg_count = len(func_args or ()) + len(func_kwargs or {})
if function_arg_count == 0:
function_signature = get_abi_element_signature(contract_function)

if func_args is None:
func_args = []
if func_kwargs is None:
Expand All @@ -734,7 +783,14 @@ async def async_invoke_contract(
f"allowable_invoke_method must be one of: {allowable_call_desig}"
)

function = contract.functions[contract_function]
fn_abi = get_abi_element(
contract.abi,
function_signature,
*func_args,
abi_codec=contract.w3.codec,
**func_kwargs,
)
function = contract.functions[abi_to_signature(fn_abi)]
result = await getattr(function(*func_args, **func_kwargs), api_call_desig)(
tx_params
)
Expand Down
Loading