feat(tron): add (Un)DelegateResourceContract support.
What changed, and why it matters
This commit adds support for two new Tron blockchain transaction types—delegating and undelegating bandwidth or energy resources—on Trezor hardware wallets. It is a feature addition, not a security fix. The code adds user confirmation screens, input validation, and test fixtures, with no obvious signs of a vulnerability being patched.
No immediate security action required. Review the new validation logic and confirmation flows during normal QA for the Tron feature release.
Security signals we found
Feature addition for new Tron contract types
Input validation added for delegation parameters (minimum balance, max lock period, lock/lock_period consistency)
User confirmation prompts added for both delegate and undelegate operations
No vendor disclosure of security relevance
No attribution to independent security researcher
Evidence from the diff
The commit implements Tron DelegateResourceContract and UnDelegateResourceContract handling in the Trezor firmware. It adds message parsing in trezorlib, contract-specific validation in sign_tx.py (minimum balance, lock period bounds, consistency between lock flag and lock_period), user-facing confirmation layouts, and test vectors. The change also refactors some string formatting imports and adds a chunkify_number helper. There is no evidence in the commit or supplied references that this is a security patch or that it addresses a disclosed vulnerability.
Changed components
core/src/apps/tron/sign_tx.pycore/src/apps/tron/layout.pycore/src/trezor/strings.pypython/src/trezorlib/tron.pycommon/tests/fixtures/tron/sign_tx.jsonInspect captured patch +532 / −6
### common/tests/fixtures/tron/sign_tx.json
@@ -1424,6 +1424,300 @@
"result": {
"signature": "460fe864e1367df5c5b35e1c4a71588af06fd20337d2323b5c69fc6f31f1f9961374e0fb942aede50bbcdfebedadcb8f542c4b3188563d1c56fba3d52d59286b1B"
}
+ },
+ {
+ "name": "Delegate_Energy_with_lock_default_period",
+ "parameters": {
+ "address_n": "m/44'/195'/0'/0/0",
+ "tx": {
+ "ref_block_bytes": "66f5",
+ "ref_block_hash": "8f8142c7157dc54c",
+ "expiration": 1781261643000,
+ "timestamp": 1781257984314
+ },
+ "contract": {
+ "_message_type": "TronDelegateResourceContract",
+ "owner_address": "41f2cd810c48c401d392ead3c6e1e1cb9f57750a58",
+ "resource": "ENERGY",
+ "balance": 1000000,
+ "receiver_address": "41f59b1e8c74ed1a925eef999c9f6f2dac11c11a9f",
+ "lock": true,
+ "lock_period": 86400
+ },
+ "raw_data_hex": "0a0266f522088f8142c7157dc54c40f8d9dedbeb335a77083912730a35747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e44656c65676174655265736f75726365436f6e7472616374123a0a1541f2cd810c48c401d392ead3c6e1e1cb9f57750a58100118c0843d221541f59b1e8c74ed1a925eef999c9f6f2dac11c11a9f28013080a30570bab2ffd9eb33",
+ "raw": {
+ "visible": false,
+ "txID": "103605661c49e3bc35909c0f59a1b044057c7c6166e242266fbe174f4f71ddd7",
+ "raw_data_hex": "0a0266f522088f8142c7157dc54c40f8d9dedbeb335a77083912730a35747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e44656c65676174655265736f75726365436f6e7472616374123a0a1541f2cd810c48c401d392ead3c6e1e1cb9f57750a58100118c0843d221541f59b1e8c74ed1a925eef999c9f6f2dac11c11a9f28013080a30570bab2ffd9eb33",
+ "raw_data": {
+ "contract": [
+ {
+ "parameter": {
+ "value": {
+ "balance": 1000000,
+ "resource": "ENERGY",
+ "receiver_address": "41f59b1e8c74ed1a925eef999c9f6f2dac11c11a9f",
+ "lock": true,
+ "owner_address": "41f2cd810c48c401d392ead3c6e1e1cb9f57750a58",
+ "lock_period": 86400
+ },
+ "type_url": "type.googleapis.com/protocol.DelegateResourceContract"
+ },
+ "type": "DelegateResourceContract"
+ }
+ ],
+ "ref_block_bytes": "66f5",
+ "ref_block_hash": "8f8142c7157dc54c",
+ "expiration": 1781261643000,
+ "timestamp": 1781257984314
+ }
+ }
+ },
+ "result": {
+ "signature": "a5f63ccd2987772bba930a30ceb9afa71ed86646a5de8f4c61119262d26312b47ae1b14b338f2125fb729cb71fd9a69bd79407ac0a5cb530dbfaf4fdf1fb2fb51C"
+ }
+ },
+ {
+ "name": "Delegate_Energy_with_lock_no_period",
+ "parameters": {
+ "address_n": "m/44'/195'/0'/0/0",
+ "tx": {
+ "ref_block_bytes": "5c1a",
+ "ref_block_hash": "17c5a464f19f4fb6",
+ "expiration": 1781253222000,
+ "timestamp": 1781249563856
+ },
+ "contract": {
+ "_message_type": "TronDelegateResourceContract",
+ "owner_address": "41f2cd810c48c401d392ead3c6e1e1cb9f57750a58",
+ "resource": "ENERGY",
+ "balance": 1000000,
+ "receiver_address": "41f59b1e8c74ed1a925eef999c9f6f2dac11c11a9f",
+ "lock": true
+ },
+ "raw_data_hex": "0a025c1a220817c5a464f19f4fb640f0dcdcd7eb335a730839126f0a35747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e44656c65676174655265736f75726365436f6e747261637412360a1541f2cd810c48c401d392ead3c6e1e1cb9f57750a58100118c0843d221541f59b1e8c74ed1a925eef999c9f6f2dac11c11a9f280170d0b9fdd5eb33",
+ "raw": {
+ "visible": false,
+ "txID": "660a6abab18e55a65a535425a80f553e1c430ba06cd559aaaac7e65de6ff2315",
+ "raw_data_hex": "0a025c1a220817c5a464f19f4fb640f0dcdcd7eb335a730839126f0a35747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e44656c65676174655265736f75726365436f6e747261637412360a1541f2cd810c48c401d392ead3c6e1e1cb9f57750a58100118c0843d221541f59b1e8c74ed1a925eef999c9f6f2dac11c11a9f280170d0b9fdd5eb33",
+ "raw_data": {
+ "contract": [
+ {
+ "parameter": {
+ "value": {
+ "balance": 1000000,
+ "resource": "ENERGY",
+ "receiver_address": "41f59b1e8c74ed1a925eef999c9f6f2dac11c11a9f",
+ "lock": true,
+ "owner_address": "41f2cd810c48c401d392ead3c6e1e1cb9f57750a58"
+ },
+ "type_url": "type.googleapis.com/protocol.DelegateResourceContract"
+ },
+ "type": "DelegateResourceContract"
+ }
+ ],
+ "ref_block_bytes": "5c1a",
+ "ref_block_hash": "17c5a464f19f4fb6",
+ "expiration": 1781253222000,
+ "timestamp": 1781249563856
+ }
+ }
+ },
+ "result": {
+ "signature": "c05c95aabc04b7e6063928379ef1a843fba63556880255697503b39c6bc51fa34427cca7a6030d0971aa0cb3c18dbf548c3f49439456a9e057ca14fff7648fff1C"
+ }
+ },
+ {
+ "name": "Delegate_Energy",
+ "parameters": {
+ "address_n": "m/44'/195'/0'/0/0",
+ "tx": {
+ "ref_block_bytes": "5c22",
+ "ref_block_hash": "7fbe4c8e28c4be67",
+ "expiration": 1781253246000,
+ "timestamp": 1781249588466
+ },
+ "contract": {
+ "_message_type": "TronDelegateResourceContract",
+ "owner_address": "41f2cd810c48c401d392ead3c6e1e1cb9f57750a58",
+ "resource": "ENERGY",
+ "balance": 1000000,
+ "receiver_address": "41f59b1e8c74ed1a925eef999c9f6f2dac11c11a9f"
+ },
+ "raw_data_hex": "0a025c2222087fbe4c8e28c4be6740b098ded7eb335a710839126d0a35747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e44656c65676174655265736f75726365436f6e747261637412340a1541f2cd810c48c401d392ead3c6e1e1cb9f57750a58100118c0843d221541f59b1e8c74ed1a925eef999c9f6f2dac11c11a9f70f2f9fed5eb33",
+ "raw": {
+ "visible": false,
+ "txID": "3e657fd5a1d85c161ddab09b50d81f3cd0c73c8097b262a6dae2cd870f7d701a",
+ "raw_data_hex": "0a025c2222087fbe4c8e28c4be6740b098ded7eb335a710839126d0a35747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e44656c65676174655265736f75726365436f6e747261637412340a1541f2cd810c48c401d392ead3c6e1e1cb9f57750a58100118c0843d221541f59b1e8c74ed1a925eef999c9f6f2dac11c11a9f70f2f9fed5eb33",
+ "raw_data": {
+ "contract": [
+ {
+ "parameter": {
+ "value": {
+ "balance": 1000000,
+ "resource": "ENERGY",
+ "receiver_address": "41f59b1e8c74ed1a925eef999c9f6f2dac11c11a9f",
+ "owner_address": "41f2cd810c48c401d392ead3c6e1e1cb9f57750a58"
+ },
+ "type_url": "type.googleapis.com/protocol.DelegateResourceContract"
+ },
+ "type": "DelegateResourceContract"
+ }
+ ],
+ "ref_block_bytes": "5c22",
+ "ref_block_hash": "7fbe4c8e28c4be67",
+ "expiration": 1781253246000,
+ "timestamp": 1781249588466
+ }
+ }
+ },
+ "result": {
+ "signature": "f97c4162cbc8d3761ff85761ffaf23ace493037ce55b0a5276585ccb3fdcf813251bc6480eda6843ea13d9fbe35c4aa084e66c2e1371160dfb8dae06a2c7c2631B"
+ }
+ },
+ {
+ "name": "Delegate_Bandwidth",
+ "parameters": {
+ "address_n": "m/44'/195'/0'/0/0",
+ "tx": {
+ "ref_block_bytes": "f369",
+ "ref_block_hash": "72bad941dfc6ff17",
+ "expiration": 1781172009000,
+ "timestamp": 1781168350883
+ },
+ "contract": {
+ "_message_type": "TronDelegateResourceContract",
+ "owner_address": "41f2cd810c48c401d392ead3c6e1e1cb9f57750a58",
+ "balance": 1000000,
+ "receiver_address": "41f59b1e8c74ed1a925eef999c9f6f2dac11c11a9f"
+ },
+ "raw_data_hex": "0a02f369220872bad941dfc6ff1740a8f0ffb0eb335a6f0839126b0a35747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e44656c65676174655265736f75726365436f6e747261637412320a1541f2cd810c48c401d392ead3c6e1e1cb9f57750a5818c0843d221541f59b1e8c74ed1a925eef999c9f6f2dac11c11a9f70a3cda0afeb33",
+ "raw": {
+ "visible": false,
+ "txID": "d69f0ffa7a484d648bab90362e3f7672ff73f06a5075df2096d96e1bb812011c",
+ "raw_data_hex": "0a02f369220872bad941dfc6ff1740a8f0ffb0eb335a6f0839126b0a35747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e44656c65676174655265736f75726365436f6e747261637412320a1541f2cd810c48c401d392ead3c6e1e1cb9f57750a5818c0843d221541f59b1e8c74ed1a925eef999c9f6f2dac11c11a9f70a3cda0afeb33",
+ "raw_data": {
+ "contract": [
+ {
+ "parameter": {
+ "value": {
+ "balance": 1000000,
+ "receiver_address": "41f59b1e8c74ed1a925eef999c9f6f2dac11c11a9f",
+ "owner_address": "41f2cd810c48c401d392ead3c6e1e1cb9f57750a58"
+ },
+ "type_url": "type.googleapis.com/protocol.DelegateResourceContract"
+ },
+ "type": "DelegateResourceContract"
+ }
+ ],
+ "ref_block_bytes": "f369",
+ "ref_block_hash": "72bad941dfc6ff17",
+ "expiration": 1781172009000,
+ "timestamp": 1781168350883
+ }
+ }
+ },
+ "result": {
+ "signature": "390e587ef879d512bb211854b07341ddfdc385f783698524750d7255c77309bb5401dc47ac4746b9013100729eebd33ebe58b1bbb035c698a8678dd1143b9efa1C"
+ }
+ },
+ {
+ "name": "Delegate_Energy_with_lock_period",
+ "parameters": {
+ "address_n": "m/44'/195'/0'/0/0",
+ "tx": {
+ "ref_block_bytes": "96fd",
+ "ref_block_hash": "01173b845095f921",
+ "expiration": 1781298903000,
+ "timestamp": 1781295243256
+ },
+ "contract": {
+ "_message_type": "TronDelegateResourceContract",
+ "owner_address": "41f2cd810c48c401d392ead3c6e1e1cb9f57750a58",
+ "resource": "ENERGY",
+ "balance": 1000000,
+ "receiver_address": "41f59b1e8c74ed1a925eef999c9f6f2dac11c11a9f",
+ "lock": true,
+ "lock_period": 123456
+ },
+ "raw_data_hex": "0a0296fd220801173b845095f92140d8efc0edeb335a77083912730a35747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e44656c65676174655265736f75726365436f6e7472616374123a0a1541f2cd810c48c401d392ead3c6e1e1cb9f57750a58100118c0843d221541f59b1e8c74ed1a925eef999c9f6f2dac11c11a9f280130c0c40770f8bfe1ebeb33",
+ "raw": {
+ "visible": false,
+ "txID": "8a8e6b50aca0dd1ca53392e1df99dc842aa18395564a1959b492049262506f4f",
+ "raw_data_hex": "0a0296fd220801173b845095f92140d8efc0edeb335a77083912730a35747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e44656c65676174655265736f75726365436f6e7472616374123a0a1541f2cd810c48c401d392ead3c6e1e1cb9f57750a58100118c0843d221541f59b1e8c74ed1a925eef999c9f6f2dac11c11a9f280130c0c40770f8bfe1ebeb33",
+ "raw_data": {
+ "contract": [
+ {
+ "parameter": {
+ "value": {
+ "balance": 1000000,
+ "resource": "ENERGY",
+ "receiver_address": "41f59b1e8c74ed1a925eef999c9f6f2dac11c11a9f",
+ "lock": true,
+ "owner_address": "41f2cd810c48c401d392ead3c6e1e1cb9f57750a58",
+ "lock_period": 123456
+ },
+ "type_url": "type.googleapis.com/protocol.DelegateResourceContract"
+ },
+ "type": "DelegateResourceContract"
+ }
+ ],
+ "ref_block_bytes": "96fd",
+ "ref_block_hash": "01173b845095f921",
+ "expiration": 1781298903000,
+ "timestamp": 1781295243256
+ }
+ }
+ },
+ "result": {
+ "signature": "f68796ebbf5c9306c2e6468e5a7c030b7325b704d5a62350b378199af7b35b24461c44bb5ffb894626505bf0403152a6c6955897a1005486c54d0fba9fb88e101C"
+ }
+ },
+ {
+ "name": "Undelegate_Bandwidth",
+ "parameters": {
+ "address_n": "m/44'/195'/0'/0/0",
+ "tx": {
+ "ref_block_bytes": "f37d",
+ "ref_block_hash": "96f8f7fefcf225a1",
+ "expiration": 1781169669000,
+ "timestamp": 1781168410623
+ },
+ "contract": {
+ "_message_type": "TronUnDelegateResourceContract",
+ "owner_address": "41f2cd810c48c401d392ead3c6e1e1cb9f57750a58",
+ "balance": 1000000,
+ "receiver_address": "41f59b1e8c74ed1a925eef999c9f6f2dac11c11a9f"
+ },
+ "raw_data_hex": "0a02f37d220896f8f7fefcf225a1408887f1afeb335a71083a126d0a37747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e556e44656c65676174655265736f75726365436f6e747261637412320a1541f2cd810c48c401d392ead3c6e1e1cb9f57750a5818c0843d221541f59b1e8c74ed1a925eef999c9f6f2dac11c11a9f70ff9fa4afeb33",
+ "raw": {
+ "visible": false,
+ "txID": "690ace4bea2eb210140fc95be26ef09b6d9df4d4cc9a631919acba0b8a29a5fb",
+ "raw_data_hex": "0a02f37d220896f8f7fefcf225a1408887f1afeb335a71083a126d0a37747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e556e44656c65676174655265736f75726365436f6e747261637412320a1541f2cd810c48c401d392ead3c6e1e1cb9f57750a5818c0843d221541f59b1e8c74ed1a925eef999c9f6f2dac11c11a9f70ff9fa4afeb33",
+ "raw_data": {
+ "contract": [
+ {
+ "parameter": {
+ "value": {
+ "balance": 1000000,
+ "receiver_address": "41f59b1e8c74ed1a925eef999c9f6f2dac11c11a9f",
+ "owner_address": "41f2cd810c48c401d392ead3c6e1e1cb9f57750a58"
+ },
+ "type_url": "type.googleapis.com/protocol.UnDelegateResourceContract"
+ },
+ "type": "UnDelegateResourceContract"
+ }
+ ],
+ "ref_block_bytes": "f37d",
+ "ref_block_hash": "96f8f7fefcf225a1",
+ "expiration": 1781169669000,
+ "timestamp": 1781168410623
+ }
+ }
+ },
+ "result": {
+ "signature": "c53e9f67de5b57834e22603934ca3d35a15d0186e37e761c59163c66fccfa74f1dba4129f6f0ca4a6aedf963cb50031569d9d044bf4eab7cc3507f5461b1f7e71C"
+ }
}
]
}
### core/.changelog.d/7100.added
@@ -0,0 +1 @@
+Tron: Support for Resource delegation.
### core/src/apps/tron/README.md
@@ -34,6 +34,8 @@ Therefore, we do not show any fees to the user on the device. The host applicati
- [X] `WithdrawExpireUnfreeze`: Claim Unstaked `TRX` past their lock-up period
- [X] `VoteWitnessContract`: Vote using earned TRON Power.
- [X] `WithdrawBalanceContract`: Claim Voting rewards.
+- [X] `DelegateResourceContract`: Delegate Energy or Bandwidth to other users.
+- [X] `UnDelegateResourceContract`: Undelegate previously delegated resource from other users.
### Notes
### core/src/apps/tron/layout.py
@@ -2,7 +2,8 @@
from typing import TYPE_CHECKING
import trezor.ui.layouts as layouts
-from trezor import TR, strings
+from trezor import TR
+from trezor.strings import chunkify_number, format_amount
from .helpers import get_encoded_address
@@ -20,15 +21,35 @@ def format_trx_amount(amount: int) -> str:
# 1 SUN = 0.000001 TRX
_TRX_AMOUNT_DECIMALS = const(6)
- return f"{strings.format_amount(amount, _TRX_AMOUNT_DECIMALS)} TRX"
+ return f"{format_amount(amount, _TRX_AMOUNT_DECIMALS)} TRX"
def format_token_amount(amount: int, token_decimals: int, token_symbol: str) -> str:
- return f"{strings.format_amount(amount, token_decimals)} {token_symbol}"
+ return f"{format_amount(amount, token_decimals)} {token_symbol}"
def format_energy_amount(amount: int) -> str:
- return f"{strings.format_amount(amount, 0)} SUN"
+ return f"{format_amount(amount, 0)} SUN"
+
+
+def format_blocks_as_time(blocks: int) -> str:
+
+ # Tron gives each SR 3 seconds to produce a block.
+ total_seconds = blocks * 3
+
+ days, rem = divmod(total_seconds, 86400)
+ hours, rem = divmod(rem, 3600)
+ minutes, seconds = divmod(rem, 60)
+ parts = []
+ if days:
+ parts.append(f"{days}d")
+ if hours:
+ parts.append(f"{hours}h")
+ if minutes:
+ parts.append(f"{minutes}m")
+ if seconds:
+ parts.append(f"{seconds}s")
+ return " ".join(parts)
async def confirm_trx_transfer(
@@ -191,6 +212,86 @@ async def confirm_claim(
)
+async def confirm_delegate_resource(
+ receiver_address: AnyBytes,
+ balance: int,
+ resource: int,
+ lock_period: int | None,
+) -> None:
+ from trezor.enums import TronResourceCode
+ from trezor.ui.layouts import confirm_address, confirm_properties
+
+ title = TR.tron__delegate_resource
+ br_name = "tron/delegate"
+
+ await confirm_address(
+ title=title,
+ description=TR.words__recipient,
+ address=get_encoded_address(receiver_address),
+ chunkify=True,
+ br_name=f"{br_name}/recipient",
+ )
+
+ await confirm_properties(
+ br_name=f"{br_name}/resource",
+ title=title,
+ props=[
+ (TR.words__amount, format_trx_amount(balance), False),
+ (
+ TR.words__resource,
+ "Energy" if resource == TronResourceCode.ENERGY else "Bandwidth",
+ False,
+ ),
+ ],
+ hold=lock_period is None,
+ )
+ if lock_period:
+ await confirm_properties(
+ br_name=f"{br_name}/lock_period",
+ title=title,
+ subtitle=TR.tron__lock_period,
+ props=[
+ (TR.tron__lockperiod_time, format_blocks_as_time(lock_period), True),
+ (TR.tron__lockperiod_blocks, chunkify_number(lock_period), True),
+ ],
+ hold=True,
+ )
+
+
+async def confirm_undelegate_resource(
+ receiver_address: AnyBytes,
+ balance: int,
+ resource: int,
+) -> None:
+ from trezor.enums import TronResourceCode
+ from trezor.ui.layouts import confirm_address, confirm_properties
+
+ title = TR.tron__undelegate_resource
+ br_name = "tron/undelegate"
+
+ await confirm_address(
+ title=title,
+ description=TR.words__from_title,
+ address=get_encoded_address(receiver_address),
+ chunkify=True,
+ br_name=f"{br_name}/from_address",
+ )
+
+ await confirm_properties(
+ title=title,
+ props=[
+ (TR.words__amount, format_trx_amount(balance), False),
+ (
+ TR.words__resource,
+ "Energy" if resource == TronResourceCode.ENERGY else "Bandwidth",
+ False,
+ ),
+ ],
+ br_name=f"{br_name}/resource",
+ hold=True,
+ )
+
+
async def confirm_votes(contract: TronVoteWitnessContract) -> None:
from trezor.ui.layouts import confirm_tron_voting
### core/src/apps/tron/sign_tx.py
@@ -93,6 +93,20 @@ async def process_contract(
from .helpers import get_encoded_address
+ # https://github.com/tronprotocol/java-tron/blob/1691fddbd4f8df35df31713cc7772273d7d03360/actuator/src/main/java/org/tron/core/actuator/DelegateResourceActuator.java#L149
+ _MINIMUM_DELEGATION_BALANCE = const(1_000_000) # 1 TRX in SUN
+ # Network parameter valuegetMaxDelegateLockPeriod = 864000 (30D) via api.trongrid.io/wallet/getchainparameters
+ # But we're leaving room for future changes since network will reject invalid values anyway.
+ # 1 block per 3 seconds = 20 blocks per minute.
+ _MAX_LOCK_PERIOD_BLOCKS = const(365 * 24 * 60 * 20)
+ # Network defined. Not a protobuff default. (proto3 doesn't support [default = 81400] and Tron uses proto3)
+ _DEFAULT_LOCK_PERIOD_BLOCKS = const(
+ 86400
+ ) # 3 days. TRON's default lock period when lock is true but period unspecified.
+
+ # 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)
@@ -102,11 +116,14 @@ async def process_contract(
is_different_owner = owner_address is not None and owner_address != signer_address
if messages.TronTransferContract.is_type_of(contract):
- from .layout import confirm_trx_transfer
- contract_type = TronRawContractType.TransferContract
+ # Contract specific validation
if contract.amount > _INT64_MAX:
raise DataError("Tron: invalid transfer amount")
+ contract_type = TronRawContractType.TransferContract
+
+ from .layout import confirm_trx_transfer
+
await confirm_trx_transfer(contract, account_details, chunkify)
elif messages.TronTriggerSmartContract.is_type_of(contract):
@@ -174,10 +191,84 @@ async def process_contract(
)
elif messages.TronVoteWitnessContract.is_type_of(contract):
+
+ # Contract specific validation
if len(contract.votes) > 9:
raise DataError("Tron: too many votes")
+
contract_type = TronRawContractType.VoteWitnessContract
await layout.confirm_votes(contract)
+
+ elif messages.TronDelegateResourceContract.is_type_of(contract):
+
+ raw_lock_period = contract.lock_period # local_cache_attribute
+ lock = contract.lock # local_cache_attribute
+
+ lock_period = None
+ # Contract specific validation
+ if lock:
+ lock_period = (
+ raw_lock_period
+ if raw_lock_period not in (None, 0)
+ else _DEFAULT_LOCK_PERIOD_BLOCKS
+ )
+ assert lock_period is not None
+ if lock_period > _MAX_LOCK_PERIOD_BLOCKS:
+ raise DataError("Tron: Invalid lock period (Max 365d)")
+ elif raw_lock_period is not None:
+ # Tron technically allows this (rejects the lock, encodes the value) but we would rather the host not send inconsistent values.
+ raise DataError("Tron: lock_period should not be set when lock is false")
+ if contract.balance < _MINIMUM_DELEGATION_BALANCE:
+ raise DataError("Tron: Amount too low (Min 1 TRX)")
+
+ from trezor.enums import TronResourceCode
+
+ contract_type = TronRawContractType.DelegateResourceContract
+
+ await layout.confirm_delegate_resource(
+ receiver_address=contract.receiver_address,
+ balance=contract.balance,
+ resource=contract.resource,
+ lock_period=lock_period,
+ )
+
+ # Match proto3 encoding: omit fields that equal their default values
+ contract = messages.TronDelegateResourceContract(
+ owner_address=contract.owner_address,
+ receiver_address=contract.receiver_address,
+ balance=contract.balance,
+ resource=(
+ None
+ if contract.resource == TronResourceCode.BANDWIDTH
+ else contract.resource
+ ),
+ lock=lock or None,
+ lock_period=raw_lock_period if lock else None,
+ )
+
+ elif messages.TronUnDelegateResourceContract.is_type_of(contract):
+ from trezor.enums import TronResourceCode
+
+ contract_type = TronRawContractType.UnDelegateResourceContract
+
+ await layout.confirm_undelegate_resource(
+ receiver_address=contract.receiver_address,
+ balance=contract.balance,
+ resource=contract.resource,
+ )
+
+ # Match proto3 encoding: omit fields that equal their default values
+ contract = messages.TronUnDelegateResourceContract(
+ owner_address=contract.owner_address,
+ receiver_address=contract.receiver_address,
+ balance=contract.balance,
+ resource=(
+ None
+ if contract.resource == TronResourceCode.BANDWIDTH
+ else contract.resource
+ ),
+ )
+
else:
raise DataError("Tron: contract type unknown")
### core/src/trezor/strings.py
@@ -153,3 +153,13 @@ def format_timestamp(timestamp: int) -> str:
# that is used internally.
d = utime.gmtime2000(timestamp - _SECONDS_1970_TO_2000)
return f"{d[0]}-{d[1]:02d}-{d[2]:02d} {d[3]:02d}:{d[4]:02d}:{d[5]:02d}"
+
+
+def chunkify_number(number: int, chunk_size: int = 3) -> str:
+ # Group digits from the right in chunks of `chunk_size`, separated by spaces.
+ # e.g. 123456 => "123 456", 1234 => "1 234"
+ digits = str(number)
+ chunks = []
+ for i in range(len(digits), 0, -chunk_size):
+ chunks.append(digits[max(0, i - chunk_size) : i])
+ return " ".join(reversed(chunks))
### python/.changelog.d/7100.added
@@ -0,0 +1 @@
+Tron: Support for Resource delegation.
### python/src/trezorlib/tron.py
@@ -16,6 +16,8 @@
messages.TronWithdrawUnfreeze,
messages.TronWithdrawBalance,
messages.TronVoteWitnessContract,
+ messages.TronDelegateResourceContract,
+ messages.TronUnDelegateResourceContract,
]
DEFAULT_BIP32_PATH = "m/44h/195h/0h/0/0"
@@ -105,6 +107,30 @@ def from_raw_data(
contract = messages.TronVoteWitnessContract(
owner_address=raw_contract.owner_address, votes=raw_contract.votes
)
+ elif contract_type == messages.TronRawContractType.DelegateResourceContract:
+ raw_contract = load_message(
+ io.BytesIO(parameter_value),
+ messages.TronDelegateResourceContract,
+ )
+ contract = messages.TronDelegateResourceContract(
+ owner_address=raw_contract.owner_address,
+ receiver_address=raw_contract.receiver_address,
+ balance=raw_contract.balance,
+ resource=raw_contract.resource,
+ lock_period=raw_contract.lock_period,
+ lock=raw_contract.lock,
+ )
+ elif contract_type == messages.TronRawContractType.UnDelegateResourceContract:
+ raw_contract = load_message(
+ io.BytesIO(parameter_value),
+ messages.TronUnDelegateResourceContract,
+ )
+ contract = messages.TronUnDelegateResourceContract(
+ owner_address=raw_contract.owner_address,
+ receiver_address=raw_contract.receiver_address,
+ balance=raw_contract.balance,
+ resource=raw_contract.resource,
+ )
else:
raise ValueError(f"Unsupported contract type: {contract_type}")
Why this scored 12/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.