From cf6cd3e9ec244c4c2f69b7e37b86de052d03e7fd Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Mon, 25 Sep 2023 15:10:42 -0700 Subject: [PATCH] Process unnamed arguments in JSON entries correctly --- docs/changelog.rst | 12 ++++++++++++ pons/_abi_types.py | 12 ++++++++++-- pons/_contract_abi.py | 5 +++++ tests/test_abi_types.py | 17 ++++++++++++++++- tests/test_contract_abi.py | 25 +++++++++++++++++++++++++ 5 files changed, 68 insertions(+), 3 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 816910a..7d3c23b 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -2,6 +2,18 @@ Changelog --------- +0.7.1 (unreleased) +~~~~~~~~~~~~~~~~~~ + +Fixed +^^^^^ + +- Process unnamed arguments in JSON entries correctly (as positional arguments). (PR_51_) + + +.. _PR_51: https://github.com/fjarri/pons/pull/51 + + 0.7.0 (09-07-2023) ~~~~~~~~~~~~~~~~~~ diff --git a/pons/_abi_types.py b/pons/_abi_types.py index 760df7a..9f66de2 100644 --- a/pons/_abi_types.py +++ b/pons/_abi_types.py @@ -511,9 +511,17 @@ def dispatch_type(abi_entry: Mapping[str, Any]) -> Type: return type_from_abi_string(element_type_name) -def dispatch_types(abi_entry: Iterable[Dict[str, Any]]) -> Dict[str, Type]: - # Since we are returning a dictionary, need to be sure we don't silently merge entries +def dispatch_types(abi_entry: Iterable[Dict[str, Any]]) -> Union[List[Type], Dict[str, Type]]: names = [entry["name"] for entry in abi_entry] + + # Unnamed arguments; treat as positional arguments + if names and all(not name for name in names): + return [dispatch_type(entry) for entry in abi_entry] + + if any(not name for name in names): + raise ValueError("Arguments must be either all named or all unnamed") + + # Since we are returning a dictionary, need to be sure we don't silently merge entries if len(names) != len(set(names)): raise ValueError("All ABI entries must have distinct names") return {entry["name"]: dispatch_type(entry) for entry in abi_entry} diff --git a/pons/_contract_abi.py b/pons/_contract_abi.py index 4f47e8e..c722cdd 100644 --- a/pons/_contract_abi.py +++ b/pons/_contract_abi.py @@ -436,6 +436,9 @@ def from_json(cls, event_entry: Dict[str, Any]) -> "Event": name = event_entry["name"] fields = dispatch_types(event_entry["inputs"]) + if isinstance(fields, list): + raise ValueError("Event fields must be named") + indexed = {input_["name"] for input_ in event_entry["inputs"] if input_["indexed"]} return cls(name=name, fields=fields, indexed=indexed, anonymous=event_entry["anonymous"]) @@ -530,6 +533,8 @@ def from_json(cls, error_entry: Dict[str, Any]) -> "Error": name = error_entry["name"] fields = dispatch_types(error_entry["inputs"]) + if isinstance(fields, list): + raise ValueError("Error fields must be named") return cls(name=name, fields=fields) diff --git a/tests/test_abi_types.py b/tests/test_abi_types.py index 9abb48a..8bcbd52 100644 --- a/tests/test_abi_types.py +++ b/tests/test_abi_types.py @@ -212,14 +212,29 @@ def test_dispatch_types(): dict(name="param2", type="uint8"), dict(name="param1", type="uint16[2]"), ] + + assert dispatch_types(entries) == dict(param2=abi.uint(8), param1=abi.uint(16)[2]) + # Check that the order is preserved, too assert list(dispatch_types(entries).items()) == [ ("param2", abi.uint(8)), ("param1", abi.uint(16)[2]), ] + # Note that if all the names are empty, it is treated as a list of positional arguments + assert dispatch_types([dict(name="", type="uint8"), dict(name="", type="uint16[2]")]) == [ + abi.uint(8), + abi.uint(16)[2], + ] + + # For an empty argument list we choose to resolve it as an empty dictionary, for certainty. + assert dispatch_types([]) == {} + + with pytest.raises(ValueError, match="Arguments must be either all named or all unnamed"): + dispatch_types([dict(name="foo", type="uint8"), dict(name="", type="uint16[2]")]) + with pytest.raises(ValueError, match="All ABI entries must have distinct names"): - dispatch_types([dict(name="", type="uint8"), dict(name="", type="uint16[2]")]) + dispatch_types([dict(name="foo", type="uint8"), dict(name="foo", type="uint16[2]")]) def test_making_arrays(): diff --git a/tests/test_contract_abi.py b/tests/test_contract_abi.py index af963c7..a891c01 100644 --- a/tests/test_contract_abi.py +++ b/tests/test_contract_abi.py @@ -600,6 +600,19 @@ def test_event_errors(): # This works Event("Foo", dict(a=uint8, b=uint8, c=uint8, d=uint8, e=uint8), indexed={"a", "b", "c"}) + with pytest.raises(ValueError, match="Event fields must be named"): + Event.from_json( + dict( + anonymous=True, + inputs=[ + dict(indexed=True, internalType="address", name="", type="address"), + dict(indexed=False, internalType="uint8", name="", type="uint8"), + ], + name="Foo", + type="event", + ) + ) + def test_error_from_json(): error = Error.from_json( @@ -622,6 +635,18 @@ def test_error_from_json(): ): Error.from_json(dict(type="constructor")) + with pytest.raises(ValueError, match="Error fields must be named"): + Error.from_json( + dict( + inputs=[ + dict(internalType="address", name="", type="address"), + dict(internalType="bytes", name="", type="bytes"), + ], + name="Foo", + type="error", + ) + ) + def test_error_init(): error = Error(