Skip to content

Commit 05c844e

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
1 parent 719e838 commit 05c844e

File tree

11 files changed

+134
-381
lines changed

11 files changed

+134
-381
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: 27 additions & 83 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
)
@@ -21,12 +21,9 @@
2121
from eth_typing import (
2222
ABI,
2323
ABICallable,
24-
ABIConstructor,
2524
ABIElement,
2625
ABIEvent,
27-
ABIFallback,
2826
ABIFunction,
29-
ABIReceive,
3027
ChecksumAddress,
3128
HexStr,
3229
TypeStr,
@@ -56,10 +53,6 @@
5653
from web3._utils.encoding import (
5754
to_hex,
5855
)
59-
from web3._utils.function_identifiers import (
60-
FallbackFn,
61-
ReceiveFn,
62-
)
6356
from web3._utils.method_formatters import (
6457
to_integer_if_hex,
6558
)
@@ -85,9 +78,6 @@
8578
check_if_arguments_can_be_encoded,
8679
get_abi_element,
8780
get_abi_element_info,
88-
get_constructor_function_abi,
89-
get_fallback_function_abi,
90-
get_receive_function_abi,
9181
)
9282

9383
if TYPE_CHECKING:
@@ -102,7 +92,7 @@ def find_matching_event_abi(
10292
event_name: Optional[str] = None,
10393
argument_names: Optional[Sequence[str]] = None,
10494
) -> ABIEvent:
105-
filters = [
95+
filters: List[functools.partial[Sequence[ABIElement]]] = [
10696
functools.partial(filter_abi_by_type, "event"),
10797
]
10898

@@ -112,7 +102,7 @@ def find_matching_event_abi(
112102
if argument_names is not None:
113103
filters.append(functools.partial(filter_by_argument_name, argument_names))
114104

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

117107
if len(event_abi_candidates) == 1:
118108
return event_abi_candidates[0]
@@ -172,9 +162,9 @@ def encode_abi(
172162
def prepare_transaction(
173163
address: ChecksumAddress,
174164
w3: Union["AsyncWeb3", "Web3"],
175-
fn_identifier: Union[str, Type[FallbackFn], Type[ReceiveFn]],
165+
fn_identifier: FunctionIdentifier,
176166
contract_abi: Optional[ABI] = None,
177-
fn_abi: Optional[ABIElement] = None,
167+
abi_callable: Optional[ABICallable] = None,
178168
transaction: Optional[TxParams] = None,
179169
fn_args: Optional[Sequence[Any]] = None,
180170
fn_kwargs: Optional[Any] = None,
@@ -186,17 +176,15 @@ def prepare_transaction(
186176
"""
187177
fn_args = fn_args or []
188178
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."
179+
if abi_callable is None:
180+
abi_callable = cast(
181+
ABICallable,
182+
get_abi_element(
183+
contract_abi, fn_identifier, *fn_args, abi_codec=w3.codec, **fn_kwargs
184+
),
197185
)
198186

199-
validate_payable(transaction, fn_abi)
187+
validate_payable(transaction, abi_callable)
200188

201189
if transaction is None:
202190
prepared_transaction: TxParams = {}
@@ -213,7 +201,6 @@ def prepare_transaction(
213201
w3,
214202
fn_identifier,
215203
contract_abi,
216-
fn_abi,
217204
fn_args,
218205
fn_kwargs,
219206
)
@@ -224,32 +211,19 @@ def encode_transaction_data(
224211
w3: Union["AsyncWeb3", "Web3"],
225212
fn_identifier: FunctionIdentifier,
226213
contract_abi: Optional[ABI] = None,
227-
fn_abi: Optional[ABICallable] = None,
228214
args: Optional[Sequence[Any]] = None,
229215
kwargs: Optional[Any] = None,
230216
) -> HexStr:
231-
info_abi: ABIElement
232-
if fn_identifier is FallbackFn:
233-
info_abi, info_selector, info_arguments = get_fallback_function_info(
234-
contract_abi, cast(ABIFallback, fn_abi)
235-
)
236-
elif fn_identifier is ReceiveFn:
237-
info_abi, info_selector, info_arguments = get_receive_function_info(
238-
contract_abi, cast(ABIReceive, fn_abi)
239-
)
240-
elif isinstance(fn_identifier, str):
241-
fn_info = get_abi_element_info(
242-
contract_abi,
243-
fn_identifier,
244-
*args,
245-
abi_codec=w3.codec,
246-
**kwargs,
247-
)
248-
info_abi = fn_info["abi"]
249-
info_selector = fn_info["selector"]
250-
info_arguments = fn_info["arguments"]
251-
else:
252-
raise Web3TypeError("Unsupported function identifier")
217+
fn_info = get_abi_element_info(
218+
contract_abi,
219+
fn_identifier,
220+
*args,
221+
abi_codec=w3.codec,
222+
**kwargs,
223+
)
224+
info_abi = fn_info["abi"]
225+
info_selector = fn_info["selector"]
226+
info_arguments = fn_info["arguments"]
253227

254228
return add_0x_prefix(encode_abi(w3, info_abi, info_arguments, info_selector))
255229

@@ -268,37 +242,7 @@ def decode_transaction_data(
268242
return named_tree(fn_abi["inputs"], decoded)
269243

270244

271-
def get_constructor_function_info(
272-
contract_abi: Optional[ABI] = None, fn_abi: Optional[ABIConstructor] = None
273-
) -> Tuple[ABIConstructor, HexStr, Tuple[Any, ...]]:
274-
if fn_abi is None:
275-
fn_abi = get_constructor_function_abi(contract_abi)
276-
fn_selector = encode_hex(b"")
277-
fn_arguments: Tuple[Any, ...] = tuple()
278-
return fn_abi, fn_selector, fn_arguments
279-
280-
281-
def get_fallback_function_info(
282-
contract_abi: Optional[ABI] = None, fn_abi: Optional[ABIFallback] = None
283-
) -> Tuple[ABIFallback, HexStr, Tuple[Any, ...]]:
284-
if fn_abi is None:
285-
fn_abi = get_fallback_function_abi(contract_abi)
286-
fn_selector = encode_hex(b"")
287-
fn_arguments: Tuple[Any, ...] = tuple()
288-
return fn_abi, fn_selector, fn_arguments
289-
290-
291-
def get_receive_function_info(
292-
contract_abi: Optional[ABI] = None, fn_abi: Optional[ABIReceive] = None
293-
) -> Tuple[ABIReceive, HexStr, Tuple[Any, ...]]:
294-
if fn_abi is None:
295-
fn_abi = get_receive_function_abi(contract_abi)
296-
fn_selector = encode_hex(b"")
297-
fn_arguments: Tuple[Any, ...] = tuple()
298-
return fn_abi, fn_selector, fn_arguments
299-
300-
301-
def validate_payable(transaction: TxParams, abi_element: ABICallable) -> None:
245+
def validate_payable(transaction: TxParams, abi_callable: ABICallable) -> None:
302246
"""
303247
Raise Web3ValidationError if non-zero ether
304248
is sent to a non-payable function.
@@ -307,10 +251,10 @@ def validate_payable(transaction: TxParams, abi_element: ABICallable) -> None:
307251
"value" in transaction
308252
and to_integer_if_hex(transaction["value"]) != 0
309253
and (
310-
"payable" in abi_element
311-
and not abi_element["payable"]
312-
or "stateMutability" in abi_element
313-
and abi_element["stateMutability"] == "nonpayable"
254+
"payable" in abi_callable
255+
and not abi_callable["payable"]
256+
or "stateMutability" in abi_callable
257+
and abi_callable["stateMutability"] == "nonpayable"
314258
)
315259
):
316260
raise Web3ValidationError(

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

0 commit comments

Comments
 (0)