Skip to content

Commit e411d79

Browse files
author
Stuart Reed
committed
Fix argument type and function logic for prepare_transaction
Fix typing for `find_matching_event_abi` Remove redundant `get_*_function_abi` methods, typing can be done client side Fix mypy errors Fix type for `get_abi_element` `args` as `Optional[Sequence[Any]]` Fix docstrings types and ordering Ensure `get_element_abi` uses the right `constructor`, `fallback`, or `receive` function Fix issue with constructor function in `get_abi_element` Use ``abi_element_name`` as argument name Revert changes for `get_*_function_abi` for edge case tests
1 parent 719e838 commit e411d79

File tree

11 files changed

+137
-318
lines changed

11 files changed

+137
-318
lines changed

docs/migration.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,8 @@ Miscellaneous Changes
292292
- ``get_default_ipc_path()`` and ``get_dev_ipc_path()`` now return the path value
293293
without checking if the ``geth.ipc`` file exists.
294294
- ``Web3.is_address()`` returns ``True`` for non-checksummed addresses.
295-
- ``Contract.encodeABI()`` has been renamed to ``Contract.encode_abi()``.
295+
- ``Contract.encodeABI()`` has been renamed to ``Contract.encode_abi()``. The ``fn_name``
296+
argument has been changed to ``abi_element_name``.
296297
- JSON-RPC responses are now more strictly validated against the JSON-RPC 2.0
297298
specification while providing more informative error messages for invalid responses.
298299

newsfragments/3408.breaking.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Upgrades to use latest ``ABI`` utilities and typings from ``eth-utils`` and ``eth-typing``.
2+
* Typings for ``ABI`` components are now available in the ``eth-typing`` package. ``ABI`` types previously in ``web3.types`` have been removed.
3+
* New versions of existing ABI functions were added to ``eth-utils`` and are now exposed in `web3.py` via ``web3.utils.abi``.
4+
* ABI exceptions have been renamed in ``web3.exceptions``. The ``ABIEventFunctionNotFound`` and ``FallbackNotFound`` exceptions have been removed. Use ``ABIEventNotFound`` and ``ABIFallbackNotFound`` instead.
5+
* ``MismatchedABI`` exceptions are raised instead of a ``Web3ValidationError`` for ABI related errors.
6+
* ``encode_abi`` arguments have been updated to use ``abi_element_name`` instead of ``fn_name``.

newsfragments/3408.feature.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Utilities to extract function and event ``ABI`` attributes from a contract. Utilities in the ``web3.utils.abi`` module parse ABI elements and check encodability of provided arguments. ABI functions in ``eth-utils`` are exposed by the ``web3.utils.abi`` module.
2+
* ``get_abi_element_info`` returns an ``ABIElementInfo`` TypedDict with the ``abi``, ``selector``, and ``arguments``.
3+
* ``get_abi_element`` returns the ``ABI`` of a function, event, or error given the name and arguments.
4+
* ``check_if_arguments_can_be_encoded`` returns true if the arguments can be encoded with the given ABI.
5+
* ``get_event_abi`` returns the ``ABI`` of an event given the name.
6+
* ``get_event_log_topics`` returns the log topics of an event given the name.
7+
* ``log_topics_to_bytes`` returns the log topics as bytes.

tests/core/utilities/test_abi.py

