diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..8606948b2 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Ruff +b52f9071f50b11058dd8bd689281c52cbfb6c5f2 diff --git a/.github/workflows/build_and_functional_tests.yml b/.github/workflows/build_and_functional_tests.yml index 1293a7b8a..b6d165f1f 100644 --- a/.github/workflows/build_and_functional_tests.yml +++ b/.github/workflows/build_and_functional_tests.yml @@ -41,4 +41,3 @@ jobs: with: download_app_binaries_artifact: "app_tezos_binaries" regenerate_snapshots: ${{ github.event_name == 'workflow_dispatch' && inputs.golden_run == 'Open a PR' }} - test_dir: "tests/standalone" diff --git a/.github/workflows/coding_style_checks.yml b/.github/workflows/coding_style_checks.yml index c6c6f198e..638841e0e 100644 --- a/.github/workflows/coding_style_checks.yml +++ b/.github/workflows/coding_style_checks.yml @@ -21,4 +21,3 @@ jobs: uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_lint.yml@v1 with: source: './app/src' - extensions: 'h,c' diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 863ce0419..f21fd1dde 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -6,10 +6,8 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v7 + - uses: actions/setup-python@v6 with: - python-version: '3.10' - - run: python -m pip install pre-commit - - run: python -m pip freeze --local - - run: SKIP=clang-format,pylint pre-commit run --all-files + python-version: '3.12' + - uses: pre-commit/action@v3.0.1 diff --git a/.github/workflows/python_client_checks.yml b/.github/workflows/python_client_checks.yml index 1c5e236e0..967e69921 100644 --- a/.github/workflows/python_client_checks.yml +++ b/.github/workflows/python_client_checks.yml @@ -18,7 +18,6 @@ jobs: name: Call Ledger Python linters uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_python_checks.yml@v1 with: - run_linter: pylint run_type_check: true src_directory: . setup_directory: tests/standalone diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 1c948930b..25787a869 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -13,4 +13,6 @@ jobs: job_unit_test: name: Call Ledger unit_test uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_unit_tests.yml@v1 - secrets: inherit + secrets: + token: ${{ secrets.GITHUB_TOKEN }} + codecov_token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4f0e4b72f..fd5fc4bf3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,24 +7,40 @@ repos: rev: v6.0.0 hooks: - id: trailing-whitespace + args: ['--markdown-linebreak-ext=md'] - id: end-of-file-fixer + - id: mixed-line-ending - id: check-yaml - id: check-added-large-files - - repo: local + - id: check-merge-conflict + - id: check-case-conflict + + - repo: https://github.com/codespell-project/codespell + rev: v2.4.2 + hooks: + - id: codespell + args: ['--ignore-words-list', 'cips', '3sT'] + + - repo: https://github.com/pre-commit/mirrors-clang-format + rev: v21.1.8 hooks: - id: clang-format - name: clang-format - description: Run `clang-format` on C/C++/CUDA files. - entry: clang-format -i - language: system - files: ^ - types: [file, c] + types_or: [c] - - id: pylint - name: pylint - description: Run pylint with custom .pylintrc configuration - entry: pylint - language: system - pass_filenames: false - args: ["--errors-only", "--rcfile=tests/integration/.pylintrc", "tests/standalone/"] - types: [python] + - repo: https://github.com/Mateusz-Grzelinski/actionlint-py + rev: v1.7.12.24 + hooks: + - id: actionlint + types_or: [yaml] + args: [-shellcheck="" -pyflakes=""] + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.15.20 + hooks: + # Run the linter. + - id: ruff-check + types_or: [ python, pyi ] + args: [ --fix ] + # Run the formatter. + - id: ruff-format + types_or: [ python, pyi ] diff --git a/app/src/format.c b/app/src/app_format.c similarity index 97% rename from app/src/format.c rename to app/src/app_format.c index 69bab1090..64878fcb9 100644 --- a/app/src/format.c +++ b/app/src/app_format.c @@ -18,9 +18,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -#include - #include "format.h" +#include "app_format.h" bool tz_mutez_to_string(char *obuf, size_t olen, uint64_t amount) diff --git a/app/src/format.h b/app/src/app_format.h similarity index 100% rename from app/src/format.h rename to app/src/app_format.h diff --git a/app/src/handler/sign.c b/app/src/handler/sign.c index 7b9aa228c..3ffc25c08 100644 --- a/app/src/handler/sign.c +++ b/app/src/handler/sign.c @@ -34,7 +34,7 @@ #include #endif -#include "../format.h" +#include "app_format.h" #include "globals.h" #include "handle_swap.h" #include "keys.h" diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 000000000..976ba0294 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,2 @@ +[mypy] +ignore_missing_imports = True diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 000000000..92486d2c7 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths = tests/functional +pythonpath = tests/functional diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 000000000..e77b4cd1a --- /dev/null +++ b/ruff.toml @@ -0,0 +1,20 @@ +target-version = "py311" +line-length = 130 + +# Vendored third-party packages: not our code to lint. +exclude = [ + "deps/" +] + +[lint] +select = [ + "E", # pycodestyle errors + "F", # pyflakes + "I", # isort + "B", # bugbear + "UP", # pyupgrade + "RUF", # ruff-native rules +] + +ignore = [ +] diff --git a/scripts/gen_fa2_ctest_hex.py b/scripts/gen_fa2_ctest_hex.py index 306f8fdff..c4e46d593 100644 --- a/scripts/gen_fa2_ctest_hex.py +++ b/scripts/gen_fa2_ctest_hex.py @@ -301,9 +301,7 @@ def main() -> None: print(f' "{forge_batch_hex(_FA2_NEG)}"') print() print("/* FA2 clear-signing (token_id=1, wrapped token registry): */") - print( - f' "{forge_batch_hex(_FA2_TRANSFER_TOKEN_ID_GT0_PARAMETER, "KT18fp5rcTW7mbWDmzFwjLDUhs5MeJmagDSZ")}"' - ) + print(f' "{forge_batch_hex(_FA2_TRANSFER_TOKEN_ID_GT0_PARAMETER, "KT18fp5rcTW7mbWDmzFwjLDUhs5MeJmagDSZ")}"') if __name__ == "__main__": diff --git a/tests/integration/check_section_text.py b/tests/integration/check_section_text.py index 885bb666c..9dc2c93da 100755 --- a/tests/integration/check_section_text.py +++ b/tests/integration/check_section_text.py @@ -15,129 +15,141 @@ import argparse import os -import requests -from dataclasses import dataclass import time +from dataclasses import dataclass + +import requests + +TIMEOUT = int(os.environ.get("TIMEOUT", "5")) -TIMEOUT = int(os.environ.get('TIMEOUT', '5')) @dataclass class Screen: - title: str - text: list[str] - - def __str__(self): - return f"title=\"{self.title}\" Text=[{', '.join(self.text)}]" - - def matches(self, content: str, content_lines: int) -> bool: - for l in self.text: - content = content.lstrip('\n') - print(f"l: {l}, c: {content[:len(l)]}") - if not content.startswith(l): - return False - content = content.removeprefix(l) - content = content.lstrip('\n') - - return True - - def strip(self, content: str) -> str: - for l in self.text: - content = content.lstrip('\n') - content = content.removeprefix(l) - content = content.lstrip('\n') - return content - -def with_retry(f, attempts= (2 * TIMEOUT)): + title: str + text: list[str] + + def __str__(self): + return f'title="{self.title}" Text=[{", ".join(self.text)}]' + + def matches(self, content: str, content_lines: int) -> bool: + for length in self.text: + content = content.lstrip("\n") + print(f"l: {length}, c: {content[: len(length)]}") + if not content.startswith(length): + return False + content = content.removeprefix(length) + content = content.lstrip("\n") + + return True + + def strip(self, content: str) -> str: + for length in self.text: + content = content.lstrip("\n") + content = content.removeprefix(length) + content = content.lstrip("\n") + return content + + +def with_retry(f, attempts=(2 * TIMEOUT)): while True: try: return f() except AssertionError as e: - print('- retrying:', e) + print("- retrying:", e) if attempts <= 0: raise e attempts -= 1 time.sleep(0.5) + def get_screen(url): """Retrieve the current screen title & contents""" - r = requests.get(f'{url}/events', params = {"currentscreenonly":"true"}) + r = requests.get(f"{url}/events", params={"currentscreenonly": "true"}) r.raise_for_status() lines = [e["text"] for e in r.json()["events"]] assert len(lines) > 0, "Unexpected empty screen. Speculos killed?" screen = Screen(title=lines[0], text=lines[1:]) - print(f'- {screen} -') + print(f"- {screen} -") return screen + def find_last(lst, elm): - """Find the index of the last element in lst that matches elm""" - gen = (len(lst) - 1 - i for i, v in enumerate(reversed(lst)) if v == elm) - return next(gen, None) + """Find the index of the last element in lst that matches elm""" + gen = (len(lst) - 1 - i for i, v in enumerate(reversed(lst)) if v == elm) + return next(gen, None) + def get_titled_screen(url, title): """Retrieve the screen until title""" - r = requests.get(f'{url}/events') + r = requests.get(f"{url}/events") r.raise_for_status() lines = [e["text"] for e in r.json()["events"]] assert len(lines) > 0, "Unexpected empty screen. Speculos killed?" - assert title in lines, f"Title \"{title}\" no found" + assert title in lines, f'Title "{title}" no found' title_index = find_last(lines, title) - screen = Screen(title=lines[title_index], text=lines[title_index+1:]) - print(f'- {screen} -') + screen = Screen(title=lines[title_index], text=lines[title_index + 1 :]) + print(f"- {screen} -") return screen + def press_button(url, button): """Press a button on the ledger device.""" print("- press", button) - r = requests.post(f'{url}/button/{button}', json = {"action": "press-and-release"}) + r = requests.post(f"{url}/button/{button}", json={"action": "press-and-release"}) r.raise_for_status() + def press_right(url): """Press the right button.""" - return press_button(url, 'right') + return press_button(url, "right") + def press_left(url): """Press the left button.""" - return press_button(url, 'left') + return press_button(url, "left") + def check_multi_screen(url, title, content, content_lines, device): """Assert that the screen contents across all screens with the given title match expected content.""" while True: - def check_screen(): - if device in ["nanos", "nanosp"]: - screen = get_titled_screen(url, title) - assert screen.title == title, f"expected section '{title}' but on '{screen.title}'" - # https://github.com/trilitech/ledger-app-tezos-wallet/issues/43 - # Get screens contents with the 'events' service for nanox - # while the 'events?currentscreenonly=true' service does not - # work properly for nanox in the sha-6a34680 version of - # speculos - if device == "nanox": - screen = get_titled_screen(url, title) - assert screen.matches(content, content_lines), \ - f"{screen} did not match {content[:10]}...{content[-10:]}" - return screen.strip(content) + def check_screen(content=content): + if device in ["nanos", "nanosp"]: + screen = get_titled_screen(url, title) + assert screen.title == title, f"expected section '{title}' but on '{screen.title}'" + # https://github.com/trilitech/ledger-app-tezos-wallet/issues/43 + # Get screens contents with the 'events' service for nanox + # while the 'events?currentscreenonly=true' service does not + # work properly for nanox in the sha-6a34680 version of + # speculos + if device == "nanox": + screen = get_titled_screen(url, title) + assert screen.matches(content, content_lines), f"{screen} did not match {content[:10]}...{content[-10:]}" + + return screen.strip(content) - content = with_retry(check_screen) + content = with_retry(check_screen) - if content == "": - break + if content == "": + break + + press_right(url) - press_right(url) def device_content_lines(device: str) -> int: - if device == "nanos": - return 1 - if device in ["nanosp", "nanox"]: - return 4 + if device == "nanos": + return 1 + if device in ["nanosp", "nanox"]: + return 4 + + raise ValueError(f"unsupported device '{device}'") - raise ValueError(f"unsupported device '{device}'") if __name__ == "__main__": parser = argparse.ArgumentParser(description="Check a section of pages contain the expected content.") diff --git a/tests/integration/check_tlv_signature.py b/tests/integration/check_tlv_signature.py index c23f25485..e7d9942bf 100755 --- a/tests/integration/check_tlv_signature.py +++ b/tests/integration/check_tlv_signature.py @@ -14,11 +14,14 @@ # limitations under the License. import sys + import base58 from pytezos import pytezos + def adjust_size(bytes, size): - return bytes[-size:].rjust(size, b'\x00') + return bytes[-size:].rjust(size, b"\x00") + def signature_of_tlv(tlv): # See: https://developers.ledger.com/docs/embedded-app/crypto-api/lcx__ecdsa_8h/#cx_ecdsa_sign @@ -46,27 +49,31 @@ def signature_of_tlv(tlv): # Sometimes \x00 are added or removed, a size adjustment is required here. return adjust_size(r, 32) + adjust_size(s, 32) + def signature_of_hex_tlv(hex_string): tlv = bytearray.fromhex(hex_string) # Remove the unwanted parity information set here. tlv[0] &= ~0x01 return signature_of_tlv(tlv) + def check_signature(hex_tlv, pk, message): sig = signature_of_hex_tlv(hex_tlv) - sig_prefix = bytes.fromhex("04822b") # sig(96) + sig_prefix = bytes.fromhex("04822b") # sig(96) signature = base58.b58encode_check(sig_prefix + sig) ctxt = pytezos.using(key=pk) assert ctxt.key.verify(signature, message) + def check_between(prefix, suffix, v): if v.startswith(prefix) and v.endswith(suffix): return v[len(prefix) : -len(suffix)] else: raise ValueError("Invalid prefix/suffix") + if __name__ == "__main__": if len(sys.argv) != 6: print("Usage: check_tlv_signature.py ") diff --git a/tests/standalone/conftest.py b/tests/standalone/conftest.py index 6b4007836..7a8f3abeb 100644 --- a/tests/standalone/conftest.py +++ b/tests/standalone/conftest.py @@ -16,17 +16,16 @@ """Conftest based on ragger's base conftest plus Tezos fixtures.""" import re +from collections.abc import Generator from pathlib import Path -from typing import Dict, Generator, List, Optional import pytest from ledgered.devices import Device from ragger.conftest import configuration from ragger.conftest.base_conftest import prepare_speculos_args from ragger.error import MissingElfError -from ragger.navigator import Navigator, NanoNavigator, TouchNavigator - -from utils.account import Account, DEFAULT_ACCOUNT, DEFAULT_SEED +from ragger.navigator import NanoNavigator, Navigator, TouchNavigator +from utils.account import DEFAULT_ACCOUNT, DEFAULT_SEED, Account from utils.backend import SpeculosTezosBackend, TezosBackend from utils.navigator import TezosNavigator @@ -43,25 +42,29 @@ def _sanitize_node_name(name: str) -> str: name = re.sub(r"_+", "_", name).strip("_") return name + def pytest_addoption(parser): """Register local pytest options on top of ragger defaults.""" parser.addoption("--log-dir", type=Path, help="Log directory") + @pytest.fixture(scope="function") def seed(request) -> str: """Get `seed` for pytest.""" param = getattr(request, "param", None) return param.get("seed", DEFAULT_SEED) if param else DEFAULT_SEED + @pytest.fixture(scope="function") def account(request) -> Account: """Get `account` for pytest.""" param = getattr(request, "param", None) return param.get("account", DEFAULT_ACCOUNT) if param else DEFAULT_ACCOUNT -def _override_seed(args: List[str], seed: str) -> List[str]: # pylint: disable=redefined-outer-name + +def _override_seed(args: list[str], seed: str) -> list[str]: """Replace any existing --seed argument with the one provided.""" - result: List[str] = [] + result: list[str] = [] i = 0 while i < len(args): if args[i] == "--seed": @@ -76,18 +79,18 @@ def _override_seed(args: List[str], seed: str) -> List[str]: # pylint: disable= @pytest.fixture(scope="function") -def backend( # pylint: disable=too-many-arguments,too-many-positional-arguments,too-many-locals +def backend( skip_tests_for_unsupported_devices, root_pytest_dir: Path, device: Device, display: bool, pki_prod: bool, - log_apdu_file: Optional[Path], - cli_user_seed: Optional[str], - additional_speculos_arguments: List[str], + log_apdu_file: Path | None, + cli_user_seed: str | None, + additional_speculos_arguments: list[str], verbose_speculos: bool, ignore_missing_binaries: bool, - seed: str, # pylint: disable=redefined-outer-name + seed: str, ) -> Generator[TezosBackend, None, None]: """Provide Tezos-specific backend while reusing ragger discovery logic.""" _ = skip_tests_for_unsupported_devices @@ -110,7 +113,7 @@ def backend( # pylint: disable=too-many-arguments,too-many-positional-arguments speculos_kwargs["args"] = args speculos_kwargs["log_apdu_file"] = log_apdu_file - backend_instance = SpeculosTezosBackend( # pylint: disable=abstract-class-instantiated,unexpected-keyword-arg + backend_instance = SpeculosTezosBackend( app_path, device, **speculos_kwargs, @@ -118,11 +121,12 @@ def backend( # pylint: disable=too-many-arguments,too-many-positional-arguments with backend_instance as instance: yield instance + @pytest.fixture(scope="function") def tezos_navigator( - backend: TezosBackend, # pylint: disable=redefined-outer-name - device: Device, - golden_run: bool + backend: TezosBackend, + device: Device, + golden_run: bool, ) -> TezosNavigator: """Get `navigator` for pytest.""" if device.is_nano: @@ -131,6 +135,7 @@ def tezos_navigator( navigator = TouchNavigator(backend, device, golden_run) return TezosNavigator(backend, device, navigator) + @pytest.fixture(scope="function") def snapshot_dir(request) -> Path: """Get the test snapshot location.""" @@ -138,32 +143,34 @@ def snapshot_dir(request) -> Path: file_name = test_file_path.stem test_name = _sanitize_node_name(request.node.name) # Get test directory from the root - test_file_snapshot_dir = Path(*test_file_path.parts[len(Path(__file__).parts)-1:-1]) + test_file_snapshot_dir = Path(*test_file_path.parts[len(Path(__file__).parts) - 1 : -1]) return test_file_snapshot_dir / file_name / test_name + def requires_device(device): """Wrapper to run the pytest test only with the provided device.""" return pytest.mark.skipif( - f"config.getvalue('device') != '{ device }'", - reason=f"Test requires device to be { device }." + f"config.getvalue('device') != '{device}'", + reason=f"Test requires device to be {device}.", ) + @pytest.fixture(autouse=True) def use_only_on_device(request, device: Device): """Fixture to add tests on specific devices.""" - def get_devices(dev: str) -> List[str]: + def get_devices(dev: str) -> list[str]: if dev == "nano": return ["nanos", "nanosp", "nanox"] if dev == "touch": return ["stax", "flex", "apex_p"] return [dev] - marker = request.node.get_closest_marker('use_on_device') + marker = request.node.get_closest_marker("use_on_device") if marker: current_device = device.name.lower() requested_devices = marker.args[0] - devices: List[str] = [] + devices: list[str] = [] if isinstance(requested_devices, str): devices = get_devices(requested_devices) else: @@ -174,10 +181,10 @@ def get_devices(dev: str) -> List[str]: pytest.skip(f'skipped on this device: "{current_device}"') -_log_dir_state: Dict[str, Optional[Path]] = {"value": None} +_log_dir_state: dict[str, Path | None] = {"value": None} -def _get_log_dir() -> Optional[Path]: +def _get_log_dir() -> Path | None: """Return the current global log directory.""" return _log_dir_state["value"] @@ -195,18 +202,22 @@ def pytest_configure(config): if log_dir is not None: _log_dir_state["value"] = Path(log_dir) -logs : Dict[str, List[pytest.TestReport]] = {} + +logs: dict[str, list[pytest.TestReport]] = {} + @pytest.hookimpl(tryfirst=True) def pytest_runtest_logstart(location): """Called at the start of running the runtest protocol for a single item.""" logs[location[2]] = [] + @pytest.hookimpl(tryfirst=True) def pytest_runtest_logreport(report): """Called at the end of running the runtest protocol for a single test.""" logs[report.head_line].append(report) + @pytest.hookimpl(tryfirst=True) def pytest_runtest_logfinish(nodeid, location): """Called at the end of running the runtest protocol for a single item.""" @@ -216,12 +227,12 @@ def pytest_runtest_logfinish(nodeid, location): # Remove `tests/standalone/` test_root = "standalone" if test_root in log_dir.parts: - log_dir = Path(*log_dir.parts[log_dir.parts.index(test_root) + 1:]) + log_dir = Path(*log_dir.parts[log_dir.parts.index(test_root) + 1 :]) log_dir = global_log_dir / log_dir log_dir.mkdir(parents=True, exist_ok=True) head_line = location[2] log_file = log_dir / f"{head_line}.log" - with open(log_file, 'w', encoding="utf-8") as writer: + with open(log_file, "w", encoding="utf-8") as writer: for report in logs[head_line]: when = report.when.capitalize() outcome = report.outcome diff --git a/tests/standalone/setup.cfg b/tests/standalone/setup.cfg index 34490ebd9..f1b54e366 100644 --- a/tests/standalone/setup.cfg +++ b/tests/standalone/setup.cfg @@ -1,8 +1,2 @@ [tool:pytest] addopts = --strict-markers - -[pylint] -disable = duplicate-code - -[mypy] -ignore_missing_imports = True diff --git a/tests/standalone/test_menu.py b/tests/standalone/test_menu.py index ab0d6e272..250a4c6d3 100755 --- a/tests/standalone/test_menu.py +++ b/tests/standalone/test_menu.py @@ -18,20 +18,17 @@ # pylint: disable=protected-access from pathlib import Path -from typing import List, Union import requests - from ledgered.devices import DeviceType from ragger.navigator import BaseNavInsID, NavIns, NavInsID - from utils.backend import TezosBackend from utils.navigator import TezosNavigator, TezosNavInsID def test_home_menu(tezos_navigator: TezosNavigator, snapshot_dir: Path): """Check home menu flow""" - instructions: List[Union[NavIns, BaseNavInsID]] = [] + instructions: list[NavIns | BaseNavInsID] = [] if tezos_navigator._device.is_nano: instructions = [ # Home @@ -50,7 +47,7 @@ def test_home_menu(tezos_navigator: TezosNavigator, snapshot_dir: Path): def test_settings_menu(tezos_navigator: TezosNavigator, snapshot_dir: Path): """Check settings menu flow""" tezos_navigator.navigate_to_settings() - instructions: List[Union[NavIns, BaseNavInsID]] = [] + instructions: list[NavIns | BaseNavInsID] = [] if tezos_navigator._device.is_nano: instructions = [ # Expert Mode @@ -69,10 +66,7 @@ def test_settings_menu(tezos_navigator: TezosNavigator, snapshot_dir: Path): NavInsID.USE_CASE_SETTINGS_NEXT, TezosNavInsID.SETTINGS_EXIT, ] - tezos_navigator.navigate( - instructions=instructions, - snap_path=snapshot_dir - ) + tezos_navigator.navigate(instructions=instructions, snap_path=snapshot_dir) def test_toggle_expert_mode(tezos_navigator: TezosNavigator, snapshot_dir: Path): @@ -88,6 +82,7 @@ def test_toggle_blindsign(tezos_navigator: TezosNavigator, snapshot_dir: Path): # Toggle back tezos_navigator.toggle_blindsign(snap_start_idx=snap_idx, snap_path=snapshot_dir) + def test_quit(tezos_navigator: TezosNavigator, backend: TezosBackend): """Check quit app""" if backend._device.is_nano: @@ -96,7 +91,7 @@ def test_quit(tezos_navigator: TezosNavigator, backend: TezosBackend): backend.wait_for_screen_change() # Quit try: backend.both_click() - assert False, "Must have lost connection with speculos" + raise AssertionError("Must have lost connection with speculos") except requests.exceptions.ConnectionError: pass except requests.exceptions.ChunkedEncodingError: diff --git a/tests/standalone/test_public_key.py b/tests/standalone/test_public_key.py index 86408e2bf..4a43d8dc4 100644 --- a/tests/standalone/test_public_key.py +++ b/tests/standalone/test_public_key.py @@ -19,26 +19,34 @@ from pathlib import Path import pytest - from utils.account import Account, PublicKey, SigType -from utils.backend import TezosBackend, StatusCode +from utils.backend import StatusCode, TezosBackend from utils.navigator import TezosNavigator accounts = [ - Account("m/44'/1729'/0'/0'", - SigType.ED25519, - "edpkuXX2VdkdXzkN11oLCb8Aurdo1BTAtQiK8ZY9UPj2YMt3AHEpcY"), - Account("m/44'/1729'/0'/0'", - SigType.SECP256K1, - "sppk7bVy617DmGvXsMqcwsiLtnedTN2trUi5ugXcNig7en4rHJyunK1"), - Account("m/44'/1729'/0'/0'", - SigType.SECP256R1, - "p2pk67fq5pzuMMABZ9RDrooYbLrgmnQbLt8z7PTGM9mskf7LXS5tdBG"), - Account("m/44'/1729'/0'/0'", - SigType.BIP32_ED25519, - "edpkumJgSsSxkpiB5hmTq6eZcrmc6BsJtLAhYceFTiziFqje4mongz") + Account( + "m/44'/1729'/0'/0'", + SigType.ED25519, + "edpkuXX2VdkdXzkN11oLCb8Aurdo1BTAtQiK8ZY9UPj2YMt3AHEpcY", + ), + Account( + "m/44'/1729'/0'/0'", + SigType.SECP256K1, + "sppk7bVy617DmGvXsMqcwsiLtnedTN2trUi5ugXcNig7en4rHJyunK1", + ), + Account( + "m/44'/1729'/0'/0'", + SigType.SECP256R1, + "p2pk67fq5pzuMMABZ9RDrooYbLrgmnQbLt8z7PTGM9mskf7LXS5tdBG", + ), + Account( + "m/44'/1729'/0'/0'", + SigType.BIP32_ED25519, + "edpkumJgSsSxkpiB5hmTq6eZcrmc6BsJtLAhYceFTiziFqje4mongz", + ), ] + @pytest.mark.parametrize("account", accounts, ids=lambda account: f"{account.sig_type}") def test_get_pk(backend: TezosBackend, account: Account): """Test that public keys get from the app are correct.""" @@ -49,16 +57,15 @@ def test_get_pk(backend: TezosBackend, account: Account): public_key = PublicKey.from_bytes(data, account.sig_type) - assert public_key == expected_public_key.encode(), \ - f"Expected public key {expected_public_key} but got {public_key}" + assert public_key == expected_public_key.encode(), f"Expected public key {expected_public_key} but got {public_key}" @pytest.mark.parametrize("account", accounts, ids=lambda account: f"{account.sig_type}") def test_provide_pk( - backend: TezosBackend, - tezos_navigator: TezosNavigator, - account: Account, - snapshot_dir: Path + backend: TezosBackend, + tezos_navigator: TezosNavigator, + account: Account, + snapshot_dir: Path, ): """Test that public keys get from the app are correct and correctly displayed.""" @@ -69,16 +76,15 @@ def test_provide_pk( public_key = PublicKey.from_bytes(result.value, account.sig_type) - assert public_key == expected_public_key.encode(), \ - f"Expected public key {expected_public_key} but got {public_key}" + assert public_key == expected_public_key.encode(), f"Expected public key {expected_public_key} but got {public_key}" @pytest.mark.use_on_device("touch") def test_show_qr( - backend: TezosBackend, - tezos_navigator: TezosNavigator, - account: Account, - snapshot_dir: Path + backend: TezosBackend, + tezos_navigator: TezosNavigator, + account: Account, + snapshot_dir: Path, ): """Test QR code flow.""" @@ -87,10 +93,10 @@ def test_show_qr( def test_reject_pk( - backend: TezosBackend, - tezos_navigator: TezosNavigator, - account: Account, - snapshot_dir: Path + backend: TezosBackend, + tezos_navigator: TezosNavigator, + account: Account, + snapshot_dir: Path, ): """Check reject pk behaviour""" diff --git a/tests/standalone/test_sign/operations/conftest.py b/tests/standalone/test_sign/operations/conftest.py new file mode 100644 index 000000000..469a29855 --- /dev/null +++ b/tests/standalone/test_sign/operations/conftest.py @@ -0,0 +1,18 @@ +# Copyright 2024 Functori +# Copyright 2024 Trilitech + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Conftest for operation tests: exposes the pytest_generate_tests hook.""" + +from .helper import pytest_generate_tests as pytest_generate_tests diff --git a/tests/standalone/test_sign/operations/helper.py b/tests/standalone/test_sign/operations/helper.py index 5ddd4c4f8..1c4dcb571 100644 --- a/tests/standalone/test_sign/operations/helper.py +++ b/tests/standalone/test_sign/operations/helper.py @@ -18,23 +18,20 @@ from abc import ABC, abstractmethod from pathlib import Path -from typing import Any, Dict, List, Optional, Type, TypeVar, Union +from typing import Any, TypeVar import pytest - from ledgered.devices import Device -from ragger.navigator import NavIns, NavInsID, BaseNavInsID - +from ragger.navigator import BaseNavInsID, NavIns, NavInsID from utils.account import Account from utils.backend import TezosBackend from utils.message import Operation from utils.navigator import TezosNavigator, TezosNavInsID - -Op = TypeVar('Op', bound=Operation) +Op = TypeVar("Op", bound=Operation) -class Flow: # pylint: disable=too-few-public-methods +class Flow: """Data required to run `test_operation_flow`. name: str @@ -46,7 +43,7 @@ class Flow: # pylint: disable=too-few-public-methods """ name: str - fields: Dict[str, Any] + fields: dict[str, Any] def __init__(self, name, **kwargs): self.name = name @@ -59,19 +56,15 @@ def parametrize_test_operation_flow(metafunc) -> None: args_values = [] args_ids = [] - if hasattr(metafunc.cls, 'flows'): - flows: List[Flow] = metafunc.cls.flows + if hasattr(metafunc.cls, "flows"): + flows: list[Flow] = metafunc.cls.flows args_values = [[flow.fields] for flow in flows] args_ids = [f"flow-{flow.name}" for flow in flows] - metafunc.parametrize( - args_names, - args_values, - ids=args_ids - ) + metafunc.parametrize(args_names, args_values, ids=args_ids) -class Field: # pylint: disable=too-few-public-methods +class Field: """Data required to run `test_operation_field`. name: str @@ -85,7 +78,7 @@ class Field: # pylint: disable=too-few-public-methods """ - class Case: # pylint: disable=too-few-public-methods + class Case: """Data representing a case to test for a given field. value: Any @@ -101,7 +94,7 @@ class Case: # pylint: disable=too-few-public-methods value: Any name: str - fields: Dict[str, Any] + fields: dict[str, Any] def __init__(self, value, name, **kwargs): self.value = value @@ -110,7 +103,7 @@ def __init__(self, value, name, **kwargs): name: str text: str - cases: List[Case] + cases: list[Case] def __init__(self, name, text, cases): self.name = name @@ -124,25 +117,13 @@ def parametrize_test_operation_field(metafunc) -> None: args_values = [] args_ids = [] - if hasattr(metafunc.cls, 'fields'): - fields_args: List[Field] = metafunc.cls.fields - args_values = [ - (field, case_) - for field in fields_args - for case_ in field.cases - ] - args_ids = [ - f"{field.name}-{case_.name}" - for field in fields_args - for case_ in field.cases - ] + if hasattr(metafunc.cls, "fields"): + fields_args: list[Field] = metafunc.cls.fields + args_values = [(field, case_) for field in fields_args for case_ in field.cases] + args_ids = [f"{field.name}-{case_.name}" for field in fields_args for case_ in field.cases] if args_values: - metafunc.parametrize( - args_names, - args_values, - ids=args_ids - ) + metafunc.parametrize(args_names, args_values, ids=args_ids) def pytest_generate_tests(metafunc) -> None: @@ -158,11 +139,11 @@ class TestOperation(ABC): @property @abstractmethod - def op_class(self) -> Type[Op]: + def op_class(self) -> type[Op]: """Constructor of the Operation class.""" raise NotImplementedError - def skip_signature_check(self) -> Optional[str]: + def skip_signature_check(self) -> str | None: """Reason why skipping the `test_sign_operation` test.""" return None @@ -174,17 +155,12 @@ def field_test_nav_anchor(self) -> str: """ return "Source" - def test_sign_operation( - self, - backend: TezosBackend, - tezos_navigator: TezosNavigator, - account: Account - ): + def test_sign_operation(self, backend: TezosBackend, tezos_navigator: TezosNavigator, account: Account): """Check signing: - - Hash - - Signature + - Hash + - Signature """ - reason: Optional[str] = self.skip_signature_check() + reason: str | None = self.skip_signature_check() if reason is not None: pytest.skip(reason) @@ -195,19 +171,15 @@ def test_sign_operation( with backend.sign(account, message, with_hash=True) as result: tezos_navigator.accept_sign() - account.check_signature( - message=message, - with_hash=True, - data=result.value - ) - - def test_operation_flow( # pylint: disable=too-many-arguments,too-many-positional-arguments - self, - backend: TezosBackend, - tezos_navigator: TezosNavigator, - account: Account, - fields: Dict[str, Any], - snapshot_dir: Path + account.check_signature(message=message, with_hash=True, data=result.value) + + def test_operation_flow( + self, + backend: TezosBackend, + tezos_navigator: TezosNavigator, + account: Account, + fields: dict[str, Any], + snapshot_dir: Path, ): """Check signing flow @@ -221,15 +193,15 @@ def test_operation_flow( # pylint: disable=too-many-arguments,too-many-position with backend.sign(account, message): tezos_navigator.accept_sign(snap_path=snapshot_dir) - def il( # pylint: disable=too-many-arguments,too-many-positional-arguments - self, - backend: TezosBackend, - device: Device, - tezos_navigator: TezosNavigator, - account: Account, - field: Field, - case_: Field.Case, - snapshot_dir: Path + def il( + self, + backend: TezosBackend, + device: Device, + tezos_navigator: TezosNavigator, + account: Account, + field: Field, + case_: Field.Case, + snapshot_dir: Path, ): """Check how fields are displayed @@ -245,7 +217,7 @@ def il( # pylint: disable=too-many-arguments,too-many-positional-arguments tezos_navigator.toggle_expert_mode() with backend.sign(account, operation): - validation_instructions: List[Union[NavIns, BaseNavInsID]] = [] + validation_instructions: list[NavIns | BaseNavInsID] = [] if device.is_nano: validation_instructions = [NavInsID.RIGHT_CLICK] # Navigates until a stable first row (see `field_test_nav_anchor`). @@ -260,7 +232,7 @@ def il( # pylint: disable=too-many-arguments,too-many-positional-arguments text=field.text, # Even if the screen has changed, we know we are on # the right screen because the text has been found - screen_change_after_last_instruction=False + screen_change_after_last_instruction=False, ) # Compare all field's screens @@ -271,6 +243,4 @@ def il( # pylint: disable=too-many-arguments,too-many-positional-arguments ) # Finish the signing - tezos_navigator.accept_sign( - screen_change_before_first_instruction=False - ) + tezos_navigator.accept_sign(screen_change_before_first_instruction=False) diff --git a/tests/standalone/test_sign/operations/test_sign_ballot.py b/tests/standalone/test_sign/operations/test_sign_ballot.py index b798845db..e129652c6 100755 --- a/tests/standalone/test_sign/operations/test_sign_ballot.py +++ b/tests/standalone/test_sign/operations/test_sign_ballot.py @@ -16,8 +16,11 @@ """Gathering of tests related to Ballot operations.""" +from typing import ClassVar + from utils.message import Ballot -from .helper import Flow, Field, TestOperation, pytest_generate_tests # pylint: disable=unused-import + +from .helper import Field, Flow, TestOperation class TestBallot(TestOperation): @@ -27,29 +30,45 @@ class TestBallot(TestOperation): def op_class(self): return Ballot - flows = [Flow('basic')] - - fields = [ - Field("source", "Source", [ - Field.Case('tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa', "tz1"), - Field.Case('tz2CJBeWWLsUDjVUDqGZL6od3DeBCNzYXrXk', "tz2"), - Field.Case('tz3fLwHKthqhTPK6Lar6CTXN1WbDETw1YpGB', "tz3"), - Field.Case('tz4AcerThk5nGtWNBiSqJfZFeWtz6ZqJ6mTY', "tz4"), - Field.Case('tz1Kp8NCAN5WWwvkWkMmQQXMRe68iURmoQ8w', "long-hash"), - ]), - Field("period", "Period", [ - Field.Case(0, "0"), - Field.Case(32, "32"), - Field.Case(0x7FFFFFFF, "max"), # max int32 - Field.Case(0x80000000, "min"), # min int32 - ]), - Field("proposal", "Proposal", [ - Field.Case('PsParisCZo7KAh1Z1smVd9ZMZ1HHn5gkzbM94V3PLCpknFWhUAi', "basic"), - Field.Case('Ptd4kYMModZQ6Mh4ZRNMmWpM799PgSzjmGw3GM9Q2SDqqo8WCW8', "long-hash"), - ]), - Field("ballot", "Ballot", [ - Field.Case('yay', "yay"), - Field.Case('nay', "nay"), - Field.Case('pass', "pass"), - ]), + flows: ClassVar[list[Flow]] = [Flow("basic")] + + fields: ClassVar[list[Field]] = [ + Field( + "source", + "Source", + [ + Field.Case("tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa", "tz1"), + Field.Case("tz2CJBeWWLsUDjVUDqGZL6od3DeBCNzYXrXk", "tz2"), + Field.Case("tz3fLwHKthqhTPK6Lar6CTXN1WbDETw1YpGB", "tz3"), + Field.Case("tz4AcerThk5nGtWNBiSqJfZFeWtz6ZqJ6mTY", "tz4"), + Field.Case("tz1Kp8NCAN5WWwvkWkMmQQXMRe68iURmoQ8w", "long-hash"), + ], + ), + Field( + "period", + "Period", + [ + Field.Case(0, "0"), + Field.Case(32, "32"), + Field.Case(0x7FFFFFFF, "max"), # max int32 + Field.Case(0x80000000, "min"), # min int32 + ], + ), + Field( + "proposal", + "Proposal", + [ + Field.Case("PsParisCZo7KAh1Z1smVd9ZMZ1HHn5gkzbM94V3PLCpknFWhUAi", "basic"), + Field.Case("Ptd4kYMModZQ6Mh4ZRNMmWpM799PgSzjmGw3GM9Q2SDqqo8WCW8", "long-hash"), + ], + ), + Field( + "ballot", + "Ballot", + [ + Field.Case("yay", "yay"), + Field.Case("nay", "nay"), + Field.Case("pass", "pass"), + ], + ), ] diff --git a/tests/standalone/test_sign/operations/test_sign_batched_operations.py b/tests/standalone/test_sign/operations/test_sign_batched_operations.py index 6ab420165..38cf60968 100644 --- a/tests/standalone/test_sign/operations/test_sign_batched_operations.py +++ b/tests/standalone/test_sign/operations/test_sign_batched_operations.py @@ -16,23 +16,26 @@ """Gathering of tests related to Batched operations.""" +from typing import ClassVar + from utils.message import ( + Delegation, + IncreasePaidStorage, OperationGroup, - Reveal, - Transaction, Origination, - Delegation, RegisterGlobalConstant, - SetDepositLimit, - IncreasePaidStorage, - UpdateConsensusKey, - UpdateCompanionKey, - TransferTicket, - ScRollupOriginate, + Reveal, ScRollupAddMessage, ScRollupExecuteOutboxMessage, + ScRollupOriginate, + SetDepositLimit, + Transaction, + TransferTicket, + UpdateCompanionKey, + UpdateConsensusKey, ) -from .helper import Flow, Field, TestOperation, pytest_generate_tests # pylint: disable=unused-import + +from .helper import Field, Flow, TestOperation class TestManagerOperation(TestOperation): @@ -45,25 +48,37 @@ def op_class(self): def skip_signature_check(self): return "no generic ManagerOperation" - fields = [ - Field("source", "Source", [ - Field.Case('tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa', "tz1"), - Field.Case('tz2CJBeWWLsUDjVUDqGZL6od3DeBCNzYXrXk', "tz2"), - Field.Case('tz3fLwHKthqhTPK6Lar6CTXN1WbDETw1YpGB', "tz3"), - Field.Case('tz4AcerThk5nGtWNBiSqJfZFeWtz6ZqJ6mTY', "tz4"), - Field.Case('tz1Kp8NCAN5WWwvkWkMmQQXMRe68iURmoQ8w', "long-hash"), - ]), - Field("fee", "Fee", [ - Field.Case(0, "0"), - Field.Case(1000, "1000"), - Field.Case(1000000, "1000000"), - Field.Case(1000000000, "1000000000"), - Field.Case(0xFFFFFFFFFFFFFFFF, "max"), # max uint64 - ]), - Field("storage_limit", "Storage limit", [ - Field.Case(0, "min"), - Field.Case(0xFFFFFFFFFFFFFFFFFFFF, "max"), - ]), + fields: ClassVar[list[Field]] = [ + Field( + "source", + "Source", + [ + Field.Case("tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa", "tz1"), + Field.Case("tz2CJBeWWLsUDjVUDqGZL6od3DeBCNzYXrXk", "tz2"), + Field.Case("tz3fLwHKthqhTPK6Lar6CTXN1WbDETw1YpGB", "tz3"), + Field.Case("tz4AcerThk5nGtWNBiSqJfZFeWtz6ZqJ6mTY", "tz4"), + Field.Case("tz1Kp8NCAN5WWwvkWkMmQQXMRe68iURmoQ8w", "long-hash"), + ], + ), + Field( + "fee", + "Fee", + [ + Field.Case(0, "0"), + Field.Case(1000, "1000"), + Field.Case(1000000, "1000000"), + Field.Case(1000000000, "1000000000"), + Field.Case(0xFFFFFFFFFFFFFFFF, "max"), # max uint64 + ], + ), + Field( + "storage_limit", + "Storage limit", + [ + Field.Case(0, "min"), + Field.Case(0xFFFFFFFFFFFFFFFFFFFF, "max"), + ], + ), ] @@ -77,29 +92,38 @@ def op_class(self): def skip_signature_check(self): return "no empty OperationGroup" - flows = [ - Flow('many-transactions', operations=[ - Transaction(), - Transaction(), - Transaction(), - ]), - Flow('reveal-transaction', operations=[ - Reveal(), - Transaction(), - ]), - Flow('all', operations=[ - Reveal(), - Transaction(), - Origination(), - Delegation(), - RegisterGlobalConstant(), - SetDepositLimit(), - IncreasePaidStorage(), - UpdateConsensusKey(), - UpdateCompanionKey(), - TransferTicket(), - ScRollupOriginate(), - ScRollupAddMessage(), - ScRollupExecuteOutboxMessage(), - ]), + flows: ClassVar[list[Flow]] = [ + Flow( + "many-transactions", + operations=[ + Transaction(), + Transaction(), + Transaction(), + ], + ), + Flow( + "reveal-transaction", + operations=[ + Reveal(), + Transaction(), + ], + ), + Flow( + "all", + operations=[ + Reveal(), + Transaction(), + Origination(), + Delegation(), + RegisterGlobalConstant(), + SetDepositLimit(), + IncreasePaidStorage(), + UpdateConsensusKey(), + UpdateCompanionKey(), + TransferTicket(), + ScRollupOriginate(), + ScRollupAddMessage(), + ScRollupExecuteOutboxMessage(), + ], + ), ] diff --git a/tests/standalone/test_sign/operations/test_sign_delegation.py b/tests/standalone/test_sign/operations/test_sign_delegation.py index d4f9ab1da..c56577662 100755 --- a/tests/standalone/test_sign/operations/test_sign_delegation.py +++ b/tests/standalone/test_sign/operations/test_sign_delegation.py @@ -16,8 +16,11 @@ """Gathering of tests related to Delegation operations.""" +from typing import ClassVar + from utils.message import Default, Delegation -from .helper import Flow, Field, TestOperation, pytest_generate_tests # pylint: disable=unused-import + +from .helper import Field, Flow, TestOperation class TestDelegation(TestOperation): @@ -27,15 +30,19 @@ class TestDelegation(TestOperation): def op_class(self): return Delegation - flows = [Flow('basic', delegate=Default.ED25519_PUBLIC_KEY_HASH)] - - fields = [ - Field("delegate", "Delegate", [ - Field.Case(None, "none"), - Field.Case('tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa', "tz1"), - Field.Case('tz2CJBeWWLsUDjVUDqGZL6od3DeBCNzYXrXk', "tz2"), - Field.Case('tz3fLwHKthqhTPK6Lar6CTXN1WbDETw1YpGB', "tz3"), - Field.Case('tz4AcerThk5nGtWNBiSqJfZFeWtz6ZqJ6mTY', "tz4"), - Field.Case('tz1Kp8NCAN5WWwvkWkMmQQXMRe68iURmoQ8w', "long-hash"), - ]), + flows: ClassVar[list[Flow]] = [Flow("basic", delegate=Default.ED25519_PUBLIC_KEY_HASH)] + + fields: ClassVar[list[Field]] = [ + Field( + "delegate", + "Delegate", + [ + Field.Case(None, "none"), + Field.Case("tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa", "tz1"), + Field.Case("tz2CJBeWWLsUDjVUDqGZL6od3DeBCNzYXrXk", "tz2"), + Field.Case("tz3fLwHKthqhTPK6Lar6CTXN1WbDETw1YpGB", "tz3"), + Field.Case("tz4AcerThk5nGtWNBiSqJfZFeWtz6ZqJ6mTY", "tz4"), + Field.Case("tz1Kp8NCAN5WWwvkWkMmQQXMRe68iURmoQ8w", "long-hash"), + ], + ), ] diff --git a/tests/standalone/test_sign/operations/test_sign_failing_noop.py b/tests/standalone/test_sign/operations/test_sign_failing_noop.py index 6a24d8610..fe966bc43 100755 --- a/tests/standalone/test_sign/operations/test_sign_failing_noop.py +++ b/tests/standalone/test_sign/operations/test_sign_failing_noop.py @@ -16,8 +16,11 @@ """Gathering of tests related to Failing-noop operations.""" +from typing import ClassVar + from utils.message import FailingNoop -from .helper import Flow, Field, TestOperation, pytest_generate_tests # pylint: disable=unused-import + +from .helper import Field, Flow, TestOperation class TestFailingNoop(TestOperation): @@ -31,12 +34,19 @@ def field_test_nav_anchor(self) -> str: """No manager ``Source`` row; operation kind is shown first.""" return "Operation" - flows = [Flow('basic', message="message")] - - fields = [ - Field("message", "Message", [ - Field.Case('', "empty"), - Field.Case('message', "message"), - Field.Case('Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', "long-message"), # pylint: disable=line-too-long - ]), + flows: ClassVar[list[Flow]] = [Flow("basic", message="message")] + + fields: ClassVar[list[Field]] = [ + Field( + "message", + "Message", + [ + Field.Case("", "empty"), + Field.Case("message", "message"), + Field.Case( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", # noqa: E501 + "long-message", + ), + ], + ), ] diff --git a/tests/standalone/test_sign/operations/test_sign_increase_paid_storage.py b/tests/standalone/test_sign/operations/test_sign_increase_paid_storage.py index bc68801ed..e9e3ff388 100755 --- a/tests/standalone/test_sign/operations/test_sign_increase_paid_storage.py +++ b/tests/standalone/test_sign/operations/test_sign_increase_paid_storage.py @@ -16,8 +16,11 @@ """Gathering of tests related to Increase-paid-storage operations.""" +from typing import ClassVar + from utils.message import IncreasePaidStorage -from .helper import Flow, Field, TestOperation, pytest_generate_tests # pylint: disable=unused-import + +from .helper import Field, Flow, TestOperation class TestIncreasePaidStorage(TestOperation): @@ -27,18 +30,26 @@ class TestIncreasePaidStorage(TestOperation): def op_class(self): return IncreasePaidStorage - flows = [Flow('basic')] - - fields = [ - Field("amount", "Amount", [ - Field.Case(0, "0"), - Field.Case(1000, "1000"), - Field.Case(1000000, "1000000"), - Field.Case(1000000000, "1000000000"), - Field.Case(0xFFFFFFFFFFFFFFFF, "max"), # max uint64 - ]), - Field("destination", "Destination", [ - Field.Case('KT18amZmM5W7qDWVt2pH6uj7sCEd3kbzLrHT', "kt1"), - Field.Case('KT1GW4QHn66m7WWWMWCMNaWmGYpCRbg5ahwU', "long-hash"), - ]), + flows: ClassVar[list[Flow]] = [Flow("basic")] + + fields: ClassVar[list[Field]] = [ + Field( + "amount", + "Amount", + [ + Field.Case(0, "0"), + Field.Case(1000, "1000"), + Field.Case(1000000, "1000000"), + Field.Case(1000000000, "1000000000"), + Field.Case(0xFFFFFFFFFFFFFFFF, "max"), # max uint64 + ], + ), + Field( + "destination", + "Destination", + [ + Field.Case("KT18amZmM5W7qDWVt2pH6uj7sCEd3kbzLrHT", "kt1"), + Field.Case("KT1GW4QHn66m7WWWMWCMNaWmGYpCRbg5ahwU", "long-hash"), + ], + ), ] diff --git a/tests/standalone/test_sign/operations/test_sign_micheline.py b/tests/standalone/test_sign/operations/test_sign_micheline.py index bcfcaed6d..a4ae8c5e1 100644 --- a/tests/standalone/test_sign/operations/test_sign_micheline.py +++ b/tests/standalone/test_sign/operations/test_sign_micheline.py @@ -18,23 +18,21 @@ from pathlib import Path from string import ascii_lowercase, ascii_uppercase, digits -from typing import List, Tuple import pytest from _pytest.mark import ParameterSet - from utils.account import Account from utils.backend import TezosBackend -from utils.message import Default, MichelineExpr, Micheline +from utils.message import Default, Micheline, MichelineExpr from utils.navigator import TezosNavigator -sequence_cases: List[Tuple[Micheline, str]] = [ +sequence_cases: list[tuple[Micheline, str]] = [ ([], "empty"), ([{"prim": "Unit"}], "singleton"), ([{"int": i} for i in range(0, 256)], "long"), ] -int_cases: List[Tuple[Micheline, str]] = [ +int_cases: list[tuple[Micheline, str]] = [ ({"int": 0}, "0"), ({"int": 123456789}, "all-digit"), ({"int": 1000000000000000000}, "very-big"), @@ -42,50 +40,88 @@ ({"int": -1000000000000000000}, "very-small"), ] -bytes_cases: List[Tuple[Micheline, str]] = [ - ({"bytes": b''.hex()}, "empty"), - ({"bytes": b'00'.hex()}, "0"), +bytes_cases: list[tuple[Micheline, str]] = [ + ({"bytes": b"".hex()}, "empty"), + ({"bytes": b"00".hex()}, "0"), ({"bytes": bytes(range(0, 256)).hex()}, "all-byte"), ] -string_cases: List[Tuple[Micheline, str]] = [ +string_cases: list[tuple[Micheline, str]] = [ ({"string": ""}, "empty"), - ({"string": ''.join(map(chr, range(0, 256)))}, "first-256-char"), + ({"string": "".join(map(chr, range(0, 256)))}, "first-256-char"), ] -default_view_interface = ({"prim": "nat"}, {"prim": "unit"}, [{"prim": "DROP"}, {"prim": "UNIT"}]) +default_view_interface = ( + {"prim": "nat"}, + {"prim": "unit"}, + [{"prim": "DROP"}, {"prim": "UNIT"}], +) # Some prim cases use -data / -instr / -type suffixes on their pytest ids so snapshot # paths stay unique on case-insensitive filesystems (e.g. macOS). If a device directory # under snapshots/ is missing PNGs for a parametrized case, run tests with --golden-run # for that device to refresh goldens. -prim_cases: List[Tuple[Micheline, str]] = [ - +prim_cases: list[tuple[Micheline, str]] = [ # Keyword - ({"prim": "parameter", 'args': [{"prim": "unit"}]}, "parameter"), - ({"prim": "storage", 'args': [{"prim": "unit"}]}, "storage"), - ({"prim": "code", 'args': [{"prim": "ADD"}]}, "code"), - ({"prim": "view", 'args': [{'string': ''}, *default_view_interface]}, "small-view-name"), - ({"prim": "view", 'args': [{'string': 'v' * 31}, *default_view_interface]}, "long-view-name"), - ({"prim": "view", 'args': [{'string': 'view\nname'}, *default_view_interface]}, "view-name-with-newline"), # pylint: disable=line-too-long - + ({"prim": "parameter", "args": [{"prim": "unit"}]}, "parameter"), + ({"prim": "storage", "args": [{"prim": "unit"}]}, "storage"), + ({"prim": "code", "args": [{"prim": "ADD"}]}, "code"), + ( + {"prim": "view", "args": [{"string": ""}, *default_view_interface]}, + "small-view-name", + ), + ( + {"prim": "view", "args": [{"string": "v" * 31}, *default_view_interface]}, + "long-view-name", + ), + ( + {"prim": "view", "args": [{"string": "view\nname"}, *default_view_interface]}, + "view-name-with-newline", + ), # Data ({"prim": "False"}, "False"), - ({"prim": "Elt", 'args': [{"prim": "Unit"}, {"prim": "Unit"}]}, "Elt"), - ({"prim": "Left", 'args': [{"prim": "Unit"}]}, "Left-data"), + ({"prim": "Elt", "args": [{"prim": "Unit"}, {"prim": "Unit"}]}, "Elt"), + ({"prim": "Left", "args": [{"prim": "Unit"}]}, "Left-data"), ({"prim": "None"}, "None-data"), - ({"prim": "Pair", 'args': [{"prim": "Unit"}, {"prim": "Unit"}]}, "Pair-data"), - ({"prim": "Pair", 'args': [{"prim": "Unit"}, {"prim": "Unit"}, {"prim": "Unit"}, {"prim": "Unit"}]}, "long-Pair-data"), # pylint: disable=line-too-long - ({"prim": "Right", 'args': [{"prim": "Unit"}]}, "Right-data"), - ({"prim": "Some", 'args': [{"prim": "Unit"}]}, "Some-data"), + ({"prim": "Pair", "args": [{"prim": "Unit"}, {"prim": "Unit"}]}, "Pair-data"), + ( + { + "prim": "Pair", + "args": [ + {"prim": "Unit"}, + {"prim": "Unit"}, + {"prim": "Unit"}, + {"prim": "Unit"}, + ], + }, + "long-Pair-data", + ), + ({"prim": "Right", "args": [{"prim": "Unit"}]}, "Right-data"), + ({"prim": "Some", "args": [{"prim": "Unit"}]}, "Some-data"), ({"prim": "True"}, "True"), ({"prim": "Unit"}, "Unit-data"), - ({"prim": "Ticket", 'args': [{"string": Default.ORIGINATED_ADDRESS+"%my_entrypoint"}, {"prim": "unit"}, {"prim": "Unit"}, {"int": 0}]}, "Ticket-data"), # pylint: disable=line-too-long - ({"prim": "Lambda_rec", 'args': [[{"prim": "DROP"}, {"prim": "DROP"}, {"prim": "UNIT"}]]}, "Lambda_rec-data"), # pylint: disable=line-too-long - + ( + { + "prim": "Ticket", + "args": [ + {"string": Default.ORIGINATED_ADDRESS + "%my_entrypoint"}, + {"prim": "unit"}, + {"prim": "Unit"}, + {"int": 0}, + ], + }, + "Ticket-data", + ), + ( + { + "prim": "Lambda_rec", + "args": [[{"prim": "DROP"}, {"prim": "DROP"}, {"prim": "UNIT"}]], + }, + "Lambda_rec-data", + ), # Instruction ({"prim": "PACK"}, "PACK"), - ({"prim": "UNPACK", 'args': [{"prim": "unit"}]}, "UNPACK"), + ({"prim": "UNPACK", "args": [{"prim": "unit"}]}, "UNPACK"), ({"prim": "BLAKE2B"}, "BLAKE2B"), ({"prim": "SHA256"}, "SHA256"), ({"prim": "SHA512"}, "SHA512"), @@ -102,72 +138,109 @@ ({"prim": "CONCAT"}, "CONCAT"), ({"prim": "CONS"}, "CONS"), ({"prim": "__CREATE_ACCOUNT__"}, "CREATE_ACCOUNT"), # deprecated - ({"prim": "CREATE_CONTRACT", 'args': [[ - {"prim": "parameter", 'args': [{"prim": "unit"}]}, - {"prim": "storage", 'args': [{"prim": "unit"}]}, - {"prim": "code", 'args': [{"prim": "ADD"}]}, - ]]}, "CREATE_CONTRACT"), - ({"prim": "CREATE_CONTRACT", 'args': [[ - {"prim": "parameter", 'args': [{"prim": "unit"}]}, - {"prim": "storage", 'args': [{"prim": "unit"}]}, - {"prim": "code", 'args': [{"prim": "ADD"}]}, - {"prim": "view", 'args': [{'string': 'view-name1'}, *default_view_interface]}, - {"prim": "view", 'args': [{'string': 'view-name2'}, *default_view_interface]}, - ]]}, "CREATE_CONTRACT-with-views"), + ( + { + "prim": "CREATE_CONTRACT", + "args": [ + [ + {"prim": "parameter", "args": [{"prim": "unit"}]}, + {"prim": "storage", "args": [{"prim": "unit"}]}, + {"prim": "code", "args": [{"prim": "ADD"}]}, + ] + ], + }, + "CREATE_CONTRACT", + ), + ( + { + "prim": "CREATE_CONTRACT", + "args": [ + [ + {"prim": "parameter", "args": [{"prim": "unit"}]}, + {"prim": "storage", "args": [{"prim": "unit"}]}, + {"prim": "code", "args": [{"prim": "ADD"}]}, + { + "prim": "view", + "args": [{"string": "view-name1"}, *default_view_interface], + }, + { + "prim": "view", + "args": [{"string": "view-name2"}, *default_view_interface], + }, + ] + ], + }, + "CREATE_CONTRACT-with-views", + ), ({"prim": "IMPLICIT_ACCOUNT"}, "IMPLICIT_ACCOUNT"), - ({"prim": "DIP", 'args': [[{"prim": "UNIT"}]]}, "DIP"), - ({"prim": "DIP", 'args': [{"int": 0x3FF}, [{"prim": "UNIT"}]]}, "DIP-n"), - ({"prim": "DROP", 'args': [{"int": 0x3FF}]}, "DROP"), + ({"prim": "DIP", "args": [[{"prim": "UNIT"}]]}, "DIP"), + ({"prim": "DIP", "args": [{"int": 0x3FF}, [{"prim": "UNIT"}]]}, "DIP-n"), + ({"prim": "DROP", "args": [{"int": 0x3FF}]}, "DROP"), ({"prim": "DROP"}, "DROP-no-arg"), - ({"prim": "DUP", 'args': [{"int": 0x3FF}]}, "DUP"), + ({"prim": "DUP", "args": [{"int": 0x3FF}]}, "DUP"), ({"prim": "DUP"}, "DUP-no-arg"), - ({"prim": "VIEW", 'args': [{'string': 'view-name'}, {"prim": "unit"}]}, "VIEW"), + ({"prim": "VIEW", "args": [{"string": "view-name"}, {"prim": "unit"}]}, "VIEW"), ({"prim": "EDIV"}, "EDIV"), - ({"prim": "EMPTY_BIG_MAP", 'args': [{"prim": "unit"}, {"prim": "unit"}]}, "EMPTY_BIG_MAP"), - ({"prim": "EMPTY_MAP", 'args': [{"prim": "unit"}, {"prim": "unit"}]}, "EMPTY_MAP"), - ({"prim": "EMPTY_SET", 'args': [{"prim": "unit"}]}, "EMPTY_SET"), + ( + {"prim": "EMPTY_BIG_MAP", "args": [{"prim": "unit"}, {"prim": "unit"}]}, + "EMPTY_BIG_MAP", + ), + ({"prim": "EMPTY_MAP", "args": [{"prim": "unit"}, {"prim": "unit"}]}, "EMPTY_MAP"), + ({"prim": "EMPTY_SET", "args": [{"prim": "unit"}]}, "EMPTY_SET"), ({"prim": "EQ"}, "EQ"), ({"prim": "EXEC"}, "EXEC"), ({"prim": "APPLY"}, "APPLY"), ({"prim": "FAILWITH"}, "FAILWITH"), ({"prim": "GE"}, "GE"), - ({"prim": "GET", 'args': [{"int": 0x7FF}]}, "GET"), + ({"prim": "GET", "args": [{"int": 0x7FF}]}, "GET"), ({"prim": "GET"}, "GET-no-arg"), ({"prim": "GET_AND_UPDATE"}, "GET_AND_UPDATE"), ({"prim": "GT"}, "GT"), ({"prim": "HASH_KEY"}, "HASH_KEY"), - ({"prim": "IF", 'args': [[{"prim": "UNIT"}], [{"prim": "UNIT"}]]}, "IF"), - ({"prim": "IF_CONS", 'args': [[{"prim": "UNIT"}], [{"prim": "UNIT"}]]}, "IF_CONS"), - ({"prim": "IF_LEFT", 'args': [[{"prim": "UNIT"}], [{"prim": "UNIT"}]]}, "IF_LEFT"), - ({"prim": "IF_NONE", 'args': [[{"prim": "UNIT"}], [{"prim": "UNIT"}]]}, "IF_NONE"), + ({"prim": "IF", "args": [[{"prim": "UNIT"}], [{"prim": "UNIT"}]]}, "IF"), + ({"prim": "IF_CONS", "args": [[{"prim": "UNIT"}], [{"prim": "UNIT"}]]}, "IF_CONS"), + ({"prim": "IF_LEFT", "args": [[{"prim": "UNIT"}], [{"prim": "UNIT"}]]}, "IF_LEFT"), + ({"prim": "IF_NONE", "args": [[{"prim": "UNIT"}], [{"prim": "UNIT"}]]}, "IF_NONE"), ({"prim": "INT"}, "INT-instr"), - ({"prim": "LAMBDA", 'args': [{"prim": "unit"}, {"prim": "unit"}, [{"prim": "UNIT"}]]}, "LAMBDA-instr"), # pylint: disable=line-too-long - ({"prim": "LAMBDA_REC", 'args': [{"prim": "unit"}, {"prim": "unit"}, [{"prim": "UNIT"}]]}, "LAMBDA_REC-instr"), # pylint: disable=line-too-long + ( + { + "prim": "LAMBDA", + "args": [{"prim": "unit"}, {"prim": "unit"}, [{"prim": "UNIT"}]], + }, + "LAMBDA-instr", + ), + ( + { + "prim": "LAMBDA_REC", + "args": [{"prim": "unit"}, {"prim": "unit"}, [{"prim": "UNIT"}]], + }, + "LAMBDA_REC-instr", + ), ({"prim": "LE"}, "LE"), - ({"prim": "LEFT", 'args': [{"prim": "unit"}]}, "LEFT-instr"), + ({"prim": "LEFT", "args": [{"prim": "unit"}]}, "LEFT-instr"), ({"prim": "LEVEL"}, "LEVEL"), - ({"prim": "LOOP", 'args': [[{"prim": "UNIT"}]]}, "LOOP"), + ({"prim": "LOOP", "args": [[{"prim": "UNIT"}]]}, "LOOP"), ({"prim": "LSL"}, "LSL"), ({"prim": "LSR"}, "LSR"), ({"prim": "LT"}, "LT"), - ({"prim": "MAP", 'args': [[{"prim": "UNIT"}]]}, "MAP-instr"), + ({"prim": "MAP", "args": [[{"prim": "UNIT"}]]}, "MAP-instr"), ({"prim": "MEM"}, "MEM"), ({"prim": "MUL"}, "MUL"), ({"prim": "NEG"}, "NEG"), ({"prim": "NEQ"}, "NEQ"), - ({"prim": "NIL", 'args': [{"prim": "unit"}]}, "NIL"), - ({"prim": "NONE", 'args': [{"prim": "unit"}]}, "NONE-instr"), + ({"prim": "NIL", "args": [{"prim": "unit"}]}, "NIL"), + ({"prim": "NONE", "args": [{"prim": "unit"}]}, "NONE-instr"), ({"prim": "NOT"}, "NOT"), ({"prim": "NOW"}, "NOW"), ({"prim": "MIN_BLOCK_TIME"}, "MIN_BLOCK_TIME"), ({"prim": "OR"}, "OR-instr"), - ({"prim": "PAIR", 'args': [{"int": 0x3FF}]}, "PAIR-instr"), + ({"prim": "PAIR", "args": [{"int": 0x3FF}]}, "PAIR-instr"), ({"prim": "PAIR"}, "PAIR-no-arg"), - ({"prim": "UNPAIR", 'args': [{"int": 0}]}, "UNPAIR-min"), - ({"prim": "UNPAIR", 'args': [{"int": 0x3FF}]}, "UNPAIR-max"), + ({"prim": "UNPAIR", "args": [{"int": 0}]}, "UNPAIR-min"), + ({"prim": "UNPAIR", "args": [{"int": 0x3FF}]}, "UNPAIR-max"), ({"prim": "UNPAIR"}, "UNPAIR-no-arg"), - ({"prim": "PUSH", 'args': [{"prim": "unit"}, {"prim": "Unit"}]}, "PUSH"), - ({"prim": "RIGHT", 'args': [{"prim": "unit"}]}, "RIGHT-instr"), + ({"prim": "PUSH", "args": [{"prim": "unit"}, {"prim": "Unit"}]}, "PUSH"), + ({"prim": "RIGHT", "args": [{"prim": "unit"}]}, "RIGHT-instr"), ({"prim": "SIZE"}, "SIZE"), ({"prim": "SOME"}, "SOME-instr"), ({"prim": "SOURCE"}, "SOURCE"), @@ -182,21 +255,21 @@ ({"prim": "TRANSFER_TOKENS"}, "TRANSFER_TOKENS"), ({"prim": "SET_DELEGATE"}, "SET_DELEGATE"), ({"prim": "UNIT"}, "UNIT-instr"), - ({"prim": "UPDATE", 'args': [{"int": 0}]}, "UPDATE-min"), - ({"prim": "UPDATE", 'args': [{"int": 0x7FF}]}, "UPDATE-max"), + ({"prim": "UPDATE", "args": [{"int": 0}]}, "UPDATE-min"), + ({"prim": "UPDATE", "args": [{"int": 0x7FF}]}, "UPDATE-max"), ({"prim": "UPDATE"}, "UPDATE-no-arg"), ({"prim": "XOR"}, "XOR"), - ({"prim": "ITER", 'args': [[{"prim": "UNIT"}]]}, "ITER"), - ({"prim": "LOOP_LEFT", 'args': [[{"prim": "UNIT"}]]}, "LOOP_LEFT"), + ({"prim": "ITER", "args": [[{"prim": "UNIT"}]]}, "ITER"), + ({"prim": "LOOP_LEFT", "args": [[{"prim": "UNIT"}]]}, "LOOP_LEFT"), ({"prim": "ADDRESS"}, "ADDRESS-instr"), - ({"prim": "CONTRACT", 'args': [{"prim": "unit"}]}, "CONTRACT-instr"), + ({"prim": "CONTRACT", "args": [{"prim": "unit"}]}, "CONTRACT-instr"), ({"prim": "ISNAT"}, "ISNAT"), - ({"prim": "CAST", 'args': [{"prim": "unit"}]}, "CAST"), + ({"prim": "CAST", "args": [{"prim": "unit"}]}, "CAST"), ({"prim": "RENAME"}, "RENAME"), - ({"prim": "SAPLING_EMPTY_STATE", 'args': [{"int": 0XFFFF}]}, "SAPLING_EMPTY_STATE"), + ({"prim": "SAPLING_EMPTY_STATE", "args": [{"int": 0xFFFF}]}, "SAPLING_EMPTY_STATE"), ({"prim": "SAPLING_VERIFY_UPDATE"}, "SAPLING_VERIFY_UPDATE"), - ({"prim": "DIG", 'args': [{"int": 0x3FF}]}, "DIG"), - ({"prim": "DUG", 'args': [{"int": 0x3FF}]}, "DUG"), + ({"prim": "DIG", "args": [{"int": 0x3FF}]}, "DIG"), + ({"prim": "DUG", "args": [{"int": 0x3FF}]}, "DUG"), ({"prim": "NEVER"}, "NEVER-instr"), ({"prim": "VOTING_POWER"}, "VOTING_POWER"), ({"prim": "TOTAL_VOTING_POWER"}, "TOTAL_VOTING_POWER"), @@ -209,30 +282,40 @@ ({"prim": "SPLIT_TICKET"}, "SPLIT_TICKET"), ({"prim": "JOIN_TICKETS"}, "JOIN_TICKETS"), ({"prim": "OPEN_CHEST"}, "OPEN_CHEST"), - ({"prim": "EMIT", 'args': [{"prim": "unit"}]}, "EMIT"), + ({"prim": "EMIT", "args": [{"prim": "unit"}]}, "EMIT"), ({"prim": "EMIT"}, "EMIT-not-type"), ({"prim": "BYTES"}, "BYTES-instr"), ({"prim": "NAT"}, "NAT-instr"), ({"prim": "IS_IMPLICIT_ACCOUNT"}, "IS_IMPLICIT_ACCOUNT"), ({"prim": "INDEX_ADDRESS"}, "INDEX_ADDRESS"), ({"prim": "GET_ADDRESS_INDEX"}, "GET_ADDRESS_INDEX"), - # Type ({"prim": "bool"}, "bool"), - ({"prim": "contract", 'args': [{"prim": "unit"}]}, "contract-type"), + ({"prim": "contract", "args": [{"prim": "unit"}]}, "contract-type"), ({"prim": "int"}, "int-type"), ({"prim": "key"}, "key"), ({"prim": "key_hash"}, "key_hash"), - ({"prim": "lambda", 'args': [{"prim": "unit"}, {"prim": "unit"}]}, "lambda-type"), - ({"prim": "list", 'args': [{"prim": "unit"}]}, "list"), - ({"prim": "map", 'args': [{"prim": "unit"}, {"prim": "unit"}]}, "map-type"), - ({"prim": "big_map", 'args': [{"prim": "unit"}, {"prim": "unit"}]}, "big_map"), + ({"prim": "lambda", "args": [{"prim": "unit"}, {"prim": "unit"}]}, "lambda-type"), + ({"prim": "list", "args": [{"prim": "unit"}]}, "list"), + ({"prim": "map", "args": [{"prim": "unit"}, {"prim": "unit"}]}, "map-type"), + ({"prim": "big_map", "args": [{"prim": "unit"}, {"prim": "unit"}]}, "big_map"), ({"prim": "nat"}, "nat-type"), - ({"prim": "option", 'args': [{"prim": "unit"}]}, "option"), - ({"prim": "or", 'args': [{"prim": "unit"}, {"prim": "unit"}]}, "or-type"), - ({"prim": "pair", 'args': [{"prim": "unit"}, {"prim": "unit"}]}, "pair-type"), - ({"prim": "pair", 'args': [{"prim": "unit"}, {"prim": "unit"}, {"prim": "unit"}, {"prim": "unit"}]}, "long-pair-type"), # pylint: disable=line-too-long - ({"prim": "set", 'args': [{"prim": "unit"}]}, "set"), + ({"prim": "option", "args": [{"prim": "unit"}]}, "option"), + ({"prim": "or", "args": [{"prim": "unit"}, {"prim": "unit"}]}, "or-type"), + ({"prim": "pair", "args": [{"prim": "unit"}, {"prim": "unit"}]}, "pair-type"), + ( + { + "prim": "pair", + "args": [ + {"prim": "unit"}, + {"prim": "unit"}, + {"prim": "unit"}, + {"prim": "unit"}, + ], + }, + "long-pair-type", + ), + ({"prim": "set", "args": [{"prim": "unit"}]}, "set"), ({"prim": "signature"}, "signature"), ({"prim": "string"}, "string"), ({"prim": "bytes"}, "bytes-type"), @@ -242,148 +325,359 @@ ({"prim": "operation"}, "operation"), ({"prim": "address"}, "address-type"), ({"prim": "tx_rollup_l2_address"}, "tx_rollup_l2_address"), # deprecated - ({"prim": "sapling_transaction", 'args': [{"int": 0}]}, "sapling_transaction"), - ({"prim": "sapling_transaction", 'args': [{"int": 0XFFFF}]}, "sapling_transaction-max-memo-size"), # pylint: disable=line-too-long - ({"prim": "sapling_transaction_deprecated", 'args': [{"int": 0XFFFF}]}, "sapling_transaction_deprecated"), # pylint: disable=line-too-long - ({"prim": "sapling_state", 'args': [{"int": 0}]}, "sapling_state"), + ({"prim": "sapling_transaction", "args": [{"int": 0}]}, "sapling_transaction"), + ( + {"prim": "sapling_transaction", "args": [{"int": 0xFFFF}]}, + "sapling_transaction-max-memo-size", + ), + ( + {"prim": "sapling_transaction_deprecated", "args": [{"int": 0xFFFF}]}, + "sapling_transaction_deprecated", + ), + ({"prim": "sapling_state", "args": [{"int": 0}]}, "sapling_state"), ({"prim": "chain_id"}, "chain_id-type"), ({"prim": "never"}, "never-type"), ({"prim": "bls12_381_g1"}, "bls12_381_g1"), ({"prim": "bls12_381_g2"}, "bls12_381_g2"), ({"prim": "bls12_381_fr"}, "bls12_381_fr"), - ({"prim": "ticket", 'args': [{"prim": "unit"}]}, "ticket-type"), + ({"prim": "ticket", "args": [{"prim": "unit"}]}, "ticket-type"), ({"prim": "chest_key"}, "chest_key"), ({"prim": "chest"}, "chest"), - # Constant - ({"prim": "constant", 'args': [{"string": Default.SCRIPT_EXPR_HASH}]}, "constant"), - + ({"prim": "constant", "args": [{"string": Default.SCRIPT_EXPR_HASH}]}, "constant"), # Annots ({"prim": "unit", "annots": [":annot"]}, "with-annot"), ({"prim": "PAIR", "annots": ["@annot1", "%@annot2"]}, "with-annots"), - ({"prim": "LEFT", "annots": ["%annot"], 'args': [{"prim": "unit"}]}, "with-annot-and-arg"), - ({"prim": "unit", "annots": [":" + ascii_lowercase + ascii_uppercase + '_' + digits]}, "allowed-char-annot"), # pylint: disable=line-too-long - ({"prim": "unit", "annots": [":" + (ascii_lowercase * ((254 // len(ascii_lowercase)) + 1))[:254]]}, "max-len-annot"), # pylint: disable=line-too-long - + ( + {"prim": "LEFT", "annots": ["%annot"], "args": [{"prim": "unit"}]}, + "with-annot-and-arg", + ), + ( + { + "prim": "unit", + "annots": [":" + ascii_lowercase + ascii_uppercase + "_" + digits], + }, + "allowed-char-annot", + ), + ( + { + "prim": "unit", + "annots": [":" + (ascii_lowercase * ((254 // len(ascii_lowercase)) + 1))[:254]], + }, + "max-len-annot", + ), ] -real_cases: List[Tuple[Micheline, str]] = [ - - ({"prim": "pair", 'args': [ - {"prim": "pair", "annots": [":payload"], 'args': [ - {"prim": "nat", "annots": [":counter"]}, - {"prim": "or", "annots": [":action"], 'args': [ - {"prim": "pair", "annots": [":transfer"], 'args': [ - {"prim": "mutez", "annots": [":amount"]}, - {"prim": "contract", "annots": [":dest"], 'args': [{"prim": "unit"}]} - ]}, - {"prim": "or", 'args': [ - {"prim": "option", "annots": [":delegate"], 'args': [{"prim": "key_hash"}]}, - {"prim": "pair", "annots": [":change_keys"], 'args': [ - {"prim": "nat", "annots": [":threshold"]}, - {"prim": "list", "annots": [":keys"], 'args': [{"prim": "key"}]} - ]} - ]} - ]} - ]}, - {"prim": "list", "annots": [":sigs"], 'args': [{"prim": "option", 'args': [{"prim": "signature"}]}]} # pylint: disable=line-too-long - ]}, "type"), - - ({"prim": "Pair", 'args': [ - {"prim": "Pair", 'args': [ - {"int": 42}, - {"prim": "Left", 'args': [ - {"prim": "Pair", 'args': [ - {"int": 123456789}, - {"string": "tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU"} - ]}, - ]}, - ]}, +real_cases: list[tuple[Micheline, str]] = [ + ( + { + "prim": "pair", + "args": [ + { + "prim": "pair", + "annots": [":payload"], + "args": [ + {"prim": "nat", "annots": [":counter"]}, + { + "prim": "or", + "annots": [":action"], + "args": [ + { + "prim": "pair", + "annots": [":transfer"], + "args": [ + {"prim": "mutez", "annots": [":amount"]}, + { + "prim": "contract", + "annots": [":dest"], + "args": [{"prim": "unit"}], + }, + ], + }, + { + "prim": "or", + "args": [ + { + "prim": "option", + "annots": [":delegate"], + "args": [{"prim": "key_hash"}], + }, + { + "prim": "pair", + "annots": [":change_keys"], + "args": [ + { + "prim": "nat", + "annots": [":threshold"], + }, + { + "prim": "list", + "annots": [":keys"], + "args": [{"prim": "key"}], + }, + ], + }, + ], + }, + ], + }, + ], + }, + { + "prim": "list", + "annots": [":sigs"], + "args": [{"prim": "option", "args": [{"prim": "signature"}]}], + }, + ], + }, + "type", + ), + ( + { + "prim": "Pair", + "args": [ + { + "prim": "Pair", + "args": [ + {"int": 42}, + { + "prim": "Left", + "args": [ + { + "prim": "Pair", + "args": [ + {"int": 123456789}, + {"string": "tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU"}, + ], + }, + ], + }, + ], + }, + [ + { + "prim": "Some", + "args": [ + { + "string": "edsigtXomBKi5CTRf5cjATJWSyaRvhfYNHqSUGrn4SdbYRcGwQrUGjzEfQDTuqHhuA8b2d8NarZjz8TRf65WkpQmo423BtomS8Q" # noqa: E501 + } + ], + }, + {"prim": "None"}, + ], + ], + }, + "data", + ), + ( [ - {"prim": "Some", 'args': [ - {"string": "edsigtXomBKi5CTRf5cjATJWSyaRvhfYNHqSUGrn4SdbYRcGwQrUGjzEfQDTuqHhuA8b2d8NarZjz8TRf65WkpQmo423BtomS8Q"} # pylint: disable=line-too-long - ]}, - {"prim": "None"} - ] - ]}, "data"), - - ([ - {"prim": "UNPAIR"}, {"prim": "SWAP"}, {"prim": "DUP"}, {"prim": "DIP", 'args': [[{"prim": "SWAP"}]]}, # pylint: disable=line-too-long - {"prim": "DIP", 'args': [[ {"prim": "UNPAIR"}, - {"prim": "DUP"}, {"prim": "SELF"}, {"prim": "ADDRESS"}, {"prim": "CHAIN_ID"}, {"prim": "PAIR"}, {"prim": "PAIR"}, # pylint: disable=line-too-long - {"prim": "PACK"}, - {"prim": "DIP", 'args': [[{"prim": "UNPAIR", "annots": ["@counter"]}, {"prim": "DIP", 'args': [[{"prim": "SWAP"}]]}]]}, {"prim": "SWAP"}, # pylint: disable=line-too-long - ]]}, - {"prim": "UNPAIR", "annots": ["@stored_counter"]}, {"prim": "DIP", 'args': [[{"prim": "SWAP"}]]}, # pylint: disable=line-too-long - {"prim": "COMPARE"}, {"prim": "EQ"}, {"prim": "IF", 'args': [[], [{"prim": "UNIT"}, {"prim": "FAILWITH"}]]}, # pylint: disable=line-too-long - {"prim": "DIP", 'args': [[{"prim": "SWAP"}]]}, {"prim": "UNPAIR", "annots": ["@threshold", "@keys"]}, # pylint: disable=line-too-long - {"prim": "DIP", 'args': [[ - {"prim": "PUSH", "annots": ["@valid"], 'args': [{"prim": "nat"}, {"int": 0}]}, {"prim": "SWAP"}, # pylint: disable=line-too-long - {"prim": "ITER", 'args': [[ - {"prim": "DIP", 'args': [[{"prim": "SWAP"}]]}, {"prim": "SWAP"}, - {"prim": "IF_CONS", 'args': [ - [{"prim": "IF_NONE", 'args': [ - [{"prim": "SWAP"}, {"prim": "DROP"}], - [ - {"prim": "SWAP"}, - {"prim": "DIP", 'args': [[ - {"prim": "SWAP"}, {"prim": "DIP", 'args': [[{"prim": "DIP", 'args': [[{"prim": "DUP"}]]}, {"prim": "SWAP"}]]}, # pylint: disable=line-too-long - {"prim": "CHECK_SIGNATURE"}, {"prim": "IF", 'args': [[], [{"prim": "UNIT"}, {"prim": "FAILWITH"}]]}, # pylint: disable=line-too-long - {"prim": "PUSH", 'args': [{"prim": "nat"}, {"int": 1}]}, {"prim": "ADD", "annots": ["@valid"]}, # pylint: disable=line-too-long - ]]}, - ], - ]}], - [{"prim": "UNIT"}, {"prim": "FAILWITH"}], - ]}, - {"prim": "SWAP"}, - ]]}, - ]]}, - {"prim": "COMPARE"}, {"prim": "LE"}, {"prim": "IF", 'args': [[], [{"prim": "UNIT"}, {"prim": "FAILWITH"}]]}, # pylint: disable=line-too-long - {"prim": "DROP"}, {"prim": "DROP"}, - {"prim": "DIP", 'args': [[{"prim": "UNPAIR"}, {"prim": "PUSH", 'args': [{"prim": "nat"}, {"int": 1}]}, {"prim": "ADD", "annots": ["@new_counter"]}, {"prim": "PAIR"}]]}, # pylint: disable=line-too-long - {"prim": "NIL", 'args': [{"prim": "operation"}]}, {"prim": "SWAP"}, - {"prim": "IF_LEFT", 'args': [ - [{"prim": "UNPAIR"}, {"prim": "UNIT"}, {"prim": "TRANSFER_TOKENS"}, {"prim": "CONS"}], - [{"prim": "IF_LEFT", 'args': [ - [{"prim": "SET_DELEGATE"}, {"prim": "CONS"}], - [{"prim": "DIP", 'args': [[{"prim": "SWAP"}, {"prim": "CAR"}]]}, {"prim": "SWAP"}, {"prim": "PAIR"}, {"prim": "SWAP"}], # pylint: disable=line-too-long - ]}], - ]}, - {"prim": "PAIR"}, - ], "code"), - + {"prim": "SWAP"}, + {"prim": "DUP"}, + {"prim": "DIP", "args": [[{"prim": "SWAP"}]]}, + { + "prim": "DIP", + "args": [ + [ + {"prim": "UNPAIR"}, + {"prim": "DUP"}, + {"prim": "SELF"}, + {"prim": "ADDRESS"}, + {"prim": "CHAIN_ID"}, + {"prim": "PAIR"}, + {"prim": "PAIR"}, + {"prim": "PACK"}, + { + "prim": "DIP", + "args": [ + [ + {"prim": "UNPAIR", "annots": ["@counter"]}, + {"prim": "DIP", "args": [[{"prim": "SWAP"}]]}, + ] + ], + }, + {"prim": "SWAP"}, + ] + ], + }, + {"prim": "UNPAIR", "annots": ["@stored_counter"]}, + {"prim": "DIP", "args": [[{"prim": "SWAP"}]]}, + {"prim": "COMPARE"}, + {"prim": "EQ"}, + {"prim": "IF", "args": [[], [{"prim": "UNIT"}, {"prim": "FAILWITH"}]]}, + {"prim": "DIP", "args": [[{"prim": "SWAP"}]]}, + {"prim": "UNPAIR", "annots": ["@threshold", "@keys"]}, + { + "prim": "DIP", + "args": [ + [ + { + "prim": "PUSH", + "annots": ["@valid"], + "args": [{"prim": "nat"}, {"int": 0}], + }, + {"prim": "SWAP"}, + { + "prim": "ITER", + "args": [ + [ + {"prim": "DIP", "args": [[{"prim": "SWAP"}]]}, + {"prim": "SWAP"}, + { + "prim": "IF_CONS", + "args": [ + [ + { + "prim": "IF_NONE", + "args": [ + [ + {"prim": "SWAP"}, + {"prim": "DROP"}, + ], + [ + {"prim": "SWAP"}, + { + "prim": "DIP", + "args": [ + [ + {"prim": "SWAP"}, + { + "prim": "DIP", + "args": [ + [ + { + "prim": "DIP", + "args": [[{"prim": "DUP"}]], + }, + {"prim": "SWAP"}, + ] + ], + }, + {"prim": "CHECK_SIGNATURE"}, + { + "prim": "IF", + "args": [ + [], + [ + {"prim": "UNIT"}, + {"prim": "FAILWITH"}, + ], + ], + }, + { + "prim": "PUSH", + "args": [ + {"prim": "nat"}, + {"int": 1}, + ], + }, + { + "prim": "ADD", + "annots": ["@valid"], + }, + ] + ], + }, + ], + ], + } + ], + [{"prim": "UNIT"}, {"prim": "FAILWITH"}], + ], + }, + {"prim": "SWAP"}, + ] + ], + }, + ] + ], + }, + {"prim": "COMPARE"}, + {"prim": "LE"}, + {"prim": "IF", "args": [[], [{"prim": "UNIT"}, {"prim": "FAILWITH"}]]}, + {"prim": "DROP"}, + {"prim": "DROP"}, + { + "prim": "DIP", + "args": [ + [ + {"prim": "UNPAIR"}, + {"prim": "PUSH", "args": [{"prim": "nat"}, {"int": 1}]}, + {"prim": "ADD", "annots": ["@new_counter"]}, + {"prim": "PAIR"}, + ] + ], + }, + {"prim": "NIL", "args": [{"prim": "operation"}]}, + {"prim": "SWAP"}, + { + "prim": "IF_LEFT", + "args": [ + [ + {"prim": "UNPAIR"}, + {"prim": "UNIT"}, + {"prim": "TRANSFER_TOKENS"}, + {"prim": "CONS"}, + ], + [ + { + "prim": "IF_LEFT", + "args": [ + [{"prim": "SET_DELEGATE"}, {"prim": "CONS"}], + [ + { + "prim": "DIP", + "args": [[{"prim": "SWAP"}, {"prim": "CAR"}]], + }, + {"prim": "SWAP"}, + {"prim": "PAIR"}, + {"prim": "SWAP"}, + ], + ], + } + ], + ], + }, + {"prim": "PAIR"}, + ], + "code", + ), ] def _param(micheline: Micheline, name: str, category: str) -> ParameterSet: - return pytest.param(micheline, id=f'{category}-{name}') + return pytest.param(micheline, id=f"{category}-{name}") -def _params(cases: List[Tuple[Micheline, str]], category: str) -> List[ParameterSet]: +def _params(cases: list[tuple[Micheline, str]], category: str) -> list[ParameterSet]: return [_param(*case_, category) for case_ in cases] -def _all_params(cases: List[Tuple[List[Tuple[Micheline, str]], str]]) -> List[ParameterSet]: - return sum([_params(*cases_) for cases_ in cases], []) +def _all_params( + cases: list[tuple[list[tuple[Micheline, str]], str]], +) -> list[ParameterSet]: + return [p for cases_ in cases for p in _params(*cases_)] @pytest.mark.parametrize( - "micheline", _all_params([ - (sequence_cases, "sequence"), - (int_cases, "int"), - (bytes_cases, "bytes"), - (string_cases, "string"), - (prim_cases, "prim"), - (real_cases, "real"), - ]) + "micheline", + _all_params( + [ + (sequence_cases, "sequence"), + (int_cases, "int"), + (bytes_cases, "bytes"), + (string_cases, "string"), + (prim_cases, "prim"), + (real_cases, "real"), + ] + ), ) def test_sign_micheline( - backend: TezosBackend, - tezos_navigator: TezosNavigator, - account: Account, - snapshot_dir: Path, - micheline: Micheline + backend: TezosBackend, + tezos_navigator: TezosNavigator, + account: Account, + snapshot_dir: Path, + micheline: Micheline, ): """Check signing micheline""" @@ -392,8 +686,4 @@ def test_sign_micheline( with backend.sign(account, message, with_hash=False) as result: tezos_navigator.accept_sign(snap_path=snapshot_dir) - account.check_signature( - message=message, - with_hash=False, - data=result.value - ) + account.check_signature(message=message, with_hash=False, data=result.value) diff --git a/tests/standalone/test_sign/operations/test_sign_origination.py b/tests/standalone/test_sign/operations/test_sign_origination.py index 53e7f1276..ce26146f4 100755 --- a/tests/standalone/test_sign/operations/test_sign_origination.py +++ b/tests/standalone/test_sign/operations/test_sign_origination.py @@ -16,8 +16,11 @@ """Gathering of tests related to Origination operations.""" +from typing import ClassVar + from utils.message import Default, Origination -from .helper import Flow, Field, TestOperation, pytest_generate_tests # pylint: disable=unused-import + +from .helper import Field, Flow, TestOperation class TestOrigination(TestOperation): @@ -27,32 +30,58 @@ class TestOrigination(TestOperation): def op_class(self): return Origination - flows = [Flow('basic', delegate=Default.ED25519_PUBLIC_KEY_HASH)] - - fields = [ - Field("balance", "Balance", [ - Field.Case(0, "0"), - Field.Case(1000, "1000"), - Field.Case(1000000, "1000000"), - Field.Case(1000000000, "1000000000"), - Field.Case(0xFFFFFFFFFFFFFFFF, "max"), # max uint64 - ]), - Field("delegate", "Delegate", [ - Field.Case(None, "none"), - Field.Case('tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa', "tz1"), - Field.Case('tz2CJBeWWLsUDjVUDqGZL6od3DeBCNzYXrXk', "tz2"), - Field.Case('tz3fLwHKthqhTPK6Lar6CTXN1WbDETw1YpGB', "tz3"), - Field.Case('tz4AcerThk5nGtWNBiSqJfZFeWtz6ZqJ6mTY', "tz4"), - Field.Case('tz1Kp8NCAN5WWwvkWkMmQQXMRe68iURmoQ8w', "long-hash"), - ]), - Field("code", "Code", [ - Field.Case([], "empty"), - Field.Case([{'prim': 'CDR'}, {'prim': 'NIL', 'args': [{'prim': 'operation'}]}, {'prim': 'PAIR'}], "small"), # pylint: disable=line-too-long - # More test about Micheline in micheline tests - ]), - Field("storage", "Storage", [ - Field.Case({'prim': 'unit'}, "unit"), - Field.Case({'prim': 'or', 'args': [{'prim': 'int'}, {'prim': 'string'}]}, "basic"), - # More test about Micheline in micheline tests - ]), + flows: ClassVar[list[Flow]] = [Flow("basic", delegate=Default.ED25519_PUBLIC_KEY_HASH)] + + fields: ClassVar[list[Field]] = [ + Field( + "balance", + "Balance", + [ + Field.Case(0, "0"), + Field.Case(1000, "1000"), + Field.Case(1000000, "1000000"), + Field.Case(1000000000, "1000000000"), + Field.Case(0xFFFFFFFFFFFFFFFF, "max"), # max uint64 + ], + ), + Field( + "delegate", + "Delegate", + [ + Field.Case(None, "none"), + Field.Case("tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa", "tz1"), + Field.Case("tz2CJBeWWLsUDjVUDqGZL6od3DeBCNzYXrXk", "tz2"), + Field.Case("tz3fLwHKthqhTPK6Lar6CTXN1WbDETw1YpGB", "tz3"), + Field.Case("tz4AcerThk5nGtWNBiSqJfZFeWtz6ZqJ6mTY", "tz4"), + Field.Case("tz1Kp8NCAN5WWwvkWkMmQQXMRe68iURmoQ8w", "long-hash"), + ], + ), + Field( + "code", + "Code", + [ + Field.Case([], "empty"), + Field.Case( + [ + {"prim": "CDR"}, + {"prim": "NIL", "args": [{"prim": "operation"}]}, + {"prim": "PAIR"}, + ], + "small", + ), + # More test about Micheline in micheline tests + ], + ), + Field( + "storage", + "Storage", + [ + Field.Case({"prim": "unit"}, "unit"), + Field.Case( + {"prim": "or", "args": [{"prim": "int"}, {"prim": "string"}]}, + "basic", + ), + # More test about Micheline in micheline tests + ], + ), ] diff --git a/tests/standalone/test_sign/operations/test_sign_proposals.py b/tests/standalone/test_sign/operations/test_sign_proposals.py index 8cd3d1aa0..5344fa6b5 100755 --- a/tests/standalone/test_sign/operations/test_sign_proposals.py +++ b/tests/standalone/test_sign/operations/test_sign_proposals.py @@ -16,8 +16,11 @@ """Gathering of tests related to Proposals operations.""" +from typing import ClassVar + from utils.message import Proposals -from .helper import Flow, Field, TestOperation, pytest_generate_tests # pylint: disable=unused-import + +from .helper import Field, Flow, TestOperation class TestProposals(TestOperation): @@ -27,48 +30,60 @@ class TestProposals(TestOperation): def op_class(self): return Proposals - flows = [ - Flow('basic'), - Flow('empty-proposals', proposals=[]) - ] + flows: ClassVar[list[Flow]] = [Flow("basic"), Flow("empty-proposals", proposals=[])] - fields = [ - Field("source", "Source", [ - Field.Case('tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa', "tz1"), - Field.Case('tz2CJBeWWLsUDjVUDqGZL6od3DeBCNzYXrXk', "tz2"), - Field.Case('tz3fLwHKthqhTPK6Lar6CTXN1WbDETw1YpGB', "tz3"), - Field.Case('tz4AcerThk5nGtWNBiSqJfZFeWtz6ZqJ6mTY', "tz4"), - Field.Case('tz1Kp8NCAN5WWwvkWkMmQQXMRe68iURmoQ8w', "long-hash"), - ]), - Field("period", "Period", [ - Field.Case(0, "0"), - Field.Case(32, "32"), - Field.Case(0x7FFFFFFF, "max"), # max int32 - Field.Case(0x80000000, "min"), # min int32 - ]), - Field("proposals", "Proposal", [ - Field.Case([ - 'Ps9mPmXaRzmzk35gbAYNCAw6UXdE2qoABTHbN2oEEc1qM7CwT9P', - 'PtCJ7pwoxe8JasnHY8YonnLYjcVHmhiARPJvqcC6VfHT5s8k8sY', - 'PsYLVpVvgbLhAhoqAkMFUo6gudkJ9weNXhUYCiLDzcUpFpkk8Wt', - 'PsddFKi32cMJ2qPjf43Qv5GDWLDPZb3T3bF6fLKiF5HtvHNU7aP', - 'Pt24m4xiPbLDhVgVfABUjirbmda3yohdN82Sp9FeuAXJ4eV9otd', - 'PsBABY5HQTSkA4297zNHfsZNKtxULfL18y95qb3m53QJiXGmrbU', - 'PsBabyM1eUXZseaJdmXFApDSBqj8YBfwELoxZHHW77EMcAbbwAS', - 'PsCARTHAGazKbHtnKfLzQg3kms52kSRpgnDY982a9oYsSXRLQEb', - 'PsDELPH1Kxsxt8f9eWbxQeRxkjfbxoqM52jvs5Y5fBxWWh4ifpo', - 'PtEdoTezd3RHSC31mpxxo1npxFjoWWcFgQtxapi51Z8TLu6v6Uq', - 'PtEdo2ZkT9oKpimTah6x2embF25oss54njMuPzkJTEi5RqfdZFA', - 'PsFLorenaUUuikDWvMDr6fGBRG8kt3e3D3fHoXK1j1BFRxeSH4i', - 'PtGRANADsDU8R9daYKAgWnQYAJ64omN1o3KMGVCykShA97vQbvV', - 'PtHangz2aRngywmSRGGvrcTyMbbdpWdpFKuS4uMWxg2RaH9i1qx', - 'Psithaca2MLRFYargivpo7YvUr7wUDqyxrdhC5CQq78mRvimz6A', - 'PtJakart2xVj7pYXJBXrqHgd82rdkLey5ZeeGwDgPp9rhQUbSqY', - 'PtKathmankSpLLDALzWw7CGD2j2MtyveTwboEYokqUCP4a1LxMg', - 'PtLimaPtLMwfNinJi9rCfDPWea8dFgTZ1MeJ9f1m2SRic6ayiwW', - 'PtMumbai2TmsJHNGRkD8v8YDbtao7BLUC3wjASn1inAKLFCjaH1', - 'PtNairobiyssHuh87hEhfVBGCVrK3WnS8Z2FT4ymB5tAa4r1nQf', - ], "full"), # 20 max - Field.Case(['Ptd4kYMModZQ6Mh4ZRNMmWpM799PgSzjmGw3GM9Q2SDqqo8WCW8'], "long-hash"), - ]), + fields: ClassVar[list[Field]] = [ + Field( + "source", + "Source", + [ + Field.Case("tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa", "tz1"), + Field.Case("tz2CJBeWWLsUDjVUDqGZL6od3DeBCNzYXrXk", "tz2"), + Field.Case("tz3fLwHKthqhTPK6Lar6CTXN1WbDETw1YpGB", "tz3"), + Field.Case("tz4AcerThk5nGtWNBiSqJfZFeWtz6ZqJ6mTY", "tz4"), + Field.Case("tz1Kp8NCAN5WWwvkWkMmQQXMRe68iURmoQ8w", "long-hash"), + ], + ), + Field( + "period", + "Period", + [ + Field.Case(0, "0"), + Field.Case(32, "32"), + Field.Case(0x7FFFFFFF, "max"), # max int32 + Field.Case(0x80000000, "min"), # min int32 + ], + ), + Field( + "proposals", + "Proposal", + [ + Field.Case( + [ + "Ps9mPmXaRzmzk35gbAYNCAw6UXdE2qoABTHbN2oEEc1qM7CwT9P", + "PtCJ7pwoxe8JasnHY8YonnLYjcVHmhiARPJvqcC6VfHT5s8k8sY", + "PsYLVpVvgbLhAhoqAkMFUo6gudkJ9weNXhUYCiLDzcUpFpkk8Wt", + "PsddFKi32cMJ2qPjf43Qv5GDWLDPZb3T3bF6fLKiF5HtvHNU7aP", + "Pt24m4xiPbLDhVgVfABUjirbmda3yohdN82Sp9FeuAXJ4eV9otd", + "PsBABY5HQTSkA4297zNHfsZNKtxULfL18y95qb3m53QJiXGmrbU", + "PsBabyM1eUXZseaJdmXFApDSBqj8YBfwELoxZHHW77EMcAbbwAS", + "PsCARTHAGazKbHtnKfLzQg3kms52kSRpgnDY982a9oYsSXRLQEb", + "PsDELPH1Kxsxt8f9eWbxQeRxkjfbxoqM52jvs5Y5fBxWWh4ifpo", + "PtEdoTezd3RHSC31mpxxo1npxFjoWWcFgQtxapi51Z8TLu6v6Uq", + "PtEdo2ZkT9oKpimTah6x2embF25oss54njMuPzkJTEi5RqfdZFA", + "PsFLorenaUUuikDWvMDr6fGBRG8kt3e3D3fHoXK1j1BFRxeSH4i", + "PtGRANADsDU8R9daYKAgWnQYAJ64omN1o3KMGVCykShA97vQbvV", + "PtHangz2aRngywmSRGGvrcTyMbbdpWdpFKuS4uMWxg2RaH9i1qx", + "Psithaca2MLRFYargivpo7YvUr7wUDqyxrdhC5CQq78mRvimz6A", + "PtJakart2xVj7pYXJBXrqHgd82rdkLey5ZeeGwDgPp9rhQUbSqY", + "PtKathmankSpLLDALzWw7CGD2j2MtyveTwboEYokqUCP4a1LxMg", + "PtLimaPtLMwfNinJi9rCfDPWea8dFgTZ1MeJ9f1m2SRic6ayiwW", + "PtMumbai2TmsJHNGRkD8v8YDbtao7BLUC3wjASn1inAKLFCjaH1", + "PtNairobiyssHuh87hEhfVBGCVrK3WnS8Z2FT4ymB5tAa4r1nQf", + ], + "full", + ), # 20 max + Field.Case(["Ptd4kYMModZQ6Mh4ZRNMmWpM799PgSzjmGw3GM9Q2SDqqo8WCW8"], "long-hash"), + ], + ), ] diff --git a/tests/standalone/test_sign/operations/test_sign_register_global_constant.py b/tests/standalone/test_sign/operations/test_sign_register_global_constant.py index 23d7d4d71..fbb94dc7a 100755 --- a/tests/standalone/test_sign/operations/test_sign_register_global_constant.py +++ b/tests/standalone/test_sign/operations/test_sign_register_global_constant.py @@ -16,8 +16,11 @@ """Gathering of tests related to Register-global-constant operations.""" +from typing import ClassVar + from utils.message import Default, RegisterGlobalConstant -from .helper import Flow, Field, TestOperation, pytest_generate_tests # pylint: disable=unused-import + +from .helper import Field, Flow, TestOperation class TestRegisterGlobalConstant(TestOperation): @@ -27,13 +30,23 @@ class TestRegisterGlobalConstant(TestOperation): def op_class(self): return RegisterGlobalConstant - flows = [Flow('basic')] - - fields = [ - Field("value", "Value", [ - Field.Case({'prim': 'Unit'}, "unit"), - Field.Case({'prim': 'Pair', 'args': [{'string': 'a'}, {'int': 1}]}, "basic"), - Field.Case({"prim": "constant", 'args': [{"string": Default.SCRIPT_EXPR_HASH}]}, "with-constant") # pylint: disable=line-too-long - # More test about Micheline in micheline tests - ]), + flows: ClassVar[list[Flow]] = [Flow("basic")] + + fields: ClassVar[list[Field]] = [ + Field( + "value", + "Value", + [ + Field.Case({"prim": "Unit"}, "unit"), + Field.Case({"prim": "Pair", "args": [{"string": "a"}, {"int": 1}]}, "basic"), + Field.Case( + { + "prim": "constant", + "args": [{"string": Default.SCRIPT_EXPR_HASH}], + }, + "with-constant", + ), + # More test about Micheline in micheline tests + ], + ), ] diff --git a/tests/standalone/test_sign/operations/test_sign_reveal.py b/tests/standalone/test_sign/operations/test_sign_reveal.py index df8129a82..69fbe0cfa 100755 --- a/tests/standalone/test_sign/operations/test_sign_reveal.py +++ b/tests/standalone/test_sign/operations/test_sign_reveal.py @@ -16,8 +16,11 @@ """Gathering of tests related to Reveal operations.""" +from typing import ClassVar + from utils.message import Reveal -from .helper import Flow, Field, TestOperation, pytest_generate_tests # pylint: disable=unused-import + +from .helper import Field, Flow, TestOperation class TestReveal(TestOperation): @@ -27,24 +30,41 @@ class TestReveal(TestOperation): def op_class(self): return Reveal - flows = [ - Flow('basic'), + flows: ClassVar[list[Flow]] = [ + Flow("basic"), Flow( - 'with_proof', - public_key='BLpk1koaE6qJifAmUjjeukrgUdZaHCWWcHj6fBqrQLSWvVwHfqNcKKCSv5GxxVHhGirQbjHFsTTk', # pylint: disable=line-too-long - proof='BLsigAQBhGrS8qP3rPEiRJdVMkwN8LgWXzXwty5RDYUaU4D8uLuFPKUj2cfWfznFwj8LG598MC71dDJpcrFk41uYne9MHGN3NecBtkWTKvZXAUkiXKvNssG7YiwD6q7D28gTXmE864dn3x' # pylint: disable=line-too-long + "with_proof", + public_key="BLpk1koaE6qJifAmUjjeukrgUdZaHCWWcHj6fBqrQLSWvVwHfqNcKKCSv5GxxVHhGirQbjHFsTTk", + proof="BLsigAQBhGrS8qP3rPEiRJdVMkwN8LgWXzXwty5RDYUaU4D8uLuFPKUj2cfWfznFwj8LG598MC71dDJpcrFk41uYne9MHGN3NecBtkWTKvZXAUkiXKvNssG7YiwD6q7D28gTXmE864dn3x", ), ] - fields = [ - Field("public_key", "Public key", [ - Field.Case('edpkvMUjmJu9CYyKBAjUV3jtU8Y89TemDAcD29bSNh393Bc8z8BH3t', "tz1"), - Field.Case('sppk7ZT8R42AGSy672NHz9ps6Q4idqWYejAgMwqTWnyYAeq9XZEqWvZ', "tz2"), - Field.Case('p2pk665znpiyPRWEwpu8tZ7JdNPipkfYpGUhYALjaS4Tm7F7wcx1iRs', "tz3"), - Field.Case('BLpk1koaE6qJifAmUjjeukrgUdZaHCWWcHj6fBqrQLSWvVwHfqNcKKCSv5GxxVHhGirQbjHFsTTk', "tz4"), # pylint: disable=line-too-long - Field.Case('edpkuWUfaAWqaxJoG9QKgQRQUHMWfsN1EmoMMXWMwYoE8kjWMWUGDk', "long-hash"), - ]), - Field("proof", "Proof", [ - Field.Case('BLsigAQBhGrS8qP3rPEiRJdVMkwN8LgWXzXwty5RDYUaU4D8uLuFPKUj2cfWfznFwj8LG598MC71dDJpcrFk41uYne9MHGN3NecBtkWTKvZXAUkiXKvNssG7YiwD6q7D28gTXmE864dn3x', "bls"), # pylint: disable=line-too-long - ]), + fields: ClassVar[list[Field]] = [ + Field( + "public_key", + "Public key", + [ + Field.Case("edpkvMUjmJu9CYyKBAjUV3jtU8Y89TemDAcD29bSNh393Bc8z8BH3t", "tz1"), + Field.Case("sppk7ZT8R42AGSy672NHz9ps6Q4idqWYejAgMwqTWnyYAeq9XZEqWvZ", "tz2"), + Field.Case("p2pk665znpiyPRWEwpu8tZ7JdNPipkfYpGUhYALjaS4Tm7F7wcx1iRs", "tz3"), + Field.Case( + "BLpk1koaE6qJifAmUjjeukrgUdZaHCWWcHj6fBqrQLSWvVwHfqNcKKCSv5GxxVHhGirQbjHFsTTk", + "tz4", + ), + Field.Case( + "edpkuWUfaAWqaxJoG9QKgQRQUHMWfsN1EmoMMXWMwYoE8kjWMWUGDk", + "long-hash", + ), + ], + ), + Field( + "proof", + "Proof", + [ + Field.Case( + "BLsigAQBhGrS8qP3rPEiRJdVMkwN8LgWXzXwty5RDYUaU4D8uLuFPKUj2cfWfznFwj8LG598MC71dDJpcrFk41uYne9MHGN3NecBtkWTKvZXAUkiXKvNssG7YiwD6q7D28gTXmE864dn3x", + "bls", + ), + ], + ), ] diff --git a/tests/standalone/test_sign/operations/test_sign_sc_rollup_add_messages.py b/tests/standalone/test_sign/operations/test_sign_sc_rollup_add_messages.py index d4dc63023..e1fed7381 100755 --- a/tests/standalone/test_sign/operations/test_sign_sc_rollup_add_messages.py +++ b/tests/standalone/test_sign/operations/test_sign_sc_rollup_add_messages.py @@ -16,8 +16,11 @@ """Gathering of tests related to Smart-rollup Add-message operations.""" +from typing import ClassVar + from utils.message import ScRollupAddMessage -from .helper import Flow, Field, TestOperation, pytest_generate_tests # pylint: disable=unused-import + +from .helper import Field, Flow, TestOperation class TestScRollupAddMessage(TestOperation): @@ -27,15 +30,22 @@ class TestScRollupAddMessage(TestOperation): def op_class(self): return ScRollupAddMessage - flows = [ - Flow('basic', message=[bytes.fromhex('0123456789ABCDEF')]), - Flow('none', message=[]) + flows: ClassVar[list[Flow]] = [ + Flow("basic", message=[bytes.fromhex("0123456789ABCDEF")]), + Flow("none", message=[]), ] - fields = [ - Field("message", "Message", [ - Field.Case([b''], "empty"), - Field.Case([bytes.fromhex('0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF')], "long"), # pylint: disable=line-too-long - Field.Case([b'\00'] * 20, "many"), # No max - ]), + fields: ClassVar[list[Field]] = [ + Field( + "message", + "Message", + [ + Field.Case([b""], "empty"), + Field.Case( + [bytes.fromhex("0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF")], + "long", + ), + Field.Case([b"\00"] * 20, "many"), # No max + ], + ), ] diff --git a/tests/standalone/test_sign/operations/test_sign_sc_rollup_execute_outbox_message.py b/tests/standalone/test_sign/operations/test_sign_sc_rollup_execute_outbox_message.py index e872fbec0..9a9a75bb7 100755 --- a/tests/standalone/test_sign/operations/test_sign_sc_rollup_execute_outbox_message.py +++ b/tests/standalone/test_sign/operations/test_sign_sc_rollup_execute_outbox_message.py @@ -16,8 +16,11 @@ """Gathering of tests related to Smart-rollup Execute-outbox operations.""" +from typing import ClassVar + from utils.message import ScRollupExecuteOutboxMessage -from .helper import Flow, Field, TestOperation, pytest_generate_tests # pylint: disable=unused-import + +from .helper import Field, Flow, TestOperation class TestScRollupExecuteOutboxMessage(TestOperation): @@ -27,19 +30,37 @@ class TestScRollupExecuteOutboxMessage(TestOperation): def op_class(self): return ScRollupExecuteOutboxMessage - flows = [Flow('basic', output_proof=b'0123456789ABCDEF')] - - fields = [ - Field("rollup", "Rollup", [ - Field.Case('sr163Lv22CdE8QagCwf48PWDTquk6isQwv57', "sr1"), - Field.Case('sr1M9PWZMtqu3MW2dMNCJNo3NApM2Wx9DwMW', "long-hash"), - ]), - Field("cemented_commitment", "Commitment", [ - Field.Case('src12UJzB8mg7yU6nWPzicH7ofJbFjyJEbHvwtZdfRXi8DQHNp1LY8', "src1"), - Field.Case('src13w9G1om8PMHmgMNEwX6NMWKAgDSfmU4mZXbkNWbikmCuWhuB7W', "long-hash"), - ]), - Field("output_proof", "Output proof", [ - Field.Case(b'', "empty"), - Field.Case(b'0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF', "long"), # pylint: disable=line-too-long - ]), + flows: ClassVar[list[Flow]] = [Flow("basic", output_proof=b"0123456789ABCDEF")] + + fields: ClassVar[list[Field]] = [ + Field( + "rollup", + "Rollup", + [ + Field.Case("sr163Lv22CdE8QagCwf48PWDTquk6isQwv57", "sr1"), + Field.Case("sr1M9PWZMtqu3MW2dMNCJNo3NApM2Wx9DwMW", "long-hash"), + ], + ), + Field( + "cemented_commitment", + "Commitment", + [ + Field.Case("src12UJzB8mg7yU6nWPzicH7ofJbFjyJEbHvwtZdfRXi8DQHNp1LY8", "src1"), + Field.Case( + "src13w9G1om8PMHmgMNEwX6NMWKAgDSfmU4mZXbkNWbikmCuWhuB7W", + "long-hash", + ), + ], + ), + Field( + "output_proof", + "Output proof", + [ + Field.Case(b"", "empty"), + Field.Case( + b"0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF", + "long", + ), + ], + ), ] diff --git a/tests/standalone/test_sign/operations/test_sign_sc_rollup_originate.py b/tests/standalone/test_sign/operations/test_sign_sc_rollup_originate.py index 4510c0236..c45625311 100755 --- a/tests/standalone/test_sign/operations/test_sign_sc_rollup_originate.py +++ b/tests/standalone/test_sign/operations/test_sign_sc_rollup_originate.py @@ -16,8 +16,11 @@ """Gathering of tests related to Smart-rollup Originate operations.""" +from typing import ClassVar + from utils.message import ScRollupOriginate -from .helper import Flow, Field, TestOperation, pytest_generate_tests # pylint: disable=unused-import + +from .helper import Field, Flow, TestOperation class TestScRollupOriginate(TestOperation): @@ -27,59 +30,87 @@ class TestScRollupOriginate(TestOperation): def op_class(self): return ScRollupOriginate - flows = [ + flows: ClassVar[list[Flow]] = [ Flow( - 'basic', - kernel='0123456789ABCDEF', - whitelist=['tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa'] + "basic", + kernel="0123456789ABCDEF", + whitelist=["tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa"], ), - Flow('no-whitelist', whitelist=None), - Flow('empty-whitelist', whitelist=[]) + Flow("no-whitelist", whitelist=None), + Flow("empty-whitelist", whitelist=[]), ] - fields = [ - Field("pvm_kind", "Kind", [ - Field.Case('arith', "arith"), - Field.Case('wasm_2_0_0', "wasm_2_0_0"), - Field.Case('riscv', "riscv"), - ]), - Field("kernel", "Kernel", [ - Field.Case('', 'empty'), - Field.Case('0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF', 'long'), # pylint: disable=line-too-long - ]), - Field("parameters_ty", "Parameters", [ - Field.Case({'prim': 'unit'}, "unit"), - Field.Case({'prim': 'or', 'args': [{'prim': 'int'}, {'prim': 'string'}]}, "basic"), - # More test about Micheline in micheline tests - ]), - Field("whitelist", "Whitelist", [ - Field.Case([ - 'tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa', - 'tz2W3Tvcm64GjcV2bipUynnEsctLFz5Z6yRa', - 'tz3XeTwXXJeWNgVR3LqMcyBDdnxjbZ7TeEGH', - 'tz4AcerThk5nGtWNBiSqJfZFeWtz6ZqJ6mTY', - 'tz1er74kx433vTtpYddGsf3dDt5piBZeeHyQ', - 'tz2JPgTWZZpxZZLqHMfS69UAy1UHm4Aw5iHu', - 'tz3fLwHKthqhTPK6Lar6CTXN1WbDETw1YpGB', - 'tz4J1Kjhjc3QpMxTaLeQaJxWR7DVV8VK5gdq', - 'tz1McCh72NRhYmJBcWr3zDrLJAxnfR9swcFh', - 'tz2CJBeWWLsUDjVUDqGZL6od3DeBCNzYXrXk', - 'tz3eydffbLkjdVb8zx42BvxpGV87zaRnqL3r', - 'tz4HQ7WgTRdgrxEdLWcGrgzYrHbz6a9ELZi3', - 'tz1TmFPVZsGQ8MnrBJtnECJgkFUwLa6EWYDm', - 'tz2KC42yW9FXFMJpkUooae2NFYQsM5do3E8H', - 'tz3hCsUiQDfneTgD7CSZDaUro8SA5aEhwCp2', - 'tz4H6NGpYd76yxZ4aGbPNKtWMJEEfZFBch2W', - 'tz1e8fEumaLvXXe5jV52gejCSt3mGodoKut9', - 'tz2PPZ2WN4j92Rdx4NM7oW3HAp3x825HUyac', - 'tz3Wazpbs4CFj78qv2KBJ8Z7HEyqk6ZPxwWZ', - 'tz4Mh4LFWMpACmKNWm1WNntMCPixsBWaMWMU', - ], "many"), # Max 4096 - Field.Case([ - 'tz1Kp8NCAN5WWwvkWkMmQQXMRe68iURmoQ8w', - 'tz2WmivuMG8MMRKMEmzKRMMxMApxZQWYNS4W', - 'tz3XMQscBFM9vPmpbYMavMmwxRMUWvWGZMQQ', - 'tz4DNQhMQaU9WMCVGwH6mQGGWqMNQHTjywDe', - ], "long-hash"), - ]), + fields: ClassVar[list[Field]] = [ + Field( + "pvm_kind", + "Kind", + [ + Field.Case("arith", "arith"), + Field.Case("wasm_2_0_0", "wasm_2_0_0"), + Field.Case("riscv", "riscv"), + ], + ), + Field( + "kernel", + "Kernel", + [ + Field.Case("", "empty"), + Field.Case( + "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF", + "long", + ), + ], + ), + Field( + "parameters_ty", + "Parameters", + [ + Field.Case({"prim": "unit"}, "unit"), + Field.Case( + {"prim": "or", "args": [{"prim": "int"}, {"prim": "string"}]}, + "basic", + ), + # More test about Micheline in micheline tests + ], + ), + Field( + "whitelist", + "Whitelist", + [ + Field.Case( + [ + "tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa", + "tz2W3Tvcm64GjcV2bipUynnEsctLFz5Z6yRa", + "tz3XeTwXXJeWNgVR3LqMcyBDdnxjbZ7TeEGH", + "tz4AcerThk5nGtWNBiSqJfZFeWtz6ZqJ6mTY", + "tz1er74kx433vTtpYddGsf3dDt5piBZeeHyQ", + "tz2JPgTWZZpxZZLqHMfS69UAy1UHm4Aw5iHu", + "tz3fLwHKthqhTPK6Lar6CTXN1WbDETw1YpGB", + "tz4J1Kjhjc3QpMxTaLeQaJxWR7DVV8VK5gdq", + "tz1McCh72NRhYmJBcWr3zDrLJAxnfR9swcFh", + "tz2CJBeWWLsUDjVUDqGZL6od3DeBCNzYXrXk", + "tz3eydffbLkjdVb8zx42BvxpGV87zaRnqL3r", + "tz4HQ7WgTRdgrxEdLWcGrgzYrHbz6a9ELZi3", + "tz1TmFPVZsGQ8MnrBJtnECJgkFUwLa6EWYDm", + "tz2KC42yW9FXFMJpkUooae2NFYQsM5do3E8H", + "tz3hCsUiQDfneTgD7CSZDaUro8SA5aEhwCp2", + "tz4H6NGpYd76yxZ4aGbPNKtWMJEEfZFBch2W", + "tz1e8fEumaLvXXe5jV52gejCSt3mGodoKut9", + "tz2PPZ2WN4j92Rdx4NM7oW3HAp3x825HUyac", + "tz3Wazpbs4CFj78qv2KBJ8Z7HEyqk6ZPxwWZ", + "tz4Mh4LFWMpACmKNWm1WNntMCPixsBWaMWMU", + ], + "many", + ), # Max 4096 + Field.Case( + [ + "tz1Kp8NCAN5WWwvkWkMmQQXMRe68iURmoQ8w", + "tz2WmivuMG8MMRKMEmzKRMMxMApxZQWYNS4W", + "tz3XMQscBFM9vPmpbYMavMmwxRMUWvWGZMQQ", + "tz4DNQhMQaU9WMCVGwH6mQGGWqMNQHTjywDe", + ], + "long-hash", + ), + ], + ), ] diff --git a/tests/standalone/test_sign/operations/test_sign_set_companion_key.py b/tests/standalone/test_sign/operations/test_sign_set_companion_key.py index bb716e9ca..44d875dae 100644 --- a/tests/standalone/test_sign/operations/test_sign_set_companion_key.py +++ b/tests/standalone/test_sign/operations/test_sign_set_companion_key.py @@ -16,8 +16,11 @@ """Gathering of tests related to Set-companion-key operations.""" +from typing import ClassVar + from utils.message import UpdateCompanionKey -from .helper import Flow, Field, TestOperation, pytest_generate_tests # pylint: disable=unused-import + +from .helper import Field, Flow, TestOperation class TestUpdateCompanionKey(TestOperation): @@ -27,24 +30,41 @@ class TestUpdateCompanionKey(TestOperation): def op_class(self): return UpdateCompanionKey - flows = [ - Flow('basic'), + flows: ClassVar[list[Flow]] = [ + Flow("basic"), Flow( - 'with_proof', - pk='BLpk1koaE6qJifAmUjjeukrgUdZaHCWWcHj6fBqrQLSWvVwHfqNcKKCSv5GxxVHhGirQbjHFsTTk', - proof='BLsigAQBhGrS8qP3rPEiRJdVMkwN8LgWXzXwty5RDYUaU4D8uLuFPKUj2cfWfznFwj8LG598MC71dDJpcrFk41uYne9MHGN3NecBtkWTKvZXAUkiXKvNssG7YiwD6q7D28gTXmE864dn3x' # pylint: disable=line-too-long + "with_proof", + pk="BLpk1koaE6qJifAmUjjeukrgUdZaHCWWcHj6fBqrQLSWvVwHfqNcKKCSv5GxxVHhGirQbjHFsTTk", + proof="BLsigAQBhGrS8qP3rPEiRJdVMkwN8LgWXzXwty5RDYUaU4D8uLuFPKUj2cfWfznFwj8LG598MC71dDJpcrFk41uYne9MHGN3NecBtkWTKvZXAUkiXKvNssG7YiwD6q7D28gTXmE864dn3x", ), ] - fields = [ - Field("pk", "Public key", [ - Field.Case('edpkvMUjmJu9CYyKBAjUV3jtU8Y89TemDAcD29bSNh393Bc8z8BH3t', "tz1"), - Field.Case('sppk7ZT8R42AGSy672NHz9ps6Q4idqWYejAgMwqTWnyYAeq9XZEqWvZ', "tz2"), - Field.Case('p2pk665znpiyPRWEwpu8tZ7JdNPipkfYpGUhYALjaS4Tm7F7wcx1iRs', "tz3"), - Field.Case('BLpk1koaE6qJifAmUjjeukrgUdZaHCWWcHj6fBqrQLSWvVwHfqNcKKCSv5GxxVHhGirQbjHFsTTk', "tz4"), # pylint: disable=line-too-long - Field.Case('edpkuWUfaAWqaxJoG9QKgQRQUHMWfsN1EmoMMXWMwYoE8kjWMWUGDk', "long-hash"), - ]), - Field("proof", "Proof", [ - Field.Case('BLsigAQBhGrS8qP3rPEiRJdVMkwN8LgWXzXwty5RDYUaU4D8uLuFPKUj2cfWfznFwj8LG598MC71dDJpcrFk41uYne9MHGN3NecBtkWTKvZXAUkiXKvNssG7YiwD6q7D28gTXmE864dn3x', "bls"), # pylint: disable=line-too-long - ]), + fields: ClassVar[list[Field]] = [ + Field( + "pk", + "Public key", + [ + Field.Case("edpkvMUjmJu9CYyKBAjUV3jtU8Y89TemDAcD29bSNh393Bc8z8BH3t", "tz1"), + Field.Case("sppk7ZT8R42AGSy672NHz9ps6Q4idqWYejAgMwqTWnyYAeq9XZEqWvZ", "tz2"), + Field.Case("p2pk665znpiyPRWEwpu8tZ7JdNPipkfYpGUhYALjaS4Tm7F7wcx1iRs", "tz3"), + Field.Case( + "BLpk1koaE6qJifAmUjjeukrgUdZaHCWWcHj6fBqrQLSWvVwHfqNcKKCSv5GxxVHhGirQbjHFsTTk", + "tz4", + ), + Field.Case( + "edpkuWUfaAWqaxJoG9QKgQRQUHMWfsN1EmoMMXWMwYoE8kjWMWUGDk", + "long-hash", + ), + ], + ), + Field( + "proof", + "Proof", + [ + Field.Case( + "BLsigAQBhGrS8qP3rPEiRJdVMkwN8LgWXzXwty5RDYUaU4D8uLuFPKUj2cfWfznFwj8LG598MC71dDJpcrFk41uYne9MHGN3NecBtkWTKvZXAUkiXKvNssG7YiwD6q7D28gTXmE864dn3x", + "bls", + ), + ], + ), ] diff --git a/tests/standalone/test_sign/operations/test_sign_set_consensus_key.py b/tests/standalone/test_sign/operations/test_sign_set_consensus_key.py index 44d84ed65..2fb668a88 100755 --- a/tests/standalone/test_sign/operations/test_sign_set_consensus_key.py +++ b/tests/standalone/test_sign/operations/test_sign_set_consensus_key.py @@ -16,8 +16,11 @@ """Gathering of tests related to Set-consensus-key operations.""" +from typing import ClassVar + from utils.message import UpdateConsensusKey -from .helper import Flow, Field, TestOperation, pytest_generate_tests # pylint: disable=unused-import + +from .helper import Field, Flow, TestOperation class TestUpdateConsensusKey(TestOperation): @@ -27,24 +30,41 @@ class TestUpdateConsensusKey(TestOperation): def op_class(self): return UpdateConsensusKey - flows = [ - Flow('basic'), + flows: ClassVar[list[Flow]] = [ + Flow("basic"), Flow( - 'with_proof', - pk='BLpk1koaE6qJifAmUjjeukrgUdZaHCWWcHj6fBqrQLSWvVwHfqNcKKCSv5GxxVHhGirQbjHFsTTk', - proof='BLsigAQBhGrS8qP3rPEiRJdVMkwN8LgWXzXwty5RDYUaU4D8uLuFPKUj2cfWfznFwj8LG598MC71dDJpcrFk41uYne9MHGN3NecBtkWTKvZXAUkiXKvNssG7YiwD6q7D28gTXmE864dn3x' # pylint: disable=line-too-long + "with_proof", + pk="BLpk1koaE6qJifAmUjjeukrgUdZaHCWWcHj6fBqrQLSWvVwHfqNcKKCSv5GxxVHhGirQbjHFsTTk", + proof="BLsigAQBhGrS8qP3rPEiRJdVMkwN8LgWXzXwty5RDYUaU4D8uLuFPKUj2cfWfznFwj8LG598MC71dDJpcrFk41uYne9MHGN3NecBtkWTKvZXAUkiXKvNssG7YiwD6q7D28gTXmE864dn3x", ), ] - fields = [ - Field("pk", "Public key", [ - Field.Case('edpkvMUjmJu9CYyKBAjUV3jtU8Y89TemDAcD29bSNh393Bc8z8BH3t', "tz1"), - Field.Case('sppk7ZT8R42AGSy672NHz9ps6Q4idqWYejAgMwqTWnyYAeq9XZEqWvZ', "tz2"), - Field.Case('p2pk665znpiyPRWEwpu8tZ7JdNPipkfYpGUhYALjaS4Tm7F7wcx1iRs', "tz3"), - Field.Case('BLpk1koaE6qJifAmUjjeukrgUdZaHCWWcHj6fBqrQLSWvVwHfqNcKKCSv5GxxVHhGirQbjHFsTTk', "tz4"), # pylint: disable=line-too-long - Field.Case('edpkuWUfaAWqaxJoG9QKgQRQUHMWfsN1EmoMMXWMwYoE8kjWMWUGDk', "long-hash"), - ]), - Field("proof", "Proof", [ - Field.Case('BLsigAQBhGrS8qP3rPEiRJdVMkwN8LgWXzXwty5RDYUaU4D8uLuFPKUj2cfWfznFwj8LG598MC71dDJpcrFk41uYne9MHGN3NecBtkWTKvZXAUkiXKvNssG7YiwD6q7D28gTXmE864dn3x', "bls"), # pylint: disable=line-too-long - ]), + fields: ClassVar[list[Field]] = [ + Field( + "pk", + "Public key", + [ + Field.Case("edpkvMUjmJu9CYyKBAjUV3jtU8Y89TemDAcD29bSNh393Bc8z8BH3t", "tz1"), + Field.Case("sppk7ZT8R42AGSy672NHz9ps6Q4idqWYejAgMwqTWnyYAeq9XZEqWvZ", "tz2"), + Field.Case("p2pk665znpiyPRWEwpu8tZ7JdNPipkfYpGUhYALjaS4Tm7F7wcx1iRs", "tz3"), + Field.Case( + "BLpk1koaE6qJifAmUjjeukrgUdZaHCWWcHj6fBqrQLSWvVwHfqNcKKCSv5GxxVHhGirQbjHFsTTk", + "tz4", + ), + Field.Case( + "edpkuWUfaAWqaxJoG9QKgQRQUHMWfsN1EmoMMXWMwYoE8kjWMWUGDk", + "long-hash", + ), + ], + ), + Field( + "proof", + "Proof", + [ + Field.Case( + "BLsigAQBhGrS8qP3rPEiRJdVMkwN8LgWXzXwty5RDYUaU4D8uLuFPKUj2cfWfznFwj8LG598MC71dDJpcrFk41uYne9MHGN3NecBtkWTKvZXAUkiXKvNssG7YiwD6q7D28gTXmE864dn3x", + "bls", + ), + ], + ), ] diff --git a/tests/standalone/test_sign/operations/test_sign_set_deposit_limit.py b/tests/standalone/test_sign/operations/test_sign_set_deposit_limit.py index 5b957b863..ee240b826 100755 --- a/tests/standalone/test_sign/operations/test_sign_set_deposit_limit.py +++ b/tests/standalone/test_sign/operations/test_sign_set_deposit_limit.py @@ -16,8 +16,11 @@ """Gathering of tests related to Set-deposit-limit operations.""" +from typing import ClassVar + from utils.message import SetDepositLimit -from .helper import Flow, Field, TestOperation, pytest_generate_tests # pylint: disable=unused-import + +from .helper import Field, Flow, TestOperation class TestSetDepositLimit(TestOperation): @@ -27,15 +30,19 @@ class TestSetDepositLimit(TestOperation): def op_class(self): return SetDepositLimit - flows = [Flow('basic', limit=400)] - - fields = [ - Field("limit", "Staking limit", [ - Field.Case(None, "none"), - Field.Case(0, "0"), - Field.Case(1000, "1000"), - Field.Case(1000000, "1000000"), - Field.Case(1000000000, "1000000000"), - Field.Case(0xFFFFFFFFFFFFFFFF, "max"), # max uint64 - ]), + flows: ClassVar[list[Flow]] = [Flow("basic", limit=400)] + + fields: ClassVar[list[Field]] = [ + Field( + "limit", + "Staking limit", + [ + Field.Case(None, "none"), + Field.Case(0, "0"), + Field.Case(1000, "1000"), + Field.Case(1000000, "1000000"), + Field.Case(1000000000, "1000000000"), + Field.Case(0xFFFFFFFFFFFFFFFF, "max"), # max uint64 + ], + ), ] diff --git a/tests/standalone/test_sign/operations/test_sign_staking2.py b/tests/standalone/test_sign/operations/test_sign_staking2.py index 952c600ad..c229b47cd 100644 --- a/tests/standalone/test_sign/operations/test_sign_staking2.py +++ b/tests/standalone/test_sign/operations/test_sign_staking2.py @@ -31,16 +31,15 @@ sign flows** for the doc-shaped staking vectors. """ -from typing import Optional +from typing import ClassVar import pytest - from utils.account import Account from utils.backend import TezosBackend from utils.message import Transaction from utils.navigator import TezosNavigator -from .helper import Flow, TestOperation, pytest_generate_tests # pylint: disable=unused-import +from .helper import Flow, TestOperation # Self-transfer destination matching default manager source in tests. _SELF = "tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU" @@ -51,7 +50,7 @@ class TestStaking2DocExamples(TestOperation): """Sign flows for Staking 2.0 doc-shaped tag-108 operations.""" - def skip_signature_check(self) -> Optional[str]: + def skip_signature_check(self) -> str | None: """Same ``Transaction`` class as ``TestTransaction``; avoid duplicate hash/signature test.""" return "Signature check for Transaction is already run in test_sign_transaction." @@ -62,65 +61,62 @@ def op_class(self): # Flow names are stable: snapshot dirs are ``flow-staking2_*`` # (see test_sign_transaction history). - flows = [ + flows: ClassVar[list[Flow]] = [ # Paris doc § pseudo-ops: stake — non-zero amount, self-transfer, Unit parameter. Flow( - 'staking2_stake_self', + "staking2_stake_self", amount=1000000000, destination=_SELF, - entrypoint='stake', - parameter={'prim': 'Unit'}, + entrypoint="stake", + parameter={"prim": "Unit"}, ), # unstake — positive amount, self-transfer, Unit (typical wallet encoding). Flow( - 'staking2_unstake_self', + "staking2_unstake_self", amount=500000000, destination=_SELF, - entrypoint='unstake', - parameter={'prim': 'Unit'}, + entrypoint="unstake", + parameter={"prim": "Unit"}, ), # finalize_unstake — classic self-call: zero amount, Unit. Flow( - 'staking2_finalize_unstake_self', + "staking2_finalize_unstake_self", amount=0, destination=_SELF, - entrypoint='finalize_unstake', - parameter={'prim': 'Unit'}, + entrypoint="finalize_unstake", + parameter={"prim": "Unit"}, ), # Seoul doc: third party pays fee; # destination = staker implicit, zero amount, finalize_unstake. Flow( - 'staking2_finalize_unstake_sponsored', + "staking2_finalize_unstake_sponsored", amount=0, destination=_STAKER_OTHER, - entrypoint='finalize_unstake', - parameter={'prim': 'Unit'}, + entrypoint="finalize_unstake", + parameter={"prim": "Unit"}, ), # set_delegate_parameters — zero amount self-call; # Pair limits (illustrative values, Paris doc shape). Flow( - 'staking2_set_delegate_parameters', + "staking2_set_delegate_parameters", amount=0, destination=_SELF, - entrypoint='set_delegate_parameters', - parameter={'prim': 'Pair', 'args': [ - {'int': 4000000}, - {'prim': 'Pair', 'args': [ - {'int': 20000000}, - {'prim': 'Unit'} - ]} - ]}, + entrypoint="set_delegate_parameters", + parameter={ + "prim": "Pair", + "args": [ + {"int": 4000000}, + {"prim": "Pair", "args": [{"int": 20000000}, {"prim": "Unit"}]}, + ], + }, ), ] - def test_operation_field( # pylint: disable=unused-argument - self, - backend: TezosBackend, - tezos_navigator: TezosNavigator, - account: Account, + def test_operation_field( + self, + backend: TezosBackend, + tezos_navigator: TezosNavigator, + account: Account, ): """Omitted here: use ``TestTransaction`` for the shared field matrix.""" - pytest.skip( - "Staking 2.0 entrypoint/amount/parameter field tests are covered by " - "test_sign_transaction.TestTransaction." - ) + pytest.skip("Staking 2.0 entrypoint/amount/parameter field tests are covered by test_sign_transaction.TestTransaction.") diff --git a/tests/standalone/test_sign/operations/test_sign_transaction.py b/tests/standalone/test_sign/operations/test_sign_transaction.py index 33c1b3490..a7aa7a57a 100755 --- a/tests/standalone/test_sign/operations/test_sign_transaction.py +++ b/tests/standalone/test_sign/operations/test_sign_transaction.py @@ -19,68 +19,91 @@ Staking 2.0 doc-shaped **sign flows** live in ``test_sign_staking2.py``. """ +from typing import ClassVar + from utils.message import Transaction -from .helper import Flow, Field, TestOperation, pytest_generate_tests # pylint: disable=unused-import + +from .helper import Field, Flow, TestOperation # FA2 transfer parameter: single item, token_id=0 (clear-signing supported). # Inner list(pair(uint64,uint64)) as direct Pair (common wallet encoding, e.g. Temple). _FA2_TRANSFER_PARAMETER = [ - {'prim': 'Pair', 'args': [ - {'string': 'KT1QWdbASvaTXW8GWfhfNh3JMjgXvnZAATJW'}, - [{'prim': 'Pair', 'args': [ - {'string': 'sr1MyCwR83hZphCSqaYSQApPxPMeyksJWWnh'}, - {'prim': 'Pair', 'args': [ - {'int': 0}, - {'int': 200000} - ]} - ]}] - ]} + { + "prim": "Pair", + "args": [ + {"string": "KT1QWdbASvaTXW8GWfhfNh3JMjgXvnZAATJW"}, + [ + { + "prim": "Pair", + "args": [ + {"string": "sr1MyCwR83hZphCSqaYSQApPxPMeyksJWWnh"}, + {"prim": "Pair", "args": [{"int": 0}, {"int": 200000}]}, + ], + } + ], + ], + } ] # Same as above but inner txs item uses SEQ [ Pair(...) ] (alternative encoding). _FA2_TRANSFER_PARAMETER_SEQ_WRAPPED = [ - {'prim': 'Pair', 'args': [ - {'string': 'KT1QWdbASvaTXW8GWfhfNh3JMjgXvnZAATJW'}, - [{'prim': 'Pair', 'args': [ - {'string': 'sr1MyCwR83hZphCSqaYSQApPxPMeyksJWWnh'}, - [{'prim': 'Pair', 'args': [ - {'int': 0}, - {'int': 200000} - ]}] - ]}] - ]} + { + "prim": "Pair", + "args": [ + {"string": "KT1QWdbASvaTXW8GWfhfNh3JMjgXvnZAATJW"}, + [ + { + "prim": "Pair", + "args": [ + {"string": "sr1MyCwR83hZphCSqaYSQApPxPMeyksJWWnh"}, + [{"prim": "Pair", "args": [{"int": 0}, {"int": 200000}]}], + ], + } + ], + ], + } ] # FA2 transfer parameter: token_id!=0 (clear-signing supported when registered). _FA2_TRANSFER_PARAMETER_TOKEN_ID_1 = [ - {'prim': 'Pair', 'args': [ - {'string': 'KT1QWdbASvaTXW8GWfhfNh3JMjgXvnZAATJW'}, - [{'prim': 'Pair', 'args': [ - {'string': 'sr1MyCwR83hZphCSqaYSQApPxPMeyksJWWnh'}, - {'prim': 'Pair', 'args': [ - {'int': 1}, - {'int': 100} - ]} - ]}] - ]} + { + "prim": "Pair", + "args": [ + {"string": "KT1QWdbASvaTXW8GWfhfNh3JMjgXvnZAATJW"}, + [ + { + "prim": "Pair", + "args": [ + {"string": "sr1MyCwR83hZphCSqaYSQApPxPMeyksJWWnh"}, + {"prim": "Pair", "args": [{"int": 1}, {"int": 100}]}, + ], + } + ], + ], + } ] # FA2 transfer parameter: token_id not in registry (fallback to generic display). _FA2_TRANSFER_UNREGISTERED_TOKEN_ID_PARAMETER = [ - {'prim': 'Pair', 'args': [ - {'string': 'KT1QWdbASvaTXW8GWfhfNh3JMjgXvnZAATJW'}, - [{'prim': 'Pair', 'args': [ - {'string': 'sr1MyCwR83hZphCSqaYSQApPxPMeyksJWWnh'}, - {'prim': 'Pair', 'args': [ - {'int': 42}, - {'int': 100} - ]} - ]}] - ]} + { + "prim": "Pair", + "args": [ + {"string": "KT1QWdbASvaTXW8GWfhfNh3JMjgXvnZAATJW"}, + [ + { + "prim": "Pair", + "args": [ + {"string": "sr1MyCwR83hZphCSqaYSQApPxPMeyksJWWnh"}, + {"prim": "Pair", "args": [{"int": 42}, {"int": 100}]}, + ], + } + ], + ], + } ] -_FA2_KT1_DESTINATION = 'KT18fp5rcTW7mbWDmzFwjLDUhs5MeJmagDSZ' -_FA2_KT1_TOKEN_ID_0_DESTINATION = 'KT193D4vozYnhGJQVtw7CoxxqphqUEEwK6Vb' +_FA2_KT1_DESTINATION = "KT18fp5rcTW7mbWDmzFwjLDUhs5MeJmagDSZ" +_FA2_KT1_TOKEN_ID_0_DESTINATION = "KT193D4vozYnhGJQVtw7CoxxqphqUEEwK6Vb" class TestTransaction(TestOperation): @@ -90,102 +113,126 @@ class TestTransaction(TestOperation): def op_class(self): return Transaction - flows = [ - Flow('basic', amount=5), + flows: ClassVar[list[Flow]] = [ + Flow("basic", amount=5), Flow( - 'contract_call', + "contract_call", destination=_FA2_KT1_TOKEN_ID_0_DESTINATION, entrypoint="transfer", parameter=_FA2_TRANSFER_PARAMETER, ), Flow( - 'contract_call_fa2_fallback', + "contract_call_fa2_fallback", destination=_FA2_KT1_DESTINATION, entrypoint="transfer", parameter=_FA2_TRANSFER_UNREGISTERED_TOKEN_ID_PARAMETER, ), Flow( - 'contract_call_fa2_token_id_not_0', + "contract_call_fa2_token_id_not_0", destination=_FA2_KT1_DESTINATION, entrypoint="transfer", parameter=_FA2_TRANSFER_PARAMETER_TOKEN_ID_1, ), ] - fields = [ - Field("amount", "Amount", [ - Field.Case(0, "0"), - Field.Case(1000, "1000"), - Field.Case(1000000, "1000000"), - Field.Case(1000000000, "1000000000"), - Field.Case(0xFFFFFFFFFFFFFFFF, "max"), # max uint64 - ]), - Field("destination", "Destination", [ - Field.Case('tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa', "tz1"), - Field.Case('tz2CJBeWWLsUDjVUDqGZL6od3DeBCNzYXrXk', "tz2"), - Field.Case('tz3fLwHKthqhTPK6Lar6CTXN1WbDETw1YpGB', "tz3"), - Field.Case('tz4DNQhMQaU9WMCVGwH6mQGGWqMNQHTjywDe', "tz4"), - Field.Case('KT18amZmM5W7qDWVt2pH6uj7sCEd3kbzLrHT', "kt1"), - Field.Case('tz1Kp8NCAN5WWwvkWkMmQQXMRe68iURmoQ8w', "long-hash"), - ]), - Field("entrypoint", "Entrypoint", [ - # `parameter` is set to make sure `entrypoint` is displayed with `default` - Field.Case('default', "default", parameter={'prim': 'None'}), - Field.Case('root', "root"), - Field.Case('do', "do"), - Field.Case('set_delegate', "set_delegate"), - Field.Case('remove_delegate', "remove_delegate"), - Field.Case('deposit', "deposit"), - Field.Case('stake', "stake"), - Field.Case('unstake', "unstake"), - Field.Case('finalize_unstake', "finalize_unstake"), - Field.Case('set_delegate_parameters', "set_delegate_parameters"), - Field.Case('custom_entrypoint', "custom_entrypoint"), - ]), - Field("parameter", "Parameter", [ - # `entrypoint` is set to make sure `parameter` is displayed with `unit` - Field.Case({'prim': 'Unit'}, "unit", entrypoint='entrypoint'), - Field.Case({'prim': 'Pair', 'args': [{'string': 'a'}, {'int': 1}]}, "basic"), - # More test about Micheline in micheline tests - ]), - Field("parameter", "Transfer tokens to", [ - Field.Case( - _FA2_TRANSFER_PARAMETER, - "fa2_transfer_to", - entrypoint='transfer', - destination=_FA2_KT1_TOKEN_ID_0_DESTINATION, - ), - Field.Case( - _FA2_TRANSFER_PARAMETER_SEQ_WRAPPED, - "fa2_transfer_to_seq_wrapped", - entrypoint='transfer', - destination=_FA2_KT1_TOKEN_ID_0_DESTINATION, - ), - Field.Case( - _FA2_TRANSFER_PARAMETER_TOKEN_ID_1, - "fa2_transfer_to_token_id_1", - entrypoint='transfer', - destination=_FA2_KT1_DESTINATION, - ), - ]), - Field("parameter", "Token Amount", [ - Field.Case( - _FA2_TRANSFER_PARAMETER, - "fa2_amount", - entrypoint='transfer', - destination=_FA2_KT1_TOKEN_ID_0_DESTINATION, - ), - Field.Case( - _FA2_TRANSFER_PARAMETER_SEQ_WRAPPED, - "fa2_amount_seq_wrapped", - entrypoint='transfer', - destination=_FA2_KT1_TOKEN_ID_0_DESTINATION, - ), - Field.Case( - _FA2_TRANSFER_PARAMETER_TOKEN_ID_1, - "fa2_amount_token_id_1", - entrypoint='transfer', - destination=_FA2_KT1_DESTINATION, - ), - ]), + fields: ClassVar[list[Field]] = [ + Field( + "amount", + "Amount", + [ + Field.Case(0, "0"), + Field.Case(1000, "1000"), + Field.Case(1000000, "1000000"), + Field.Case(1000000000, "1000000000"), + Field.Case(0xFFFFFFFFFFFFFFFF, "max"), # max uint64 + ], + ), + Field( + "destination", + "Destination", + [ + Field.Case("tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa", "tz1"), + Field.Case("tz2CJBeWWLsUDjVUDqGZL6od3DeBCNzYXrXk", "tz2"), + Field.Case("tz3fLwHKthqhTPK6Lar6CTXN1WbDETw1YpGB", "tz3"), + Field.Case("tz4DNQhMQaU9WMCVGwH6mQGGWqMNQHTjywDe", "tz4"), + Field.Case("KT18amZmM5W7qDWVt2pH6uj7sCEd3kbzLrHT", "kt1"), + Field.Case("tz1Kp8NCAN5WWwvkWkMmQQXMRe68iURmoQ8w", "long-hash"), + ], + ), + Field( + "entrypoint", + "Entrypoint", + [ + # `parameter` is set to make sure `entrypoint` is displayed with `default` + Field.Case("default", "default", parameter={"prim": "None"}), + Field.Case("root", "root"), + Field.Case("do", "do"), + Field.Case("set_delegate", "set_delegate"), + Field.Case("remove_delegate", "remove_delegate"), + Field.Case("deposit", "deposit"), + Field.Case("stake", "stake"), + Field.Case("unstake", "unstake"), + Field.Case("finalize_unstake", "finalize_unstake"), + Field.Case("set_delegate_parameters", "set_delegate_parameters"), + Field.Case("custom_entrypoint", "custom_entrypoint"), + ], + ), + Field( + "parameter", + "Parameter", + [ + # `entrypoint` is set to make sure `parameter` is displayed with `unit` + Field.Case({"prim": "Unit"}, "unit", entrypoint="entrypoint"), + Field.Case({"prim": "Pair", "args": [{"string": "a"}, {"int": 1}]}, "basic"), + # More test about Micheline in micheline tests + ], + ), + Field( + "parameter", + "Transfer tokens to", + [ + Field.Case( + _FA2_TRANSFER_PARAMETER, + "fa2_transfer_to", + entrypoint="transfer", + destination=_FA2_KT1_TOKEN_ID_0_DESTINATION, + ), + Field.Case( + _FA2_TRANSFER_PARAMETER_SEQ_WRAPPED, + "fa2_transfer_to_seq_wrapped", + entrypoint="transfer", + destination=_FA2_KT1_TOKEN_ID_0_DESTINATION, + ), + Field.Case( + _FA2_TRANSFER_PARAMETER_TOKEN_ID_1, + "fa2_transfer_to_token_id_1", + entrypoint="transfer", + destination=_FA2_KT1_DESTINATION, + ), + ], + ), + Field( + "parameter", + "Token Amount", + [ + Field.Case( + _FA2_TRANSFER_PARAMETER, + "fa2_amount", + entrypoint="transfer", + destination=_FA2_KT1_TOKEN_ID_0_DESTINATION, + ), + Field.Case( + _FA2_TRANSFER_PARAMETER_SEQ_WRAPPED, + "fa2_amount_seq_wrapped", + entrypoint="transfer", + destination=_FA2_KT1_TOKEN_ID_0_DESTINATION, + ), + Field.Case( + _FA2_TRANSFER_PARAMETER_TOKEN_ID_1, + "fa2_amount_token_id_1", + entrypoint="transfer", + destination=_FA2_KT1_DESTINATION, + ), + ], + ), ] diff --git a/tests/standalone/test_sign/operations/test_sign_transfer_ticket.py b/tests/standalone/test_sign/operations/test_sign_transfer_ticket.py index 1054203c4..7280159e8 100755 --- a/tests/standalone/test_sign/operations/test_sign_transfer_ticket.py +++ b/tests/standalone/test_sign/operations/test_sign_transfer_ticket.py @@ -16,8 +16,11 @@ """Gathering of tests related to Transfer-ticket operations.""" +from typing import ClassVar + from utils.message import TransferTicket -from .helper import Flow, Field, TestOperation, pytest_generate_tests # pylint: disable=unused-import + +from .helper import Field, Flow, TestOperation class TestTransferTicket(TestOperation): @@ -27,53 +30,80 @@ class TestTransferTicket(TestOperation): def op_class(self): return TransferTicket - flows = [Flow('basic', ticket_amount=5)] + flows: ClassVar[list[Flow]] = [Flow("basic", ticket_amount=5)] - fields = [ - Field("ticket_contents", "Contents", [ - Field.Case({'prim': 'Unit'}, "unit"), - Field.Case({'prim': 'Pair', 'args': [{'string': 'a'}, {'int': 1}]}, "basic"), - # More test about Micheline in micheline tests - ]), - Field("ticket_ty", "Type", [ - Field.Case({'prim': 'unit'}, "unit"), - Field.Case({'prim': 'or', 'args': [{'prim': 'int'}, {'prim': 'string'}]}, "basic"), - # More test about Micheline in micheline tests - ]), - Field("ticket_ticketer", "Ticketer", [ - Field.Case('tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa', "tz1"), - Field.Case('tz2CJBeWWLsUDjVUDqGZL6od3DeBCNzYXrXk', "tz2"), - Field.Case('tz3fLwHKthqhTPK6Lar6CTXN1WbDETw1YpGB', "tz3"), - Field.Case('tz4DNQhMQaU9WMCVGwH6mQGGWqMNQHTjywDe', "tz4"), - Field.Case('KT18amZmM5W7qDWVt2pH6uj7sCEd3kbzLrHT', "kt1"), - Field.Case('tz1Kp8NCAN5WWwvkWkMmQQXMRe68iURmoQ8w', "long-hash"), - ]), - Field("ticket_amount", "Amount", [ - Field.Case(0, "0"), - Field.Case(1000, "1000"), - Field.Case(1000000, "1000000"), - Field.Case(1000000000, "1000000000"), - Field.Case(0xFFFFFFFFFFFFFFFF, "max"), # max uint64 - ]), - Field("destination", "Destination", [ - Field.Case('tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa', "tz1"), - Field.Case('tz2CJBeWWLsUDjVUDqGZL6od3DeBCNzYXrXk', "tz2"), - Field.Case('tz3fLwHKthqhTPK6Lar6CTXN1WbDETw1YpGB', "tz3"), - Field.Case('tz4DNQhMQaU9WMCVGwH6mQGGWqMNQHTjywDe', "tz4"), - Field.Case('KT18amZmM5W7qDWVt2pH6uj7sCEd3kbzLrHT', "kt1"), - Field.Case('tz1Kp8NCAN5WWwvkWkMmQQXMRe68iURmoQ8w', "long-hash"), - ]), - Field("entrypoint", "Entrypoint", [ - Field.Case('default', "default"), - Field.Case('root', "root"), - Field.Case('do', "do"), - Field.Case('set_delegate', "set_delegate"), - Field.Case('remove_delegate', "remove_delegate"), - Field.Case('deposit', "deposit"), - Field.Case('stake', "stake"), - Field.Case('unstake', "unstake"), - Field.Case('finalize_unstake', "finalize_unstake"), - Field.Case('set_delegate_parameters', "set_delegate_parameters"), - Field.Case('custom_entrypoint', "custom_entrypoint"), - ]), + fields: ClassVar[list[Field]] = [ + Field( + "ticket_contents", + "Contents", + [ + Field.Case({"prim": "Unit"}, "unit"), + Field.Case({"prim": "Pair", "args": [{"string": "a"}, {"int": 1}]}, "basic"), + # More test about Micheline in micheline tests + ], + ), + Field( + "ticket_ty", + "Type", + [ + Field.Case({"prim": "unit"}, "unit"), + Field.Case( + {"prim": "or", "args": [{"prim": "int"}, {"prim": "string"}]}, + "basic", + ), + # More test about Micheline in micheline tests + ], + ), + Field( + "ticket_ticketer", + "Ticketer", + [ + Field.Case("tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa", "tz1"), + Field.Case("tz2CJBeWWLsUDjVUDqGZL6od3DeBCNzYXrXk", "tz2"), + Field.Case("tz3fLwHKthqhTPK6Lar6CTXN1WbDETw1YpGB", "tz3"), + Field.Case("tz4DNQhMQaU9WMCVGwH6mQGGWqMNQHTjywDe", "tz4"), + Field.Case("KT18amZmM5W7qDWVt2pH6uj7sCEd3kbzLrHT", "kt1"), + Field.Case("tz1Kp8NCAN5WWwvkWkMmQQXMRe68iURmoQ8w", "long-hash"), + ], + ), + Field( + "ticket_amount", + "Amount", + [ + Field.Case(0, "0"), + Field.Case(1000, "1000"), + Field.Case(1000000, "1000000"), + Field.Case(1000000000, "1000000000"), + Field.Case(0xFFFFFFFFFFFFFFFF, "max"), # max uint64 + ], + ), + Field( + "destination", + "Destination", + [ + Field.Case("tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa", "tz1"), + Field.Case("tz2CJBeWWLsUDjVUDqGZL6od3DeBCNzYXrXk", "tz2"), + Field.Case("tz3fLwHKthqhTPK6Lar6CTXN1WbDETw1YpGB", "tz3"), + Field.Case("tz4DNQhMQaU9WMCVGwH6mQGGWqMNQHTjywDe", "tz4"), + Field.Case("KT18amZmM5W7qDWVt2pH6uj7sCEd3kbzLrHT", "kt1"), + Field.Case("tz1Kp8NCAN5WWwvkWkMmQQXMRe68iURmoQ8w", "long-hash"), + ], + ), + Field( + "entrypoint", + "Entrypoint", + [ + Field.Case("default", "default"), + Field.Case("root", "root"), + Field.Case("do", "do"), + Field.Case("set_delegate", "set_delegate"), + Field.Case("remove_delegate", "remove_delegate"), + Field.Case("deposit", "deposit"), + Field.Case("stake", "stake"), + Field.Case("unstake", "unstake"), + Field.Case("finalize_unstake", "finalize_unstake"), + Field.Case("set_delegate_parameters", "set_delegate_parameters"), + Field.Case("custom_entrypoint", "custom_entrypoint"), + ], + ), ] diff --git a/tests/standalone/test_sign/operations/test_staking2_protocol.py b/tests/standalone/test_sign/operations/test_staking2_protocol.py index b59626b2a..66fd9235b 100644 --- a/tests/standalone/test_sign/operations/test_staking2_protocol.py +++ b/tests/standalone/test_sign/operations/test_staking2_protocol.py @@ -14,8 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Regression tests for Staking 2.0 wire encoding (Paris+ pseudo-operations). -""" +"""Regression tests for Staking 2.0 wire encoding (Paris+ pseudo-operations).""" from utils.message import Default, Transaction from utils.tezos_encoding import forge_entrypoint @@ -39,5 +38,5 @@ def test_stake_entrypoint_uses_builtin_not_length_prefixed_name(): parameter={"prim": "Unit"}, ) raw = bytes(op) - # Legacy test harness mistakenly used arbitrary name encoding for builtins 6–9. + # Legacy test harness mistakenly used arbitrary name encoding for builtins 6-9. assert b"\xff\x05stake" not in raw diff --git a/tests/standalone/test_sign/test_apdu_sign.py b/tests/standalone/test_sign/test_apdu_sign.py index bf7555112..26674ad7c 100644 --- a/tests/standalone/test_sign/test_apdu_sign.py +++ b/tests/standalone/test_sign/test_apdu_sign.py @@ -19,9 +19,7 @@ from pathlib import Path import pytest - from ragger.navigator import NavInsID - from utils.account import Account from utils.backend import StatusCode, TezosBackend from utils.message import Transaction @@ -30,10 +28,10 @@ @pytest.mark.parametrize("with_hash", [True, False]) def test_sign( - backend: TezosBackend, - tezos_navigator: TezosNavigator, - account: Account, - with_hash: bool + backend: TezosBackend, + tezos_navigator: TezosNavigator, + account: Account, + with_hash: bool, ): """Check signing with or without getting hash""" @@ -42,17 +40,14 @@ def test_sign( with backend.sign(account, message, with_hash=with_hash) as result: tezos_navigator.accept_sign() - account.check_signature( - message=message, - with_hash=with_hash, - data=result.value - ) + account.check_signature(message=message, with_hash=with_hash, data=result.value) + def test_reject_operation( - backend: TezosBackend, - tezos_navigator: TezosNavigator, - account: Account, - snapshot_dir: Path + backend: TezosBackend, + tezos_navigator: TezosNavigator, + account: Account, + snapshot_dir: Path, ): """Check reject transaction""" @@ -62,12 +57,13 @@ def test_reject_operation( with backend.sign(account, message, with_hash=True): tezos_navigator.reject_sign(snap_path=snapshot_dir) + @pytest.mark.use_on_device("touch") def test_reject_operation_at_start( - backend: TezosBackend, - tezos_navigator: TezosNavigator, - account: Account, - snapshot_dir: Path + backend: TezosBackend, + tezos_navigator: TezosNavigator, + account: Account, + snapshot_dir: Path, ): """Check reject at first screen.""" @@ -83,15 +79,16 @@ def test_reject_operation_at_start( ], screen_change_before_first_instruction=True, screen_change_after_last_instruction=False, - snap_path=snapshot_dir + snap_path=snapshot_dir, ) + @pytest.mark.use_on_device("touch") def test_reject_operation_at_fields( - backend: TezosBackend, - tezos_navigator: TezosNavigator, - account: Account, - snapshot_dir: Path + backend: TezosBackend, + tezos_navigator: TezosNavigator, + account: Account, + snapshot_dir: Path, ): """Check reject at fields.""" @@ -108,14 +105,11 @@ def test_reject_operation_at_fields( ], screen_change_before_first_instruction=True, screen_change_after_last_instruction=False, - snap_path=snapshot_dir + snap_path=snapshot_dir, ) -def test_sign_with_small_packet( - backend: TezosBackend, - tezos_navigator: TezosNavigator, - account: Account -): + +def test_sign_with_small_packet(backend: TezosBackend, tezos_navigator: TezosNavigator, account: Account): """Check signing using small packet instead of full size packets""" tezos_navigator.toggle_expert_mode() @@ -125,8 +119,4 @@ def test_sign_with_small_packet( with backend.sign(account, message, apdu_size=10) as result: tezos_navigator.accept_sign() - account.check_signature( - message=message, - with_hash=False, - data=result.value - ) + account.check_signature(message=message, with_hash=False, data=result.value) diff --git a/tests/standalone/test_sign/test_blindsign.py b/tests/standalone/test_sign/test_blindsign.py index 5b29a1c4e..4b0f1f8fc 100644 --- a/tests/standalone/test_sign/test_blindsign.py +++ b/tests/standalone/test_sign/test_blindsign.py @@ -17,104 +17,94 @@ """Gathering of tests related to Blindsign.""" from pathlib import Path -from typing import List, Union import pytest - from ledgered.devices import Device, DeviceType from ragger.navigator import BaseNavInsID, NavIns, NavInsID - from utils.account import Account -from utils.backend import TezosBackend, StatusCode +from utils.backend import StatusCode, TezosBackend from utils.message import ( + Delegation, MichelineExpr, - Proposals, OperationGroup, - Reveal, - Transaction, - Delegation, + Proposals, RegisterGlobalConstant, + Reveal, + ScRollupAddMessage, SetDepositLimit, - ScRollupAddMessage + Transaction, ) from utils.navigator import TezosNavigator - ### Too long operation ### -BASIC_OPERATION = OperationGroup([ - Reveal( - source = 'tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa', - fee = 1000000, - counter = 11, - gas_limit = 1, - storage_limit = 4, - public_key = 'p2pk66m3NQsd4n6LJWe9WMwx9WHeXwKmBaMwXX92WkMQCR99zmwk2PM' - ), - Transaction( - source = 'tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa', - fee = 2000000, - counter = 12, - gas_limit = 1, - storage_limit = 7, - destination = 'tz3XMQscBFM9vPmpbYMavMmwxRMUWvWGZMQQ', - amount = 3000000, - entrypoint = 'update_config', - parameter = {'prim': 'Pair', 'args': [ {'int': 5}, {'prim': 'True'} ]} - ), - Delegation( - source = 'tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa', - fee = 3000000, - counter = 13, - gas_limit = 1, - storage_limit = 5, - delegate = 'tz2W3Tvcm64GjcV2bipUynnEsctLFz5Z6yRa' - ), - ScRollupAddMessage( - source = 'tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa', - fee = 4000000, - counter = 14, - gas_limit = 1, - storage_limit = 6, - message = [ - bytes.fromhex('6d65737361676530'), - bytes.fromhex('6d65737361676531'), - bytes.fromhex('6d65737361676532'), - bytes.fromhex('6d65737361676533'), - bytes.fromhex('6d65737361676534'), - bytes.fromhex('6d65737361676535'), - bytes.fromhex('6d65737361676536'), - bytes.fromhex('6d65737361676537'), - bytes.fromhex('6d65737361676538'), - bytes.fromhex('6d65737361676539'), - bytes.fromhex('6d6573736167653130'), - bytes.fromhex('6d6573736167653131'), - bytes.fromhex('6d6573736167653132'), - bytes.fromhex('6d6573736167653133'), - bytes.fromhex('6d6573736167653134'), - bytes.fromhex('6d6573736167653135'), - bytes.fromhex('6d6573736167653136'), - bytes.fromhex('6d6573736167653137'), - bytes.fromhex('6d6573736167653138'), - bytes.fromhex('6d6573736167653139') - ] - ), - SetDepositLimit( - source = 'tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa', - fee = 1000000, - counter = 15, - gas_limit = 1, - storage_limit = 3, - limit = 10000000 - ) -]) +BASIC_OPERATION = OperationGroup( + [ + Reveal( + source="tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa", + fee=1000000, + counter=11, + gas_limit=1, + storage_limit=4, + public_key="p2pk66m3NQsd4n6LJWe9WMwx9WHeXwKmBaMwXX92WkMQCR99zmwk2PM", + ), + Transaction( + source="tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa", + fee=2000000, + counter=12, + gas_limit=1, + storage_limit=7, + destination="tz3XMQscBFM9vPmpbYMavMmwxRMUWvWGZMQQ", + amount=3000000, + entrypoint="update_config", + parameter={"prim": "Pair", "args": [{"int": 5}, {"prim": "True"}]}, + ), + Delegation( + source="tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa", + fee=3000000, + counter=13, + gas_limit=1, + storage_limit=5, + delegate="tz2W3Tvcm64GjcV2bipUynnEsctLFz5Z6yRa", + ), + ScRollupAddMessage( + source="tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa", + fee=4000000, + counter=14, + gas_limit=1, + storage_limit=6, + message=[ + bytes.fromhex("6d65737361676530"), + bytes.fromhex("6d65737361676531"), + bytes.fromhex("6d65737361676532"), + bytes.fromhex("6d65737361676533"), + bytes.fromhex("6d65737361676534"), + bytes.fromhex("6d65737361676535"), + bytes.fromhex("6d65737361676536"), + bytes.fromhex("6d65737361676537"), + bytes.fromhex("6d65737361676538"), + bytes.fromhex("6d65737361676539"), + bytes.fromhex("6d6573736167653130"), + bytes.fromhex("6d6573736167653131"), + bytes.fromhex("6d6573736167653132"), + bytes.fromhex("6d6573736167653133"), + bytes.fromhex("6d6573736167653134"), + bytes.fromhex("6d6573736167653135"), + bytes.fromhex("6d6573736167653136"), + bytes.fromhex("6d6573736167653137"), + bytes.fromhex("6d6573736167653138"), + bytes.fromhex("6d6573736167653139"), + ], + ), + SetDepositLimit( + source="tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa", fee=1000000, counter=15, gas_limit=1, storage_limit=3, limit=10000000 + ), + ] +) + def test_sign_basic_too_long_operation( - backend: TezosBackend, - device: Device, - tezos_navigator: TezosNavigator, - account: Account, - snapshot_dir: Path + backend: TezosBackend, device: Device, tezos_navigator: TezosNavigator, account: Account, snapshot_dir: Path ): """Check sign too long operation""" @@ -125,26 +115,17 @@ def test_sign_basic_too_long_operation( with backend.sign(account, message, with_hash=True) as result: if device.is_nano: - tezos_navigator.accept_sign_blindsign_risk( - snap_path=snapshot_dir / "clear_n_too_long_warning" - ) + tezos_navigator.accept_sign_blindsign_risk(snap_path=snapshot_dir / "clear_n_too_long_warning") else: tezos_navigator.skip_sign(snap_path=snapshot_dir / "skip") tezos_navigator.accept_sign_blindsign_risk(snap_path=snapshot_dir / "blindsign_warning") tezos_navigator.accept_sign(snap_path=snapshot_dir / "summary") - account.check_signature( - message=message, - with_hash=True, - data=result.value - ) + account.check_signature(message=message, with_hash=True, data=result.value) + def test_reject_basic_too_long_operation_at_warning( - backend: TezosBackend, - device: Device, - tezos_navigator: TezosNavigator, - account: Account, - snapshot_dir: Path + backend: TezosBackend, device: Device, tezos_navigator: TezosNavigator, account: Account, snapshot_dir: Path ): """Check reject too long operation at warning""" @@ -154,21 +135,14 @@ def test_reject_basic_too_long_operation_at_warning( with StatusCode.REJECT.expected(): with backend.sign(account, BASIC_OPERATION): if device.is_nano: - tezos_navigator.refuse_sign_blindsign_risk( - snap_path=snapshot_dir / "clear_n_too_long_warning" - ) + tezos_navigator.refuse_sign_blindsign_risk(snap_path=snapshot_dir / "clear_n_too_long_warning") else: tezos_navigator.skip_sign(snap_path=snapshot_dir / "skip") - tezos_navigator.refuse_sign_blindsign_risk( - snap_path=snapshot_dir / "blindsign_warning" - ) + tezos_navigator.refuse_sign_blindsign_risk(snap_path=snapshot_dir / "blindsign_warning") + def test_reject_basic_too_long_operation_at_summary( - backend: TezosBackend, - device: Device, - tezos_navigator: TezosNavigator, - account: Account, - snapshot_dir: Path + backend: TezosBackend, device: Device, tezos_navigator: TezosNavigator, account: Account, snapshot_dir: Path ): """Check reject too long operation at summary""" @@ -178,23 +152,15 @@ def test_reject_basic_too_long_operation_at_summary( with StatusCode.REJECT.expected(): with backend.sign(account, BASIC_OPERATION): if device.is_nano: - tezos_navigator.accept_sign_blindsign_risk( - snap_path=snapshot_dir / "clear_n_too_long_warning" - ) + tezos_navigator.accept_sign_blindsign_risk(snap_path=snapshot_dir / "clear_n_too_long_warning") else: tezos_navigator.skip_sign(snap_path=snapshot_dir / "skip") - tezos_navigator.accept_sign_blindsign_risk( - snap_path=snapshot_dir / "blindsign_warning" - ) + tezos_navigator.accept_sign_blindsign_risk(snap_path=snapshot_dir / "blindsign_warning") tezos_navigator.reject_sign(snap_path=snapshot_dir / "summary") + @pytest.mark.use_on_device("touch") -def test_reject_at_skip( - backend: TezosBackend, - tezos_navigator: TezosNavigator, - account: Account, - snapshot_dir: Path -): +def test_reject_at_skip(backend: TezosBackend, tezos_navigator: TezosNavigator, account: Account, snapshot_dir: Path): """Check reject at skip.""" tezos_navigator.toggle_expert_mode() @@ -213,123 +179,113 @@ def test_reject_at_skip( ### Different kind of too long operation ### + def test_sign_too_long_operation_with_only_transactions( - backend: TezosBackend, - device: Device, - tezos_navigator: TezosNavigator, - account: Account, - snapshot_dir: Path + backend: TezosBackend, device: Device, tezos_navigator: TezosNavigator, account: Account, snapshot_dir: Path ): """Check sign too long operation that contains only transaction""" - message = OperationGroup([ - Transaction( - source = 'tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa', - fee = 0, - counter = 11, - gas_limit = 1, - storage_limit = 0, - destination = 'tz1er74kx433vTtpYddGsf3dDt5piBZeeHyQ', - amount = 10000000 - ), - Transaction( - source = 'tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa', - fee = 1000000, - counter = 12, - gas_limit = 1, - storage_limit = 1, - destination = 'tz2PPZ2WN4j92Rdx4NM7oW3HAp3x825HUyac', - amount = 1000000 - ), - Transaction( - source = 'tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa', - fee = 2000000, - counter = 13, - gas_limit = 1, - storage_limit = 2, - destination = 'tz1Kp8NCAN5WWwvkWkMmQQXMRe68iURmoQ8w', - amount = 2000000 - ), - Transaction( - source = 'tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa', - fee = 3000000, - counter = 14, - gas_limit = 1, - storage_limit = 3, - destination = 'tz3fLwHKthqhTPK6Lar6CTXN1WbDETw1YpGB', - amount = 3000000 - ), - Transaction( - source = 'tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa', - fee = 4000000, - counter = 15, - gas_limit = 1, - storage_limit = 4, - destination = 'tz3eydffbLkjdVb8zx42BvxpGV87zaRnqL3r', - amount = 4000000 - ), - Transaction( - source = 'tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa', - fee = 5000000, - counter = 16, - gas_limit = 1, - storage_limit = 5, - destination = 'tz2JPgTWZZpxZZLqHMfS69UAy1UHm4Aw5iHu', - amount = 5000000 - ) - ]) + message = OperationGroup( + [ + Transaction( + source="tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa", + fee=0, + counter=11, + gas_limit=1, + storage_limit=0, + destination="tz1er74kx433vTtpYddGsf3dDt5piBZeeHyQ", + amount=10000000, + ), + Transaction( + source="tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa", + fee=1000000, + counter=12, + gas_limit=1, + storage_limit=1, + destination="tz2PPZ2WN4j92Rdx4NM7oW3HAp3x825HUyac", + amount=1000000, + ), + Transaction( + source="tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa", + fee=2000000, + counter=13, + gas_limit=1, + storage_limit=2, + destination="tz1Kp8NCAN5WWwvkWkMmQQXMRe68iURmoQ8w", + amount=2000000, + ), + Transaction( + source="tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa", + fee=3000000, + counter=14, + gas_limit=1, + storage_limit=3, + destination="tz3fLwHKthqhTPK6Lar6CTXN1WbDETw1YpGB", + amount=3000000, + ), + Transaction( + source="tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa", + fee=4000000, + counter=15, + gas_limit=1, + storage_limit=4, + destination="tz3eydffbLkjdVb8zx42BvxpGV87zaRnqL3r", + amount=4000000, + ), + Transaction( + source="tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa", + fee=5000000, + counter=16, + gas_limit=1, + storage_limit=5, + destination="tz2JPgTWZZpxZZLqHMfS69UAy1UHm4Aw5iHu", + amount=5000000, + ), + ] + ) tezos_navigator.toggle_expert_mode() tezos_navigator.toggle_blindsign() with backend.sign(account, message, with_hash=True) as result: if device.is_nano: - tezos_navigator.accept_sign_blindsign_risk( - snap_path=snapshot_dir / "clear_n_too_long_warning" - ) + tezos_navigator.accept_sign_blindsign_risk(snap_path=snapshot_dir / "clear_n_too_long_warning") else: tezos_navigator.skip_sign(snap_path=snapshot_dir / "skip") tezos_navigator.accept_sign_blindsign_risk(snap_path=snapshot_dir / "blindsign_warning") tezos_navigator.accept_sign(snap_path=snapshot_dir / "summary") - account.check_signature( - message=message, - with_hash=True, - data=result.value - ) + account.check_signature(message=message, with_hash=True, data=result.value) + def test_sign_too_long_operation_without_fee_or_amount( - backend: TezosBackend, - device: Device, - tezos_navigator: TezosNavigator, - account: Account, - snapshot_dir: Path + backend: TezosBackend, device: Device, tezos_navigator: TezosNavigator, account: Account, snapshot_dir: Path ): """Check sign too long operation that doesn't have fees or amount""" message = Proposals( - source = 'tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa', - proposals = [ - 'ProtoDemoNoopsDemoNoopsDemoNoopsDemoNoopsDemo6XBoYp', - 'ProtoGenesisGenesisGenesisGenesisGenesisGenesk612im', - 'PrihK96nBAFSxVL1GLJTVhu9YnzkMFiBeuJRPA8NwuZVZCE1L6i', - 'Ps9mPmXaRzmzk35gbAYNCAw6UXdE2qoABTHbN2oEEc1qM7CwT9P', - 'PsBabyM1eUXZseaJdmXFApDSBqj8YBfwELoxZHHW77EMcAbbwAS', - 'PsCARTHAGazKbHtnKfLzQg3kms52kSRpgnDY982a9oYsSXRLQEb', - 'PsDELPH1Kxsxt8f9eWbxQeRxkjfbxoqM52jvs5Y5fBxWWh4ifpo', - 'PtEdo2ZkT9oKpimTah6x2embF25oss54njMuPzkJTEi5RqfdZFA', - 'PsFLorenaUUuikDWvMDr6fGBRG8kt3e3D3fHoXK1j1BFRxeSH4i', - 'PtGRANADsDU8R9daYKAgWnQYAJ64omN1o3KMGVCykShA97vQbvV', - 'PtHangz2aRngywmSRGGvrcTyMbbdpWdpFKuS4uMWxg2RaH9i1qx', - 'Psithaca2MLRFYargivpo7YvUr7wUDqyxrdhC5CQq78mRvimz6A', - 'PtJakart2xVj7pYXJBXrqHgd82rdkLey5ZeeGwDgPp9rhQUbSqY', - 'PtKathmankSpLLDALzWw7CGD2j2MtyveTwboEYokqUCP4a1LxMg', - 'PtLimaPtLMwfNinJi9rCfDPWea8dFgTZ1MeJ9f1m2SRic6ayiwW', - 'PtMumbai2TmsJHNGRkD8v8YDbtao7BLUC3wjASn1inAKLFCjaH1', - 'PtNairobiyssHuh87hEhfVBGCVrK3WnS8Z2FT4ymB5tAa4r1nQf', - 'ProxfordYmVfjWnRcgjWH36fW6PArwqykTFzotUxRs6gmTcZDuH', - 'PtParisBxoLz5gzMmn3d9WBQNoPSZakgnkMC2VNuQ3KXfUtUQeZ', - 'ProtoALphaALphaALphaALphaALphaALphaALphaALphaDdp3zK' + source="tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa", + proposals=[ + "ProtoDemoNoopsDemoNoopsDemoNoopsDemoNoopsDemo6XBoYp", + "ProtoGenesisGenesisGenesisGenesisGenesisGenesk612im", + "PrihK96nBAFSxVL1GLJTVhu9YnzkMFiBeuJRPA8NwuZVZCE1L6i", + "Ps9mPmXaRzmzk35gbAYNCAw6UXdE2qoABTHbN2oEEc1qM7CwT9P", + "PsBabyM1eUXZseaJdmXFApDSBqj8YBfwELoxZHHW77EMcAbbwAS", + "PsCARTHAGazKbHtnKfLzQg3kms52kSRpgnDY982a9oYsSXRLQEb", + "PsDELPH1Kxsxt8f9eWbxQeRxkjfbxoqM52jvs5Y5fBxWWh4ifpo", + "PtEdo2ZkT9oKpimTah6x2embF25oss54njMuPzkJTEi5RqfdZFA", + "PsFLorenaUUuikDWvMDr6fGBRG8kt3e3D3fHoXK1j1BFRxeSH4i", + "PtGRANADsDU8R9daYKAgWnQYAJ64omN1o3KMGVCykShA97vQbvV", + "PtHangz2aRngywmSRGGvrcTyMbbdpWdpFKuS4uMWxg2RaH9i1qx", + "Psithaca2MLRFYargivpo7YvUr7wUDqyxrdhC5CQq78mRvimz6A", + "PtJakart2xVj7pYXJBXrqHgd82rdkLey5ZeeGwDgPp9rhQUbSqY", + "PtKathmankSpLLDALzWw7CGD2j2MtyveTwboEYokqUCP4a1LxMg", + "PtLimaPtLMwfNinJi9rCfDPWea8dFgTZ1MeJ9f1m2SRic6ayiwW", + "PtMumbai2TmsJHNGRkD8v8YDbtao7BLUC3wjASn1inAKLFCjaH1", + "PtNairobiyssHuh87hEhfVBGCVrK3WnS8Z2FT4ymB5tAa4r1nQf", + "ProxfordYmVfjWnRcgjWH36fW6PArwqykTFzotUxRs6gmTcZDuH", + "PtParisBxoLz5gzMmn3d9WBQNoPSZakgnkMC2VNuQ3KXfUtUQeZ", + "ProtoALphaALphaALphaALphaALphaALphaALphaALphaDdp3zK", ], - period = 32 + period=32, ) tezos_navigator.toggle_expert_mode() @@ -337,79 +293,72 @@ def test_sign_too_long_operation_without_fee_or_amount( with backend.sign(account, message, with_hash=True) as result: if device.is_nano: - tezos_navigator.accept_sign_blindsign_risk( - snap_path=snapshot_dir / "clear_n_too_long_warning" - ) + tezos_navigator.accept_sign_blindsign_risk(snap_path=snapshot_dir / "clear_n_too_long_warning") else: tezos_navigator.skip_sign(snap_path=snapshot_dir / "skip") tezos_navigator.accept_sign_blindsign_risk(snap_path=snapshot_dir / "blindsign_warning") tezos_navigator.accept_sign(snap_path=snapshot_dir / "summary") - account.check_signature( - message=message, - with_hash=True, - data=result.value - ) + account.check_signature(message=message, with_hash=True, data=result.value) ### Too long operation containing a too large number ### -OPERATION_WITH_TOO_LARGE = OperationGroup([ - ScRollupAddMessage( - source = 'tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa', - fee = 4000000, - counter = 11, - gas_limit = 1, - storage_limit = 6, - message = [ - bytes.fromhex('6d65737361676530'), - bytes.fromhex('6d65737361676531'), - bytes.fromhex('6d65737361676532'), - bytes.fromhex('6d65737361676533'), - bytes.fromhex('6d65737361676534'), - bytes.fromhex('6d65737361676535'), - bytes.fromhex('6d65737361676536'), - bytes.fromhex('6d65737361676537'), - bytes.fromhex('6d65737361676538'), - bytes.fromhex('6d65737361676539'), - bytes.fromhex('6d6573736167653130'), - bytes.fromhex('6d6573736167653131'), - bytes.fromhex('6d6573736167653132'), - bytes.fromhex('6d6573736167653133'), - bytes.fromhex('6d6573736167653134'), - bytes.fromhex('6d6573736167653135'), - bytes.fromhex('6d6573736167653136'), - bytes.fromhex('6d6573736167653137'), - bytes.fromhex('6d6573736167653138'), - bytes.fromhex('6d6573736167653139'), - bytes.fromhex('6d6573736167653230'), - bytes.fromhex('6d6573736167653231'), - bytes.fromhex('6d6573736167653232'), - bytes.fromhex('6d6573736167653233'), - bytes.fromhex('6d6573736167653234'), - bytes.fromhex('6d6573736167653235'), - bytes.fromhex('6d6573736167653236'), - bytes.fromhex('6d6573736167653237'), - bytes.fromhex('6d6573736167653238'), - bytes.fromhex('6d6573736167653239') - ] - ), - RegisterGlobalConstant( - source = 'tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa', - fee = 5000000, - counter = 12, - gas_limit = 1, - storage_limit = 3, - value = {'int': 115792089237316195423570985008687907853269984665640564039457584007913129639936} # pylint: disable=line-too-long - ) -]) +OPERATION_WITH_TOO_LARGE = OperationGroup( + [ + ScRollupAddMessage( + source="tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa", + fee=4000000, + counter=11, + gas_limit=1, + storage_limit=6, + message=[ + bytes.fromhex("6d65737361676530"), + bytes.fromhex("6d65737361676531"), + bytes.fromhex("6d65737361676532"), + bytes.fromhex("6d65737361676533"), + bytes.fromhex("6d65737361676534"), + bytes.fromhex("6d65737361676535"), + bytes.fromhex("6d65737361676536"), + bytes.fromhex("6d65737361676537"), + bytes.fromhex("6d65737361676538"), + bytes.fromhex("6d65737361676539"), + bytes.fromhex("6d6573736167653130"), + bytes.fromhex("6d6573736167653131"), + bytes.fromhex("6d6573736167653132"), + bytes.fromhex("6d6573736167653133"), + bytes.fromhex("6d6573736167653134"), + bytes.fromhex("6d6573736167653135"), + bytes.fromhex("6d6573736167653136"), + bytes.fromhex("6d6573736167653137"), + bytes.fromhex("6d6573736167653138"), + bytes.fromhex("6d6573736167653139"), + bytes.fromhex("6d6573736167653230"), + bytes.fromhex("6d6573736167653231"), + bytes.fromhex("6d6573736167653232"), + bytes.fromhex("6d6573736167653233"), + bytes.fromhex("6d6573736167653234"), + bytes.fromhex("6d6573736167653235"), + bytes.fromhex("6d6573736167653236"), + bytes.fromhex("6d6573736167653237"), + bytes.fromhex("6d6573736167653238"), + bytes.fromhex("6d6573736167653239"), + ], + ), + RegisterGlobalConstant( + source="tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa", + fee=5000000, + counter=12, + gas_limit=1, + storage_limit=3, + value={"int": 115792089237316195423570985008687907853269984665640564039457584007913129639936}, + ), + ] +) + def test_sign_too_long_operation_with_too_large( - backend: TezosBackend, - device: Device, - tezos_navigator: TezosNavigator, - account: Account, - snapshot_dir: Path + backend: TezosBackend, device: Device, tezos_navigator: TezosNavigator, account: Account, snapshot_dir: Path ): """Check sign too long operation that will also fail the parsing""" @@ -420,27 +369,18 @@ def test_sign_too_long_operation_with_too_large( with backend.sign(account, message, with_hash=True) as result: if device.is_nano: - tezos_navigator.accept_sign_error_risk( - snap_path=snapshot_dir / "clear_n_too_large_warning" - ) + tezos_navigator.accept_sign_error_risk(snap_path=snapshot_dir / "clear_n_too_large_warning") else: tezos_navigator.skip_sign(snap_path=snapshot_dir / "skip") tezos_navigator.accept_sign_error_risk(snap_path=snapshot_dir / "too_large_warning") tezos_navigator.accept_sign_blindsign_risk(snap_path=snapshot_dir / "blindsign_warning") tezos_navigator.accept_sign(snap_path=snapshot_dir / "blindsigning") - account.check_signature( - message=message, - with_hash=True, - data=result.value - ) + account.check_signature(message=message, with_hash=True, data=result.value) + def test_reject_too_long_operation_with_too_large_at_too_large_warning( - backend: TezosBackend, - device: Device, - tezos_navigator: TezosNavigator, - account: Account, - snapshot_dir: Path + backend: TezosBackend, device: Device, tezos_navigator: TezosNavigator, account: Account, snapshot_dir: Path ): """Check reject too long operation that will also fail the parsing at too large warning""" @@ -450,19 +390,14 @@ def test_reject_too_long_operation_with_too_large_at_too_large_warning( with StatusCode.PARSE_ERROR.expected(): with backend.sign(account, OPERATION_WITH_TOO_LARGE): if device.is_nano: - tezos_navigator.refuse_sign_error_risk( - snap_path=snapshot_dir / "clear_n_too_large_warning" - ) + tezos_navigator.refuse_sign_error_risk(snap_path=snapshot_dir / "clear_n_too_large_warning") else: tezos_navigator.skip_sign(snap_path=snapshot_dir / "skip") tezos_navigator.refuse_sign_error_risk(snap_path=snapshot_dir / "too_large_warning") + def test_reject_too_long_operation_with_too_large_at_blindsigning( - backend: TezosBackend, - device: Device, - tezos_navigator: TezosNavigator, - account: Account, - snapshot_dir: Path + backend: TezosBackend, device: Device, tezos_navigator: TezosNavigator, account: Account, snapshot_dir: Path ): """Check reject too long operation that will also fail the parsing at blindsigning""" @@ -477,23 +412,17 @@ def test_reject_too_long_operation_with_too_large_at_blindsigning( with error.expected(): with backend.sign(account, OPERATION_WITH_TOO_LARGE): if device.is_nano: - tezos_navigator.accept_sign_error_risk( - snap_path=snapshot_dir / "clear_n_too_large_warning" - ) + tezos_navigator.accept_sign_error_risk(snap_path=snapshot_dir / "clear_n_too_large_warning") else: tezos_navigator.skip_sign(snap_path=snapshot_dir / "skip") tezos_navigator.accept_sign_error_risk(snap_path=snapshot_dir / "too_large_warning") - tezos_navigator.accept_sign_blindsign_risk( - snap_path=snapshot_dir / "blindsigning_warning" - ) + tezos_navigator.accept_sign_blindsign_risk(snap_path=snapshot_dir / "blindsigning_warning") tezos_navigator.reject_sign(snap_path=snapshot_dir / "blindsigning") + @pytest.mark.use_on_device("touch") def test_reject_too_long_operation_with_too_large_at_blindsigning_warning( - backend: TezosBackend, - tezos_navigator: TezosNavigator, - account: Account, - snapshot_dir: Path + backend: TezosBackend, tezos_navigator: TezosNavigator, account: Account, snapshot_dir: Path ): """Check reject too long operation that will also fail the parsing at blindsigning""" @@ -504,27 +433,25 @@ def test_reject_too_long_operation_with_too_large_at_blindsigning_warning( with backend.sign(account, OPERATION_WITH_TOO_LARGE): tezos_navigator.skip_sign(snap_path=snapshot_dir / "skip") tezos_navigator.accept_sign_error_risk(snap_path=snapshot_dir / "too_large_warning") - tezos_navigator.refuse_sign_blindsign_risk( - snap_path=snapshot_dir / "blindsigning_warning" - ) + tezos_navigator.refuse_sign_blindsign_risk(snap_path=snapshot_dir / "blindsigning_warning") + def test_blindsign_too_deep( - backend: TezosBackend, - device: Device, - tezos_navigator: TezosNavigator, - account: Account, - snapshot_dir: Path): + backend: TezosBackend, device: Device, tezos_navigator: TezosNavigator, account: Account, snapshot_dir: Path +): """Check blindsigning on too deep expression""" tezos_navigator.toggle_blindsign() - expression = MichelineExpr([[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[{'int':42}]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]) # pylint: disable=line-too-long + expression = MichelineExpr( + [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[{"int": 42}]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] + ) with backend.sign(account, expression, with_hash=True) as result: if device.type == DeviceType.NANOS: - ### Simulate `navigate_review` up to `ACCEPT_RISK` because the nanos screen can look like it hasn't changed. # pylint: disable=line-too-long + ### Simulate `navigate_review` up to `ACCEPT_RISK` because the nanos screen can look like it hasn't changed. # noqa: E501 - instructions: List[Union[NavIns, BaseNavInsID]] = [ + instructions: list[NavIns | BaseNavInsID] = [ # 'Review operation' NavInsID.RIGHT_CLICK, # 'Expression {{{...{{{' NavInsID.RIGHT_CLICK, # 'Expression {{{...{{{' @@ -544,54 +471,37 @@ def test_blindsign_too_deep( else: tezos_navigator.accept_sign_error_risk(snap_path=snapshot_dir / "clear") if not device.is_nano: - tezos_navigator.accept_sign_blindsign_risk( - snap_path=snapshot_dir / "blindsigning_warning" - ) + tezos_navigator.accept_sign_blindsign_risk(snap_path=snapshot_dir / "blindsigning_warning") tezos_navigator.accept_sign(snap_path=snapshot_dir / "blind") - account.check_signature( - message=expression, - with_hash=True, - data=result.value - ) + account.check_signature(message=expression, with_hash=True, data=result.value) + def test_blindsign_too_large( - backend: TezosBackend, - device: Device, - tezos_navigator: TezosNavigator, - account: Account, - snapshot_dir: Path + backend: TezosBackend, device: Device, tezos_navigator: TezosNavigator, account: Account, snapshot_dir: Path ): """Check blindsigning on too large expression""" tezos_navigator.toggle_blindsign() - message = MichelineExpr({'int':12345678901234567890123456789012345678901234567890123456789012345678901234567890}) # pylint: disable=line-too-long + message = MichelineExpr({"int": 12345678901234567890123456789012345678901234567890123456789012345678901234567890}) with backend.sign(account, message, with_hash=True) as result: tezos_navigator.accept_sign_error_risk(snap_path=snapshot_dir / "clear") if not device.is_nano: - tezos_navigator.accept_sign_blindsign_risk( - snap_path=snapshot_dir / "blindsigning_warning" - ) + tezos_navigator.accept_sign_blindsign_risk(snap_path=snapshot_dir / "blindsigning_warning") tezos_navigator.accept_sign(snap_path=snapshot_dir / "blind") - account.check_signature( - message=message, - with_hash=True, - data=result.value - ) + account.check_signature(message=message, with_hash=True, data=result.value) + def test_blindsign_reject_from_clear( - backend: TezosBackend, - tezos_navigator: TezosNavigator, - account: Account, - snapshot_dir: Path + backend: TezosBackend, tezos_navigator: TezosNavigator, account: Account, snapshot_dir: Path ): """Check blindsigning rejection""" - expression = MichelineExpr({'int':12345678901234567890123456789012345678901234567890123456789012345678901234567890}) # pylint: disable=line-too-long + expression = MichelineExpr({"int": 12345678901234567890123456789012345678901234567890123456789012345678901234567890}) tezos_navigator.toggle_blindsign() @@ -599,16 +509,13 @@ def test_blindsign_reject_from_clear( with backend.sign(account, expression): tezos_navigator.refuse_sign_error_risk(snap_path=snapshot_dir) + def test_blindsign_reject_from_blind( - backend: TezosBackend, - device: Device, - tezos_navigator: TezosNavigator, - account: Account, - snapshot_dir: Path + backend: TezosBackend, device: Device, tezos_navigator: TezosNavigator, account: Account, snapshot_dir: Path ): """Check blindsigning rejection""" - expression = MichelineExpr({'int':12345678901234567890123456789012345678901234567890123456789012345678901234567890}) # pylint: disable=line-too-long + expression = MichelineExpr({"int": 12345678901234567890123456789012345678901234567890123456789012345678901234567890}) tezos_navigator.toggle_blindsign() @@ -624,12 +531,9 @@ def test_blindsign_reject_from_blind( tezos_navigator.accept_sign_blindsign_risk(snap_path=snapshot_dir / "blind_warning") tezos_navigator.reject_sign(snap_path=snapshot_dir / "blind") + def test_ensure_always_clearsign( - backend: TezosBackend, - device: Device, - tezos_navigator: TezosNavigator, - account: Account, - snapshot_dir: Path + backend: TezosBackend, device: Device, tezos_navigator: TezosNavigator, account: Account, snapshot_dir: Path ): """Check clear signing never blindsign""" @@ -637,23 +541,21 @@ def test_ensure_always_clearsign( if not device.is_nano: tezos_navigator.toggle_blindsign() + # fmt: off message = Transaction( - source = 'tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa', - fee = 10000, - counter = 2, - gas_limit = 3, - storage_limit = 4, - destination = 'KT18amZmM5W7qDWVt2pH6uj7sCEd3kbzLrHT', - amount = 0, - entrypoint = 'root', - parameter = [{'prim':'pair','args':[{'string':"["},{'prim':'pair','args':[{'string':"Z"},{'prim':'pair','args':[{'string':"Y"},{'prim':'pair','args':[{'string':"X"},{'prim':'pair','args':[{'string':"W"},{'prim':'pair','args':[{'string':"V"},{'prim':'pair','args':[{'string':"U"},{'prim':'pair','args':[{'string':"T"},{'prim':'pair','args':[{'string':"S"},{'prim':'pair','args':[{'string':"R"},{'prim':'pair','args':[{'string':"Q"},{'prim':'pair','args':[{'string':"P"},{'prim':'pair','args':[{'string':"O"},{'prim':'pair','args':[{'string':"N"},{'prim':'pair','args':[{'string':"M"},{'prim':'pair','args':[{'string':"L"},{'prim':'pair','args':[{'string':"K"},{'prim':'pair','args':[{'string':"J"},{'prim':'pair','args':[{'string':"I"},{'prim':'pair','args':[{'string':"H"},{'prim':'pair','args':[{'string':"G"},{'prim':'pair','args':[{'string':"F"},{'prim':'pair','args':[{'string':"E"},{'prim':'pair','args':[{'string':"D"},{'prim':'pair','args':[{'string':"C"},{'prim':'pair','args':[{'string':"B"},[]]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]},{'prim':'pair','args':[{'int':10},{'prim':'pair','args':[{'int':9},{'prim':'pair','args':[{'int':8},{'prim':'pair','args':[{'int':7},{'prim':'pair','args':[{'int':6},{'prim':'pair','args':[{'int':5},{'prim':'pair','args':[{'int':4},{'prim':'pair','args':[{'int':3},{'prim':'pair','args':[{'int':2},{'prim':'pair','args':[{'int':1},[]]}]}]}]}]}]}]}]}]}]}] # pylint: disable=line-too-long + source="tz1ixvCiPJYyMjsp2nKBVaq54f6AdbV8hCKa", + fee=10000, + counter=2, + gas_limit=3, + storage_limit=4, + destination="KT18amZmM5W7qDWVt2pH6uj7sCEd3kbzLrHT", + amount=0, + entrypoint="root", + parameter = [{"prim":"pair","args":[{"string":"["},{"prim":"pair","args":[{"string":"Z"},{"prim":"pair","args":[{"string":"Y"},{"prim":"pair","args":[{"string":"X"},{"prim":"pair","args":[{"string":"W"},{"prim":"pair","args":[{"string":"V"},{"prim":"pair","args":[{"string":"U"},{"prim":"pair","args":[{"string":"T"},{"prim":"pair","args":[{"string":"S"},{"prim":"pair","args":[{"string":"R"},{"prim":"pair","args":[{"string":"Q"},{"prim":"pair","args":[{"string":"P"},{"prim":"pair","args":[{"string":"O"},{"prim":"pair","args":[{"string":"N"},{"prim":"pair","args":[{"string":"M"},{"prim":"pair","args":[{"string":"L"},{"prim":"pair","args":[{"string":"K"},{"prim":"pair","args":[{"string":"J"},{"prim":"pair","args":[{"string":"I"},{"prim":"pair","args":[{"string":"H"},{"prim":"pair","args":[{"string":"G"},{"prim":"pair","args":[{"string":"F"},{"prim":"pair","args":[{"string":"E"},{"prim":"pair","args":[{"string":"D"},{"prim":"pair","args":[{"string":"C"},{"prim":"pair","args":[{"string":"B"},[]]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]},{"prim":"pair","args":[{"int":10},{"prim":"pair","args":[{"int":9},{"prim":"pair","args":[{"int":8},{"prim":"pair","args":[{"int":7},{"prim":"pair","args":[{"int":6},{"prim":"pair","args":[{"int":5},{"prim":"pair","args":[{"int":4},{"prim":"pair","args":[{"int":3},{"prim":"pair","args":[{"int":2},{"prim":"pair","args":[{"int":1},[]]}]}]}]}]}]}]}]}]}]}] # noqa: E501 ) + # fmt: on with backend.sign(account, message, with_hash=True) as result: tezos_navigator.accept_sign(snap_path=snapshot_dir) - account.check_signature( - message=message, - with_hash=True, - data=result.value - ) + account.check_signature(message=message, with_hash=True, data=result.value) diff --git a/tests/standalone/test_sign/test_expert_mode.py b/tests/standalone/test_sign/test_expert_mode.py index 0d0071c87..3ce09c49f 100644 --- a/tests/standalone/test_sign/test_expert_mode.py +++ b/tests/standalone/test_sign/test_expert_mode.py @@ -19,25 +19,20 @@ from pathlib import Path import pytest - from ragger.navigator import NavInsID - from utils.account import Account from utils.backend import StatusCode, TezosBackend from utils.message import Default, RegisterGlobalConstant from utils.navigator import TezosNavigator, TezosNavInsID - -EXPERT_OPERATION = RegisterGlobalConstant( - {"prim": "constant", 'args': [{"string": Default.SCRIPT_EXPR_HASH}]} -) +EXPERT_OPERATION = RegisterGlobalConstant({"prim": "constant", "args": [{"string": Default.SCRIPT_EXPR_HASH}]}) def test_reject_expert_mode( - backend: TezosBackend, - tezos_navigator: TezosNavigator, - account: Account, - snapshot_dir: Path + backend: TezosBackend, + tezos_navigator: TezosNavigator, + account: Account, + snapshot_dir: Path, ): """Check expert mode reject.""" @@ -48,10 +43,10 @@ def test_reject_expert_mode( @pytest.mark.use_on_device("touch") def test_enable_expert_mode_in_review( - backend: TezosBackend, - tezos_navigator: TezosNavigator, - account: Account, - snapshot_dir: Path + backend: TezosBackend, + tezos_navigator: TezosNavigator, + account: Account, + snapshot_dir: Path, ): """Check that expert mode can be activated from review on touch devices.""" @@ -60,26 +55,21 @@ def test_enable_expert_mode_in_review( tezos_navigator.accept_sign(snap_path=snapshot_dir / "review") # To ensure that expert mode is ON - tezos_navigator.navigate_to_settings( - screen_change_before_first_instruction=True, - snap_path=snapshot_dir / "check" - ) + tezos_navigator.navigate_to_settings(screen_change_before_first_instruction=True, snap_path=snapshot_dir / "check") @pytest.mark.use_on_device("touch") def test_reject_sign_at_expert_mode_after_enabling( - backend: TezosBackend, - tezos_navigator: TezosNavigator, - account: Account, - snapshot_dir: Path + backend: TezosBackend, + tezos_navigator: TezosNavigator, + account: Account, + snapshot_dir: Path, ): """Check reject at expert splash screen after enabling expert mode""" with StatusCode.REJECT.expected(): with backend.sign(account, EXPERT_OPERATION): - tezos_navigator.expert_accept_sign( - screen_change_after_last_instruction=False - ) + tezos_navigator.expert_accept_sign(screen_change_after_last_instruction=False) tezos_navigator.navigate( instructions=[ NavInsID.USE_CASE_REVIEW_REJECT, @@ -87,16 +77,16 @@ def test_reject_sign_at_expert_mode_after_enabling( NavInsID.USE_CASE_STATUS_DISMISS, ], screen_change_before_first_instruction=True, - snap_path=snapshot_dir + snap_path=snapshot_dir, ) @pytest.mark.use_on_device("touch") def test_reject_sign_at_expert_mode_when_enabled( - backend: TezosBackend, - tezos_navigator: TezosNavigator, - account: Account, - snapshot_dir: Path + backend: TezosBackend, + tezos_navigator: TezosNavigator, + account: Account, + snapshot_dir: Path, ): """Check reject at expert splash screen if expert mode already enabled""" @@ -110,5 +100,5 @@ def test_reject_sign_at_expert_mode_when_enabled( TezosNavInsID.REJECT_CHOICE_CONFIRM, NavInsID.USE_CASE_STATUS_DISMISS, ], - snap_path=snapshot_dir + snap_path=snapshot_dir, ) diff --git a/tests/standalone/test_sign/test_key.py b/tests/standalone/test_sign/test_key.py index 8f714695b..3a0fb2614 100644 --- a/tests/standalone/test_sign/test_key.py +++ b/tests/standalone/test_sign/test_key.py @@ -17,7 +17,6 @@ """Gathering of tests related to Key signatures.""" import pytest - from utils.account import Account, SigType from utils.backend import TezosBackend from utils.message import MichelineExpr @@ -25,66 +24,63 @@ @pytest.mark.parametrize( - "account", [ - Account("m/44'/1729'/0'/0'", - SigType.ED25519, - "edpkuXX2VdkdXzkN11oLCb8Aurdo1BTAtQiK8ZY9UPj2YMt3AHEpcY"), - Account("m/44'/1729'/0'/0'", - SigType.SECP256K1, - "sppk7bVy617DmGvXsMqcwsiLtnedTN2trUi5ugXcNig7en4rHJyunK1"), - Account("m/44'/1729'/0'/0'", - SigType.SECP256R1, - "p2pk67fq5pzuMMABZ9RDrooYbLrgmnQbLt8z7PTGM9mskf7LXS5tdBG"), - Account("m/44'/1729'/0'/0'", - SigType.BIP32_ED25519, - "edpkumJgSsSxkpiB5hmTq6eZcrmc6BsJtLAhYceFTiziFqje4mongz"), + "account", + [ + Account( + "m/44'/1729'/0'/0'", + SigType.ED25519, + "edpkuXX2VdkdXzkN11oLCb8Aurdo1BTAtQiK8ZY9UPj2YMt3AHEpcY", + ), + Account( + "m/44'/1729'/0'/0'", + SigType.SECP256K1, + "sppk7bVy617DmGvXsMqcwsiLtnedTN2trUi5ugXcNig7en4rHJyunK1", + ), + Account( + "m/44'/1729'/0'/0'", + SigType.SECP256R1, + "p2pk67fq5pzuMMABZ9RDrooYbLrgmnQbLt8z7PTGM9mskf7LXS5tdBG", + ), + Account( + "m/44'/1729'/0'/0'", + SigType.BIP32_ED25519, + "edpkumJgSsSxkpiB5hmTq6eZcrmc6BsJtLAhYceFTiziFqje4mongz", + ), ], - ids=lambda account: f"{account.sig_type}" + ids=lambda account: f"{account.sig_type}", ) -def test_sign_with_another_sig( - backend: TezosBackend, - tezos_navigator: TezosNavigator, - account: Account -): +def test_sign_with_another_sig(backend: TezosBackend, tezos_navigator: TezosNavigator, account: Account): """Check signing with ed25519""" - message = MichelineExpr([{'int': 0}]) + message = MichelineExpr([{"int": 0}]) with backend.sign(account, message, with_hash=True) as result: tezos_navigator.accept_sign() - account.check_signature( - message=message, - with_hash=True, - data=result.value - ) + account.check_signature(message=message, with_hash=True, data=result.value) @pytest.mark.parametrize( - "seed", [ - "around dignity equal spread between young lawsuit interest climb wide that panther rather mom snake scene ecology reunion ice illegal brush" # pylint: disable=line-too-long + "seed", + [ + "around dignity equal spread between young lawsuit interest climb wide that panther rather mom snake scene ecology reunion ice illegal brush" # noqa: E501 ], - ids=["seed21"] + ids=["seed21"], ) -def test_sign_with_another_seed( - backend: TezosBackend, - tezos_navigator: TezosNavigator -): +def test_sign_with_another_seed(backend: TezosBackend, tezos_navigator: TezosNavigator): """Check signing using another seed than [zebra*24]""" tezos_navigator.toggle_expert_mode() - account = Account("m/44'/1729'/0'/0'", - SigType.ED25519, - "edpkupntwMyERpYniuK1GDWquPaPU1wYsQgMirJPLGmC4Y5dMUsQNo") + account = Account( + "m/44'/1729'/0'/0'", + SigType.ED25519, + "edpkupntwMyERpYniuK1GDWquPaPU1wYsQgMirJPLGmC4Y5dMUsQNo", + ) - message = MichelineExpr([{'int': 0}]) + message = MichelineExpr([{"int": 0}]) with backend.sign(account, message, with_hash=True) as result: tezos_navigator.accept_sign() - account.check_signature( - message=message, - with_hash=True, - data=result.value - ) + account.check_signature(message=message, with_hash=True, data=result.value) diff --git a/tests/standalone/test_sign/test_parsing_errors.py b/tests/standalone/test_sign/test_parsing_errors.py index e907a6d9b..b37ecc1c7 100755 --- a/tests/standalone/test_sign/test_parsing_errors.py +++ b/tests/standalone/test_sign/test_parsing_errors.py @@ -19,13 +19,11 @@ from pathlib import Path import pytest - from utils.account import Account from utils.backend import StatusCode, TezosBackend from utils.message import RawMessage from utils.navigator import TezosNavigator - # Operation (0): Transaction # Source: tz2JPgTWZZpxZZLqHMfS69UAy1UHm4Aw5iHu # Fee: 0.05 XTZ @@ -37,15 +35,17 @@ # Entrypoint: do # Parameter: CAR -# original bytes : 0300000000000000000000000000000000000000000000000000000000000000006c016e8874874d31c3fbd636e924d5a036a43ec8faa7d0860308362d80d30e01000000000000000000000000000000000000000000ff02000000020316 # pylint: disable=line-too-long +# original bytes : 0300000000000000000000000000000000000000000000000000000000000000006c016e8874874d31c3fbd636e924d5a036a43ec8faa7d0860308362d80d30e01000000000000000000000000000000000000000000ff02000000020316 # noqa: E501 + @pytest.mark.parametrize( - "raw_msg", [ - "0100000000000000000000000000000000000000000000000000000000000000006c016e8874874d31c3fbd636e924d5a036a43ec8faa7d0860308362d80d30e01000000000000000000000000000000000000000000ff02000000020316", # pylint: disable=line-too-long - "03000000000000000000000000000000000000000000000000000000000000000001016e8874874d31c3fbd636e924d5a036a43ec8faa7d0860308362d80d30e01000000000000000000000000000000000000000000ff02000000020316", # pylint: disable=line-too-long - "0300000000000000000000000000000000000000000000000000000000000000006c016e8874874d31c3fbd636e924d5a036a43ec8faa7d0860308362d80d30e010000000000000000000000000000000000000000ff02000000020316", # pylint: disable=line-too-long - "0300000000000000000000000000000000000000000000000000000000000000006c016e8874874d31c3fbd636e924d5a036a43ec8faa7d0860308362d80d30e01000000000000000000000000000000000000000000ff0200000002031645", # pylint: disable=line-too-long - "0300000000000000000000000000000000000000000000000000000000000000006c016e8874874d31c3fbd636e924d5a036a43ec8faa7d0860308362d80d30e0100000000000000000000000000000000000000000000ff02000000020316", # pylint: disable=line-too-long + "raw_msg", + [ + "0100000000000000000000000000000000000000000000000000000000000000006c016e8874874d31c3fbd636e924d5a036a43ec8faa7d0860308362d80d30e01000000000000000000000000000000000000000000ff02000000020316", + "03000000000000000000000000000000000000000000000000000000000000000001016e8874874d31c3fbd636e924d5a036a43ec8faa7d0860308362d80d30e01000000000000000000000000000000000000000000ff02000000020316", + "0300000000000000000000000000000000000000000000000000000000000000006c016e8874874d31c3fbd636e924d5a036a43ec8faa7d0860308362d80d30e010000000000000000000000000000000000000000ff02000000020316", + "0300000000000000000000000000000000000000000000000000000000000000006c016e8874874d31c3fbd636e924d5a036a43ec8faa7d0860308362d80d30e01000000000000000000000000000000000000000000ff0200000002031645", + "0300000000000000000000000000000000000000000000000000000000000000006c016e8874874d31c3fbd636e924d5a036a43ec8faa7d0860308362d80d30e0100000000000000000000000000000000000000000000ff02000000020316", ], ids=[ "unknown_magic_bytes", @@ -53,14 +53,14 @@ "one_byte_removed_inside", "one_byte_added_at_the_end", "one_byte_added_inside", - ] + ], ) def test_parsing_error( - backend: TezosBackend, - tezos_navigator: TezosNavigator, - raw_msg: str, - account: Account, - snapshot_dir: Path + backend: TezosBackend, + tezos_navigator: TezosNavigator, + raw_msg: str, + account: Account, + snapshot_dir: Path, ): """Check parsing error handling""" @@ -71,57 +71,44 @@ def test_parsing_error( tezos_navigator.toggle_blindsign() with StatusCode.PARSE_ERROR.expected(): - with backend.sign( - account, - RawMessage(raw_msg), - with_hash=True - ): + with backend.sign(account, RawMessage(raw_msg), with_hash=True): tezos_navigator.refuse_sign_error_risk(snap_path=snapshot_dir) -def test_parsing_error_blindsign_disabled( - backend: TezosBackend, - account: Account -): +def test_parsing_error_blindsign_disabled(backend: TezosBackend, account: Account): """F-09: with blind-signing disabled (the default), a parse error must be rejected immediately with EXC_PARSE_ERROR, without offering a blind-sign prompt (app/src/handler/sign.c refill_error).""" # An unknown operation tag: parsing fails up-front. Blind-signing is left # disabled, so no on-device prompt is shown and no navigation is needed. - unknown_operation = "03000000000000000000000000000000000000000000000000000000000000000001016e8874874d31c3fbd636e924d5a036a43ec8faa7d0860308362d80d30e01000000000000000000000000000000000000000000ff02000000020316" # pylint: disable=line-too-long + unknown_operation = "03000000000000000000000000000000000000000000000000000000000000000001016e8874874d31c3fbd636e924d5a036a43ec8faa7d0860308362d80d30e01000000000000000000000000000000000000000000ff02000000020316" # noqa: E501 with StatusCode.PARSE_ERROR.expected(): - with backend.sign( - account, - RawMessage(unknown_operation), - with_hash=True - ): + with backend.sign(account, RawMessage(unknown_operation), with_hash=True): pass + @pytest.mark.parametrize( - "raw_msg", [ - "030000000000000000000000000000000000000000000000000000000000000000ce00ffdd6102321bc251e4a5190ad5b12b251069d9b4904e02030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c63966303966323935326433343532386337333366393436313563666333396263353535363139666335353064643461363762613232303863653865383637616133643133613665663939646662653332633639373461613961323135306432316563613239633333343965353963313362393038316631", # pylint: disable=line-too-long + "raw_msg", + [ + "030000000000000000000000000000000000000000000000000000000000000000ce00ffdd6102321bc251e4a5190ad5b12b251069d9b4904e02030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c63966303966323935326433343532386337333366393436313563666333396263353535363139666335353064643461363762613232303863653865383637616133643133613665663939646662653332633639373461613961323135306432316563613239633333343965353963313362393038316631", ], ids=[ "wrong_last_packet", - ] + ], ) def test_parsing_hard_fail( - backend: TezosBackend, - tezos_navigator: TezosNavigator, - raw_msg: str, - account: Account, - snapshot_dir: Path + backend: TezosBackend, + tezos_navigator: TezosNavigator, + raw_msg: str, + account: Account, + snapshot_dir: Path, ): """Check parsing error hard failing""" tezos_navigator.toggle_expert_mode() with StatusCode.UNEXPECTED_SIGN_STATE.expected(): - with backend.sign( - account, - RawMessage(raw_msg), - with_hash=True - ): + with backend.sign(account, RawMessage(raw_msg), with_hash=True): tezos_navigator.hard_reject_sign(snap_path=snapshot_dir) diff --git a/tests/standalone/test_version.py b/tests/standalone/test_version.py index 602e26d7a..40c8ff970 100755 --- a/tests/standalone/test_version.py +++ b/tests/standalone/test_version.py @@ -17,7 +17,6 @@ """Gathering of tests related to app version.""" import git - from utils.backend import TezosBackend, Version @@ -29,28 +28,19 @@ def test_version(backend: TezosBackend): app_version = Version.from_bytes(data) - assert current_version == app_version, \ - f"Expected {current_version} but got {app_version}" + assert current_version == app_version, f"Expected {current_version} but got {app_version}" def test_git(backend: TezosBackend): """Test that the app commit is the same as the current git commit.""" git_repo = git.Repo(search_parent_directories=True) - git_describe = git_repo.git.describe( - tags=True, - abbrev=8, - always=True, - long=True, - dirty=True - ) - current_commit = git_describe.replace('-dirty', '*') + git_describe = git_repo.git.describe(tags=True, abbrev=8, always=True, long=True, dirty=True) + current_commit = git_describe.replace("-dirty", "*") data = backend.git() - assert data.endswith(b'\x00'), \ - f"Should end with by '\x00' but got {data.hex()}" + assert data.endswith(b"\x00"), f"Should end with by '\x00' but got {data.hex()}" - app_commit = data[:-1].decode('utf-8') + app_commit = data[:-1].decode("utf-8") - assert current_commit == app_commit, \ - f"Expected {current_commit} but got {app_commit}" + assert current_commit == app_commit, f"Expected {current_commit} but got {app_commit}" diff --git a/tests/standalone/test_wrong_apdu.py b/tests/standalone/test_wrong_apdu.py index 67ef645aa..daa6439ae 100644 --- a/tests/standalone/test_wrong_apdu.py +++ b/tests/standalone/test_wrong_apdu.py @@ -17,21 +17,17 @@ """Gathering of tests related to APDU checks.""" # pylint: disable=protected-access -from typing import Any, Callable, Union +from collections.abc import Callable +from typing import Any import pytest - from utils.account import Account, SigType -from utils.backend import Cla, TezosBackend, Index, Ins, StatusCode +from utils.backend import Cla, Index, Ins, StatusCode, TezosBackend from utils.message import Transaction from utils.navigator import TezosNavigator -def test_regression_continue_after_reject( - backend: TezosBackend, - tezos_navigator: TezosNavigator, - account: Account -): +def test_regression_continue_after_reject(backend: TezosBackend, tezos_navigator: TezosNavigator, account: Account): """Check the app still runs after rejects signing""" tezos_navigator.toggle_expert_mode() @@ -60,18 +56,13 @@ def test_change_sign_instruction(backend: TezosBackend, account: Account): backend._ask_sign(Ins.SIGN_WITH_HASH, account) with StatusCode.INVALID_INS.expected(): - backend._continue_sign( - Ins.SIGN, - payload, - last=True) + backend._continue_sign(Ins.SIGN, payload, last=True) backend._ask_sign(Ins.SIGN, account) with StatusCode.INVALID_INS.expected(): - backend._continue_sign( - Ins.SIGN_WITH_HASH, - payload, - last=True) + backend._continue_sign(Ins.SIGN_WITH_HASH, payload, last=True) + def test_mixing_command(backend: TezosBackend, account: Account): """Check that mixing instruction fails""" @@ -101,20 +92,14 @@ def test_mixing_command(backend: TezosBackend, account: Account): with StatusCode.UNEXPECTED_STATE.expected(): backend.git() -@pytest.mark.parametrize( - "ins", [Ins.GET_PUBLIC_KEY, Ins.PROMPT_PUBLIC_KEY], ids=lambda ins: f"{ins}" -) + +@pytest.mark.parametrize("ins", [Ins.GET_PUBLIC_KEY, Ins.PROMPT_PUBLIC_KEY], ids=lambda ins: f"{ins}") @pytest.mark.parametrize("index", [Index.OTHER, Index.LAST], ids=lambda index: f"{index}") def test_wrong_index(backend: TezosBackend, account: Account, ins: Ins, index: Index): """Check wrong apdu index behaviour""" with StatusCode.WRONG_PARAM.expected(): - backend._exchange( - ins, - index=index, - sig_type=account.sig_type, - payload=account.path - ) + backend._exchange(ins, index=index, sig_type=account.sig_type, payload=account.path) @pytest.mark.parametrize( @@ -123,18 +108,16 @@ def test_wrong_index(backend: TezosBackend, account: Account, ins: Ins, index: I lambda backend, account: backend._provide_public_key(account, with_prompt=False), lambda backend, account: backend._provide_public_key(account, with_prompt=True), lambda backend, account: backend._ask_sign(Ins.SIGN, account), - lambda backend, account: backend._ask_sign(Ins.SIGN_WITH_HASH, account) + lambda backend, account: backend._ask_sign(Ins.SIGN_WITH_HASH, account), ], ids=[ "get_pk_without_prompt", "get_pk_with_prompt", "sign_without_hash", "sign_with_hash", - ] + ], ) -def test_wrong_derivation_type( - backend: TezosBackend, - sender: Callable[[TezosBackend, Account], Any]): +def test_wrong_derivation_type(backend: TezosBackend, sender: Callable[[TezosBackend, Account], Any]): """Check wrong derivation type behaviour""" account = Account("m/44'/1729'/0'/0'", 0x04, "__unused__") @@ -148,14 +131,14 @@ def test_wrong_derivation_type( lambda backend, account: backend._provide_public_key(account, with_prompt=False), lambda backend, account: backend._provide_public_key(account, with_prompt=True), lambda backend, account: backend._ask_sign(Ins.SIGN, account), - lambda backend, account: backend._ask_sign(Ins.SIGN_WITH_HASH, account) + lambda backend, account: backend._ask_sign(Ins.SIGN_WITH_HASH, account), ], ids=[ "get_pk_without_prompt", "get_pk_with_prompt", "sign_without_hash", "sign_with_hash", - ] + ], ) @pytest.mark.parametrize( "account", @@ -163,67 +146,73 @@ def test_wrong_derivation_type( Account( bytes.fromhex("058000002c800006c18000000080000000"), SigType.ED25519, - "__unused__"), + "__unused__", + ), Account( bytes.fromhex("048000002c800006c180000000800000"), SigType.ED25519, - "__unused__"), + "__unused__", + ), Account( - bytes.fromhex("0b8000002c800006c1800000008000000080000000800000008000000080000000800000008000000080000000"), # pylint: disable=line-too-long + bytes.fromhex("0b8000002c800006c1800000008000000080000000800000008000000080000000800000008000000080000000"), SigType.ED25519, - "__unused__"), + "__unused__", + ), ], ids=[ "wrong_number_index_account", "wrong_length_account", "too_much_index_account", - ] + ], ) def test_wrong_derivation_path( - backend: TezosBackend, - account: Account, - sender: Callable[[TezosBackend, Account], Any]): + backend: TezosBackend, + account: Account, + sender: Callable[[TezosBackend, Account], Any], +): """Check wrong derivation path behaviour""" with StatusCode.WRONG_LENGTH_FOR_INS.expected(): sender(backend, account) + @pytest.mark.parametrize("class_", [0x00, 0x81]) def test_wrong_class(backend: TezosBackend, class_: int): """Check wrong apdu class behaviour""" - raw = \ - class_.to_bytes(1, 'big') + \ - int(Ins.VERSION).to_bytes(1, 'big') + \ - int(Index.FIRST).to_bytes(1, 'big') + \ - int(SigType.ED25519).to_bytes(1, 'big') + \ - int(0x00).to_bytes(1, 'big') + raw = ( + class_.to_bytes(1, "big") + + int(Ins.VERSION).to_bytes(1, "big") + + int(Index.FIRST).to_bytes(1, "big") + + int(SigType.ED25519).to_bytes(1, "big") + + (0x00).to_bytes(1, "big") + ) with StatusCode.CLASS.expected(): backend.exchange_raw(raw) + @pytest.mark.parametrize( "size, data", - [ - (0, b'\x00'), - (1, b'') - ], - ids=lambda param: f"size={param}" if isinstance(param, int) else f"data={param}" + [(0, b"\x00"), (1, b"")], + ids=lambda param: f"size={param}" if isinstance(param, int) else f"data={param}", ) def test_wrong_apdu_length(backend: TezosBackend, size: int, data: bytes): """Check wrong apdu length behaviour""" - raw = \ - int(Cla.DEFAULT).to_bytes(1, 'big') + \ - int(Ins.VERSION).to_bytes(1, 'big') + \ - int(Index.FIRST).to_bytes(1, 'big') + \ - int(SigType.ED25519).to_bytes(1, 'big') + \ - size.to_bytes(1, 'big') + \ - data + raw = ( + int(Cla.DEFAULT).to_bytes(1, "big") + + int(Ins.VERSION).to_bytes(1, "big") + + int(Index.FIRST).to_bytes(1, "big") + + int(SigType.ED25519).to_bytes(1, "big") + + size.to_bytes(1, "big") + + data + ) with StatusCode.WRONG_LENGTH_FOR_INS.expected(): backend.exchange_raw(raw) + @pytest.mark.parametrize( "ins", [ @@ -237,11 +226,11 @@ def test_wrong_apdu_length(backend: TezosBackend, size: int, data: bytes): Ins.DEAUTHORIZE, Ins.QUERY_AUTH_KEY_WITH_CURVE, Ins.HMAC, - 0xff + 0xFF, ], - ids=lambda ins: f"ins={ins}" + ids=lambda ins: f"ins={ins}", ) -def test_unimplemented_commands(backend: TezosBackend, ins: Union[int, Ins]): +def test_unimplemented_commands(backend: TezosBackend, ins: int | Ins): """Check unimplemented commands""" with StatusCode.INVALID_INS.expected(): diff --git a/tests/standalone/utils/account.py b/tests/standalone/utils/account.py index f281dca75..23b68cbea 100644 --- a/tests/standalone/utils/account.py +++ b/tests/standalone/utils/account.py @@ -15,31 +15,32 @@ """Module providing an account interface.""" from enum import IntEnum -from typing import Union import coincurve -from coincurve import ecdsa as coincurve_ecdsa import fastecdsa.curve import fastecdsa.ecdsa import fastecdsa.encoding.sec1 import nacl.exceptions import nacl.signing +from coincurve import ecdsa as coincurve_ecdsa from ragger.bip import pack_derivation_path from .message import Message from .tezos_encoding import base58_decode, base58_encode, blake2b_32 + class SigType(IntEnum): """Class representing signature type.""" - ED25519 = 0x00 - SECP256K1 = 0x01 - SECP256R1 = 0x02 + ED25519 = 0x00 + SECP256K1 = 0x01 + SECP256R1 = 0x02 BIP32_ED25519 = 0x03 def __str__(self) -> str: return self.name + class Signature(bytes): """Class representing signature.""" @@ -47,7 +48,7 @@ def __str__(self) -> str: return self.decode() @staticmethod - def from_secp256_tlv(tlv: Union[bytes, bytearray]) -> bytes: + def from_secp256_tlv(tlv: bytes | bytearray) -> bytes: """Get the signature encapsulated in a TLV.""" # See: # https://developers.ledger.com/docs/embedded-app/crypto-api/lcx__ecdsa_8h/#cx_ecdsa_sign @@ -76,25 +77,27 @@ def from_secp256_tlv(tlv: Union[bytes, bytearray]) -> bytes: s_len = tlv[s_len_index] r = tlv[r_index : r_index + r_len] s = tlv[s_index : s_index + s_len] + # Sometimes \x00 are added or removed # A size adjustment is required here. def adjust_size(data, size): - return data[-size:].rjust(size, b'\x00') + return data[-size:].rjust(size, b"\x00") + return adjust_size(r, 32) + adjust_size(s, 32) @classmethod - def from_bytes(cls, data: bytes, sig_type: SigType) -> 'Signature': + def from_bytes(cls, data: bytes, sig_type: SigType) -> "Signature": """Get the signature according to the SigType.""" if sig_type in {SigType.ED25519, SigType.BIP32_ED25519}: - prefix = b'edsig' + prefix = b"edsig" elif sig_type == SigType.SECP256K1: - prefix = b'spsig' + prefix = b"spsig" data = Signature.from_secp256_tlv(data) elif sig_type == SigType.SECP256R1: - prefix = b'p2sig' + prefix = b"p2sig" data = Signature.from_secp256_tlv(data) else: - assert False, f"Wrong signature type: {sig_type}" + raise AssertionError(f"Wrong signature type: {sig_type}") return cls(base58_encode(data, prefix)) @@ -107,15 +110,16 @@ def __str__(self) -> str: class CompressionKind(IntEnum): """Bytes compression kind""" - EVEN = 0x02 - ODD = 0x03 + + EVEN = 0x02 + ODD = 0x03 UNCOMPRESSED = 0x04 def __bytes__(self) -> bytes: return bytes([self]) @classmethod - def from_bytes(cls, data: bytes, sig_type: Union[SigType, int]) -> 'PublicKey': + def from_bytes(cls, data: bytes, sig_type: SigType | int) -> "PublicKey": """Convert a public key from bytes to string""" length, data = data[0], data[1:] @@ -128,33 +132,21 @@ def from_bytes(cls, data: bytes, sig_type: Union[SigType, int]) -> 'PublicKey': data = data[1:] # Ed25519 - if sig_type in [ - SigType.ED25519, - SigType.BIP32_ED25519 - ]: - assert kind == cls.CompressionKind.EVEN, \ - f"Wrong Ed25519 public key compression kind: {kind}" - assert len(data) == 32, \ - f"Wrong Ed25519 public key length: {len(data)}" - return cls(base58_encode(data, b'edpk')) + if sig_type in [SigType.ED25519, SigType.BIP32_ED25519]: + assert kind == cls.CompressionKind.EVEN, f"Wrong Ed25519 public key compression kind: {kind}" + assert len(data) == 32, f"Wrong Ed25519 public key length: {len(data)}" + return cls(base58_encode(data, b"edpk")) # Secp256 - if sig_type in [ - SigType.SECP256K1, - SigType.SECP256R1 - ]: - assert kind == cls.CompressionKind.UNCOMPRESSED, \ - f"Wrong Secp256 public key compression kind: {kind}" - assert len(data) == 2 * 32, \ - f"Wrong Secp256 public key length: {len(data)}" - kind = cls.CompressionKind.ODD if data[-1] & 1 else \ - cls.CompressionKind.EVEN - prefix = b'sppk' if sig_type == SigType.SECP256K1 \ - else b'p2pk' + if sig_type in [SigType.SECP256K1, SigType.SECP256R1]: + assert kind == cls.CompressionKind.UNCOMPRESSED, f"Wrong Secp256 public key compression kind: {kind}" + assert len(data) == 2 * 32, f"Wrong Secp256 public key length: {len(data)}" + kind = cls.CompressionKind.ODD if data[-1] & 1 else cls.CompressionKind.EVEN + prefix = b"sppk" if sig_type == SigType.SECP256K1 else b"p2pk" data = bytes(kind) + data[:32] return cls(base58_encode(data, prefix)) - assert False, f"Wrong signature type: {sig_type}" + raise AssertionError(f"Wrong signature type: {sig_type}") class AccountKey: @@ -177,7 +169,7 @@ def public_key(self) -> str: """Return the encoded public key.""" return self._encoded_public_key - def verify(self, signature: Union[str, bytes], message: Union[str, bytes]) -> bool: + def verify(self, signature: str | bytes, message: str | bytes) -> bool: """Verify that signature is a valid signature of message for this account.""" encoded_signature = signature if isinstance(signature, bytes) else signature.encode() encoded_message = message if isinstance(message, bytes) else message.encode() @@ -196,9 +188,7 @@ def verify(self, signature: Union[str, bytes], message: Union[str, bytes]) -> bo raise ValueError("Signature is invalid.") from exc elif self._curve == b"sp": pk = coincurve.PublicKey(self._public_point) - der_sig = coincurve_ecdsa.cdata_to_der( - coincurve_ecdsa.deserialize_compact(decoded_signature) - ) + der_sig = coincurve_ecdsa.cdata_to_der(coincurve_ecdsa.deserialize_compact(decoded_signature)) if not pk.verify( signature=der_sig, message=encoded_message, @@ -206,13 +196,10 @@ def verify(self, signature: Union[str, bytes], message: Union[str, bytes]) -> bo ): raise ValueError("Signature is invalid.") elif self._curve == b"p2": - pk = fastecdsa.encoding.sec1.SEC1Encoder.decode_public_key( - self._public_point, curve=fastecdsa.curve.P256 - ) # type: ignore[assignment] + pk = fastecdsa.encoding.sec1.SEC1Encoder.decode_public_key(self._public_point, curve=fastecdsa.curve.P256) # type: ignore[assignment] r = int.from_bytes(decoded_signature[:32], "big") s = int.from_bytes(decoded_signature[32:], "big") - if not fastecdsa.ecdsa.verify( - sig=(r, s), msg=encoded_message, Q=pk, hashfunc=blake2b_32): # type: ignore[arg-type] # pylint: disable=line-too-long + if not fastecdsa.ecdsa.verify(sig=(r, s), msg=encoded_message, Q=pk, hashfunc=blake2b_32): # type: ignore[arg-type] raise ValueError("Signature is invalid.") else: raise ValueError(f"Invalid or unsupported curve type: `{self._curve!r}`.") @@ -224,16 +211,11 @@ class Account: """Class representing account.""" path: bytes - sig_type: Union[SigType, int] + sig_type: SigType | int __key: str - def __init__(self, - path: Union[str, bytes], - sig_type: Union[SigType, int], - key: str): - self.path = \ - pack_derivation_path(path) if isinstance(path, str) \ - else path + def __init__(self, path: str | bytes, sig_type: SigType | int, key: str): + self.path = pack_derivation_path(path) if isinstance(path, str) else path self.sig_type = sig_type self.__key = key @@ -245,29 +227,25 @@ def key(self) -> AccountKey: """Public key wrapper providing signature verification.""" return AccountKey(self.__key) - def check_signature( - self, - data: bytes, - message: Message, - with_hash: bool): + def check_signature(self, data: bytes, message: Message, with_hash: bool): """Checks if signature correspond to a signature of message sign by the account.""" if with_hash: - assert data.startswith(message.hash), \ - f"Expected a starting hash {message.hash.hex()} but got {data.hex()}" - data = data[len(message.hash):] + assert data.startswith(message.hash), f"Expected a starting hash {message.hash.hex()} but got {data.hex()}" + data = data[len(message.hash) :] signature = Signature.from_bytes(data, SigType(self.sig_type)) - assert self.key.verify(signature, bytes(message)), \ + assert self.key.verify(signature, bytes(message)), ( f"Fail to verify signature {signature!r}, \n\ with account {self} \n\ and message {message}" + ) -DEFAULT_SEED = ' '.join(['zebra']*24) +DEFAULT_SEED = " ".join(["zebra"] * 24) DEFAULT_ACCOUNT = Account( "m/44'/1729'/0'/0'", SigType.ED25519, - "edpkuXX2VdkdXzkN11oLCb8Aurdo1BTAtQiK8ZY9UPj2YMt3AHEpcY" + "edpkuXX2VdkdXzkN11oLCb8Aurdo1BTAtQiK8ZY9UPj2YMt3AHEpcY", ) diff --git a/tests/standalone/utils/backend.py b/tests/standalone/utils/backend.py index dd675768a..20c161431 100644 --- a/tests/standalone/utils/backend.py +++ b/tests/standalone/utils/backend.py @@ -14,17 +14,17 @@ """Tezos app backend.""" +import time +from collections.abc import Callable, Generator from contextlib import contextmanager from enum import IntEnum from multiprocessing.pool import ThreadPool from struct import unpack -import time -from typing import Callable, Generator, TypeVar, Union - from types import SimpleNamespace +from typing import TypeVar from ragger.backend import SpeculosBackend -from ragger.backend.interface import BackendInterface, RAPDU +from ragger.backend.interface import RAPDU, BackendInterface from ragger.error import ExceptionRAPDU from .account import Account, SigType @@ -48,11 +48,7 @@ def __str__(self) -> str: minor: int patch: int - def __init__(self, - app_kind: AppKind, - major: int, - minor: int, - patch: int): + def __init__(self, app_kind: AppKind, major: int, minor: int, patch: int): self.app_kind = app_kind self.major = major self.minor = minor @@ -64,16 +60,17 @@ def __repr__(self) -> str: def __eq__(self, other: object): if not isinstance(other, Version): return NotImplemented - return \ - self.app_kind == other.app_kind and \ - self.major == other.major and \ - self.minor == other.minor and \ - self.patch == other.patch + return ( + self.app_kind == other.app_kind + and self.major == other.major + and self.minor == other.minor + and self.patch == other.patch + ) @classmethod - def from_bytes(cls, raw: bytes) -> 'Version': + def from_bytes(cls, raw: bytes) -> "Version": """Create a version from bytes.""" - (app_kind, major, minor, patch) = unpack(' str: return self.name + class Ins(IntEnum): """Class representing instruction.""" - VERSION = 0x00 - AUTHORIZE_BAKING = 0x01 - GET_PUBLIC_KEY = 0x02 - PROMPT_PUBLIC_KEY = 0x03 - SIGN = 0x04 - SIGN_UNSAFE = 0x05 - RESET = 0x06 - QUERY_AUTH_KEY = 0x07 - QUERY_MAIN_HWM = 0x08 - GIT = 0x09 - SETUP = 0x0a - QUERY_ALL_HWM = 0x0b - DEAUTHORIZE = 0x0c - QUERY_AUTH_KEY_WITH_CURVE = 0x0d - HMAC = 0x0e - SIGN_WITH_HASH = 0x0f + VERSION = 0x00 + AUTHORIZE_BAKING = 0x01 + GET_PUBLIC_KEY = 0x02 + PROMPT_PUBLIC_KEY = 0x03 + SIGN = 0x04 + SIGN_UNSAFE = 0x05 + RESET = 0x06 + QUERY_AUTH_KEY = 0x07 + QUERY_MAIN_HWM = 0x08 + GIT = 0x09 + SETUP = 0x0A + QUERY_ALL_HWM = 0x0B + DEAUTHORIZE = 0x0C + QUERY_AUTH_KEY_WITH_CURVE = 0x0D + HMAC = 0x0E + SIGN_WITH_HASH = 0x0F def __str__(self) -> str: return self.name + class Index(IntEnum): """Class representing packet index.""" - FIRST = 0x00 - OTHER = 0x01 - LAST = 0x80 + FIRST = 0x00 + OTHER = 0x01 + LAST = 0x80 OTHER_LAST = 0x81 def __str__(self) -> str: return self.name + class StatusCode(IntEnum): """Class representing the status code.""" - SECURITY = 0x6982 - HID_REQUIRED = 0x6983 - REJECT = 0x6985 - WRONG_VALUES = 0x6A80 + SECURITY = 0x6982 + HID_REQUIRED = 0x6983 + REJECT = 0x6985 + WRONG_VALUES = 0x6A80 REFERENCED_DATA_NOT_FOUND = 0x6A88 - WRONG_PARAM = 0x6B00 - WRONG_LENGTH = 0x6C00 - INVALID_INS = 0x6D00 - CLASS = 0x6E00 - OK = 0x9000 - UNEXPECTED_STATE = 0x9001 - UNEXPECTED_SIGN_STATE = 0x9002 - UNKNOWN_CX_ERR = 0x9003 - UNKNOWN = 0x90FF - WRONG_LENGTH_FOR_INS = 0x917E - MEMORY_ERROR = 0x9200 - PARSE_ERROR = 0x9405 + WRONG_PARAM = 0x6B00 + WRONG_LENGTH = 0x6C00 + INVALID_INS = 0x6D00 + CLASS = 0x6E00 + OK = 0x9000 + UNEXPECTED_STATE = 0x9001 + UNEXPECTED_SIGN_STATE = 0x9002 + UNKNOWN_CX_ERR = 0x9003 + UNKNOWN = 0x90FF + WRONG_LENGTH_FOR_INS = 0x917E + MEMORY_ERROR = 0x9200 + PARSE_ERROR = 0x9405 def __str__(self) -> str: return self.name @@ -148,62 +148,57 @@ def expected(self) -> Generator[None, None, None]: """Fail if the right RAPDU code exception is not raise.""" try: yield - assert self == StatusCode.OK, \ - f"Expect fail with {self} but succeed" + assert self == StatusCode.OK, f"Expect fail with {self} but succeed" except ExceptionRAPDU as e: try: status = f"{StatusCode(e.status)}" except ValueError: status = f"0x{e.status:x}" - assert self == e.status, \ - f"Expect fail with {self} but fail with {status}" + assert self == e.status, f"Expect fail with {self} but fail with {status}" -RESPONSE = TypeVar('RESPONSE') +RESPONSE = TypeVar("RESPONSE") def async_thread(function: Callable[..., RESPONSE]): """Decorator that runs asynchronously the function and provides a context manager to access the result in a `value` field""" + @contextmanager def wrapper(*args, **kwargs) -> Generator[SimpleNamespace, None, None]: thread_result = SimpleNamespace(value=None) thread_pool = ThreadPool(processes=1) try: - thread_async_result = thread_pool.apply_async( - function, - args=args, - kwds=kwargs - ) + thread_async_result = thread_pool.apply_async(function, args=args, kwds=kwargs) yield thread_result thread_result.value = thread_async_result.get() finally: thread_pool.terminate() + return wrapper MAX_APDU_SIZE: int = 235 + class TezosBackend(BackendInterface): """Class representing the backen of the tezos app.""" - def _exchange(self, - ins: Union[Ins, int], - index: Union[Index, int] = Index.FIRST, - sig_type: Union[SigType, int, None] = None, - payload: bytes = b'') -> bytes: + def _exchange( + self, + ins: Ins | int, + index: Index | int = Index.FIRST, + sig_type: SigType | int | None = None, + payload: bytes = b"", + ) -> bytes: """Override of `exchange` for the tezos app.""" assert len(payload) <= MAX_APDU_SIZE, f"Apdu too large {len(payload)}" # Set to a non-existent value to ensure that p2 is unused - p2: int = sig_type if sig_type is not None else 0xff + p2: int = sig_type if sig_type is not None else 0xFF - rapdu: RAPDU = self.exchange(Cla.DEFAULT, - ins, - p1=index, - p2=p2, - data=payload) + rapdu: RAPDU = self.exchange(Cla.DEFAULT, ins, p1=index, p2=p2, data=payload) if rapdu.status != StatusCode.OK: raise ExceptionRAPDU(rapdu.status, rapdu.data) @@ -218,16 +213,12 @@ def version(self) -> bytes: """Requests the app version.""" return self._exchange(Ins.VERSION) - def _provide_public_key(self, - account: Account, - with_prompt: bool = False) -> bytes: + def _provide_public_key(self, account: Account, with_prompt: bool = False) -> bytes: """Requests the public key according to the account. Use `with_prompt` ask user confirmation """ ins = Ins.PROMPT_PUBLIC_KEY if with_prompt else Ins.GET_PUBLIC_KEY - return self._exchange(ins, - sig_type=account.sig_type, - payload=account.path) + return self._exchange(ins, sig_type=account.sig_type, payload=account.path) def get_public_key(self, account: Account) -> bytes: """Requests the public key according to the account.""" @@ -254,11 +245,13 @@ def _continue_sign(self, ins: Ins, payload: bytes, last: bool) -> bytes: return self._exchange(ins, index, payload=payload) @async_thread - def sign(self, - account: Account, - message: Message, - with_hash: bool = False, - apdu_size: int = MAX_APDU_SIZE) -> bytes: + def sign( + self, + account: Account, + message: Message, + with_hash: bool = False, + apdu_size: int = MAX_APDU_SIZE, + ) -> bytes: """Requests the signature of a message.""" msg = bytes(message) assert msg, "Do not sign empty message" @@ -269,23 +262,25 @@ def sign(self, while msg: payload = msg[:apdu_size] - msg = msg[apdu_size:] - last = not msg - data = self._continue_sign(ins, payload, last) + msg = msg[apdu_size:] + last = not msg + data = self._continue_sign(ins, payload, last) if last: return data assert not data, f"No data expected but got {data.hex()}" - assert False, "We should have already returned" + raise AssertionError("We should have already returned") + MAX_ATTEMPTS = 50 + def with_retry(f, attempts=MAX_ATTEMPTS): """Try f until it succeeds or has been executed attempts times.""" while True: try: return f() - except Exception as e: # pylint: disable=broad-exception-caught + except Exception as e: if attempts <= 0: print("- with_retry: attempts exhausted -") raise e @@ -293,6 +288,7 @@ def with_retry(f, attempts=MAX_ATTEMPTS): # Give plenty of time for speculos to update - can take a long time on CI machines time.sleep(0.5) + class SpeculosTezosBackend(TezosBackend, SpeculosBackend): """Class representing Tezos app running on Speculos.""" @@ -302,12 +298,12 @@ def __enter__(self) -> "SpeculosTezosBackend": try: super().__enter__() return self - except Exception: # pylint: disable=broad-exception-caught + except Exception: process = self._client.process try: - with_retry(self._client._wait_until_ready, attempts=5) # pylint: disable=protected-access + with_retry(self._client._wait_until_ready, attempts=5) super().__enter__() - except Exception as e: # pylint: disable=broad-exception-caught + except Exception as e: self._client.stop() # do not forget to close the first process self._client.process = process diff --git a/tests/standalone/utils/message.py b/tests/standalone/utils/message.py index 686d76284..97f3b50ab 100644 --- a/tests/standalone/utils/message.py +++ b/tests/standalone/utils/message.py @@ -17,7 +17,7 @@ from abc import ABC, abstractmethod from enum import IntEnum -from typing import Any, Dict, List, Optional, Union +from typing import Any, ClassVar from .tezos_encoding import ( blake2b_32, @@ -26,8 +26,8 @@ forge_base58, forge_bool, forge_entrypoint, - forge_int_fixed, forge_int32, + forge_int_fixed, forge_micheline, forge_nat, forge_public_key, @@ -40,6 +40,7 @@ reserved_entrypoints, ) + class Message(ABC): """Class representing a message.""" @@ -55,58 +56,65 @@ def __bytes__(self) -> bytes: def __str__(self) -> str: return bytes(self).hex() + class RawMessage(Message): """Class representing a raw message.""" _value: bytes - def __init__(self, value: Union[str, bytes]): - self._value = value if isinstance(value, bytes) else \ - bytes.fromhex(value) + def __init__(self, value: str | bytes): + self._value = value if isinstance(value, bytes) else bytes.fromhex(value) def __bytes__(self) -> bytes: return self._value -Micheline = Union[List, Dict] +Micheline = list | dict -class Default: # pylint: disable=too-few-public-methods + +class Default: """Class holding default values.""" - BLOCK_HASH: str = 'BKiHLREqU3JkXfzEDYAkmmfX48gBDtYhMrpA98s7Aq4SzbUAB6M' - PROTOCOL_HASH: str = 'PrihK96nBAFSxVL1GLJTVhu9YnzkMFiBeuJRPA8NwuZVZCE1L6i' - ED25519_PUBLIC_KEY_HASH: str = 'tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU' - ORIGINATED_ADDRESS: str = 'KT18amZmM5W7qDWVt2pH6uj7sCEd3kbzLrHT' - ORIGINATED_SMART_ROLLUP_ADDRESS: str = 'sr163Lv22CdE8QagCwf48PWDTquk6isQwv57' - SMART_ROLLUP_COMMITMENT_HASH: str = 'src12UJzB8mg7yU6nWPzicH7ofJbFjyJEbHvwtZdfRXi8DQHNp1LY8' - ED25519_PUBLIC_KEY: str = 'edpkteDwHwoNPB18tKToFKeSCykvr1ExnoMV5nawTJy9Y9nLTfQ541' - SCRIPT_EXPR_HASH: str = 'exprtWsu7N8st7XBhS685Qa2B4xP6TuTN9ve9UPCU29fV94ySDo5Va' - ENTRYPOINT: str = 'default' - BALLOT: str = 'yay' - SMART_ROLLUP_KIND: str = 'arith' - - class DefaultMicheline: # pylint: disable=too-few-public-methods + + BLOCK_HASH: str = "BKiHLREqU3JkXfzEDYAkmmfX48gBDtYhMrpA98s7Aq4SzbUAB6M" + PROTOCOL_HASH: str = "PrihK96nBAFSxVL1GLJTVhu9YnzkMFiBeuJRPA8NwuZVZCE1L6i" + ED25519_PUBLIC_KEY_HASH: str = "tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU" + ORIGINATED_ADDRESS: str = "KT18amZmM5W7qDWVt2pH6uj7sCEd3kbzLrHT" + ORIGINATED_SMART_ROLLUP_ADDRESS: str = "sr163Lv22CdE8QagCwf48PWDTquk6isQwv57" + SMART_ROLLUP_COMMITMENT_HASH: str = "src12UJzB8mg7yU6nWPzicH7ofJbFjyJEbHvwtZdfRXi8DQHNp1LY8" + ED25519_PUBLIC_KEY: str = "edpkteDwHwoNPB18tKToFKeSCykvr1ExnoMV5nawTJy9Y9nLTfQ541" + SCRIPT_EXPR_HASH: str = "exprtWsu7N8st7XBhS685Qa2B4xP6TuTN9ve9UPCU29fV94ySDo5Va" + ENTRYPOINT: str = "default" + BALLOT: str = "yay" + SMART_ROLLUP_KIND: str = "arith" + + class DefaultMicheline: """Class holding Micheline default values.""" - VALUE: Micheline = {'prim': 'Unit'} - TYPE: Micheline = {'prim': 'unit'} - CODE: Micheline = [ - {'prim': 'CDR'}, - {'prim': 'NIL', 'args': [{'prim': 'operation'}]}, - {'prim': 'PAIR'}, + + VALUE: ClassVar[Micheline] = {"prim": "Unit"} + TYPE: ClassVar[Micheline] = {"prim": "unit"} + CODE: ClassVar[Micheline] = [ + {"prim": "CDR"}, + {"prim": "NIL", "args": [{"prim": "operation"}]}, + {"prim": "PAIR"}, ] + class Watermark(IntEnum): """Class hodling messages watermark.""" - MANAGER_OPERATION = 0x03 + + MANAGER_OPERATION = 0x03 MICHELINE_EXPRESSION = 0x05 + # pytezos is not up to date with the protocol Seoul # See https://gitlab.com/tezos/tezos/-/blob/v23-release/src/proto_023_PtSeouLo/ # lib_protocol/michelson_v1_primitives.ml#L807 -prim_tags.update({ 'IS_IMPLICIT_ACCOUNT': b'\x9e'}) +prim_tags.update({"IS_IMPLICIT_ACCOUNT": b"\x9e"}) # See https://gitlab.com/tezos/tezos/-/blob/master/src/proto_024_PsU87LFi/ # lib_protocol/michelson_v1_primitives.ml#L816-L817 -prim_tags.update({ 'INDEX_ADDRESS': b'\x9f'}) -prim_tags.update({ 'GET_ADDRESS_INDEX': b'\xa0'}) +prim_tags.update({"INDEX_ADDRESS": b"\x9f"}) +prim_tags.update({"GET_ADDRESS_INDEX": b"\xa0"}) + class MichelineExpr(Message): """Class representing a tezos micheline expression.""" @@ -117,7 +125,7 @@ def __init__(self, expr: Micheline): self.expr = expr def __bytes__(self) -> bytes: - raw = b'' + raw = b"" raw += forge_int_fixed(Watermark.MICHELINE_EXPRESSION, 1) raw += forge_micheline(self.expr) return raw @@ -126,134 +134,130 @@ def __bytes__(self) -> bytes: class OperationBuilder: """Class representing operation content builders.""" - def operation(self, content: Dict[str, Any]): + def operation(self, content: dict[str, Any]): """Return the operation content dict.""" return content - def proposals(self, proposals, source='', period=0): + def proposals(self, proposals, source="", period=0): """Build a Tezos proposals operation.""" if not isinstance(proposals, list): proposals = [proposals] return self.operation( { - 'kind': 'proposals', - 'source': source, - 'period': str(period), - 'proposals': proposals, + "kind": "proposals", + "source": source, + "period": str(period), + "proposals": proposals, } ) - def ballot(self, proposal, ballot, source='', period=0): + def ballot(self, proposal, ballot, source="", period=0): """Build a Tezos ballot operation.""" return self.operation( { - 'kind': 'ballot', - 'source': source, - 'period': str(period), - 'proposal': proposal, - 'ballot': ballot, + "kind": "ballot", + "source": source, + "period": str(period), + "proposal": proposal, + "ballot": ballot, } ) - def reveal( # pylint: disable=too-many-arguments,too-many-positional-arguments - self, - public_key='', - source='', - counter=0, - fee=0, - gas_limit=0, - storage_limit=0, - proof=''): + def reveal( + self, + public_key="", + source="", + counter=0, + fee=0, + gas_limit=0, + storage_limit=0, + proof="", + ): """Build a Tezos reveal operation.""" content = { - 'kind': 'reveal', - 'source': source, - 'fee': format_mutez(fee), - 'counter': str(counter), - 'gas_limit': str(gas_limit), - 'storage_limit': str(storage_limit), - 'public_key': public_key, + "kind": "reveal", + "source": source, + "fee": format_mutez(fee), + "counter": str(counter), + "gas_limit": str(gas_limit), + "storage_limit": str(storage_limit), + "public_key": public_key, } if proof: - content['proof'] = proof + content["proof"] = proof return self.operation(content) - def transaction( # pylint: disable=too-many-arguments,too-many-positional-arguments - self, - destination, - amount=0, - parameters=None, - source='', - counter=0, - fee=0, - gas_limit=0, - storage_limit=0): + def transaction( + self, + destination, + amount=0, + parameters=None, + source="", + counter=0, + fee=0, + gas_limit=0, + storage_limit=0, + ): """Build a Tezos transaction operation.""" content = { - 'kind': 'transaction', - 'source': source, - 'fee': format_mutez(fee), - 'counter': str(counter), - 'gas_limit': str(gas_limit), - 'storage_limit': str(storage_limit), - 'amount': format_mutez(amount), - 'destination': destination, + "kind": "transaction", + "source": source, + "fee": format_mutez(fee), + "counter": str(counter), + "gas_limit": str(gas_limit), + "storage_limit": str(storage_limit), + "amount": format_mutez(amount), + "destination": destination, } if parameters is not None: - content['parameters'] = parameters + content["parameters"] = parameters return self.operation(content) - def origination( # pylint: disable=too-many-arguments,too-many-positional-arguments - self, - script, - balance=0, - delegate=None, - source='', - counter=0, - fee=0, - gas_limit=0, - storage_limit=0): + def origination( + self, + script, + balance=0, + delegate=None, + source="", + counter=0, + fee=0, + gas_limit=0, + storage_limit=0, + ): """Build a Tezos origination operation.""" content = { - 'kind': 'origination', - 'source': source, - 'fee': format_mutez(fee), - 'counter': str(counter), - 'gas_limit': str(gas_limit), - 'storage_limit': str(storage_limit), - 'balance': format_mutez(balance), - 'script': script, + "kind": "origination", + "source": source, + "fee": format_mutez(fee), + "counter": str(counter), + "gas_limit": str(gas_limit), + "storage_limit": str(storage_limit), + "balance": format_mutez(balance), + "script": script, } if delegate is not None: - content['delegate'] = delegate + content["delegate"] = delegate return self.operation(content) - def delegation( # pylint: disable=too-many-arguments,too-many-positional-arguments - self, - delegate='', - source='', - counter=0, - fee=0, - gas_limit=0, - storage_limit=0): + def delegation(self, delegate="", source="", counter=0, fee=0, gas_limit=0, storage_limit=0): """Build a Tezos delegation operation.""" content = { - 'kind': 'delegation', - 'source': source, - 'fee': format_mutez(fee), - 'counter': str(counter), - 'gas_limit': str(gas_limit), - 'storage_limit': str(storage_limit), - 'delegate': delegate, + "kind": "delegation", + "source": source, + "fee": format_mutez(fee), + "counter": str(counter), + "gas_limit": str(gas_limit), + "storage_limit": str(storage_limit), + "delegate": delegate, } if delegate is None: - content.pop('delegate') + content.pop("delegate") return self.operation(content) @@ -261,506 +265,508 @@ def failing_noop(self, arbitrary: str): """Build a Tezos failing_noop operation.""" return self.operation( { - 'kind': 'failing_noop', - 'arbitrary': arbitrary, + "kind": "failing_noop", + "arbitrary": arbitrary, } ) - def register_global_constant( # pylint: disable=too-many-arguments,too-many-positional-arguments - self, value: Any, source='', counter=0, fee=0, gas_limit=0, storage_limit=0): + def register_global_constant(self, value: Any, source="", counter=0, fee=0, gas_limit=0, storage_limit=0): """Build a Tezos register_global_constant operation.""" return self.operation( { - 'kind': 'register_global_constant', - 'value': value, - 'source': source, - 'fee': format_mutez(fee), - 'counter': str(counter), - 'gas_limit': str(gas_limit), - 'storage_limit': str(storage_limit), + "kind": "register_global_constant", + "value": value, + "source": source, + "fee": format_mutez(fee), + "counter": str(counter), + "gas_limit": str(gas_limit), + "storage_limit": str(storage_limit), } ) - def transfer_ticket( # pylint: disable=too-many-arguments,too-many-positional-arguments - self, - ticket_contents: Any, - ticket_ty: Any, - ticket_ticketer: str, - ticket_amount: int, - destination: str, - entrypoint='default', - source='', - counter=0, - fee=0, - gas_limit=0, - storage_limit=0): + def transfer_ticket( + self, + ticket_contents: Any, + ticket_ty: Any, + ticket_ticketer: str, + ticket_amount: int, + destination: str, + entrypoint="default", + source="", + counter=0, + fee=0, + gas_limit=0, + storage_limit=0, + ): """Build a Tezos transfer_ticket operation.""" return self.operation( { - 'kind': 'transfer_ticket', - 'ticket_contents': ticket_contents, - 'ticket_ty': ticket_ty, - 'ticket_ticketer': ticket_ticketer, - 'ticket_amount': str(ticket_amount), - 'destination': destination, - 'entrypoint': entrypoint, - 'source': source, - 'fee': format_mutez(fee), - 'counter': str(counter), - 'gas_limit': str(gas_limit), - 'storage_limit': str(storage_limit), + "kind": "transfer_ticket", + "ticket_contents": ticket_contents, + "ticket_ty": ticket_ty, + "ticket_ticketer": ticket_ticketer, + "ticket_amount": str(ticket_amount), + "destination": destination, + "entrypoint": entrypoint, + "source": source, + "fee": format_mutez(fee), + "counter": str(counter), + "gas_limit": str(gas_limit), + "storage_limit": str(storage_limit), } ) - def smart_rollup_add_messages( # pylint: disable=too-many-arguments,too-many-positional-arguments - self, - message: List[bytes], - source='', - counter=0, - fee=0, - gas_limit=0, - storage_limit=0): + def smart_rollup_add_messages( + self, + message: list[bytes], + source="", + counter=0, + fee=0, + gas_limit=0, + storage_limit=0, + ): """Build a Tezos smart_rollup_add_messages operation.""" return self.operation( { - 'kind': 'smart_rollup_add_messages', - 'message': [m.hex() for m in message], - 'source': source, - 'fee': format_mutez(fee), - 'counter': str(counter), - 'gas_limit': str(gas_limit), - 'storage_limit': str(storage_limit), + "kind": "smart_rollup_add_messages", + "message": [m.hex() for m in message], + "source": source, + "fee": format_mutez(fee), + "counter": str(counter), + "gas_limit": str(gas_limit), + "storage_limit": str(storage_limit), } ) - def smart_rollup_execute_outbox_message( # pylint: disable=too-many-arguments,too-many-positional-arguments - self, - rollup: str, - cemented_commitment: str, - output_proof: bytes, - source='', - counter=0, - fee=0, - gas_limit=0, - storage_limit=0): + def smart_rollup_execute_outbox_message( + self, + rollup: str, + cemented_commitment: str, + output_proof: bytes, + source="", + counter=0, + fee=0, + gas_limit=0, + storage_limit=0, + ): """Build a Tezos smart_rollup_execute_outbox_message operation.""" return self.operation( { - 'kind': 'smart_rollup_execute_outbox_message', - 'rollup': rollup, - 'cemented_commitment': cemented_commitment, - 'output_proof': output_proof.hex(), - 'source': source, - 'fee': format_mutez(fee), - 'counter': str(counter), - 'gas_limit': str(gas_limit), - 'storage_limit': str(storage_limit), + "kind": "smart_rollup_execute_outbox_message", + "rollup": rollup, + "cemented_commitment": cemented_commitment, + "output_proof": output_proof.hex(), + "source": source, + "fee": format_mutez(fee), + "counter": str(counter), + "gas_limit": str(gas_limit), + "storage_limit": str(storage_limit), } ) - def set_deposit_limit( # pylint: disable=too-many-arguments,too-many-positional-arguments - self, - limit: Optional[int] = None, - source: str = '', - counter: int = 0, - fee: int = 0, - gas_limit: int = 0, - storage_limit: int = 0): + def set_deposit_limit( + self, + limit: int | None = None, + source: str = "", + counter: int = 0, + fee: int = 0, + gas_limit: int = 0, + storage_limit: int = 0, + ): """Build a Tezos set-deposit-limit.""" content = { - 'kind': 'set_deposit_limit', - 'source': source, - 'fee': format_mutez(fee), - 'counter': str(counter), - 'gas_limit': str(gas_limit), - 'storage_limit': str(storage_limit), + "kind": "set_deposit_limit", + "source": source, + "fee": format_mutez(fee), + "counter": str(counter), + "gas_limit": str(gas_limit), + "storage_limit": str(storage_limit), } if limit is not None: - content['limit'] = format_mutez(limit) + content["limit"] = format_mutez(limit) return self.operation(content) - def increase_paid_storage( # pylint: disable=too-many-arguments,too-many-positional-arguments - self, - amount: int = 0, - destination: str = '', - source: str = '', - counter: int = 0, - fee: int = 0, - gas_limit: int = 0, - storage_limit: int = 0): + def increase_paid_storage( + self, + amount: int = 0, + destination: str = "", + source: str = "", + counter: int = 0, + fee: int = 0, + gas_limit: int = 0, + storage_limit: int = 0, + ): """Build a Tezos increase-paid-storage.""" return self.operation( { - 'kind': 'increase_paid_storage', - 'source': source, - 'fee': format_mutez(fee), - 'counter': str(counter), - 'gas_limit': str(gas_limit), - 'storage_limit': str(storage_limit), - 'amount': str(amount), - 'destination': destination, + "kind": "increase_paid_storage", + "source": source, + "fee": format_mutez(fee), + "counter": str(counter), + "gas_limit": str(gas_limit), + "storage_limit": str(storage_limit), + "amount": str(amount), + "destination": destination, } ) - def update_consensus_key( # pylint: disable=too-many-arguments,too-many-positional-arguments - self, - pk: str = '', - proof: Optional[str] = None, - source: str = '', - counter: int = 0, - fee: int = 0, - gas_limit: int = 0, - storage_limit: int = 0): + def update_consensus_key( + self, + pk: str = "", + proof: str | None = None, + source: str = "", + counter: int = 0, + fee: int = 0, + gas_limit: int = 0, + storage_limit: int = 0, + ): """Build a Tezos update-consensus-key.""" content = { - 'kind': 'update_consensus_key', - 'source': source, - 'fee': format_mutez(fee), - 'counter': str(counter), - 'gas_limit': str(gas_limit), - 'storage_limit': str(storage_limit), - 'pk': pk, + "kind": "update_consensus_key", + "source": source, + "fee": format_mutez(fee), + "counter": str(counter), + "gas_limit": str(gas_limit), + "storage_limit": str(storage_limit), + "pk": pk, } if proof is not None: - content['proof'] = proof + content["proof"] = proof return self.operation(content) - def update_companion_key( # pylint: disable=too-many-arguments,too-many-positional-arguments - self, - pk: str = '', - proof: Optional[str] = None, - source: str = '', - counter: int = 0, - fee: int = 0, - gas_limit: int = 0, - storage_limit: int = 0): + def update_companion_key( + self, + pk: str = "", + proof: str | None = None, + source: str = "", + counter: int = 0, + fee: int = 0, + gas_limit: int = 0, + storage_limit: int = 0, + ): """Build a Tezos update-companion-key.""" content = { - 'kind': 'update_companion_key', - 'source': source, - 'fee': format_mutez(fee), - 'counter': str(counter), - 'gas_limit': str(gas_limit), - 'storage_limit': str(storage_limit), - 'pk': pk, + "kind": "update_companion_key", + "source": source, + "fee": format_mutez(fee), + "counter": str(counter), + "gas_limit": str(gas_limit), + "storage_limit": str(storage_limit), + "pk": pk, } if proof is not None: - content['proof'] = proof + content["proof"] = proof return self.operation(content) - def smart_rollup_originate( # pylint: disable=dangerous-default-value,too-many-arguments,too-many-positional-arguments - self, - pvm_kind: str = '', - kernel: str = '', - parameters_ty: Micheline = Default.DefaultMicheline.TYPE, - whitelist: Optional[List[str]] = None, - source: str = '', - counter: int = 0, - fee: int = 0, - gas_limit: int = 0, - storage_limit: int = 0): + def smart_rollup_originate( + self, + pvm_kind: str = "", + kernel: str = "", + parameters_ty: Micheline = Default.DefaultMicheline.TYPE, + whitelist: list[str] | None = None, + source: str = "", + counter: int = 0, + fee: int = 0, + gas_limit: int = 0, + storage_limit: int = 0, + ): """Build a Tezos smart rollup originate.""" content = { - 'kind': 'smart_rollup_originate', - 'source': source, - 'fee': format_mutez(fee), - 'counter': str(counter), - 'gas_limit': str(gas_limit), - 'storage_limit': str(storage_limit), - 'pvm_kind': pvm_kind, - 'kernel': kernel, - 'parameters_ty': parameters_ty, + "kind": "smart_rollup_originate", + "source": source, + "fee": format_mutez(fee), + "counter": str(counter), + "gas_limit": str(gas_limit), + "storage_limit": str(storage_limit), + "pvm_kind": pvm_kind, + "kernel": kernel, + "parameters_ty": parameters_ty, } if whitelist is not None: - content['whitelist'] = whitelist + content["whitelist"] = whitelist return self.operation(content) + class OperationForge: """Class to helps forging Tezos operation.""" # Insert new reserved entrypoint - reserved_entrypoints['stake'] = b'\x06' - reserved_entrypoints['unstake'] = b'\x07' - reserved_entrypoints['finalize_unstake'] = b'\x08' - reserved_entrypoints['delegate_parameters'] = b'\x09' + reserved_entrypoints["stake"] = b"\x06" + reserved_entrypoints["unstake"] = b"\x07" + reserved_entrypoints["finalize_unstake"] = b"\x08" + reserved_entrypoints["delegate_parameters"] = b"\x09" # Insert new operation tag - operation_tags['set_deposit_limit'] = 112 - operation_tags['increase_paid_storage'] = 113 - operation_tags['update_consensus_key'] = 114 - operation_tags['update_companion_key'] = 115 - operation_tags['smart_rollup_originate'] = 200 + operation_tags["set_deposit_limit"] = 112 + operation_tags["increase_paid_storage"] = 113 + operation_tags["update_consensus_key"] = 114 + operation_tags["update_companion_key"] = 115 + operation_tags["smart_rollup_originate"] = 200 @staticmethod - def failing_noop(content: Dict[str, Any]) -> bytes: + def failing_noop(content: dict[str, Any]) -> bytes: """Forge a failing_noop operation to bytes.""" - res = forge_tag(operation_tags[content['kind']]) - res += forge_array(content['arbitrary'].encode()) + res = forge_tag(operation_tags[content["kind"]]) + res += forge_array(content["arbitrary"].encode()) return res @staticmethod - def transaction(content: Dict[str, Any]) -> bytes: + def transaction(content: dict[str, Any]) -> bytes: """Forge a transaction operation to bytes.""" - res = forge_tag(operation_tags[content['kind']]) - res += forge_address(content['source'], tz_only=True) - res += forge_nat(int(content['fee'])) - res += forge_nat(int(content['counter'])) - res += forge_nat(int(content['gas_limit'])) - res += forge_nat(int(content['storage_limit'])) - res += forge_nat(int(content['amount'])) - res += forge_address(content['destination']) + res = forge_tag(operation_tags[content["kind"]]) + res += forge_address(content["source"], tz_only=True) + res += forge_nat(int(content["fee"])) + res += forge_nat(int(content["counter"])) + res += forge_nat(int(content["gas_limit"])) + res += forge_nat(int(content["storage_limit"])) + res += forge_nat(int(content["amount"])) + res += forge_address(content["destination"]) if has_parameters(content): res += forge_bool(True) - res += forge_entrypoint(content['parameters']['entrypoint']) - res += forge_array(forge_micheline(content['parameters']['value'])) + res += forge_entrypoint(content["parameters"]["entrypoint"]) + res += forge_array(forge_micheline(content["parameters"]["value"])) else: res += forge_bool(False) return res @staticmethod - def origination(content: Dict[str, Any]) -> bytes: + def origination(content: dict[str, Any]) -> bytes: """Forge an origination operation to bytes.""" - res = forge_tag(operation_tags[content['kind']]) - res += forge_address(content['source'], tz_only=True) - res += forge_nat(int(content['fee'])) - res += forge_nat(int(content['counter'])) - res += forge_nat(int(content['gas_limit'])) - res += forge_nat(int(content['storage_limit'])) - res += forge_nat(int(content['balance'])) - - if content.get('delegate'): + res = forge_tag(operation_tags[content["kind"]]) + res += forge_address(content["source"], tz_only=True) + res += forge_nat(int(content["fee"])) + res += forge_nat(int(content["counter"])) + res += forge_nat(int(content["gas_limit"])) + res += forge_nat(int(content["storage_limit"])) + res += forge_nat(int(content["balance"])) + + if content.get("delegate"): res += forge_bool(True) - res += forge_address(content['delegate'], tz_only=True) + res += forge_address(content["delegate"], tz_only=True) else: res += forge_bool(False) - res += forge_script(content['script']) + res += forge_script(content["script"]) return res @staticmethod - def delegation(content: Dict[str, Any]) -> bytes: + def delegation(content: dict[str, Any]) -> bytes: """Forge a delegation operation to bytes.""" - res = forge_tag(operation_tags[content['kind']]) - res += forge_address(content['source'], tz_only=True) - res += forge_nat(int(content['fee'])) - res += forge_nat(int(content['counter'])) - res += forge_nat(int(content['gas_limit'])) - res += forge_nat(int(content['storage_limit'])) - - if content.get('delegate'): + res = forge_tag(operation_tags[content["kind"]]) + res += forge_address(content["source"], tz_only=True) + res += forge_nat(int(content["fee"])) + res += forge_nat(int(content["counter"])) + res += forge_nat(int(content["gas_limit"])) + res += forge_nat(int(content["storage_limit"])) + + if content.get("delegate"): res += forge_bool(True) - res += forge_address(content['delegate'], tz_only=True) + res += forge_address(content["delegate"], tz_only=True) else: res += forge_bool(False) return res @staticmethod - def register_global_constant(content: Dict[str, Any]) -> bytes: + def register_global_constant(content: dict[str, Any]) -> bytes: """Forge a register_global_constant operation to bytes.""" - res = forge_tag(operation_tags[content['kind']]) - res += forge_address(content['source'], tz_only=True) - res += forge_nat(int(content['fee'])) - res += forge_nat(int(content['counter'])) - res += forge_nat(int(content['gas_limit'])) - res += forge_nat(int(content['storage_limit'])) - res += forge_array(forge_micheline(content['value'])) + res = forge_tag(operation_tags[content["kind"]]) + res += forge_address(content["source"], tz_only=True) + res += forge_nat(int(content["fee"])) + res += forge_nat(int(content["counter"])) + res += forge_nat(int(content["gas_limit"])) + res += forge_nat(int(content["storage_limit"])) + res += forge_array(forge_micheline(content["value"])) return res @staticmethod - def transfer_ticket(content: Dict[str, Any]) -> bytes: + def transfer_ticket(content: dict[str, Any]) -> bytes: """Forge a transfer_ticket operation to bytes.""" - res = forge_tag(operation_tags[content['kind']]) - res += forge_address(content['source'], tz_only=True) - res += forge_nat(int(content['fee'])) - res += forge_nat(int(content['counter'])) - res += forge_nat(int(content['gas_limit'])) - res += forge_nat(int(content['storage_limit'])) - res += forge_array(forge_micheline(content['ticket_contents'])) - res += forge_array(forge_micheline(content['ticket_ty'])) - res += forge_address(content['ticket_ticketer']) - res += forge_nat(int(content['ticket_amount'])) - res += forge_address(content['destination']) - res += forge_array(content['entrypoint'].encode()) + res = forge_tag(operation_tags[content["kind"]]) + res += forge_address(content["source"], tz_only=True) + res += forge_nat(int(content["fee"])) + res += forge_nat(int(content["counter"])) + res += forge_nat(int(content["gas_limit"])) + res += forge_nat(int(content["storage_limit"])) + res += forge_array(forge_micheline(content["ticket_contents"])) + res += forge_array(forge_micheline(content["ticket_ty"])) + res += forge_address(content["ticket_ticketer"]) + res += forge_nat(int(content["ticket_amount"])) + res += forge_address(content["destination"]) + res += forge_array(content["entrypoint"].encode()) return res @staticmethod - def smart_rollup_add_messages(content: Dict[str, Any]) -> bytes: + def smart_rollup_add_messages(content: dict[str, Any]) -> bytes: """Forge a smart_rollup_add_messages operation to bytes.""" - res = forge_tag(operation_tags[content['kind']]) - res += forge_address(content['source'], tz_only=True) - res += forge_nat(int(content['fee'])) - res += forge_nat(int(content['counter'])) - res += forge_nat(int(content['gas_limit'])) - res += forge_nat(int(content['storage_limit'])) - res += forge_array( - b''.join(forge_array(bytes.fromhex(msg)) for msg in content['message']) - ) + res = forge_tag(operation_tags[content["kind"]]) + res += forge_address(content["source"], tz_only=True) + res += forge_nat(int(content["fee"])) + res += forge_nat(int(content["counter"])) + res += forge_nat(int(content["gas_limit"])) + res += forge_nat(int(content["storage_limit"])) + res += forge_array(b"".join(forge_array(bytes.fromhex(msg)) for msg in content["message"])) return res @staticmethod - def smart_rollup_execute_outbox_message(content: Dict[str, Any]) -> bytes: + def smart_rollup_execute_outbox_message(content: dict[str, Any]) -> bytes: """Forge a smart_rollup_execute_outbox_message operation to bytes.""" - res = forge_tag(operation_tags[content['kind']]) - res += forge_address(content['source'], tz_only=True) - res += forge_nat(int(content['fee'])) - res += forge_nat(int(content['counter'])) - res += forge_nat(int(content['gas_limit'])) - res += forge_nat(int(content['storage_limit'])) - res += forge_base58(content['rollup']) - res += forge_base58(content['cemented_commitment']) - res += forge_array(bytes.fromhex(content['output_proof'])) + res = forge_tag(operation_tags[content["kind"]]) + res += forge_address(content["source"], tz_only=True) + res += forge_nat(int(content["fee"])) + res += forge_nat(int(content["counter"])) + res += forge_nat(int(content["gas_limit"])) + res += forge_nat(int(content["storage_limit"])) + res += forge_base58(content["rollup"]) + res += forge_base58(content["cemented_commitment"]) + res += forge_array(bytes.fromhex(content["output_proof"])) return res # Fix Pytezos reveal forging updated in 3.16.0 @staticmethod - def reveal(content: Dict[str, Any]) -> bytes: + def reveal(content: dict[str, Any]) -> bytes: """Forge a Tezos reveal.""" - res = forge_tag(operation_tags[content['kind']]) - res += forge_address(content['source'], tz_only=True) - res += forge_nat(int(content['fee'])) - res += forge_nat(int(content['counter'])) - res += forge_nat(int(content['gas_limit'])) - res += forge_nat(int(content['storage_limit'])) - res += forge_public_key(content['public_key']) - - if content.get('proof'): + res = forge_tag(operation_tags[content["kind"]]) + res += forge_address(content["source"], tz_only=True) + res += forge_nat(int(content["fee"])) + res += forge_nat(int(content["counter"])) + res += forge_nat(int(content["gas_limit"])) + res += forge_nat(int(content["storage_limit"])) + res += forge_public_key(content["public_key"]) + + if content.get("proof"): res += forge_bool(True) - res += forge_array(forge_base58(content['proof'])) + res += forge_array(forge_base58(content["proof"])) else: res += forge_bool(False) return res @staticmethod - def proposals(content: Dict[str, Any]) -> bytes: + def proposals(content: dict[str, Any]) -> bytes: """Forge a Tezos proposals.""" - res = forge_tag(operation_tags[content['kind']]) - res += forge_address(content['source'], tz_only=True) - res += forge_int32(int(content['period'])) - res += forge_array(b''.join(map(forge_base58, content['proposals']))) + res = forge_tag(operation_tags[content["kind"]]) + res += forge_address(content["source"], tz_only=True) + res += forge_int32(int(content["period"])) + res += forge_array(b"".join(map(forge_base58, content["proposals"]))) return res - BALLOT_TAG = { 'yay': 0, 'nay': 1, 'pass': 2 } + BALLOT_TAG: ClassVar[dict[str, int]] = {"yay": 0, "nay": 1, "pass": 2} @staticmethod - def ballot(content: Dict[str, Any]) -> bytes: + def ballot(content: dict[str, Any]) -> bytes: """Forge a Tezos ballot.""" - res = forge_tag(operation_tags[content['kind']]) - res += forge_address(content['source'], tz_only=True) - res += forge_int32(int(content['period'])) - res += forge_base58(content['proposal']) - res += forge_int_fixed(OperationForge.BALLOT_TAG[content['ballot']], 1) + res = forge_tag(operation_tags[content["kind"]]) + res += forge_address(content["source"], tz_only=True) + res += forge_int32(int(content["period"])) + res += forge_base58(content["proposal"]) + res += forge_int_fixed(OperationForge.BALLOT_TAG[content["ballot"]], 1) return res @staticmethod - def set_deposit_limit(content: Dict[str, Any]) -> bytes: + def set_deposit_limit(content: dict[str, Any]) -> bytes: """Forge a Tezos set-deposit-limit.""" - res = forge_tag(operation_tags[content['kind']]) - res += forge_address(content['source'], tz_only=True) - res += forge_nat(int(content['fee'])) - res += forge_nat(int(content['counter'])) - res += forge_nat(int(content['gas_limit'])) - res += forge_nat(int(content['storage_limit'])) - - if content.get('limit'): + res = forge_tag(operation_tags[content["kind"]]) + res += forge_address(content["source"], tz_only=True) + res += forge_nat(int(content["fee"])) + res += forge_nat(int(content["counter"])) + res += forge_nat(int(content["gas_limit"])) + res += forge_nat(int(content["storage_limit"])) + + if content.get("limit"): res += forge_bool(True) - res += forge_nat(int(content['limit'])) + res += forge_nat(int(content["limit"])) else: res += forge_bool(False) return res @staticmethod - def increase_paid_storage(content: Dict[str, Any]) -> bytes: + def increase_paid_storage(content: dict[str, Any]) -> bytes: """Forge a Tezos increase-paid-storage.""" - res = forge_tag(operation_tags[content['kind']]) - res += forge_address(content['source'], tz_only=True) - res += forge_nat(int(content['fee'])) - res += forge_nat(int(content['counter'])) - res += forge_nat(int(content['gas_limit'])) - res += forge_nat(int(content['storage_limit'])) - res += forge_nat(int(content['amount'])) - res += forge_address(content['destination']) + res = forge_tag(operation_tags[content["kind"]]) + res += forge_address(content["source"], tz_only=True) + res += forge_nat(int(content["fee"])) + res += forge_nat(int(content["counter"])) + res += forge_nat(int(content["gas_limit"])) + res += forge_nat(int(content["storage_limit"])) + res += forge_nat(int(content["amount"])) + res += forge_address(content["destination"]) return res @staticmethod - def update_consensus_key(content: Dict[str, Any]) -> bytes: + def update_consensus_key(content: dict[str, Any]) -> bytes: """Forge a Tezos update-consensus-key.""" - res = forge_tag(operation_tags[content['kind']]) - res += forge_address(content['source'], tz_only=True) - res += forge_nat(int(content['fee'])) - res += forge_nat(int(content['counter'])) - res += forge_nat(int(content['gas_limit'])) - res += forge_nat(int(content['storage_limit'])) - res += forge_public_key(content['pk']) - - if content.get('proof'): + res = forge_tag(operation_tags[content["kind"]]) + res += forge_address(content["source"], tz_only=True) + res += forge_nat(int(content["fee"])) + res += forge_nat(int(content["counter"])) + res += forge_nat(int(content["gas_limit"])) + res += forge_nat(int(content["storage_limit"])) + res += forge_public_key(content["pk"]) + + if content.get("proof"): res += forge_bool(True) - res += forge_array(forge_base58(content['proof'])) + res += forge_array(forge_base58(content["proof"])) else: res += forge_bool(False) return res @staticmethod - def update_companion_key(content: Dict[str, Any]) -> bytes: + def update_companion_key(content: dict[str, Any]) -> bytes: """Forge a Tezos update-companion-key.""" - res = forge_tag(operation_tags[content['kind']]) - res += forge_address(content['source'], tz_only=True) - res += forge_nat(int(content['fee'])) - res += forge_nat(int(content['counter'])) - res += forge_nat(int(content['gas_limit'])) - res += forge_nat(int(content['storage_limit'])) - res += forge_public_key(content['pk']) - - if content.get('proof'): + res = forge_tag(operation_tags[content["kind"]]) + res += forge_address(content["source"], tz_only=True) + res += forge_nat(int(content["fee"])) + res += forge_nat(int(content["counter"])) + res += forge_nat(int(content["gas_limit"])) + res += forge_nat(int(content["storage_limit"])) + res += forge_public_key(content["pk"]) + + if content.get("proof"): res += forge_bool(True) - res += forge_array(forge_base58(content['proof'])) + res += forge_array(forge_base58(content["proof"])) else: res += forge_bool(False) return res - PVM_KIND_TAG = { 'arith': 0, 'wasm_2_0_0': 1, 'riscv': 2 } + PVM_KIND_TAG: ClassVar[dict[str, int]] = {"arith": 0, "wasm_2_0_0": 1, "riscv": 2} @staticmethod - def smart_rollup_originate(content: Dict[str, Any]) -> bytes: + def smart_rollup_originate(content: dict[str, Any]) -> bytes: """Forge a Tezos smart rollup originate.""" - res = forge_tag(operation_tags[content['kind']]) - res += forge_address(content['source'], tz_only=True) - res += forge_nat(int(content['fee'])) - res += forge_nat(int(content['counter'])) - res += forge_nat(int(content['gas_limit'])) - res += forge_nat(int(content['storage_limit'])) - res += forge_int_fixed( - OperationForge.PVM_KIND_TAG[content['pvm_kind']], 1 - ) - res += forge_array(bytes.fromhex(content['kernel'])) - res += forge_array(forge_micheline(content['parameters_ty'])) - - if content.get('whitelist') is not None: + res = forge_tag(operation_tags[content["kind"]]) + res += forge_address(content["source"], tz_only=True) + res += forge_nat(int(content["fee"])) + res += forge_nat(int(content["counter"])) + res += forge_nat(int(content["gas_limit"])) + res += forge_nat(int(content["storage_limit"])) + res += forge_int_fixed(OperationForge.PVM_KIND_TAG[content["pvm_kind"]], 1) + res += forge_array(bytes.fromhex(content["kernel"])) + res += forge_array(forge_micheline(content["parameters_ty"])) + + if content.get("whitelist") is not None: res += forge_bool(True) - res += forge_array(b''.join( - forge_address(pkh, tz_only=True) - for pkh in content['whitelist'] - )) + res += forge_array(b"".join(forge_address(pkh, tz_only=True) for pkh in content["whitelist"])) else: res += forge_bool(False) return res + class Operation(Message, OperationBuilder): """Class representing a tezos operation.""" @@ -775,52 +781,52 @@ def forge(self) -> bytes: raise NotImplementedError def __bytes__(self) -> bytes: - raw = b'' + raw = b"" raw += forge_int_fixed(Watermark.MANAGER_OPERATION, 1) raw += forge_base58(self.branch) raw += self.forge() return raw + class Proposals(Operation): """Class representing a tezos proposals.""" - proposals_: List[str] + proposals_: list[str] source: str period: int - def __init__(self, # pylint: disable=dangerous-default-value - proposals: List[str] = [Default.PROTOCOL_HASH], - source: str = Default.ED25519_PUBLIC_KEY_HASH, - period: int = 0, - **kwargs): - self.proposals_ = proposals + def __init__( + self, + proposals: list[str] | None = None, + source: str = Default.ED25519_PUBLIC_KEY_HASH, + period: int = 0, + **kwargs, + ): + self.proposals_ = proposals if proposals is not None else [Default.PROTOCOL_HASH] self.source = source self.period = period Operation.__init__(self, **kwargs) def forge(self) -> bytes: - return OperationForge.proposals( - self.proposals( - self.proposals_, - self.source, - self.period - ) - ) + return OperationForge.proposals(self.proposals(self.proposals_, self.source, self.period)) + class Ballot(Operation): """Class representing a tezos ballot.""" - proposal : str - ballot_ : str - source : str + proposal: str + ballot_: str + source: str period: int - def __init__(self, - proposal: str = Default.PROTOCOL_HASH, - ballot: str = Default.BALLOT, - source: str = Default.ED25519_PUBLIC_KEY_HASH, - period: int = 0, - **kwargs): + def __init__( + self, + proposal: str = Default.PROTOCOL_HASH, + ballot: str = Default.BALLOT, + source: str = Default.ED25519_PUBLIC_KEY_HASH, + period: int = 0, + **kwargs, + ): self.proposal = proposal self.ballot_ = ballot self.source = source @@ -828,14 +834,8 @@ def __init__(self, Operation.__init__(self, **kwargs) def forge(self) -> bytes: - return OperationForge.ballot( - self.ballot( - self.proposal, - self.ballot_, - self.source, - self.period - ) - ) + return OperationForge.ballot(self.ballot(self.proposal, self.ballot_, self.source, self.period)) + class FailingNoop(Operation): """Class representing a tezos failing-noop.""" @@ -849,6 +849,7 @@ def __init__(self, message: str = "", **kwargs): def forge(self) -> bytes: return OperationForge.failing_noop(self.failing_noop(self.message)) + class ManagerOperation(Operation): """Class representing a tezos manager operation.""" @@ -856,15 +857,17 @@ class ManagerOperation(Operation): fee: int counter: int gas_limit: int - storage_limit: int # pylint: disable=too-many-arguments,too-many-positional-arguments - - def __init__(self, # pylint: disable=too-many-arguments,too-many-positional-arguments - source: str = Default.ED25519_PUBLIC_KEY_HASH, - fee: int = 0, - counter: int = 0, - gas_limit: int = 0, - storage_limit: int = 0, - **kwargs): + storage_limit: int + + def __init__( + self, + source: str = Default.ED25519_PUBLIC_KEY_HASH, + fee: int = 0, + counter: int = 0, + gas_limit: int = 0, + storage_limit: int = 0, + **kwargs, + ): self.source = source self.fee = fee self.counter = counter @@ -872,30 +875,36 @@ def __init__(self, # pylint: disable=too-many-arguments,too-many-positional-arg self.storage_limit = storage_limit Operation.__init__(self, **kwargs) + class OperationGroup(Operation): """Class representing a group of tezos manager operation.""" - operations: List[ManagerOperation] + operations: list[ManagerOperation] - def __init__(self, # pylint: disable=dangerous-default-value - operations: List[ManagerOperation] = [], - **kwargs): - self.operations = operations + def __init__( + self, + operations: list[ManagerOperation] | None = None, + **kwargs, + ): + self.operations = operations if operations is not None else [] Operation.__init__(self, **kwargs) def forge(self) -> bytes: - return b''.join(map(lambda op: op.forge(), self.operations)) + return b"".join(map(lambda op: op.forge(), self.operations)) + class Reveal(ManagerOperation): """Class representing a tezos reveal.""" public_key: str - proof: Optional[str] - - def __init__(self, - public_key: str = Default.ED25519_PUBLIC_KEY, - proof: Optional[str] = None, - **kwargs): + proof: str | None + + def __init__( + self, + public_key: str = Default.ED25519_PUBLIC_KEY, + proof: str | None = None, + **kwargs, + ): self.public_key = public_key self.proof = proof ManagerOperation.__init__(self, **kwargs) @@ -909,10 +918,11 @@ def forge(self) -> bytes: self.fee, self.gas_limit, self.storage_limit, - self.proof + self.proof, ) ) + class Transaction(ManagerOperation): """Class representing a tezos transaction.""" @@ -921,12 +931,14 @@ class Transaction(ManagerOperation): entrypoint: str parameter: Micheline - def __init__(self, # pylint: disable=dangerous-default-value - destination: str = Default.ED25519_PUBLIC_KEY_HASH, - amount: int = 0, - entrypoint: str = Default.ENTRYPOINT, - parameter: Micheline = Default.DefaultMicheline.VALUE, - **kwargs): + def __init__( + self, + destination: str = Default.ED25519_PUBLIC_KEY_HASH, + amount: int = 0, + entrypoint: str = Default.ENTRYPOINT, + parameter: Micheline = Default.DefaultMicheline.VALUE, + **kwargs, + ): self.destination = destination self.amount = amount self.entrypoint = entrypoint @@ -934,7 +946,7 @@ def __init__(self, # pylint: disable=dangerous-default-value ManagerOperation.__init__(self, **kwargs) def forge(self) -> bytes: - parameters = { "entrypoint": self.entrypoint, "value": self.parameter } + parameters = {"entrypoint": self.entrypoint, "value": self.parameter} return OperationForge.transaction( self.transaction( self.destination, @@ -944,24 +956,27 @@ def forge(self) -> bytes: self.counter, self.fee, self.gas_limit, - self.storage_limit + self.storage_limit, ) ) + class Origination(ManagerOperation): """Class representing a tezos origination.""" code: Micheline storage: Micheline balance: int - delegate: Optional[str] - - def __init__(self, # pylint: disable=dangerous-default-value - code: Micheline = Default.DefaultMicheline.CODE, - storage: Micheline = Default.DefaultMicheline.TYPE, - balance: int = 0, - delegate: Optional[str] = None, - **kwargs): + delegate: str | None + + def __init__( + self, + code: Micheline = Default.DefaultMicheline.CODE, + storage: Micheline = Default.DefaultMicheline.TYPE, + balance: int = 0, + delegate: str | None = None, + **kwargs, + ): self.code = code self.storage = storage self.balance = balance @@ -969,7 +984,7 @@ def __init__(self, # pylint: disable=dangerous-default-value ManagerOperation.__init__(self, **kwargs) def forge(self) -> bytes: - script = { "code": self.code, "storage": self.storage } + script = {"code": self.code, "storage": self.storage} return OperationForge.origination( self.origination( script, @@ -979,18 +994,17 @@ def forge(self) -> bytes: self.counter, self.fee, self.gas_limit, - self.storage_limit + self.storage_limit, ) ) + class Delegation(ManagerOperation): """Class representing a tezos delegation.""" - delegate: Optional[str] + delegate: str | None - def __init__(self, - delegate: Optional[str] = None, - **kwargs): + def __init__(self, delegate: str | None = None, **kwargs): self.delegate = delegate ManagerOperation.__init__(self, **kwargs) @@ -1002,18 +1016,21 @@ def forge(self) -> bytes: self.counter, self.fee, self.gas_limit, - self.storage_limit + self.storage_limit, ) ) + class RegisterGlobalConstant(ManagerOperation): """Class representing a tezos register global constant.""" value: Micheline - def __init__(self, # pylint: disable=dangerous-default-value - value: Micheline = Default.DefaultMicheline.VALUE, - **kwargs): + def __init__( + self, + value: Micheline = Default.DefaultMicheline.VALUE, + **kwargs, + ): self.value = value ManagerOperation.__init__(self, **kwargs) @@ -1025,18 +1042,17 @@ def forge(self) -> bytes: self.counter, self.fee, self.gas_limit, - self.storage_limit + self.storage_limit, ) ) + class SetDepositLimit(ManagerOperation): """Class representing a tezos set deposit limit.""" - limit: Optional[int] + limit: int | None - def __init__(self, - limit: Optional[int] = None, - **kwargs): + def __init__(self, limit: int | None = None, **kwargs): self.limit = limit ManagerOperation.__init__(self, **kwargs) @@ -1048,20 +1064,18 @@ def forge(self) -> bytes: self.counter, self.fee, self.gas_limit, - self.storage_limit + self.storage_limit, ) ) + class IncreasePaidStorage(ManagerOperation): """Class representing a tezos increase paid storage.""" amount: int destination: str - def __init__(self, - amount: int = 0, - destination: str = Default.ORIGINATED_ADDRESS, - **kwargs): + def __init__(self, amount: int = 0, destination: str = Default.ORIGINATED_ADDRESS, **kwargs): self.amount = amount self.destination = destination ManagerOperation.__init__(self, **kwargs) @@ -1075,20 +1089,23 @@ def forge(self) -> bytes: self.counter, self.fee, self.gas_limit, - self.storage_limit + self.storage_limit, ) ) + class UpdateConsensusKey(ManagerOperation): """Class representing a tezos update consensus key.""" pk: str - proof: Optional[str] - - def __init__(self, - pk: str = Default.ED25519_PUBLIC_KEY, - proof: Optional[str] = None, - **kwargs): + proof: str | None + + def __init__( + self, + pk: str = Default.ED25519_PUBLIC_KEY, + proof: str | None = None, + **kwargs, + ): self.pk = pk self.proof = proof ManagerOperation.__init__(self, **kwargs) @@ -1102,20 +1119,23 @@ def forge(self) -> bytes: self.counter, self.fee, self.gas_limit, - self.storage_limit + self.storage_limit, ) ) + class UpdateCompanionKey(ManagerOperation): """Class representing a tezos update companion key.""" pk: str - proof: Optional[str] - - def __init__(self, - pk: str = Default.ED25519_PUBLIC_KEY, - proof: Optional[str] = None, - **kwargs): + proof: str | None + + def __init__( + self, + pk: str = Default.ED25519_PUBLIC_KEY, + proof: str | None = None, + **kwargs, + ): self.pk = pk self.proof = proof ManagerOperation.__init__(self, **kwargs) @@ -1129,10 +1149,11 @@ def forge(self) -> bytes: self.counter, self.fee, self.gas_limit, - self.storage_limit + self.storage_limit, ) ) + class TransferTicket(ManagerOperation): """Class representing a tezos transfer ticket.""" @@ -1143,14 +1164,16 @@ class TransferTicket(ManagerOperation): destination: str entrypoint: str - def __init__(self, # pylint: disable=dangerous-default-value,too-many-arguments,too-many-positional-arguments - ticket_contents: Micheline = Default.DefaultMicheline.VALUE, - ticket_ty: Micheline = Default.DefaultMicheline.TYPE, - ticket_ticketer: str = Default.ORIGINATED_ADDRESS, - ticket_amount: int = 0, - destination: str = Default.ORIGINATED_ADDRESS, - entrypoint: str = Default.ENTRYPOINT, - **kwargs): + def __init__( + self, + ticket_contents: Micheline = Default.DefaultMicheline.VALUE, + ticket_ty: Micheline = Default.DefaultMicheline.TYPE, + ticket_ticketer: str = Default.ORIGINATED_ADDRESS, + ticket_amount: int = 0, + destination: str = Default.ORIGINATED_ADDRESS, + entrypoint: str = Default.ENTRYPOINT, + **kwargs, + ): self.ticket_contents = ticket_contents self.ticket_ty = ticket_ty self.ticket_ticketer = ticket_ticketer @@ -1172,24 +1195,27 @@ def forge(self) -> bytes: self.counter, self.fee, self.gas_limit, - self.storage_limit + self.storage_limit, ) ) + class ScRollupOriginate(ManagerOperation): """Class representing a tezos smart rollup originate.""" pvm_kind: str kernel: str parameters_ty: Micheline - whitelist: Optional[List[str]] - - def __init__(self, # pylint: disable=dangerous-default-value - pvm_kind: str = Default.SMART_ROLLUP_KIND, - kernel: str = "", - parameters_ty: Micheline = Default.DefaultMicheline.TYPE, - whitelist: Optional[List[str]] = None, - **kwargs): + whitelist: list[str] | None + + def __init__( + self, + pvm_kind: str = Default.SMART_ROLLUP_KIND, + kernel: str = "", + parameters_ty: Micheline = Default.DefaultMicheline.TYPE, + whitelist: list[str] | None = None, + **kwargs, + ): self.pvm_kind = pvm_kind self.kernel = kernel self.parameters_ty = parameters_ty @@ -1207,19 +1233,22 @@ def forge(self) -> bytes: self.counter, self.fee, self.gas_limit, - self.storage_limit + self.storage_limit, ) ) + class ScRollupAddMessage(ManagerOperation): """Class representing a tezos smart rollup add message.""" - message: List[bytes] + message: list[bytes] - def __init__(self, # pylint: disable=dangerous-default-value - message: List[bytes] = [b''], - **kwargs): - self.message = message + def __init__( + self, + message: list[bytes] | None = None, + **kwargs, + ): + self.message = message if message is not None else [b""] ManagerOperation.__init__(self, **kwargs) def forge(self) -> bytes: @@ -1230,10 +1259,11 @@ def forge(self) -> bytes: self.counter, self.fee, self.gas_limit, - self.storage_limit + self.storage_limit, ) ) + class ScRollupExecuteOutboxMessage(ManagerOperation): """Class representing a tezos smart rollup execute outbox message.""" @@ -1241,11 +1271,13 @@ class ScRollupExecuteOutboxMessage(ManagerOperation): cemented_commitment: str output_proof: bytes - def __init__(self, - rollup: str = Default.ORIGINATED_SMART_ROLLUP_ADDRESS, - cemented_commitment: str = Default.SMART_ROLLUP_COMMITMENT_HASH, - output_proof: bytes = b'', - **kwargs): + def __init__( + self, + rollup: str = Default.ORIGINATED_SMART_ROLLUP_ADDRESS, + cemented_commitment: str = Default.SMART_ROLLUP_COMMITMENT_HASH, + output_proof: bytes = b"", + **kwargs, + ): self.rollup = rollup self.cemented_commitment = cemented_commitment self.output_proof = output_proof @@ -1261,6 +1293,6 @@ def forge(self) -> bytes: self.counter, self.fee, self.gas_limit, - self.storage_limit + self.storage_limit, ) ) diff --git a/tests/standalone/utils/navigator.py b/tests/standalone/utils/navigator.py index d9bd5050f..61beb46e8 100644 --- a/tests/standalone/utils/navigator.py +++ b/tests/standalone/utils/navigator.py @@ -15,34 +15,41 @@ """Tezos app backend.""" +import time +from collections.abc import Callable from enum import auto from pathlib import Path -import time -from typing import Any, Callable, Dict, List, Optional, Union +from typing import Any, ClassVar from ledgered.devices import Device, DeviceType from ragger.backend import BackendInterface, SpeculosBackend from ragger.firmware.touch.element import Center from ragger.firmware.touch.layouts import ChoiceList, RightHeader from ragger.firmware.touch.positions import ( - Position, - STAX_BUTTON_LOWER_LEFT, - STAX_BUTTON_ABOVE_LOWER_MIDDLE, - FLEX_BUTTON_LOWER_LEFT, - FLEX_BUTTON_ABOVE_LOWER_MIDDLE, - APEX_P_BUTTON_LOWER_LEFT, APEX_P_BUTTON_ABOVE_LOWER_MIDDLE, + APEX_P_BUTTON_LOWER_LEFT, + FLEX_BUTTON_ABOVE_LOWER_MIDDLE, + FLEX_BUTTON_LOWER_LEFT, + STAX_BUTTON_ABOVE_LOWER_MIDDLE, + STAX_BUTTON_LOWER_LEFT, + Position, ) from ragger.firmware.touch.screen import MetaScreen from ragger.firmware.touch.use_cases import ( - UseCaseHome, - UseCaseSettings as OriginalUseCaseSettings, UseCaseAddressConfirmation as OriginalUseCaseAddressConfirmation, - UseCaseReview as OriginalUseCaseReview, +) +from ragger.firmware.touch.use_cases import ( UseCaseChoice, + UseCaseHome, UseCaseViewDetails, ) -from ragger.navigator import BaseNavInsID, NavIns, NavInsID, Navigator +from ragger.firmware.touch.use_cases import ( + UseCaseReview as OriginalUseCaseReview, +) +from ragger.firmware.touch.use_cases import ( + UseCaseSettings as OriginalUseCaseSettings, +) +from ragger.navigator import BaseNavInsID, Navigator, NavIns, NavInsID from .backend import TezosBackend @@ -54,7 +61,7 @@ class UseCaseSettings(OriginalUseCaseSettings, metaclass=MetaScreen): _toggle_list: ChoiceList - def __init__(self, client: BackendInterface, device: Device): # pylint: disable=useless-parent-delegation + def __init__(self, client: BackendInterface, device: Device): # `MetaScreen` requires an explicit __init__ function super().__init__(client, device) @@ -71,23 +78,14 @@ def exit(self) -> None: self.multi_page_exit() -class UseCaseAddressConfirmation(OriginalUseCaseAddressConfirmation): # pylint: disable=too-few-public-methods +class UseCaseAddressConfirmation(OriginalUseCaseAddressConfirmation): """Custom UseCaseAddressConfirmation.""" # *_BUTTON_ABOVE_LOWER_LEFT - QR_POSITIONS = { - DeviceType.STAX: Position( - STAX_BUTTON_LOWER_LEFT.x, - STAX_BUTTON_ABOVE_LOWER_MIDDLE.y - ), - DeviceType.FLEX: Position( - FLEX_BUTTON_LOWER_LEFT.x, - FLEX_BUTTON_ABOVE_LOWER_MIDDLE.y - ), - DeviceType.APEX_P: Position( - APEX_P_BUTTON_LOWER_LEFT.x, - APEX_P_BUTTON_ABOVE_LOWER_MIDDLE.y - ) + QR_POSITIONS: ClassVar[dict[DeviceType, Position]] = { + DeviceType.STAX: Position(STAX_BUTTON_LOWER_LEFT.x, STAX_BUTTON_ABOVE_LOWER_MIDDLE.y), + DeviceType.FLEX: Position(FLEX_BUTTON_LOWER_LEFT.x, FLEX_BUTTON_ABOVE_LOWER_MIDDLE.y), + DeviceType.APEX_P: Position(APEX_P_BUTTON_LOWER_LEFT.x, APEX_P_BUTTON_ABOVE_LOWER_MIDDLE.y), } def show_qr(self) -> None: @@ -106,25 +104,25 @@ class UseCaseReview(OriginalUseCaseReview, metaclass=MetaScreen): use_case_details = UseCaseViewDetails layout__skip_header = RightHeader - reject_choice: UseCaseChoice - enable_expert_choice: UseCaseChoice + reject_choice: UseCaseChoice + enable_expert_choice: UseCaseChoice enable_blindsign_choice: UseCaseChoice - skip_choice: UseCaseChoice - back_to_safety: UseCaseChoice - details: UseCaseViewDetails - _skip_header: RightHeader + skip_choice: UseCaseChoice + back_to_safety: UseCaseChoice + details: UseCaseViewDetails + _skip_header: RightHeader - # The ‘more’ button is positioned just below the very long + # The 'more' button is positioned just below the very long # data displayed. As the size of the data is not fixed, the # position of the button cannot be defined statically. The # static positions below are defined for the test data. - MORE_POSITIONS = { + MORE_POSITIONS: ClassVar[dict[DeviceType, Position]] = { DeviceType.STAX: Position(200, 390), DeviceType.FLEX: Position(240, 350), - DeviceType.APEX_P: Position(180, 270) + DeviceType.APEX_P: Position(180, 270), } - def __init__(self, client: BackendInterface, device: Device): # pylint: disable=useless-parent-delegation + def __init__(self, client: BackendInterface, device: Device): # `MetaScreen` requires an explicit __init__ function super().__init__(client, device) @@ -137,7 +135,7 @@ def skip(self) -> None: self._skip_header.tap() -class TezosNavInsID(BaseNavInsID): # pylint: disable=too-few-public-methods +class TezosNavInsID(BaseNavInsID): """Custom NavInsID.""" # UseCaseSettings @@ -162,7 +160,7 @@ class TezosNavInsID(BaseNavInsID): # pylint: disable=too-few-public-methods WARNING_CHOICE_BLINDSIGN = auto() -class TezosNavigator(metaclass=MetaScreen): # pylint: disable=too-many-public-methods +class TezosNavigator(metaclass=MetaScreen): """Class representing Tezos app navigation.""" use_case_home = UseCaseHome @@ -171,21 +169,18 @@ class TezosNavigator(metaclass=MetaScreen): # pylint: disable=too-many-public-m use_case_review_tx = UseCaseReview element_center = Center - home: UseCaseHome - settings: UseCaseSettings + home: UseCaseHome + settings: UseCaseSettings review_pk: UseCaseAddressConfirmation review_tx: UseCaseReview - center: Center + center: Center _backend: TezosBackend _device: Device _navigator: Navigator _root_dir: Path - def __init__(self, - backend: TezosBackend, - device: Device, - navigator: Navigator): + def __init__(self, backend: TezosBackend, device: Device, navigator: Navigator): self._backend = backend self._device = device self._navigator = navigator @@ -193,7 +188,7 @@ def __init__(self, if self._device.is_nano: self._navigator.add_callback(TezosNavInsID.REVIEW_TX_NEXT, self._backend.right_click) else: - tezos_callbacks: Dict[BaseNavInsID, Callable[..., Any]] = { + tezos_callbacks: dict[BaseNavInsID, Callable[..., Any]] = { TezosNavInsID.SETTINGS_TOGGLE_EXPERT_MODE: self.settings.toggle_expert_mode, TezosNavInsID.SETTINGS_TOGGLE_BLINDSIGNING: self.settings.toggle_blindsigning, TezosNavInsID.SETTINGS_EXIT: self.settings.exit, @@ -205,77 +200,74 @@ def __init__(self, TezosNavInsID.REJECT_CHOICE_RETURN: self.review_tx.reject_choice.reject, TezosNavInsID.EXPERT_CHOICE_ENABLE: self.review_tx.enable_expert_choice.confirm, TezosNavInsID.EXPERT_CHOICE_REJECT: self.review_tx.enable_expert_choice.reject, - TezosNavInsID.BLINDSIGN_CHOICE_ENABLE: ( - self.review_tx.enable_blindsign_choice.confirm - ), - TezosNavInsID.BLINDSIGN_CHOICE_REJECT: ( - self.review_tx.enable_blindsign_choice.reject - ), - TezosNavInsID.SKIP_CHOICE_CONFIRM: ( - self._ignore_processing(self.review_tx.skip_choice.confirm) - ), + TezosNavInsID.BLINDSIGN_CHOICE_ENABLE: (self.review_tx.enable_blindsign_choice.confirm), + TezosNavInsID.BLINDSIGN_CHOICE_REJECT: (self.review_tx.enable_blindsign_choice.reject), + TezosNavInsID.SKIP_CHOICE_CONFIRM: (self._ignore_processing(self.review_tx.skip_choice.confirm)), TezosNavInsID.SKIP_CHOICE_REJECT: self.review_tx.skip_choice.reject, TezosNavInsID.WARNING_CHOICE_SAFETY: self.review_tx.back_to_safety.confirm, - TezosNavInsID.WARNING_CHOICE_BLINDSIGN: ( - self._ignore_processing(self.review_tx.back_to_safety.reject) - ), + TezosNavInsID.WARNING_CHOICE_BLINDSIGN: (self._ignore_processing(self.review_tx.back_to_safety.reject)), } - self._navigator._callbacks.update(tezos_callbacks) # pylint: disable=protected-access + self._navigator._callbacks.update(tezos_callbacks) self._root_dir = Path(__file__).resolve().parent.parent def _ignore_processing(self, callback: Callable): """Wrapper to ignore the `Processing` screen""" + def wrapper(*args, **kwargs): if not isinstance(self._backend, SpeculosBackend): callback(*args, **kwargs) else: - last_screenshot = self._backend._last_screenshot # pylint: disable=protected-access + last_screenshot = self._backend._last_screenshot callback(*args, **kwargs) self._backend.wait_for_screen_change() if self._backend.compare_screen_with_text("^(Processing|Loading operation)$"): self._backend.send_tick() # Wait a text that is not "Processing" self._backend.wait_for_text_on_screen("^(?!Processing$|Loading operation$).*") - self._backend._last_screenshot = last_screenshot # pylint: disable=protected-access + self._backend._last_screenshot = last_screenshot + return wrapper - def navigate(self, - snap_path: Optional[Path] = None, - screen_change_before_first_instruction: bool = False, - **kwargs) -> None: + def navigate( + self, + snap_path: Path | None = None, + screen_change_before_first_instruction: bool = False, + **kwargs, + ) -> None: """Wrapper of `navigator.navigate_and_compare`""" self._navigator.navigate_and_compare( path=self._root_dir, test_case_name=snap_path, screen_change_before_first_instruction=screen_change_before_first_instruction, - **kwargs + **kwargs, ) - def navigate_until_text(self, - snap_path: Optional[Path] = None, - screen_change_before_first_instruction: bool = False, - validation_instructions: Optional[ - List[Union[NavIns, BaseNavInsID]] - ] = None, - **kwargs) -> None: + def navigate_until_text( + self, + snap_path: Path | None = None, + screen_change_before_first_instruction: bool = False, + validation_instructions: list[NavIns | BaseNavInsID] | None = None, + **kwargs, + ) -> None: """Wrapper of `navigator.navigate_until_text_and_compare`""" self._navigator.navigate_until_text_and_compare( path=self._root_dir, test_case_name=snap_path, screen_change_before_first_instruction=screen_change_before_first_instruction, validation_instructions=validation_instructions or [], - **kwargs + **kwargs, ) - def navigate_while_text_and_compare( # pylint: disable=too-many-arguments,too-many-positional-arguments - self, - navigate_instruction: Union[NavIns, BaseNavInsID], - text: str, - validation_instructions: Optional[List[Union[NavIns, BaseNavInsID]]] = None, - snap_path: Optional[Path] = None, - timeout: int = 300, - screen_change_before_first_instruction: bool = False, - screen_change_after_last_instruction: bool = True) -> None: + def navigate_while_text_and_compare( + self, + navigate_instruction: NavIns | BaseNavInsID, + text: str, + validation_instructions: list[NavIns | BaseNavInsID] | None = None, + snap_path: Path | None = None, + timeout: int = 300, + screen_change_before_first_instruction: bool = False, + screen_change_after_last_instruction: bool = True, + ) -> None: """Navigate while some text is found on the screen content displayed. Function based on @@ -285,7 +277,7 @@ def navigate_while_text_and_compare( # pylint: disable=too-many-arguments,too-m path = self._root_dir test_case_name = snap_path idx = 0 - prev_shot: Optional[Any] = None + prev_shot: Any | None = None start = time.time() if not isinstance(self._backend, SpeculosBackend): if timeout == 30: @@ -293,12 +285,11 @@ def navigate_while_text_and_compare( # pylint: disable=too-many-arguments,too-m self._backend.pause_ticker() - # pylint: disable=protected-access # Wait for screen to change if needed self._navigator._run_instruction( - NavIns(NavInsID.WAIT, (0, )), + NavIns(NavInsID.WAIT, (0,)), timeout, - wait_for_screen_change=screen_change_before_first_instruction + wait_for_screen_change=screen_change_before_first_instruction, ) while self._backend.compare_screen_with_text(text): @@ -308,12 +299,12 @@ def navigate_while_text_and_compare( # pylint: disable=too-many-arguments,too-m # Compare screen, screen already waited self._navigator._run_instruction( - NavIns(NavInsID.WAIT, (0, )), + NavIns(NavInsID.WAIT, (0,)), remaining, wait_for_screen_change=False, path=path, test_case_name=test_case_name, - snap_idx=idx + snap_idx=idx, ) remaining = timeout - (time.time() - start) @@ -322,21 +313,16 @@ def navigate_while_text_and_compare( # pylint: disable=too-many-arguments,too-m # Go to the next screen. # Don't compare because the text may not be on the screen. - self._navigator._run_instruction( - navigate_instruction, - remaining, - wait_for_screen_change=True - ) + self._navigator._run_instruction(navigate_instruction, remaining, wait_for_screen_change=True) idx += 1 - cur_shot = getattr(self._backend, '_last_screenshot', None) + cur_shot = getattr(self._backend, "_last_screenshot", None) # On the final Accept/Reject page, further RIGHT presses often do not # change the framebuffer while OCR can still match a field label # (e.g. "Source") from the session. Stop instead of spinning until # timeout — this is not a device stack overflow. - if (cur_shot is not None and cur_shot == prev_shot and idx >= 1): + if cur_shot is not None and cur_shot == prev_shot and idx >= 1: break prev_shot = cur_shot - # pylint: enable=protected-access if validation_instructions: remaining = timeout - (time.time() - start) @@ -347,19 +333,20 @@ def navigate_while_text_and_compare( # pylint: disable=too-many-arguments,too-m timeout=remaining, screen_change_before_first_instruction=True, screen_change_after_last_instruction=screen_change_after_last_instruction, - snap_start_idx=idx + snap_start_idx=idx, ) self._backend.resume_ticker() - def unsafe_navigate( # pylint: disable=too-many-arguments,too-many-positional-arguments - self, - instructions: List[Union[NavIns, BaseNavInsID]], - snap_path: Optional[Path] = None, - timeout: float = 10.0, - screen_change_before_first_instruction: bool = False, - screen_change_after_last_instruction: bool = True, - snap_start_idx: int = 0) -> None: + def unsafe_navigate( + self, + instructions: list[NavIns | BaseNavInsID], + snap_path: Path | None = None, + timeout: float = 10.0, + screen_change_before_first_instruction: bool = False, + screen_change_after_last_instruction: bool = True, + snap_start_idx: int = 0, + ) -> None: """Navigate using instructions but do not wait for screens to change. Only use this function if consecutive screens are the same. @@ -368,14 +355,14 @@ def unsafe_navigate( # pylint: disable=too-many-arguments,too-many-positional-a """ self._backend.pause_ticker() - # pylint: disable=protected-access + self._navigator._run_instruction( - NavIns(NavInsID.WAIT, (0, )), + NavIns(NavInsID.WAIT, (0,)), timeout, wait_for_screen_change=screen_change_before_first_instruction, path=self._root_dir, test_case_name=snap_path, - snap_idx=snap_start_idx + snap_idx=snap_start_idx, ) for idx, instruction in enumerate(instructions): if idx + 1 != len(instructions) or screen_change_after_last_instruction: @@ -385,21 +372,20 @@ def unsafe_navigate( # pylint: disable=too-many-arguments,too-many-positional-a wait_for_screen_change=False, path=self._root_dir, test_case_name=snap_path, - snap_idx=snap_start_idx + idx + 1 + snap_idx=snap_start_idx + idx + 1, ) else: self._navigator._run_instruction( instruction, timeout, wait_for_screen_change=False, - snap_idx=snap_start_idx + idx + 1 + snap_idx=snap_start_idx + idx + 1, ) - # pylint: enable=protected-access self._backend.resume_ticker() def navigate_to_settings(self, **kwargs) -> int: """Navigate from Home screen to settings.""" - instructions: List[Union[NavIns, BaseNavInsID]] = [] + instructions: list[NavIns | BaseNavInsID] = [] if self._device.is_nano: instructions = [ # Home @@ -410,16 +396,16 @@ def navigate_to_settings(self, **kwargs) -> int: else: instructions = [NavInsID.USE_CASE_HOME_SETTINGS] self.navigate(instructions=instructions, **kwargs) - snap_start_idx = kwargs['snap_start_idx'] if 'snap_start_idx' in kwargs else 0 + snap_start_idx = kwargs["snap_start_idx"] if "snap_start_idx" in kwargs else 0 return snap_start_idx + len(instructions) def toggle_expert_mode(self, **kwargs) -> int: """Enable expert-mode from home screen.""" go_to_settings_kwargs = kwargs.copy() - go_to_settings_kwargs['screen_change_after_last_instruction'] = True + go_to_settings_kwargs["screen_change_after_last_instruction"] = True snap_idx = self.navigate_to_settings(**go_to_settings_kwargs) - instructions: List[Union[NavIns, BaseNavInsID]] = [] + instructions: list[NavIns | BaseNavInsID] = [] if self._device.is_nano: instructions = [ # Expert Mode @@ -433,8 +419,8 @@ def toggle_expert_mode(self, **kwargs) -> int: TezosNavInsID.SETTINGS_TOGGLE_EXPERT_MODE, TezosNavInsID.SETTINGS_EXIT, ] - kwargs['snap_start_idx'] = snap_idx - kwargs['screen_change_before_first_instruction'] = False + kwargs["snap_start_idx"] = snap_idx + kwargs["screen_change_before_first_instruction"] = False self.navigate(instructions=instructions, **kwargs) return snap_idx + len(instructions) @@ -442,10 +428,10 @@ def toggle_expert_mode(self, **kwargs) -> int: def toggle_blindsign(self, **kwargs) -> int: """Enable blindsign from home screen.""" go_to_settings_kwargs = kwargs.copy() - go_to_settings_kwargs['screen_change_after_last_instruction'] = True + go_to_settings_kwargs["screen_change_after_last_instruction"] = True snap_idx = self.navigate_to_settings(**go_to_settings_kwargs) - instructions: List[Union[NavIns, BaseNavInsID]] = [] + instructions: list[NavIns | BaseNavInsID] = [] if self._device.is_nano: instructions = [ # Expert Mode @@ -459,35 +445,32 @@ def toggle_blindsign(self, **kwargs) -> int: TezosNavInsID.SETTINGS_TOGGLE_BLINDSIGNING, TezosNavInsID.SETTINGS_EXIT, ] - kwargs['snap_start_idx'] = snap_idx - kwargs['screen_change_before_first_instruction'] = False + kwargs["snap_start_idx"] = snap_idx + kwargs["screen_change_before_first_instruction"] = False self.navigate(instructions=instructions, **kwargs) return snap_idx + len(instructions) def navigate_forward(self, **kwargs) -> None: """Navigate forward until the text is found.""" - self.navigate_until_text( - navigate_instruction=TezosNavInsID.REVIEW_TX_NEXT, - **kwargs - ) + self.navigate_until_text(navigate_instruction=TezosNavInsID.REVIEW_TX_NEXT, **kwargs) def _navigate_review( - self, - screen_change_before_first_instruction=True, - screen_change_after_last_instruction=False, - **kwargs + self, + screen_change_before_first_instruction=True, + screen_change_after_last_instruction=False, + **kwargs, ) -> None: """Helper to navigate forward in a new flow.""" self.navigate_forward( screen_change_before_first_instruction=screen_change_before_first_instruction, screen_change_after_last_instruction=screen_change_after_last_instruction, - **kwargs + **kwargs, ) def accept_public_key(self, show_qr: bool = False, **kwargs) -> None: """Navigate through public key flow and accept public key""" - validation_instructions: List[Union[NavIns, BaseNavInsID]] = [] + validation_instructions: list[NavIns | BaseNavInsID] = [] if self._device.is_nano: text = "^Approve$" validation_instructions = [NavInsID.BOTH_CLICK] @@ -502,15 +485,11 @@ def accept_public_key(self, show_qr: bool = False, **kwargs) -> None: NavInsID.USE_CASE_ADDRESS_CONFIRMATION_CONFIRM, NavInsID.USE_CASE_STATUS_DISMISS, ] - self._navigate_review( - text=text, - validation_instructions=validation_instructions, - **kwargs - ) + self._navigate_review(text=text, validation_instructions=validation_instructions, **kwargs) def reject_public_key(self, **kwargs) -> None: """Navigate through public key flow and reject""" - validation_instructions: List[Union[NavIns, BaseNavInsID]] = [] + validation_instructions: list[NavIns | BaseNavInsID] = [] if self._device.is_nano: text = "^Reject$" validation_instructions = [NavInsID.BOTH_CLICK] @@ -520,15 +499,11 @@ def reject_public_key(self, **kwargs) -> None: NavInsID.USE_CASE_ADDRESS_CONFIRMATION_CANCEL, NavInsID.USE_CASE_STATUS_DISMISS, ] - self._navigate_review( - text=text, - validation_instructions=validation_instructions, - **kwargs - ) + self._navigate_review(text=text, validation_instructions=validation_instructions, **kwargs) def accept_sign(self, **kwargs) -> None: """Navigate through signing flow and accept to sign""" - validation_instructions: List[Union[NavIns, BaseNavInsID]] = [] + validation_instructions: list[NavIns | BaseNavInsID] = [] if self._device.is_nano: text = "^Accept$" if self._device.type == DeviceType.NANOS: @@ -540,15 +515,11 @@ def accept_sign(self, **kwargs) -> None: NavInsID.USE_CASE_REVIEW_CONFIRM, NavInsID.USE_CASE_STATUS_DISMISS, ] - self._navigate_review( - text=text, - validation_instructions=validation_instructions, - **kwargs - ) + self._navigate_review(text=text, validation_instructions=validation_instructions, **kwargs) def reject_sign(self, **kwargs) -> None: """Navigate through signing flow and reject.""" - validation_instructions: List[Union[NavIns, BaseNavInsID]] = [] + validation_instructions: list[NavIns | BaseNavInsID] = [] if self._device.is_nano: text = "^Reject$" validation_instructions = [NavInsID.BOTH_CLICK] @@ -559,11 +530,7 @@ def reject_sign(self, **kwargs) -> None: TezosNavInsID.REJECT_CHOICE_CONFIRM, NavInsID.USE_CASE_STATUS_DISMISS, ] - self._navigate_review( - text=text, - validation_instructions=validation_instructions, - **kwargs - ) + self._navigate_review(text=text, validation_instructions=validation_instructions, **kwargs) def hard_reject_sign(self, **kwargs) -> None: """Navigate through signing flow and until a hard reject send @@ -572,11 +539,7 @@ def hard_reject_sign(self, **kwargs) -> None: text = "^Application$" else: text = "^Tezos Wallet$" - self._navigate_review( - text=text, - validation_instructions=[], - **kwargs - ) + self._navigate_review(text=text, validation_instructions=[], **kwargs) def expert_accept_sign(self, **kwargs) -> None: """Navigate through the signing expert requirement flow and accept. @@ -589,12 +552,12 @@ def expert_accept_sign(self, **kwargs) -> None: TezosNavInsID.EXPERT_CHOICE_ENABLE, NavInsID.USE_CASE_STATUS_DISMISS, ], - **kwargs + **kwargs, ) def expert_reject_sign(self, **kwargs) -> None: """Navigate through the signing expert requirement flow and reject.""" - validation_instructions: List[Union[NavIns, BaseNavInsID]] = [] + validation_instructions: list[NavIns | BaseNavInsID] = [] if self._device.is_nano: text = "^Home$" validation_instructions = [NavInsID.BOTH_CLICK] @@ -605,11 +568,7 @@ def expert_reject_sign(self, **kwargs) -> None: TezosNavInsID.REJECT_CHOICE_CONFIRM, NavInsID.USE_CASE_STATUS_DISMISS, ] - self._navigate_review( - text=text, - validation_instructions=validation_instructions, - **kwargs - ) + self._navigate_review(text=text, validation_instructions=validation_instructions, **kwargs) def expert_splash_navigate(self, **kwargs) -> None: """Navigate until the expert mode splash screen.""" @@ -621,22 +580,18 @@ def expert_splash_navigate(self, **kwargs) -> None: def accept_sign_error_risk(self, **kwargs) -> None: """Navigate through signing risk warning flow and accept risk.""" - validation_instructions: List[Union[NavIns, BaseNavInsID]] = [] + validation_instructions: list[NavIns | BaseNavInsID] = [] if self._device.is_nano: text = "^Accept risk$" validation_instructions = [NavInsID.BOTH_CLICK] else: text = "^Proceed to Blindsign$" validation_instructions = [TezosNavInsID.WARNING_CHOICE_BLINDSIGN] - self._navigate_review( - text=text, - validation_instructions=validation_instructions, - **kwargs - ) + self._navigate_review(text=text, validation_instructions=validation_instructions, **kwargs) def refuse_sign_error_risk(self, **kwargs) -> None: """Navigate through signing risk warning flow and accept risk.""" - validation_instructions: List[Union[NavIns, BaseNavInsID]] = [] + validation_instructions: list[NavIns | BaseNavInsID] = [] if self._device.is_nano: text = "^Reject$" validation_instructions = [NavInsID.BOTH_CLICK] @@ -647,11 +602,7 @@ def refuse_sign_error_risk(self, **kwargs) -> None: TezosNavInsID.REJECT_CHOICE_CONFIRM, NavInsID.USE_CASE_STATUS_DISMISS, ] - self._navigate_review( - text=text, - validation_instructions=validation_instructions, - **kwargs - ) + self._navigate_review(text=text, validation_instructions=validation_instructions, **kwargs) def accept_sign_blindsign_risk(self, **kwargs) -> None: """Navigate through signing risk warning flow and accept risk.""" @@ -659,24 +610,20 @@ def accept_sign_blindsign_risk(self, **kwargs) -> None: self._navigate_review( text="^Accept risk$", validation_instructions=[NavInsID.BOTH_CLICK], - **kwargs + **kwargs, ) else: self.navigate( instructions=[TezosNavInsID.WARNING_CHOICE_BLINDSIGN], screen_change_before_first_instruction=True, screen_change_after_last_instruction=False, - **kwargs + **kwargs, ) def refuse_sign_blindsign_risk(self, **kwargs) -> None: """Navigate through signing risk warning flow and accept risk.""" if self._device.is_nano: - self._navigate_review( - text="^Reject$", - validation_instructions=[NavInsID.BOTH_CLICK], - **kwargs - ) + self._navigate_review(text="^Reject$", validation_instructions=[NavInsID.BOTH_CLICK], **kwargs) else: self.navigate( instructions=[ @@ -686,7 +633,7 @@ def refuse_sign_blindsign_risk(self, **kwargs) -> None: ], screen_change_before_first_instruction=True, screen_change_after_last_instruction=False, - **kwargs + **kwargs, ) def skip_sign(self, **kwargs) -> None: @@ -700,7 +647,7 @@ def skip_sign(self, **kwargs) -> None: TezosNavInsID.REVIEW_TX_SKIP, TezosNavInsID.SKIP_CHOICE_CONFIRM, ], - **kwargs + **kwargs, ) def skip_reject(self, **kwargs) -> None: @@ -714,5 +661,5 @@ def skip_reject(self, **kwargs) -> None: TezosNavInsID.REVIEW_TX_SKIP, TezosNavInsID.SKIP_CHOICE_REJECT, ], - **kwargs + **kwargs, ) diff --git a/tests/standalone/utils/tezos_encoding.py b/tests/standalone/utils/tezos_encoding.py index 6b4a924fb..a013d2eb1 100644 --- a/tests/standalone/utils/tezos_encoding.py +++ b/tests/standalone/utils/tezos_encoding.py @@ -2,7 +2,7 @@ from decimal import Decimal from hashlib import blake2b -from typing import Any, Dict, List, Optional, Union +from typing import Any import base58 @@ -272,7 +272,7 @@ } -def scrub_input(v: Union[str, bytes]) -> bytes: +def scrub_input(v: str | bytes) -> bytes: """Normalize input to bytes.""" if isinstance(v, bytes): return v @@ -281,25 +281,21 @@ def scrub_input(v: Union[str, bytes]) -> bytes: return bytes.fromhex(v.removeprefix("0x")) except ValueError: return v.encode("ascii") - raise TypeError( - f"A bytes-like object is required (also str), not `{type(v).__name__}`" - ) + raise TypeError(f"A bytes-like object is required (also str), not `{type(v).__name__}`") -def blake2b_32(v: Union[str, bytes] = b""): +def blake2b_32(v: str | bytes = b""): """Return a 32-byte BLAKE2b digest of v.""" return blake2b(scrub_input(v), digest_size=32) -def base58_decode(v: Union[str, bytes]) -> bytes: +def base58_decode(v: str | bytes) -> bytes: """Decode a base58check-encoded Tezos value, stripping the prefix.""" if isinstance(v, str): v = v.encode() try: prefix_len = next( - len(encoding[2]) - for encoding in base58_encodings - if len(v) == encoding[1] and v.startswith(encoding[0]) + len(encoding[2]) for encoding in base58_encodings if len(v) == encoding[1] and v.startswith(encoding[0]) ) except StopIteration as exc: raise ValueError("Invalid encoding, prefix or length mismatch.") from exc @@ -309,17 +305,13 @@ def base58_decode(v: Union[str, bytes]) -> bytes: def base58_encode(v: bytes, prefix: bytes) -> bytes: """Encode bytes as a base58check Tezos value with the given prefix.""" try: - encoding = next( - encoding - for encoding in base58_encodings - if len(v) == encoding[3] and prefix == encoding[0] - ) + encoding = next(encoding for encoding in base58_encodings if len(v) == encoding[3] and prefix == encoding[0]) except StopIteration as exc: raise ValueError("Invalid encoding, prefix or length mismatch.") from exc return base58.b58encode_check(encoding[2] + v) -def format_mutez(value: Optional[Union[int, Decimal]]) -> str: +def format_mutez(value: int | Decimal | None) -> str: """Format a mutez value as a string.""" if value is None: value = 0 @@ -442,7 +434,7 @@ def forge_public_key(value: str) -> bytes: raise ValueError(f"Unrecognized key type: #{prefix}") -def forge_micheline(data: Union[List, Dict]) -> bytes: # pylint: disable=too-many-branches +def forge_micheline(data: list | dict) -> bytes: """Encode a Micheline expression to its binary representation.""" res = [] if isinstance(data, list): @@ -482,21 +474,18 @@ def forge_micheline(data: Union[List, Dict]) -> bytes: # pylint: disable=too-ma return b"".join(res) -def forge_script(script: Dict[str, Any]) -> bytes: +def forge_script(script: dict[str, Any]) -> bytes: """Encode a Tezos script (code + storage) to its binary representation.""" code = forge_micheline(script["code"]) storage = forge_micheline(script["storage"]) return forge_array(code) + forge_array(storage) -def has_parameters(content: Dict[str, Any]) -> bool: +def has_parameters(content: dict[str, Any]) -> bool: """Return True if the operation content has non-default parameters.""" if not content.get("parameters"): return False - return not ( - content["parameters"]["entrypoint"] == "default" - and content["parameters"]["value"] == {"prim": "Unit"} - ) + return not (content["parameters"]["entrypoint"] == "default" and content["parameters"]["value"] == {"prim": "Unit"}) def forge_entrypoint(entrypoint: str) -> bytes: diff --git a/unit-tests/test_operation_parser.c b/unit-tests/test_operation_parser.c index 968a5519b..1b2181ee5 100644 --- a/unit-tests/test_operation_parser.c +++ b/unit-tests/test_operation_parser.c @@ -1344,7 +1344,8 @@ test_check_fa2_transfer_unregistered_decodes_fields(void **state) "33685a70684353716159535141705078504d65796b734a57576e680707000100a4" "01"; char value[256]; - capture_field_value(data, str, "Transfer tokens to", value, sizeof(value)); + capture_field_value(data, str, "Transfer tokens to", value, + sizeof(value)); assert_string_equal(value, "sr1MyCwR83hZphCSqaYSQApPxPMeyksJWWnh"); capture_field_value(data, str, "Token ID", value, sizeof(value)); assert_string_equal(value, "1");