-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
base: main
Are you sure you want to change the base?
Conversation
bdc2282
to
4b2adad
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! Just left a few nits!
tests/core/contracts/conftest.py
Outdated
@@ -760,3 +785,4 @@ def async_call(request): | |||
@pytest.fixture | |||
def async_estimate_gas(request): | |||
return async_partial(async_invoke_contract, api_call_desig="estimate_gas") | |||
return async_partial(async_invoke_contract, api_call_desig="estimate_gas") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
copy pasta?
assert event_abi == ABI_EVENT_DEPOSIT | ||
|
||
|
||
def test_get_abi_element_with_signature_for_amibguous_tuple_events() -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
def test_get_abi_element_with_signature_for_amibguous_tuple_events() -> None: | |
def test_get_abi_element_with_signature_for_amibiguous_tuple_events() -> None: |
web3/contract/base_contract.py
Outdated
def __init__( | ||
self, | ||
*argument_names: Tuple[str], | ||
abi: ABIEvent = None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit?
abi: ABIEvent = None, | |
abi: Optional[ABIEvent] = None, |
web3/contract/base_contract.py
Outdated
|
||
def __getattr__(self, event_name: str) -> Type["BaseContractEvent"]: | ||
if "_events" not in self.__dict__: | ||
raise NoABIEventsFound( | ||
"The abi for this contract contains no event definitions. ", | ||
"Are you sure you provided the correct contract abi?", | ||
) | ||
elif event_name not in self.__dict__["_events"]: | ||
elif event_name.split("(")[0] not in [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this event_name.split...
may deserve to be its own method since you're using it so much in here. Feel free to take or leave :)
newsfragments/3476.feature.rst
Outdated
@@ -0,0 +1 @@ | |||
Contracts which 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
Contracts which 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. | |
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. |
web3/contract/base_contract.py
Outdated
*self.args, | ||
abi_codec=self.w3.codec, | ||
**self.kwargs, | ||
cls.contract_abi, get_abi_element_identifier(abi_element_identifier) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need the codec passed too?
The ambiguity with functions is also an issue unfortunately. Taking a look at how to disambiguate since right now it uses the "first" one. |
7b15976
to
4354b1a
Compare
@@ -173,14 +173,14 @@ def test_offchain_resolver_function_call_raises_with_ccip_read_disabled( | |||
with pytest.raises(OffchainLookup): | |||
offchain_resolver.functions.resolve( | |||
ens_encode_name("offchainexample.eth"), | |||
ENCODED_ADDR_CALLDATA, | |||
ENCODED_ADDR_CALLDATA.encode(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the added encode
here? Test passes for me with or without it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for this after all, good callout.
web3/contract/contract.py
Outdated
|
||
def __iter__(self) -> Iterable["ContractEvent"]: | ||
""" | ||
Iterate over supported |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit - docstring should state what the function does (not sure if it's necessary for a magic method?). Also should match for the same method in AsyncContractEvents
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, there's no practical information I provided there anyway. Removing!
web3/utils/abi.py
Outdated
) -> ABIElement: | ||
""" | ||
Return the interface for an ``ABIElement`` which matches the provided identifier | ||
and arguments. | ||
Return the interface for an ``ABIElement`` from the `abi` that matches the provided |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Return the interface for an ``ABIElement`` from the `abi` that matches the provided | |
Return the interface for an ``ABIElement`` from the ``abi`` that matches the provided |
web3/utils/abi.py
Outdated
def get_event_abi( | ||
abi: ABI, | ||
event_name: str, | ||
argument_names: Optional[Sequence[str]] = None, | ||
) -> ABIEvent: | ||
""" | ||
.. warning:: | ||
This function is deprecated. It is unable to distinguish between | ||
overloaded events. Use `get_abi_element` instead. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
overloaded events. Use `get_abi_element` instead. | |
overloaded events. Use ``get_abi_element`` instead. |
__getattr__ now uses the ABI to look up the function class by signature Additional fixes for ens
Refactoring and docs update for ``get_abi_element`` and ``_build_filters`` Rename ``get_abi_element_identifier`` -> ``get_abi_element_signature``
…ith_name`` Further cleanup and refactoring
Fix contract compilation and naming
Remove docs from events iter Fix docstring formatting
1b05ab8
to
e1d16e5
Compare
054b8e5
to
233fa23
Compare
web3/utils/abi.py
Outdated
filter is a partial function that takes a contract ABI and returns a filtered list. | ||
Each parameter is checked before applying the relevant filter. | ||
|
||
When the `abi_element_identifier` is a function name or signature and no arguments |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the `abi_element_identifier` is a function name or signature and no arguments | |
When the ``abi_element_identifier`` is a function name or signature and no arguments |
web3/utils/abi.py
Outdated
filters include the function name, argument count, argument name, argument type, | ||
and argument encodability. | ||
|
||
`constructor`, `fallback`, and `receive` ABI elements are handled only with a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
`constructor`, `fallback`, and `receive` ABI elements are handled only with a | |
``constructor``, ``fallback``, and ``receive`` ABI elements are handled only with a |
web3/utils/abi.py
Outdated
:param abi_element_identifier: Find an element ABI with matching identifier. | ||
:param abi_element_identifier: Find an element ABI with matching identifier. The \ | ||
identifier may be a function name, signature, or ``FallbackFn`` or ``ReceiveFn``. \ | ||
A function signature is in the form `name(arg1Type,arg2Type,...)`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A function signature is in the form `name(arg1Type,arg2Type,...)`. | |
A function signature is in the form ``name(arg1Type,arg2Type,...)``. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🐑
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sweet! I didn't see anything major, just left a few small comments.
function_name_tester_contract_abi, | ||
function_name_tester_contract, | ||
): | ||
# test `w3` function name does not throw when creating the contract factory |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# test `w3` function name does not throw when creating the contract factory | |
# test `abi` function name does not throw when creating the contract factory |
# re-instantiate the contract | ||
contract = contract_factory(function_name_tester_contract.address) | ||
|
||
# Contract w3 function should not override web3 instance |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe something to test that contract.abi
doesn't get overridden here instead?
|
||
assert contract.abi == function_name_tester_contract_abi | ||
|
||
# assert the `w3` function returns true when called |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# assert the `w3` function returns true when called | |
# assert the `abi` function returns true when called |
function_name_tester_contract_abi, | ||
async_function_name_tester_contract, | ||
): | ||
# test `w3` function name does not throw when creating the contract factory |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# test `w3` function name does not throw when creating the contract factory | |
# test `abi` function name does not throw when creating the contract factory |
# Contract w3 function should not override web3 instance | ||
with pytest.raises(AttributeError): | ||
contract.functions.abi.get_block("latest") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe a test to make sure abi
doesn't get overridden here instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made a slight adjustment to the order of the assertion/exception cases and updated the inline comments to be accurate. Let me know if there's further clarity needed there!
|
||
assert contract.abi == function_name_tester_contract_abi | ||
|
||
# assert the `w3` function returns true when called |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# assert the `w3` function returns true when called | |
# assert the `abi` function returns true when called |
web3/contract/base_contract.py
Outdated
@@ -486,23 +516,49 @@ class BaseContractFunction: | |||
args: Any = None | |||
kwargs: Any = None | |||
|
|||
def __init__(self, abi: Optional[ABIFunction] = None) -> None: | |||
def __init__(self, *arguments: Any, abi: Optional[ABIFunction] = None) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a breaking change, since I believe now abi has to be passed in as a kwarg. Does that change anything for the UI?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was originally intended to bring the same interface to BaseContractFunction
and BaseContractEvent
, but the arugments
are never actually used.
I've also taken further steps to eliminate extraneous arguments passed to the factory function. Passing the signature is enough to find the ABI that is needed later on. I've made updates to the async/sync contracts so initialization of the ABI still works as intended.
web3/contract/base_contract.py
Outdated
abi: Optional[ABIEvent] = None, | ||
abi_element_identifier: Optional[ABIElementIdentifier] = None, | ||
) -> None: | ||
# event was referenced with parens, juts return the event |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
# event was referenced with parens, juts return the event | |
# event was referenced with parens, just return the event |
web3/contract/utils.py
Outdated
@@ -119,6 +122,34 @@ def format_contract_call_return_data_curried( | |||
return normalized_data[0] if len(normalized_data) == 1 else normalized_data | |||
|
|||
|
|||
def copy_contract_function( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure we gain anything by exposing this publicly. Consider moving these two methods to web3/_utils/contracts.py
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, moved!
|
||
event_get_abi_result = ambiguous_event_contract.events[ | ||
"LogSingleArg(uint256)" | ||
]._get_event_abi() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a stylistic nitpick, but it seems a little smelly to me to test a private method. It looks like _get_event_abi
is used to assign the abi attribute on ContractEvent
. Would it be possible to do something like:
]._get_event_abi() | |
event_get_abi_result = ambiguous_event_contract.events[ | |
"LogSingleArg(uint256)" | |
].abi |
Feel free to take or leave if that doesn't make sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is definitely better as .abi
👍
Filter by type prior to getting an ABI element
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🐑
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! Since this is such a core component of our library, it's probably worth some manual testing to make sure old functionality still works as expected. I did some myself and have fairly high confidence in the tests, but it's probably worth one more run through!
What was wrong?
Closes #1704, #3482
The
Contract
class cannot initialize if the contract contains overloaded functions or events. Currently only the function/event name is used to filter ABI events from the contract ABI, so an exception is raised. Currently, events raise a ValueError when_get_event_abi
is called on the Contract or ContractEvent class. Functions raise a MismatchedABI error.Example:
BaseContractEvent._get_event_abi()
calls eth-utilsget_event_abi()
and expects to return the ABI for the event class. The event classes are not created for every overloaded function. When there are contract events with the same name, only one is ever created and used.How was it fixed?
Initialize the
ContractFunctions
orContractEvents
class with every ABI found in the contract, storing the abi signature in the class name. Along with the signature, theabi
property is set for each class within the__init__
method. This ensures the correct instance is found and used for subsequent functions likeget_logs()
.get_abi_element
now handles both functions and events using the ABI signature to find a unique ABI. The signature is represented by a string that includes it's name and list of input types within parentheses.name(arg1Type,arg2Type,...)
Exceptions are now shared between functions and events for consistency.
Todo:
Cute Animal Picture