feat(core): confirm ETH stake, unstake, claim

pull/3546/merge
obrusvit 3 months ago committed by Vít Obrusník
parent e1f696b4dd
commit ebcf3e2db2

@ -0,0 +1,128 @@
{
"setup": {
"mnemonic": "alcohol woman abuse must during monitor noble actual mixed trade anger aisle",
"passphrase": ""
},
"tests": [
{
"name": "stake_holesky",
"parameters": {
"comment": "Stake transaction - Holesky testnet",
"data": "3a29dbae0000000000000000000000000000000000000000000000000000000000000001",
"path": "m/44'/60'/0'/0/0",
"to_address": "0xAFA848357154a6a624686b348303EF9a13F63264",
"chain_id": 1,
"nonce": "0x0",
"gas_price": "0x14",
"gas_limit": "0x14",
"tx_type": null,
"value": "0x16345785D8A0000"
},
"result": {
"sig_v": 37,
"sig_r": "1a43606f1a3e9a61c4986cc9b324dd74f84557942cddf2e4ffccea82c2c54824",
"sig_s": "63e8595bfa1e383a1fea7205bff5afcf3b3d3513b45a2f792396f2f4251f4c55"
}
},
{
"name": "stake_main",
"parameters": {
"comment": "Stake transaction - Mainnet",
"data": "3a29dbae0000000000000000000000000000000000000000000000000000000000000001",
"path": "m/44'/60'/0'/0/0",
"to_address": "0xD523794C879D9eC028960a231F866758e405bE34",
"chain_id": 1,
"nonce": "0x0",
"gas_price": "0x14",
"gas_limit": "0x14",
"tx_type": null,
"value": "0x16345785D8A0000"
},
"result": {
"sig_v": 37,
"sig_r": "104ae56ff2ec396a86191fa94b6e79af20efcc28e5d16b39d90fe05c990a2ce6",
"sig_s": "504fd80e50890df83a9b004d0ba97db11b93354327e717df5c0874036f616d47"
}
},
{
"name": "unstake_holesky",
"parameters": {
"comment": "Unstake transaction - Holesky testnet",
"data": "76ec871c000000000000000000000000000000000000000000000000016345785d8a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001",
"path": "m/44'/60'/0'/0/0",
"to_address": "0xAFA848357154a6a624686b348303EF9a13F63264",
"chain_id": 1,
"nonce": "0x0",
"gas_price": "0x14",
"gas_limit": "0x14",
"tx_type": null,
"value": "0x0"
},
"result": {
"sig_v": 38,
"sig_r": "f6553486737da2ceb42067047d0e8dd0add8e82f49b524cf657215e1d2487d16",
"sig_s": "21336e4c53537bcdf06a71ed6cbefc3374461476d8da6e988e6cf4957b6a8bb1"
}
},
{
"name": "unstake_main",
"parameters": {
"comment": "Unstake transaction - Mainnet",
"data": "76ec871c000000000000000000000000000000000000000000000000016345785d8a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001",
"path": "m/44'/60'/0'/0/0",
"to_address": "0xD523794C879D9eC028960a231F866758e405bE34",
"chain_id": 1,
"nonce": "0x0",
"gas_price": "0x14",
"gas_limit": "0x14",
"tx_type": null,
"value": "0x0"
},
"result": {
"sig_v": 38,
"sig_r": "09077008477f40468928a94c45bfdb0b0ff473473401cd918740af4c98734bea",
"sig_s": "3cad467c41810a2d8901020a803d6836d1d5a39d8198eaba3eec121a48997b18"
}
},
{
"name": "claim_holesky",
"parameters": {
"comment": "Claim transaction - Holesky testnet",
"data": "33986ffa",
"path": "m/44'/60'/0'/0/0",
"to_address": "0x624087DD1904ab122A32878Ce9e933C7071F53B9",
"chain_id": 1,
"nonce": "0x0",
"gas_price": "0x14",
"gas_limit": "0x14",
"tx_type": null,
"value": "0x0"
},
"result": {
"sig_v": 37,
"sig_r": "4a77a5f7437c4744c8dc4e48f968a6fedccd86db0ee15ce6a832e71b17f11a9a",
"sig_s": "75e8efc0ecf1484ce745bbcd19899fbc458677869f3008be5ddb9c8ad8766d40"
}
},
{
"name": "claim_mainnet",
"parameters": {
"comment": "Claim transaction - Mainnet",
"data": "33986ffa",
"path": "m/44'/60'/0'/0/0",
"to_address": "0x7a7f0b3c23C23a31cFcb0c44709be70d4D545c6e",
"chain_id": 1,
"nonce": "0x0",
"gas_price": "0x14",
"gas_limit": "0x14",
"tx_type": null,
"value": "0x0"
},
"result": {
"sig_v": 37,
"sig_r": "c477417e2471d94c089d082fe1570fa8ba114e6572211c4b539244e2d40457d1",
"sig_s": "0bb029ed5dbe76016f74263c0a8709bcbd89affac62504196a1cf546d7c723a3"
}
}
]
}

@ -0,0 +1,108 @@
{
"setup": {
"mnemonic": "alcohol woman abuse must during monitor noble actual mixed trade anger aisle",
"passphrase": ""
},
"tests": [
{
"name": "stake_bad_inputs_1",
"parameters": {
"comment": "Stake transaction - Holesky testnet. Wrong source argument (should be 1).",
"data": "3a29dbae0000000000000000000000000000000000000000000000000000000000000002",
"path": "m/44'/60'/0'/0/0",
"to_address": "0xAFA848357154a6a624686b348303EF9a13F63264",
"chain_id": 1,
"nonce": "0x0",
"gas_price": "0x14",
"gas_limit": "0x14",
"tx_type": null,
"value": "0x16345785D8A0000"
},
"result": {
"sig_v": 0,
"sig_r": "0x0",
"sig_s": "0x0"
}
},
{
"name": "stake_bad_inputs_2",
"parameters": {
"comment": "Stake transaction - Mainnet. Missing arguments.",
"data": "3a29dbae",
"path": "m/44'/60'/0'/0/0",
"to_address": "0xD523794C879D9eC028960a231F866758e405bE34",
"chain_id": 1,
"nonce": "0x0",
"gas_price": "0x14",
"gas_limit": "0x14",
"tx_type": null,
"value": "0x16345785D8A0000"
},
"result": {
"sig_v": 0,
"sig_r": "0x0",
"sig_s": "0x0"
}
},
{
"name": "unstake_bad_inputs_1",
"parameters": {
"comment": "Unstake transaction - Holesky testnet. Wrong source argument (should be 1).",
"data": "76ec871c000000000000000000000000000000000000000000000000016345785d8a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002",
"path": "m/44'/60'/0'/0/0",
"to_address": "0xAFA848357154a6a624686b348303EF9a13F63264",
"chain_id": 1,
"nonce": "0x0",
"gas_price": "0x14",
"gas_limit": "0x14",
"tx_type": null,
"value": "0x0"
},
"result": {
"sig_v": 0,
"sig_r": "0x0",
"sig_s": "0x0"
}
},
{
"name": "unstake_bad_inputs_2",
"parameters": {
"comment": "Unstake transaction - Holesky testnet. Misaligned arguments.",
"data": "76ec871c000000000000000000",
"path": "m/44'/60'/0'/0/0",
"to_address": "0xAFA848357154a6a624686b348303EF9a13F63264",
"chain_id": 1,
"nonce": "0x0",
"gas_price": "0x14",
"gas_limit": "0x14",
"tx_type": null,
"value": "0x0"
},
"result": {
"sig_v": 0,
"sig_r": "0x0",
"sig_s": "0x0"
}
},
{
"name": "claim_bad_inputs_1",
"parameters": {
"comment": "Claim transaction - Mainnet. Misaligned data.",
"data": "33986ffaaa000aaa",
"path": "m/44'/60'/0'/0/0",
"to_address": "0x7a7f0b3c23C23a31cFcb0c44709be70d4D545c6e",
"chain_id": 1,
"nonce": "0x0",
"gas_price": "0x14",
"gas_limit": "0x14",
"tx_type": null,
"value": "0x0"
},
"result": {
"sig_v": 0,
"sig_r": "0x0",
"sig_s": "0x0"
}
}
]
}

