Skip to content

Commit

Permalink
Tests: have a proper structure in the CAL to manage coin configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
fbeutin-ledger committed Apr 18, 2024
1 parent 08e6944 commit 9113daa
Show file tree
Hide file tree
Showing 16 changed files with 128 additions and 170 deletions.
83 changes: 32 additions & 51 deletions test/python/apps/cal.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import Optional
from dataclasses import dataclass

from ragger.utils import prefix_with_len

Expand All @@ -7,7 +8,7 @@
# Eth family
from .ethereum import ETH_PACKED_DERIVATION_PATH, ETH_CONF
from .ethereum import ETC_PACKED_DERIVATION_PATH, ETC_CONF
from .ethereum import BSC_PACKED_DERIVATION_PATH, BSC_CONF
from .ethereum import BSC_PACKED_DERIVATION_PATH, BSC_CONF, BSC_CONF_LEGACY

from .litecoin import LTC_PACKED_DERIVATION_PATH, LTC_CONF
from .bitcoin import BTC_PACKED_DERIVATION_PATH, BTC_CONF
Expand All @@ -19,64 +20,44 @@
from .tron import TRX_PACKED_DERIVATION_PATH, TRX_CONF
from .tron import TRX_USDT_CONF, TRX_USDC_CONF, TRX_TUSD_CONF, TRX_USDD_CONF

TICKER_ID_TO_CONF = {
"ETC": ETC_CONF,
"ETH": ETH_CONF,
"BTC": BTC_CONF,
"LTC": LTC_CONF,
"XLM": XLM_CONF,
"SOL": SOL_CONF,
"XRP": XRP_CONF,
"XTZ": XTZ_CONF,
"BNB": BSC_CONF,
"DOT": DOT_CONF,
"TRX": TRX_CONF,
"USDT": TRX_USDT_CONF,
"USDC": TRX_USDC_CONF,
"TUSD": TRX_TUSD_CONF,
"USDD": TRX_USDD_CONF,
}
@dataclass
class CurrencyConfiguration:
ticker: str
conf: bytes
packed_derivation_path: bytes

# Get the correct coin configuration, can specify a signer to use instead of the correct ledger test one
def get_conf_for_ticker(self, overload_signer: Optional[SigningAuthority]=None) -> bytes:
currency_conf = self.conf
signed_conf = sign_currency_conf(currency_conf, overload_signer)
derivation_path = self.packed_derivation_path
return prefix_with_len(currency_conf) + signed_conf + prefix_with_len(derivation_path)

ETC_CURRENCY_CONFIGURATION = CurrencyConfiguration(ticker="ETC", conf=ETC_CONF, packed_derivation_path=ETC_PACKED_DERIVATION_PATH)
ETH_CURRENCY_CONFIGURATION = CurrencyConfiguration(ticker="ETH", conf=ETH_CONF, packed_derivation_path=ETH_PACKED_DERIVATION_PATH)
BTC_CURRENCY_CONFIGURATION = CurrencyConfiguration(ticker="BTC", conf=BTC_CONF, packed_derivation_path=BTC_PACKED_DERIVATION_PATH)
LTC_CURRENCY_CONFIGURATION = CurrencyConfiguration(ticker="LTC", conf=LTC_CONF, packed_derivation_path=LTC_PACKED_DERIVATION_PATH)
XLM_CURRENCY_CONFIGURATION = CurrencyConfiguration(ticker="XLM", conf=XLM_CONF, packed_derivation_path=XLM_PACKED_DERIVATION_PATH)
SOL_CURRENCY_CONFIGURATION = CurrencyConfiguration(ticker="SOL", conf=SOL_CONF, packed_derivation_path=SOL_PACKED_DERIVATION_PATH)
XRP_CURRENCY_CONFIGURATION = CurrencyConfiguration(ticker="XRP", conf=XRP_CONF, packed_derivation_path=XRP_PACKED_DERIVATION_PATH)
XTZ_CURRENCY_CONFIGURATION = CurrencyConfiguration(ticker="XTZ", conf=XTZ_CONF, packed_derivation_path=XTZ_PACKED_DERIVATION_PATH)
BNB_CURRENCY_CONFIGURATION = CurrencyConfiguration(ticker="BNB", conf=BSC_CONF, packed_derivation_path=BSC_PACKED_DERIVATION_PATH)
BNB_LEGACY_CURRENCY_CONFIGURATION = CurrencyConfiguration(ticker="BNB", conf=BSC_CONF_LEGACY, packed_derivation_path=BSC_PACKED_DERIVATION_PATH)
DOT_CURRENCY_CONFIGURATION = CurrencyConfiguration(ticker="DOT", conf=DOT_CONF, packed_derivation_path=DOT_PACKED_DERIVATION_PATH)
TRX_CURRENCY_CONFIGURATION = CurrencyConfiguration(ticker="TRX", conf=TRX_CONF, packed_derivation_path=TRX_PACKED_DERIVATION_PATH)
USDT_CURRENCY_CONFIGURATION = CurrencyConfiguration(ticker="USDT", conf=TRX_USDT_CONF, packed_derivation_path=TRX_PACKED_DERIVATION_PATH)
USDC_CURRENCY_CONFIGURATION = CurrencyConfiguration(ticker="USDC", conf=TRX_USDC_CONF, packed_derivation_path=TRX_PACKED_DERIVATION_PATH)
TUSD_CURRENCY_CONFIGURATION = CurrencyConfiguration(ticker="TUSD", conf=TRX_TUSD_CONF, packed_derivation_path=TRX_PACKED_DERIVATION_PATH)
USDD_CURRENCY_CONFIGURATION = CurrencyConfiguration(ticker="USDD", conf=TRX_USDD_CONF, packed_derivation_path=TRX_PACKED_DERIVATION_PATH)

