You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
trezor-firmware/core/src/apps/solana/layout.py

362 lines
11 KiB

from typing import TYPE_CHECKING
from trezor import TR
from trezor.crypto import base58
from trezor.enums import ButtonRequestType
from trezor.strings import format_amount
from trezor.ui.layouts import (
confirm_metadata,
confirm_properties,
confirm_solana_tx,
confirm_value,
)
from apps.common.paths import address_n_to_str
from .types import AddressType
if TYPE_CHECKING:
from .transaction.instructions import Instruction, SystemProgramTransferInstruction
from .types import AddressReference
def _format_path(path: list[int]) -> str:
from micropython import const
from apps.common.paths import unharden
if len(path) < 4:
return address_n_to_str(path)
ACCOUNT_PATH_INDEX = const(3)
account_index = path[ACCOUNT_PATH_INDEX]
return f"Solana #{unharden(account_index) + 1}"
def _get_address_reference_props(address: AddressReference, display_name: str):
return (
(TR.solana__is_provided_via_lookup_table_template.format(display_name), ""),
(f"{TR.solana__lookup_table_address}:", base58.encode(address[0])),
(f"{TR.solana__account_index}:", f"{address[1]}"),
)
async def confirm_instruction(
instruction: Instruction,
instructions_count: int,
instruction_index: int,
signer_path: list[int],
signer_public_key: bytes,
) -> None:
instruction_title = (
f"{instruction_index}/{instructions_count}: {instruction.ui_name}"
)
if instruction.is_deprecated_warning is not None:
await confirm_metadata(
"confirm_deprecated_warning",
instruction_title,
instruction.is_deprecated_warning,
br_code=ButtonRequestType.Other,
)
if instruction.multisig_signers:
await confirm_metadata(
"confirm_multisig",
TR.solana__confirm_multisig,
TR.solana__instruction_is_multisig,
br_code=ButtonRequestType.Other,
)
for ui_property in instruction.ui_properties:
if ui_property.parameter is not None:
property_template = instruction.get_property_template(ui_property.parameter)
value = instruction.parsed_data[ui_property.parameter]
if property_template.is_authority and signer_public_key == value:
continue
if property_template.is_optional and value is None:
continue
if ui_property.default_value_to_hide == value:
continue
await confirm_properties(
"confirm_instruction",
f"{instruction_index}/{instructions_count}: {instruction.ui_name}",
(
(
ui_property.display_name,
property_template.format(instruction, value),
),
),
)
elif ui_property.account is not None:
account_template = instruction.get_account_template(ui_property.account)
# optional account, skip if not present
if ui_property.account not in instruction.parsed_accounts:
continue
account_value = instruction.parsed_accounts[ui_property.account]
if account_template.is_authority:
if signer_public_key == account_value[0]:
continue
account_data: list[tuple[str, str]] = []
if len(account_value) == 2:
signer_suffix = ""
if account_value[0] == signer_public_key:
signer_suffix = f" ({TR.words__signer})"
account_data.append(
(
ui_property.display_name,
f"{base58.encode(account_value[0])}{signer_suffix}",
)
)
elif len(account_value) == 3:
account_data += _get_address_reference_props(
account_value, ui_property.display_name
)
else:
raise ValueError # Invalid account value
await confirm_properties(
"confirm_instruction",
f"{instruction_index}/{instructions_count}: {instruction.ui_name}",
account_data,
)
else:
raise ValueError # Invalid ui property
if instruction.multisig_signers:
signers: list[tuple[str, str]] = []
for i, multisig_signer in enumerate(instruction.multisig_signers, 1):
multisig_signer_public_key = multisig_signer[0]
path_str = ""
if multisig_signer_public_key == signer_public_key:
path_str = f" ({address_n_to_str(signer_path)})"
signers.append(
(
f"{TR.words__signer} {i}{path_str}:",
base58.encode(multisig_signer[0]),
)
)
await confirm_properties(
"confirm_instruction",
f"{instruction_index}/{instructions_count}: {instruction.ui_name}",
signers,
)
def get_address_type(address_type: AddressType) -> str:
if address_type == AddressType.AddressSig:
return f"({TR.words__writable}, {TR.words__signer})"
if address_type == AddressType.AddressSigReadOnly:
return f"({TR.words__signer})"
if address_type == AddressType.AddressReadOnly:
return ""
if address_type == AddressType.AddressRw:
return f"({TR.words__writable})"
raise ValueError # Invalid address type
async def confirm_unsupported_instruction_details(
instruction: Instruction,
title: str,
signer_path: list[int],
signer_public_key: bytes,
) -> None:
from trezor.ui import NORMAL
from trezor.ui.layouts import confirm_properties, should_show_more
should_show_instruction_details = await should_show_more(
title,
(
(
NORMAL,
TR.solana__instruction_accounts_template.format(
len(instruction.accounts), len(instruction.instruction_data)
),
),
),
TR.buttons__show_details,
confirm=TR.buttons__continue,
)
if should_show_instruction_details:
await confirm_properties(
"instruction_data",
title,
((f"{TR.solana__instruction_data}:", bytes(instruction.instruction_data)),),
)
accounts = []
for i, account in enumerate(instruction.accounts, 1):
if len(account) == 2:
account_public_key = account[0]
address_type = get_address_type(account[1])
path_str = ""
if account_public_key == signer_public_key:
path_str = f" ({address_n_to_str(signer_path)})"
accounts.append(
(
f"{TR.words__account} {i}{path_str} {address_type}:",
base58.encode(account_public_key),
)
)
elif len(account) == 3:
address_type = get_address_type(account[2])
accounts += _get_address_reference_props(
account, f"{TR.words__account} {i} {address_type}"
)
else:
raise ValueError # Invalid account value
await confirm_properties(
"accounts",
title,
accounts,
)
async def confirm_unsupported_instruction_confirm(
instruction: Instruction,
instructions_count: int,
instruction_index: int,
signer_path: list[int],
signer_public_key: bytes,
) -> None:
formatted_instruction_id = (
instruction.instruction_id if instruction.instruction_id is not None else "N/A"
)
title = f"{instruction_index}/{instructions_count}: {instruction.ui_name}: instruction id ({formatted_instruction_id})"
return await confirm_unsupported_instruction_details(
instruction, title, signer_path, signer_public_key
)
async def confirm_unsupported_program_confirm(
instruction: Instruction,
instructions_count: int,
instruction_index: int,
signer_path: list[int],
signer_public_key: bytes,
) -> None:
title = f"{instruction_index}/{instructions_count}: {instruction.ui_name}"
return await confirm_unsupported_instruction_details(
instruction, title, signer_path, signer_public_key
)
async def confirm_system_transfer(
transfer_instruction: SystemProgramTransferInstruction,
fee: int,
signer_path: list[int],
blockhash: bytes,
) -> None:
await confirm_value(
title=TR.words__recipient,
value=base58.encode(transfer_instruction.recipient_account[0]),
description="",
br_type="confirm_recipient",
br_code=ButtonRequestType.ConfirmOutput,
verb=TR.buttons__continue,
)
await confirm_custom_transaction(
transfer_instruction.lamports,
9,
"SOL",
fee,
signer_path,
blockhash,
)
async def confirm_token_transfer(
destination_account: bytes,
token_account: bytes,
token_mint: bytes,
amount: int,
decimals: int,
fee: int,
signer_path: list[int],
blockhash: bytes,
):
await confirm_value(
title=TR.words__recipient,
value=base58.encode(destination_account),
description="",
br_type="confirm_recipient",
br_code=ButtonRequestType.ConfirmOutput,
verb=TR.buttons__continue,
info_items=(
((f"{TR.solana__associated_token_account}:", base58.encode(token_account)),)
if token_account != destination_account
else None
),
)
await confirm_value(
title=TR.solana__token_address,
value=base58.encode(token_mint),
description="",
br_type="confirm_token_address",
br_code=ButtonRequestType.ConfirmOutput,
verb=TR.buttons__continue,
)
await confirm_custom_transaction(
amount,
decimals,
"[TOKEN]",
fee,
signer_path,
blockhash,
)
async def confirm_custom_transaction(
amount: int,
decimals: int,
unit: str,
fee: int,
signer_path: list[int],
blockhash: bytes,
) -> None:
await confirm_solana_tx(
amount=f"{format_amount(amount, decimals)} {unit}",
fee=f"{format_amount(fee, 9)} SOL",
fee_title=f"{TR.solana__expected_fee}:",
items=(
(f"{TR.words__account}:", _format_path(signer_path)),
(f"{TR.words__blockhash}:", base58.encode(blockhash)),
),
)
async def confirm_transaction(
signer_path: list[int], blockhash: bytes, fee: int
) -> None:
await confirm_solana_tx(
amount="",
amount_title="",
fee=f"{format_amount(fee, 9)} SOL",
fee_title=f"{TR.solana__expected_fee}:",
items=(
(f"{TR.words__account}:", _format_path(signer_path)),
(f"{TR.words__blockhash}:", base58.encode(blockhash)),
),
)