1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-22 22:38:08 +00:00

feat(core/rust): add account and address labels into send flow

[no changelog]
This commit is contained in:
grdddj 2023-06-20 17:19:56 +02:00 committed by Jiří Musil
parent 570ffe2c0d
commit 64236e699f
7 changed files with 233 additions and 122 deletions

View File

@ -16,10 +16,12 @@ static void _librust_qstrs(void) {
MP_QSTR___dict__;
MP_QSTR___name__;
MP_QSTR_account;
MP_QSTR_account_label;
MP_QSTR_accounts;
MP_QSTR_action;
MP_QSTR_active;
MP_QSTR_address;
MP_QSTR_address_label;
MP_QSTR_address_title;
MP_QSTR_allow_cancel;
MP_QSTR_amount;

View File

@ -522,6 +522,7 @@ extern "C" fn new_confirm_modify_output(n_args: usize, args: *const Obj, kwargs:
extern "C" fn new_confirm_output(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], kwargs: &Map| {
let address: StrBuffer = kwargs.get(Qstr::MP_QSTR_address)?.try_into()?;
let address_label: StrBuffer = kwargs.get(Qstr::MP_QSTR_address_label)?.try_into()?;
let amount: StrBuffer = kwargs.get(Qstr::MP_QSTR_amount)?.try_into()?;
let address_title: StrBuffer = kwargs.get(Qstr::MP_QSTR_address_title)?.try_into()?;
let address_title_clone = address_title.clone();
@ -532,23 +533,27 @@ extern "C" fn new_confirm_output(n_args: usize, args: *const Obj, kwargs: *mut M
match page_index {
0 => {
// RECIPIENT + address
let btn_layout = ButtonLayout::cancel_none_text("CONFIRM".into());
let btn_layout = ButtonLayout::cancel_none_text("CONTINUE".into());
let btn_actions = ButtonActions::cancel_none_next();
// Not putting hyphens in the address
let ops = OpTextLayout::new(theme::TEXT_MONO)
.line_breaking(LineBreaking::BreakWordsNoHyphen)
.text_mono(address.clone());
let formatted = FormattedText::new(ops);
// Not putting hyphens in the address.
// Potentially adding address label in different font.
let mut ops = OpTextLayout::new(theme::TEXT_MONO)
.line_breaking(LineBreaking::BreakWordsNoHyphen);
if !address_label.is_empty() {
ops = ops.text_normal(address_label.clone()).newline();
}
ops = ops.text_mono(address.clone());
let formatted =
FormattedText::new(ops).vertically_aligned(geometry::Alignment::Center);
Page::new(btn_layout, btn_actions, formatted).with_title(address_title.clone())
}
1 => {
// AMOUNT + amount
let btn_layout = ButtonLayout::up_arrow_none_text("CONFIRM".into());
let btn_actions = ButtonActions::prev_none_confirm();
let ops = OpTextLayout::new(theme::TEXT_MONO)
.newline()
.text_mono(amount.clone());
let formatted = FormattedText::new(ops);
let ops = OpTextLayout::new(theme::TEXT_MONO).text_mono(amount.clone());
let formatted =
FormattedText::new(ops).vertically_aligned(geometry::Alignment::Center);
Page::new(btn_layout, btn_actions, formatted).with_title(amount_title.clone())
}
_ => unreachable!(),
@ -569,34 +574,75 @@ extern "C" fn new_confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Ma
let fee_rate_amount: Option<StrBuffer> = kwargs
.get(Qstr::MP_QSTR_fee_rate_amount)?
.try_into_option()?;
let account_label: Option<StrBuffer> =
kwargs.get(Qstr::MP_QSTR_account_label)?.try_into_option()?;
let total_label: StrBuffer = kwargs.get(Qstr::MP_QSTR_total_label)?.try_into()?;
let fee_label: StrBuffer = kwargs.get(Qstr::MP_QSTR_fee_label)?.try_into()?;
let get_page = move |page_index| {
// Total amount + fee
assert!(page_index == 0);
match page_index {
0 => {
// Total amount + fee
let btn_layout = ButtonLayout::cancel_armed_info("CONFIRM".into());
let btn_actions = ButtonActions::cancel_confirm_next();
let btn_layout = ButtonLayout::cancel_none_htc("HOLD TO CONFIRM".into());
let btn_actions = ButtonActions::cancel_none_confirm();
let ops = OpTextLayout::new(theme::TEXT_MONO)
.text_bold(total_label.clone())
.newline()
.text_mono(total_amount.clone())
.newline()
.newline()
.text_bold(fee_label.clone())
.newline()
.text_mono(fee_amount.clone());
let mut ops = OpTextLayout::new(theme::TEXT_MONO)
.text_bold(total_label.clone())
.newline()
.text_mono(total_amount.clone())
.newline()
.text_bold(fee_label.clone())
.newline()
.text_mono(fee_amount.clone());
let formatted = FormattedText::new(ops);
Page::new(btn_layout, btn_actions, formatted)
}
1 => {
// Fee rate info
let btn_layout = ButtonLayout::arrow_none_arrow();
let btn_actions = ButtonActions::prev_none_next();
// Fee rate amount might not be there
if let Some(fee_rate_amount) = fee_rate_amount.clone() {
ops = ops.newline().text_mono(fee_rate_amount)
let fee_rate_amount = fee_rate_amount.clone().unwrap_or_default();
let ops = OpTextLayout::new(theme::TEXT_MONO)
.text_bold("FEE INFORMATION".into())
.newline()
.newline()
.newline_half()
.text_bold("Fee rate:".into())
.newline()
.text_mono(fee_rate_amount);
let formatted = FormattedText::new(ops);
Page::new(btn_layout, btn_actions, formatted)
}
2 => {
// Wallet and account info
let btn_layout = ButtonLayout::arrow_none_none();
let btn_actions = ButtonActions::prev_none_none();
let account_label = account_label.clone().unwrap_or_default();
// TODO: include wallet info when available
let ops = OpTextLayout::new(theme::TEXT_MONO)
.text_bold("SENDING FROM".into())
.newline()
.newline()
.newline_half()
.text_bold("Account:".into())
.newline()
.text_mono(account_label);
let formatted = FormattedText::new(ops);
Page::new(btn_layout, btn_actions, formatted)
}
_ => unreachable!(),
}
let formatted = FormattedText::new(ops);
Page::new(btn_layout, btn_actions, formatted)
};
let pages = FlowPages::new(get_page, 1);
let pages = FlowPages::new(get_page, 3);
let obj = LayoutObj::new(Flow::new(pages))?;
Ok(obj.into())
@ -612,7 +658,7 @@ extern "C" fn new_confirm_address(n_args: usize, args: *const Obj, kwargs: *mut
let get_page = move |page_index| {
assert!(page_index == 0);
let btn_layout = ButtonLayout::cancel_armed_text("CONFIRM".into(), "i".into());
let btn_layout = ButtonLayout::cancel_armed_info("CONFIRM".into());
let btn_actions = ButtonActions::cancel_confirm_info();
let ops = OpTextLayout::new(theme::TEXT_MONO)
.line_breaking(LineBreaking::BreakWordsNoHyphen)
@ -1354,6 +1400,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// def confirm_output(
/// *,
/// address: str,
/// address_label: str,
/// amount: str,
/// address_title: str,
/// amount_title: str,
@ -1366,6 +1413,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// total_amount: str,
/// fee_amount: str,
/// fee_rate_amount: str | None,
/// account_label: str | None,
/// total_label: str,
/// fee_label: str,
/// ) -> object:

View File

@ -123,6 +123,7 @@ def confirm_modify_output(
def confirm_output(
*,
address: str,
address_label: str,
amount: str,
address_title: str,
amount_title: str,
@ -136,6 +137,7 @@ def confirm_total(
total_amount: str,
fee_amount: str,
fee_rate_amount: str | None,
account_label: str | None,
total_label: str,
fee_label: str,
) -> object:

View File

@ -693,15 +693,13 @@ async def confirm_output(
)
amount_title = "AMOUNT" if output_index is None else f"AMOUNT #{output_index + 1}"
# TODO: implement `hold` to be consistent with `TT`?
# TODO: incorporate label? - label = f" ({address_label})" if address_label else ""
await raise_if_not_confirmed(
interact(
ctx,
RustLayout(
trezorui2.confirm_output(
address=address,
address_label=address_label or "",
address_title=address_title,
amount_title=amount_title,
amount=amount,
@ -944,13 +942,11 @@ async def confirm_total(
fee_rate_amount: str | None = None,
title: str = "SENDING",
total_label: str = "TOTAL AMOUNT",
fee_label: str = "INCLUDING FEE",
fee_label: str = "Including fee:",
account_label: str | None = None,
br_type: str = "confirm_total",
br_code: ButtonRequestType = ButtonRequestType.SignTx,
) -> None:
# TODO: incorporate account_label
# f"From {account_label}\r\n{total_label}" if account_label else total_label,
await raise_if_not_confirmed(
interact(
ctx,
@ -960,8 +956,9 @@ async def confirm_total(
total_amount=total_amount, # type: ignore [No parameter named]
fee_amount=fee_amount, # type: ignore [No parameter named]
fee_rate_amount=fee_rate_amount, # type: ignore [No parameter named]
account_label=account_label, # type: ignore [No parameter named]
total_label=total_label.upper(), # type: ignore [No parameter named]
fee_label=fee_label.upper(), # type: ignore [No parameter named]
fee_label=fee_label, # type: ignore [No parameter named]
)
),
br_type,

View File

@ -104,7 +104,6 @@ def test_autolock_interrupts_signing(device_handler: "BackgroundDeviceHandler"):
layout = debug.click(buttons.OK, wait=True)
assert "Total amount: 0.0039 BTC" in layout.text_content()
elif debug.model == "R":
debug.press_right(wait=True)
debug.press_right(wait=True)
layout = debug.press_right(wait=True)
assert "TOTAL AMOUNT 0.0039 BTC" in layout.text_content()
@ -165,7 +164,7 @@ def test_autolock_does_not_interrupt_signing(device_handler: "BackgroundDeviceHa
if debug.model == "T":
debug.click(buttons.OK)
elif debug.model == "R":
debug.press_right_htc(1200)
debug.press_middle()
signatures, tx = device_handler.result()
assert len(signatures) == 1

View File

@ -27,6 +27,10 @@ from ...input_flows import (
InputFlowLockTimeBlockHeight,
InputFlowLockTimeDatetime,
InputFlowSignTxHighFee,
InputFlowSignTxInformation,
InputFlowSignTxInformationCancel,
InputFlowSignTxInformationMixed,
InputFlowSignTxInformationReplacement,
)
from ...tx_cache import TxCache
from .signtx import (
@ -1524,7 +1528,6 @@ def test_lock_time_datetime(client: Client, lock_time_str: str):
@pytest.mark.skip_t1(reason="Cannot test layouts on T1")
@pytest.mark.skip_tr(reason="Not implemented yet")
def test_information(client: Client):
# input tx: 0dac366fd8a67b2a89fbb0d31086e7acded7a5bbf9ef9daa935bc873229ef5b5
@ -1542,29 +1545,9 @@ def test_information(client: Client):
script_type=messages.OutputScriptType.PAYTOADDRESS,
)
def input_flow():
yield # confirm output
client.debug.wait_layout()
client.debug.press_yes()
yield # confirm output
client.debug.wait_layout()
client.debug.press_yes()
yield # confirm transaction
client.debug.wait_layout()
client.debug.press_info()
layout = client.debug.wait_layout()
content = layout.text_content().lower()
assert "sending from" in content
assert "legacy #6" in content
assert "fee rate" in content
assert "71.56 sat" in content
client.debug.click(CORNER_BUTTON, wait=True)
client.debug.press_yes()
with client:
client.set_input_flow(input_flow)
IF = InputFlowSignTxInformation(client)
client.set_input_flow(IF.get())
client.watch_layout(True)
btc.sign_tx(
@ -1577,7 +1560,6 @@ def test_information(client: Client):
@pytest.mark.skip_t1(reason="Cannot test layouts on T1")
@pytest.mark.skip_tr(reason="Not implemented yet")
def test_information_mixed(client: Client):
inp1 = messages.TxInputType(
address_n=parse_path("m/44h/1h/0h/0/0"), # mvbu1Gdy8SUjTenqerxUaZyYjmveZvt33q
@ -1599,29 +1581,9 @@ def test_information_mixed(client: Client):
script_type=messages.OutputScriptType.PAYTOADDRESS,
)
def input_flow():
yield # confirm output
client.debug.wait_layout()
client.debug.press_yes()
yield # confirm output
client.debug.wait_layout()
client.debug.press_yes()
yield # confirm transaction
client.debug.wait_layout()
client.debug.press_info()
layout = client.debug.wait_layout()
content = layout.text_content().lower()
assert "sending from" in content
assert "multiple accounts" in content
assert "fee rate" in content
assert "18.33 sat" in content
client.debug.click(CORNER_BUTTON, wait=True)
client.debug.press_yes()
with client:
client.set_input_flow(input_flow)
IF = InputFlowSignTxInformationMixed(client)
client.set_input_flow(IF.get())
client.watch_layout(True)
btc.sign_tx(
@ -1634,7 +1596,6 @@ def test_information_mixed(client: Client):
@pytest.mark.skip_t1(reason="Cannot test layouts on T1")
@pytest.mark.skip_tr(reason="Not implemented yet")
def test_information_cancel(client: Client):
# input tx: 0dac366fd8a67b2a89fbb0d31086e7acded7a5bbf9ef9daa935bc873229ef5b5
@ -1652,24 +1613,9 @@ def test_information_cancel(client: Client):
script_type=messages.OutputScriptType.PAYTOADDRESS,
)
def input_flow():
yield # confirm output
client.debug.wait_layout()
client.debug.press_yes()
yield # confirm output
client.debug.wait_layout()
client.debug.press_yes()
yield # confirm transaction
client.debug.wait_layout()
client.debug.press_info()
client.debug.wait_layout()
client.debug.click(CORNER_BUTTON, wait=True)
client.debug.press_no()
with client, pytest.raises(Cancelled):
client.set_input_flow(input_flow)
IF = InputFlowSignTxInformationCancel(client)
client.set_input_flow(IF.get())
client.watch_layout(True)
btc.sign_tx(
@ -1682,7 +1628,6 @@ def test_information_cancel(client: Client):
@pytest.mark.skip_t1(reason="Cannot test layouts on T1")
@pytest.mark.skip_tr(reason="Input flow different on TR")
def test_information_replacement(client: Client):
# Use the change output and an external output to bump the fee.
# Originally fee was 3780, now 108060 (94280 from change and 10000 from external).
@ -1715,25 +1660,9 @@ def test_information_replacement(client: Client):
orig_index=0,
)
def input_flow():
yield # confirm txid
client.debug.press_yes()
yield # confirm address
client.debug.press_yes()
# go back to address
client.debug.press_no()
# confirm address
client.debug.press_yes()
yield # confirm amount
client.debug.press_yes()
yield # transaction summary, press info
client.debug.press_info(wait=True)
client.debug.click(CORNER_BUTTON, wait=True)
client.debug.press_yes()
with client:
client.set_input_flow(input_flow)
IF = InputFlowSignTxInformationReplacement(client)
client.set_input_flow(IF.get())
client.watch_layout(True)
btc.sign_tx(

View File

@ -446,6 +446,140 @@ class InputFlowSignTxHighFee(InputFlowBase):
yield from self.go_through_all_screens(screens)
def sign_tx_go_to_info(client: Client) -> Generator[None, None, str]:
yield # confirm output
client.debug.wait_layout()
client.debug.press_yes()
yield # confirm output
client.debug.wait_layout()
client.debug.press_yes()
yield # confirm transaction
client.debug.wait_layout()
client.debug.press_info()
layout = client.debug.wait_layout()
content = layout.text_content().lower()
client.debug.click(buttons.CORNER_BUTTON, wait=True)
return content
def sign_tx_go_to_info_tr(
client: Client,
) -> Generator[None, None, str]:
yield # confirm output
client.debug.wait_layout()
client.debug.press_right() # CONTINUE
client.debug.wait_layout()
client.debug.press_right() # CONFIRM
screen_texts: list[str] = []
yield # confirm total
layout = client.debug.press_right(wait=True)
screen_texts.append(layout.text_content())
layout = client.debug.press_right(wait=True)
screen_texts.append(layout.text_content())
client.debug.press_left()
client.debug.press_left()
return "\n".join(screen_texts)
class InputFlowSignTxInformation(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
def assert_content(self, content: str) -> None:
assert "sending from" in content
assert "legacy #6" in content
assert "fee rate" in content
assert "71.56 sat" in content
def input_flow_tt(self) -> GeneratorType:
content = yield from sign_tx_go_to_info(self.client)
self.assert_content(content)
self.client.debug.press_yes()
def input_flow_tr(self) -> GeneratorType:
content = yield from sign_tx_go_to_info_tr(self.client)
self.assert_content(content.lower())
self.client.debug.press_yes()
class InputFlowSignTxInformationMixed(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
def assert_content(self, content: str) -> None:
assert "sending from" in content
assert "multiple accounts" in content
assert "fee rate" in content
assert "18.33 sat" in content
def input_flow_tt(self) -> GeneratorType:
content = yield from sign_tx_go_to_info(self.client)
self.assert_content(content)
self.client.debug.press_yes()
def input_flow_tr(self) -> GeneratorType:
content = yield from sign_tx_go_to_info_tr(self.client)
self.assert_content(content.lower())
self.client.debug.press_yes()
class InputFlowSignTxInformationCancel(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
def input_flow_tt(self) -> GeneratorType:
yield from sign_tx_go_to_info(self.client)
self.client.debug.press_no()
def input_flow_tr(self) -> GeneratorType:
yield from sign_tx_go_to_info_tr(self.client)
self.client.debug.press_left()
class InputFlowSignTxInformationReplacement(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
def input_flow_tt(self) -> GeneratorType:
yield # confirm txid
self.client.debug.press_yes()
yield # confirm address
self.client.debug.press_yes()
# go back to address
self.client.debug.press_no()
# confirm address
self.client.debug.press_yes()
yield # confirm amount
self.client.debug.press_yes()
yield # transaction summary, press info
self.client.debug.press_info(wait=True)
self.client.debug.click(buttons.CORNER_BUTTON, wait=True)
self.client.debug.press_yes()
def input_flow_tr(self) -> GeneratorType:
yield # confirm txid
self.client.debug.press_right()
self.client.debug.press_right()
yield # confirm address
self.client.debug.press_right()
self.client.debug.press_right()
self.client.debug.press_right()
yield # confirm amount
self.client.debug.press_right()
self.client.debug.press_right()
self.client.debug.press_right()
def lock_time_input_flow_tt(
debug: DebugLink,
layout_assert_func: Callable[[DebugLink], None],