@ -0,0 +1,128 @@
{
"setup": {
"mnemonic": "alcohol woman abuse must during monitor noble actual mixed trade anger aisle",
"passphrase": ""
},
"tests": [
{
"name": "stake_holesky",
"parameters": {
"comment": "Stake transaction - Holesky testnet",
"data": "3a29dbae0000000000000000000000000000000000000000000000000000000000000001",
"path": "m/44'/60'/0'/0/0",
"to_address": "0xAFA848357154a6a624686b348303EF9a13F63264",
"chain_id": 1,
"nonce": "0x0",
"gas_limit": "0x14",
"max_gas_fee": "0x14",
"max_priority_fee": "0x1",
"value": "0x16345785D8A0000"
},
"result": {
"sig_v": 0,
"sig_r": "77d68a17e2bcacccb791ca3d7e298588be0511a7d3c055abbb2030f54b56c6fa",
"sig_s": "567092e781fe1aedc458bdbc9ce4328398bf1d7d635787191881131c1afa7143"
}
},
{
"name": "stake_main",
"parameters": {
"comment": "Stake transaction - Mainnet",
"data": "3a29dbae0000000000000000000000000000000000000000000000000000000000000001",
"path": "m/44'/60'/0'/0/0",
"to_address": "0xD523794C879D9eC028960a231F866758e405bE34",
"chain_id": 1,
"nonce": "0x0",
"gas_limit": "0x14",
"max_gas_fee": "0x14",
"max_priority_fee": "0x1",
"value": "0x16345785D8A0000"
},
"result": {
"sig_v": 0,
"sig_r": "05a37bf477df7f256729ec8607aa20a6286c42a246b5deebaf6d54915c0f87e3",
"sig_s": "30ffe2e80119452403bbf238b02def90a120ade2e73ab60926060ed10562f5ed"
}
},
{
"name": "unstake_holesky",
"parameters": {
"comment": "Unstake transaction - Holesky testnet",
"data": "76ec871c000000000000000000000000000000000000000000000000016345785d8a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001",
"path": "m/44'/60'/0'/0/0",
"to_address": "0xAFA848357154a6a624686b348303EF9a13F63264",
"chain_id": 1,
"nonce": "0x0",
"gas_limit": "0x14",
"max_gas_fee": "0x14",
"max_priority_fee": "0x1",
"value": "0x0"
},
"result": {
"sig_v": 0,
"sig_r": "9dfa73ea497785ffcb61598c554a57e46fce0a605c9ed06a4c8f265fccfd912e",
"sig_s": "45db0bd2189d4bc4828c859d28b907b3d47c4a35eef707052cde2f4fff4ef4fe"
}
},
{
"name": "unstake_main",
"parameters": {
"comment": "Unstake transaction - Mainnet",
"data": "76ec871c000000000000000000000000000000000000000000000000016345785d8a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001",
"path": "m/44'/60'/0'/0/0",
"to_address": "0xD523794C879D9eC028960a231F866758e405bE34",
"chain_id": 1,
"nonce": "0x0",
"gas_limit": "0x14",
"max_gas_fee": "0x14",
"max_priority_fee": "0x1",
"value": "0x0"
},
"result": {
"sig_v": 0,
"sig_r": "15eb458e473c7f74abf99ba8833885e25435f913e77e5bc1259396942f6b9539",
"sig_s": "14ea7a1417cfab7bb2cdbd0750aa7929f02a30999b624dd2ae1029c6be9dc9d8"
}
},
{
"name": "claim_holesky",
"parameters": {
"comment": "Claim transaction - Holesky testnet",
"data": "33986ffa",
"path": "m/44'/60'/0'/0/0",
"to_address": "0x624087DD1904ab122A32878Ce9e933C7071F53B9",
"chain_id": 1,
"nonce": "0x0",
"gas_limit": "0x14",
"max_gas_fee": "0x14",
"max_priority_fee": "0x1",
"value": "0x0"
},
"result": {
"sig_v": 1,
"sig_r": "5abf2a99ac6431fce9e436234e2962502b1a67d4b582f90f4bc592e790f8fd7e",
"sig_s": "60b01cd76d40f089dc9464cb442c6bbac435f80a35b5a1ed128de7cb9718aa3a"
}
},
{
"name": "claim_mainnet",
"parameters": {
"comment": "Claim transaction - Mainnet",
"data": "33986ffa",
"path": "m/44'/60'/0'/0/0",
"to_address": "0x7a7f0b3c23C23a31cFcb0c44709be70d4D545c6e",
"chain_id": 1,
"nonce": "0x0",
"gas_limit": "0x14",
"max_gas_fee": "0x14",
"max_priority_fee": "0x1",
"value": "0x0"
},
"result": {
"sig_v": 0,
"sig_r": "7b3a09f1e0b28b2a25ddc3de02168fc20e9faada1d97181679dfb6f8383382da",
"sig_s": "6667dbb7b7b63d3e51df012a62dc6b01f13b60c7598b04aa499722cfb26864f3"
}
}
]
}

@ -0,0 +1 @@
Clear sign ETH staking transactions on Everstake pool.

