Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 21 additions & 1 deletion src/erc7730/convert/calldata/v1/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,26 @@
from erc7730.model.resolved.context import ResolvedDeployment
from erc7730.model.types import Id, Selector

# A bool holds 0 or 1 in calldata, and an enum key is the field value it matches, so these are
# the keys a descriptor writes for a bool field. JSON has no bool object key, so they arrive as
# text, and the casing follows whoever wrote the descriptor rather than any rule.
BOOLEAN_ORDINALS = {"true": 1, "false": 0}


def enum_ordinal(ordinal: str) -> int:
"""Value the device compares an enum entry against.

:param ordinal: enum key, as written in the descriptor
:return: the numeric field value it stands for
:raises ValueError: if the key is neither decimal nor a boolean literal
"""
try:
return int(ordinal)
except ValueError:
if (value := BOOLEAN_ORDINALS.get(ordinal.strip().lower())) is not None:
return value
raise


def convert_enums(
deployment: ResolvedDeployment, selector: Selector, enums: dict[Id, EnumDefinition] | None
Expand All @@ -29,7 +49,7 @@ def convert_enums(
selector=selector,
id=i,
enum_id=enum_id,
value=int(ordinal),
value=enum_ordinal(ordinal),
name=name,
)
for i, (enum_id, enum) in enumerate(enums.items())
Expand Down
40 changes: 40 additions & 0 deletions tests/convert/calldata/v1/test_enum_ordinal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import pytest

from erc7730.convert.calldata.v1.enum import enum_ordinal


@pytest.mark.parametrize(
"ordinal,expected",
[
pytest.param("0", 0, id="zero"),
pytest.param("1", 1, id="one"),
pytest.param("42", 42, id="larger"),
pytest.param("-1", -1, id="negative"),
],
)
def test_a_decimal_key_is_its_own_value(ordinal: str, expected: int) -> None:
assert enum_ordinal(ordinal) == expected


@pytest.mark.parametrize(
"ordinal,expected",
[
pytest.param("false", 0, id="false"),
pytest.param("true", 1, id="true"),
pytest.param("False", 0, id="False"),
pytest.param("True", 1, id="True"),
pytest.param("FALSE", 0, id="FALSE"),
pytest.param("TRUE", 1, id="TRUE"),
pytest.param(" true ", 1, id="padded"),
],
)
def test_a_boolean_key_is_the_value_the_bool_holds_in_calldata(ordinal: str, expected: int) -> None:
"""A bool holds 0 or 1, and the casing follows whoever wrote the descriptor."""
assert enum_ordinal(ordinal) == expected


@pytest.mark.parametrize("ordinal", ["maybe", "", "0x1", "1.0", "yes"])
def test_a_key_that_is_neither_still_raises(ordinal: str) -> None:
"""Silently mapping an unrecognised key to a number would build an entry that never matches."""
with pytest.raises(ValueError):
enum_ordinal(ordinal)
Loading