TICKER_ID_TO_PACKED_DERIVATION_PATH = {
"ETC": ETC_PACKED_DERIVATION_PATH,
"ETH": ETH_PACKED_DERIVATION_PATH,
"BTC": BTC_PACKED_DERIVATION_PATH,
"LTC": LTC_PACKED_DERIVATION_PATH,
"XLM": XLM_PACKED_DERIVATION_PATH,
"SOL": SOL_PACKED_DERIVATION_PATH,
"XRP": XRP_PACKED_DERIVATION_PATH,
"XTZ": XTZ_PACKED_DERIVATION_PATH,
"BNB": BSC_PACKED_DERIVATION_PATH,
"DOT": DOT_PACKED_DERIVATION_PATH,
"TRX": TRX_PACKED_DERIVATION_PATH,
"USDT": TRX_PACKED_DERIVATION_PATH,
"USDC": TRX_PACKED_DERIVATION_PATH,
"TUSD": TRX_PACKED_DERIVATION_PATH,
"USDD": TRX_PACKED_DERIVATION_PATH,
}

# Helper that can be called from outside if we want to generate errors easily
def get_currency_conf(ticker_id: str) -> bytes:
return TICKER_ID_TO_CONF[ticker_id]

# Helper that can be called from outside if we want to generate errors easily
def sign_currency_conf(currency_conf: bytes, overload_signer: Optional[SigningAuthority]=None) -> bytes:
if overload_signer:
if overload_signer is not None:
signer = overload_signer
else:
signer = LEDGER_SIGNER

return signer.sign(currency_conf)

# Helper that can be called from outside if we want to generate errors easily
def get_derivation_path(ticker_id: str) -> bytes:
return TICKER_ID_TO_PACKED_DERIVATION_PATH[ticker_id]

# Get the correct coin configuration, can specify a signer to use instead of the correct ledger test one
def get_conf_for_ticker(ticker_id: str, overload_signer: Optional[SigningAuthority]=None) -> bytes:
if ticker_id is None:
return None
currency_conf = get_currency_conf(ticker_id)
signed_conf = sign_currency_conf(currency_conf, overload_signer)
derivation_path = get_derivation_path(ticker_id)
return prefix_with_len(currency_conf) + signed_conf + prefix_with_len(derivation_path)
41 changes: 19 additions & 22 deletions test/python/apps/exchange_test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from ragger.error import ExceptionRAPDU

from .exchange import ExchangeClient, Rate, SubCommand, Errors
from .exchange_transaction_builder import get_partner_curve, extract_payout_ticker, extract_refund_ticker, get_credentials, craft_and_sign_tx
from .exchange_transaction_builder import get_partner_curve, get_credentials, craft_and_sign_tx
from . import cal as cal
from .signing_authority import SigningAuthority, LEDGER_SIGNER

Expand All @@ -21,7 +21,7 @@
class ExchangeTestRunner:

# You will need to define the following elements in the child application:
# currency_ticker: str
# currency_configuration: CurrencyConfiguration
# valid_destination_1: str
# valid_destination_memo_1: str
# valid_destination_2: str
Expand Down Expand Up @@ -87,7 +87,7 @@ def run_test(self, function_to_test: str):
self.exchange_navigation_helper.set_test_name_suffix("_" + function_to_test)
getattr(self, TEST_METHOD_PREFIX + function_to_test)(use_legacy_flow)

