1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-05 05:58:23 +00:00
trezor-firmware/tests/device_tests/ethereum/test_definitions.py
2024-11-29 11:12:10 +01:00

243 lines
9.1 KiB
Python

from __future__ import annotations
from typing import Callable
import pytest
from trezorlib import ethereum
from trezorlib.debuglink import SessionDebugWrapper as Session
from trezorlib.exceptions import TrezorFailure
from trezorlib.tools import parse_path
from ...input_flows import InputFlowConfirmAllWarnings
from . import common
from .test_sign_typed_data import DATA as TYPED_DATA
pytestmark = [pytest.mark.altcoin, pytest.mark.ethereum]
ERC20_OPERATION = "a9059cbb000000000000000000000000574bbb36871ba6b78e27f4b4dcfb76ea0091880b0000000000000000000000000000000000000000000000000000000000000123"
ERC20_BUILTIN_TOKEN = "0xdac17f958d2ee523a2206206994597c13d831ec7" # USDT
ERC20_FAKE_ADDRESS = "0xdddddddddddddddddddddddddddddddddddddddd"
DEFAULT_TX_PARAMS = {
"nonce": 0x0,
"gas_price": 0x4A817C800,
"gas_limit": 0x5208,
"value": 0x2540BE400,
"to": "0x1d1c328764a41bda0492b66baa30c4a339ff85ef",
"chain_id": 1,
"n": parse_path("m/44h/60h/0h/0/0"),
}
DEFAULT_ERC20_PARAMS = {
"nonce": 0x0,
"gas_price": 0x4A817C800,
"gas_limit": 0x5208,
"value": 0x0,
"chain_id": 1,
"n": parse_path("m/44h/60h/0h/0/0"),
"data": bytes.fromhex(ERC20_OPERATION),
}
def test_builtin(session: Session) -> None:
# Ethereum (SLIP-44 60, chain_id 1) will sign without any definitions provided
ethereum.sign_tx(session, **DEFAULT_TX_PARAMS)
def test_chain_id_allowed(session: Session) -> None:
# Any chain id is allowed as long as the SLIP44 stays the same
params = DEFAULT_TX_PARAMS.copy()
params.update(chain_id=222222)
ethereum.sign_tx(session, **params)
def test_slip44_disallowed(session: Session) -> None:
# SLIP44 is not allowed without a valid network definition
params = DEFAULT_TX_PARAMS.copy()
params.update(n=parse_path("m/44h/66666h/0h/0/0"))
with pytest.raises(TrezorFailure, match="Forbidden key path"):
ethereum.sign_tx(session, **params)
def test_slip44_external(session: Session) -> None:
# to use a non-default SLIP44, a valid network definition must be provided
network = common.encode_network(chain_id=66666, slip44=66666)
params = DEFAULT_TX_PARAMS.copy()
params.update(n=parse_path("m/44h/66666h/0h/0/0"), chain_id=66666)
ethereum.sign_tx(session, **params, definitions=common.make_defs(network, None))
def test_slip44_external_disallowed(session: Session) -> None:
# network definition does not allow a different SLIP44
network = common.encode_network(chain_id=66666, slip44=66666)
params = DEFAULT_TX_PARAMS.copy()
params.update(n=parse_path("m/44h/55555h/0h/0/0"), chain_id=66666)
with pytest.raises(TrezorFailure, match="Forbidden key path"):
ethereum.sign_tx(session, **params, definitions=common.make_defs(network, None))
def test_chain_id_mismatch(session: Session) -> None:
# network definition for a different chain id will be rejected
network = common.encode_network(chain_id=66666, slip44=60)
params = DEFAULT_TX_PARAMS.copy()
params.update(chain_id=55555)
with pytest.raises(TrezorFailure, match="Network definition mismatch"):
ethereum.sign_tx(session, **params, definitions=common.make_defs(network, None))
def test_definition_does_not_override_builtin(session: Session) -> None:
# The builtin definition for Ethereum (SLIP44 60, chain_id 1) will be used
# even if a valid definition with a different SLIP44 is provided
network = common.encode_network(chain_id=1, slip44=66666)
params = DEFAULT_TX_PARAMS.copy()
params.update(n=parse_path("m/44h/66666h/0h/0/0"), chain_id=1)
with pytest.raises(TrezorFailure, match="Forbidden key path"):
ethereum.sign_tx(session, **params, definitions=common.make_defs(network, None))
# TODO: test that the builtin definition will not show different symbol
# TODO: figure out how to test acceptance of a token definition
# all tokens are currently accepted, we would need to check the screenshots
def test_builtin_token(session: Session) -> None:
# The builtin definition for USDT (ERC20) will be used even if not provided
params = DEFAULT_ERC20_PARAMS.copy()
params.update(to=ERC20_BUILTIN_TOKEN)
ethereum.sign_tx(session, **params)
# TODO check that USDT symbol is shown
# TODO: test_builtin_token_not_overriden (builtin definition is used even if a custom one is provided)
def test_external_token(session: Session) -> None:
# A valid token definition must be provided to use a non-builtin token
token = common.encode_token(address=ERC20_FAKE_ADDRESS, chain_id=1, decimals=8)
params = DEFAULT_ERC20_PARAMS.copy()
params.update(to=ERC20_FAKE_ADDRESS)
ethereum.sign_tx(session, **params, definitions=common.make_defs(None, token))
# TODO check that FakeTok symbol is shown
def test_external_chain_without_token(session: Session) -> None:
with session, session.client as client:
if not client.debug.legacy_debug:
client.set_input_flow(InputFlowConfirmAllWarnings(client).get())
# when using an external chains, unknown tokens are allowed
network = common.encode_network(chain_id=66666, slip44=60)
params = DEFAULT_ERC20_PARAMS.copy()
params.update(to=ERC20_BUILTIN_TOKEN, chain_id=66666)
ethereum.sign_tx(session, **params, definitions=common.make_defs(network, None))
# TODO check that UNKN token is used, FAKE network
def test_external_chain_token_ok(session: Session) -> None:
# when providing an external chain and matching token, everything works
network = common.encode_network(chain_id=66666, slip44=60)
token = common.encode_token(address=ERC20_FAKE_ADDRESS, chain_id=66666, decimals=8)
params = DEFAULT_ERC20_PARAMS.copy()
params.update(to=ERC20_FAKE_ADDRESS, chain_id=66666)
ethereum.sign_tx(session, **params, definitions=common.make_defs(network, token))
# TODO check that FakeTok is used, FAKE network
def test_external_chain_token_mismatch(session: Session) -> None:
with session, session.client as client:
if not client.debug.legacy_debug:
client.set_input_flow(InputFlowConfirmAllWarnings(client).get())
# when providing external defs, we explicitly allow, but not use, tokens
# from other chains
network = common.encode_network(chain_id=66666, slip44=60)
token = common.encode_token(
address=ERC20_FAKE_ADDRESS, chain_id=55555, decimals=8
)
params = DEFAULT_ERC20_PARAMS.copy()
params.update(to=ERC20_FAKE_ADDRESS, chain_id=66666)
ethereum.sign_tx(
session, **params, definitions=common.make_defs(network, token)
)
# TODO check that UNKN is used for token, FAKE for network
def _call_getaddress(session: Session, slip44: int, network: bytes | None) -> None:
ethereum.get_address(
session,
parse_path(f"m/44h/{slip44}h/0h"),
show_display=False,
encoded_network=network,
)
def _call_signmessage(session: Session, slip44: int, network: bytes | None) -> None:
ethereum.sign_message(
session,
parse_path(f"m/44h/{slip44}h/0h"),
b"hello",
encoded_network=network,
)
def _call_sign_typed_data(session: Session, slip44: int, network: bytes | None) -> None:
ethereum.sign_typed_data(
session,
parse_path(f"m/44h/{slip44}h/0h/0/0"),
TYPED_DATA,
metamask_v4_compat=True,
definitions=common.make_defs(network, None),
)
def _call_sign_typed_data_hash(
session: Session, slip44: int, network: bytes | None
) -> None:
ethereum.sign_typed_data_hash(
session,
parse_path(f"m/44h/{slip44}h/0h/0/0"),
b"\x00" * 32,
b"\xff" * 32,
encoded_network=network,
)
MethodType = Callable[[Session, int, "bytes | None"], None]
METHODS = (
_call_getaddress,
_call_signmessage,
pytest.param(_call_sign_typed_data, marks=pytest.mark.models("core")),
pytest.param(_call_sign_typed_data_hash, marks=pytest.mark.models("legacy")),
)
@pytest.mark.parametrize("method", METHODS)
def test_method_builtin(session: Session, method: MethodType) -> None:
# calling a method with a builtin slip44 will work
method(session, 60, None)
@pytest.mark.parametrize("method", METHODS)
def test_method_def_missing(session: Session, method: MethodType) -> None:
# calling a method with a slip44 that has no definition will fail
with pytest.raises(TrezorFailure, match="Forbidden key path"):
method(session, 66666, None)
@pytest.mark.parametrize("method", METHODS)
def test_method_external(session: Session, method: MethodType) -> None:
# calling a method with a slip44 that has an external definition will work
network = common.encode_network(slip44=66666)
method(session, 66666, network)
@pytest.mark.parametrize("method", METHODS)
def test_method_external_mismatch(session: Session, method: MethodType) -> None:
# calling a method with a slip44 that has an external definition that does not match
# the slip44 will fail
network = common.encode_network(slip44=77777)
with pytest.raises(TrezorFailure, match="Network definition mismatch"):
method(session, 66666, network)