Skip to content

Y333/use new swap workflow #161

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f0e9ccb
Use local swap tests
yogh333 May 26, 2025
92b5ca0
Add standalone swap tests
yogh333 May 29, 2025
773843a
Fix path to Exchange app binaries issue
yogh333 May 29, 2025
c94fb5d
Swap tests OK \o/
yogh333 May 29, 2025
3a7804b
Swap tests OK for all devices
yogh333 Jun 5, 2025
bc7a88d
Reorganize tests + update manifest
yogh333 Jun 6, 2025
71a9c84
Update CI to support swap tests
yogh333 Jun 10, 2025
73ae7ee
launch both standalone and swap tests with reusable ragger wf
yogh333 Jun 13, 2025
8d91953
added missing field when calling reusable ragger wf
yogh333 Jun 13, 2025
1d79fed
Use test_dir to set the pytest directory when using ragger reusable wf
yogh333 Jun 13, 2025
1e6d416
Fix path to get Makefile
yogh333 Jun 13, 2025
fb9cbe1
Install toml dep (required for Ragger)
yogh333 Jun 16, 2025
da755bd
Fix destination path to download Exchange binaries
yogh333 Jun 16, 2025
db9d840
Remove toml dep
yogh333 Jun 16, 2025
5d78cc6
Use callable swap workflow
yogh333 Jun 16, 2025
414941f
Exchnage and Ethereum apps can be already compiled
yogh333 Jun 16, 2025
8dcb190
Use manifest usecase to set compilation flags for Exchange and Ethere…
yogh333 Jun 16, 2025
a4af57a
Use most simple callable swap tests workflow
yogh333 Jun 17, 2025
91ebd78
Fix inputs args
yogh333 Jun 17, 2025
72b1037
Build Ethereum and Exchange if callable workflow and only if required
yogh333 Jun 17, 2025
472ba00
Wait for artifacts to be ready
yogh333 Jun 17, 2025
63d6700
Fix conditional tests
yogh333 Jun 17, 2025
08a4325
Use env variable to set swap test directory
yogh333 Jun 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
23 changes: 16 additions & 7 deletions .github/workflows/build_and_functional_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,23 @@ on:
jobs:
build_application:
name: Build application using the reusable workflow
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_build.yml@v1
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_build.yml@y333/manage_several_pytest_directories
with:
upload_app_binaries_artifact: "compiled_app_binaries"
upload_app_binaries_artifact: "app_boilerplate_binaries"

ragger_tests:
name: Run ragger tests using the reusable workflow
tests_standalone:
name: Run standalone ragger tests using the reusable workflow
needs: build_application
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_ragger_tests.yml@v1
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_ragger_tests.yml@y333/manage_several_pytest_directories
with:
download_app_binaries_artifact: "compiled_app_binaries"
regenerate_snapshots: ${{ inputs.golden_run == 'Open a PR' }}
download_app_binaries_artifact: "app_boilerplate_binaries"
regenerate_snapshots: ${{ github.event_name == 'workflow_dispatch' && inputs.golden_run == 'Open a PR' }}
test_dir: "tests/standalone"

tests_swap:
name: Run swap tests using the callable workflow
needs: build_application
uses: ./.github/workflows/callable_swap_tests.yml
with:
app_build_artifact: "app_boilerplate_binaries"
regenerate_snapshots: ${{ github.event_name == 'workflow_dispatch' && inputs.golden_run == 'Open a PR' }}
114 changes: 114 additions & 0 deletions .github/workflows/callable_swap_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
name: Callable Swap Functional tests

on:
workflow_call:
inputs:
app_build_artifact:
required: true
type: string
exchange_build_artifact:
required: false
default: ''
description: 'If not provided, the workflow will build the Exchange app.'
type: string
ethereum_build_artifact:
required: false
default: ''
description: 'If not provided, the workflow will build the Ethereum app.'
type: string
regenerate_snapshots:
required: false
default: false
type: boolean

env:
SWAP_TEST_DIR: "tests/swap"

