feat(tron): core edits to include `call_value` in `TriggerSmartContract`
What changed, and why it matters
This commit changes how Trezor hardware wallets handle Tron smart-contract calls that include native TRX tokens. Previously, the device did not display or validate the `call_value` field, which means a user could be tricked into sending TRX along with a contract call without seeing it on the device screen. The patch adds a safety check that rejects oversized values and shows the TRX amount during transaction confirmation. It is a defensive fix rather than an exploit.
Treat as a security-hardening fix. Review whether any other Tron contract types carry native value fields that are similarly unconfirmed, and ensure the new `call_value` display is tested for both known TRC-20 and unknown smart-contract paths.
Security signals we found
Missing UI confirmation of native asset transfer in smart-contract call
Addition of input validation bound (`call_value > _INT64_MAX`)
Display of previously hidden `call_value` in transaction summary
Client-side protobuf field propagation enabling the new device behavior
Evidence from the diff
The patch updates Tron TriggerSmartContract handling in trezor-firmware. It moves _INT64_MAX to module scope, validates contract.call_value against that bound, and propagates the value through process_smart_contract, process_known_trc20_contract, and the layout confirmation flows (confirm_unknown_smart_contract, confirm_known_trc20_smart_contract). The Python client now forwards call_value from raw contract data into the protobuf message. This closes a gap where native TRX attached to a smart-contract call was silently ignored by the UI.
Changed components
core/src/apps/tron/sign_tx.pycore/src/apps/tron/layout.pypython/src/trezorlib/tron.pyInspect captured patch +34 / −17
### core/src/apps/tron/layout.py
@@ -47,7 +47,10 @@ async def confirm_trx_transfer(
# TODO: Refactor ETH references to crypto-neutral references.
async def confirm_unknown_smart_contract(
- contract: TronTriggerSmartContract, fee_limit: int, chunkify: bool
+ contract: TronTriggerSmartContract,
+ fee_limit: int,
+ trx_value: int | None,
+ chunkify: bool,
) -> None:
from trezor.enums import ButtonRequestType
@@ -79,7 +82,9 @@ async def confirm_unknown_smart_contract(
)
await confirm_tron_summary(
- TR.words__title_summary, None, format_energy_amount(fee_limit)
+ title=TR.words__title_summary,
+ amount=format_trx_amount(trx_value) if trx_value else None,
+ fee=format_energy_amount(fee_limit),
)
@@ -90,6 +95,7 @@ async def confirm_known_trc20_smart_contract(
fee_limit: int,
token_decimals: int,
token_symbol: str,
+ trx_value: int | None,
chunkify: bool,
) -> None:
from trezor.ui.layouts import confirm_tron_approve, confirm_tron_transfer
@@ -110,6 +116,7 @@ async def confirm_known_trc20_smart_contract(
amount_str=amount_str,
is_revoke=is_revoke,
maximum_fee=format_energy_amount(fee_limit),
+ native_amount_str=format_trx_amount(trx_value) if trx_value else None,
chunkify=chunkify,
)
else:
@@ -119,6 +126,7 @@ async def confirm_known_trc20_smart_contract(
int.from_bytes(amount_arg, "big"), token_decimals, token_symbol
),
maximum_fee=format_energy_amount(fee_limit),
+ native_amount_str=format_trx_amount(trx_value) if trx_value else None,
chunkify=chunkify,
)
### core/src/apps/tron/sign_tx.py
@@ -23,6 +23,8 @@
from apps.common.keychain import Keychain
+_INT64_MAX = const(9_223_372_036_854_775_807)
+
@with_slip44_keychain(PATTERN, slip44_id=SLIP44_ID, curve=CURVE)
async def sign_tx(msg: TronSignTx, keychain: Keychain) -> TronSignature:
@@ -91,11 +93,6 @@ async def process_contract(
from .helpers import get_encoded_address
- _INT64_MAX = const(9_223_372_036_854_775_807)
-
- # Every Tron contract carries an `owner_address` (proto field 1). Encode it
- # and compare against the signing address once here so any contract branch
- # can reuse `is_different_owner`.
owner_address_bytes = getattr(contract, "owner_address", None)
owner_address = (
get_encoded_address(owner_address_bytes)
@@ -200,14 +197,24 @@ async def process_contract(
async def process_smart_contract(
contract: TronTriggerSmartContract, fee_limit: int, chunkify: bool
) -> None:
- if await process_known_trc20_contract(contract, fee_limit, chunkify):
+ trx_value = None
+ if contract.call_value:
+ if contract.call_value > _INT64_MAX:
+ raise DataError("Tron: invalid call value")
+ trx_value = contract.call_value
+ if await process_known_trc20_contract(contract, fee_limit, trx_value, chunkify):
return
else:
- await layout.confirm_unknown_smart_contract(contract, fee_limit, chunkify)
+ await layout.confirm_unknown_smart_contract(
+ contract, fee_limit, trx_value, chunkify
+ )
async def process_known_trc20_contract(
- contract: TronTriggerSmartContract, fee_limit: int, chunkify: bool
+ contract: TronTriggerSmartContract,
+ fee_limit: int,
+ trx_value: int | None,
+ chunkify: bool,
) -> bool:
"""Returns False when the contract is unrecognised. i.e. not (Transfer and known TRC-20)"""
from trezor.utils import BufferReader
@@ -250,13 +257,14 @@ async def process_known_trc20_contract(
amount_arg = data_reader.read_memoryview(SC_ARGUMENT_BYTES)
await layout.confirm_known_trc20_smart_contract(
- func_sig == SC_FUNC_SIG_APPROVE,
- recipient,
- amount_arg,
- fee_limit,
- token_decimals,
- token_symbol,
- chunkify,
+ is_approve=func_sig == SC_FUNC_SIG_APPROVE,
+ recipient_addr=recipient,
+ amount_arg=amount_arg,
+ fee_limit=fee_limit,
+ token_decimals=token_decimals,
+ token_symbol=token_symbol,
+ trx_value=trx_value,
+ chunkify=chunkify,
)
return True
### python/src/trezorlib/tron.py
@@ -59,6 +59,7 @@ def from_raw_data(
owner_address=raw_contract.owner_address,
contract_address=raw_contract.contract_address,
data=raw_contract.data,
+ call_value=raw_contract.call_value,
)
elif contract_type == messages.TronRawContractType.FreezeBalanceV2Contract:
raw_contract = load_message(Why this scored 44/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.