What changed, and why it matters
This commit is a simple rename from 'Struct' to 'Tuple' in the Ethereum clear-signing code. It changes only variable, class, and type alias names to better reflect that the code accesses fields by index. No behavior, logic, or security properties are changed.
No security action required. This is a non-functional refactoring change.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch renames the ABIValue subclass Struct to Tuple, along with related type aliases (StructValue → TupleValue) and all references in definitions and tests. The docstring is updated to note that tuples and structs are the same at the ABI level. There are no functional changes to parsing, bounds checking, or display logic.
Changed components
core/src/apps/ethereum/clear_signing.pycore/src/apps/ethereum/clear_signing_definitions.pycore/tests/test_apps.ethereum.clear_signing.pyInspect captured patch +30 / −30
diff --git a/core/src/apps/ethereum/clear_signing.py b/core/src/apps/ethereum/clear_signing.py
index 1dff3319..db1964b6 100644
--- a/core/src/apps/ethereum/clear_signing.py
+++ b/core/src/apps/ethereum/clear_signing.py
@@ -20,9 +20,9 @@ if TYPE_CHECKING:
# Represents values that have been parsed from the calldata
# into our internal representation.
Value = int | bytes | bool | str | None | list["Value"]
- StructValue = tuple[Value, ...]
- ListValue = list[StructValue]
- AnyValue = Value | StructValue | ListValue | list[Value | StructValue | ListValue]
+ TupleValue = tuple[Value, ...]
+ ListValue = list[TupleValue]
+ AnyValue = Value | TupleValue | ListValue | list[Value | TupleValue | ListValue]
Path = tuple[int | tuple[int] | tuple[int, int], ...] | int
PathWalker = Callable[[Path], Value]
@@ -346,19 +346,19 @@ class Dynamic(ABIValue):
return self.parser(data), 32
-class Struct(ABIValue):
- """Structs (or Tuples, which are essentially the same thing as far as ABI is concerned)
+class Tuple(ABIValue):
+ """Tuples (or Structs, which are essentially the same thing as far as ABI is concerned)
contain multiple values of different types.
- A Struct is "dynamic" if at least one of the values is dynamic.
+ A Tuple is "dynamic" if at least one of the values is dynamic.
However, dynamic structs inside arrays behave as static structs,
- hence we cannot guess if the Struct is dynamic by looking at just its fields."""
+ hence we cannot guess if the Tuple is dynamic by looking at just its fields."""
def __init__(self, fields: tuple[Parser, ...], is_dynamic: bool) -> None:
self.fields = fields
self.is_dynamic = is_dynamic
self.static_size = len(fields) * 32
- def parse(self, raw_data: memoryview, offset: int) -> tuple[StructValue, int]:
+ def parse(self, raw_data: memoryview, offset: int) -> tuple[TupleValue, int]:
if not self.is_dynamic:
base_offset = offset
consumed = self.static_size
@@ -380,7 +380,7 @@ class Struct(ABIValue):
if parser not in DYNAMIC_DATA_PARSERS:
v = parser(raw_field)
if isinstance(v, (tuple, list)):
- # Struct or Array inside a Struct
+ # Tuple or Array inside a Tuple
raise NotImplementedError
value[i] = v
else:
@@ -396,7 +396,7 @@ class Struct(ABIValue):
raw_field = raw_data[field_pointer + 32 : field_pointer + 32 + length]
v = parser(raw_field)
if isinstance(v, (tuple, list)):
- # Struct or Array inside a Struct
+ # Tuple or Array inside a Tuple
raise NotImplementedError
value[i] = v
return tuple(value), consumed
@@ -536,7 +536,7 @@ class DisplayFormat:
p = None
break
if isinstance(p, (list, tuple, bytes)):
- # walk inside Arrays or Structs
+ # walk inside Arrays or Tuples
try:
if isinstance(step, int):
p = p[step]
@@ -555,7 +555,7 @@ class DisplayFormat:
raise InvalidFormatDefinition
if isinstance(p, (list, tuple)):
# at the end of the path, we must have arrived somewhere
- # ie. not on an Array or Struct
+ # ie. not on an Array or Tuple
raise InvalidFormatDefinition
return p
diff --git a/core/src/apps/ethereum/clear_signing_definitions.py b/core/src/apps/ethereum/clear_signing_definitions.py
index 3abb8b78..7a8a95af 100644
--- a/core/src/apps/ethereum/clear_signing_definitions.py
+++ b/core/src/apps/ethereum/clear_signing_definitions.py
@@ -13,8 +13,8 @@ from .clear_signing import (
DisplayFormat,
Dynamic,
FieldDefinition,
- Struct,
TokenAmountFormatter,
+ Tuple,
UnitFormatter,
parse_address,
parse_bool,
@@ -142,7 +142,7 @@ ALL_DISPLAY_FORMATS.extend(
Atomic(parse_address), # _receiver
Atomic(parse_uint256), # _minAmountOut
Array(
- Struct(
+ Tuple(
(
parse_address, # callTo
parse_address, # approveTo
@@ -191,7 +191,7 @@ ALL_DISPLAY_FORMATS.extend(
Atomic(parse_address), # _receiver
Atomic(parse_uint256), # _minAmountOut
Array(
- Struct(
+ Tuple(
(
parse_address, # callTo
parse_address, # approveTo
@@ -238,7 +238,7 @@ ALL_DISPLAY_FORMATS.extend(
Atomic(parse_address), # _receiver
Atomic(parse_uint256), # _minAmountOut
Array(
- Struct(
+ Tuple(
(
parse_address, # callTo
parse_address, # approveTo
@@ -284,7 +284,7 @@ ALL_DISPLAY_FORMATS.extend(
Dynamic(parse_string), # _referrer
Atomic(parse_address), # _receiver
Atomic(parse_uint256), # _minAmountOut
- Struct(
+ Tuple(
(
parse_address, # callTo
parse_address, # approveTo
@@ -329,7 +329,7 @@ ALL_DISPLAY_FORMATS.extend(
Dynamic(parse_string), # _referrer
Atomic(parse_address), # _receiver
Atomic(parse_uint256), # _minAmountOut
- Struct(
+ Tuple(
(
parse_address, # callTo
parse_address, # approveTo
@@ -374,7 +374,7 @@ ALL_DISPLAY_FORMATS.extend(
Dynamic(parse_string), # _referrer
Atomic(parse_address), # _receiver
Atomic(parse_uint256), # _minAmountOut
- Struct(
+ Tuple(
(
parse_address, # callTo
parse_address, # approveTo
@@ -420,7 +420,7 @@ ALL_DISPLAY_FORMATS.extend(
Atomic(parse_address), # _receiver
Atomic(parse_uint256), # _minAmount
Array(
- Struct(
+ Tuple(
(
parse_address, # callTo
parse_address, # approveTo
@@ -482,7 +482,7 @@ ALL_DISPLAY_FORMATS.extend(
func_sig=unhexlify("b858183f"), # exactInput(tuple params)
intent="Swap",
parameter_definitions=[
- Struct(
+ Tuple(
(
parse_bytes, # path
parse_address, # recipient
@@ -519,7 +519,7 @@ ALL_DISPLAY_FORMATS.extend(
func_sig=unhexlify("04e45aaf"), # exactInputSingle(tuple params)
intent="Swap",
parameter_definitions=[
- Struct(
+ Tuple(
(
parse_address, # tokenIn
parse_address, # tokenOut
@@ -564,7 +564,7 @@ ALL_DISPLAY_FORMATS.extend(
func_sig=unhexlify("09b81346"), # exactOutput(tuple params)
intent="Swap",
parameter_definitions=[
- Struct(
+ Tuple(
(
parse_bytes, # path
parse_address, # recipient
@@ -601,7 +601,7 @@ ALL_DISPLAY_FORMATS.extend(
func_sig=unhexlify("5023b4df"), # exactOutputSingle(tuple params)
intent="Swap",
parameter_definitions=[
- Struct(
+ Tuple(
(
parse_address, # tokenIn
parse_address, # tokenOut
diff --git a/core/tests/test_apps.ethereum.clear_signing.py b/core/tests/test_apps.ethereum.clear_signing.py
index 0f7a9393..44a667a0 100644
--- a/core/tests/test_apps.ethereum.clear_signing.py
+++ b/core/tests/test_apps.ethereum.clear_signing.py
@@ -12,7 +12,7 @@ if not utils.BITCOIN_ONLY:
Atomic,
DirtyAddress,
OutOfBounds,
- Struct,
+ Tuple,
ValueOverflow,
parse_address,
parse_bool,
@@ -127,7 +127,7 @@ class TestEthereumClearSigning(unittest.TestCase):
self.assertEqual(consumed, 32)
def test_static_struct_valid(self):
- static_struct = Struct(
+ static_struct = Tuple(
(parse_address, parse_uint160, parse_bool),
is_dynamic=False,
)
@@ -148,7 +148,7 @@ class TestEthereumClearSigning(unittest.TestCase):
self.assertEqual(consumed, 96)
def test_static_struct_uint160_overflow(self):
- static_struct = Struct(
+ static_struct = Tuple(
(parse_address, parse_uint160, parse_bool),
is_dynamic=False,
)
@@ -169,7 +169,7 @@ class TestEthereumClearSigning(unittest.TestCase):
static_struct.parse(data, len(FIVE_RANDOM_BYTES))
def test_dynamic_struct_valid(self):
- dynamic_struct = Struct((parse_address, parse_string), is_dynamic=True)
+ dynamic_struct = Tuple((parse_address, parse_string), is_dynamic=True)
addr = unhexlify("d8da6bf26964af9d7eed9e03e53415d37aa96045")
# left padded with zeroes
@@ -205,7 +205,7 @@ class TestEthereumClearSigning(unittest.TestCase):
self.assertEqual(consumed, 32) # only the initial pointer is consumed
def test_dynamic_struct_out_of_bounds(self):
- dynamic_struct = Struct((parse_uint256,), is_dynamic=True)
+ dynamic_struct = Tuple((parse_uint256,), is_dynamic=True)
# pointer says struct starts at byte 1000, but data is only 32 bytes long
payload = to_bytes(1000)
@@ -263,7 +263,7 @@ class TestEthereumClearSigning(unittest.TestCase):
def test_array_of_dynamic_structs(self):
array_parser = Array(
- Struct(
+ Tuple(
(parse_address, parse_string),
# Note: dynamic structs that sit inside arrays behave as static structs
is_dynamic=False,
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.