diff --git a/.github/workflows/cflite_cron.yml b/.github/workflows/cflite_cron.yml new file mode 100644 index 00000000..44ac10c2 --- /dev/null +++ b/.github/workflows/cflite_cron.yml @@ -0,0 +1,41 @@ +name: ClusterFuzzLite cron tasks +on: + workflow_dispatch: + push: + branches: + - main # Use your actual default branch here. + schedule: + - cron: '0 13 * * 6' # At 01:00 PM, only on Saturday +permissions: read-all +jobs: + Fuzzing: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - mode: batch + sanitizer: address + - mode: batch + sanitizer: memory + - mode: prune + sanitizer: address + - mode: coverage + sanitizer: coverage + steps: + - name: Build Fuzzers (${{ matrix.mode }} - ${{ matrix.sanitizer }}) + id: build + uses: google/clusterfuzzlite/actions/build_fuzzers@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + language: c # Change this to the language you are fuzzing. + sanitizer: ${{ matrix.sanitizer }} + - name: Run Fuzzers (${{ matrix.mode }} - ${{ matrix.sanitizer }}) + id: run + uses: google/clusterfuzzlite/actions/run_fuzzers@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + fuzz-seconds: 300 # 5 minutes + mode: ${{ matrix.mode }} + sanitizer: ${{ matrix.sanitizer }} + \ No newline at end of file diff --git a/.github/workflows/cflite_pr.yml b/.github/workflows/cflite_pr.yml new file mode 100644 index 00000000..8810c6d6 --- /dev/null +++ b/.github/workflows/cflite_pr.yml @@ -0,0 +1,43 @@ +name: ClusterFuzzLite PR fuzzing +on: + pull_request: + paths: + - "**" +permissions: read-all +jobs: + PR: + runs-on: ubuntu-latest + concurrency: + group: ${{ github.workflow }}-${{ matrix.sanitizer }}-${{ github.ref }} + cancel-in-progress: true + strategy: + fail-fast: false + matrix: + sanitizer: [address, undefined, memory] # Override this with the sanitizers you want. + steps: + - name: Build Fuzzers (${{ matrix.sanitizer }}) + id: build + uses: google/clusterfuzzlite/actions/build_fuzzers@v1 + with: + language: c # Change this to the language you are fuzzing. + github-token: ${{ secrets.GITHUB_TOKEN }} + sanitizer: ${{ matrix.sanitizer }} + # Optional but recommended: used to only run fuzzers that are affected + # by the PR. + # storage-repo: https://${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/OWNER/STORAGE-REPO-NAME.git + # storage-repo-branch: main # Optional. Defaults to "main" + # storage-repo-branch-coverage: gh-pages # Optional. Defaults to "gh-pages". + - name: Run Fuzzers (${{ matrix.sanitizer }}) + id: run + uses: google/clusterfuzzlite/actions/run_fuzzers@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + fuzz-seconds: 300 # 5 minutes + mode: "code-change" + sanitizer: ${{ matrix.sanitizer }} + output-sarif: true + # Optional but recommended: used to download the corpus produced by + # batch fuzzing. + # storage-repo: https://${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/OWNER/STORAGE-REPO-NAME.git + # storage-repo-branch: main # Optional. Defaults to "main" + # storage-repo-branch-coverage: gh-pages # Optional. Defaults to "gh-pages". diff --git a/PLUGIN_SPECIFICATION.md b/PLUGIN_SPECIFICATION.md index 70bd44f5..3ed21927 100644 --- a/PLUGIN_SPECIFICATION.md +++ b/PLUGIN_SPECIFICATION.md @@ -27,9 +27,9 @@ For the smart contracts implemented, the functions covered by the plugin shall b |Contract | Function | Selector | Displayed Parameters | | --- | --- | --- | --- | -|Stakewise | burnOsToken | `0x066055e0`|
uint128 osTokenShares
uint256 timestamp
uint256 exitQueueIndex
| -|Stakewise | claimExitedAssets | `0x8697d2c2`|
address receiver
address referrer
| -|Stakewise | deposit | `0xf9609f08`|
type ParameterName
| +|Stakewise | burnOsToken | `0x066055e0`|
uint128 osTokenShares
| +|Stakewise | claimExitedAssets | `0x8697d2c2`|
uint256 positionTicket
uint256 timestamp
uint256 exitQueueIndex
| +|Stakewise | deposit | `0xf9609f08`|
address receiver
address referrer
| |Stakewise | enterExitQueue | `0x8ceab9aa`|
uint256 shares
address receiver
| |Stakewise | mintOsToken | `0x201b9eb5`|
address receiver
uint256 osTokenShares
address referrer
| |Stakewise | redeem | `0x7bde82f2`|
uint256 shares
address receiver
| diff --git a/fuzzing/CMakeLists.txt b/fuzzing/CMakeLists.txt index cda1168e..eb6dee47 100644 --- a/fuzzing/CMakeLists.txt +++ b/fuzzing/CMakeLists.txt @@ -73,6 +73,7 @@ add_compile_definitions( include_directories( ${BOLOS_SDK}/include + ${BOLOS_SDK}/lib_standard_app ${BOLOS_SDK}/lib_cxng/include ${BOLOS_SDK}/lib_cxng/src ${BOLOS_SDK}/target/nanox/include @@ -91,11 +92,16 @@ add_executable(fuzz ${SRC_DIR}/handle_provide_token.c ${SRC_DIR}/handle_query_contract_ui.c ${SRC_DIR}/handle_query_contract_id.c + ${SRC_DIR}/utils.c # Ethereum SDK ${ETH_DIR}/src/common_utils.c ${ETH_DIR}/src/plugin_utils.c + # sdk utils + ${BOLOS_SDK}/src/ledger_assert.c + ${BOLOS_SDK}/lib_standard_app/format.c + # cxng ${BOLOS_SDK}/lib_cxng/src/cx_hash.c ${BOLOS_SDK}/lib_cxng/src/cx_sha256.c diff --git a/fuzzing/mocks.c b/fuzzing/mocks.c index 5b5358cb..1c0ec847 100644 --- a/fuzzing/mocks.c +++ b/fuzzing/mocks.c @@ -1,4 +1,7 @@ #include "plugin.h" +#include "lcx_common.h" +#include "lcx_hash.h" +#include size_t strlcat(char *dst, const char *src, size_t size) { size_t srclen; /* Length of source string */ @@ -33,3 +36,13 @@ size_t strlcpy(char *dst, const char *src, size_t size) { return (srclen); } + +cx_err_t cx_keccak_256_hash_iovec(const cx_iovec_t *iovec, + size_t iovec_len, + uint8_t digest[static CX_KECCAK_256_SIZE]) { + return CX_OK; +} + +void os_sched_exit(bolos_task_status_t exit_code) { + return; +} \ No newline at end of file diff --git a/src/handle_provide_parameter.c b/src/handle_provide_parameter.c index 0a71e28d..6115775c 100644 --- a/src/handle_provide_parameter.c +++ b/src/handle_provide_parameter.c @@ -1,4 +1,5 @@ #include "plugin.h" +#include "utils.h" static void handle_stakewise_deposit(ethPluginProvideParameter_t *msg, context_t *context) { switch (context->next_param) { @@ -135,6 +136,7 @@ static void handle_stakewise_mint_os_token(ethPluginProvideParameter_t *msg, con } static void handle_eigenlayer_delegate_to(ethPluginProvideParameter_t *msg, context_t *context) { + uint8_t expected_bytes[3] = {0x60, 0x40, 0x41}; switch (context->next_param) { case OPERATOR: copy_address(context->receiver, msg->parameter, sizeof(context->receiver)); @@ -142,7 +144,12 @@ static void handle_eigenlayer_delegate_to(ethPluginProvideParameter_t *msg, cont break; case OFFSET_1: - context->next_param = APPROVER_SALT; + if (compare_last_n_bytes(msg->parameter, &expected_bytes[0], 1)) { + context->next_param = APPROVER_SALT; + } else { + msg->result = ETH_PLUGIN_RESULT_ERROR; + context->next_param = UNEXPECTED_PARAMETER; + } break; case APPROVER_SALT: @@ -151,7 +158,12 @@ static void handle_eigenlayer_delegate_to(ethPluginProvideParameter_t *msg, cont break; case OFFSET_2: - context->next_param = EXPIRY; + if (compare_last_n_bytes(msg->parameter, &expected_bytes[1], 1)) { + context->next_param = EXPIRY; + } else { + msg->result = ETH_PLUGIN_RESULT_ERROR; + context->next_param = UNEXPECTED_PARAMETER; + } break; case EXPIRY: @@ -160,7 +172,12 @@ static void handle_eigenlayer_delegate_to(ethPluginProvideParameter_t *msg, cont break; case ARRAY_LEN_1: - context->next_param = SIGNATURE_1; + if (compare_last_n_bytes(msg->parameter, &expected_bytes[2], 1)) { + context->next_param = SIGNATURE_1; + } else { + msg->result = ETH_PLUGIN_RESULT_ERROR; + context->next_param = UNEXPECTED_PARAMETER; + } break; case SIGNATURE_1: @@ -214,13 +231,31 @@ static void handle_eigenlayer_inc_dec_delegated_shares(ethPluginProvideParameter static void handle_eigenlayer_complete_queued_withdrawal(ethPluginProvideParameter_t *msg, context_t *context) { + uint8_t expected_bytes[5] = { + 0x80, + 0x01, + 0xe0, + 0x01, + 0x20, + }; switch (context->next_param) { case OFFSET_1: - context->next_param = OFFSET_2; + if (compare_last_n_bytes(msg->parameter, &expected_bytes[0], 1)) { + context->next_param = OFFSET_2; + } else { + msg->result = ETH_PLUGIN_RESULT_ERROR; + context->next_param = UNEXPECTED_PARAMETER; + } + break; case OFFSET_2: - context->next_param = MIDDLEWARE_TIMES_INDEX; + if (compare_last_n_bytes(msg->parameter, &expected_bytes[1], 2)) { + context->next_param = MIDDLEWARE_TIMES_INDEX; + } else { + msg->result = ETH_PLUGIN_RESULT_ERROR; + context->next_param = UNEXPECTED_PARAMETER; + } break; case MIDDLEWARE_TIMES_INDEX: @@ -259,20 +294,35 @@ static void handle_eigenlayer_complete_queued_withdrawal(ethPluginProvideParamet break; case START_BLOCK: - copy_parameter(context->uint32_var, msg->parameter + 28, sizeof(context->uint32_var)); + copy_parameter(context->os_token_shares, msg->parameter + 28, 4); context->next_param = OFFSET_3; break; case OFFSET_3: - context->next_param = OFFSET_4; + if (compare_last_n_bytes(msg->parameter, &expected_bytes[2], 1)) { + context->next_param = OFFSET_4; + } else { + msg->result = ETH_PLUGIN_RESULT_ERROR; + context->next_param = UNEXPECTED_PARAMETER; + } break; case OFFSET_4: - context->next_param = ARRAY_LEN_1; + if (compare_last_n_bytes(msg->parameter, &expected_bytes[3], 2)) { + context->next_param = ARRAY_LEN_1; + } else { + msg->result = ETH_PLUGIN_RESULT_ERROR; + context->next_param = UNEXPECTED_PARAMETER; + } break; case ARRAY_LEN_1: - context->next_param = STRATEGY; + if (compare_last_n_bytes(msg->parameter, &expected_bytes[1], 1)) { + context->next_param = STRATEGY; + } else { + msg->result = ETH_PLUGIN_RESULT_ERROR; + context->next_param = UNEXPECTED_PARAMETER; + } break; case STRATEGY: @@ -282,7 +332,12 @@ static void handle_eigenlayer_complete_queued_withdrawal(ethPluginProvideParamet break; case ARRAY_LEN_2: - context->next_param = SHARES; + if (compare_last_n_bytes(msg->parameter, &expected_bytes[1], 1)) { + context->next_param = SHARES; + } else { + msg->result = ETH_PLUGIN_RESULT_ERROR; + context->next_param = UNEXPECTED_PARAMETER; + } break; case SHARES: @@ -291,7 +346,12 @@ static void handle_eigenlayer_complete_queued_withdrawal(ethPluginProvideParamet break; case ARRAY_LEN_3: - context->next_param = TOKENS; + if (compare_last_n_bytes(msg->parameter, &expected_bytes[1], 1)) { + context->next_param = TOKENS; + } else { + msg->result = ETH_PLUGIN_RESULT_ERROR; + context->next_param = UNEXPECTED_PARAMETER; + } break; case TOKENS: @@ -310,25 +370,57 @@ static void handle_eigenlayer_complete_queued_withdrawal(ethPluginProvideParamet static void handle_eigenlayer_queue_withdrawal(ethPluginProvideParameter_t *msg, context_t *context) { + uint8_t expected_bytes[5] = { + 0x20, + 0x01, + 0x60, + 0xa0, + }; switch (context->next_param) { case OFFSET_1: - context->next_param = ARRAY_LEN_1; + if (compare_last_n_bytes(msg->parameter, &expected_bytes[0], 1)) { + context->next_param = ARRAY_LEN_1; + } else { + msg->result = ETH_PLUGIN_RESULT_ERROR; + context->next_param = UNEXPECTED_PARAMETER; + } + break; case ARRAY_LEN_1: - context->next_param = OFFSET_2; + if (compare_last_n_bytes(msg->parameter, &expected_bytes[1], 1)) { + context->next_param = OFFSET_2; + } else { + msg->result = ETH_PLUGIN_RESULT_ERROR; + context->next_param = UNEXPECTED_PARAMETER; + } break; case OFFSET_2: - context->next_param = OFFSET_3; + if (compare_last_n_bytes(msg->parameter, &expected_bytes[0], 1)) { + context->next_param = OFFSET_3; + } else { + msg->result = ETH_PLUGIN_RESULT_ERROR; + context->next_param = UNEXPECTED_PARAMETER; + } break; case OFFSET_3: - context->next_param = OFFSET_4; + if (compare_last_n_bytes(msg->parameter, &expected_bytes[2], 1)) { + context->next_param = OFFSET_4; + } else { + msg->result = ETH_PLUGIN_RESULT_ERROR; + context->next_param = UNEXPECTED_PARAMETER; + } break; case OFFSET_4: - context->next_param = WITHDRAWER; + if (compare_last_n_bytes(msg->parameter, &expected_bytes[3], 1)) { + context->next_param = WITHDRAWER; + } else { + msg->result = ETH_PLUGIN_RESULT_ERROR; + context->next_param = UNEXPECTED_PARAMETER; + } break; case WITHDRAWER: @@ -337,7 +429,12 @@ static void handle_eigenlayer_queue_withdrawal(ethPluginProvideParameter_t *msg, break; case ARRAY_LEN_2: - context->next_param = STRATEGY; + if (compare_last_n_bytes(msg->parameter, &expected_bytes[1], 1)) { + context->next_param = STRATEGY; + } else { + msg->result = ETH_PLUGIN_RESULT_ERROR; + context->next_param = UNEXPECTED_PARAMETER; + } break; case STRATEGY: @@ -346,7 +443,12 @@ static void handle_eigenlayer_queue_withdrawal(ethPluginProvideParameter_t *msg, break; case ARRAY_LEN_3: - context->next_param = SHARES; + if (compare_last_n_bytes(msg->parameter, &expected_bytes[1], 1)) { + context->next_param = SHARES; + } else { + msg->result = ETH_PLUGIN_RESULT_ERROR; + context->next_param = UNEXPECTED_PARAMETER; + } break; case SHARES: diff --git a/src/handle_query_contract_ui.c b/src/handle_query_contract_ui.c index 824f0c82..9edb0d9b 100644 --- a/src/handle_query_contract_ui.c +++ b/src/handle_query_contract_ui.c @@ -264,12 +264,7 @@ static bool eigenlayer_complete_queued_withdrawal_ui(ethQueryContractUI_t *msg, case 4: strlcpy(msg->title, "Start Block", msg->titleLength); - amountToString(context->uint32_var, - sizeof(context->uint32_var), - 0, - "", - msg->msg, - msg->msgLength); + amountToString(context->os_token_shares, 4, 0, "", msg->msg, msg->msgLength); return true; case 5: diff --git a/src/plugin.h b/src/plugin.h index 1dc7208b..0cdae3cd 100644 --- a/src/plugin.h +++ b/src/plugin.h @@ -63,7 +63,8 @@ extern const uint32_t SELECTORS[SELECTOR_COUNT]; // Enumeration used to parse the smart contract data. // EDIT THIS: Adapt the parameter names here. typedef enum { - RECEIVER = 0, // Address + // Stakewise + RECEIVER = 0, REFERRER, OS_TOKEN_SHARES, VAULT_SHARES, @@ -71,7 +72,7 @@ typedef enum { TIMESTAMP, EXIT_QUEUE_INDEX, OWNER, - // EIGENLAYER + // Eigenlayer OPERATOR, OFFSET_1, OFFSET_2, @@ -94,7 +95,7 @@ typedef enum { MIDDLEWARE_TIMES_INDEX, RECEIVE_AS_TOKENS, SHARES, - // Sybiotic + // Symbiotic DEADLINE, UNEXPECTED_PARAMETER, } parameter; @@ -111,7 +112,6 @@ typedef struct context_s { uint8_t timestamp[INT256_LENGTH]; uint8_t exit_queue_index[INT256_LENGTH]; - uint8_t uint32_var[4]; uint8_t bool_var; // For parsing data. diff --git a/src/utils.c b/src/utils.c index 1812fd3a..c3918143 100644 --- a/src/utils.c +++ b/src/utils.c @@ -1,4 +1,5 @@ #include +#include "utils.h" const char HEX_CHARS[] = "0123456789ABCDEF"; @@ -29,3 +30,14 @@ void display_first_and_last_bytes(ethQueryContractUI_t *msg, next_ptr += 2; } } + +bool compare_last_n_bytes(const uint8_t *parameter, const uint8_t *expected_bytes, size_t n) { + if (memcmp(parameter, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 32 - n) != + 0) { + return false; + } + if (memcmp(¶meter[32 - n], expected_bytes, n) != 0) { + return false; + } + return true; +} diff --git a/src/utils.h b/src/utils.h index 814c9ded..01bac53b 100644 --- a/src/utils.h +++ b/src/utils.h @@ -4,4 +4,4 @@ void display_first_and_last_bytes(ethQueryContractUI_t *msg, uint8_t *address, int array_offset, int first_last_size); -void display_hexa(ethQueryContractUI_t *msg, uint8_t *data, size_t length); \ No newline at end of file +bool compare_last_n_bytes(const uint8_t *parameter, const uint8_t *expected_bytes, size_t n); diff --git a/tests/test_eigenlayer.py b/tests/test_eigenlayer.py index e904eea7..00589059 100644 --- a/tests/test_eigenlayer.py +++ b/tests/test_eigenlayer.py @@ -38,6 +38,26 @@ def test_eigenlayer_delegate_to(ledger_utils): ledger_utils.send_tx_and_compare_snapshots(tx_params) +def test_eigenlayer_delegate_to_different_length(ledger_utils): + operator = bytes.fromhex("0102030000000000000000000000000000030201") + signature_expiry = ( + "0x01020322222222222222222222222222222222222222222222222222222222223333333333333333333333333333333333333333333333333333333333040506", + 123456789, + ) + approver_salt = "0xfffefdfffffffffffffffffffffffffffffffffffffffffffffffffffffcfbfa" + + data = contract.encode_abi( + "delegateTo", [operator, signature_expiry, approver_salt] + ) + + ledger_utils.set_external_plugin( + contract.address, + data, + ) + tx_params = get_default_tx_params(contract.address, data) + ledger_utils.send_tx_expect_error(tx_params) + + def test_eigenlayer_increase_delegated_shares_wallet_address(ledger_utils): receiver = ledger_utils.get() strategy = bytes.fromhex("0405060000000000000000000000000000070809") @@ -98,6 +118,43 @@ def test_eigenlayer_decrease_delegated_shares(ledger_utils): ledger_utils.send_tx_and_compare_snapshots(tx_params) +def test_eigenlayer_complete_queued_withdrawal_more_than_one_element(ledger_utils): + if ledger_utils.firmware.device.startswith("stax"): + # This test is failing in stax when we try to show + # 8 screens or more: see https://github.com/LedgerHQ/app-plugin-boilerplate/issues/152 + return + withdrawal = ( + bytes.fromhex("0102030000000000000000000000000000040506"), + bytes.fromhex("07080900000000000000000000000000000a0b0c"), + bytes.fromhex("0d0e0f0000000000000000000000000000101112"), + 19, + 99, + [ + bytes.fromhex("13141500000000000000000000000000001c1d1e"), + bytes.fromhex("1f20210000000000000000000000000000222324"), + ], + [10, 11], + ) + tokens = [ + bytes.fromhex("252627000000000000000000000000000028292a"), + bytes.fromhex("2b2c2d00000000000000000000000000002e2f30"), + ] + middleware_times_index = 255 + receive_as_tokens = True + + data = contract.encode_abi( + "completeQueuedWithdrawal", + [withdrawal, tokens, middleware_times_index, receive_as_tokens], + ) + + ledger_utils.set_external_plugin( + contract.address, + data, + ) + tx_params = get_default_tx_params(contract.address, data) + ledger_utils.send_tx_expect_error(tx_params) + + def test_eigenlayer_complete_queued_withdrawal(ledger_utils): if ledger_utils.firmware.device.startswith("stax"): # This test is failing in stax when we try to show @@ -148,6 +205,49 @@ def test_eigenlayer_queue_withdrawal(ledger_utils): ledger_utils.send_tx_and_compare_snapshots(tx_params) +def test_eigenlayer_queue_withdrawal_different_offset(ledger_utils): + withdrawal = [ + ( + [bytes.fromhex("0102030000000000000000000000000000040506")], + [255], + bytes.fromhex("07080900000000000000000000000000000a0b0c"), + ) + ] + + data = contract.encode_abi("queueWithdrawals", [withdrawal]) + data = data[:264] + "7" + data[265:] + print("DATA", data) + + ledger_utils.set_external_plugin( + contract.address, + data, + ) + tx_params = get_default_tx_params(contract.address, data) + ledger_utils.send_tx_expect_error(tx_params) + + +def test_eigenlayer_queue_withdrawal_more_than_one_element(ledger_utils): + withdrawal = [ + ( + [ + bytes.fromhex("0102030000000000000000000000000000040506"), + bytes.fromhex("07080900000000000000000000000000000a0b0c"), + ], + [255, 254], + bytes.fromhex("0d0e0f0000000000000000000000000000101112"), + ) + ] + + data = contract.encode_abi("queueWithdrawals", [withdrawal]) + + ledger_utils.set_external_plugin( + contract.address, + data, + ) + tx_params = get_default_tx_params(contract.address, data) + ledger_utils.send_tx_expect_error(tx_params) + + def test_eigenlayer_undelegate(ledger_utils): staker = bytes.fromhex("07080900000000000000000000000000000a0b0c") diff --git a/tests/utils.py b/tests/utils.py index 2bb4c41d..6b3ce736 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -6,6 +6,7 @@ from web3 import Web3 from ledger_app_clients.ethereum.utils import recover_transaction from ragger.navigator import NavInsID +import ragger from eth_typing import ChainId from ledger_app_clients.ethereum.client import EthAppClient from ledger_app_clients.ethereum.utils import get_selector_from_data @@ -63,27 +64,11 @@ def get_default_tx_params(contract, data): return tx_params -def send_tx_and_compare_snapshots(client, navigator, wallet_addr, test_name, tx_params): - # send the transaction - with client.sign(DERIVATION_PATH, tx_params): - # Validate the on-screen request by performing the navigation appropriate for this device - navigator.navigate_until_text_and_compare( - NavInsID.RIGHT_CLICK, - [NavInsID.BOTH_CLICK], - "Accept", - ROOT_SCREENSHOT_PATH, - test_name, - ) - # verify signature - vrs = ResponseParser.signature(client.response().data) - addr = recover_transaction(tx_params, vrs) - assert addr == wallet_addr.get() - - class LedgerUtils: client: EthAppClient test_name: str firmware: str + navigator: ragger.navigator.Navigator def __init__(self, backend, navigator, firmware, test_name): self.client = EthAppClient(backend) @@ -104,6 +89,14 @@ def set_external_plugin(self, contract_address, data): get_selector_from_data(data), ) + def send_tx_expect_error(self, tx_params): + try: + with self.client.sign(DERIVATION_PATH, tx_params): + pass + except ragger.error.ExceptionRAPDU as e: + return + assert False + def send_tx_and_compare_snapshots(self, tx_params): # send the transaction with self.client.sign(DERIVATION_PATH, tx_params): @@ -123,7 +116,7 @@ def send_tx_and_compare_snapshots(self, tx_params): end_text, ROOT_SCREENSHOT_PATH, self.test_name, - screen_change_after_last_instruction=False + screen_change_after_last_instruction=False, ) # verify signature vrs = ResponseParser.signature(self.client.response().data)