def _perform_valid_exchange(self, subcommand, tx_infos, fees, ui_validation):
def _perform_valid_exchange(self, subcommand, tx_infos, from_currency_configuration, to_currency_configuration, fees, ui_validation):
# Initialize the exchange client plugin that will format and send the APDUs to the device
ex = ExchangeClient(self.backend, Rate.FIXED, subcommand)

Expand All @@ -109,18 +109,15 @@ def _perform_valid_exchange(self, subcommand, tx_infos, fees, ui_validation):
ex.process_transaction(tx)
ex.check_transaction_signature(tx_signature)

# Ask our fake CAL the coin configuration for both payout and refund tickers (None for refund in case of FUND or SELL)
payout_ticker = extract_payout_ticker(subcommand, tx_infos)
payout_configuration = cal.get_conf_for_ticker(payout_ticker)
# Ask our fake CAL the coin configuration for both FROM and TO currencies (None for TO in case of FUND or SELL)
from_configuration = cal.get_conf_for_ticker(from_currency_configuration)
to_configuration = cal.get_conf_for_ticker(to_currency_configuration)

if subcommand == SubCommand.SWAP or subcommand == SubCommand.SWAP_NG:
ex.check_payout_address(payout_configuration)

refund_ticker = extract_refund_ticker(subcommand, tx_infos)
refund_configuration = cal.get_conf_for_ticker(refund_ticker)
ex.check_payout_address(to_configuration)

# Request the final address check and UI approval request on the device
with ex.check_refund_address(refund_configuration):
with ex.check_refund_address(from_configuration):
if ui_validation:
self.exchange_navigation_helper.simple_accept()
else:
Expand All @@ -129,7 +126,7 @@ def _perform_valid_exchange(self, subcommand, tx_infos, fees, ui_validation):
# As a workaround, we avoid calling the navigation if we want the function to raise
pass
else:
with ex.check_asset_in(payout_configuration):
with ex.check_asset_in(from_configuration):
if ui_validation:
self.exchange_navigation_helper.simple_accept()
else:
Expand All @@ -152,13 +149,13 @@ def perform_valid_swap_from_custom(self, destination, send_amount, fees, memo, r
"refund_extra_id": refund_memo.encode(),
"payout_address": b"0xDad77910DbDFdE764fC21FCD4E74D71bBACA6D8D", # Default
"payout_extra_id": b"", # Default
"currency_from": self.currency_ticker,
"currency_to": "ETH", # Default
"currency_from": self.currency_configuration.ticker,
"currency_to": cal.ETH_CURRENCY_CONFIGURATION.ticker,
"amount_to_provider": int_to_minimally_sized_bytes(send_amount),
"amount_to_wallet": b"\246\333t\233+\330\000", # Default
}
subcommand = SubCommand.SWAP if legacy else SubCommand.SWAP_NG
self._perform_valid_exchange(subcommand, tx_infos, fees, ui_validation=ui_validation)
self._perform_valid_exchange(subcommand, tx_infos, self.currency_configuration, cal.ETH_CURRENCY_CONFIGURATION, fees, ui_validation=ui_validation)

