feat(tests,ethereum): definition request
What changed, and why it matters
This commit adds a new feature to Trezor's Ethereum signing flow: the device can now ask the host computer mid-transaction for extra token or display-format definitions. The main production code change is small and appears to fix a token-resolution bug in clear signing by preferring a token returned from the parsed field data over the one looked up from the static definitions. Most of the diff is new and updated test code exercising the new request/response mechanism and renaming helpers from 'erc7730' to the more generic 'display format'.
Review the token-resolution change in clear_signing.py for correctness, ensure that accepting host-supplied definitions does not bypass existing validation, and confirm that fallback to blind signing on invalid/missing definitions is intentional and safe. No immediate security patch appears required from this commit alone.
Security signals we found
Feature addition for runtime definition fetching
Token resolution fallback change in clear signing
New test coverage for malformed/missing definition responses
No explicit security claim in commit message or diff
Evidence from the diff
The commit implements EthereumDefinitionRequest handling for Ethereum transactions. In clear_signing.py, _handle_approve and _handle_transfer now use actual_token from the parsed field tuple when available, falling back to defs.get_token(address_bytes). This ensures token metadata discovered during ABI decoding is respected. The protobuf comments are updated to clarify that token_address may also be a target contract address when func_sig is set, and that definitions may be omitted in EthereumDefinitionAck. The bulk of the change is test infrastructure: renaming helpers, adding supports_definition_request flags, and introducing test_definitions_request.py to verify the device requests display formats and token definitions correctly, handles missing/invalid definitions by falling back to blind signing, and does not send requests when the host does not advertise support.
Changed components
core/src/apps/ethereum/clear_signing.pycommon/protob/messages-ethereum.prototests/definitions.pytests/device_tests/ethereum/test_definitions.pytests/device_tests/ethereum/test_definitions_bad.pytests/device_tests/ethereum/test_definitions_request.pyInspect captured patch +385 / −32
diff --git a/common/protob/messages-ethereum.proto b/common/protob/messages-ethereum.proto
index bee59315..b46892a8 100644
--- a/common/protob/messages-ethereum.proto
+++ b/common/protob/messages-ethereum.proto
@@ -133,8 +133,10 @@ message EthereumTxAck {
}
/**
- * Structure representing a request for network / token definitions
- * and optionally also for an ERC-7730 display format.
+ * Structure representing a request for extra Ethereum definitions.
+ * `token_address` carries the token address for token-definition lookups, or
+ * the target contract address when `func_sig` is set and a display format is
+ * being requested.
* @next EthereumDefinitionAck
*/
message EthereumDefinitionRequest {
@@ -145,6 +147,7 @@ message EthereumDefinitionRequest {
/**
* Request: Definitions payload data.
+ * `definitions` can be missing if the host did not find matching definitions.
* @next EthereumTxRequest
* @next EthereumDefinitionRequest
*/
diff --git a/core/src/apps/ethereum/clear_signing.py b/core/src/apps/ethereum/clear_signing.py
index 04bd79d9..46e3fe28 100644
--- a/core/src/apps/ethereum/clear_signing.py
+++ b/core/src/apps/ethereum/clear_signing.py
@@ -900,7 +900,7 @@ async def _handle_approve(
assert isinstance(recipient_addr, str)
arg1_raw_value = args[1]
- (field1_name, value, _), _, _ = fields[1]
+ (field1_name, value, _), actual_token, _ = fields[1]
assert field1_name == "Amount"
assert isinstance(arg1_raw_value, int)
@@ -921,7 +921,7 @@ async def _handle_approve(
fee_items,
msg.chain_id,
defs.network,
- defs.get_token(address_bytes),
+ actual_token or defs.get_token(address_bytes),
address_bytes,
is_revoke,
bool(msg.chunkify),
@@ -951,7 +951,7 @@ async def _handle_transfer(
arg1_raw_value = args[1]
assert isinstance(arg1_raw_value, int)
- (arg1_name, value, _), _, _ = fields[1]
+ (arg1_name, value, _), actual_token, _ = fields[1]
assert arg1_name == "Amount"
assert isinstance(value, str)
@@ -970,7 +970,7 @@ async def _handle_transfer(
fee_items,
msg.chain_id,
defs.network,
- defs.get_token(address_bytes),
+ actual_token or defs.get_token(address_bytes),
address_from_bytes(address_bytes, defs.network),
)
else:
@@ -981,7 +981,7 @@ async def _handle_transfer(
msg.address_n,
maximum_fee,
fee_items,
- defs.get_token(address_bytes),
+ actual_token or defs.get_token(address_bytes),
is_send=True,
chunkify=bool(msg.chunkify),
)
diff --git a/tests/definitions.py b/tests/definitions.py
index 34ba6c74..a18996cd 100644
--- a/tests/definitions.py
+++ b/tests/definitions.py
@@ -160,7 +160,7 @@ def encode_solana_token(
return payload + proof + signature
-def make_eth_erc7730_display_format(
+def make_eth_display_format(
chain_id: int = 0,
address: t.AnyStr = b"",
func_sig: bytes = b"",
@@ -184,7 +184,7 @@ def make_eth_erc7730_display_format(
)
-def encode_eth_erc7730_display_format(
+def encode_eth_display_format(
display_format: messages.EthereumDisplayFormatInfo,
) -> bytes:
payload = make_payload(
diff --git a/tests/device_tests/ethereum/test_definitions.py b/tests/device_tests/ethereum/test_definitions.py
index fcb3adf9..abc69b1c 100644
--- a/tests/device_tests/ethereum/test_definitions.py
+++ b/tests/device_tests/ethereum/test_definitions.py
@@ -335,6 +335,7 @@ UNISWAP_WETH_WETH2_CALLDATA = bytes.fromhex(
def get_clear_signing_sign_tx_params(
data: bytes = UNISWAP_EXACT_INPUT_SINGLE_CALLDATA,
+ supports_definition_request: bool = False,
) -> dict:
return dict(
n=parse_path("m/44h/60h/0h/0/1"),
@@ -345,12 +346,31 @@ def get_clear_signing_sign_tx_params(
value=0x0,
data=data,
chain_id=1,
+ supports_definition_request=supports_definition_request,
+ )
+
+
+def get_clear_signing_sign_tx_eip1559_params(
+ data: bytes = UNISWAP_EXACT_INPUT_SINGLE_CALLDATA,
+ supports_definition_request: bool = False,
+) -> dict:
+ return dict(
+ n=parse_path("m/44h/60h/0h/0/1"),
+ nonce=0x0,
+ max_gas_fee=0x14,
+ gas_limit=0x14,
+ max_priority_fee=0x14,
+ to=UNISWAP_V3_ROUTER2,
+ value=0x0,
+ data=data,
+ chain_id=1,
+ supports_definition_request=supports_definition_request,
)
# ERC-7730 display format for Uniswap V3 `exactInputSingle`.
# `exactInputSingle` takes one struct parameter with 7 fields.
-UNISWAP_EXACT_INPUT_SINGLE_DISPLAY_FORMAT = definitions.make_eth_erc7730_display_format(
+UNISWAP_EXACT_INPUT_SINGLE_DISPLAY_FORMAT = definitions.make_eth_display_format(
chain_id=1,
address=UNISWAP_V3_ROUTER2,
func_sig=FUNC_SIG_FAKE,
@@ -446,7 +466,7 @@ def _sign_tx_with_display_format(
)
-def _make_label_checker(
+def make_label_checker(
expected: set[str] | None = None,
absent: set[str] | None = None,
) -> tuple[Callable, Callable[[], None]]:
@@ -473,7 +493,7 @@ def _make_label_checker(
@pytest.mark.models("core")
def test_clear_signing_with_definition_and_token(session: Session) -> None:
# With WETH provided, TokenAmountFormatter can resolve the token symbol.
- on_page, assert_all_seen = _make_label_checker(
+ on_page, assert_all_seen = make_label_checker(
expected=UNISWAP_EXACT_INPUT_SINGLE_DISPLAY_FORMAT_LABELS | {"FAKE WETH"},
absent={"UNKN"},
)
@@ -491,7 +511,7 @@ def test_clear_signing_builtin_token_no_override(session: Session) -> None:
# With USDT (tokenOut) provided as encoded_token.
# Note however that we still render it as "USDT" (built in token)
# rather than "FAKE USDT"!
- on_page, assert_all_seen = _make_label_checker(
+ on_page, assert_all_seen = make_label_checker(
expected=UNISWAP_EXACT_INPUT_SINGLE_DISPLAY_FORMAT_LABELS | {"UNKN"},
absent={"FAKE USDT"},
)
@@ -507,7 +527,7 @@ def test_clear_signing_builtin_token_no_override(session: Session) -> None:
@pytest.mark.models("core")
def test_clear_signing_without_token(session: Session) -> None:
# Without token definitions, amounts are shown as UNKNOWN tokens.
- on_page, assert_all_seen = _make_label_checker(
+ on_page, assert_all_seen = make_label_checker(
expected=(UNISWAP_EXACT_INPUT_SINGLE_DISPLAY_FORMAT_LABELS | {"UNKN"}),
absent={"WETH", "USDT"},
)
@@ -524,7 +544,7 @@ def test_clear_signing_without_token(session: Session) -> None:
def test_clear_signing_with_definition_without_token(session: Session) -> None:
# Without a token definition, amounts are shown as UNKNOWN token
# (but builtin USDT is resolved).
- on_page, assert_all_seen = _make_label_checker(
+ on_page, assert_all_seen = make_label_checker(
expected=(UNISWAP_EXACT_INPUT_SINGLE_DISPLAY_FORMAT_LABELS | {"UNKN", "USDT"}),
absent={"WETH"},
)
@@ -554,7 +574,7 @@ def test_clear_signing_with_mismatched_definition(session: Session) -> None:
],
is_dynamic=False,
)
- bad_display_format = definitions.make_eth_erc7730_display_format(
+ bad_display_format = definitions.make_eth_display_format(
chain_id=UNISWAP_EXACT_INPUT_SINGLE_DISPLAY_FORMAT.chain_id,
address=UNISWAP_EXACT_INPUT_SINGLE_DISPLAY_FORMAT.address,
func_sig=UNISWAP_EXACT_INPUT_SINGLE_DISPLAY_FORMAT.func_sig,
@@ -564,7 +584,7 @@ def test_clear_signing_with_mismatched_definition(session: Session) -> None:
UNISWAP_EXACT_INPUT_SINGLE_DISPLAY_FORMAT.field_definitions
),
)
- on_page, assert_all_seen = _make_label_checker(
+ on_page, assert_all_seen = make_label_checker(
absent=(
UNISWAP_EXACT_INPUT_SINGLE_DISPLAY_FORMAT_LABELS | {"UNKN", "WETH", "USDT"}
)
diff --git a/tests/device_tests/ethereum/test_definitions_bad.py b/tests/device_tests/ethereum/test_definitions_bad.py
index 79d8533e..9e0f5ed9 100644
--- a/tests/device_tests/ethereum/test_definitions_bad.py
+++ b/tests/device_tests/ethereum/test_definitions_bad.py
@@ -11,7 +11,7 @@ from trezorlib.messages import DefinitionType
from trezorlib.tools import parse_path
from ...definitions import (
- make_eth_erc7730_display_format,
+ make_eth_display_format,
make_eth_network,
make_eth_token,
make_payload,
@@ -47,19 +47,44 @@ def _fails_token(session: Session, token: bytes, match: str) -> None:
)
-def _fails_erc7730_display_format(
- session: Session, erc7730_display_format: bytes, match: str
-) -> None:
+def _fails_display_format(session: Session, display_format: bytes, match: str) -> None:
with pytest.raises(TrezorFailure, match=match):
ethereum.sign_tx(
session,
**get_clear_signing_sign_tx_params(),
definitions=messages.EthereumDefinitions(
- encoded_erc7730_display_format=erc7730_display_format,
+ encoded_display_format=display_format,
),
)
+def _fails_display_format_via_request(
+ session: Session, display_format: bytes, match: str
+) -> None:
+ calls: list[messages.EthereumDefinitionRequest] = []
+
+ def provider(
+ req: messages.EthereumDefinitionRequest,
+ ) -> messages.EthereumDefinitionAck:
+ calls.append(req)
+ return messages.EthereumDefinitionAck(
+ definitions=messages.EthereumDefinitions(
+ encoded_display_format=display_format,
+ )
+ )
+
+ with pytest.raises(TrezorFailure, match=match):
+ ethereum.sign_tx(
+ session,
+ **get_clear_signing_sign_tx_params(supports_definition_request=True),
+ definition_provider=provider,
+ )
+
+ # Firmware requests the display format once then fails validation. No token requests follow.
+ assert len(calls) == 1
+ assert calls[0].func_sig is not None
+
+
def _make_token_payload(
timestamp: int = 0xFFFF_FFFF,
message: messages.EthereumTokenInfo | bytes = make_eth_token(),
@@ -76,7 +101,7 @@ def _make_display_format_payload(
message: messages.EthereumDisplayFormatInfo | bytes | None = None,
) -> bytes:
if message is None:
- message = make_eth_erc7730_display_format()
+ message = make_eth_display_format()
return make_payload(
data_type=DefinitionType.ETHEREUM_DISPLAY_FORMAT,
message=message,
@@ -90,7 +115,8 @@ def _cases(session: Session) -> list[tuple]:
(_make_token_payload, _fails_token),
]
if session.model in models.CORE_MODELS:
- cases.append((_make_display_format_payload, _fails_erc7730_display_format))
+ cases.append((_make_display_format_payload, _fails_display_format))
+ cases.append((_make_display_format_payload, _fails_display_format_via_request))
return cases
@@ -170,23 +196,33 @@ def test_bad_type(session: Session) -> None:
cases += [
(
DefinitionType.ETHEREUM_DISPLAY_FORMAT,
- make_eth_erc7730_display_format(),
+ make_eth_display_format(),
_fails_network,
),
(
DefinitionType.ETHEREUM_DISPLAY_FORMAT,
- make_eth_erc7730_display_format(),
+ make_eth_display_format(),
_fails_token,
),
(
DefinitionType.ETHEREUM_TOKEN,
make_eth_token(),
- _fails_erc7730_display_format,
+ _fails_display_format,
),
(
DefinitionType.ETHEREUM_NETWORK,
make_eth_network(),
- _fails_erc7730_display_format,
+ _fails_display_format,
+ ),
+ (
+ DefinitionType.ETHEREUM_TOKEN,
+ make_eth_token(),
+ _fails_display_format_via_request,
+ ),
+ (
+ DefinitionType.ETHEREUM_NETWORK,
+ make_eth_network(),
+ _fails_display_format_via_request,
),
]
for data_type, message, check in cases:
@@ -218,23 +254,33 @@ def test_protobuf_mismatch(session: Session) -> None:
cases += [
(
DefinitionType.ETHEREUM_NETWORK,
- make_eth_erc7730_display_format(),
+ make_eth_display_format(),
_fails_network,
),
(
DefinitionType.ETHEREUM_TOKEN,
- make_eth_erc7730_display_format(),
+ make_eth_display_format(),
_fails_token,
),
(
DefinitionType.ETHEREUM_DISPLAY_FORMAT,
make_eth_token(),
- _fails_erc7730_display_format,
+ _fails_display_format,
+ ),
+ (
+ DefinitionType.ETHEREUM_DISPLAY_FORMAT,
+ make_eth_network(),
+ _fails_display_format,
+ ),
+ (
+ DefinitionType.ETHEREUM_DISPLAY_FORMAT,
+ make_eth_token(),
+ _fails_display_format_via_request,
),
(
DefinitionType.ETHEREUM_DISPLAY_FORMAT,
make_eth_network(),
- _fails_erc7730_display_format,
+ _fails_display_format_via_request,
),
]
for data_type, message, check in cases:
diff --git a/tests/device_tests/ethereum/test_definitions_request.py b/tests/device_tests/ethereum/test_definitions_request.py
new file mode 100644
index 00000000..09fcf9be
--- /dev/null
+++ b/tests/device_tests/ethereum/test_definitions_request.py
@@ -0,0 +1,284 @@
+from __future__ import annotations
+
+from binascii import hexlify
+from typing import Callable
+
+import pytest
+
+from trezorlib import ethereum, messages
+from trezorlib.debuglink import DebugSession as Session
+
+from ... import definitions
+from ...input_flows import InputFlowConfirmAllWarnings
+from .test_definitions import (
+ FUNC_SIG_FAKE,
+ UNISWAP_EXACT_INPUT_SINGLE_DISPLAY_FORMAT,
+ UNISWAP_EXACT_INPUT_SINGLE_DISPLAY_FORMAT_LABELS,
+ UNISWAP_V3_ROUTER2,
+ UNISWAP_WETH_WETH2_CALLDATA,
+ WETH2_TOKEN_DEFINITION,
+ WETH_TOKEN_DEFINITION,
+ get_clear_signing_sign_tx_eip1559_params,
+ get_clear_signing_sign_tx_params,
+ make_label_checker,
+)
+
+pytestmark = [pytest.mark.altcoin, pytest.mark.ethereum, pytest.mark.models("core")]
+
+
+def _make_display_format_definition_provider(
+ display_format_requests: list,
+ token_requests: list,
+ display_format_info: messages.EthereumDisplayFormatInfo,
+ token_definitions: dict[str, dict] | None = None,
+) -> Callable[[messages.EthereumDefinitionRequest], messages.EthereumDefinitionAck]:
+ if token_definitions is None:
+ token_definitions = {
+ WETH_TOKEN_DEFINITION["address"][2:].lower(): WETH_TOKEN_DEFINITION
+ }
+
+ def provider(
+ req: messages.EthereumDefinitionRequest,
+ ) -> messages.EthereumDefinitionAck:
+ if not req.func_sig:
+ # No func_sig means the firmware is requesting a token/network definition
+ # only (e.g. from `TokenAmountFormatter` during field formatting).
+ token_requests.append(req)
+ addr = hexlify(req.token_address).decode("ascii").lower()
+ token_def = token_definitions.get(addr)
+ assert token_def is not None, f"Unexpected token request for {addr}"
+ assert req.chain_id == token_def["chain_id"]
+
+ return messages.EthereumDefinitionAck(
+ definitions=messages.EthereumDefinitions(
+ encoded_network=definitions.encode_eth_network(
+ chain_id=token_def["chain_id"]
+ ),
+ encoded_token=definitions.encode_eth_token(**token_def),
+ ),
+ )
+ else: # Display format was requested.
+ display_format_requests.append(req)
+ return messages.EthereumDefinitionAck(
+ definitions=messages.EthereumDefinitions(
+ encoded_display_format=definitions.encode_eth_display_format(
+ display_format_info
+ )
+ ),
+ )
+
+ return provider
+
+
+def test_definition_request_sent(session: Session) -> None:
+ # When clear signing data is present the firmware requests a display format
+ # mid-flow via EthereumDefinitionRequest (if the host signaled that it supports that).
+ # Verify it is called with the right fields and that signing completes
+ # without clear signing when we reply with no definition.
+
+ def provider(
+ req: messages.EthereumDefinitionRequest,
+ ) -> messages.EthereumDefinitionAck:
+ definition_requests.append(req)
+ return messages.EthereumDefinitionAck(definitions=None)
+
+ for sign_tx, param_getter in [
+ (ethereum.sign_tx, get_clear_signing_sign_tx_params),
+ (ethereum.sign_tx_eip1559, get_clear_signing_sign_tx_eip1559_params),
+ ]:
+ on_page, assert_all_seen = make_label_checker(
+ expected=set(),
+ absent=UNISWAP_EXACT_INPUT_SINGLE_DISPLAY_FORMAT_LABELS
+ | {"WETH", "USDT", "UNKN"},
+ )
+
+ definition_requests: list[messages.EthereumDefinitionRequest] = []
+ with session.test_ctx as client:
+ if not session.debug.legacy_debug:
+ client.set_input_flow(
+ InputFlowConfirmAllWarnings(session, on_page=on_page).get()
+ )
+ sign_tx(
+ session,
+ **param_getter(supports_definition_request=True),
+ definition_provider=provider,
+ )
+
+ assert len(definition_requests) == 1
+ req = definition_requests[0]
+ assert req.chain_id == 1
+ assert req.token_address == bytes.fromhex(UNISWAP_V3_ROUTER2[2:].lower())
+ assert req.func_sig == FUNC_SIG_FAKE
+
+ assert_all_seen()
+
+
+def test_definition_request_not_sent(session: Session) -> None:
+ # When clear signing data is present the firmware does not request a display format
+ # mid-flow via EthereumDefinitionRequest if the host did not signal that it supports that.
+
+ def provider(
+ req: messages.EthereumDefinitionRequest,
+ ) -> messages.EthereumDefinitionAck:
+ definition_requests.append(req)
+ return messages.EthereumDefinitionAck(definitions=None)
+
+ for sign_tx, param_getter in [
+ (ethereum.sign_tx, get_clear_signing_sign_tx_params),
+ (ethereum.sign_tx_eip1559, get_clear_signing_sign_tx_eip1559_params),
+ ]:
+ on_page, assert_all_seen = make_label_checker(
+ expected=set(),
+ absent=UNISWAP_EXACT_INPUT_SINGLE_DISPLAY_FORMAT_LABELS
+ | {"WETH", "USDT", "UNKN"},
+ )
+
+ definition_requests: list[messages.EthereumDefinitionRequest] = []
+ with session.test_ctx as client:
+ if not session.debug.legacy_debug:
+ client.set_input_flow(
+ InputFlowConfirmAllWarnings(session, on_page=on_page).get()
+ )
+ sign_tx(
+ session,
+ **param_getter(supports_definition_request=False),
+ definition_provider=provider,
+ )
+
+ assert len(definition_requests) == 0
+
+ assert_all_seen()
+
+
+def test_definition_request_with_display_format(session: Session) -> None:
+ # When we reply to EthereumDefinitionRequest with a valid display format,
+ # the firmware performs clear signing using the provided display format.
+
+ for sign_tx, param_getter in [
+ (ethereum.sign_tx, get_clear_signing_sign_tx_params),
+ (ethereum.sign_tx_eip1559, get_clear_signing_sign_tx_eip1559_params),
+ ]:
+ on_page, assert_all_seen = make_label_checker(
+ expected=UNISWAP_EXACT_INPUT_SINGLE_DISPLAY_FORMAT_LABELS
+ | {"FAKE WETH", "USDT"},
+ absent={"UNKN"},
+ )
+
+ display_format_requests: list[messages.EthereumDefinitionRequest] = []
+ token_requests: list[messages.EthereumDefinitionRequest] = []
+ with session.test_ctx as client:
+ if not session.debug.legacy_debug:
+ client.set_input_flow(
+ InputFlowConfirmAllWarnings(session, on_page=on_page).get()
+ )
+ sign_tx(
+ session,
+ **param_getter(supports_definition_request=True),
+ definition_provider=_make_display_format_definition_provider(
+ display_format_requests,
+ token_requests,
+ UNISWAP_EXACT_INPUT_SINGLE_DISPLAY_FORMAT,
+ ),
+ )
+ assert_all_seen()
+ assert len(display_format_requests) == 1
+ assert len(token_requests) == 1 # WETH requested, built in USDT not requested
+
+
+def test_definition_request_with_invalid_display_format(session: Session) -> None:
+ # A definition whose tuple claims an extra field causes the firmware to fail parsing it
+ # and reverting to blind signing.
+
+ assert (
+ UNISWAP_EXACT_INPUT_SINGLE_DISPLAY_FORMAT.parameter_definitions[0].tuple
+ is not None
+ )
+ bad_tuple = messages.EthereumABITupleInfo(
+ fields=[
+ *UNISWAP_EXACT_INPUT_SINGLE_DISPLAY_FORMAT.parameter_definitions[
+ 0
+ ].tuple.fields,
+ # extra field — causes OutOfBounds during clear signing
+ messages.EthereumABIValueInfo(atomic=messages.EthereumABIType.ABI_UINT256),
+ ],
+ is_dynamic=False,
+ )
+ bad_display_format = definitions.make_eth_display_format(
+ chain_id=UNISWAP_EXACT_INPUT_SINGLE_DISPLAY_FORMAT.chain_id,
+ address=UNISWAP_EXACT_INPUT_SINGLE_DISPLAY_FORMAT.address,
+ func_sig=UNISWAP_EXACT_INPUT_SINGLE_DISPLAY_FORMAT.func_sig,
+ intent=UNISWAP_EXACT_INPUT_SINGLE_DISPLAY_FORMAT.intent,
+ parameter_definitions=[messages.EthereumABIValueInfo(tuple=bad_tuple)],
+ field_definitions=list(
+ UNISWAP_EXACT_INPUT_SINGLE_DISPLAY_FORMAT.field_definitions
+ ),
+ )
+
+ for sign_tx, param_getter in [
+ (ethereum.sign_tx, get_clear_signing_sign_tx_params),
+ (ethereum.sign_tx_eip1559, get_clear_signing_sign_tx_eip1559_params),
+ ]:
+ on_page, assert_all_seen = make_label_checker(
+ absent=UNISWAP_EXACT_INPUT_SINGLE_DISPLAY_FORMAT_LABELS
+ | {"UNKN", "WETH", "USDT"},
+ )
+
+ display_format_requests: list[messages.EthereumDefinitionRequest] = []
+ token_requests: list[messages.EthereumDefinitionRequest] = []
+ with session.test_ctx as client:
+ if not session.debug.legacy_debug:
+ client.set_input_flow(
+ InputFlowConfirmAllWarnings(session, on_page=on_page).get()
+ )
+ sign_tx(
+ session,
+ **param_getter(supports_definition_request=True),
+ definition_provider=_make_display_format_definition_provider(
+ display_format_requests, token_requests, bad_display_format
+ ),
+ )
+
+ assert len(display_format_requests) == 1
+ assert len(token_requests) == 0
+ assert_all_seen()
+
+
+def test_definition_request_two_tokens(session: Session) -> None:
+ # When the calldata references two non-builtin tokens (WETH and WETH2),
+ # the firmware sends a separate token definition request for each.
+ token_defs = {
+ WETH_TOKEN_DEFINITION["address"][2:].lower(): WETH_TOKEN_DEFINITION,
+ WETH2_TOKEN_DEFINITION["address"][2:].lower(): WETH2_TOKEN_DEFINITION,
+ }
+ for sign_tx, param_getter in [
+ (ethereum.sign_tx, get_clear_signing_sign_tx_params),
+ (ethereum.sign_tx_eip1559, get_clear_signing_sign_tx_eip1559_params),
+ ]:
+ on_page, assert_all_seen = make_label_checker(
+ expected=UNISWAP_EXACT_INPUT_SINGLE_DISPLAY_FORMAT_LABELS
+ | {"FAKE WETH", "FAKE WETH2"},
+ absent={"UNKN"},
+ )
+ display_format_requests: list[messages.EthereumDefinitionRequest] = []
+ token_requests: list[messages.EthereumDefinitionRequest] = []
+ with session.test_ctx as client:
+ if not session.debug.legacy_debug:
+ client.set_input_flow(
+ InputFlowConfirmAllWarnings(session, on_page=on_page).get()
+ )
+ sign_tx(
+ session,
+ **param_getter(
+ data=UNISWAP_WETH_WETH2_CALLDATA,
+ supports_definition_request=True,
+ ),
+ definition_provider=_make_display_format_definition_provider(
+ display_format_requests,
+ token_requests,
+ UNISWAP_EXACT_INPUT_SINGLE_DISPLAY_FORMAT,
+ token_definitions=token_defs,
+ ),
+ )
+ assert_all_seen()
+ assert len(display_format_requests) == 1
+ assert len(token_requests) == 2
Why this scored 28/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.