@ -367,6 +367,14 @@ static void _librust_qstrs(void) {
MP_QSTR_ethereum__show_full_message;
MP_QSTR_ethereum__show_full_struct;
MP_QSTR_ethereum__sign_eip712;
MP_QSTR_ethereum__staking_claim;
MP_QSTR_ethereum__staking_claim_address;
MP_QSTR_ethereum__staking_claim_intro;
MP_QSTR_ethereum__staking_stake;
MP_QSTR_ethereum__staking_stake_address;
MP_QSTR_ethereum__staking_stake_intro;
MP_QSTR_ethereum__staking_unstake;
MP_QSTR_ethereum__staking_unstake_intro;
MP_QSTR_ethereum__title_confirm_data;
MP_QSTR_ethereum__title_confirm_domain;
MP_QSTR_ethereum__title_confirm_message;
@ -898,6 +906,7 @@ static void _librust_qstrs(void) {
MP_QSTR_stellar__your_account;
MP_QSTR_subprompt;
MP_QSTR_subtitle;
MP_QSTR_text_mono;
MP_QSTR_tezos__baker_address;
MP_QSTR_tezos__balance;
MP_QSTR_tezos__ballot;

@ -844,6 +844,14 @@ pub enum TranslatedString {
words__yes = 831,
reboot_to_bootloader__just_a_moment = 832,
inputs__previous = 833,
ethereum__staking_claim = 834,
ethereum__staking_claim_address = 835,
ethereum__staking_claim_intro = 836,
ethereum__staking_stake = 837,
ethereum__staking_stake_address = 838,
ethereum__staking_stake_intro = 839,
ethereum__staking_unstake = 840,
ethereum__staking_unstake_intro = 841,
}
impl TranslatedString {
@ -1683,6 +1691,14 @@ impl TranslatedString {
Self::words__yes => "Yes",
Self::reboot_to_bootloader__just_a_moment => "Just a moment...",
Self::inputs__previous => "PREVIOUS",
Self::ethereum__staking_claim => "CLAIM",
Self::ethereum__staking_claim_address => "CLAIM ADDRESS",
Self::ethereum__staking_claim_intro => "Claim ETH from Everstake?",
Self::ethereum__staking_stake => "STAKE",
Self::ethereum__staking_stake_address => "STAKE ADDRESS",
Self::ethereum__staking_stake_intro => "Stake ETH on Everstake?",
Self::ethereum__staking_unstake => "UNSTAKE",
Self::ethereum__staking_unstake_intro => "Unstake ETH from Everstake?",
}
}
@ -2523,6 +2539,14 @@ impl TranslatedString {
Qstr::MP_QSTR_words__yes => Some(Self::words__yes),
Qstr::MP_QSTR_reboot_to_bootloader__just_a_moment => Some(Self::reboot_to_bootloader__just_a_moment),
Qstr::MP_QSTR_inputs__previous => Some(Self::inputs__previous),
Qstr::MP_QSTR_ethereum__staking_claim => Some(Self::ethereum__staking_claim),
Qstr::MP_QSTR_ethereum__staking_claim_address => Some(Self::ethereum__staking_claim_address),
Qstr::MP_QSTR_ethereum__staking_claim_intro => Some(Self::ethereum__staking_claim_intro),
Qstr::MP_QSTR_ethereum__staking_stake => Some(Self::ethereum__staking_stake),
Qstr::MP_QSTR_ethereum__staking_stake_address => Some(Self::ethereum__staking_stake_address),
Qstr::MP_QSTR_ethereum__staking_stake_intro => Some(Self::ethereum__staking_stake_intro),
Qstr::MP_QSTR_ethereum__staking_unstake => Some(Self::ethereum__staking_unstake),
Qstr::MP_QSTR_ethereum__staking_unstake_intro => Some(Self::ethereum__staking_unstake_intro),
_ => None,
}
}

@ -463,6 +463,7 @@ struct ConfirmBlobParams {
info_button: bool,
hold: bool,
chunkify: bool,
text_mono: bool,
}
impl ConfirmBlobParams {
@ -485,6 +486,7 @@ impl ConfirmBlobParams {
info_button: false,
hold,
chunkify: false,
text_mono: true,
}
}
@ -508,6 +510,11 @@ impl ConfirmBlobParams {
self
}
fn with_text_mono(mut self, text_mono: bool) -> Self {
self.text_mono = text_mono;
self
}
fn into_layout(self) -> Result<Obj, Error> {
let paragraphs = ConfirmBlob {
description: self.description.unwrap_or_else(StrBuffer::empty),
@ -518,8 +525,10 @@ impl ConfirmBlobParams {
data_font: if self.chunkify {
let data: StrBuffer = self.data.try_into()?;
theme::get_chunkified_text_style(data.len())
} else {
} else if self.text_mono {
&theme::TEXT_MONO
} else {
&theme::TEXT_NORMAL
},
}
.into_paragraphs();
@ -738,6 +747,7 @@ extern "C" fn new_show_info_with_cancel(n_args: usize, args: *const Obj, kwargs:
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?;
let horizontal: bool = kwargs.get_or(Qstr::MP_QSTR_horizontal, false)?;
let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?;
let mut paragraphs = ParagraphVecShort::new();
@ -746,7 +756,14 @@ extern "C" fn new_show_info_with_cancel(n_args: usize, args: *const Obj, kwargs:
let key: StrBuffer = key.try_into()?;
let value: StrBuffer = value.try_into()?;
paragraphs.add(Paragraph::new(&theme::TEXT_NORMAL, key).no_break());
paragraphs.add(Paragraph::new(&theme::TEXT_MONO, value));
if chunkify {
paragraphs.add(Paragraph::new(
theme::get_chunkified_text_style(value.len()),
value,
));
} else {
paragraphs.add(Paragraph::new(&theme::TEXT_MONO, value));
}
}
let axis = match horizontal {
@ -787,11 +804,13 @@ extern "C" fn new_confirm_value(n_args: usize, args: *const Obj, kwargs: *mut Ma
.try_into_option()?;
let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?;
let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?;
let text_mono: bool = kwargs.get_or(Qstr::MP_QSTR_text_mono, true)?;
ConfirmBlobParams::new(title, value, description, verb, verb_cancel, hold)
.with_subtitle(subtitle)
.with_info_button(info_button)
.with_chunkify(chunkify)
.with_text_mono(text_mono)
.into_layout()
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -1779,6 +1798,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// title: str,
/// items: Iterable[Tuple[str, str]],
/// horizontal: bool = False,
/// chunkify: bool = False,
/// ) -> object:
/// """Show metadata for outgoing transaction."""
Qstr::MP_QSTR_show_info_with_cancel => obj_fn_kw!(0, new_show_info_with_cancel).as_obj(),
@ -1794,6 +1814,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// info_button: bool = False,
/// hold: bool = False,
/// chunkify: bool = False,
/// text_mono: bool = True,
/// ) -> object:
/// """Confirm value. Merge of confirm_total and confirm_output."""
Qstr::MP_QSTR_confirm_value => obj_fn_kw!(0, new_confirm_value).as_obj(),

@ -574,6 +574,7 @@ def show_info_with_cancel(
title: str,
items: Iterable[Tuple[str, str]],
horizontal: bool = False,
chunkify: bool = False,
) -> object:
"""Show metadata for outgoing transaction."""
@ -590,6 +591,7 @@ def confirm_value(
info_button: bool = False,
hold: bool = False,
chunkify: bool = False,
text_mono: bool = True,
) -> object:
"""Confirm value. Merge of confirm_total and confirm_output."""

@ -288,6 +288,14 @@ class TR:
ethereum__show_full_message: str = "Show full message"
ethereum__show_full_struct: str = "Show full struct"
ethereum__sign_eip712: str = "Really sign EIP-712 typed data?"
ethereum__staking_claim: str = "CLAIM"
ethereum__staking_claim_address: str = "CLAIM ADDRESS"
ethereum__staking_claim_intro: str = "Claim ETH from Everstake?"
ethereum__staking_stake: str = "STAKE"
ethereum__staking_stake_address: str = "STAKE ADDRESS"
ethereum__staking_stake_intro: str = "Stake ETH on Everstake?"
ethereum__staking_unstake: str = "UNSTAKE"
ethereum__staking_unstake_intro: str = "Unstake ETH from Everstake?"
ethereum__title_confirm_data: str = "CONFIRM DATA"
ethereum__title_confirm_domain: str = "CONFIRM DOMAIN"
ethereum__title_confirm_message: str = "CONFIRM MESSAGE"

@ -535,6 +535,8 @@ if not utils.BITCOIN_ONLY:
import apps.ethereum.sign_tx_eip1559
apps.ethereum.sign_typed_data
import apps.ethereum.sign_typed_data
apps.ethereum.staking_tx_constants
import apps.ethereum.staking_tx_constants
apps.ethereum.tokens
import apps.ethereum.tokens
apps.ethereum.verify_message

@ -1,13 +1,16 @@
from typing import TYPE_CHECKING
from ubinascii import hexlify
from trezor import TR
from . import networks
if TYPE_CHECKING:
from trezor.messages import EthereumFieldType
from typing import Iterable
from .networks import EthereumNetworkInfo
from trezor.messages import EthereumFieldType, EthereumTokenInfo
from .networks import EthereumNetworkInfo
RSKIP60_NETWORKS = (30, 31)
@ -126,6 +129,72 @@ def decode_typed_data(data: bytes, type_name: str) -> str:
raise ValueError # Unsupported data type for direct field decoding
def get_fee_items_regular(
gas_price: int, gas_limit: int, network: EthereumNetworkInfo
) -> Iterable[tuple[str, str]]:
# regular
gas_limit_str = TR.ethereum__units_template.format(gas_limit)
gas_price_str = format_ethereum_amount(
gas_price, None, network, force_unit_gwei=True
)
return (
(TR.ethereum__gas_limit, gas_limit_str),
(TR.ethereum__gas_price, gas_price_str),
)
def get_fee_items_eip1559(
max_gas_fee: int,
max_priority_fee: int,
gas_limit: int,
network: EthereumNetworkInfo,
) -> Iterable[tuple[str, str]]:
# EIP-1559
gas_limit_str = TR.ethereum__units_template.format(gas_limit)
max_gas_fee_str = format_ethereum_amount(
max_gas_fee, None, network, force_unit_gwei=True
)
max_priority_fee_str = format_ethereum_amount(
max_priority_fee, None, network, force_unit_gwei=True
)
return (
(TR.ethereum__gas_limit, gas_limit_str),
(TR.ethereum__max_gas_price, max_gas_fee_str),
(TR.ethereum__priority_fee, max_priority_fee_str),
)
def format_ethereum_amount(
value: int,
token: EthereumTokenInfo | None,
network: EthereumNetworkInfo,
force_unit_gwei: bool = False,
) -> str:
from trezor.strings import format_amount
if token:
suffix = token.symbol
decimals = token.decimals
else:
suffix = network.symbol
decimals = 18
if force_unit_gwei:
assert token is None
assert decimals >= 9
decimals = decimals - 9
suffix = "Gwei"
elif decimals > 9 and value < 10 ** (decimals - 9):
# Don't want to display wei values for tokens with small decimal numbers
suffix = "Wei " + suffix
decimals = 0
amount = format_amount(value, decimals)
return f"{amount} {suffix}"
def _from_bytes_bigendian_signed(b: bytes) -> int:
negative = b[0] & 0x80
if negative:

@ -4,12 +4,12 @@ from trezor import TR, ui
from trezor.enums import ButtonRequestType
from trezor.ui.layouts import (
confirm_blob,
confirm_ethereum_tx,
confirm_ethereum_staking_tx,
confirm_text,
should_show_more,
)
from .helpers import address_from_bytes, decode_typed_data
from .helpers import address_from_bytes, decode_typed_data, format_ethereum_amount
if TYPE_CHECKING:
from typing import Awaitable, Iterable
@ -25,12 +25,14 @@ if TYPE_CHECKING:
async def require_confirm_tx(
to_bytes: bytes,
value: int,
gas_price: int,
gas_limit: int,
maximum_fee: str,
fee_info_items: Iterable[tuple[str, str]],
network: EthereumNetworkInfo,
token: EthereumTokenInfo | None,
chunkify: bool,
) -> None:
from trezor.ui.layouts import confirm_ethereum_tx
if to_bytes:
to_str = address_from_bytes(to_bytes, network)
else:
@ -38,56 +40,80 @@ async def require_confirm_tx(
chunkify = False
total_amount = format_ethereum_amount(value, token, network)
maximum_fee = format_ethereum_amount(gas_price * gas_limit, None, network)
gas_limit_str = TR.ethereum__units_template.format(gas_limit)
gas_price_str = format_ethereum_amount(
gas_price, None, network, force_unit_gwei=True
)
items = (
(TR.ethereum__gas_limit, gas_limit_str),
(TR.ethereum__gas_price, gas_price_str),
)
await confirm_ethereum_tx(
to_str, total_amount, maximum_fee, items, chunkify=chunkify
to_str, total_amount, maximum_fee, fee_info_items, chunkify=chunkify
)
async def require_confirm_tx_eip1559(
to_bytes: bytes,
async def require_confirm_stake(
addr_bytes: bytes,
value: int,
max_gas_fee: int,
max_priority_fee: int,
gas_limit: int,
maximum_fee: str,
fee_info_items: Iterable[tuple[str, str]],
network: EthereumNetworkInfo,
token: EthereumTokenInfo | None,
chunkify: bool,
) -> None:
if to_bytes:
to_str = address_from_bytes(to_bytes, network)
else:
to_str = TR.ethereum__new_contract
chunkify = False
total_amount = format_ethereum_amount(value, token, network)
maximum_fee = format_ethereum_amount(max_gas_fee * gas_limit, None, network)
gas_limit_str = TR.ethereum__units_template.format(gas_limit)
max_gas_fee_str = format_ethereum_amount(
max_gas_fee, None, network, force_unit_gwei=True
)
max_priority_fee_str = format_ethereum_amount(
max_priority_fee, None, network, force_unit_gwei=True
addr_str = address_from_bytes(addr_bytes, network)
total_amount = format_ethereum_amount(value, None, network)
await confirm_ethereum_staking_tx(
TR.ethereum__staking_stake, # title
TR.ethereum__staking_stake_intro, # intro_question
TR.ethereum__staking_stake, # verb
total_amount, # total_amount
maximum_fee, # maximum_fee
addr_str, # address
TR.ethereum__staking_stake_address, # address_title
fee_info_items, # info_items
chunkify=chunkify,
)
items: tuple[tuple[str, str], ...] = (
(TR.ethereum__gas_limit, gas_limit_str),
(TR.ethereum__max_gas_price, max_gas_fee_str),
(TR.ethereum__priority_fee, max_priority_fee_str),
async def require_confirm_unstake(
addr_bytes: bytes,
value: int,
maximum_fee: str,
fee_info_items: Iterable[tuple[str, str]],
network: EthereumNetworkInfo,
chunkify: bool,
) -> None:
addr_str = address_from_bytes(addr_bytes, network)
total_amount = format_ethereum_amount(value, None, network)
await confirm_ethereum_staking_tx(
TR.ethereum__staking_unstake, # title
TR.ethereum__staking_unstake_intro, # intro_question
TR.ethereum__staking_unstake, # verb
total_amount, # total_amount
maximum_fee, # maximum_fee
addr_str, # address
TR.ethereum__staking_stake_address, # address_title
fee_info_items, # info_items
chunkify=chunkify,
)
await confirm_ethereum_tx(
to_str, total_amount, maximum_fee, items, chunkify=chunkify
async def require_confirm_claim(
addr_bytes: bytes,
maximum_fee: str,
fee_info_items: Iterable[tuple[str, str]],
network: EthereumNetworkInfo,
chunkify: bool,
) -> None:
addr_str = address_from_bytes(addr_bytes, network)
await confirm_ethereum_staking_tx(
TR.ethereum__staking_claim, # title
TR.ethereum__staking_claim_intro, # intro_question
TR.ethereum__staking_claim, # verb
"", # total_amount
maximum_fee, # maximum_fee
addr_str, # address
TR.ethereum__staking_claim_address, # address_title
fee_info_items, # info_items
chunkify=chunkify,
)
@ -119,7 +145,7 @@ def require_confirm_address(address_bytes: bytes) -> Awaitable[None]:
)
def require_confirm_data(data: bytes, data_total: int) -> Awaitable[None]:
def require_confirm_other_data(data: bytes, data_total: int) -> Awaitable[None]:
return confirm_blob(
"confirm_data",
TR.ethereum__title_confirm_data,
@ -258,35 +284,6 @@ async def confirm_typed_value(
)
def format_ethereum_amount(
value: int,
token: EthereumTokenInfo | None,
network: EthereumNetworkInfo,
force_unit_gwei: bool = False,
) -> str:
from trezor.strings import format_amount
if token:
suffix = token.symbol
decimals = token.decimals
else:
suffix = network.symbol
decimals = 18
if force_unit_gwei:
assert token is None
assert decimals >= 9
decimals = decimals - 9
suffix = "Gwei"
elif decimals > 9 and value < 10 ** (decimals - 9):
# Don't want to display wei values for tokens with small decimal numbers
suffix = "Wei " + suffix
decimals = 0
amount = format_amount(value, decimals)
return f"{amount} {suffix}"
def limit_str(s: str, limit: int = 16) -> str:
"""Shortens string to show the last <limit> characters."""
if len(s) <= limit + 2:

@ -2,13 +2,23 @@ from typing import TYPE_CHECKING
from trezor.crypto import rlp
from trezor.messages import EthereumTxRequest
from trezor.utils import BufferReader
from trezor.wire import DataError
from apps.ethereum import staking_tx_constants as constants
from .helpers import bytes_from_address
from .keychain import with_keychain_from_chain_id
if TYPE_CHECKING:
from trezor.messages import EthereumSignTx, EthereumTokenInfo, EthereumTxAck
from typing import Iterable
from trezor.messages import (
EthereumNetworkInfo,
EthereumSignTx,
EthereumTokenInfo,
EthereumTxAck,
)
from apps.common.keychain import Keychain
@ -33,7 +43,9 @@ async def sign_tx(
from apps.common import paths
from .layout import require_confirm_data, require_confirm_tx
from .helpers import format_ethereum_amount, get_fee_items_regular
data_total = msg.data_length # local_cache_attribute
# check
if msg.tx_type not in [1, 6, None]:
@ -42,26 +54,20 @@ async def sign_tx(
raise DataError("Fee overflow")
check_common_fields(msg)
# have a user confirm signing
await paths.validate_path(keychain, msg.address_n)
# Handle ERC20s
token, address_bytes, recipient, value = await handle_erc20(msg, defs)
data_total = msg.data_length # local_cache_attribute
if token is None and data_total > 0:
await require_confirm_data(msg.data_initial_chunk, data_total)
await require_confirm_tx(
recipient,
value,
int.from_bytes(msg.gas_price, "big"),
int.from_bytes(msg.gas_limit, "big"),
address_bytes = bytes_from_address(msg.to)
gas_price = int.from_bytes(msg.gas_price, "big")
gas_limit = int.from_bytes(msg.gas_limit, "big")
maximum_fee = format_ethereum_amount(gas_price * gas_limit, None, defs.network)
fee_items = get_fee_items_regular(
gas_price,
gas_limit,
defs.network,
token,
bool(msg.chunkify),
)
await confirm_tx_data(msg, defs, address_bytes, maximum_fee, fee_items, data_total)
# sign
data = bytearray()
data += msg.data_initial_chunk
data_left = data_total - len(msg.data_initial_chunk)
@ -99,16 +105,89 @@ async def sign_tx(
return result
async def handle_erc20(
async def confirm_tx_data(
msg: MsgInSignTx,
defs: Definitions,
address_bytes: bytes,
maximum_fee: str,
fee_items: Iterable[tuple[str, str]],
data_total_len: int,
) -> None:
# function distinguishes between staking / smart contracts / regular transactions
from .layout import require_confirm_other_data, require_confirm_tx
if await handle_staking(msg, defs.network, address_bytes, maximum_fee, fee_items):
return
# Handle ERC-20, currently only 'transfer' function
token, recipient, value = await handle_erc20_transfer(msg, defs, address_bytes)
if token is None and data_total_len > 0:
await require_confirm_other_data(msg.data_initial_chunk, data_total_len)
await require_confirm_tx(
recipient,
value,
maximum_fee,
fee_items,
defs.network,
token,
bool(msg.chunkify),
)
async def handle_staking(
msg: MsgInSignTx,
network: EthereumNetworkInfo,
address_bytes: bytes,
maximum_fee: str,
fee_items: Iterable[tuple[str, str]],
) -> bool:
data_reader = BufferReader(msg.data_initial_chunk)
if data_reader.remaining_count() < constants.SC_FUNC_SIG_BYTES:
return False
func_sig = data_reader.read_memoryview(constants.SC_FUNC_SIG_BYTES)
if address_bytes in constants.ADDRESSES_POOL:
if func_sig == constants.SC_FUNC_SIG_STAKE:
await _handle_staking_tx_stake(
data_reader, msg, network, address_bytes, maximum_fee, fee_items
)
return True
if func_sig == constants.SC_FUNC_SIG_UNSTAKE:
await _handle_staking_tx_unstake(
data_reader, msg, network, address_bytes, maximum_fee, fee_items
)
return True
if address_bytes in constants.ADDRESSES_ACCOUNTING:
if func_sig == constants.SC_FUNC_SIG_CLAIM:
await _handle_staking_tx_claim(
data_reader,
address_bytes,
maximum_fee,
fee_items,
network,
bool(msg.chunkify),
)
return True
# data not corresponding to staking transaction
return False
async def handle_erc20_transfer(
msg: MsgInSignTx,
definitions: Definitions,
) -> tuple[EthereumTokenInfo | None, bytes, bytes, int]:
address_bytes: bytes,
) -> tuple[EthereumTokenInfo | None, bytes, int]:
from . import tokens
from .layout import require_confirm_unknown_token
data_initial_chunk = msg.data_initial_chunk # local_cache_attribute
token = None
address_bytes = recipient = bytes_from_address(msg.to)
recipient = address_bytes
value = int.from_bytes(msg.value, "big")
if (
len(msg.to) in (40, 42)
@ -125,7 +204,7 @@ async def handle_erc20(
if token is tokens.UNKNOWN_TOKEN:
await require_confirm_unknown_token(address_bytes)
return token, address_bytes, recipient, value
return token, recipient, value
def _get_total_length(msg: EthereumSignTx, data_total: int) -> int:
@ -208,3 +287,92 @@ def check_common_fields(msg: MsgInSignTx) -> None:
if msg.chain_id == 0:
raise DataError("Chain ID out of bounds")
async def _handle_staking_tx_stake(
data_reader: BufferReader,
msg: MsgInSignTx,
network: EthereumNetworkInfo,
address_bytes: bytes,
maximum_fee: str,
fee_items: Iterable[tuple[str, str]],
) -> None:
from .layout import require_confirm_stake
# stake args:
# - arg0: uint64, source (should be 1)
try:
source = int.from_bytes(
data_reader.read_memoryview(constants.SC_ARGUMENT_BYTES), "big"
)
if source != 1:
raise ValueError # wrong value of 1st argument ('source' should be 1)
if data_reader.remaining_count() != 0:
raise ValueError # wrong number of arguments for stake (should be 1)
except (ValueError, EOFError):
raise DataError("Invalid staking transaction call")
await require_confirm_stake(
address_bytes,
int.from_bytes(msg.value, "big"),
maximum_fee,
fee_items,
network,
bool(msg.chunkify),
)
async def _handle_staking_tx_unstake(
data_reader: BufferReader,
msg: MsgInSignTx,
network: EthereumNetworkInfo,
address_bytes: bytes,
maximum_fee: str,
fee_items: Iterable[tuple[str, str]],
) -> None:
from .layout import require_confirm_unstake
# unstake args:
# - arg0: uint256, value
# - arg1: uint16, isAllowedInterchange (bool)
# - arg2: uint64, source, should be 1
try:
value = int.from_bytes(
data_reader.read_memoryview(constants.SC_ARGUMENT_BYTES), "big"
)
_ = data_reader.read_memoryview(constants.SC_ARGUMENT_BYTES) # skip arg1
source = int.from_bytes(
data_reader.read_memoryview(constants.SC_ARGUMENT_BYTES), "big"
)
if source != 1:
raise ValueError # wrong value of 3rd argument ('source' should be 1)
if data_reader.remaining_count() != 0:
raise ValueError # wrong number of arguments for unstake (should be 3)
except (ValueError, EOFError):
raise DataError("Invalid staking transaction call")
await require_confirm_unstake(
address_bytes,
value,
maximum_fee,
fee_items,
network,
bool(msg.chunkify),
)
async def _handle_staking_tx_claim(
data_reader: BufferReader,
staking_addr: bytes,
maximum_fee: str,
fee_items: Iterable[tuple[str, str]],
network: EthereumNetworkInfo,
chunkify: bool,
) -> None:
from .layout import require_confirm_claim
# claim has no args
if data_reader.remaining_count() != 0:
raise DataError("Invalid staking transaction call")
await require_confirm_claim(staking_addr, maximum_fee, fee_items, network, chunkify)

@ -42,8 +42,8 @@ async def sign_tx_eip1559(
from apps.common import paths
from .layout import require_confirm_data, require_confirm_tx_eip1559
from .sign_tx import check_common_fields, handle_erc20, send_request_chunk
from .helpers import format_ethereum_amount, get_fee_items_eip1559
from .sign_tx import check_common_fields, confirm_tx_data, send_request_chunk
gas_limit = msg.gas_limit # local_cache_attribute
data_total = msg.data_length # local_cache_attribute
@ -55,25 +55,23 @@ async def sign_tx_eip1559(
raise wire.DataError("Fee overflow")
check_common_fields(msg)
# have a user confirm signing
await paths.validate_path(keychain, msg.address_n)
# Handle ERC20s
token, address_bytes, recipient, value = await handle_erc20(msg, defs)
if token is None and data_total > 0:
await require_confirm_data(msg.data_initial_chunk, data_total)
await require_confirm_tx_eip1559(
recipient,
value,
int.from_bytes(msg.max_gas_fee, "big"),
int.from_bytes(msg.max_priority_fee, "big"),
int.from_bytes(gas_limit, "big"),
address_bytes = bytes_from_address(msg.to)
max_gas_fee = int.from_bytes(msg.max_gas_fee, "big")
max_priority_fee = int.from_bytes(msg.max_priority_fee, "big")
gas_limit = int.from_bytes(msg.gas_limit, "big")
maximum_fee = format_ethereum_amount(max_gas_fee * gas_limit, None, defs.network)
fee_items = get_fee_items_eip1559(
max_gas_fee,
max_priority_fee,
gas_limit,
defs.network,
token,
bool(msg.chunkify),
)
await confirm_tx_data(msg, defs, address_bytes, maximum_fee, fee_items, data_total)
# transaction data confirmed, proceed with signing
data = bytearray()
data += msg.data_initial_chunk
data_left = data_total - len(msg.data_initial_chunk)

@ -0,0 +1,21 @@
from micropython import const
from ubinascii import unhexlify
# smart contract 'data' field lengths in bytes
SC_FUNC_SIG_BYTES = const(4)
SC_ARGUMENT_BYTES = const(32)
# staking operations function signatures
SC_FUNC_SIG_STAKE = unhexlify("3a29dbae")
SC_FUNC_SIG_UNSTAKE = unhexlify("76ec871c")
SC_FUNC_SIG_CLAIM = unhexlify("33986ffa")
# addresses for pool (stake/unstake) and accounting (claim) operations
ADDRESSES_POOL = (
unhexlify("AFA848357154a6a624686b348303EF9a13F63264"), # holesky testnet
unhexlify("D523794C879D9eC028960a231F866758e405bE34"), # mainnet
)
ADDRESSES_ACCOUNTING = (
unhexlify("624087DD1904ab122A32878Ce9e933C7071F53B9"), # holesky testnet
unhexlify("7a7f0b3c23C23a31cFcb0c44709be70d4D545c6e"), # mainnet
)

@ -1023,7 +1023,7 @@ async def confirm_value(
title=info_title.upper(),
action=info_value,
description=description,
verb=TR.buttons__back,
verb="",
verb_cancel="<",
hold=False,
reverse=False,
@ -1067,6 +1067,56 @@ async def confirm_total(
)
async def confirm_ethereum_staking_tx(
title: str,
intro_question: str,
verb: str,
total_amount: str,
maximum_fee: str,
address: str,
address_title: str,
info_items: Iterable[tuple[str, str]],
br_type: str = "confirm_ethereum_staking_tx",
br_code: ButtonRequestType = ButtonRequestType.SignTx,
chunkify: bool = False,
) -> None:
# intro
await confirm_value(
title,
intro_question,
"",
br_type,
br_code,
verb=verb,
info_items=((address_title, address),),
)
# confirmation
if verb == TR.ethereum__staking_claim:
amount_title = verb
amount_value = ""
else:
amount_title = TR.words__amount + ":"
amount_value = total_amount
await raise_if_not_confirmed(
interact(
RustLayout(
trezorui2.altcoin_tx_summary(
amount_title=amount_title,
amount_value=amount_value,
fee_title=TR.send__maximum_fee,
fee_value=maximum_fee,
items=info_items,
cancel_cross=True,
)
),
br_type=br_type,
br_code=br_code,
)
)
async def confirm_solana_tx(
amount: str,
fee: str,

@ -859,6 +859,7 @@ def confirm_value(
verb: str | None = None,
subtitle: str | None = None,
hold: bool = False,
value_text_mono: bool = True,
info_items: Iterable[tuple[str, str]] | None = None,
) -> Awaitable[None]:
"""General confirmation dialog, used by many other confirm_* functions."""
@ -948,7 +949,11 @@ async def confirm_total(
info_items.append((TR.confirm_total__fee_rate, fee_rate_amount))
await confirm_summary(
items, TR.words__title_summary, info_items, br_type=br_type, br_code=br_code
items,
TR.words__title_summary,
info_items=info_items,
br_type=br_type,
br_code=br_code,
)
@ -956,6 +961,7 @@ async def confirm_summary(
items: Iterable[tuple[str, str]],
title: str | None = None,
info_items: Iterable[tuple[str, str]] | None = None,
info_title: str | None = None,
br_type: str = "confirm_total",
br_code: ButtonRequestType = ButtonRequestType.SignTx,
) -> None:
@ -971,7 +977,7 @@ async def confirm_summary(
info_items = info_items or []
info_layout = RustLayout(
trezorui2.show_info_with_cancel(
title=TR.words__title_information,
title=info_title.upper() if info_title else TR.words__title_information,
items=info_items,
)
)
@ -1025,6 +1031,60 @@ async def confirm_ethereum_tx(
continue
async def confirm_ethereum_staking_tx(
title: str,
intro_question: str,
verb: str,
total_amount: str,
maximum_fee: str,
address: str,
address_title: str,
info_items: Iterable[tuple[str, str]] | None = None,
chunkify: bool = False,
br_type: str = "confirm_ethereum_staking_tx",
br_code: ButtonRequestType = ButtonRequestType.SignTx,
) -> None:
# intro
# NOTE: this layout very similar to `confirm_value` with some adjustments
msg_layout = RustLayout(
trezorui2.confirm_value(
title=title,
value=intro_question,
description=None,
subtitle=None,
verb=verb,
info_button=True,
text_mono=False,
)
)
info_layout = RustLayout(
trezorui2.show_info_with_cancel(
title=address_title,
items=(("", address),),
chunkify=chunkify,
)
)
await raise_if_not_confirmed(with_info(msg_layout, info_layout, br_type, br_code))
# confirmation
if verb == TR.ethereum__staking_claim:
items = ((TR.send__maximum_fee, maximum_fee),)
else:
items = (
(TR.words__amount + ":", total_amount),
(TR.send__maximum_fee, maximum_fee),
)
await confirm_summary(
items, # items
title=title,
info_title=TR.confirm_total__title_fee,
info_items=info_items,
br_type=br_type,
br_code=br_code,
)
async def confirm_solana_tx(
amount: str,
fee: str,

@ -4,12 +4,11 @@ if not utils.BITCOIN_ONLY:
from ethereum_common import make_network, make_token
from apps.ethereum import networks
from apps.ethereum.layout import format_ethereum_amount
from apps.ethereum.helpers import format_ethereum_amount
from apps.ethereum.tokens import UNKNOWN_TOKEN
ETH = networks.by_chain_id(1)
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
class TestFormatEthereumAmount(unittest.TestCase):
def test_denominations(self):

@ -299,6 +299,14 @@
"ethereum__units_template": "{} units",
"ethereum__unknown_token": "Unknown token",
"ethereum__valid_signature": "The signature is valid.",
"ethereum__staking_stake": "STAKE",
"ethereum__staking_stake_address": "STAKE ADDRESS",
"ethereum__staking_stake_intro": "Stake ETH on Everstake?",
"ethereum__staking_unstake": "UNSTAKE",
"ethereum__staking_unstake_intro": "Unstake ETH from Everstake?",
"ethereum__staking_claim": "CLAIM",
"ethereum__staking_claim_address": "CLAIM ADDRESS",
"ethereum__staking_claim_intro": "Claim ETH from Everstake?",
"experimental_mode__enable": "Enable experimental features?",
"experimental_mode__only_for_dev": "Only for development and beta testing!",
"experimental_mode__title": "EXPERIMENTAL MODE",

@ -832,5 +832,13 @@
"830": "words__writable",
"831": "words__yes",
"832": "reboot_to_bootloader__just_a_moment",
"833": "inputs__previous"
"833": "inputs__previous",
"834": "ethereum__staking_claim",
"835": "ethereum__staking_claim_address",
"836": "ethereum__staking_claim_intro",
"837": "ethereum__staking_stake",
"838": "ethereum__staking_stake_address",
"839": "ethereum__staking_stake_intro",
"840": "ethereum__staking_unstake",
"841": "ethereum__staking_unstake_intro"
}

@ -1,9 +1,9 @@
{
"current": {
"merkle_root": "4add9b7a2b80544a382378bc1abdae38600460825ef8010d45da5c2f28d86d26",
"merkle_root": "ebba747f556487944a26b19deb5910648694670f0de05f5a1569e1e12cf47ea0",
"signature": null,
"datetime": "2024-02-21T09:03:23.136322",
"commit": "1dc00561ae04804aecbb0715d092c2a907e8eed8"
"datetime": "2024-02-21T15:09:10.231125",
"commit": "4204ba14044132269dc708e3f0b0cfac3bbfd906"
},
"history": []
}

@ -454,3 +454,74 @@ def test_signtx_data_pagination(client: Client, flow):
client.watch_layout()
client.set_input_flow(flow(client, cancel=True))
_sign_tx_call()
@pytest.mark.skip_t1("T1 does not support Everstake")
@parametrize_using_common_fixtures("ethereum/sign_tx_staking.json")
# TODO input flows to go into info screens - then also parametrizing chunkify might make sense
# @pytest.mark.parametrize("chunkify", (True, False))
def test_signtx_staking(client: Client, parameters: dict, result: dict):
with client:
sig_v, sig_r, sig_s = ethereum.sign_tx(
client,
n=parse_path(parameters["path"]),
nonce=int(parameters["nonce"], 16),
gas_price=int(parameters["gas_price"], 16),
gas_limit=int(parameters["gas_limit"], 16),
to=parameters["to_address"],
value=int(parameters["value"], 16),
data=bytes.fromhex(parameters["data"]),
chain_id=parameters["chain_id"],
tx_type=parameters["tx_type"],
definitions=None,
chunkify=False,
)
expected_v = 2 * parameters["chain_id"] + 35
assert sig_v in (expected_v, expected_v + 1)
assert sig_r.hex() == result["sig_r"]
assert sig_s.hex() == result["sig_s"]
assert sig_v == result["sig_v"]
@pytest.mark.skip_t1("T1 does not support Everstake")
@parametrize_using_common_fixtures("ethereum/sign_tx_staking_data_error.json")
def test_signtx_staking_bad_inputs(client: Client, parameters: dict, result: dict):
# result not needed
with pytest.raises(TrezorFailure, match=r"DataError"):
ethereum.sign_tx(
client,
n=parse_path(parameters["path"]),
nonce=int(parameters["nonce"], 16),
gas_price=int(parameters["gas_price"], 16),
gas_limit=int(parameters["gas_limit"], 16),
to=parameters["to_address"],
value=int(parameters["value"], 16),
data=bytes.fromhex(parameters["data"]),
chain_id=parameters["chain_id"],
tx_type=parameters["tx_type"],
definitions=None,
chunkify=False,
)
@pytest.mark.skip_t1("T1 does not support Everstake")
@parametrize_using_common_fixtures("ethereum/sign_tx_staking_eip1559.json")
def test_signtx_staking_eip1559(client: Client, parameters: dict, result: dict):
with client:
sig_v, sig_r, sig_s = ethereum.sign_tx_eip1559(
client,
n=parse_path(parameters["path"]),
nonce=int(parameters["nonce"], 16),
max_gas_fee=int(parameters["max_gas_fee"], 16),
max_priority_fee=int(parameters["max_priority_fee"], 16),
gas_limit=int(parameters["gas_limit"], 16),
to=parameters["to_address"],
value=int(parameters["value"], 16),
data=bytes.fromhex(parameters["data"]),
chain_id=parameters["chain_id"],
definitions=None,
chunkify=True,
)
assert sig_r.hex() == result["sig_r"]
assert sig_s.hex() == result["sig_s"]
assert sig_v == result["sig_v"]

@ -4507,6 +4507,23 @@
"TR_en_ethereum-test_signtx.py::test_signtx_eip1559_access_list_larger": "5ec441ee292a9034c7d859f216050e7af702dcc219ed16e4ca17352ae4784c9b",
"TR_en_ethereum-test_signtx.py::test_signtx_fee_info": "b4ae728ff71c1e6112abbb0111b85b2760f957b677726b35734e63c318495408",
"TR_en_ethereum-test_signtx.py::test_signtx_go_back_from_summary": "263993daffe2a77a46a17d5b598aca84de52ba0e051e4cb5de5c524a48192ed3",
"TR_en_ethereum-test_signtx.py::test_signtx_staking[claim_holesky]": "8381fbc4aac0431757244c7813c33abee1a3457661a71b7b8b71fb4cd319d6f8",
"TR_en_ethereum-test_signtx.py::test_signtx_staking[claim_mainnet]": "8381fbc4aac0431757244c7813c33abee1a3457661a71b7b8b71fb4cd319d6f8",
"TR_en_ethereum-test_signtx.py::test_signtx_staking[stake_holesky]": "856bc0275109dcf0fbac25b20916ac78f358533e41e896b299959fbc0364a240",
"TR_en_ethereum-test_signtx.py::test_signtx_staking[stake_main]": "856bc0275109dcf0fbac25b20916ac78f358533e41e896b299959fbc0364a240",
"TR_en_ethereum-test_signtx.py::test_signtx_staking[unstake_holesky]": "4d80581b69013423285c924d837135a2c2b09b62cf2635fdbe3a8f224511bc7e",
"TR_en_ethereum-test_signtx.py::test_signtx_staking[unstake_main]": "4d80581b69013423285c924d837135a2c2b09b62cf2635fdbe3a8f224511bc7e",
"TR_en_ethereum-test_signtx.py::test_signtx_staking_bad_inputs[claim_bad_inputs_1]": "b70d9d2aa7a8ace3251763c1d2fcb53dd8c741b7520d717398df8f7ff8ac9128",
"TR_en_ethereum-test_signtx.py::test_signtx_staking_bad_inputs[stake_bad_inputs_1]": "b70d9d2aa7a8ace3251763c1d2fcb53dd8c741b7520d717398df8f7ff8ac9128",
"TR_en_ethereum-test_signtx.py::test_signtx_staking_bad_inputs[stake_bad_inputs_2]": "b70d9d2aa7a8ace3251763c1d2fcb53dd8c741b7520d717398df8f7ff8ac9128",
"TR_en_ethereum-test_signtx.py::test_signtx_staking_bad_inputs[unstake_bad_inputs_1]": "b70d9d2aa7a8ace3251763c1d2fcb53dd8c741b7520d717398df8f7ff8ac9128",
"TR_en_ethereum-test_signtx.py::test_signtx_staking_bad_inputs[unstake_bad_inputs_2]": "b70d9d2aa7a8ace3251763c1d2fcb53dd8c741b7520d717398df8f7ff8ac9128",
"TR_en_ethereum-test_signtx.py::test_signtx_staking_eip1559[claim_holesky]": "bf6067c40a106b593ecd43632308fd384a505be4b10269eae07090d1bb24b3f1",
"TR_en_ethereum-test_signtx.py::test_signtx_staking_eip1559[claim_mainnet]": "bf6067c40a106b593ecd43632308fd384a505be4b10269eae07090d1bb24b3f1",
"TR_en_ethereum-test_signtx.py::test_signtx_staking_eip1559[stake_holesky]": "5f0472c1ae8e221509b5568a89f7942e18caaa8e7b022d75519cfa0e671c94bc",
"TR_en_ethereum-test_signtx.py::test_signtx_staking_eip1559[stake_main]": "5f0472c1ae8e221509b5568a89f7942e18caaa8e7b022d75519cfa0e671c94bc",
"TR_en_ethereum-test_signtx.py::test_signtx_staking_eip1559[unstake_holesky]": "9c0d8add3295ed7de71bc13084e1216149af5cce259a72dc9c558c4fbfc9e479",
"TR_en_ethereum-test_signtx.py::test_signtx_staking_eip1559[unstake_main]": "9c0d8add3295ed7de71bc13084e1216149af5cce259a72dc9c558c4fbfc9e479",
"TR_en_misc-test_cosi.py::test_cosi_different_key": "8c801bd0142e5c1ad4aad50b34c7debb1b8f17a2e0a87eb7f95531b9fd15e095",
"TR_en_misc-test_cosi.py::test_cosi_nonce": "df3420ca2395ced6fb2e3e5b984ece9d1a1151d877061681582c8f9404416600",
"TR_en_misc-test_cosi.py::test_cosi_pubkey": "8c801bd0142e5c1ad4aad50b34c7debb1b8f17a2e0a87eb7f95531b9fd15e095",
@ -11564,6 +11581,23 @@
"TT_en_ethereum-test_signtx.py::test_signtx_eip1559_access_list_larger": "243010310ac5a4c70c627507ea8501cc61c2e20728eb06bc796f093132bebb4f",
"TT_en_ethereum-test_signtx.py::test_signtx_fee_info": "714e4c5f6e6b45fa3e78f74c7ee5e3332f39686f8b708a4f56232105bde0c3e4",
"TT_en_ethereum-test_signtx.py::test_signtx_go_back_from_summary": "8bc38a773c40a70c1eb9b91a5d02ce0a61591ce9e42bd0073bc1395f560f2490",
"TT_en_ethereum-test_signtx.py::test_signtx_staking[claim_holesky]": "63a2b20a46d7eb9dbe188f45286f0e19b696b4fa072743156a1f70b8c33d5dad",
"TT_en_ethereum-test_signtx.py::test_signtx_staking[claim_mainnet]": "63a2b20a46d7eb9dbe188f45286f0e19b696b4fa072743156a1f70b8c33d5dad",
"TT_en_ethereum-test_signtx.py::test_signtx_staking[stake_holesky]": "f24ba4c504e12ec403aa99f19f9d9c78cc513edb2b7671063033902d089d894c",
"TT_en_ethereum-test_signtx.py::test_signtx_staking[stake_main]": "f24ba4c504e12ec403aa99f19f9d9c78cc513edb2b7671063033902d089d894c",
"TT_en_ethereum-test_signtx.py::test_signtx_staking[unstake_holesky]": "b24d5247a866e3aa69fe3fc17eabaa210890b285e3c4b84eb253570fcc0c8bed",
"TT_en_ethereum-test_signtx.py::test_signtx_staking[unstake_main]": "b24d5247a866e3aa69fe3fc17eabaa210890b285e3c4b84eb253570fcc0c8bed",
"TT_en_ethereum-test_signtx.py::test_signtx_staking_bad_inputs[claim_bad_inputs_1]": "3b6c5cf5c6512f1491b77f895d21d2f850f774c2b9d67c1b76eaeb2892e95e6b",
"TT_en_ethereum-test_signtx.py::test_signtx_staking_bad_inputs[stake_bad_inputs_1]": "3b6c5cf5c6512f1491b77f895d21d2f850f774c2b9d67c1b76eaeb2892e95e6b",
"TT_en_ethereum-test_signtx.py::test_signtx_staking_bad_inputs[stake_bad_inputs_2]": "3b6c5cf5c6512f1491b77f895d21d2f850f774c2b9d67c1b76eaeb2892e95e6b",
"TT_en_ethereum-test_signtx.py::test_signtx_staking_bad_inputs[unstake_bad_inputs_1]": "3b6c5cf5c6512f1491b77f895d21d2f850f774c2b9d67c1b76eaeb2892e95e6b",
"TT_en_ethereum-test_signtx.py::test_signtx_staking_bad_inputs[unstake_bad_inputs_2]": "3b6c5cf5c6512f1491b77f895d21d2f850f774c2b9d67c1b76eaeb2892e95e6b",
"TT_en_ethereum-test_signtx.py::test_signtx_staking_eip1559[claim_holesky]": "63a2b20a46d7eb9dbe188f45286f0e19b696b4fa072743156a1f70b8c33d5dad",
"TT_en_ethereum-test_signtx.py::test_signtx_staking_eip1559[claim_mainnet]": "63a2b20a46d7eb9dbe188f45286f0e19b696b4fa072743156a1f70b8c33d5dad",
"TT_en_ethereum-test_signtx.py::test_signtx_staking_eip1559[stake_holesky]": "f24ba4c504e12ec403aa99f19f9d9c78cc513edb2b7671063033902d089d894c",
"TT_en_ethereum-test_signtx.py::test_signtx_staking_eip1559[stake_main]": "f24ba4c504e12ec403aa99f19f9d9c78cc513edb2b7671063033902d089d894c",
"TT_en_ethereum-test_signtx.py::test_signtx_staking_eip1559[unstake_holesky]": "b24d5247a866e3aa69fe3fc17eabaa210890b285e3c4b84eb253570fcc0c8bed",
"TT_en_ethereum-test_signtx.py::test_signtx_staking_eip1559[unstake_main]": "b24d5247a866e3aa69fe3fc17eabaa210890b285e3c4b84eb253570fcc0c8bed",
"TT_en_misc-test_cosi.py::test_cosi_different_key": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",
"TT_en_misc-test_cosi.py::test_cosi_nonce": "25a47ec1384fb563a6495d92d9319d19220cbb15b0f33fbdc26f01d3ccde1980",
"TT_en_misc-test_cosi.py::test_cosi_pubkey": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",

@ -6,7 +6,7 @@ EXCEPTIONS+=( "decred" ) # "decred" figures in field names used by the bitcoin
EXCEPTIONS+=( "omni" ) # OMNI is part of the bitcoin app
# BIP39 or SLIP39 words that have "dash" in them
EXCEPTIONS+=( "dash" )
EXCEPTIONS+=( "confirm_ethereum_tx" ) # is model-specific, so is in layout/__init__.py instead of ethereum/layout.py
EXCEPTIONS+=( "confirm_ethereum_tx" "confirm_ethereum_staking_tx" ) # model-specific, so is in layout/__init__.py instead of ethereum/layout.py
EXCEPTIONS+=( "__" ) # ignoring the translations blob (section__key delimiter)
EXCEPTIONS+=( "{}" ) # ignoring the translations blob (template identifier)

@ -1 +1 @@
Subproject commit 9cfd22ef20fec2c34d0f0e5c16a5d5152da30861
Subproject commit 28e177c4424820aee8a6f031474c890e5bafe72c
Loading…
Cancel
Save