def perform_valid_swap_to_custom(self, destination, send_amount, fees, memo, ui_validation=True, legacy=False):
tx_infos = {
Expand All @@ -168,36 +165,36 @@ def perform_valid_swap_to_custom(self, destination, send_amount, fees, memo, ui_
"refund_extra_id": "", # Default
"payout_address": destination,
"payout_extra_id": memo.encode(),
"currency_from": "ETH", # Default
"currency_to": self.currency_ticker,
"currency_from": cal.ETH_CURRENCY_CONFIGURATION.ticker,
"currency_to": self.currency_configuration.ticker,
"amount_to_provider": int_to_minimally_sized_bytes(send_amount),
"amount_to_wallet": b"\246\333t\233+\330\000", # Default
}
subcommand = SubCommand.SWAP if legacy else SubCommand.SWAP_NG
self._perform_valid_exchange(subcommand, tx_infos, fees, ui_validation=ui_validation)
self._perform_valid_exchange(subcommand, tx_infos, cal.ETH_CURRENCY_CONFIGURATION, self.currency_configuration, fees, ui_validation=ui_validation)

def perform_valid_fund_from_custom(self, destination, send_amount, fees, legacy=False):
tx_infos = {
"user_id": self.fund_user_id,
"account_name": self.fund_account_name,
"in_currency": self.currency_ticker,
"in_currency": self.currency_configuration.ticker,
"in_amount": int_to_minimally_sized_bytes(send_amount),
"in_address": destination,
}
subcommand = SubCommand.FUND if legacy else SubCommand.FUND_NG
self._perform_valid_exchange(subcommand, tx_infos, fees, ui_validation=True)
self._perform_valid_exchange(subcommand, tx_infos, self.currency_configuration, None, fees, ui_validation=True)

def perform_valid_sell_from_custom(self, destination, send_amount, fees, legacy=False):
tx_infos = {
"trader_email": self.sell_trader_email,
"out_currency": self.sell_out_currency,
"out_amount": self.sell_out_amount,
"in_currency": self.currency_ticker,
"in_currency": self.currency_configuration.ticker,
"in_amount": int_to_minimally_sized_bytes(send_amount),
"in_address": destination,
}
subcommand = SubCommand.SELL if legacy else SubCommand.SELL_NG
self._perform_valid_exchange(subcommand, tx_infos, fees, ui_validation=True)
self._perform_valid_exchange(subcommand, tx_infos, self.currency_configuration, None, fees, ui_validation=True)

# Implement this function for each tested coin
def perform_final_tx(self, destination, send_amount, fees, memo):
Expand Down
25 changes: 0 additions & 25 deletions test/python/apps/exchange_transaction_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@ class SubCommandSpecs:
transaction_type: Callable
required_fields: Iterable[str]
transaction_id_field: str
payout_field: str
refund_field: Optional[str]

@property
def dot_prefix(self):
Expand Down Expand Up @@ -133,8 +131,6 @@ def encode_transaction_signature(self, signer: SigningAuthority, tx: bytes) -> b
"payout_address", "payout_extra_id", "currency_from", "currency_to",
"amount_to_provider", "amount_to_wallet"],
transaction_id_field = "device_transaction_id_ng",
payout_field = "currency_to",
refund_field = "currency_from",
)

SWAP_SPECS = SubCommandSpecs(
Expand All @@ -148,8 +144,6 @@ def encode_transaction_signature(self, signer: SigningAuthority, tx: bytes) -> b
"payout_address", "payout_extra_id", "currency_from", "currency_to",
"amount_to_provider", "amount_to_wallet"],
transaction_id_field = "device_transaction_id",
payout_field = "currency_to",
refund_field = "currency_from",
)

SELL_NG_SPECS = SubCommandSpecs(
Expand All @@ -161,8 +155,6 @@ def encode_transaction_signature(self, signer: SigningAuthority, tx: bytes) -> b
transaction_type = NewSellResponse,
transaction_id_field = "device_transaction_id",
required_fields = ["trader_email", "in_currency", "in_amount", "in_address", "out_currency", "out_amount"],
payout_field = "in_currency",
refund_field = None,
)

SELL_SPECS = SubCommandSpecs(
Expand All @@ -174,8 +166,6 @@ def encode_transaction_signature(self, signer: SigningAuthority, tx: bytes) -> b
transaction_type = NewSellResponse,
transaction_id_field = "device_transaction_id",
required_fields = ["trader_email", "in_currency", "in_amount", "in_address", "out_currency", "out_amount"],
payout_field = "in_currency",
refund_field = None,
)

FUND_NG_SPECS = SubCommandSpecs(
Expand All @@ -187,8 +177,6 @@ def encode_transaction_signature(self, signer: SigningAuthority, tx: bytes) -> b
transaction_type = NewFundResponse,
required_fields = ["user_id", "account_name", "in_currency", "in_amount", "in_address"],
transaction_id_field = "device_transaction_id",
payout_field = "in_currency",
refund_field = None,
)

FUND_SPECS = SubCommandSpecs(
Expand All @@ -200,8 +188,6 @@ def encode_transaction_signature(self, signer: SigningAuthority, tx: bytes) -> b
transaction_type = NewFundResponse,
required_fields = ["user_id", "account_name", "in_currency", "in_amount", "in_address"],
transaction_id_field = "device_transaction_id",
payout_field = "in_currency",
refund_field = None,
)

