feat(ethereum): parsing dynamic values in calldata
What changed, and why it matters
This commit is a feature expansion for Trezor's Ethereum 'clear signing' system, which decodes smart-contract call data so users see human-readable details instead of raw hex. It adds support for dynamic Solidity types such as strings, byte arrays, structs, and arrays. The change is large and refactors how transaction data is parsed and presented. It does not appear to be a disclosed security fix, but because it handles untrusted transaction data, any parsing bugs could in principle let a malformed transaction bypass warnings or crash the device. The diff itself shows explicit bounds checks and validation, which is a positive sign, but the new code paths are complex and have not been externally audited in the provided materials.
Treat this as a high-complexity feature change rather than a confirmed vulnerability. Review the new ABI parser for memory safety and correctness on malformed calldata, especially dynamic offsets, nested arrays, and struct handling. Run fuzzing against the parser with crafted transaction data. Verify that any parser exception reliably falls back to blind-signing confirmation or aborts, and does not silently truncate or mis-display fields. No immediate patch is indicated by the supplied materials.
Security signals we found
Large refactor of untrusted input parser for Ethereum transaction calldata
New dynamic type parsing (strings, bytes, arrays, structs) increases attack surface
Explicit bounds checks and padding validation added for dynamic values
Malformed calldata now raises InvalidFunctionCall/OutOfBounds and aborts clear-signing path
No vendor security disclosure or advisory referenced in commit or supplied materials
No independent researcher attribution in commit or supplied materials
Evidence from the diff
The patch rewrites the Ethereum clear-signing parser in trezor-firmware. It replaces a simple 32-byte-per-argument parser with an ABI-aware parser supporting Atomic, Dynamic, Struct, and Array ABI values. New helpers include parse_uint160, parse_uint24, parse_bool, parse_bytes, parse_string, parse_uint256_array, and formatters such as UnitFormatter. The old sc_constants.py and clear_signing_constants.py are removed; definitions move to clear_signing_definitions.py. Transaction-data confirmation callbacks are refactored from sign_tx.py into helpers.py (get_data_confirmer, get_progress_indicator). The clear_signing_approver now returns a tuple of (data_chunk_callback, summary_coroutine) instead of only the summary coroutine, allowing the parser to consume data chunks incrementally. Bounds checks (OutOfBounds) and padding checks (DirtyAddress/ValueOverflow) are added, and malformed calls raise InvalidFunctionCall and are rejected. No CVE, advisory, or vendor security statement is present in the supplied materials.
Changed components
core/src/apps/ethereum/clear_signing.pycore/src/apps/ethereum/clear_signing_definitions.pycore/src/apps/ethereum/helpers.pycore/src/apps/ethereum/sign_tx.pycore/src/apps/ethereum/sign_tx_eip1559.pycore/src/apps/ethereum/staking.pyInspect captured patch +759 / −332
diff --git a/core/embed/upymod/qstrdefsport.h b/core/embed/upymod/qstrdefsport.h
index d4d0f5eb..59463e6e 100644
--- a/core/embed/upymod/qstrdefsport.h
+++ b/core/embed/upymod/qstrdefsport.h
@@ -543,7 +543,7 @@ Q(apps.eos.sign_tx)
Q(apps.eos.writers)
Q(apps.ethereum)
Q(apps.ethereum.clear_signing)
-Q(apps.ethereum.clear_signing_constants)
+Q(apps.ethereum.clear_signing_definitions)
Q(apps.ethereum.definitions)
Q(apps.ethereum.get_address)
Q(apps.ethereum.get_public_key)
@@ -551,7 +551,6 @@ Q(apps.ethereum.helpers)
Q(apps.ethereum.keychain)
Q(apps.ethereum.layout)
Q(apps.ethereum.networks)
-Q(apps.ethereum.sc_constants)
Q(apps.ethereum.sign_message)
Q(apps.ethereum.sign_tx)
Q(apps.ethereum.sign_tx_eip1559)
@@ -696,7 +695,7 @@ Q(certificates)
Q(chacha_poly)
Q(chunks)
Q(clear_signing)
-Q(clear_signing_constants)
+Q(clear_signing_definitions)
Q(clsag)
Q(common)
Q(constants)
diff --git a/core/src/apps/ethereum/clear_signing.py b/core/src/apps/ethereum/clear_signing.py
index 225f69ee..94e74406 100644
--- a/core/src/apps/ethereum/clear_signing.py
+++ b/core/src/apps/ethereum/clear_signing.py
@@ -2,71 +2,185 @@ from micropython import const
from typing import TYPE_CHECKING
from trezor import TR
-from trezor.crypto import base58
from trezor.utils import BufferReader
-from apps.ethereum import clear_signing_constants as constants
-
-from .helpers import address_from_bytes, format_ethereum_amount
+from .helpers import address_from_bytes, format_ethereum_amount, get_account_and_path
if TYPE_CHECKING:
+ from buffer_types import AnyBytes
from typing import Any, Callable, Coroutine, Iterable
from trezor.messages import EthereumNetworkInfo, EthereumTokenInfo
from trezor.ui.layouts import StrPropertyType
from .definitions import Definitions
+ from .helpers import ConfirmDataFn
from .keychain import MsgInSignTx
- Value = int | bytes | None
- FieldParser = Callable[[memoryview], Value]
- FieldFormatter = Callable[
- [Value, EthereumNetworkInfo, EthereumTokenInfo], str | None
- ]
+ # 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]
+
+ # Parses a Value from a slice of the calldata.
+ # Assumes that the memoryview contains just that value.
+ Parser = Callable[[memoryview], AnyValue]
+
+
+SC_FUNC_SIG_BYTES = const(4)
class InvalidFunctionCall(Exception):
+ """Raised when the calldata encoding of a function call,
+ including its parameters, is invalid."""
+
+ pass
+
+
+class ValueOverflow(InvalidFunctionCall):
+ """Raised when a value that should be encoded on less than 32 bytes
+ actually uses more bytes than it should."""
+
pass
-# field types - can be any Solidity type - currently just address and uint256
+class DirtyAddress(ValueOverflow):
+ pass
-def parse_address(arg: memoryview) -> Value:
- from .sc_constants import SC_ARGUMENT_ADDRESS_BYTES, SC_ARGUMENT_BYTES
+class OutOfBounds(InvalidFunctionCall):
+ """Raised when we try to read outside the bounds of the raw data."""
- if any(byte != 0 for byte in arg[: SC_ARGUMENT_BYTES - SC_ARGUMENT_ADDRESS_BYTES]):
- raise InvalidFunctionCall
+ pass
- return bytes(arg[SC_ARGUMENT_BYTES - SC_ARGUMENT_ADDRESS_BYTES :])
+class InvalidFormatDefinition(Exception):
+ """Raised when we fail to format data according to the definitions,
+ if for example the parsed calldata has other types than what
+ the format definition expects."""
-def parse_uint256(arg: memoryview) -> Value:
- return int.from_bytes(arg, "big")
+ pass
-# field formatters: https://eips.ethereum.org/EIPS/eip-7730#field-formats
+# Value Parsers
-def format_address_name(
- address: Value, network: EthereumNetworkInfo, _token: EthereumTokenInfo
-) -> str | None:
- if address is None:
- return None
- else:
- assert isinstance(address, bytes)
- return address_from_bytes(address, network)
+def _check_padding_zero(
+ raw_data: memoryview, used_bytes: int, exc: type[ValueOverflow] = ValueOverflow
+) -> None:
+ """Sanity check to make sure unused data is zeroed out."""
+ if any(raw_data[: 32 - used_bytes]):
+ raise exc
+
+
+def parse_uint256(raw_data: memoryview) -> Value:
+ if len(raw_data) < 32:
+ raise OutOfBounds
+ return int.from_bytes(raw_data, "big")
+
+
+def parse_uint160(raw_data: memoryview) -> Value:
+ if len(raw_data) < 32:
+ raise OutOfBounds
+ _check_padding_zero(raw_data, 160 // 8)
+ return parse_uint256(raw_data)
+
+
+def parse_uint24(raw_data: memoryview) -> Value:
+ if len(raw_data) < 32:
+ raise OutOfBounds
+ _check_padding_zero(raw_data, 24 // 8)
+ return parse_uint256(raw_data)
+
+
+def parse_bool(raw_data: memoryview) -> Value:
+ if len(raw_data) < 32:
+ raise OutOfBounds
+ uint_value = parse_uint256(raw_data)
+ if uint_value not in (0, 1):
+ raise ValueOverflow
+ return uint_value == 1
+
+
+def parse_address(raw_data: memoryview) -> Value:
+ if len(raw_data) < 32:
+ raise OutOfBounds
+ _check_padding_zero(raw_data, 20, DirtyAddress)
+ return bytes(raw_data[32 - 20 :])
+
+
+def parse_bytes(raw_data: memoryview) -> Value:
+ return bytes(raw_data)
+
+
+def parse_string(raw_data: memoryview) -> Value:
+ return bytes(raw_data).decode("utf-8")
+
+
+def parse_uint256_array(raw_data: memoryview) -> list[Value]:
+ return [
+ parse_uint256(raw_data[i * 32 : (i + 1) * 32])
+ for i in range(len(raw_data) // 32)
+ ]
+
+
+DYNAMIC_DATA_PARSERS = [parse_bytes, parse_string, parse_uint256_array]
+
+# Field formatters: https://eips.ethereum.org/EIPS/eip-7730#field-formats
-def get_token_amount_formatter(threshold: int | None = None) -> FieldFormatter:
- def format_token_amount(
- amount: Value, network: EthereumNetworkInfo, token: EthereumTokenInfo
+class FieldFormatter:
+ def format(
+ self, value: AnyValue, network: EthereumNetworkInfo, token: EthereumTokenInfo
+ ) -> str | None:
+ raise NotImplementedError
+
+
+class AddressNameFormatter(FieldFormatter):
+ def format(
+ self, address: AnyValue, network: EthereumNetworkInfo, _token: EthereumTokenInfo
+ ) -> str | None:
+ if address is None:
+ return "(None)"
+ elif isinstance(address, str):
+ return address
+ else:
+ if not isinstance(address, bytes):
+ raise InvalidFormatDefinition
+ return address_from_bytes(address, network)
+
+
+class AmountFormatter(FieldFormatter):
+ def format(
+ self, amount: AnyValue, network: EthereumNetworkInfo, _token: EthereumTokenInfo
+ ) -> str | None:
+ if amount is None:
+ return None
+ else:
+ if not isinstance(amount, int):
+ raise InvalidFormatDefinition
+
+ # Note: we are passing None rather than `_token`
+ # to `format_ethereum_amount` because this formatter
+ # is meant to be used with native ETH amounts
+ return format_ethereum_amount(amount, None, network)
+
+
+class TokenAmountFormatter(FieldFormatter):
+ def __init__(self, threshold: int | None = None) -> None:
+ self.threshold = threshold
+
+ def format(
+ self, amount: AnyValue, network: EthereumNetworkInfo, token: EthereumTokenInfo
) -> str | None:
if amount is None:
return None
else:
- assert isinstance(amount, int)
- if threshold is not None and amount > threshold:
+ if not isinstance(amount, int):
+ raise InvalidFormatDefinition
+ if self.threshold is not None and amount > self.threshold:
# TODO: figure out a way for the formatter to signal that the amount was above the threshold.
# For now we return None and `confirm_ethereum_approve` shows the "Unlimited amount" warning,
# but the `tokenAmount` spec allows this message to be customized in which case
@@ -74,7 +188,44 @@ def get_token_amount_formatter(threshold: int | None = None) -> FieldFormatter:
return None
return format_ethereum_amount(amount, token, network)
- return format_token_amount
+
+class UnitFormatter(FieldFormatter):
+ def __init__(self, decimals: int = 0, base: str = "", prefix: bool = False) -> None:
+ self.decimals = decimals
+ self.base = base
+ self.prefix = prefix
+
+ def format(
+ self, value: AnyValue, network: EthereumNetworkInfo, token: EthereumTokenInfo
+ ) -> str | None:
+ if value is None:
+ return None
+ else:
+ if not isinstance(value, int):
+ raise InvalidFormatDefinition
+
+ scaled_value = value / (10**self.decimals)
+
+ if not self.prefix or scaled_value == 0:
+ return f"{scaled_value:g}{self.base}"
+
+ si_prefixes = {12: "T", 9: "G", 6: "M", 3: "k", 0: "", -3: "m"}
+
+ temp_val = abs(scaled_value)
+ exponent = 0
+ if temp_val >= 1:
+ while temp_val >= 1000 and exponent < 12:
+ temp_val /= 1000
+ exponent += 3
+ else:
+ while temp_val < 1 and exponent > -3:
+ temp_val *= 1000
+ exponent -= 3
+
+ significand = scaled_value / (10**exponent)
+ prefix_symbol = si_prefixes.get(exponent, "")
+
+ return f"{significand:g}{prefix_symbol}{self.base}"
# https://eips.ethereum.org/EIPS/eip-7730#context-section
@@ -94,17 +245,168 @@ class BindingContext:
# https://eips.ethereum.org/EIPS/eip-7730#structured-data-format-specification
-class Field:
+class ABIValue:
+ def parse(self, raw_data: memoryview, offset: int) -> tuple[AnyValue, int]:
+ raise NotImplementedError
+
+
+class Atomic(ABIValue):
+ """Atomic values, such as integers or addresses, are always stored on 32 bytes."""
+
+ def __init__(self, parser: Parser) -> None:
+ self.parser = parser
+
+ def parse(self, raw_data: memoryview, offset: int) -> tuple[AnyValue, int]:
+ if offset > len(raw_data):
+ raise OutOfBounds
+ return self.parser(raw_data[offset : offset + 32]), 32
+
+
+class Dynamic(ABIValue):
+ """Dynamic values, such as strings or `bytes` are stored later in the calldata,
+ the inline value being just a pointer to the actual location.
+ Also they have an arbitrary length, which is encoded on the first 32 bytes,
+ after which the actual value follows."""
+
+ def __init__(self, parser: Parser) -> None:
+ self.parser = parser
+
+ def parse(self, raw_data: memoryview, offset: int) -> tuple[AnyValue, int]:
+ if offset + 32 > len(raw_data):
+ raise OutOfBounds
+ pointer = int.from_bytes(raw_data[offset : offset + 32], "big")
+ if pointer + 32 > len(raw_data):
+ raise OutOfBounds
+ length = int.from_bytes(raw_data[pointer : pointer + 32], "big")
+ if pointer + 32 + length > len(raw_data):
+ raise OutOfBounds
+ data = raw_data[pointer + 32 : pointer + 32 + length]
+ return self.parser(data), 32
+
+
+class Struct(ABIValue):
+ """Structs (or Tuples, 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.
+ However, dynamic structs inside arrays behave as static structs,
+ hence we cannot guess if the Struct 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]:
+ if not self.is_dynamic:
+ base_offset = offset
+ consumed = self.static_size
+ else:
+ if offset + 32 > len(raw_data):
+ raise OutOfBounds
+ pointer = int.from_bytes(raw_data[offset : offset + 32], "big")
+ base_offset = pointer
+ consumed = 32 # dynamic structs just consume the pointer
+
+ if base_offset + self.static_size > len(raw_data):
+ raise OutOfBounds
+
+ value: list[Value] = [None] * len(self.fields)
+
+ for i, parser in enumerate(self.fields):
+ field_head_pos = base_offset + (i * 32)
+ raw_field = raw_data[field_head_pos : field_head_pos + 32]
+ if parser not in DYNAMIC_DATA_PARSERS:
+ v = parser(raw_field)
+ if isinstance(v, (tuple, list)):
+ # Struct or Array inside a Struct
+ raise NotImplementedError
+ value[i] = v
+ else:
+ field_pointer = base_offset + int.from_bytes(raw_field, "big")
+
+ if field_pointer + 32 > len(raw_data):
+ raise OutOfBounds
+ length = int.from_bytes(
+ raw_data[field_pointer : field_pointer + 32], "big"
+ )
+ if field_pointer + 32 + length > len(raw_data):
+ raise OutOfBounds
+ 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
+ raise NotImplementedError
+ value[i] = v
+ return tuple(value), consumed
+
+
+class Array(ABIValue):
+ """Arrays are sequences of value of the same type."""
+
+ def __init__(self, element_definition: ABIValue) -> None:
+ self.element_definition = element_definition
+
+ def parse(self, raw_data: memoryview, offset: int) -> tuple[ListValue, int]:
+ if offset + 32 > len(raw_data):
+ raise OutOfBounds
+ array_pointer = int.from_bytes(raw_data[offset : offset + 32], "big")
+ if array_pointer + 32 > len(raw_data):
+ raise OutOfBounds
+ array_length = int.from_bytes(
+ raw_data[array_pointer : array_pointer + 32], "big"
+ )
+ array_heads_end = array_pointer + 32 + (array_length * 32)
+ if array_heads_end > len(raw_data):
+ raise OutOfBounds
+
+ value = []
+
+ for i in range(array_length):
+ p = array_pointer + 32 + (i * 32)
+ if p + 32 > len(raw_data):
+ raise OutOfBounds
+ if isinstance(self.element_definition, Atomic):
+ # atomic types are encoded in place
+ data, _ = self.element_definition.parse(raw_data, p)
+ else:
+ element_pointer = int.from_bytes(raw_data[p : p + 32], "big")
+ element_absolute_pointer = array_pointer + 32 + element_pointer
+ data, _ = self.element_definition.parse(
+ raw_data, element_absolute_pointer
+ )
+ value.append(data)
+
+ return value, 32 # arrays just consume the pointer
+
+
+# https://eips.ethereum.org/EIPS/eip-7730#evm-transaction-container
+
+
+class ContainerPath:
+ From = 1
+ Value = 2
+ To = 3
+ ChainID = 4
+
+
+class FieldDefinition:
def __init__(
self,
- label: str | None,
- parser: FieldParser,
- formatter: FieldFormatter,
+ path: tuple[int, ...] | int,
+ label: str,
+ formatter: FieldFormatter | type[FieldFormatter],
) -> None:
+ self.path = path
self.label = label
- self.parser = parser
self.formatter = formatter
+ def get_formatter(self) -> FieldFormatter:
+ # instantiate formatters only if needed
+ formatter = self.formatter
+ if isinstance(formatter, type):
+ formatter = formatter()
+ return formatter
+
class DisplayFormat:
def __init__(
@@ -112,44 +414,100 @@ class DisplayFormat:
binding_context: BindingContext | None,
func_sig: bytes,
intent: str,
- interpolated_intent: str | None,
- fields: list[Field],
+ parameter_definitions: list[ABIValue],
+ field_definitions: list[FieldDefinition],
) -> None:
self.binding_context = binding_context
self.func_sig = func_sig
self.intent = intent
- self.interpolated_intent = interpolated_intent
- self.fields = fields
+ self.parameter_definitions = parameter_definitions
+ self.field_definitions = field_definitions
+
+ self.parameters = []
- def parse_fields(
+ def matches_context(self, chain_id: int, address: bytes) -> bool:
+ if self.binding_context is None:
+ # applies to anything without context verification
+ # (for approve and transfer)
+ return True
+
+ return self.binding_context.matches(chain_id, address)
+
+
+class ParsingContext:
+ def __init__(self, display_format: DisplayFormat) -> None:
+ self.data = bytes()
+ self.display_format = display_format
+
+ def process_data_chunk(self, offset: int, chunk: memoryview) -> None:
+ if offset == 0:
+ # skip function signature
+ chunk = chunk[SC_FUNC_SIG_BYTES:]
+ self.data += bytes(chunk)
+ # TODO: don't keep more than 4 chunks!
+
+ def get_parameters_and_fields(
self,
- data_reader: BufferReader,
+ address_n: list[int],
+ tx_value: AnyBytes,
network: EthereumNetworkInfo,
token: EthereumTokenInfo,
- ) -> Iterable[tuple[Value, StrPropertyType]]:
- from .sc_constants import SC_ARGUMENT_BYTES
-
- for field in self.fields:
- if data_reader.remaining_count() < SC_ARGUMENT_BYTES:
- raise InvalidFunctionCall
- arg = data_reader.read_memoryview(SC_ARGUMENT_BYTES)
- value = field.parser(arg)
- yield (
- value,
+ ) -> tuple[list[AnyValue], list[StrPropertyType]]:
+
+ parameters: list[AnyValue] = []
+
+ data = memoryview(self.data)
+ offset = 0
+ for parameter_definition in self.display_format.parameter_definitions:
+ value, consumed = parameter_definition.parse(data, offset)
+ parameters.append(value)
+ offset += consumed
+
+ fields = []
+ for field_definition in self.display_format.field_definitions:
+ path = field_definition.path
+ if isinstance(path, int): # ContainerPath
+ # standard container paths like @.from, @.value...
+ if path == ContainerPath.From:
+ account, _ = get_account_and_path(address_n)
+ p = account
+ elif path == ContainerPath.Value:
+ p = int.from_bytes(tx_value, "big")
+ else:
+ raise NotImplementedError # TODO
+ else:
+ if len(path) == 0:
+ # can't get anywhere by walking an inexisting path!
+ raise InvalidFormatDefinition
+
+ # walk the path
+ p = parameters
+ for step in path:
+ if p is None:
+ p = None
+ break
+ if isinstance(p, (list, tuple)):
+ # walk inside Arrays or Structs
+ try:
+ p = p[step]
+ except (IndexError, TypeError):
+ raise InvalidFormatDefinition
+ else:
+ # can't walk inside basic types
+ 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
+ raise InvalidFormatDefinition
+ fields.append(
(
- field.label,
- field.formatter(value, network, token),
+ field_definition.label,
+ field_definition.get_formatter().format(p, network, token),
None,
- ),
+ )
)
- if data_reader.remaining_count() > 0:
- raise InvalidFunctionCall
- def matches_context(self, chain_id: int, address: bytes) -> bool:
- if self.binding_context is None:
- return True
-
- return self.binding_context.matches(chain_id, address)
+ return parameters, fields
def get_approver(
@@ -159,14 +517,14 @@ def get_approver(
value: int,
maximum_fee: str,
fee_items: Iterable[StrPropertyType],
-) -> Coroutine[Any, Any, None] | None:
- from .sc_constants import SC_FUNC_SIG_BYTES
+) -> tuple[ConfirmDataFn, Coroutine[Any, Any, None]] | None:
+ from .clear_signing_definitions import ALL_DISPLAY_FORMATS
# local_cache_attribute
- network = definitions.network
chain_id = msg.chain_id
+ network = definitions.network
- if not address_bytes or value != 0:
+ if not address_bytes:
return None
# only parse the initial chunk for now
@@ -191,31 +549,46 @@ def get_approver(
if not display_format.matches_context(chain_id, address_bytes):
return None
+ parser, parsing_context = _get_data_chunk_parser(display_format)
- try:
- args = list(display_format.parse_fields(data_reader, network, token))
- except InvalidFunctionCall:
- return None
+ return parser, _get_summary_handler(
+ parsing_context, address_bytes, msg, network, token, maximum_fee, fee_items
+ )
- # custom treatment of certain functions (APPROVE, TRANSFER)
- if func_sig == APPROVE_DISPLAY_FORMAT.func_sig:
- assert len(args) == 2
+def _get_data_chunk_parser(
+ display_format: DisplayFormat,
+) -> tuple[ConfirmDataFn, ParsingContext]:
+ offset = 0
+ context = ParsingContext(display_format)
+
+ async def confirm_fn(chunk: AnyBytes) -> None:
+ nonlocal offset
+ context.process_data_chunk(offset, memoryview(chunk))
+ offset += len(chunk)
+
+ return confirm_fn, context
- (arg0_raw_value, (arg0_name, arg0_formatted_value, _)) = args[0]
- assert arg0_name == "Spender"
- assert isinstance(arg0_raw_value, bytes)
- assert isinstance(arg0_formatted_value, str)
- (arg1_raw_value, (arg1_name, arg1_formatted_value, _)) = args[1]
- assert arg1_name == "Amount"
- assert isinstance(arg1_raw_value, int)
+def _get_summary_handler(
+ context: ParsingContext,
+ address_bytes: bytes,
+ msg: MsgInSignTx,
+ network: EthereumNetworkInfo,
+ token: EthereumTokenInfo,
+ maximum_fee: str,
+ fee_items: Iterable[StrPropertyType],
+) -> Coroutine[Any, Any, None]:
+ from .clear_signing_definitions import (
+ APPROVE_DISPLAY_FORMAT,
+ TRANSFER_DISPLAY_FORMAT,
+ )
+
+ # custom treatment of certain functions (APPROVE, TRANSFER)
- return _get_approve_handler(
- arg0_formatted_value,
- constants.KNOWN_ADDRESSES.get(arg0_raw_value),
- arg1_formatted_value,
- arg1_raw_value == SC_FUNC_APPROVE_REVOKE_AMOUNT,
+ if context.display_format.func_sig == APPROVE_DISPLAY_FORMAT.func_sig:
+ return _handle_approve(
+ context,
address_bytes,
msg,
network,
@@ -223,21 +596,12 @@ def get_approver(
maximum_fee,
fee_items,
)
- elif func_sig == TRANSFER_DISPLAY_FORMAT.func_sig:
- assert len(args) == 2
- (_, (arg0_name, arg0_formatted_value, _)) = args[0]
- assert arg0_name == "To"
- assert isinstance(arg0_formatted_value, str)
-
- (_, (arg1_name, arg1_formatted_value, _)) = args[1]
- assert arg1_name == "Amount"
- assert isinstance(arg1_formatted_value, str)
-
- return _get_transfer_handler(
- arg0_formatted_value,
- arg1_formatted_value,
+ elif context.display_format.func_sig == TRANSFER_DISPLAY_FORMAT.func_sig:
+ return _handle_transfer(
+ context,
address_bytes,
msg,
+ network,
token,
maximum_fee,
fee_items,
@@ -245,24 +609,47 @@ def get_approver(
# generic UI for any function that has a `DisplayFormat`
- return _handle_generic_ui(display_format, args, address_bytes, token)
+ return _handle_generic_ui(context, msg, network, address_bytes, token)
-def _get_approve_handler(
- recipient_addr: str,
- recipient_str: str | None,
- value: str | None,
- is_revoke: bool,
+async def _handle_approve(
+ context: ParsingContext,
address_bytes: bytes,
msg: MsgInSignTx,
network: EthereumNetworkInfo,
token: EthereumTokenInfo,
maximum_fee: str,
fee_items: Iterable[StrPropertyType],
-) -> Coroutine[Any, Any, None] | None:
+) -> None:
+ from .clear_signing_definitions import (
+ KNOWN_ADDRESSES,
+ SC_FUNC_APPROVE_REVOKE_AMOUNT,
+ )
from .layout import require_confirm_approve
- return require_confirm_approve(
+ args, fields = context.get_parameters_and_fields(
+ msg.address_n, msg.value, network, token
+ )
+
+ assert len(args) == 2
+ assert len(fields) == 2
+
+ arg0_raw_value = args[0]
+ (field0_name, recipient_addr, _) = fields[0]
+ assert field0_name == "Spender"
+ assert isinstance(arg0_raw_value, bytes)
+ assert isinstance(recipient_addr, str)
+
+ arg1_raw_value = args[1]
+ (field1_name, value, _) = fields[1]
+ assert field1_name == "Amount"
+ assert isinstance(arg1_raw_value, int)
+
+ recipient_str = KNOWN_ADDRESSES.get(arg0_raw_value)
+
+ is_revoke = arg1_raw_value == SC_FUNC_APPROVE_REVOKE_AMOUNT
+
+ await require_confirm_approve(
recipient_addr,
value,
recipient_str,
@@ -278,18 +665,33 @@ def _get_approve_handler(
)
-def _get_transfer_handler(
- recipient_addr: str,
- value: str,
+async def _handle_transfer(
+ context: ParsingContext,
address_bytes: bytes,
msg: MsgInSignTx,
+ network: EthereumNetworkInfo,
token: EthereumTokenInfo,
maximum_fee: str,
fee_items: Iterable[StrPropertyType],
-) -> Coroutine[Any, Any, None] | None:
+) -> None:
from .layout import require_confirm_tx
- return require_confirm_tx(
+ args, fields = context.get_parameters_and_fields(
+ msg.address_n, msg.value, network, token
+ )
+
+ assert len(args) == 2
+ assert len(fields) == 2
+
+ (arg0_name, recipient_addr, _) = fields[0]
+ assert arg0_name == "To"
+ assert isinstance(recipient_addr, str)
+
+ (arg1_name, value, _) = fields[1]
+ assert arg1_name == "Amount"
+ assert isinstance(value, str)
+
+ await require_confirm_tx(
recipient_addr,
value,
address_bytes,
@@ -303,8 +705,9 @@ def _get_transfer_handler(
async def _handle_generic_ui(
- f: DisplayFormat,
- args: list[tuple[Value, StrPropertyType]],
+ context: ParsingContext,
+ msg: MsgInSignTx,
+ network: EthereumNetworkInfo,
address_bytes: bytes,
token: EthereumTokenInfo,
) -> None:
@@ -315,8 +718,13 @@ async def _handle_generic_ui(
)
from . import tokens
+ from .clear_signing_definitions import KNOWN_ADDRESSES
+ from .helpers import bytes_from_address
from .layout import require_confirm_address, require_confirm_unknown_token
+ _, fields = context.get_parameters_and_fields(
+ msg.address_n, msg.value, network, token
+ )
if token is tokens.UNKNOWN_TOKEN:
title = ethereum_address_title()
await require_confirm_unknown_token(title)
@@ -329,43 +737,13 @@ async def _handle_generic_ui(
TR.ethereum__unknown_contract_address,
)
- await confirm_action("confirm_contract", "Intent", f.intent)
+ # TODO ??
+ recipient_str = KNOWN_ADDRESSES.get(bytes_from_address(msg.to))
+
+ await confirm_action("confirm_contract", "Provider", recipient_str)
+ await confirm_action("confirm_contract", "Intent", context.display_format.intent)
await confirm_properties(
"confirm_contract",
"Confirm contract",
- (field_display for (_, field_display) in args),
+ fields,
)
-
-
-# https://github.com/LedgerHQ/clear-signing-erc7730-registry/blob/master/ercs/calldata-erc20-tokens.json#L27
-
-APPROVE_DISPLAY_FORMAT = DisplayFormat(
- binding_context=None,
- func_sig=base58.keccak_32(b"approve(address,uint256)"),
- intent="Approve",
- interpolated_intent=None,
- fields=[
- Field("Spender", parse_address, format_address_name), # _spender
- Field(
- "Amount",
- parse_uint256,
- get_token_amount_formatter(
- threshold=0x8000000000000000000000000000000000000000000000000000000000000000
- ), # _value
- ),
- ],
-)
-SC_FUNC_APPROVE_REVOKE_AMOUNT = const(0)
-
-TRANSFER_DISPLAY_FORMAT = DisplayFormat(
- binding_context=None,
- func_sig=base58.keccak_32(b"transfer(address,uint256)"),
- intent="Send",
- interpolated_intent=None,
- fields=[
- Field("To", parse_address, format_address_name), # _to
- Field("Amount", parse_uint256, get_token_amount_formatter()), # _value
- ],
-)
-
-ALL_DISPLAY_FORMATS = [APPROVE_DISPLAY_FORMAT, TRANSFER_DISPLAY_FORMAT]
diff --git a/core/src/apps/ethereum/clear_signing_constants.py b/core/src/apps/ethereum/clear_signing_constants.py
deleted file mode 100644
index 177c4018..00000000
--- a/core/src/apps/ethereum/clear_signing_constants.py
+++ /dev/null
@@ -1,70 +0,0 @@
-from ubinascii import unhexlify
-
-# https://github.com/LedgerHQ/clear-signing-erc7730-registry/blob/master/registry/1inch/calldata-AggregationRouterV6.json#L9
-ONEINCH_ADDRESS = unhexlify("111111125421cA6dc452d289314280a0f8842A65")
-ONEINCH_CHAINS = [
- 1,
- 10,
- 56,
- 100,
- 137,
- 146,
- 250,
- 8217,
- 8453,
- 42161,
- 43114,
- 59144,
- 1313161554,
-]
-ONEINCH_OWNER = "1inch Aggregation Router V6"
-
-# https://github.com/LedgerHQ/clear-signing-erc7730-registry/blob/master/registry/lifi/calldata-LIFIDiamond.json#L6
-LIFI_ADDRESS = unhexlify("1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE")
-LIFI_CHAINS = [
- 1,
- 10,
- 25,
- 56,
- 100,
- 106,
- 122,
- 137,
- 204,
- 250,
- 252,
- 288,
- 324,
- 1088,
- 1284,
- 1285,
- 5000,
- 8453,
- 9001,
- 34443,
- 42161,
- 42170,
- 42220,
- 43114,
- 59144,
- 81457,
- 167004,
- 534352,
- 1313161554,
- 1666600000,
-]
-LIFI_OWNER = "LiFI Diamond"
-
-# https://etherscan.io/address/0xe592427a0aece92de3edee1f18e0157c05861564
-UNISWAP_V3_ROUTER_ADDRESS = unhexlify("e592427a0aece92de3edee1f18e0157c05861564")
-# https://github.com/LedgerHQ/clear-signing-erc7730-registry/blob/master/registry/uniswap/calldata-UniswapV3Router02.json#L6
-UNISWAP_V3_ROUTER_02_ADDRESS = unhexlify("68b3465833fb72A70ecDF485E0e4C7bD8665Fc45")
-UNISWAP_V3_ROUTER_CHAINS = [1]
-UNISWAP_OWNER = "Uniswap V3 Router"
-
-KNOWN_ADDRESSES = {
- ONEINCH_ADDRESS: ONEINCH_OWNER,
- LIFI_ADDRESS: LIFI_OWNER,
- UNISWAP_V3_ROUTER_ADDRESS: UNISWAP_OWNER,
- UNISWAP_V3_ROUTER_02_ADDRESS: UNISWAP_OWNER,
-}
diff --git a/core/src/apps/ethereum/clear_signing_definitions.py b/core/src/apps/ethereum/clear_signing_definitions.py
new file mode 100644
index 00000000..e78562a2
--- /dev/null
+++ b/core/src/apps/ethereum/clear_signing_definitions.py
@@ -0,0 +1,134 @@
+from micropython import const
+from ubinascii import unhexlify
+
+from trezor.crypto import base58
+
+from .clear_signing import (
+ AddressNameFormatter,
+ AmountFormatter,
+ Array,
+ Atomic,
+ BindingContext,
+ ContainerPath,
+ DisplayFormat,
+ Dynamic,
+ FieldDefinition,
+ Struct,
+ TokenAmountFormatter,
+ UnitFormatter,
+ parse_address,
+ parse_bool,
+ parse_bytes,
+ parse_string,
+ parse_uint24,
+ parse_uint160,
+ parse_uint256,
+ parse_uint256_array,
+)
+
+# https://github.com/LedgerHQ/clear-signing-erc7730-registry/blob/master/ercs/calldata-erc20-tokens.json#L27
+
+APPROVE_DISPLAY_FORMAT = DisplayFormat(
+ binding_context=None,
+ func_sig=base58.keccak_32(b"approve(address,uint256)"),
+ intent="Approve",
+ parameter_definitions=[
+ Atomic(parse_address), # _spender
+ Atomic(parse_uint256), # _value
+ ],
+ field_definitions=[
+ FieldDefinition((0,), "Spender", AddressNameFormatter),
+ FieldDefinition(
+ (1,),
+ "Amount",
+ TokenAmountFormatter(
+ threshold=0x8000000000000000000000000000000000000000000000000000000000000000
+ ),
+ ),
+ ],
+)
+SC_FUNC_APPROVE_REVOKE_AMOUNT = const(0)
+
+TRANSFER_DISPLAY_FORMAT = DisplayFormat(
+ binding_context=None,
+ func_sig=base58.keccak_32(b"transfer(address,uint256)"),
+ intent="Send",
+ parameter_definitions=[
+ Atomic(parse_address), # _to
+ Atomic(parse_uint256), # _value
+ ],
+ field_definitions=[
+ FieldDefinition((0,), "To", AddressNameFormatter),
+ FieldDefinition((1,), "Amount", TokenAmountFormatter),
+ ],
+)
+
+ALL_DISPLAY_FORMATS = [APPROVE_DISPLAY_FORMAT, TRANSFER_DISPLAY_FORMAT]
+
+
+# https://github.com/LedgerHQ/clear-signing-erc7730-registry/blob/master/registry/1inch/calldata-AggregationRouterV6.json#L9
+ONEINCH_ADDRESS = unhexlify("111111125421cA6dc452d289314280a0f8842A65")
+ONEINCH_CHAINS = [
+ 1,
+ 10,
+ 56,
+ 100,
+ 137,
+ 146,
+ 250,
+ 8217,
+ 8453,
+ 42161,
+ 43114,
+ 59144,
+ 1313161554,
+]
+ONEINCH_OWNER = "1inch Aggregation Router V6"
+
+# https://github.com/LedgerHQ/clear-signing-erc7730-registry/blob/master/registry/lifi/calldata-LIFIDiamond.json
+LIFI_ADDRESS = unhexlify("1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE")
+LIFI_CHAINS = [
+ 1,
+ 10,
+ 25,
+ 56,
+ 100,
+ 106,
+ 122,
+ 137,
+ 204,
+ 250,
+ 252,
+ 288,
+ 324,
+ 1088,
+ 1284,
+ 1285,
+ 5000,
+ 8453,
+ 9001,
+ 34443,
+ 42161,
+ 42170,
+ 42220,
+ 43114,
+ 59144,
+ 81457,
+ 167004,
+ 534352,
+ 1313161554,
+ 1666600000,
+]
+LIFI_OWNER = "LiFI Diamond"
+
+# https://github.com/LedgerHQ/clear-signing-erc7730-registry/blob/master/registry/uniswap/calldata-UniswapV3Router02.json#L6
+UNISWAP_V3_ROUTER_ADDRESS = unhexlify("68b3465833fb72A70ecDF485E0e4C7bD8665Fc45")
+UNISWAP_V3_ROUTER_CHAINS = [1]
+UNISWAP_OWNER = "Uniswap V3 Router"
+
+
+KNOWN_ADDRESSES = {
+ ONEINCH_ADDRESS: ONEINCH_OWNER,
+ LIFI_ADDRESS: LIFI_OWNER,
+ UNISWAP_V3_ROUTER_ADDRESS: UNISWAP_OWNER,
+}
diff --git a/core/src/apps/ethereum/helpers.py b/core/src/apps/ethereum/helpers.py
index 952ffcb6..e32ae8b3 100644
--- a/core/src/apps/ethereum/helpers.py
+++ b/core/src/apps/ethereum/helpers.py
@@ -7,13 +7,16 @@ from . import networks
if TYPE_CHECKING:
from buffer_types import AnyBytes
- from typing import Iterable
+ from typing import Awaitable, Callable, Iterable
from trezor.messages import EthereumFieldType, EthereumTokenInfo
from trezor.ui.layouts import StrPropertyType
from .networks import EthereumNetworkInfo
+ ConfirmDataFn = Callable[[AnyBytes], Awaitable[None]]
+
+
RSKIP60_NETWORKS = (30, 31)
@@ -223,3 +226,63 @@ def _from_bytes_bigendian_signed(b: AnyBytes) -> int:
return -result - 1
else:
return int.from_bytes(b, "big")
+
+
+def get_progress_indicator(total_len: int, progress_len: int = 0) -> ConfirmDataFn:
+ from trezor.ui.layouts.progress import progress
+
+ def _progress_value() -> int:
+ assert 0 <= progress_len <= total_len
+ if total_len == 0:
+ return 1000
+ return (1000 * progress_len) // total_len
+
+ layout = progress(title=TR.progress__loading_transaction)
+ layout.value = _progress_value()
+
+ async def confirm_fn(chunk: AnyBytes) -> None:
+ nonlocal progress_len
+ progress_len += len(chunk)
+ layout.report(_progress_value())
+
+ return confirm_fn
+
+
+def get_data_confirmer(total_len: int) -> ConfirmDataFn:
+ from trezor.enums import ButtonRequestType
+ from trezor.ui.layouts import confirm_blob_prefix
+
+ confirmed_len = 0
+ progress_bar: ConfirmDataFn | None = None
+
+ async def confirm_fn(chunk: AnyBytes) -> None:
+ nonlocal confirmed_len
+ nonlocal progress_bar
+
+ if progress_bar is not None:
+ return await progress_bar(chunk)
+
+ # for efficient chunk slicing (see below)
+ chunk = memoryview(chunk)
+ while True:
+ assert 0 <= confirmed_len <= total_len
+ prefix_len = await confirm_blob_prefix(
+ title=TR.ethereum__title_input_data,
+ data=chunk,
+ total_len=total_len,
+ confirmed_len=confirmed_len,
+ br_name="confirm_data",
+ br_code=ButtonRequestType.SignTx,
+ )
+ if prefix_len is None:
+ # skip this and following chunks confirmation - use a progress bar instead
+ assert progress_bar is None
+ progress_bar = get_progress_indicator(total_len, confirmed_len)
+ return await progress_bar(chunk)
+ else:
+ confirmed_len += prefix_len
+ chunk = chunk[prefix_len:]
+ if not chunk:
+ return
+
+ return confirm_fn
diff --git a/core/src/apps/ethereum/sc_constants.py b/core/src/apps/ethereum/sc_constants.py
deleted file mode 100644
index 24574e3c..00000000
--- a/core/src/apps/ethereum/sc_constants.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from micropython import const
-
-# smart contract 'data' field lengths in bytes
-SC_FUNC_SIG_BYTES = const(4)
-SC_ARGUMENT_BYTES = const(32)
-SC_ARGUMENT_ADDRESS_BYTES = const(20)
-assert SC_ARGUMENT_ADDRESS_BYTES <= SC_ARGUMENT_BYTES
diff --git a/core/src/apps/ethereum/sign_tx.py b/core/src/apps/ethereum/sign_tx.py
index a5cf4c5e..1426f0cf 100644
--- a/core/src/apps/ethereum/sign_tx.py
+++ b/core/src/apps/ethereum/sign_tx.py
@@ -7,12 +7,16 @@ from trezor.crypto import rlp
from trezor.messages import EthereumTxRequest
from trezor.wire import DataError
-from .helpers import address_from_bytes, bytes_from_address
+from .helpers import (
+ address_from_bytes,
+ bytes_from_address,
+ get_data_confirmer,
+ get_progress_indicator,
+)
from .keychain import with_keychain_from_chain_id
if TYPE_CHECKING:
- from buffer_types import AnyBytes
- from typing import Any, Awaitable, Callable, Coroutine, Iterable
+ from typing import Any, Coroutine, Iterable
from trezor.messages import EthereumSignTx, EthereumTxAck
from trezor.ui.layouts import StrPropertyType
@@ -21,10 +25,9 @@ if TYPE_CHECKING:
from apps.common.payment_request import PaymentRequestVerifier
from .definitions import Definitions
+ from .helpers import ConfirmDataFn
from .keychain import MsgInSignTx
- ConfirmDataFn = Callable[[AnyBytes], Awaitable[None]]
-
# Maximum chain_id which returns the full signature_v (which must fit into an uint32).
# chain_ids larger than this will only return one bit and the caller must recalculate
@@ -112,7 +115,6 @@ async def sign_tx(
address_bytes,
maximum_fee,
fee_items,
- data_total,
payment_req_verifier,
)
@@ -156,78 +158,6 @@ async def sign_tx(
return result
-def make_progress(total_len: int, progress_len: int = 0) -> ConfirmDataFn:
- from trezor.ui.layouts.progress import progress
-
- if __debug__:
- from trezor import log
-
- def _progress_value() -> int:
- assert 0 <= progress_len <= total_len
- if total_len == 0:
- return 1000
- return (1000 * progress_len) // total_len
-
- layout = progress(title=TR.progress__loading_transaction)
- layout.value = _progress_value()
-
- async def confirm_fn(chunk: AnyBytes) -> None:
- nonlocal progress_len
-
- if __debug__:
- log.debug(
- __name__,
- "chunk=%d [%d/%d]",
- len(chunk),
- progress_len,
- total_len,
- )
- progress_len += len(chunk)
- layout.report(_progress_value())
-
- return confirm_fn
-
-
-def make_confirm_data(total_len: int) -> ConfirmDataFn:
- from trezor.enums import ButtonRequestType
- from trezor.ui.layouts import confirm_blob_prefix
-
- confirmed_len = 0
- progress_bar: ConfirmDataFn | None = None
-
- async def confirm_fn(chunk: AnyBytes) -> None:
- nonlocal confirmed_len
- nonlocal progress_bar
-
- if progress_bar is not None:
- return await progress_bar(chunk)
-
- # for efficient chunk slicing (see below)
- chunk = memoryview(chunk)
- while True:
- assert 0 <= confirmed_len <= total_len
- prefix_len = await confirm_blob_prefix(
- title=TR.ethereum__title_input_data,
- data=chunk,
- total_len=total_len,
- confirmed_len=confirmed_len,
- br_name="confirm_data",
- br_code=ButtonRequestType.SignTx,
- )
- if prefix_len is None:
- # skip this and following chunks confirmation - use a progress bar instead
- assert progress_bar is None
- progress_bar = make_progress(total_len, confirmed_len)
- return await progress_bar(chunk)
- else:
- confirmed_len += prefix_len
- chunk = chunk[prefix_len:]
- if not chunk:
- return
-
- return confirm_fn
-
-
async def confirm_tx_data(
msg: MsgInSignTx,
defs: Definitions,
@@ -235,7 +165,6 @@ async def confirm_tx_data(
address_bytes: bytes,
maximum_fee: str,
fee_items: Iterable[StrPropertyType],
- data_total_len: int,
payment_request_verifier: PaymentRequestVerifier | None,
) -> tuple[ConfirmDataFn, Coroutine[Any, Any, None]]:
"""Returns data chunk callback and transaction summary layout to be awaited."""
@@ -246,6 +175,7 @@ async def confirm_tx_data(
from .layout import require_confirm_payment_request, require_confirm_tx
# local_cache_attribute
+ data_length = msg.data_length
network = defs.network
staking_approver = staking.get_approver(
@@ -254,7 +184,7 @@ async def confirm_tx_data(
if staking_approver is not None:
if payment_request_verifier is not None:
raise DataError("Payment Requests don't support staking")
- return make_progress(data_total_len), staking_approver
+ return staking_approver
if tx_type == EIP_7702_TX_TYPE:
# we have already made sure that the address is a known address
@@ -274,14 +204,14 @@ async def confirm_tx_data(
if clear_signing_approver is not None:
if payment_request_verifier is not None:
raise DataError("Payment Requests don't support contract interactions")
- return make_progress(data_total_len), clear_signing_approver
+ return clear_signing_approver
recipient_str = (
address_from_bytes(address_bytes, network) if address_bytes else None
)
if payment_request_verifier is not None:
- if data_total_len > 0:
+ if data_length > 0:
raise DataError("Payment Requests don't support contract interactions")
# If a payment_request_verifier is provided, then msg.payment_req must have been set.
@@ -290,7 +220,7 @@ async def confirm_tx_data(
payment_request_verifier.add_output(value, recipient_str or "")
payment_request_verifier.verify()
- return make_progress(data_total_len), require_confirm_payment_request(
+ return get_progress_indicator(data_length), require_confirm_payment_request(
recipient_str,
msg.payment_req,
msg.address_n,
@@ -303,11 +233,11 @@ async def confirm_tx_data(
None,
)
else:
- if data_total_len > 0:
+ if data_length > 0:
# blind signing: we have data but `clear_signing` did not recognize the function
- confirm_data_chunk = make_confirm_data(data_total_len)
+ confirm_data_chunk = get_data_confirmer(data_length)
else:
- confirm_data_chunk = make_progress(data_total_len)
+ confirm_data_chunk = get_progress_indicator(data_length)
token = (
None # what we want to confirm here is the ETH amount being sent on-chain
@@ -320,7 +250,7 @@ async def confirm_tx_data(
maximum_fee,
fee_items,
token,
- is_send=(data_total_len == 0 and tx_type != EIP_7702_TX_TYPE),
+ is_send=(data_length == 0 and tx_type != EIP_7702_TX_TYPE),
chunkify=bool(msg.chunkify),
)
diff --git a/core/src/apps/ethereum/sign_tx_eip1559.py b/core/src/apps/ethereum/sign_tx_eip1559.py
index 860af3c0..5484c0a5 100644
--- a/core/src/apps/ethereum/sign_tx_eip1559.py
+++ b/core/src/apps/ethereum/sign_tx_eip1559.py
@@ -89,7 +89,6 @@ async def sign_tx_eip1559(
address_bytes,
maximum_fee,
fee_items,
- data_total,
payment_req_verifier,
)
diff --git a/core/src/apps/ethereum/staking.py b/core/src/apps/ethereum/staking.py
index 04c03506..e614993f 100644
--- a/core/src/apps/ethereum/staking.py
+++ b/core/src/apps/ethereum/staking.py
@@ -10,6 +10,7 @@ if TYPE_CHECKING:
from trezor.messages import EthereumNetworkInfo
from trezor.ui.layouts import StrPropertyType
+ from .helpers import ConfirmDataFn
from .keychain import MsgInSignTx
@@ -34,14 +35,18 @@ def get_approver(
address_bytes: bytes,
maximum_fee: str,
fee_items: Iterable[StrPropertyType],
-) -> Coroutine[Any, Any, None] | None:
+) -> tuple[ConfirmDataFn, Coroutine[Any, Any, None]] | None:
"""
Returns a awaitable confirmation for ETH staking approval.
`None` is returned for non-staking related transactions.
"""
- from .sc_constants import SC_FUNC_SIG_BYTES
+ from .clear_signing import SC_FUNC_SIG_BYTES
+ from .helpers import get_progress_indicator
+
+ # local_cache_attribute
+ data_length = msg.data_length
if msg.data_length > len(msg.data_initial_chunk):
return None
@@ -53,17 +58,17 @@ def get_approver(
func_sig = data_reader.read_memoryview(SC_FUNC_SIG_BYTES)
if address_bytes in ADDRESSES_POOL:
if func_sig == FUNC_SIG_STAKE:
- return _handle_staking_tx_stake(
+ return get_progress_indicator(data_length), _handle_staking_tx_stake(
data_reader, msg, network, address_bytes, maximum_fee, fee_items
)
if func_sig == FUNC_SIG_UNSTAKE:
- return _handle_staking_tx_unstake(
+ return get_progress_indicator(data_length), _handle_staking_tx_unstake(
data_reader, msg, network, address_bytes, maximum_fee, fee_items
)
if address_bytes in ADDRESSES_ACCOUNTING:
if func_sig == FUNC_SIG_CLAIM:
- return _handle_staking_tx_claim(
+ return get_progress_indicator(data_length), _handle_staking_tx_claim(
data_reader,
msg,
address_bytes,
@@ -86,12 +91,11 @@ async def _handle_staking_tx_stake(
fee_items: Iterable[StrPropertyType],
) -> None:
from .layout import require_confirm_stake
- from .sc_constants import SC_ARGUMENT_BYTES
# stake args:
# - arg0: uint64, source (1 for Trezor)
try:
- _ = data_reader.read_memoryview(SC_ARGUMENT_BYTES) # skip arg0
+ _ = data_reader.read_memoryview(32) # skip arg0
if data_reader.remaining_count() != 0:
raise ValueError # wrong number of arguments for stake (should be 1)
except (ValueError, EOFError):
@@ -117,18 +121,15 @@ async def _handle_staking_tx_unstake(
fee_items: Iterable[StrPropertyType],
) -> None:
from .layout import require_confirm_unstake
- from .sc_constants import SC_ARGUMENT_BYTES
# unstake args:
# - arg0: uint256, value
# - arg1: uint16, isAllowedInterchange (bool)
# - arg2: uint64, source (1 for Trezor)
try:
- value = int.from_bytes(
- data_reader.read_memoryview(SC_ARGUMENT_BYTES), "big"
- ) # parse arg0
- _ = data_reader.read_memoryview(SC_ARGUMENT_BYTES) # skip arg1
- _ = data_reader.read_memoryview(SC_ARGUMENT_BYTES) # skip arg2
+ value = int.from_bytes(data_reader.read_memoryview(32), "big") # parse arg0
+ _ = data_reader.read_memoryview(32) # skip arg1
+ _ = data_reader.read_memoryview(32) # skip arg2
if data_reader.remaining_count() != 0:
raise ValueError # wrong number of arguments for unstake (should be 3)
except (ValueError, EOFError):
Why this scored 38/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.