jobs:
build_exchange:
name: Build Exchange app using the reusable workflow
if: ${{ inputs.exchange_build_artifact == '' }}
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_build.yml@y333/manage_several_pytest_directories
with:
app_repository: 'LedgerHQ/app-exchange'
app_branch_name: 'develop'
upload_app_binaries_artifact: "app_exchange_binaries"
use_case: 'dbg_full_replay'

build_ethereum:
name: Build Ethereum app using the reusable workflow
if: ${{ inputs.ethereum_build_artifact == '' }}
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_build.yml@y333/manage_several_pytest_directories
with:
app_repository: 'LedgerHQ/app-ethereum'
app_branch_name: 'develop'
upload_app_binaries_artifact: "app_ethereum_binaries"
upload_as_lib_artifact: "ethereum_"
use_case: 'use_test_keys'

get_artifacts_names:
name: Retrieve artifact names
needs: [build_exchange, build_ethereum]
if: ${{ always() }}
outputs:
exchange_build_artifact: ${{ steps.get_exchange_binaries.outputs.exchange_build_artifact }}
ethereum_build_artifact: ${{ steps.get_ethereum_binaries.outputs.ethereum_build_artifact }}
runs-on: ubuntu-latest
steps:
- name: Get Exchange binaries artifact name
id: get_exchange_binaries
run: |
if [ "${{ needs.build_exchange.result }}" = "skipped" ]; then
echo "exchange_build_artifact=${{ inputs.exchange_build_artifact }}" >> "$GITHUB_OUTPUT"
elif [ "${{ needs.build_exchange.result }}" = "success" ]; then
echo "exchange_build_artifact=app_exchange_binaries" >> "$GITHUB_OUTPUT"
else
echo "Not able to retrieve Exchange binaries artifact name" >&2
exit 1
fi
- name: Get Ethereum binaries artifact name
id: get_ethereum_binaries
run: |
if [ "${{ needs.build_ethereum.result }}" = "skipped" ]; then
echo "ethereum_build_artifact=${{ inputs.ethereum_build_artifact }}" >> "$GITHUB_OUTPUT"
elif [ "${{ needs.build_ethereum.result }}" = "success" ]; then
echo "ethereum_build_artifact=app_ethereum_binaries" >> "$GITHUB_OUTPUT"
else
echo "Not able to retrieve Ethereum binaries artifact name" >&2
exit 1
fi
- name: Show artifact names
run: |
echo "Exchange build artifact: ${{ steps.get_exchange_binaries.outputs.exchange_build_artifact }}"
echo "Ethereum build artifact: ${{ steps.get_ethereum_binaries.outputs.ethereum_build_artifact }}"
get_dir_names:
name: Build artifact destination directories
runs-on: ubuntu-latest
outputs:
swap_test_dir: ${{ steps.get_swap_test_dir.outputs.swap_test_dir }}
main_app_dir: ${{ steps.get_swap_test_dir.outputs.main_app_dir }}
steps:
- name: Get swap test directory
id: get_swap_test_dir
run: |
echo "swap_test_dir=${{ env.SWAP_TEST_DIR }}" >> "$GITHUB_OUTPUT"
echo "main_app_dir=${{ env.SWAP_TEST_DIR }}/main_app/exchange/build" >> "$GITHUB_OUTPUT"
- name: Show swap test directory
run: |
echo "Swap test directory: ${{ steps.get_swap_test_dir.outputs.swap_test_dir }}"
echo "Main app directory: ${{ steps.get_swap_test_dir.outputs.main_app_dir }}"
# This job runs the swap functional tests using the reusable workflow
ragger_tests_swap:
name: Run swap ragger tests using the reusable workflow
needs: [get_artifacts_names, get_dir_names]
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_ragger_tests.yml@y333/manage_several_pytest_directories
with:
download_app_binaries_artifact: ${{ inputs.app_build_artifact }}
additional_app_binaries_artifact: ${{ needs.get_artifacts_names.outputs.exchange_build_artifact }}
additional_app_binaries_artifact_dir: ${{ needs.get_dir_names.outputs.main_app_dir }}
lib_binaries_artifact: ${{ needs.get_artifacts_names.outputs.ethereum_build_artifact }}
test_dir: ${{ needs.get_dir_names.outputs.swap_test_dir }}
regenerate_snapshots: ${{ inputs.regenerate_snapshots }}
2 changes: 1 addition & 1 deletion .github/workflows/coding_style_checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ on:
jobs:
check_linting:
name: Check linting using the reusable workflow
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_lint.yml@v1
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_lint.yml@y333/manage_several_pytest_directories
with:
source: './src'
extensions: 'h,c'
2 changes: 1 addition & 1 deletion .github/workflows/guidelines_enforcer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ on:
jobs:
guidelines_enforcer:
name: Call Ledger guidelines_enforcer
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_guidelines_enforcer.yml@v1
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_guidelines_enforcer.yml@y333/manage_several_pytest_directories
8 changes: 4 additions & 4 deletions .github/workflows/python_client_checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ jobs:
- name: Installing PIP dependencies
run: |
pip install pylint
pip install -r tests/requirements.txt
pip install -r tests/standalone/requirements.txt
- name: Lint Python code
run: pylint --rc tests/setup.cfg tests/application_client/
run: pylint --rc tests/standalone/setup.cfg tests/standalone/application_client/

