feat(core): create new Ethereum send flow for TT

[no changelog]
pull/3245/head
grdddj 11 months ago committed by Jiří Musil
parent eed6e0b71c
commit 8956350aca

@ -94,7 +94,7 @@ fn prepare_bindings() -> bindgen::Builder {
let mut clang_args: Vec<&str> = Vec::new();
let includes = env::var("RUST_INCLUDES").unwrap();
let args = includes.split(";");
let args = includes.split(';');
for arg in args {
clang_args.push(arg);

@ -34,6 +34,7 @@ static void _librust_qstrs(void) {
MP_QSTR_bounds;
MP_QSTR_button;
MP_QSTR_button_event;
MP_QSTR_cancel_arrow;
MP_QSTR_case_sensitive;
MP_QSTR_confirm_action;
MP_QSTR_confirm_address;
@ -67,9 +68,7 @@ static void _librust_qstrs(void) {
MP_QSTR_extra;
MP_QSTR_fee_amount;
MP_QSTR_fee_label;
MP_QSTR_fee_rate;
MP_QSTR_fee_rate_amount;
MP_QSTR_fee_rate_title;
MP_QSTR_hold;
MP_QSTR_hold_danger;
MP_QSTR_icon_name;
@ -114,6 +113,7 @@ static void _librust_qstrs(void) {
MP_QSTR_show_group_share_success;
MP_QSTR_show_homescreen;
MP_QSTR_show_info;
MP_QSTR_show_info_with_cancel;
MP_QSTR_show_lockscreen;
MP_QSTR_show_mismatch;
MP_QSTR_show_passphrase;
@ -122,7 +122,6 @@ static void _librust_qstrs(void) {
MP_QSTR_show_remaining_shares;
MP_QSTR_show_share_words;
MP_QSTR_show_simple;
MP_QSTR_show_spending_details;
MP_QSTR_show_success;
MP_QSTR_show_warning;
MP_QSTR_sign;

@ -133,6 +133,15 @@ impl CancelHold {
})
}
pub fn with_cancel_arrow() -> FixedHeightBar<Self> {
theme::button_bar(Self {
cancel: Some(Button::with_icon(theme::ICON_UP).into_child()),
hold: Button::with_text("HOLD TO CONFIRM")
.styled(theme::button_confirm())
.into_child(),
})
}
pub fn without_cancel() -> FixedHeightBar<Self> {
theme::button_bar(Self {
cancel: None,

@ -404,6 +404,15 @@ where
}
}
pub fn with_cancel_arrow(content: T, background: Color) -> Self {
let buttons = CancelHold::with_cancel_arrow();
Self {
inner: SwipePage::new(content, buttons, background),
loader: Loader::new(),
pad: Pad::with_background(background),
}
}
pub fn with_swipe_left(mut self) -> Self {
self.inner = self.inner.with_swipe_left();
self

@ -750,25 +750,19 @@ extern "C" fn new_show_address_details(n_args: usize, args: *const Obj, kwargs:
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn new_show_spending_details(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
extern "C" fn new_show_info_with_cancel(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let title: StrBuffer = kwargs.get_or(Qstr::MP_QSTR_title, "INFORMATION".into())?;
let account: Option<StrBuffer> = kwargs.get(Qstr::MP_QSTR_account)?.try_into_option()?;
let fee_rate: Option<StrBuffer> = kwargs.get(Qstr::MP_QSTR_fee_rate)?.try_into_option()?;
let fee_rate_title: StrBuffer =
kwargs.get_or(Qstr::MP_QSTR_fee_rate_title, "Fee rate:".into())?;
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?;
let mut paragraphs = ParagraphVecShort::new();
if let Some(a) = account {
paragraphs.add(Paragraph::new(
&theme::TEXT_NORMAL,
"Sending from account:".into(),
));
paragraphs.add(Paragraph::new(&theme::TEXT_MONO, a));
}
if let Some(f) = fee_rate {
paragraphs.add(Paragraph::new(&theme::TEXT_NORMAL, fee_rate_title));
paragraphs.add(Paragraph::new(&theme::TEXT_MONO, f));
for para in IterBuf::new().try_iterate(items)? {
let [key, value]: [Obj; 2] = iter_into_array(para)?;
let key: StrBuffer = key.try_into()?;
let value: StrBuffer = value.try_into()?;
paragraphs.add(Paragraph::new(&theme::TEXT_NORMAL, key));
paragraphs.add(Paragraph::new(&theme::TEXT_MONO, value));
}
let obj = LayoutObj::new(
@ -816,6 +810,7 @@ extern "C" fn new_confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Ma
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?;
let info_button: bool = kwargs.get_or(Qstr::MP_QSTR_info_button, false)?;
let cancel_arrow: bool = kwargs.get_or(Qstr::MP_QSTR_cancel_arrow, false)?;
let mut paragraphs = ParagraphVecShort::new();
@ -824,8 +819,11 @@ extern "C" fn new_confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Ma
paragraphs.add(Paragraph::new(&theme::TEXT_NORMAL, label));
paragraphs.add(Paragraph::new(&theme::TEXT_MONO, value));
}
let mut page = SwipeHoldPage::new(paragraphs.into_paragraphs(), theme::BG);
let mut page = if cancel_arrow {
SwipeHoldPage::with_cancel_arrow(paragraphs.into_paragraphs(), theme::BG)
} else {
SwipeHoldPage::new(paragraphs.into_paragraphs(), theme::BG)
};
if info_button {
page = page.with_swipe_left();
}
@ -1691,15 +1689,13 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// """Show address details - QR code, account, path, cosigner xpubs."""
Qstr::MP_QSTR_show_address_details => obj_fn_kw!(0, new_show_address_details).as_obj(),
/// def show_spending_details(
/// def show_info_with_cancel(
/// *,
/// title: str = "INFORMATION",
/// account: str | None,
/// fee_rate: str | None,
/// fee_rate_title: str = "Fee rate:",
/// title: str,
/// items: Iterable[Tuple[str, str]],
/// ) -> object:
/// """Show metadata when for outgoing transaction."""
Qstr::MP_QSTR_show_spending_details => obj_fn_kw!(0, new_show_spending_details).as_obj(),
Qstr::MP_QSTR_show_info_with_cancel => obj_fn_kw!(0, new_show_info_with_cancel).as_obj(),
/// def confirm_value(
/// *,
@ -1720,6 +1716,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// title: str,
/// items: list[tuple[str, str]],
/// info_button: bool = False,
/// cancel_arrow: bool = False,
/// ) -> object:
/// """Transaction summary. Always hold to confirm."""
Qstr::MP_QSTR_confirm_total => obj_fn_kw!(0, new_confirm_total).as_obj(),

@ -534,12 +534,10 @@ def show_address_details(
# rust/src/ui/model_tt/layout.rs
def show_spending_details(
def show_info_with_cancel(
*,
title: str = "INFORMATION",
account: str | None,
fee_rate: str | None,
fee_rate_title: str = "Fee rate:",
title: str,
items: Iterable[Tuple[str, str]],
) -> object:
"""Show metadata when for outgoing transaction."""
@ -565,6 +563,7 @@ def confirm_total(
title: str,
items: list[tuple[str, str]],
info_button: bool = False,
cancel_arrow: bool = False,
) -> object:
"""Transaction summary. Always hold to confirm."""

@ -997,7 +997,6 @@ async def confirm_ethereum_tx(
async def confirm_joint_total(spending_amount: str, total_amount: str) -> None:
await raise_if_not_confirmed(
interact(
RustLayout(

@ -894,15 +894,63 @@ async def confirm_total(
info_button=bool(account_label or fee_rate_amount),
)
)
items: list[tuple[str, str]] = []
if account_label:
items.append(("Sending from account:", account_label))
if fee_rate_amount:
items.append(("Fee rate:", fee_rate_amount))
info_layout = RustLayout(
trezorui2.show_spending_details(
account=account_label or "",
fee_rate=fee_rate_amount or "",
trezorui2.show_info_with_cancel(
title="INFORMATION",
items=items,
)
)
await with_info(total_layout, info_layout, br_type, br_code)
async def confirm_ethereum_tx(
recipient: str,
total_amount: str,
maximum_fee: str,
items: Iterable[tuple[str, str]],
br_type: str = "confirm_ethereum_tx",
br_code: ButtonRequestType = ButtonRequestType.SignTx,
) -> None:
total_layout = RustLayout(
trezorui2.confirm_total(
title="SUMMARY",
items=[
("Amount:", total_amount),
("Maximum fee:", maximum_fee),
],
info_button=True,
cancel_arrow=True,
)
)
info_layout = RustLayout(
trezorui2.show_info_with_cancel(
title="FEE INFORMATION",
items=items,
)
)
while True:
# Allowing going back and forth between recipient and summary/details
await confirm_blob(
br_type,
"RECIPIENT",
recipient,
verb="CONTINUE",
)
try:
total_layout.request_complete_repaint()
await with_info(total_layout, info_layout, br_type, br_code)
break
except ActionCancelled:
continue
async def confirm_joint_total(spending_amount: str, total_amount: str) -> None:
await raise_if_not_confirmed(
interact(
@ -1041,12 +1089,13 @@ async def confirm_modify_fee(
fee_rate_amount=fee_rate_amount,
)
)
items: list[tuple[str, str]] = []
if fee_rate_amount:
items.append(("New fee rate:", fee_rate_amount))
info_layout = RustLayout(
trezorui2.show_spending_details(
account=None,
trezorui2.show_info_with_cancel(
title="FEE INFORMATION",
fee_rate=fee_rate_amount,
fee_rate_title="New fee rate:",
items=items,
)
)
await with_info(fee_layout, info_layout, "modify_fee", ButtonRequestType.SignTx)

@ -24,9 +24,10 @@ from trezorlib.tools import parse_path, unharden
from ...common import parametrize_using_common_fixtures
from ...input_flows import (
InputFlowEthereumSignTxGoBack,
InputFlowEthereumSignTxScrollDown,
InputFlowEthereumSignTxSkip,
InputFlowEthereumSignTxDataGoBack,
InputFlowEthereumSignTxDataScrollDown,
InputFlowEthereumSignTxDataSkip,
InputFlowEthereumSignTxShowFeeInfo,
)
from .common import encode_network
@ -51,8 +52,15 @@ def make_defs(parameters: dict) -> messages.EthereumDefinitions:
"ethereum/sign_tx.json",
"ethereum/sign_tx_eip155.json",
)
def test_signtx(client: Client, parameters, result):
def test_signtx(client: Client, parameters: dict, result: dict):
_do_test_signtx(client, parameters, result)
def _do_test_signtx(client: Client, parameters: dict, result: dict, input_flow=None):
with client:
if input_flow:
client.watch_layout()
client.set_input_flow(input_flow)
sig_v, sig_r, sig_s = ethereum.sign_tx(
client,
n=parse_path(parameters["path"]),
@ -74,8 +82,32 @@ def test_signtx(client: Client, parameters, result):
assert sig_v == result["sig_v"]
@pytest.mark.skip_tr("Info is being shown in all the parametrized cases above")
@pytest.mark.skip_t1("T1 does not support input flows")
def test_signtx_fee_info(client: Client):
input_flow = InputFlowEthereumSignTxShowFeeInfo(client).get()
# Data taken from sign_tx_eip1559.json["tests"][0]
parameters = {
"chain_id": 1,
"path": "m/44'/60'/0'/0/0",
"nonce": "0x0",
"gas_price": "0x4a817c800",
"gas_limit": "0x5208",
"value": "0x2540be400",
"to_address": "0x8eA7a3fccC211ED48b763b4164884DDbcF3b0A98",
"tx_type": None,
"data": "",
}
result = {
"sig_v": 38,
"sig_r": "6a6349bddb5749bb8b96ce2566a035ef87a09dbf89b5c7e3dfdf9ed725912f24",
"sig_s": "4ae58ccd3bacee07cdc4a3e8540544fd009c4311af7048122da60f2054c07ee4",
}
_do_test_signtx(client, parameters, result, input_flow)
@parametrize_using_common_fixtures("ethereum/sign_tx_eip1559.json")
def test_signtx_eip1559(client: Client, parameters, result):
def test_signtx_eip1559(client: Client, parameters: dict, result: dict):
with client:
sig_v, sig_r, sig_s = ethereum.sign_tx_eip1559(
client,
@ -145,14 +177,13 @@ def test_data_streaming(client: Client):
checked in vectorized function above.
"""
with client:
t1 = client.features.model == "1"
tt = client.features.model == "T"
not_t1 = client.features.model != "1"
client.set_expected_responses(
[
messages.ButtonRequest(code=messages.ButtonRequestType.SignTx),
messages.ButtonRequest(code=messages.ButtonRequestType.SignTx),
(tt, messages.ButtonRequest(code=messages.ButtonRequestType.SignTx)),
(not_t1, messages.ButtonRequest(code=messages.ButtonRequestType.Other)),
(t1, messages.ButtonRequest(code=messages.ButtonRequestType.SignTx)),
(tt, messages.ButtonRequest(code=messages.ButtonRequestType.Other)),
messages.ButtonRequest(code=messages.ButtonRequestType.SignTx),
message_filters.EthereumTxRequest(
data_length=1_024,
@ -348,25 +379,25 @@ def test_sanity_checks_eip1559(client: Client):
)
def input_flow_skip(client: Client, cancel: bool = False):
return InputFlowEthereumSignTxSkip(client, cancel).get()
def input_flow_data_skip(client: Client, cancel: bool = False):
return InputFlowEthereumSignTxDataSkip(client, cancel).get()
def input_flow_scroll_down(client: Client, cancel: bool = False):
return InputFlowEthereumSignTxScrollDown(client, cancel).get()
def input_flow_data_scroll_down(client: Client, cancel: bool = False):
return InputFlowEthereumSignTxDataScrollDown(client, cancel).get()
def input_flow_go_back(client: Client, cancel: bool = False):
def input_flow_data_go_back(client: Client, cancel: bool = False):
if client.features.model == "R":
pytest.skip("Go back not supported for model R")
return InputFlowEthereumSignTxGoBack(client, cancel).get()
return InputFlowEthereumSignTxDataGoBack(client, cancel).get()
HEXDATA = "0123456789abcd000023456789abcd010003456789abcd020000456789abcd030000056789abcd040000006789abcd050000000789abcd060000000089abcd070000000009abcd080000000000abcd090000000001abcd0a0000000011abcd0b0000000111abcd0c0000001111abcd0d0000011111abcd0e0000111111abcd0f0000000002abcd100000000022abcd110000000222abcd120000002222abcd130000022222abcd140000222222abcd15"
@pytest.mark.parametrize(
"flow", (input_flow_skip, input_flow_scroll_down, input_flow_go_back)
"flow", (input_flow_data_skip, input_flow_data_scroll_down, input_flow_data_go_back)
)
@pytest.mark.skip_t1
def test_signtx_data_pagination(client: Client, flow):

@ -731,31 +731,40 @@ class InputFlowEIP712Cancel(InputFlowBase):
self.debug.press_no()
class InputFlowEthereumSignTxSkip(InputFlowBase):
class InputFlowEthereumSignTxShowFeeInfo(InputFlowBase):
def __init__(self, client: Client, cancel: bool = False):
super().__init__(client)
self.cancel = cancel
def input_flow_tt(self) -> BRGeneratorType:
yield # confirm recipient
self.debug.press_yes()
yield # summary
self.debug.press_info(wait=True)
self.debug.press_no(wait=True)
self.debug.press_yes(wait=True)
class InputFlowEthereumSignTxDataSkip(InputFlowBase):
def __init__(self, client: Client, cancel: bool = False):
super().__init__(client)
self.cancel = cancel
def input_flow_common(self) -> BRGeneratorType:
yield # confirm address
yield # confirm data
self.debug.press_yes()
yield # confirm amount
self.debug.wait_layout()
yield # confirm recipient
self.debug.press_yes()
yield # confirm data
yield # summary
if self.cancel:
self.debug.press_no()
yield # confirm recipient
self.debug.press_no()
else:
self.debug.press_yes()
yield # gas price
self.debug.press_yes()
yield # maximum fee
self.debug.press_yes()
yield # hold to confirm
self.debug.press_yes()
class InputFlowEthereumSignTxScrollDown(InputFlowBase):
class InputFlowEthereumSignTxDataScrollDown(InputFlowBase):
SHOW_ALL = (143, 167)
def __init__(self, client: Client, cancel: bool = False):
@ -763,12 +772,6 @@ class InputFlowEthereumSignTxScrollDown(InputFlowBase):
self.cancel = cancel
def input_flow_tt(self) -> BRGeneratorType:
yield # confirm address
self.debug.wait_layout()
self.debug.press_yes()
yield # confirm amount
self.debug.wait_layout()
self.debug.press_yes()
yield # confirm data
self.debug.wait_layout()
self.debug.click(self.SHOW_ALL)
@ -782,18 +785,20 @@ class InputFlowEthereumSignTxScrollDown(InputFlowBase):
self.debug.press_yes()
yield # confirm data
self.debug.press_yes()
yield # confirm recipient
self.debug.press_yes()
yield # summary
if self.cancel:
self.debug.press_no()
yield # confirm recipient
self.debug.press_no()
else:
self.debug.press_yes()
yield # gas price
self.debug.press_yes()
yield # maximum fee
self.debug.press_yes()
yield # hold to confirm
self.debug.press_yes()
self.debug.press_yes(wait=True)
def input_flow_tr(self) -> BRGeneratorType:
# TODO: fix this for TR and allow for cancelling the data
yield # confirm address
self.debug.wait_layout()
self.debug.press_yes()
@ -821,7 +826,7 @@ class InputFlowEthereumSignTxScrollDown(InputFlowBase):
self.debug.press_yes()
class InputFlowEthereumSignTxGoBack(InputFlowBase):
class InputFlowEthereumSignTxDataGoBack(InputFlowBase):
SHOW_ALL = (143, 167)
GO_BACK = (16, 220)
@ -830,13 +835,7 @@ class InputFlowEthereumSignTxGoBack(InputFlowBase):
self.cancel = cancel
def input_flow_tt(self) -> BRGeneratorType:
br = yield # confirm address
self.debug.wait_layout()
self.debug.press_yes()
br = yield # confirm amount
self.debug.wait_layout()
self.debug.press_yes()
br = yield # confirm data
yield # confirm data
self.debug.wait_layout()
self.debug.click(self.SHOW_ALL)
@ -852,20 +851,17 @@ class InputFlowEthereumSignTxGoBack(InputFlowBase):
self.debug.press_no()
else:
self.debug.press_yes()
yield # confirm address
self.debug.wait_layout()
yield # confirm recipient
self.debug.press_yes()
yield # confirm amount
self.debug.wait_layout()
self.debug.press_yes()
yield # hold to confirm
self.debug.wait_layout()
yield # summary
self.debug.press_yes()
return
elif i < br.pages - 1:
self.debug.swipe_up()
# TODO: support this for TR and allow for cancelling the data
def get_mnemonic_and_confirm_success(
debug: DebugLink,

@ -2765,9 +2765,9 @@
"TT_ethereum-test_signtx.py::test_signtx[nodata_1]": "08085423030e6828746fbf80695d1f3006bbac8a2638fb40fa1aee27c9410f2b",
"TT_ethereum-test_signtx.py::test_signtx[nodata_2_bigvalue]": "8dbd3278aa98b783387e3286ecb491963ef9e4989a1c5b33f9d8617449daf81e",
"TT_ethereum-test_signtx.py::test_signtx[wanchain]": "bd2050c78e0e958fcf566dc5a22e074cebbedd9b20247b0bda6a22b986f85a14",
"TT_ethereum-test_signtx.py::test_signtx_data_pagination[input_flow_go_back]": "8de2c846062d9b028ba23c02d9a53c54ef2fc55d377f084a43bc5d77be56e458",
"TT_ethereum-test_signtx.py::test_signtx_data_pagination[input_flow_scroll_down]": "ed6c9e088a12ea2e673dbcc99944aa5d6fd6e02e579442d908a3f3e245214145",
"TT_ethereum-test_signtx.py::test_signtx_data_pagination[input_flow_skip]": "d3c5c78e3b92dc3e32fd8d0c1a4b02ed9d826caf2d0e140c604c50ba39158082",
"TT_ethereum-test_signtx.py::test_signtx_data_pagination[input_flow_data_go_back]": "8de2c846062d9b028ba23c02d9a53c54ef2fc55d377f084a43bc5d77be56e458",
"TT_ethereum-test_signtx.py::test_signtx_data_pagination[input_flow_data_scroll_down]": "ed6c9e088a12ea2e673dbcc99944aa5d6fd6e02e579442d908a3f3e245214145",
"TT_ethereum-test_signtx.py::test_signtx_data_pagination[input_flow_data_skip]": "d3c5c78e3b92dc3e32fd8d0c1a4b02ed9d826caf2d0e140c604c50ba39158082",
"TT_ethereum-test_signtx.py::test_signtx_eip1559[Ledger Live legacy path]": "6dafe7c724f262a4fff5448b475ce848d3a74f76cb0ebaf60a5eb83bd20abb82",
"TT_ethereum-test_signtx.py::test_signtx_eip1559[data_1]": "2eb0129b9fd3c9123920b8f47d3150b0023f4455aa171b76c5e82c17f2747cc9",
"TT_ethereum-test_signtx.py::test_signtx_eip1559[data_2_bigdata]": "f9fff4dd81fbe5d950a7e699afbb58a8b2e8a87423ea10e25906f21d1c769dd4",

Loading…
Cancel
Save