Lines changed: 0 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,6 @@
4949
addresses_checksummed,
5050
)
5151
from web3.exceptions import (
52-
ABIConstructorNotFound,
53-
ABIFallbackNotFound,
54-
ABIReceiveNotFound,
5552
MismatchedABI,
5653
Web3ValidationError,
5754
Web3ValueError,
@@ -62,11 +59,8 @@
6259
from web3.utils.abi import (
6360
get_abi_element,
6461
get_abi_element_info,
65-
get_constructor_function_abi,
6662
get_event_abi,
6763
get_event_log_topics,
68-
get_fallback_function_abi,
69-
get_receive_function_abi,
7064
)
7165

7266
FUNCTION_ABI_NO_INPUTS = ABIFunction({"type": "function", "name": "myFunction"})
@@ -547,90 +541,6 @@ def test_get_abi_element_codec_override(contract_abi: ABI) -> None:
547541
assert function_abi == expected_abi
548542

549543

550-
@pytest.mark.parametrize(
551-
"abi,expected_abi",
552-
[
553-
([LOG_TWO_EVENTS_ABI, SET_VALUE_ABI, ABI_CONSTRUCTOR], ABI_CONSTRUCTOR),
554-
([ABI_CONSTRUCTOR], ABI_CONSTRUCTOR),
555-
],
556-
)
557-
def test_get_constructor_function_abi(abi: ABI, expected_abi: ABIElement) -> None:
558-
constructor_abi = get_constructor_function_abi(abi)
559-
assert constructor_abi == expected_abi
560-
561-
562-
@pytest.mark.parametrize(
563-
"abi,expected_abi",
564-
[
565-
([LOG_TWO_EVENTS_ABI, SET_VALUE_ABI, ABI_RECEIVE], ABI_RECEIVE),
566-
([ABI_RECEIVE], ABI_RECEIVE),
567-
],
568-
)
569-
def test_get_receive_function_abi(abi: ABI, expected_abi: ABIElement) -> None:
570-
receive_abi = get_receive_function_abi(abi)
571-
assert receive_abi == expected_abi
572-
573-
574-
@pytest.mark.parametrize(
575-
"contract_abi,expected_abi",
576-
[
577-
([LOG_TWO_EVENTS_ABI, SET_VALUE_ABI, ABI_FALLBACK], ABI_FALLBACK),
578-
([ABI_FALLBACK], ABI_FALLBACK),
579-
],
580-
)
581-
def test_get_fallback_function_abi(contract_abi: ABI, expected_abi: ABIElement) -> None:
582-
fallback_abi = get_fallback_function_abi(contract_abi)
583-
assert fallback_abi == expected_abi
584-
585-
586-
@pytest.mark.parametrize(
587-
"contract_abi,error_message",
588-
[
589-
(
590-
[LOG_TWO_EVENTS_ABI, SET_VALUE_ABI],
591-
"No {} function was found in the contract ABI.",
592-
),
593-
(
594-
[],
595-
"No {} function was found in the contract ABI.",
596-
),
597-
],
598-
)
599-
def test_get_function_abi_raises_if_none_found(
600-
contract_abi: ABI, error_message: str
601-
) -> None:
602-
with pytest.raises(ABIFallbackNotFound, match=error_message.format("fallback")):
603-
get_fallback_function_abi(contract_abi)
604-
605-
with pytest.raises(ABIReceiveNotFound, match=error_message.format("receive")):
606-
get_receive_function_abi(contract_abi)
607-
608-
with pytest.raises(
609-
ABIConstructorNotFound, match=error_message.format("constructor")
610-
):
611-
get_constructor_function_abi(contract_abi)
612-
613-
614-
def test_get_function_abi_raises_if_multiple_found() -> None:
615-
with pytest.raises(
616-
MismatchedABI,
617-
match="Multiple fallback functions found in the contract ABI.",
618-
):
619-
get_fallback_function_abi([ABI_FALLBACK, ABI_FALLBACK])
620-
621-
with pytest.raises(
622-
MismatchedABI,
623-
match="Multiple receive functions found in the contract ABI.",
624-
):
625-
get_receive_function_abi([ABI_RECEIVE, ABI_RECEIVE])
626-
627-
with pytest.raises(
628-
MismatchedABI,
629-
match="Multiple constructor functions found in the contract ABI.",
630-
):
631-
get_constructor_function_abi([ABI_CONSTRUCTOR, ABI_CONSTRUCTOR])
632-
633-
634544
@pytest.mark.parametrize(
635545
"topics,anonymous,expected_topics",
636546
[

web3/_utils/abi.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
ABIElement,
5353
ABIEvent,
5454
ABIFallback,
55+
ABIFunction,
5556
ABIReceive,
5657
HexStr,
5758
TypeStr,
@@ -723,7 +724,11 @@ def build_strict_registry() -> ABIRegistry:
723724

724725

725726
def named_tree(
726-
abi: Iterable[Union[ABIComponent, ABIComponentIndexed, Dict[TypeStr, Any]]],
727+
abi: Iterable[
728+
Union[
729+
ABIComponent, ABIComponentIndexed, ABIFunction, ABIEvent, Dict[TypeStr, Any]
730+
]
731+
],
727732
data: Iterable[Tuple[Any, ...]],
728733
) -> Dict[str, Any]:
729734
"""
@@ -736,10 +741,12 @@ def named_tree(
736741

737742

738743
def _named_subtree(
739-
abi: Union[ABIComponent, ABIComponentIndexed, Dict[TypeStr, Any]],
744+
abi: Union[
745+
ABIComponent, ABIComponentIndexed, ABIFunction, ABIEvent, Dict[TypeStr, Any]
746+
],
740747
data: Tuple[Any, ...],
741748
) -> Union[Dict[str, Any], Tuple[Any, ...], List[Any]]:
742-
abi_type = parse(collapse_if_tuple(abi))
749+
abi_type = parse(collapse_if_tuple(cast(Dict[str, Any], abi)))
743750

744751
if abi_type.is_array:
745752
item_type = abi_type.item_type.to_type_str()

web3/_utils/contracts.py

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
Any,
55
Callable,
66
Dict,
7+
List,
78
Optional,
89
Sequence,
910
Tuple,
10-
Type,
1111
Union,
1212
cast,
1313
)
@@ -85,9 +85,6 @@
8585
check_if_arguments_can_be_encoded,
8686
get_abi_element,
8787
get_abi_element_info,
88-
get_constructor_function_abi,
89-
get_fallback_function_abi,
90-
get_receive_function_abi,
9188
)
9289

9390
if TYPE_CHECKING:
@@ -102,7 +99,7 @@ def find_matching_event_abi(
10299
event_name: Optional[str] = None,
103100
argument_names: Optional[Sequence[str]] = None,
104101
) -> ABIEvent:
105-
filters = [
102+
filters: List[functools.partial[Sequence[ABIElement]]] = [
106103
functools.partial(filter_abi_by_type, "event"),
107104
]
108105

@@ -112,7 +109,7 @@ def find_matching_event_abi(
112109
if argument_names is not None:
113110
filters.append(functools.partial(filter_by_argument_name, argument_names))
114111

115-
event_abi_candidates = pipe(abi, *filters)
112+
event_abi_candidates: Sequence[ABIEvent] = pipe(abi, *filters)
116113

117114
if len(event_abi_candidates) == 1:
118115
return event_abi_candidates[0]
@@ -172,9 +169,9 @@ def encode_abi(
172169
def prepare_transaction(
173170
address: ChecksumAddress,
174171
w3: Union["AsyncWeb3", "Web3"],
175-
fn_identifier: Union[str, Type[FallbackFn], Type[ReceiveFn]],
172+
fn_identifier: FunctionIdentifier,
176173
contract_abi: Optional[ABI] = None,
177-
fn_abi: Optional[ABIElement] = None,
174+
abi_callable: Optional[ABICallable] = None,
178175
transaction: Optional[TxParams] = None,
179176
fn_args: Optional[Sequence[Any]] = None,
180177
fn_kwargs: Optional[Any] = None,
@@ -186,17 +183,15 @@ def prepare_transaction(
186183
"""
187184
fn_args = fn_args or []
188185
fn_kwargs = fn_kwargs or {}
189-
if fn_abi is None:
190-
fn_abi = get_abi_element(
191-
contract_abi, fn_identifier, *fn_args, abi_codec=w3.codec, **fn_kwargs
192-
)
193-
194-
if fn_abi["type"] == "error" or fn_abi["type"] == "event":
195-
raise Web3ValidationError(
196-
f"Cannot make a transaction with an `{fn_abi['type']}` ABI."
186+
if abi_callable is None:
187+
abi_callable = cast(
188+
ABICallable,
189+
get_abi_element(
190+
contract_abi, fn_identifier, *fn_args, abi_codec=w3.codec, **fn_kwargs
191+
),
197192
)
198193

199-
validate_payable(transaction, fn_abi)
194+
validate_payable(transaction, abi_callable)
200195

201196
if transaction is None:
202197
prepared_transaction: TxParams = {}
@@ -213,7 +208,7 @@ def prepare_transaction(
213208
w3,
214209
fn_identifier,
215210
contract_abi,
216-
fn_abi,
211+
abi_callable,
217212
fn_args,
218213
fn_kwargs,
219214
)
@@ -272,7 +267,7 @@ def get_constructor_function_info(
272267
contract_abi: Optional[ABI] = None, fn_abi: Optional[ABIConstructor] = None
273268
) -> Tuple[ABIConstructor, HexStr, Tuple[Any, ...]]:
274269
if fn_abi is None:
275-
fn_abi = get_constructor_function_abi(contract_abi)
270+
fn_abi = cast(ABIConstructor, get_abi_element(contract_abi, "constructor"))
276271
fn_selector = encode_hex(b"")
277272
fn_arguments: Tuple[Any, ...] = tuple()
278273
return fn_abi, fn_selector, fn_arguments
@@ -282,7 +277,7 @@ def get_fallback_function_info(
282277
contract_abi: Optional[ABI] = None, fn_abi: Optional[ABIFallback] = None
283278
) -> Tuple[ABIFallback, HexStr, Tuple[Any, ...]]:
284279
if fn_abi is None:
285-
fn_abi = get_fallback_function_abi(contract_abi)
280+
fn_abi = cast(ABIFallback, get_abi_element(contract_abi, "fallback"))
286281
fn_selector = encode_hex(b"")
287282
fn_arguments: Tuple[Any, ...] = tuple()
288283
return fn_abi, fn_selector, fn_arguments
@@ -292,13 +287,13 @@ def get_receive_function_info(
292287
contract_abi: Optional[ABI] = None, fn_abi: Optional[ABIReceive] = None
293288
) -> Tuple[ABIReceive, HexStr, Tuple[Any, ...]]:
294289
if fn_abi is None:
295-
fn_abi = get_receive_function_abi(contract_abi)
290+
fn_abi = cast(ABIReceive, get_abi_element(contract_abi, "receive"))
296291
fn_selector = encode_hex(b"")
297292
fn_arguments: Tuple[Any, ...] = tuple()
298293
return fn_abi, fn_selector, fn_arguments
299294

300295

301-
def validate_payable(transaction: TxParams, abi_element: ABICallable) -> None:
296+
def validate_payable(transaction: TxParams, abi_callable: ABICallable) -> None:
302297
"""
303298
Raise Web3ValidationError if non-zero ether
304299
is sent to a non-payable function.
@@ -307,10 +302,10 @@ def validate_payable(transaction: TxParams, abi_element: ABICallable) -> None:
307302
"value" in transaction
308303
and to_integer_if_hex(transaction["value"]) != 0
309304
and (
310-
"payable" in abi_element
311-
and not abi_element["payable"]
312-
or "stateMutability" in abi_element
313-
and abi_element["stateMutability"] == "nonpayable"
305+
"payable" in abi_callable
306+
and not abi_callable["payable"]
307+
or "stateMutability" in abi_callable
308+
and abi_callable["stateMutability"] == "nonpayable"
314309
)
315310
):
316311
raise Web3ValidationError(
@@ -378,3 +373,4 @@ async def async_parse_block_identifier_int(
378373
if block_num < 0:
379374
raise BlockNumberOutOfRange
380375
return BlockNumber(block_num)
376+
return BlockNumber(block_num)

web3/_utils/events.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ def get_event_data(
239239
log_topic_normalized_inputs = normalize_event_input_types(log_topics_abi)
240240
log_topic_types = get_event_abi_types_for_decoding(log_topic_normalized_inputs)
241241
log_topic_names = get_abi_input_names(
242-
ABIEvent({"type": "event", "inputs": log_topics_abi})
242+
ABIEvent({"name": event_abi["name"], "type": "event", "inputs": log_topics_abi})
243243
)
244244

245245
if len(log_topics_bytes) != len(log_topic_types):
@@ -252,7 +252,7 @@ def get_event_data(
252252
log_data_normalized_inputs = normalize_event_input_types(log_data_abi)
253253
log_data_types = get_event_abi_types_for_decoding(log_data_normalized_inputs)
254254
log_data_names = get_abi_input_names(
255-
ABIEvent({"type": "event", "inputs": log_data_abi})
255+
ABIEvent({"name": event_abi["name"], "type": "event", "inputs": log_data_abi})
256256
)
257257

258258
# sanity check that there are not name intersections between the topic

web3/contract/base_contract.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@
122122
check_if_arguments_can_be_encoded,
123123
get_abi_element,
124124
get_abi_element_info,
125-
get_constructor_function_abi,
126125
get_event_abi,
127126
)
128127

@@ -913,7 +912,7 @@ def _encode_constructor_data(
913912
cls, *args: Sequence[Any], **kwargs: Dict[str, Any]
914913
) -> HexStr:
915914
try:
916-
constructor_abi = get_constructor_function_abi(cls.abi)
915+
constructor_abi = get_abi_element(cls.abi, "constructor")
917916
except ABIConstructorNotFound:
918917
constructor_abi = None
919918

@@ -1097,8 +1096,8 @@ def __init__(
10971096
@combomethod
10981097
def _encode_data_in_transaction(self, *args: Any, **kwargs: Any) -> HexStr:
10991098
try:
1100-
constructor_abi = get_constructor_function_abi(self.abi)
1101-
except ABIConstructorNotFound:
1099+
constructor_abi = get_abi_element(self.abi, "constructor", *args, **kwargs)
1100+
except MismatchedABI:
11021101
constructor_abi = None
11031102

11041103
if constructor_abi:

0 commit comments

Comments
 (0)