mypy:
name: Type checking
Expand All @@ -36,6 +36,6 @@ jobs:
- name: Installing PIP dependencies
run: |
pip install mypy
pip install -r tests/requirements.txt
pip install -r tests/standalone/requirements.txt
- name: Mypy type checking
run: mypy tests/application_client/
run: mypy tests/standalone/application_client/
21 changes: 0 additions & 21 deletions .github/workflows/swap_ci_workflow.yml

This file was deleted.

7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ unit-tests/build/
unit-tests/coverage/
unit-tests/coverage.info

# Swap tests
tests_swap/snapshots-tmp/
tests_swap/app-ethereum/
tests_swap/app-exchange/
tests_swap/exchange_binaries/
tests_swap/lib_binaries/

# Fuzzing
fuzzing/build/
fuzzing/corpus/
Expand Down
17 changes: 14 additions & 3 deletions ledger_app.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@ build_directory = "./"
sdk = "C"
devices = ["nanox", "nanos+", "stax", "flex"]

[tests]
unit_directory = "./unit-tests/"
pytest_directory = "./tests/"
[unit_tests]
directory = "./unit-tests/"

[pytest.standalone]
directory = "./tests/standalone/"

[pytest.swap]
directory = "./tests/swap/"
[pytest.swap.dependencies]
testing_with_latest = [
{url = "https://github.com/LedgerHQ/app-exchange", ref = "develop", use_case="dbg_use_test_keys"},
{url = "https://github.com/LedgerHQ/app-ethereum", ref = "develop", use_case="use_test_keys"},
]

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion tests/utils.py → tests/standalone/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def verify_version(version: str) -> None:
def _read_makefile() -> List[str]:
"""Read lines from the parent Makefile """

parent = Path(__file__).parent.parent.resolve()
parent = Path(__file__).parent.parent.parent.resolve()
makefile = f"{parent}/Makefile"
with open(makefile, "r", encoding="utf-8") as f_p:
lines = f_p.readlines()
Expand Down
Empty file added tests/swap/__init__.py
Empty file.
Empty file added tests/swap/apps/__init__.py
Empty file.
8 changes: 8 additions & 0 deletions tests/swap/apps/boilerplate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from enum import IntEnum

from ragger.bip import pack_derivation_path
from ragger.utils import create_currency_config, RAPDU

BOL_PATH = "m/44'/1'/0'/0/0"
BOL_CONF = create_currency_config("BOL", "Boilerplate")
BOL_PACKED_DERIVATION_PATH = pack_derivation_path(BOL_PATH)
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
from enum import IntEnum
from typing import Generator, List, Optional
from contextlib import contextmanager

from ragger.backend.interface import BackendInterface, RAPDU
from ragger.bip import pack_derivation_path


