From 12478b1716350536b90df91f13d70d0fb0033983 Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Tue, 2 Mar 2021 23:55:56 +0100 Subject: [PATCH] refactor(core): convert apps.stellar to layouts --- core/src/apps/stellar/layout.py | 157 ++++++----- core/src/apps/stellar/operations/layout.py | 254 ++++++++++-------- core/src/trezor/ui/layouts/tt.py | 89 ++++-- .../test_msg_stellar_sign_transaction.py | 156 ++++++++++- 4 files changed, 449 insertions(+), 207 deletions(-) diff --git a/core/src/apps/stellar/layout.py b/core/src/apps/stellar/layout.py index 5b1052fa7..e7a8ab625 100644 --- a/core/src/apps/stellar/layout.py +++ b/core/src/apps/stellar/layout.py @@ -1,8 +1,12 @@ -from trezor import strings, ui, utils +from trezor import strings, ui from trezor.enums import ButtonRequestType -from trezor.ui.components.tt.text import Text - -from apps.common.confirm import require_confirm, require_hold_to_confirm +from trezor.ui.constants import MONO_ADDR_PER_LINE +from trezor.ui.layouts import ( + confirm_action, + confirm_hex, + confirm_metadata, + confirm_timebounds_stellar, +) from . import consts @@ -10,68 +14,107 @@ from . import consts async def require_confirm_init( ctx, address: str, network_passphrase: str, accounts_match: bool ): - text = Text("Confirm Stellar", ui.ICON_SEND, ui.GREEN) - text.normal("Initialize signing with") if accounts_match: - text.normal("your account") - text.mono(*split(trim_to_rows(address, 3))) + description = "Initialize signing with\nyour account" else: - text.mono(*split(address)) - await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) + description = "Initialize signing with" + await require_confirm_op( + ctx, + "confirm_init", + title="Confirm Stellar", + subtitle=None, + description=description, + data=address, + icon=ui.ICON_SEND, + is_account=True, + ) + network = get_network_warning(network_passphrase) if network: - text = Text("Confirm network", ui.ICON_CONFIRM, ui.GREEN) - text.normal("Transaction is on") - text.bold(network) - await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) + await confirm_metadata( + ctx, + "confirm_init_network", + title="Confirm network", + content="Transaction is on {}", + param=network, + icon=ui.ICON_CONFIRM, + br_code=ButtonRequestType.ConfirmOutput, + hide_continue=True, + ) async def require_confirm_timebounds(ctx, start: int, end: int): - text = Text("Confirm timebounds", ui.ICON_SEND, ui.GREEN) - text.bold("Valid from (UTC):") - if start: - text.normal(str(start)) - else: - text.mono("[no restriction]") - - text.bold("Valid to (UTC):") - if end: - text.normal(str(end)) - else: - text.mono("[no restriction]") - - await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) + await confirm_timebounds_stellar(ctx, start, end) async def require_confirm_memo(ctx, memo_type: int, memo_text: str): - text = Text("Confirm memo", ui.ICON_CONFIRM, ui.GREEN) if memo_type == consts.MEMO_TYPE_TEXT: - text.bold("Memo (TEXT)") + description = "Memo (TEXT)" elif memo_type == consts.MEMO_TYPE_ID: - text.bold("Memo (ID)") + description = "Memo (ID)" elif memo_type == consts.MEMO_TYPE_HASH: - text.bold("Memo (HASH)") + description = "Memo (HASH)" elif memo_type == consts.MEMO_TYPE_RETURN: - text.bold("Memo (RETURN)") - else: # MEMO_TYPE_NONE - text.bold("No memo set!") - text.normal("Important: Many exchanges require a memo when depositing") - if memo_type != consts.MEMO_TYPE_NONE: - text.mono(*split(memo_text)) - await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) + description = "Memo (RETURN)" + else: + return await confirm_action( + ctx, + "confirm_memo", + title="Confirm memo", + action="No memo set!", + description="Important: Many exchanges require a memo when depositing", + icon=ui.ICON_CONFIRM, + icon_color=ui.GREEN, + br_code=ButtonRequestType.ConfirmOutput, + ) + + await require_confirm_op( + ctx, + "confirm_memo", + title="Confirm memo", + subtitle=description, + data=memo_text, + split=False, + ) async def require_confirm_final(ctx, fee: int, num_operations: int): - op_str = str(num_operations) + " operation" - if num_operations > 1: - op_str += "s" - text = Text("Final confirm", ui.ICON_SEND, ui.GREEN) - text.normal("Sign this transaction") - text.normal("made up of " + op_str) - text.bold("and pay " + format_amount(fee)) - text.normal("for fee?") - # we use SignTx, not ConfirmOutput, for compatibility with T1 - await require_hold_to_confirm(ctx, text, ButtonRequestType.SignTx) + op_str = strings.format_plural("{count} {plural}", num_operations, "operation") + await confirm_metadata( + ctx, + "confirm_final", + title="Final confirm", + content="Sign this transaction made up of " + op_str + " and pay {}\nfor fee?", + param=format_amount(fee), + hide_continue=True, + hold=True, + ) + + +async def require_confirm_op( + ctx, + br_type: str, + subtitle: str | None, + data: str, + title: str = "Confirm operation", + description: str = None, + icon=ui.ICON_CONFIRM, + split: bool = True, + is_account: bool = False, +): + await confirm_hex( + ctx, + br_type, + title=title, + subtitle=subtitle, + description=description, + data=data, + width=MONO_ADDR_PER_LINE if split else None, + icon=icon, + truncate=True, + truncate_ellipsis=".." if is_account else "", + br_code=ButtonRequestType.ConfirmOutput, + ) def format_amount(amount: int, ticker=True) -> str: @@ -81,22 +124,6 @@ def format_amount(amount: int, ticker=True) -> str: return strings.format_amount(amount, consts.AMOUNT_DECIMALS) + t -def split(text): - return utils.chunks(text, 17) - - -def trim(payload: str, length: int, dots=True) -> str: - if len(payload) > length: - if dots: - return payload[: length - 2] + ".." - return payload[: length - 2] - return payload - - -def trim_to_rows(payload: str, rows: int = 1) -> str: - return trim(payload, rows * 17) - - def get_network_warning(network_passphrase: str): if network_passphrase == consts.NETWORK_PASSPHRASE_PUBLIC: return None diff --git a/core/src/apps/stellar/operations/layout.py b/core/src/apps/stellar/operations/layout.py index 79acbbcea..74d346596 100644 --- a/core/src/apps/stellar/operations/layout.py +++ b/core/src/apps/stellar/operations/layout.py @@ -15,68 +15,72 @@ from trezor.messages import ( StellarPaymentOp, StellarSetOptionsOp, ) -from trezor.ui.components.tt.text import Text +from trezor.ui.layouts import confirm_metadata from trezor.wire import ProcessError from .. import consts, helpers -from ..layout import format_amount, require_confirm, split, trim_to_rows, ui +from ..layout import format_amount, require_confirm_op, ui -async def confirm_source_account(ctx, source_account: bytes): - text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN) - text.bold("Source account:") - text.mono(*split(source_account)) - await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) +async def confirm_source_account(ctx, source_account: str): + await require_confirm_op(ctx, "confirm_source", "Source account:", source_account) async def confirm_allow_trust_op(ctx, op: StellarAllowTrustOp): - if op.is_authorized: - t = "Allow Trust" - else: - t = "Revoke Trust" - text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN) - text.bold(t) - text.normal("of %s by:" % op.asset_code) - text.mono(*split(trim_to_rows(op.trusted_account, 3))) - - await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) + await require_confirm_op( + ctx, + "op_allow_trust", + subtitle="Allow Trust" if op.is_authorized else "Revoke Trust", + description="of %s by:" % op.asset_code, + data=op.trusted_account, + is_account=True, + ) async def confirm_account_merge_op(ctx, op: StellarAccountMergeOp): - text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN) - text.bold("Account Merge") - text.normal("All XLM will be sent to:") - text.mono(*split(trim_to_rows(op.destination_account, 3))) - await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) + await require_confirm_op( + ctx, + "op_merge", + "Account Merge", + description="All XLM will be sent to:", + data=op.destination_account, + is_account=True, + ) async def confirm_bump_sequence_op(ctx, op: StellarBumpSequenceOp): - text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN) - text.bold("Bump Sequence") - text.normal("Set sequence to") - text.mono(str(op.bump_to)) - await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) + await require_confirm_op( + ctx, + "op_bump", + "Bump Sequence", + description="Set sequence to", + data=str(op.bump_to), + split=False, + ) async def confirm_change_trust_op(ctx, op: StellarChangeTrustOp): - if op.limit == 0: - t = "Delete Trust" - else: - t = "Add Trust" - text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN) - text.bold(t) - text.normal("Asset: %s" % op.asset.code) - text.normal("Amount: %s" % format_amount(op.limit, ticker=False)) - await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) + await require_confirm_op( + ctx, + "op_change_trust", + subtitle="Delete Trust" if op.limit == 0 else "Add Trust", + description="Asset: %s\nAmount: %s" + % (op.asset.code, format_amount(op.limit, ticker=False)), + data="", + split=False, + ) await confirm_asset_issuer(ctx, op.asset) async def confirm_create_account_op(ctx, op: StellarCreateAccountOp): - text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN) - text.bold("Create Account") - text.normal("with %s" % format_amount(op.starting_balance)) - text.mono(*split(trim_to_rows(op.new_account, 3))) - await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) + await require_confirm_op( + ctx, + "op_account", + subtitle="Create Account", + description="with %s" % format_amount(op.starting_balance), + data=op.new_account, + is_account=True, + ) async def confirm_create_passive_offer_op(ctx, op: StellarCreatePassiveOfferOp): @@ -100,14 +104,20 @@ async def confirm_manage_offer_op(ctx, op: StellarManageOfferOp): async def _confirm_offer(ctx, title, op): - text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN) - text.bold(title) - text.normal( - "Sell %s %s" % (format_amount(op.amount, ticker=False), op.selling_asset.code) + await require_confirm_op( + ctx, + "op_offer", + subtitle=title, + description="Sell %s %s\nFor %f\nPer %s" + % ( + format_amount(op.amount, ticker=False), + op.selling_asset.code, + op.price_n / op.price_d, + format_asset_code(op.buying_asset), + ), + data="", + split=False, ) - text.normal("For %f" % (op.price_n / op.price_d)) - text.normal("Per %s" % format_asset_code(op.buying_asset)) - await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) await confirm_asset_issuer(ctx, op.selling_asset) await confirm_asset_issuer(ctx, op.buying_asset) @@ -119,121 +129,128 @@ async def confirm_manage_data_op(ctx, op: StellarManageDataOp): title = "Set" else: title = "Clear" - text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN) - text.bold("%s data value key" % title) - text.mono(*split(op.key)) - await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) + await require_confirm_op(ctx, "op_data", "%s data value key" % title, op.key) + if op.value: digest = sha256(op.value).digest() digest_str = hexlify(digest).decode() - text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN) - text.bold("Value (SHA-256):") - text.mono(*split(digest_str)) - await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) + await require_confirm_op(ctx, "op_data_value", "Value (SHA-256):", digest_str) async def confirm_path_payment_op(ctx, op: StellarPathPaymentOp): - text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN) - text.bold("Path Pay %s" % format_amount(op.destination_amount, ticker=False)) - text.bold("%s to:" % format_asset_code(op.destination_asset)) - text.mono(*split(trim_to_rows(op.destination_account, 3))) - await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) + await require_confirm_op( + ctx, + "op_path_payment", + subtitle="Path Pay %s\n%s to:" + % ( + format_amount(op.destination_amount, ticker=False), + format_asset_code(op.destination_asset), + ), + data=op.destination_account, + is_account=True, + ) await confirm_asset_issuer(ctx, op.destination_asset) # confirm what the sender is using to pay - text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN) - text.normal("Pay using") - text.bold(format_amount(op.send_max, ticker=False)) - text.bold(format_asset_code(op.send_asset)) - text.normal("This amount is debited") - text.normal("from your account.") - await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) + await confirm_metadata( + ctx, + "op_path_payment", + "Confirm operation", + content="Pay using\n{}\nThis amount is debited from your account.", + param="{}\n{}".format( + format_amount(op.send_max, ticker=False), + format_asset_code(op.send_asset), + ), + icon=ui.ICON_CONFIRM, + hide_continue=True, + br_code=ButtonRequestType.ConfirmOutput, + ) await confirm_asset_issuer(ctx, op.send_asset) async def confirm_payment_op(ctx, op: StellarPaymentOp): - text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN) - text.bold("Pay %s" % format_amount(op.amount, ticker=False)) - text.bold("%s to:" % format_asset_code(op.asset)) - text.mono(*split(trim_to_rows(op.destination_account, 3))) - await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) + description = "Pay {}\n{} to:".format( + format_amount(op.amount, ticker=False), format_asset_code(op.asset) + ) + await require_confirm_op( + ctx, "op_payment", description, op.destination_account, is_account=True + ) + await confirm_asset_issuer(ctx, op.asset) async def confirm_set_options_op(ctx, op: StellarSetOptionsOp): if op.inflation_destination_account: - text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN) - text.bold("Set Inflation Destination") - text.mono(*split(op.inflation_destination_account)) - await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) + await require_confirm_op( + ctx, + "op_inflation", + "Set Inflation Destination", + op.inflation_destination_account, + ) if op.clear_flags: t = _format_flags(op.clear_flags) - text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN) - text.bold("Clear Flags") - text.mono(*t) - await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) + await require_confirm_op(ctx, "op_clear_flags", "Clear Flags", t, split=False) if op.set_flags: t = _format_flags(op.set_flags) - text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN) - text.bold("Set Flags") - text.mono(*t) - await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) + await require_confirm_op(ctx, "op_set_flags", "Set Flags", t, split=False) thresholds = _format_thresholds(op) if thresholds: - text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN) - text.bold("Account Thresholds") - text.mono(*thresholds) - await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) + await require_confirm_op( + ctx, + "op_thresholds", + "Account Thresholds", + thresholds, + split=False, + ) if op.home_domain: - text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN) - text.bold("Home Domain") - text.mono(*split(op.home_domain)) - await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) + await require_confirm_op(ctx, "op_home_domain", "Home Domain", op.home_domain) if op.signer_type is not None: if op.signer_weight > 0: t = "Add Signer (%s)" else: t = "Remove Signer (%s)" if op.signer_type == consts.SIGN_TYPE_ACCOUNT: - text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN) - text.bold(t % "acc") - text.mono(*split(helpers.address_from_public_key(op.signer_key))) - await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) + await require_confirm_op( + ctx, + "op_signer", + t % "acc", + helpers.address_from_public_key(op.signer_key), + ) elif op.signer_type in (consts.SIGN_TYPE_PRE_AUTH, consts.SIGN_TYPE_HASH): if op.signer_type == consts.SIGN_TYPE_PRE_AUTH: signer_type = "auth" else: signer_type = "hash" - text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN) - text.bold(t % signer_type) - text.mono(*split(hexlify(op.signer_key).decode())) - await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) + await require_confirm_op( + ctx, + "op_signer", + t % signer_type, + hexlify(op.signer_key).decode(), + ) else: raise ProcessError("Stellar: invalid signer type") def _format_thresholds(op: StellarSetOptionsOp) -> tuple: - text = () + text = "" if op.master_weight is not None: - text += ("Master Weight: %d" % op.master_weight,) + text += "Master Weight: %d\n" % op.master_weight if op.low_threshold is not None: - text += ("Low: %d" % op.low_threshold,) + text += "Low: %d\n" % op.low_threshold if op.medium_threshold is not None: - text += ("Medium: %d" % op.medium_threshold,) + text += "Medium: %d\n" % op.medium_threshold if op.high_threshold is not None: - text += ("High: %d" % op.high_threshold,) + text += "High: %d\n" % op.high_threshold return text def _format_flags(flags: int) -> tuple: if flags > consts.FLAGS_MAX_SIZE: raise ProcessError("Stellar: invalid flags") - text = () - if flags & consts.FLAG_AUTH_REQUIRED: - text += ("AUTH_REQUIRED",) - if flags & consts.FLAG_AUTH_REVOCABLE: - text += ("AUTH_REVOCABLE",) - if flags & consts.FLAG_AUTH_IMMUTABLE: - text += ("AUTH_IMMUTABLE",) + text = "{}{}{}".format( + "AUTH_REQUIRED\n" if flags & consts.FLAG_AUTH_REQUIRED else "", + "AUTH_REVOCABLE\n" if flags & consts.FLAG_AUTH_REVOCABLE else "", + "AUTH_IMMUTABLE\n" if flags & consts.FLAG_AUTH_IMMUTABLE else "", + ) return text @@ -246,7 +263,10 @@ def format_asset_code(asset: StellarAssetType) -> str: async def confirm_asset_issuer(ctx, asset: StellarAssetType): if asset is None or asset.type == consts.ASSET_TYPE_NATIVE: return - text = Text("Confirm issuer", ui.ICON_CONFIRM, ui.GREEN) - text.bold("%s issuer:" % asset.code) - text.mono(*split(asset.issuer)) - await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) + await require_confirm_op( + ctx, + "confirm_issuer", + title="Confirm issuer", + subtitle="%s issuer:" % asset.code, + data=asset.issuer, + ) diff --git a/core/src/trezor/ui/layouts/tt.py b/core/src/trezor/ui/layouts/tt.py index 7d1e2cc02..7d63c3298 100644 --- a/core/src/trezor/ui/layouts/tt.py +++ b/core/src/trezor/ui/layouts/tt.py @@ -61,6 +61,7 @@ __all__ = ( "confirm_modify_output", "confirm_modify_fee", "confirm_coinjoin", + "confirm_timebounds_stellar", ) @@ -216,16 +217,18 @@ def _truncate_hex( lines: int = TEXT_MAX_LINES, width: int = MONO_HEX_PER_LINE, middle: bool = False, + ellipsis: str = "...", # TODO: cleanup @ redesign ) -> Iterator[str]: + ell_len = len(ellipsis) if len(hex_data) > width * lines: if middle: hex_data = ( - hex_data[: lines * width // 2 - 1] - + "..." - + hex_data[-lines * width // 2 + 2 :] + hex_data[: lines * width // 2 - (ell_len // 2)] + + ellipsis + + hex_data[-lines * width // 2 + (ell_len - ell_len // 2) :] ) else: - hex_data = hex_data[: (width * lines - 3)] + "..." + hex_data = hex_data[: (width * lines - ell_len)] + ellipsis return chunks_intersperse(hex_data, width) @@ -490,40 +493,55 @@ async def confirm_hex( br_type: str, title: str, data: str, + subtitle: str | None = None, description: str | None = None, br_code: ButtonRequestType = ButtonRequestType.Other, icon: str = ui.ICON_SEND, # TODO cleanup @ redesign icon_color: int = ui.GREEN, # TODO cleanup @ redesign - font_description: int = ui.NORMAL, # TODO cleanup @ redesign color_description: int = ui.FG, # TODO cleanup @ redesign - width: int = MONO_HEX_PER_LINE, + width: int | None = MONO_HEX_PER_LINE, width_paginated: int = MONO_HEX_PER_LINE - 2, truncate: bool = False, truncate_middle: bool = False, + truncate_ellipsis: str = "...", ) -> None: if truncate: text = Text(title, icon, icon_color, new_lines=False) description_lines = 0 - if description is not None: - description_lines = Span(description, 0, font_description).count_lines() - text.content.extend( - (font_description, color_description, description, ui.FG) - ) + if subtitle is not None: + description_lines += Span(subtitle, 0, ui.BOLD).count_lines() + text.bold(subtitle) text.br() - text.mono( - *_truncate_hex( - data, - lines=TEXT_MAX_LINES - description_lines, - width=width, - middle=truncate_middle, + if description is not None: + description_lines += Span(description, 0, ui.NORMAL).count_lines() + text.content.extend((ui.NORMAL, color_description, description, ui.FG)) + text.br() + if width is not None: + text.mono( + *_truncate_hex( + data, + lines=TEXT_MAX_LINES - description_lines, + width=width, + middle=truncate_middle, + ellipsis=truncate_ellipsis, + ) ) - ) + else: + text.mono(data) content: ui.Layout = Confirm(text) else: - width_paginated = min(width, MONO_HEX_PER_LINE - 2) - assert color_description == ui.FG # only ethereum uses this and it truncates - para = [(font_description, description)] if description is not None else [] - para.extend((ui.MONO, line) for line in chunks(data, width_paginated)) + para = [] + if subtitle is not None: + para.append((ui.BOLD, subtitle)) + if description is not None: + assert ( + color_description == ui.FG + ) # only ethereum uses this and it truncates + para.append((ui.NORMAL, description)) + if width is not None: + para.extend((ui.MONO, line) for line in chunks(data, width_paginated)) + else: + para.append((ui.MONO, data)) content = paginate_paragraphs(para, title, icon, icon_color) await raise_if_cancelled(interact(ctx, content, br_type, br_code)) @@ -597,8 +615,9 @@ async def confirm_metadata( br_code: ButtonRequestType = ButtonRequestType.SignTx, hide_continue: bool = False, hold: bool = False, + icon: str = ui.ICON_SEND, # TODO cleanup @ redesign ) -> None: - text = Text(title, ui.ICON_SEND, ui.GREEN, new_lines=False) + text = Text(title, icon, ui.GREEN, new_lines=False) text.format_parametrized(content, param if param is not None else "") if not hide_continue: @@ -732,3 +751,27 @@ async def confirm_signverify( ButtonRequestType.Other, ) ) + + +# TODO cleanup @ redesign +async def confirm_timebounds_stellar( + ctx: wire.GenericContext, start: int, end: int +) -> None: + text = Text("Confirm timebounds", ui.ICON_SEND, ui.GREEN) + text.bold("Valid from (UTC):") + if start: + text.normal(str(start)) + else: + text.mono("[no restriction]") + + text.bold("Valid to (UTC):") + if end: + text.normal(str(end)) + else: + text.mono("[no restriction]") + + await raise_if_cancelled( + interact( + ctx, Confirm(text), "confirm_timebounds", ButtonRequestType.ConfirmOutput + ) + ) diff --git a/tests/device_tests/test_msg_stellar_sign_transaction.py b/tests/device_tests/test_msg_stellar_sign_transaction.py index 484c3d8a2..dd9a55e4b 100644 --- a/tests/device_tests/test_msg_stellar_sign_transaction.py +++ b/tests/device_tests/test_msg_stellar_sign_transaction.py @@ -67,12 +67,15 @@ ADDRESS_N = parse_path(stellar.DEFAULT_BIP32_PATH) NETWORK_PASSPHRASE = "Test SDF Network ; September 2015" -def _create_msg() -> messages.StellarSignTx: +def _create_msg(memo=False) -> messages.StellarSignTx: + kwargs = {"memo_type": 0} + if memo: + kwargs = {"memo_type": 1, "memo_text": "hi"} return messages.StellarSignTx( source_account="GAK5MSF74TJW6GLM7NLTL76YZJKM2S4CGP3UH4REJHPHZ4YBZW2GSBPW", fee=100, sequence_number=0x100000000, - memo_type=0, + **kwargs, ) @@ -209,6 +212,131 @@ def test_sign_tx_payment_op_custom_asset12(client): ) +# testcase added for UI code coverage, may not normally make sense +def test_sign_tx_allow_trust_op(client): + + op = messages.StellarAllowTrustOp() + op.is_authorized = True + op.trusted_account = "GBOVKZBEM2YYLOCDCUXJ4IMRKHN4LCJAE7WEAEA2KF562XFAGDBOB64V" + op.asset_type = 1 + op.asset_code = "X" + tx = _create_msg(memo=True) + + response = stellar.sign_tx(client, tx, [op], ADDRESS_N, NETWORK_PASSPHRASE) + + assert ( + b64encode(response.signature) + == b"JLLwaDvPAomcDnljhlM3LF6WQvlQzI+V/afI95X41HGRYgwbYrxXOplzzdBhbRxd09VxDkb3nQ271l6MEqn/CQ==" + ) + + +# testcase added for UI code coverage, may not normally make sense +def test_sign_tx_change_trust_op(client): + + op = messages.StellarChangeTrustOp() + op.limit = 500111000 + op.source_account = "GBOVKZBEM2YYLOCDCUXJ4IMRKHN4LCJAE7WEAEA2KF562XFAGDBOB64V" + + op.asset = messages.StellarAssetType( + type=2, + code="ABCDEFGHIJKL", + issuer="GAUYJFQCYIHFQNS7CI6BFWD2DSSFKDIQZUQ3BLQODDKE4PSW7VVBKENC", + ) + tx = _create_msg() + + response = stellar.sign_tx(client, tx, [op], ADDRESS_N, NETWORK_PASSPHRASE) + + assert ( + b64encode(response.signature) + == b"OZdDO/qW8o/xbV6nZaDM/D7z9/fqbrk+P4lSzzCqeD3C8nGOg+Jl33JqHek0zNNOW9Pn+tPpfdoQnuZWJzocCw==" + ) + + +# testcase added for UI code coverage, may not normally make sense +def test_sign_tx_passive_offer_op(client): + + op = messages.StellarCreatePassiveOfferOp() + op.selling_asset = messages.StellarAssetType( + type=2, + code="ABCDEFGHIJKL", + issuer="GAUYJFQCYIHFQNS7CI6BFWD2DSSFKDIQZUQ3BLQODDKE4PSW7VVBKENC", + ) + op.buing_asset = messages.StellarAssetType( + type=1, + code="X", + issuer="GAUYJFQCYIHFQNS7CI6BFWD2DSSFKDIQZUQ3BLQODDKE4PSW7VVBKENC", + ) + op.amount = 4 + op.price_n = 5 + op.price_d = 6 + + tx = _create_msg() + + response = stellar.sign_tx(client, tx, [op], ADDRESS_N, NETWORK_PASSPHRASE) + + assert ( + b64encode(response.signature) + == b"78/YNz3U4PZmKubPgl+PIm/Jr8omISshJtwJuqXp4rSo9bAxxYRvMSa2IGFIy9PfIe2kDxnqSajTwiUFGOOtAw==" + ) + + +# testcase added for UI code coverage, may not normally make sense +def test_sign_tx_manage_offer_op(client): + + op = messages.StellarManageOfferOp() + op.selling_asset = messages.StellarAssetType( + type=2, + code="ABCDEFGHIJKL", + issuer="GAUYJFQCYIHFQNS7CI6BFWD2DSSFKDIQZUQ3BLQODDKE4PSW7VVBKENC", + ) + op.buing_asset = messages.StellarAssetType( + type=1, + code="X", + issuer="GAUYJFQCYIHFQNS7CI6BFWD2DSSFKDIQZUQ3BLQODDKE4PSW7VVBKENC", + ) + op.amount = 4 + op.price_n = 5 + op.price_d = 6 + op.offer_id = 1337 + + tx = _create_msg() + + response = stellar.sign_tx(client, tx, [op], ADDRESS_N, NETWORK_PASSPHRASE) + + assert ( + b64encode(response.signature) + == b"tkCta8G5dmxOVYJ+/mGve1lzNyIKZB83DwWdZH2A6zGI7KF2VYGG8RNHmlMpMqeBFcit9o+/Ss+IAQ86CZBTAw==" + ) + + +# testcase added for UI code coverage, may not normally make sense +def test_sign_tx_path_payment_op(client): + + op = messages.StellarPathPaymentOp() + op.send = messages.StellarAssetType( + type=1, + code="X", + issuer="GAUYJFQCYIHFQNS7CI6BFWD2DSSFKDIQZUQ3BLQODDKE4PSW7VVBKENC", + ) + op.send_max = 50000 + op.destination_account = "GAUYJFQCYIHFQNS7CI6BFWD2DSSFKDIQZUQ3BLQODDKE4PSW7VVBKENC" + op.destination_asset = messages.StellarAssetType( + type=1, + code="X", + issuer="GAUYJFQCYIHFQNS7CI6BFWD2DSSFKDIQZUQ3BLQODDKE4PSW7VVBKENC", + ) + op.destination_amount = 6667 + + tx = _create_msg() + + response = stellar.sign_tx(client, tx, [op], ADDRESS_N, NETWORK_PASSPHRASE) + + assert ( + b64encode(response.signature) + == b"KMUKub6SRXS4R86ux74bSSJXtMMQ1tEWhxc71jcAozKRX/db5dJC7oBVSeijSCT+hJOG/wTw6Jd1UBkJC0JgCw==" + ) + + def test_sign_tx_set_options(client): """Set inflation destination""" @@ -278,6 +406,30 @@ def test_sign_tx_set_options(client): # db6adf70eaf10621396a4a4db27597f323e000ea5c95b6a356a5d469730d78bd34d29d4e845862d39d2dd7e18d469a123727d5b0918dbed948086a47e24b0301 ) + op = messages.StellarSetOptionsOp() + op.signer_type = 1 + op.signer_key = bytes.fromhex( + "72187adb879c414346d77c71af8cce7b6eaa57b528e999fd91feae6b6418628e" + ) + op.signer_weight = 0 + + tx = _create_msg() + response = stellar.sign_tx(client, tx, [op], ADDRESS_N, NETWORK_PASSPHRASE) + assert ( + b64encode(response.signature) + == b"fvZ8rzZPkJwa1lsr4/z/wXZLT6cBwFmwY861nmEnhy57Stw1dxCipo5PJeXk/EhyLxlAwL99+m7qWtdW38EZBg==" + ) + + op = messages.StellarSetOptionsOp() + op.clear_flags = 4 + + tx = _create_msg() + response = stellar.sign_tx(client, tx, [op], ADDRESS_N, NETWORK_PASSPHRASE) + assert ( + b64encode(response.signature) + == b"ZObGPxqyavv1fjXnlVQJM35FZu8zkJBAWkuOaI+KqyHTB8rOI4oXb27tohAnxiAzMn7e/tiNfqZwzQQS6RqmBg==" + ) + def test_sign_tx_timebounds(client): op = messages.StellarSetOptionsOp()