SUBCOMMAND_TO_SPECS = {
Expand All @@ -223,17 +209,6 @@ def craft_and_sign_tx(subcommand: Union[SubCommand, SubCommandSpecs], tx_infos:
signed_tx = subcommand_specs.encode_transaction_signature(signer, pb)
return tx, signed_tx

def extract_payout_ticker(subcommand: SubCommand, tx_infos: Dict) -> str:
subcommand_specs = SUBCOMMAND_TO_SPECS[subcommand]
return tx_infos[subcommand_specs.payout_field]

def extract_refund_ticker(subcommand: SubCommand, tx_infos: Dict) -> Optional[str]:
subcommand_specs = SUBCOMMAND_TO_SPECS[subcommand]
if subcommand_specs.refund_field:
return tx_infos[subcommand_specs.refund_field]
else:
return None

def get_partner_curve(subcommand: SubCommand) -> ec.EllipticCurve:
return SUBCOMMAND_TO_SPECS[subcommand].partner_curve

Expand Down
3 changes: 2 additions & 1 deletion test/python/test_bitcoin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from .apps.exchange_test_runner import ExchangeTestRunner, ALL_TESTS_EXCEPT_MEMO
from .apps.bitcoin import BitcoinClient, BitcoinErrors
from .apps import cal as cal
from ledger_bitcoin import WalletPolicy

in_wallet = WalletPolicy(
Expand Down Expand Up @@ -31,7 +32,7 @@
# ExchangeTestRunner implementation for Bitcoin
class BitcoinTests(ExchangeTestRunner):

currency_ticker = "BTC"
currency_configuration = cal.BTC_CURRENCY_CONFIGURATION
valid_destination_1 = BitcoinClient.get_address_from_wallet(out_wallet)
valid_destination_memo_1 = ""
valid_destination_2 = BitcoinClient.get_address_from_wallet(out_wallet_2)
Expand Down
15 changes: 8 additions & 7 deletions test/python/test_bsc.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import pytest

from .apps.exchange_test_runner import ExchangeTestRunner, ALL_TESTS_EXCEPT_MEMO
from .apps.ethereum import ETH_PATH, BSC_CONF_LEGACY
from .apps.ethereum import ETH_PATH
from ledger_app_clients.ethereum.client import EthAppClient
from .apps.cal import TICKER_ID_TO_CONF
from .apps import cal as cal


# ExchangeTestRunner implementation for BSC
class BSCTests(ExchangeTestRunner):
currency_ticker = "BNB"
currency_configuration = cal.BNB_CURRENCY_CONFIGURATION
valid_destination_1 = "0xd692Cb1346262F584D17B4B470954501f6715a82"
valid_destination_memo_1 = ""
valid_destination_2 = "0x999999cf1046e68e36E1aA2E0E07105eDDD1f08E"
Expand Down Expand Up @@ -42,7 +42,10 @@ def perform_final_tx(self, destination, send_amount, fees, memo):
# TODO : assert signature validity


# Use a class to reuse the same Speculos instance
class BSCLegacyTests(BSCTests):
currency_configuration = cal.BNB_LEGACY_CURRENCY_CONFIGURATION


class TestsBSC:
@pytest.mark.parametrize('test_to_run', ALL_TESTS_EXCEPT_MEMO)
def test_bsc(self, backend, exchange_navigation_helper, test_to_run):
Expand All @@ -52,6 +55,4 @@ def test_bsc(self, backend, exchange_navigation_helper, test_to_run):
class TestsBSCLegacy:
@pytest.mark.parametrize('test_to_run', ALL_TESTS_EXCEPT_MEMO)
def test_bsc_legacy(self, backend, exchange_navigation_helper, test_to_run):
# Override CAL to emulate legacy behaviour (use clone instead of Ethereum app)
TICKER_ID_TO_CONF["BNB"] = BSC_CONF_LEGACY
BSCTests(backend, exchange_navigation_helper).run_test(test_to_run)
BSCLegacyTests(backend, exchange_navigation_helper).run_test(test_to_run)
4 changes: 2 additions & 2 deletions test/python/test_ethereum.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
from .apps.exchange_test_runner import ExchangeTestRunner, ALL_TESTS_EXCEPT_MEMO
from .apps.ethereum import ETH_PATH
from ledger_app_clients.ethereum.client import EthAppClient
from web3 import Web3
from .apps import cal as cal


# ExchangeTestRunner implementation for Ethereum
class EthereumTests(ExchangeTestRunner):
currency_ticker = "ETH"
currency_configuration = cal.ETH_CURRENCY_CONFIGURATION
valid_destination_1 = "0xd692Cb1346262F584D17B4B470954501f6715a82"
valid_destination_memo_1 = ""
valid_destination_2 = "0x999999cf1046e68e36E1aA2E0E07105eDDD1f08E"
Expand Down
Loading

0 comments on commit 9113daa

Please sign in to comment.