MAX_APDU_LEN: int = 255

CLA: int = 0xE0

class P1(IntEnum):
# Parameter 1 for first APDU number.
P1_START = 0x00
# Parameter 1 for maximum APDU number.
P1_MAX = 0x03
# Parameter 1 for screen confirmation for GET_PUBLIC_KEY.
P1_CONFIRM = 0x01

class P2(IntEnum):
# Parameter 2 for last APDU to receive.
P2_LAST = 0x00
# Parameter 2 for more APDU to receive.
P2_MORE = 0x80

class InsType(IntEnum):
GET_VERSION = 0x03
GET_APP_NAME = 0x04
GET_PUBLIC_KEY = 0x05
SIGN_TX = 0x06

class Errors(IntEnum):
SW_DENY = 0x6985
SW_WRONG_P1P2 = 0x6A86
SW_WRONG_DATA_LENGTH = 0x6A87
SW_INS_NOT_SUPPORTED = 0x6D00
SW_CLA_NOT_SUPPORTED = 0x6E00
SW_WRONG_RESPONSE_LENGTH = 0xB000
SW_DISPLAY_BIP32_PATH_FAIL = 0xB001
SW_DISPLAY_ADDRESS_FAIL = 0xB002
SW_DISPLAY_AMOUNT_FAIL = 0xB003
SW_WRONG_TX_LENGTH = 0xB004
SW_TX_PARSING_FAIL = 0xB005
SW_TX_HASH_FAIL = 0xB006
SW_BAD_STATE = 0xB007
SW_SIGNATURE_FAIL = 0xB008
SW_WRONG_AMOUNT = 0xC000
SW_WRONG_ADDRESS = 0xC000


def split_message(message: bytes, max_size: int) -> List[bytes]:
return [message[x:x + max_size] for x in range(0, len(message), max_size)]


class BoilerplateCommandSender:
def __init__(self, backend: BackendInterface) -> None:
self.backend = backend


def get_app_and_version(self) -> RAPDU:
return self.backend.exchange(cla=0xB0, # specific CLA for BOLOS
ins=0x01, # specific INS for get_app_and_version
p1=P1.P1_START,
p2=P2.P2_LAST,
data=b"")


def get_version(self) -> RAPDU:
return self.backend.exchange(cla=CLA,
ins=InsType.GET_VERSION,
p1=P1.P1_START,
p2=P2.P2_LAST,
data=b"")


def get_app_name(self) -> RAPDU:
return self.backend.exchange(cla=CLA,
ins=InsType.GET_APP_NAME,
p1=P1.P1_START,
p2=P2.P2_LAST,
data=b"")


def get_public_key(self, path: str) -> RAPDU:
return self.backend.exchange(cla=CLA,
ins=InsType.GET_PUBLIC_KEY,
p1=P1.P1_START,
p2=P2.P2_LAST,
data=pack_derivation_path(path))


@contextmanager
def get_public_key_with_confirmation(self, path: str) -> Generator[None, None, None]:
with self.backend.exchange_async(cla=CLA,
ins=InsType.GET_PUBLIC_KEY,
p1=P1.P1_CONFIRM,
p2=P2.P2_LAST,
data=pack_derivation_path(path)) as response:
yield response


def sign_tx(self, path: str, transaction: bytes) -> RAPDU:

self.backend.exchange(cla=CLA,
ins=InsType.SIGN_TX,
p1=P1.P1_START,
p2=P2.P2_MORE,
data=pack_derivation_path(path))
messages = split_message(transaction, MAX_APDU_LEN)
idx: int = P1.P1_START + 1

for msg in messages[:-1]:
self.backend.exchange(cla=CLA,
ins=InsType.SIGN_TX,
p1=idx,
p2=P2.P2_MORE,
data=msg)
idx += 1

return self.backend.exchange(cla=CLA,
ins=InsType.SIGN_TX,
p1=idx,
p2=P2.P2_LAST,
data=messages[-1])

def get_async_response(self) -> Optional[RAPDU]:
return self.backend.last_async_response
Loading
Loading