Files
app-ethereum/tests/ragger/test_nft.py
Alexandre Paillier 52c0270753 New NFT Ragger test
2023-09-07 16:12:03 +02:00

378 lines
12 KiB
Python

import pytest
from pathlib import Path
from typing import Callable
from ragger.error import ExceptionRAPDU
from ragger.firmware import Firmware
from ragger.backend import BackendInterface
from ragger.navigator import Navigator, NavInsID
from ledger_app_clients.ethereum.client import EthAppClient, TxData, StatusWord
from ledger_app_clients.ethereum.settings import SettingID, settings_toggle
from ledger_app_clients.ethereum.utils import get_selector_from_function
import struct
ROOT_SCREENSHOT_PATH = Path(__file__).parent
BIP32_PATH = "m/44'/60'/0'/0/0"
NONCE = 21
GAS_PRICE = 13
GAS_LIMIT = 21000
FROM = bytes.fromhex("1122334455667788990011223344556677889900")
TO = bytes.fromhex("0099887766554433221100998877665544332211")
NFTS = [ (1, 3), (5, 2), (7, 4) ] # tuples of (token_id, amount)
DATA = "Some data".encode()
class NFTCollection:
addr: bytes
name: str
chain_id: int
def __init__(self, addr: bytes, name: str, chain_id: int):
self.addr = addr
self.name = name
self.chain_id = chain_id
class Action:
fn: str
data_fn: Callable
nav_fn: Callable
def __init__(self, fn: str, data_fn: Callable, nav_fn: Callable):
self.fn = fn
self.data_fn = data_fn
self.nav_fn = nav_fn
def common_nav_nft(is_nano: bool, nano_steps: int, stax_steps: int, reject: bool) -> list[NavInsID]:
moves = list()
if is_nano:
moves += [ NavInsID.RIGHT_CLICK ] * nano_steps
if reject:
moves += [ NavInsID.RIGHT_CLICK ]
moves += [ NavInsID.BOTH_CLICK ]
else:
moves += [ NavInsID.USE_CASE_REVIEW_TAP ] * stax_steps
if reject:
moves += [
NavInsID.USE_CASE_REVIEW_REJECT,
NavInsID.USE_CASE_CHOICE_CONFIRM
]
else:
moves += [ NavInsID.USE_CASE_REVIEW_CONFIRM ]
return moves
def snapshot_test_name(nft_type: str, fn: str, chain_id: int, reject: bool) -> str:
name = "%s_%s_%s" % (nft_type, fn.split("(")[0], str(chain_id))
if reject:
name += "-rejected"
return name
def common_test_nft(fw: Firmware,
back: BackendInterface,
nav: Navigator,
collec: NFTCollection,
action: Action,
reject: bool,
plugin_name: str):
app_client = EthAppClient(back)
selector = get_selector_from_function(action.fn)
if app_client._client.firmware.name == "nanos":
pytest.skip("Not supported on LNS")
with app_client.set_plugin(plugin_name,
collec.addr,
selector,
1):
pass
with app_client.provide_nft_metadata(collec.name, collec.addr, collec.chain_id):
pass
with app_client.sign_legacy(BIP32_PATH,
NONCE,
GAS_PRICE,
GAS_LIMIT,
collec.addr,
0,
collec.chain_id,
action.data_fn(action)):
nav.navigate_and_compare(ROOT_SCREENSHOT_PATH,
snapshot_test_name(plugin_name.lower(),
action.fn,
collec.chain_id,
reject),
action.nav_fn(fw.is_nano,
collec.chain_id,
reject))
def common_test_nft_reject(test_fn: Callable,
fw: Firmware,
back: BackendInterface,
nav: Navigator,
collec: NFTCollection,
action: Action):
try:
test_fn(fw, back, nav, collec, action, True)
except ExceptionRAPDU as e:
assert e.status == StatusWord.CONDITION_NOT_SATISFIED
else:
assert False # An exception should have been raised
# ERC-721
ERC721_PLUGIN = "ERC721"
ERC721_SAFE_TRANSFER_FROM_DATA = "safeTransferFrom(address,address,uint256,bytes)"
ERC721_SAFE_TRANSFER_FROM = "safeTransferFrom(address,address,uint256)"
ERC721_TRANSFER_FROM = "transferFrom(address,address,uint256)"
ERC721_APPROVE = "approve(address,uint256)"
ERC721_SET_APPROVAL_FOR_ALL = "setApprovalForAll(address,bool)"
## data formatting functions
def data_erc721_transfer_from(action: Action) -> TxData:
return TxData(
get_selector_from_function(action.fn),
[
FROM,
TO,
struct.pack(">H", NFTS[0][0])
]
)
def data_erc721_safe_transfer_from_data(action: Action) -> TxData:
txd = data_erc721_transfer_from(action)
txd.parameters += [ DATA ]
return txd
def data_erc721_approve(action: Action) -> TxData:
return TxData(
get_selector_from_function(action.fn),
[
TO,
struct.pack(">H", NFTS[0][0])
]
)
def data_erc721_set_approval_for_all(action: Action) -> TxData:
return TxData(
get_selector_from_function(action.fn),
[
TO,
struct.pack("b", False)
]
)
## ui nav functions
def nav_erc721_transfer_from(is_nano: bool,
chain_id: int,
reject: bool) -> list[NavInsID]:
nano_steps = 7
stax_steps = 3
if chain_id != 1:
nano_steps += 1
stax_steps += 1
return common_nav_nft(is_nano, nano_steps, stax_steps, reject)
def nav_erc721_approve(is_nano: bool,
chain_id: int,
reject: bool) -> list[NavInsID]:
nano_steps = 7
stax_steps = 3
if chain_id != 1:
nano_steps += 1
stax_steps += 1
return common_nav_nft(is_nano, nano_steps, stax_steps, reject)
def nav_erc721_set_approval_for_all(is_nano: bool,
chain_id: int,
reject: bool) -> list[NavInsID]:
nano_steps = 6
if chain_id != 1:
nano_steps += 1
return common_nav_nft(is_nano, nano_steps, 3, reject)
collecs_721 = [
NFTCollection(bytes.fromhex("bc4ca0eda7647a8ab7c2061c2e118a18a936f13d"),
"Bored Ape Yacht Club",
1),
NFTCollection(bytes.fromhex("670fd103b1a08628e9557cd66b87ded841115190"),
"y00ts",
137),
NFTCollection(bytes.fromhex("2909cf13e458a576cdd9aab6bd6617051a92dacf"),
"goerlirocks",
5)
]
actions_721 = [
Action(ERC721_SAFE_TRANSFER_FROM_DATA,
data_erc721_safe_transfer_from_data,
nav_erc721_transfer_from),
Action(ERC721_SAFE_TRANSFER_FROM,
data_erc721_transfer_from,
nav_erc721_transfer_from),
Action(ERC721_TRANSFER_FROM,
data_erc721_transfer_from,
nav_erc721_transfer_from),
Action(ERC721_APPROVE,
data_erc721_approve,
nav_erc721_approve),
Action(ERC721_SET_APPROVAL_FOR_ALL,
data_erc721_set_approval_for_all,
nav_erc721_set_approval_for_all)
]
@pytest.fixture(params=collecs_721)
def collec_721(request) -> NFTCollection:
return request.param
@pytest.fixture(params=actions_721)
def action_721(request) -> Action:
return request.param
def test_erc721(firmware: Firmware,
backend: BackendInterface,
navigator: Navigator,
collec_721: NFTCollection,
action_721: Action,
reject: bool = False):
common_test_nft(firmware,
backend,
navigator,
collec_721,
action_721,
reject,
ERC721_PLUGIN)
def test_erc721_reject(firmware: Firmware,
backend: BackendInterface,
navigator: Navigator):
common_test_nft_reject(test_erc721,
firmware,
backend,
navigator,
collecs_721[0],
actions_721[0])
# ERC-1155
ERC1155_PLUGIN = "ERC1155"
ERC1155_SAFE_TRANSFER_FROM = "safeTransferFrom(address,address,uint256,uint256,bytes)"
ERC1155_SAFE_BATCH_TRANSFER_FROM = "safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)"
ERC1155_SET_APPROVAL_FOR_ALL = "setApprovalForAll(address,bool)"
## data formatting functions
def data_erc1155_safe_transfer_from(action: Action) -> TxData:
return TxData(
get_selector_from_function(action.fn),
[
FROM,
TO,
struct.pack(">H", NFTS[0][0]),
struct.pack(">H", NFTS[0][1]),
DATA
]
)
def data_erc1155_safe_batch_transfer_from(action: Action) -> TxData:
data = TxData(
get_selector_from_function(action.fn),
[
FROM,
TO
])
data.parameters += [ int(32 * 4).to_bytes(8, "big") ] # token_ids offset
data.parameters += [int(32 * (4 + len(NFTS) + 1)).to_bytes(8, "big") ] # amounts offset
data.parameters += [ int(len(NFTS)).to_bytes(8, "big") ] # token_ids length
for nft in NFTS:
data.parameters += [ struct.pack(">H", nft[0]) ] # token_id
data.parameters += [ int(len(NFTS)).to_bytes(8, "big") ] # amounts length
for nft in NFTS:
data.parameters += [ struct.pack(">H", nft[1]) ] # amount
return data
def data_erc1155_set_approval_for_all(action: Action) -> TxData:
return TxData(
get_selector_from_function(action.fn),
[
TO,
struct.pack("b", False)
]
)
## ui nav functions
def nav_erc1155_safe_transfer_from(is_nano: bool,
chain_id: int,
reject: bool) -> list:
nano_steps = 8
if chain_id != 1:
nano_steps += 1
return common_nav_nft(is_nano, nano_steps, 4, reject)
def nav_erc1155_safe_batch_transfer_from(is_nano: bool,
chain_id: int,
reject: bool) -> list:
nano_steps = 7
stax_steps = 3
if chain_id != 1:
nano_steps += 1
stax_steps += 1
return common_nav_nft(is_nano, nano_steps, stax_steps, reject)
def nav_erc1155_set_approval_for_all(is_nano: bool,
chain_id: int,
reject: bool) -> list:
nano_steps = 6
if chain_id != 1:
nano_steps += 1
return common_nav_nft(is_nano, nano_steps, 3, reject)
collecs_1155 = [
NFTCollection(bytes.fromhex("495f947276749ce646f68ac8c248420045cb7b5e"),
"OpenSea Shared Storefront",
1),
NFTCollection(bytes.fromhex("2953399124f0cbb46d2cbacd8a89cf0599974963"),
"OpenSea Collections",
137),
NFTCollection(bytes.fromhex("f4910c763ed4e47a585e2d34baa9a4b611ae448c"),
"OpenSea Collections",
5)
]
actions_1155 = [
Action(ERC1155_SAFE_TRANSFER_FROM,
data_erc1155_safe_transfer_from,
nav_erc1155_safe_transfer_from),
Action(ERC1155_SAFE_BATCH_TRANSFER_FROM,
data_erc1155_safe_batch_transfer_from,
nav_erc1155_safe_batch_transfer_from),
Action(ERC1155_SET_APPROVAL_FOR_ALL,
data_erc1155_set_approval_for_all,
nav_erc1155_set_approval_for_all)
]
@pytest.fixture(params=collecs_1155)
def collec_1155(request) -> bool:
return request.param
@pytest.fixture(params=actions_1155)
def action_1155(request) -> Action:
return request.param
def test_erc1155(firmware: Firmware,
backend: BackendInterface,
navigator: Navigator,
collec_1155: NFTCollection,
action_1155: Action,
reject: bool = False):
common_test_nft(firmware,
backend,
navigator,
collec_1155,
action_1155,
reject,
ERC1155_PLUGIN)
def test_erc1155_reject(firmware: Firmware,
backend: BackendInterface,
navigator: Navigator):
common_test_nft_reject(test_erc1155,
firmware,
backend,
navigator,
collecs_1155[0],
actions_1155[0])