diff --git a/client/src/ledger_app_clients/ethereum/client.py b/client/src/ledger_app_clients/ethereum/client.py index 8fdc693af..a26404d74 100644 --- a/client/src/ledger_app_clients/ethereum/client.py +++ b/client/src/ledger_app_clients/ethereum/client.py @@ -77,6 +77,12 @@ class PKIPubKeyUsage(IntEnum): PUBKEY_USAGE_SEED_ID_AUTH = 0x09 +class SignMode(IntEnum): + BASIC = 0x00 + STORE = 0x01 + START_FLOW = 0x02 + + class PKIClient: _CLA: int = 0xB0 _INS: int = 0x06 @@ -130,6 +136,15 @@ def send_raw(self, cla: int, ins: int, p1: int, p2: int, payload: bytes): header.append(len(payload)) return self._exchange(header + payload) + def send_raw_async(self, cla: int, ins: int, p1: int, p2: int, payload: bytes): + header = bytearray() + header.append(cla) + header.append(ins) + header.append(p1) + header.append(p2) + header.append(len(payload)) + return self._exchange_async(header + payload) + def eip712_send_struct_def_struct_name(self, name: str): return self._exchange_async(self._cmd_builder.eip712_send_struct_def_struct_name(name)) @@ -211,7 +226,8 @@ def eip712_filtering_raw(self, name: str, sig: bytes, discarded: bool): def sign(self, bip32_path: str, - tx_params: dict): + tx_params: dict, + mode: SignMode = SignMode.BASIC): tx = Web3().eth.account.create().sign_transaction(tx_params).rawTransaction prefix = bytes() suffix = [] @@ -223,7 +239,7 @@ def sign(self, suffix = [int(tx_params["chainId"]), bytes(), bytes()] decoded = rlp.decode(tx)[:-3] # remove already computed signature tx = prefix + rlp.encode(decoded + suffix) - chunks = self._cmd_builder.sign(bip32_path, tx, suffix) + chunks = self._cmd_builder.sign(bip32_path, tx, mode) for chunk in chunks[:-1]: self._exchange(chunk) return self._exchange_async(chunks[-1]) @@ -530,3 +546,15 @@ def provide_network_information(self, assert response.status == StatusWord.OK response = self._exchange(chunks[-1]) assert response.status == StatusWord.OK + + def provide_enum_value(self, payload: bytes) -> RAPDU: + chunks = self._cmd_builder.provide_enum_value(payload) + for chunk in chunks[:-1]: + self._exchange(chunk) + return self._exchange(chunks[-1]) + + def provide_transaction_info(self, payload: bytes) -> RAPDU: + chunks = self._cmd_builder.provide_transaction_info(payload) + for chunk in chunks[:-1]: + self._exchange(chunk) + return self._exchange(chunks[-1]) diff --git a/client/src/ledger_app_clients/ethereum/command_builder.py b/client/src/ledger_app_clients/ethereum/command_builder.py index c43fe9462..090833dd8 100644 --- a/client/src/ledger_app_clients/ethereum/command_builder.py +++ b/client/src/ledger_app_clients/ethereum/command_builder.py @@ -15,6 +15,7 @@ class InsType(IntEnum): SIGN = 0x04 PERSONAL_SIGN = 0x08 PROVIDE_ERC20_TOKEN_INFORMATION = 0x0a + EXTERNAL_PLUGIN_SETUP = 0x12 PROVIDE_NFT_INFORMATION = 0x14 SET_PLUGIN = 0x16 PERFORM_PRIVACY_OPERATION = 0x18 @@ -24,7 +25,8 @@ class InsType(IntEnum): EIP712_SIGN = 0x0c GET_CHALLENGE = 0x20 PROVIDE_TRUSTED_NAME = 0x22 - EXTERNAL_PLUGIN_SETUP = 0x12 + PROVIDE_ENUM_VALUE = 0x24 + PROVIDE_TRANSACTION_INFO = 0x26 PROVIDE_NETWORK_INFORMATION = 0x30 @@ -261,7 +263,7 @@ def set_external_plugin(self, plugin_name: str, contract_address: bytes, selecto 0x00, data) - def sign(self, bip32_path: str, rlp_data: bytes, vrs: list) -> list[bytes]: + def sign(self, bip32_path: str, rlp_data: bytes, p2: int) -> list[bytes]: apdus = list() payload = pack_derivation_path(bip32_path) payload += rlp_data @@ -269,7 +271,7 @@ def sign(self, bip32_path: str, rlp_data: bytes, vrs: list) -> list[bytes]: while len(payload) > 0: apdus.append(self._serialize(InsType.SIGN, p1, - 0x00, + p2, payload[:0xff])) payload = payload[0xff:] p1 = P1Type.SIGN_SUBSQT_CHUNK @@ -427,3 +429,23 @@ def provide_network_information(self, icon = icon[0xff:] p1 = P1Type.FOLLOWING_CHUNK return chunks + + def common_tlv_serialize(self, tlv_payload: bytes, ins: InsType) -> list[bytes]: + chunks = list() + payload = struct.pack(">H", len(tlv_payload)) + payload += tlv_payload + p1 = 1 + while len(payload) > 0: + chunks.append(self._serialize(ins, + p1, + 0x00, + payload[:0xff])) + payload = payload[0xff:] + p1 = 0 + return chunks + + def provide_enum_value(self, tlv_payload: bytes) -> list[bytes]: + return self.common_tlv_serialize(tlv_payload, InsType.PROVIDE_ENUM_VALUE) + + def provide_transaction_info(self, tlv_payload: bytes) -> list[bytes]: + return self.common_tlv_serialize(tlv_payload, InsType.PROVIDE_TRANSACTION_INFO) diff --git a/client/src/ledger_app_clients/ethereum/enum_value.py b/client/src/ledger_app_clients/ethereum/enum_value.py new file mode 100644 index 000000000..84febcfa2 --- /dev/null +++ b/client/src/ledger_app_clients/ethereum/enum_value.py @@ -0,0 +1,59 @@ +from enum import IntEnum +from typing import Optional +from .tlv import format_tlv +from .keychain import sign_data, Key + + +class Tag(IntEnum): + VERSION = 0x00 + CHAIN_ID = 0x01 + CONTRACT_ADDR = 0x02 + SELECTOR = 0x03 + ID = 0x04 + VALUE = 0x05 + NAME = 0x06 + SIGNATURE = 0xff + + +class EnumValue: + version: int + chain_id: int + contract_addr: bytes + selector: bytes + id: int + value: int + name: str + signature: Optional[bytes] = None + + def __init__(self, + version: int, + chain_id: int, + contract_addr: bytes, + selector: bytes, + id: int, + value: int, + name: str, + signature: Optional[bytes] = None): + self.version = version + self.chain_id = chain_id + self.contract_addr = contract_addr + self.selector = selector + self.id = id + self.value = value + self.name = name + self.signature = signature + + def serialize(self) -> bytes: + payload = bytearray() + payload += format_tlv(Tag.VERSION, self.version) + payload += format_tlv(Tag.CHAIN_ID, self.chain_id) + payload += format_tlv(Tag.CONTRACT_ADDR, self.contract_addr) + payload += format_tlv(Tag.SELECTOR, self.selector) + payload += format_tlv(Tag.ID, self.id) + payload += format_tlv(Tag.VALUE, self.value) + payload += format_tlv(Tag.NAME, self.name) + sig = self.signature + if sig is None: + sig = sign_data(Key.CAL, payload) + payload += format_tlv(Tag.SIGNATURE, sig) + return payload diff --git a/client/src/ledger_app_clients/ethereum/gcs.py b/client/src/ledger_app_clients/ethereum/gcs.py new file mode 100644 index 000000000..b48fc6f43 --- /dev/null +++ b/client/src/ledger_app_clients/ethereum/gcs.py @@ -0,0 +1,477 @@ +from typing import Optional +from enum import IntEnum +import struct + +from .tlv import format_tlv +from .keychain import sign_data, Key +from .client import TrustedNameType, TrustedNameSource + + +class TxInfo(): + version: int + chain_id: int + contract_addr: bytes + selector: bytes + fields_hash: bytes + operation_type: str + creator_name: Optional[str] + creator_legal_name: Optional[str] + creator_url: Optional[str] + contract_name: Optional[str] + deploy_date: Optional[int] + signature: Optional[bytes] + + def __init__(self, + version: int, + chain_id: int, + contract_addr: bytes, + selector: bytes, + fields_hash: bytes, + operation_type: str, + creator_name: Optional[str] = None, + creator_legal_name: Optional[str] = None, + creator_url: Optional[str] = None, + contract_name: Optional[str] = None, + deploy_date: Optional[int] = None, + signature: Optional[bytes] = None): + self.version = version + self.chain_id = chain_id + self.contract_addr = contract_addr + self.selector = selector + self.fields_hash = fields_hash + self.operation_type = operation_type + self.creator_name = creator_name + self.creator_legal_name = creator_legal_name + self.creator_url = creator_url + self.contract_name = contract_name + self.deploy_date = deploy_date + self.signature = signature + + def serialize(self) -> bytes: + payload = bytearray() + payload += format_tlv(0x00, self.version) + payload += format_tlv(0x01, self.chain_id) + payload += format_tlv(0x02, self.contract_addr) + payload += format_tlv(0x03, self.selector) + payload += format_tlv(0x04, self.fields_hash) + payload += format_tlv(0x05, self.operation_type) + if self.creator_name is not None: + payload += format_tlv(0x06, self.creator_name) + if self.creator_legal_name is not None: + payload += format_tlv(0x07, self.creator_legal_name) + if self.creator_url is not None: + payload += format_tlv(0x08, self.creator_url) + if self.contract_name is not None: + payload += format_tlv(0x09, self.contract_name) + if self.deploy_date is not None: + payload += format_tlv(0x0a, self.deploy_date) + signature = self.signature + if signature is None: + signature = sign_data(Key.CAL, payload) + payload += format_tlv(0xff, signature) + return payload + + +class ParamType(IntEnum): + RAW = 0x00 + AMOUNT = 0x01 + TOKEN_AMOUNT = 0x02 + NFT = 0x03 + DATETIME = 0x04 + DURATION = 0x05 + UNIT = 0x06 + ENUM = 0x07 + TRUSTED_NAME = 0x08 + CALLDATA = 0x09 + + +class TypeFamily(IntEnum): + UINT = 0x01 + INT = 0x02 + UFIXED = 0x03 + FIXED = 0x04 + ADDRESS = 0x05 + BOOL = 0x06 + BYTES = 0x07 + STRING = 0x08 + + +class PathTuple: + value: int + + def __init__(self, value: int): + self.value = value + + def serialize(self) -> bytes: + return struct.pack(">H", self.value) + + +class PathArray: + weight: int + start: Optional[int] + end: Optional[int] + + def __init__(self, + weight: int = 1, + start: Optional[int] = None, + end: Optional[int] = None): + self.weight = weight + self.start = start + self.end = end + + def serialize(self) -> bytes: + payload = bytearray() + payload += format_tlv(0x01, self.weight) + if self.start is not None: + payload += format_tlv(0x02, struct.pack(">h", self.start)) + if self.end is not None: + payload += format_tlv(0x03, struct.pack(">h", self.end)) + return payload + + +class PathRef: + def __init__(self): + pass + + def serialize(self) -> bytes: + return bytes() + + +class PathLeafType(IntEnum): + ARRAY = 0x01 + TUPLE = 0x02 + STATIC = 0x03 + DYNAMIC = 0x04 + + +class PathLeaf: + type: PathLeafType + + def __init__(self, type: PathLeafType): + self.type = type + + def serialize(self) -> bytes: + return struct.pack("B", self.type) + + +class PathSlice: + start: Optional[int] + end: Optional[int] + + def __init__(self, start: Optional[int] = None, end: Optional[int] = None): + self.start = start + self.end = end + + def serialize(self) -> bytes: + payload = bytearray() + if self.start is not None: + payload += format_tlv(0x01, struct.pack(">h", self.start)) + if self.end is not None: + payload += format_tlv(0x02, struct.pack(">h", self.end)) + return payload + + +PathElement = PathTuple | PathArray | PathRef | PathLeaf | PathSlice + + +class DataPath: + version: int + path: list[PathElement] + + def __init__(self, version: int, path: list[PathElement]): + self.version = version + self.path = path + + def serialize(self) -> bytes: + payload = bytearray() + payload += format_tlv(0x00, self.version) + for node in self.path: + if isinstance(node, PathTuple): + tag = 0x01 + elif isinstance(node, PathArray): + tag = 0x02 + elif isinstance(node, PathRef): + tag = 0x03 + elif isinstance(node, PathLeaf): + tag = 0x04 + elif isinstance(node, PathSlice): + tag = 0x05 + else: + assert False, f"Unknown path node type : {type(node)}" + payload += format_tlv(tag, node.serialize()) + return payload + + +class ContainerPath(IntEnum): + FROM = 0x00 + TO = 0x01 + VALUE = 0x02 + + +class Value: + version: int + type_family: TypeFamily + type_size: Optional[int] + data_path: Optional[DataPath] + container_path: Optional[ContainerPath] + constant: Optional[bytes] + + def __init__(self, + version: int, + type_family: TypeFamily, + type_size: Optional[int] = None, + data_path: Optional[DataPath] = None, + container_path: Optional[ContainerPath] = None, + constant: Optional[bytes] = None): + self.version = version + self.type_family = type_family + self.type_size = type_size + self.data_path = data_path + self.container_path = container_path + self.constant = constant + + def serialize(self) -> bytes: + payload = bytearray() + payload += format_tlv(0x00, self.version) + payload += format_tlv(0x01, self.type_family) + if self.type_size is not None: + payload += format_tlv(0x02, self.type_size) + if self.data_path is not None: + payload += format_tlv(0x03, self.data_path.serialize()) + if self.container_path is not None: + payload += format_tlv(0x04, self.container_path) + if self.constant is not None: + payload += format_tlv(0x05, self.constant) + return payload + + +class ParamRaw: + version: int + value: Value + + def __init__(self, version: int, value: Value): + self.version = version + self.value = value + + def serialize(self) -> bytes: + payload = bytearray() + payload += format_tlv(0x00, self.version) + payload += format_tlv(0x01, self.value.serialize()) + return payload + + +class ParamAmount: + version: int + value: Value + + def __init__(self, version: int, value: Value): + self.version = version + self.value = value + + def serialize(self) -> bytes: + payload = bytearray() + payload += format_tlv(0x00, self.version) + payload += format_tlv(0x01, self.value.serialize()) + return payload + + +class ParamTokenAmount: + version: int + value: Value + token: Optional[Value] + native_currency: Optional[list[bytes]] + threshold: Optional[int] + above_threshold_msg: Optional[str] + + def __init__(self, + version: int, + value: Value, + token: Optional[Value] = None, + native_currency: Optional[list[bytes]] = None, + threshold: Optional[int] = None, + above_threshold_msg: Optional[str] = None): + self.version = version + self.value = value + self.token = token + self.native_currency = native_currency + self.threshold = threshold + self.above_threshold_msg = above_threshold_msg + + def serialize(self) -> bytes: + payload = bytearray() + payload += format_tlv(0x00, self.version) + payload += format_tlv(0x01, self.value.serialize()) + if self.token is not None: + payload += format_tlv(0x02, self.token.serialize()) + if self.native_currency is not None: + for nat_cur in self.native_currency: + payload += format_tlv(0x03, nat_cur) + if self.threshold is not None: + payload += format_tlv(0x04, self.threshold) + if self.above_threshold_msg is not None: + payload += format_tlv(0x05, self.above_threshold_msg) + return payload + + +class ParamNFT(): + version: int + id: Value + collection: Value + + def __init__(self, version: int, id: Value, collection: Value): + self.version = version + self.id = id + self.collection = collection + + def serialize(self) -> bytes: + payload = bytearray() + payload += format_tlv(0x00, self.version) + payload += format_tlv(0x01, self.id.serialize()) + payload += format_tlv(0x02, self.collection.serialize()) + return payload + + +class DatetimeType(IntEnum): + DT_UNIX = 0x00 + DT_BLOCKHEIGHT = 0x01 + + +class ParamDatetime(): + version: int + value: Value + type: DatetimeType + + def __init__(self, version: int, value: Value, type: DatetimeType): + self.version = version + self.value = value + self.type = type + + def serialize(self) -> bytes: + payload = bytearray() + payload += format_tlv(0x00, self.version) + payload += format_tlv(0x01, self.value.serialize()) + payload += format_tlv(0x02, self.type) + return payload + + +class ParamDuration(): + version: int + value: Value + + def __init__(self, version: int, value: Value): + self.version = version + self.value = value + + def serialize(self) -> bytes: + payload = bytearray() + payload += format_tlv(0x00, self.version) + payload += format_tlv(0x01, self.value.serialize()) + return payload + + +class ParamUnit(): + version: int + value: Value + base: str + decimals: Optional[int] + prefix: Optional[bool] + + def __init__(self, + version: int, + value: Value, + base: str, + decimals: Optional[int] = None, + prefix: Optional[bool] = None): + self.version = version + self.value = value + self.base = base + self.decimals = decimals + self.prefix = prefix + + def serialize(self) -> bytes: + payload = bytearray() + payload += format_tlv(0x00, self.version) + payload += format_tlv(0x01, self.value.serialize()) + payload += format_tlv(0x02, self.base) + if self.decimals is not None: + payload += format_tlv(0x03, self.decimals) + if self.prefix is not None: + payload += format_tlv(0x04, self.prefix) + return payload + + +class ParamTrustedName(): + version: int + value: Value + types: list[TrustedNameType] + sources: list[TrustedNameSource] + + def __init__(self, version: int, value: Value, types: list[TrustedNameType], sources: list[TrustedNameSource]): + self.version = version + self.value = value + self.types = types + self.sources = sources + + def serialize(self) -> bytes: + payload = bytearray() + payload += format_tlv(0x00, self.version) + payload += format_tlv(0x01, self.value.serialize()) + types = bytearray() + for type in self.types: + types.append(type) + payload += format_tlv(0x02, types) + sources = bytearray() + for source in self.sources: + sources.append(source) + payload += format_tlv(0x03, sources) + return payload + + +class ParamEnum(): + version: int + id: int + value: Value + + def __init__(self, version: int, id: int, value: Value): + self.version = version + self.id = id + self.value = value + + def serialize(self) -> bytes: + payload = bytearray() + payload += format_tlv(0x00, self.version) + payload += format_tlv(0x01, self.id) + payload += format_tlv(0x02, self.value.serialize()) + return payload + + +ParamUnion = ParamRaw | \ + ParamAmount | \ + ParamTokenAmount | \ + ParamNFT | \ + ParamDatetime | \ + ParamDuration | \ + ParamUnit | \ + ParamTrustedName | \ + ParamEnum + + +class Field: + version: int + name: str + param_type: ParamType + param: ParamUnion + + def __init__(self, version: int, name: str, param_type: ParamType, param: ParamUnion): + self.version = version + self.name = name + self.param_type = param_type + self.param = param + + def serialize(self) -> bytes: + payload = bytearray() + payload += format_tlv(0x00, self.version) + payload += format_tlv(0x01, self.name) + payload += format_tlv(0x02, self.param_type) + payload += format_tlv(0x03, self.param.serialize()) + return payload diff --git a/client/src/ledger_app_clients/ethereum/tlv.py b/client/src/ledger_app_clients/ethereum/tlv.py index fd5dc109e..e7a5d4a89 100644 --- a/client/src/ledger_app_clients/ethereum/tlv.py +++ b/client/src/ledger_app_clients/ethereum/tlv.py @@ -9,12 +9,14 @@ def der_encode(value: int) -> bytes: return value_bytes -def format_tlv(tag: int, value: Union[int, str, bytes]) -> bytes: +def format_tlv(tag: int, value: Union[int, str, bytes, bytearray]) -> bytes: if isinstance(value, int): # max() to have minimum length of 1 value = value.to_bytes(max(1, (value.bit_length() + 7) // 8), 'big') elif isinstance(value, str): value = value.encode() + elif isinstance(value, bytearray): + value = bytes(value) assert isinstance(value, bytes), f"Unhandled TLV formatting for type : {type(value)}" diff --git a/doc/ethapp.adoc b/doc/ethapp.adoc index a8358a42e..058b89cf3 100644 --- a/doc/ethapp.adoc +++ b/doc/ethapp.adoc @@ -113,15 +113,21 @@ The input data is the RLP encoded transaction (as per https://github.com/ethereu [width="80%"] |============================================================================================================================== -| *CLA* | *INS* | *P1* | *P2* | *Lc* | *Le* +| *CLA* | *INS* | *P1* | *P2* | *Lc* | *Le* | E0 | 04 | 00 : first transaction data block 80 : subsequent transaction data block - | 00 | variable | variable + | 00 : process & start flow + + 01 : store only + + 02 : start flow | variable | variable |============================================================================================================================== 'Input data (first transaction data block)' +If P2 == 0x02, then no data is provided. + [width="80%"] |============================================================================================================================== | *Description* | *Length* @@ -143,6 +149,8 @@ The input data is the RLP encoded transaction (as per https://github.com/ethereu 'Output data' +If P2 == 0x01, then no data is returned. + [width="80%"] |============================================================================================================================== | *Description* | *Length* @@ -1070,6 +1078,117 @@ _Output data_ None +### PROVIDE ENUM VALUE + +_Command_ + +[width="80%"] +|============================================================== +| *CLA* | *INS* | *P1* | *P2* | *LC* +| E0 | 24 | 01 : first chunk + + 00 : following chunk + | 00 | 00 +|============================================================== + +_Input data_ + +##### If P1 == first chunk + +[width="80%"] +|========================================== +| *Description* | *Length (byte)* +| Payload length | 2 +| TLV payload | variable +|========================================== + +##### If P1 == following chunk + +[width="80%"] +|========================================== +| *Description* | *Length (byte)* +| TLV payload | variable +|========================================== + +_Output data_ + +None + + +### TRANSACTION INFO + +_Command_ + +[width="80%"] +|============================================================== +| *CLA* | *INS* | *P1* | *P2* | *LC* +| E0 | 26 | 01 : first chunk + + 00 : following chunk + | 00 | 00 +|============================================================== + +_Input data_ + +##### If P1 == first chunk + +[width="80%"] +|========================================== +| *Description* | *Length (byte)* +| Payload length | 2 +| TLV payload | variable +|========================================== + +##### If P1 == following chunk + +[width="80%"] +|========================================== +| *Description* | *Length (byte)* +| TLV payload | variable +|========================================== + +_Output data_ + +None + + +### TRANSACTION FIELD DESCRIPTION + +_Command_ + +[width="80%"] +|============================================================== +| *CLA* | *INS* | *P1* | *P2* | *LC* +| E0 | 28 | 01 : first chunk + + 00 : following chunk + | 00 | 00 +|============================================================== + +_Input data_ + +##### If P1 == first chunk + +[width="80%"] +|========================================== +| *Description* | *Length (byte)* +| Payload length | 2 +| TLV payload | variable +|========================================== + +##### If P1 == following chunk + +[width="80%"] +|========================================== +| *Description* | *Length (byte)* +| TLV payload | variable +|========================================== + +_Output data_ + +None + + ### PROVIDE NETWORK CONFIGURATION #### Description diff --git a/glyphs/review_info_button.gif b/glyphs/review_info_button.gif new file mode 100644 index 000000000..82b19e717 Binary files /dev/null and b/glyphs/review_info_button.gif differ diff --git a/makefile_conf/features.mk b/makefile_conf/features.mk index 8d1ab1ea3..b56dc9fee 100644 --- a/makefile_conf/features.mk +++ b/makefile_conf/features.mk @@ -36,6 +36,11 @@ ifneq ($(TARGET_NAME),TARGET_NANOS) DEFINES += HAVE_EIP712_FULL_SUPPORT endif +ifneq ($(TARGET_NAME),TARGET_NANOS) + DEFINES += HAVE_ENUM_VALUE + DEFINES += HAVE_GENERIC_TX_PARSER +endif + # CryptoAssetsList key CAL_TEST_KEY ?= 0 ifneq ($(CAL_TEST_KEY),0) diff --git a/src/apdu_constants.h b/src/apdu_constants.h index d523e8184..22a4eda3d 100644 --- a/src/apdu_constants.h +++ b/src/apdu_constants.h @@ -25,6 +25,9 @@ #define INS_EIP712_FILTERING 0x1E #define INS_ENS_GET_CHALLENGE 0x20 #define INS_ENS_PROVIDE_INFO 0x22 +#define INS_PROVIDE_ENUM_VALUE 0x24 +#define INS_GTP_TRANSACTION_INFO 0x26 +#define INS_GTP_FIELD 0x28 #define INS_PROVIDE_NETWORK_CONFIGURATION 0x30 #define P1_CONFIRM 0x01 #define P1_NON_CONFIRM 0x00 diff --git a/src/common_ui.h b/src/common_ui.h index e29874bbb..06ffc5d10 100644 --- a/src/common_ui.h +++ b/src/common_ui.h @@ -29,6 +29,9 @@ void ui_712_start_unfiltered(void); void ui_712_switch_to_message(void); void ui_712_switch_to_sign(void); +// Generic clear-signing +bool ui_gcs(void); + #include "ui_callbacks.h" #include diff --git a/src/main.c b/src/main.c index c0ebeae74..3e4890801 100644 --- a/src/main.c +++ b/src/main.c @@ -36,6 +36,12 @@ #include "crypto_helpers.h" #include "manage_asset_info.h" #include "network_dynamic.h" +#ifdef HAVE_DYN_MEM_ALLOC +#include "mem.h" +#endif +#include "cmd_enum_value.h" +#include "cmd_tx_info.h" +#include "cmd_field.h" tmpCtx_t tmpCtx; txContext_t txContext; @@ -73,6 +79,11 @@ void reset_app_context() { #endif memset((uint8_t *) &tmpCtx, 0, sizeof(tmpCtx)); forget_known_assets(); +#ifdef HAVE_GENERIC_TX_PARSER + if (txContext.store_calldata) { + gcs_cleanup(); + } +#endif memset((uint8_t *) &txContext, 0, sizeof(txContext)); memset((uint8_t *) &tmpContent, 0, sizeof(tmpContent)); } @@ -232,6 +243,22 @@ static uint16_t handleApdu(command_t *cmd, uint32_t *flags, uint32_t *tx) { break; #endif // HAVE_TRUSTED_NAME +#ifdef HAVE_ENUM_VALUE + case INS_PROVIDE_ENUM_VALUE: + sw = handle_enum_value(cmd->p1, cmd->p2, cmd->lc, cmd->data); + break; +#endif // HAVE_ENUM_VALUE + +#ifdef HAVE_GENERIC_TX_PARSER + case INS_GTP_TRANSACTION_INFO: + sw = handle_tx_info(cmd->p1, cmd->p2, cmd->lc, cmd->data); + break; + + case INS_GTP_FIELD: + sw = handle_field(cmd->p1, cmd->p2, cmd->lc, cmd->data); + break; +#endif // HAVE_GENERIC_TX_PARSER + default: sw = APDU_RESPONSE_INVALID_INS; break; @@ -366,6 +393,9 @@ void coin_main(eth_libargs_t *args) { io_init(); ui_idle(); +#ifdef HAVE_DYN_MEM_ALLOC + mem_init(); +#endif #ifdef HAVE_TRUSTED_NAME // to prevent it from having a fixed value at boot diff --git a/src/mem.c b/src/mem.c index 8de2e52ea..cefdb60be 100644 --- a/src/mem.c +++ b/src/mem.c @@ -41,8 +41,8 @@ void mem_reset(void) { * @return Allocated memory pointer; \ref NULL if not enough space left. */ void *mem_alloc(size_t size) { - if ((mem_idx + size) > SIZE_MEM_BUFFER) // Buffer exceeded - { + // Buffer exceeded + if ((mem_idx + size) > sizeof(mem_buffer)) { PRINTF("Error: mem_alloc(%u) failed!\n", size); return NULL; } @@ -56,8 +56,9 @@ void *mem_alloc(size_t size) { * @param[in] size Requested deallocation size in bytes */ void mem_dealloc(size_t size) { - if (size > mem_idx) // More than is already allocated - { + // More than is already allocated + if (size > mem_idx) { + PRINTF("Warning: mem_dealloc(%u) with a value larger than allocated!\n", size); mem_idx = 0; } else { mem_idx -= size; diff --git a/src/time_format.c b/src/time_format.c new file mode 100644 index 000000000..ec5a53baa --- /dev/null +++ b/src/time_format.c @@ -0,0 +1,52 @@ +#include +#include "time_format.h" + +static bool get_time_struct(const time_t *timestamp, struct tm *tstruct) { + if (gmtime_r(timestamp, tstruct) == NULL) { + return false; + } + return true; +} + +bool time_format_to_yyyymmdd(const time_t *timestamp, char *out, size_t out_size) { + struct tm tstruct; + + if (!get_time_struct(timestamp, &tstruct)) { + return false; + } + snprintf(out, + out_size, + "%04d-%02d-%02d", + tstruct.tm_year + 1900, + tstruct.tm_mon + 1, + tstruct.tm_mday); + return true; +} + +bool time_format_to_utc(const time_t *timestamp, char *out, size_t out_size) { + struct tm tstruct; + int shown_hour; + + if (!get_time_struct(timestamp, &tstruct)) { + return false; + } + if (tstruct.tm_hour == 0) { + shown_hour = 12; + } else { + shown_hour = tstruct.tm_hour; + if (shown_hour > 12) { + shown_hour -= 12; + } + } + snprintf(out, + out_size, + "%04d-%02d-%02d\n%02d:%02d:%02d %s UTC", + tstruct.tm_year + 1900, + tstruct.tm_mon + 1, + tstruct.tm_mday, + shown_hour, + tstruct.tm_min, + tstruct.tm_sec, + (tstruct.tm_hour < 12) ? "AM" : "PM"); + return true; +} diff --git a/src/time_format.h b/src/time_format.h new file mode 100644 index 000000000..b939c179d --- /dev/null +++ b/src/time_format.h @@ -0,0 +1,11 @@ +#ifndef TIME_FORMAT_H_ +#define TIME_FORMAT_H_ + +#include +#include +#include + +bool time_format_to_yyyymmdd(const time_t *timestamp, char *out, size_t out_size); +bool time_format_to_utc(const time_t *timestamp, char *out, size_t out_size); + +#endif // !TIME_FORMAT_H_ diff --git a/src/tlv.c b/src/tlv.c new file mode 100644 index 000000000..7cc1cca87 --- /dev/null +++ b/src/tlv.c @@ -0,0 +1,106 @@ +#include +#include +#include "tlv.h" +#include "os_print.h" // PRINTF +#include "read.h" // read_u32_be + +typedef enum { TLV_STATE_TAG, TLV_STATE_LENGTH, TLV_STATE_VALUE } e_tlv_state; + +#define DER_LONG_FORM_FLAG 0x80 // 8th bit set +#define DER_FIRST_BYTE_VALUE_MASK 0x7f + +static bool parse_der_value(const uint8_t *payload, + uint16_t size, + uint16_t *offset, + uint32_t *value) { + uint8_t byte_length; + uint8_t buf[sizeof(*value)]; + + if (value == NULL) { + return false; + } + if (payload[*offset] & DER_LONG_FORM_FLAG) { // long form + byte_length = payload[*offset] & DER_FIRST_BYTE_VALUE_MASK; + *offset += 1; + if ((*offset + byte_length) > size) { + PRINTF("TLV payload too small for DER encoded value\n"); + return false; + } + if ((byte_length > sizeof(buf)) || (byte_length == 0)) { + PRINTF("Unexpectedly long DER-encoded value (%u bytes)\n", byte_length); + return false; + } + memset(buf, 0, (sizeof(buf) - byte_length)); + memcpy(buf + (sizeof(buf) - byte_length), &payload[*offset], byte_length); + *value = read_u32_be(buf, 0); + *offset += byte_length; + } else { // short form + *value = payload[*offset]; + *offset += 1; + } + return true; +} + +/** + * Parse a TLV payload + * + * @param[in] payload pointer to TLV payload + * @param[in] size size of the given payload + * @param[in] handler function to be called for each TLV data + * @param[in,out] context structure passed to the handler function so the context from one handler + * invocation to the next can be kept + * @return whether the parsing (and handling) was successful or not + */ +bool tlv_parse(const uint8_t *payload, uint16_t size, f_tlv_data_handler handler, void *context) { + e_tlv_state state = TLV_STATE_TAG; + uint16_t offset = 0; + uint32_t tmp_value; + s_tlv_data data; + + while (offset < size) { + switch (state) { + case TLV_STATE_TAG: + data.raw = &payload[offset]; + if (!parse_der_value(payload, size, &offset, &tmp_value) || + (tmp_value > UINT8_MAX)) { + return false; + } + data.tag = (uint8_t) tmp_value; + state = TLV_STATE_LENGTH; + break; + + case TLV_STATE_LENGTH: + if (!parse_der_value(payload, size, &offset, &tmp_value) || + (tmp_value > UINT16_MAX)) { + return false; + } + data.length = (uint16_t) tmp_value; + data.value = &payload[offset]; + state = (data.length > 0) ? TLV_STATE_VALUE : TLV_STATE_TAG; + break; + + case TLV_STATE_VALUE: + if ((offset + data.length) > size) { + PRINTF("Error: value would go beyond the TLV payload!\n"); + return false; + } + offset += data.length; + state = TLV_STATE_TAG; + break; + + default: + return false; + } + + // back to TAG, call the handler function + if ((state == TLV_STATE_TAG) && (handler != NULL)) { + data.raw_size = (&payload[offset] - data.raw); + if (!(*handler)(&data, context)) { + PRINTF("Error: failed in handler of tag 0x%02x\n", data.tag); + return false; + } + } + } + // make sure the payload didn't end prematurely + return state == TLV_STATE_TAG; +} diff --git a/src/tlv.h b/src/tlv.h new file mode 100644 index 000000000..c41d67c66 --- /dev/null +++ b/src/tlv.h @@ -0,0 +1,24 @@ +#ifndef TLV_H_ +#define TLV_H_ + +#include +#include + +#define TLV_TAG_ERROR_MSG "Error: Unknown TLV tag (0x%02x)\n" + +typedef struct { + uint8_t tag; // T + uint16_t length; // L + const uint8_t *value; // V + + // in case the handler needs to do some processing on the raw TLV-encoded data (like computing a + // signature) + const uint8_t *raw; + uint16_t raw_size; +} s_tlv_data; + +typedef bool (*f_tlv_data_handler)(const s_tlv_data *, void *); + +bool tlv_parse(const uint8_t *payload, uint16_t size, f_tlv_data_handler handler, void *context); + +#endif // !TLV_H_ diff --git a/src/tlv_apdu.c b/src/tlv_apdu.c new file mode 100644 index 000000000..a8bc02f76 --- /dev/null +++ b/src/tlv_apdu.c @@ -0,0 +1,90 @@ +#include +#include "tlv_apdu.h" +#include "read.h" +#ifdef HAVE_DYN_MEM_ALLOC +#include "mem.h" +#endif +#include "os_print.h" + +#ifdef HAVE_DYN_MEM_ALLOC +static uint8_t *g_tlv_payload = NULL; +static uint16_t g_tlv_size; +static uint16_t g_tlv_pos; +static bool g_dyn; +#endif + +bool tlv_from_apdu(bool first_chunk, + uint8_t lc, + const uint8_t *payload, + f_tlv_payload_handler handler) { +#ifndef HAVE_DYN_MEM_ALLOC + uint16_t g_tlv_size = 0; + uint16_t g_tlv_pos = 0; +#endif + bool ret = true; + uint8_t offset = 0; + uint8_t chunk_length; + + if (first_chunk) { + if ((offset + sizeof(g_tlv_size)) > lc) { + return false; + } + g_tlv_size = read_u16_be(payload, offset); + offset += sizeof(g_tlv_size); + g_tlv_pos = 0; + if (g_tlv_size > (lc - offset)) { +#ifdef HAVE_DYN_MEM_ALLOC + if (g_tlv_payload != NULL) { + PRINTF("Error: remnants from an incomplete TLV payload!\n"); + mem_dealloc(g_tlv_size); + g_tlv_payload = NULL; + return false; + } + + g_dyn = true; + g_tlv_payload = mem_alloc(g_tlv_size); + } else { + g_dyn = false; +#else + PRINTF( + "Error: cannot handle a TLV payload accross multiple APDUs without dynamic memory " + "allocator\n"); + return false; +#endif + } + } +#ifdef HAVE_DYN_MEM_ALLOC + if (g_dyn && (g_tlv_payload == NULL)) { + return false; + } +#endif + chunk_length = lc - offset; + if ((g_tlv_pos + chunk_length) > g_tlv_size) { + PRINTF("TLV payload bigger than expected!\n"); +#ifdef HAVE_DYN_MEM_ALLOC + if (g_dyn) { + mem_dealloc(g_tlv_size); + g_tlv_payload = NULL; + } +#endif + return false; + } + +#ifdef HAVE_DYN_MEM_ALLOC + if (g_dyn) { + memcpy(g_tlv_payload + g_tlv_pos, payload + offset, chunk_length); + } +#endif + + g_tlv_pos += chunk_length; + + if (g_tlv_pos == g_tlv_size) { +#ifdef HAVE_DYN_MEM_ALLOC + ret = (*handler)(g_dyn ? g_tlv_payload : &payload[offset], g_tlv_size, g_dyn); + g_tlv_payload = NULL; +#else + ret = (*handler)(&payload[offset], g_tlv_size, false); +#endif + } + return ret; +} diff --git a/src/tlv_apdu.h b/src/tlv_apdu.h new file mode 100644 index 000000000..5c831ef0d --- /dev/null +++ b/src/tlv_apdu.h @@ -0,0 +1,14 @@ +#ifndef TLV_APDU_H_ +#define TLV_APDU_H_ + +#include +#include + +typedef bool (*f_tlv_payload_handler)(const uint8_t *payload, uint16_t size, bool to_free); + +bool tlv_from_apdu(bool first_chunk, + uint8_t lc, + const uint8_t *payload, + f_tlv_payload_handler handler); + +#endif // !TLV_APDU_H_ diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 000000000..012f351ff --- /dev/null +++ b/src/utils.c @@ -0,0 +1,31 @@ +#include +#include "utils.h" + +void buf_shrink_expand(const uint8_t *src, size_t src_size, uint8_t *dst, size_t dst_size) { + size_t src_off; + size_t dst_off; + + if (src_size > dst_size) { + src_off = src_size - dst_size; + dst_off = 0; + } else { + src_off = 0; + dst_off = dst_size - src_size; + explicit_bzero(dst, dst_off); + } + memcpy(&dst[dst_off], &src[src_off], dst_size - dst_off); +} + +void str_cpy_explicit_trunc(const char *src, size_t src_size, char *dst, size_t dst_size) { + size_t off; + const char trunc_marker[] = "..."; + + if (src_size < dst_size) { + memcpy(dst, src, src_size); + dst[src_size] = '\0'; + } else { + off = dst_size - sizeof(trunc_marker); + memcpy(dst, src, off); + memcpy(&dst[off], trunc_marker, sizeof(trunc_marker)); + } +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 000000000..20e2c99b2 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,11 @@ +#ifndef UTILS_H_ +#define UTILS_H_ + +#include + +#define SET_BIT(a) (1 << a) + +void buf_shrink_expand(const uint8_t *src, size_t src_size, uint8_t *dst, size_t dst_size); +void str_cpy_explicit_trunc(const char *src, size_t src_size, char *dst, size_t dst_size); + +#endif // !UTILS_H_ diff --git a/src_bagl/ui_flow_signTx.c b/src_bagl/ui_flow_signTx.c index d25e6c34e..0279ad748 100644 --- a/src_bagl/ui_flow_signTx.c +++ b/src_bagl/ui_flow_signTx.c @@ -2,7 +2,6 @@ #include "ui_callbacks.h" #include "chainConfig.h" #include "common_utils.h" -#include "feature_signTx.h" #include "network.h" #include "eth_plugin_handler.h" #include "ui_plugin.h" diff --git a/src_bagl/ui_gcs.c b/src_bagl/ui_gcs.c new file mode 100644 index 000000000..ca0505bf7 --- /dev/null +++ b/src_bagl/ui_gcs.c @@ -0,0 +1,445 @@ +#ifdef HAVE_GENERIC_TX_PARSER + +#include "ux.h" +#include "gtp_field_table.h" +#include "gtp_tx_info.h" +#include "feature_signTx.h" +#include "ui_callbacks.h" +#include "network.h" +#include "apdu_constants.h" + +// forward declaration for BAGL step +static void review_contract_info(void); +static void review(int index); +static void dyn_prev(void); +static void dyn_next(void); +static void switch_pre_dyn(void); +static void switch_post_dyn(void); +static void prepare_review_title(void); +static void switch_network(bool forward); +static void network_prev(void); +static void network_next(void); + +static void format_network(void) { + if (get_network_as_string(strings.tmp.tmp, sizeof(strings.tmp.tmp)) != APDU_RESPONSE_OK) { + PRINTF("Error: Could not format the network!\n"); + } +} + +static void format_fees(void) { + if (max_transaction_fee_to_string(&tmpContent.txContent.gasprice, + &tmpContent.txContent.startgas, + strings.tmp.tmp, + sizeof(strings.tmp.tmp)) == false) { + PRINTF("Error: Could not format the max fees!\n"); + } +} + +// clang-format off +UX_STEP_NOCB_INIT( + ui_gcs_review_step, + pnn, + prepare_review_title(), + { + &C_icon_eye, + strings.tmp.tmp2, + strings.tmp.tmp, + } +); +UX_STEP_CB( + ui_gcs_contract_info_btn_step, + pbb, + review_contract_info(), + { + &C_icon_certificate, + "See contract", + "information", + } +); +UX_STEP_CB( + ui_gcs_back_step, + pb, + review(1), + { + &C_icon_back_x, + "Back", + } +); +UX_STEP_INIT( + ui_gcs_switch_pre_network_step, + NULL, + NULL, + { + switch_network(true); + } +); +UX_STEP_INIT( + ui_gcs_network_prev_step, + NULL, + NULL, + { + network_prev(); + } +); +UX_STEP_NOCB_INIT( + ui_gcs_network_step, + bnnn_paging, + format_network(), + { + .title = "Network", + .text = strings.tmp.tmp, + } +); +UX_STEP_INIT( + ui_gcs_network_next_step, + NULL, + NULL, + { + network_next(); + } +); +UX_STEP_INIT( + ui_gcs_switch_post_network_step, + NULL, + NULL, + { + switch_network(false); + } +); +UX_STEP_NOCB_INIT( + ui_gcs_fees_step, + bnnn_paging, + format_fees(), + { + .title = "Max fees", + .text = strings.tmp.tmp, + } +); +UX_STEP_CB( + ui_gcs_accept_step, + pb, + tx_ok_cb(), + { + &C_icon_validate_14, + "Sign transaction", + } +); +UX_STEP_CB( + ui_gcs_reject_step, + pb, + tx_cancel_cb(), + { + &C_icon_crossmark, + "Reject", + } +); + +UX_STEP_INIT( + ui_gcs_dyn_prev_step, + NULL, + NULL, + { + dyn_prev(); + } +); +UX_STEP_NOCB( + ui_gcs_dyn_step, + bnnn_paging, + { + .title = strings.tmp.tmp2, + .text = strings.tmp.tmp, + } +); +UX_STEP_INIT( + ui_gcs_dyn_next_step, + NULL, + NULL, + { + dyn_next(); + } +); + +UX_STEP_INIT( + ui_gcs_switch_pre_dyn_step, + NULL, + NULL, + { + switch_pre_dyn(); + } +); +UX_STEP_INIT( + ui_gcs_switch_post_dyn_step, + NULL, + NULL, + { + switch_post_dyn(); + } +); +// clang-format on + +UX_FLOW(ui_gcs_flow, + &ui_gcs_review_step, + &ui_gcs_contract_info_btn_step, + + // dynamic field handling + &ui_gcs_switch_pre_dyn_step, + &ui_gcs_dyn_prev_step, + &ui_gcs_dyn_step, + &ui_gcs_dyn_next_step, + &ui_gcs_switch_post_dyn_step, + + // network handling + &ui_gcs_switch_pre_network_step, + &ui_gcs_network_prev_step, + &ui_gcs_network_step, + &ui_gcs_network_next_step, + &ui_gcs_switch_post_network_step, + + &ui_gcs_fees_step, + &ui_gcs_accept_step, + &ui_gcs_reject_step); + +UX_FLOW(ui_gcs_contract_info_beg_flow, &ui_gcs_switch_pre_dyn_step); + +UX_FLOW(ui_gcs_dyn_beg_flow, &ui_gcs_dyn_step, &ui_gcs_dyn_next_step); + +UX_FLOW(ui_gcs_dyn_middle_flow, &ui_gcs_dyn_prev_step, &ui_gcs_dyn_step, &ui_gcs_dyn_next_step); + +UX_FLOW(ui_gcs_contract_info_end_flow, &ui_gcs_switch_post_dyn_step, &ui_gcs_back_step); + +typedef enum { + TOP_LEVEL, + TX_INFO, +} e_level; + +static e_level level; +static uint8_t dyn_idx; + +static void prepare_review_title(void) { + const char *op_type; + + snprintf(strings.tmp.tmp2, sizeof(strings.tmp.tmp2), "Review transaction to"); + if ((op_type = get_operation_type()) != NULL) { + snprintf(strings.tmp.tmp, sizeof(strings.tmp.tmp), "%s", op_type); + } +} + +static bool prepare_kv_info(uint8_t idx) { + bool found = false; + uint8_t count = 0; + const char *value; + + const char *value2; + if ((value = get_creator_legal_name()) != NULL) { + count += 1; + if (count == idx) { + snprintf(strings.tmp.tmp2, sizeof(strings.tmp.tmp2), "Contract owner"); + if ((value2 = get_creator_url()) != NULL) { + snprintf(strings.tmp.tmp, sizeof(strings.tmp.tmp), "%s\n%s", value, value2); + } else { + snprintf(strings.tmp.tmp, sizeof(strings.tmp.tmp), "%s", value); + } + found = true; + } + } + + if ((value = get_contract_name()) != NULL) { + count += 1; + if (count == idx) { + snprintf(strings.tmp.tmp2, sizeof(strings.tmp.tmp2), "Contract"); + snprintf(strings.tmp.tmp, sizeof(strings.tmp.tmp), "%s", value); + found = true; + } + } + + const uint8_t *addr = get_contract_addr(); + count += 1; + if (count == idx) { + snprintf(strings.tmp.tmp2, sizeof(strings.tmp.tmp2), "Contract address"); + if (!getEthDisplayableAddress((uint8_t *) addr, + strings.tmp.tmp, + sizeof(strings.tmp.tmp), + chainConfig->chainId)) { + return false; + } + found = true; + } + + if ((value = get_deploy_date()) != NULL) { + count += 1; + if (count == idx) { + snprintf(strings.tmp.tmp2, sizeof(strings.tmp.tmp2), "Deployed on"); + snprintf(strings.tmp.tmp, sizeof(strings.tmp.tmp), "%s", value); + found = true; + } + } + return found; +} + +static bool prepare_key_value(uint8_t idx) { + bool found = false; + s_field_table_entry field; + + switch (level) { + case TX_INFO: + found = prepare_kv_info(idx); + break; + case TOP_LEVEL: + if ((found = get_from_field_table(idx - 1, &field))) { + strncpy(strings.tmp.tmp2, field.key, sizeof(strings.tmp.tmp2)); + strncpy(strings.tmp.tmp, field.value, sizeof(strings.tmp.tmp)); + } + break; + default: + break; + } + return found; +} + +static void dyn_prev(void) { + const ux_flow_step_t *const *flow = NULL; + const ux_flow_step_t *step = NULL; + + dyn_idx -= 1; + if (prepare_key_value(dyn_idx)) { + // found + switch (level) { + case TX_INFO: + flow = (dyn_idx == 1) ? ui_gcs_dyn_beg_flow : ui_gcs_dyn_middle_flow; + break; + case TOP_LEVEL: + flow = ui_gcs_flow; + break; + default: + break; + } + step = &ui_gcs_dyn_step; + } else { + // not found + switch (level) { + case TX_INFO: + flow = ui_gcs_contract_info_end_flow; + step = &ui_gcs_back_step; + break; + case TOP_LEVEL: + flow = ui_gcs_flow; + step = &ui_gcs_contract_info_btn_step; + break; + default: + break; + } + } + ux_flow_init(0, flow, step); +} + +static void dyn_next(void) { + const ux_flow_step_t *const *flow = NULL; + const ux_flow_step_t *step = NULL; + + dyn_idx += 1; + if (prepare_key_value(dyn_idx)) { + // found + switch (level) { + case TX_INFO: + flow = (dyn_idx == 1) ? ui_gcs_dyn_beg_flow : ui_gcs_dyn_middle_flow; + step = &ui_gcs_dyn_step; + break; + case TOP_LEVEL: + flow = ui_gcs_flow; + step = &ui_gcs_dyn_step; + break; + default: + break; + } + } else { + // not found + dyn_idx -= 1; + switch (level) { + case TX_INFO: + flow = ui_gcs_contract_info_end_flow; + step = &ui_gcs_back_step; + break; + case TOP_LEVEL: + flow = ui_gcs_flow; + step = &ui_gcs_switch_pre_network_step; + break; + default: + break; + } + } + ux_flow_init(0, flow, step); +} + +static void switch_pre_dyn(void) { + ux_flow_init(0, ui_gcs_flow, &ui_gcs_dyn_next_step); +} + +static void switch_post_dyn(void) { + const ux_flow_step_t *const *flow = NULL; + const ux_flow_step_t *step = NULL; + + switch (level) { + case TX_INFO: + flow = ui_gcs_dyn_middle_flow; + step = &ui_gcs_dyn_step; + break; + case TOP_LEVEL: + if (dyn_idx == 0) { + // no field has been shown + step = &ui_gcs_contract_info_btn_step; + } else { + // artificially lower the index then ask for the next one + dyn_idx -= 1; + step = &ui_gcs_dyn_next_step; + } + flow = ui_gcs_flow; + break; + default: + break; + } + ux_flow_init(0, flow, step); +} + +static void network_prev(void) { + ux_flow_init(0, ui_gcs_flow, &ui_gcs_switch_post_dyn_step); +} + +static void network_next(void) { + ux_flow_init(0, ui_gcs_flow, &ui_gcs_fees_step); +} + +static void switch_network(bool forward) { + uint64_t chain_id = get_tx_chain_id(); + + if (chain_id != chainConfig->chainId) { + ux_flow_init(0, ui_gcs_flow, &ui_gcs_network_step); + } else { + if (forward) { + network_next(); + } else { + network_prev(); + } + } +} + +static void review_contract_info(void) { + dyn_idx = 0; + level = TX_INFO; + ux_flow_init(0, ui_gcs_dyn_middle_flow, &ui_gcs_dyn_next_step); +} + +static void review(int index) { + dyn_idx = 0; + level = TOP_LEVEL; + ux_flow_init(0, ui_gcs_flow, ui_gcs_flow[index]); +} + +bool ui_gcs(void) { + review(0); + return true; +} + +void ui_gcs_cleanup(void) { +} + +#endif // HAVE_GENERIC_TX_PARSER diff --git a/src_features/generic_tx_parser/calldata.c b/src_features/generic_tx_parser/calldata.c new file mode 100644 index 000000000..0b152f70f --- /dev/null +++ b/src_features/generic_tx_parser/calldata.c @@ -0,0 +1,65 @@ +#ifdef HAVE_GENERIC_TX_PARSER + +#include +#include "calldata.h" +#include "os_print.h" +#include "mem.h" + +typedef struct { + uint8_t *ptr; + size_t size; + size_t expected_size; +} s_calldata; + +static s_calldata g_calldata = {0}; + +bool calldata_init(size_t size) { + if ((g_calldata.ptr = mem_alloc(size)) == NULL) { + return false; + } + g_calldata.expected_size = size; + return true; +} + +bool calldata_append(const uint8_t *buffer, size_t size) { + if ((g_calldata.size + size) > g_calldata.expected_size) { + return false; + } + memcpy(&g_calldata.ptr[g_calldata.size], buffer, size); + g_calldata.size += size; + return true; +} + +void calldata_cleanup(void) { + mem_dealloc(g_calldata.size); + explicit_bzero(&g_calldata, sizeof(g_calldata)); +} + +static bool has_valid_calldata(const s_calldata *calldata) { + if (calldata->ptr == NULL) { + PRINTF("Error: no calldata!\n"); + return false; + } + if (g_calldata.size != g_calldata.expected_size) { + PRINTF("Error: incomplete calldata!\n"); + return false; + } + return true; +} + +const uint8_t *calldata_get_selector(void) { + if (!has_valid_calldata(&g_calldata) || (g_calldata.size < CALLDATA_SELECTOR_SIZE)) { + return NULL; + } + return g_calldata.ptr; +} + +const uint8_t *calldata_get_chunk(int idx) { + if (!has_valid_calldata(&g_calldata) || + (g_calldata.size < (CALLDATA_SELECTOR_SIZE + ((size_t) idx + 1) * CALLDATA_CHUNK_SIZE))) { + return NULL; + } + return &g_calldata.ptr[CALLDATA_SELECTOR_SIZE + (idx * CALLDATA_CHUNK_SIZE)]; +} + +#endif // HAVE_GENERIC_TX_PARSER diff --git a/src_features/generic_tx_parser/calldata.h b/src_features/generic_tx_parser/calldata.h new file mode 100644 index 000000000..5a71a2b2d --- /dev/null +++ b/src_features/generic_tx_parser/calldata.h @@ -0,0 +1,17 @@ +#ifndef CALLDATA_H_ +#define CALLDATA_H_ + +#include +#include +#include + +#define CALLDATA_SELECTOR_SIZE 4 +#define CALLDATA_CHUNK_SIZE 32 + +bool calldata_init(size_t size); +bool calldata_append(const uint8_t *buffer, size_t size); +void calldata_cleanup(void); +const uint8_t *calldata_get_selector(void); +const uint8_t *calldata_get_chunk(int idx); + +#endif // !CALLDATA_H_ diff --git a/src_features/generic_tx_parser/cmd_field.c b/src_features/generic_tx_parser/cmd_field.c new file mode 100644 index 000000000..ff404e991 --- /dev/null +++ b/src_features/generic_tx_parser/cmd_field.c @@ -0,0 +1,51 @@ +#ifdef HAVE_GENERIC_TX_PARSER + +#include "cmd_field.h" +#include "cx.h" +#include "apdu_constants.h" +#include "mem.h" +#include "tlv.h" +#include "tlv_apdu.h" +#include "gtp_field.h" +#include "cmd_tx_info.h" +#include "gtp_tx_info.h" + +static bool handle_tlv_payload(const uint8_t *payload, uint16_t size, bool to_free) { + s_field field = {0}; + s_field_ctx ctx = {0}; + + ctx.field = &field; + if (!tlv_parse(payload, size, (f_tlv_data_handler) &handle_field_struct, &ctx)) return false; + if (cx_hash_no_throw(get_fields_hash_ctx(), 0, payload, size, NULL, 0) != CX_OK) return false; + if (to_free) mem_dealloc(size); + if (!verify_field_struct(&ctx)) { + PRINTF("Error: could not verify the field struct!\n"); + return false; + } + if (!format_field(&field)) { + PRINTF("Error while formatting the field\n"); + return false; + } + return true; +} + +uint16_t handle_field(uint8_t p1, uint8_t p2, uint8_t lc, const uint8_t *payload) { + (void) p2; + if (appState != APP_STATE_SIGNING_TX) { + PRINTF("App not in TX signing mode!\n"); + return APDU_RESPONSE_CONDITION_NOT_SATISFIED; + } + + if (g_tx_info == NULL) { + PRINTF("Error: Field received without a TX info!\n"); + gcs_cleanup(); + return APDU_RESPONSE_CONDITION_NOT_SATISFIED; + } + + if (!tlv_from_apdu(p1 == P1_FIRST_CHUNK, lc, payload, &handle_tlv_payload)) { + return APDU_RESPONSE_INVALID_DATA; + } + return APDU_RESPONSE_OK; +} + +#endif // HAVE_GENERIC_TX_PARSER diff --git a/src_features/generic_tx_parser/cmd_field.h b/src_features/generic_tx_parser/cmd_field.h new file mode 100644 index 000000000..e246b9eb1 --- /dev/null +++ b/src_features/generic_tx_parser/cmd_field.h @@ -0,0 +1,8 @@ +#ifndef CMD_FIELD_H_ +#define CMD_FIELD_H_ + +#include + +uint16_t handle_field(uint8_t p1, uint8_t p2, uint8_t lc, const uint8_t *payload); + +#endif // !CMD_FIELD_H_ diff --git a/src_features/generic_tx_parser/cmd_tx_info.c b/src_features/generic_tx_parser/cmd_tx_info.c new file mode 100644 index 000000000..c5379523c --- /dev/null +++ b/src_features/generic_tx_parser/cmd_tx_info.c @@ -0,0 +1,73 @@ +#ifdef HAVE_GENERIC_TX_PARSER + +#include +#include "cmd_tx_info.h" +#include "cx.h" +#include "apdu_constants.h" +#include "mem.h" +#include "gtp_tx_info.h" +#include "tlv.h" +#include "tlv_apdu.h" +#include "calldata.h" +#include "gtp_field_table.h" + +extern cx_sha3_t hash_ctx; + +static bool handle_tlv_payload(const uint8_t *payload, uint16_t size, bool to_free) { + s_tx_info_ctx ctx = {0}; + + ctx.tx_info = g_tx_info; + explicit_bzero(ctx.tx_info, sizeof(*ctx.tx_info)); + cx_sha256_init((cx_sha256_t *) &ctx.struct_hash); + if (!tlv_parse(payload, size, (f_tlv_data_handler) &handle_tx_info_struct, &ctx)) return false; + if (to_free) mem_dealloc(sizeof(size)); + if (!verify_tx_info_struct(&ctx)) { + return false; + } + if (cx_sha3_init_no_throw(&hash_ctx, 256) != CX_OK) { + return false; + } + field_table_init(); + return true; +} + +uint16_t handle_tx_info(uint8_t p1, uint8_t p2, uint8_t lc, const uint8_t *payload) { + (void) p2; + if (appState != APP_STATE_SIGNING_TX) { + PRINTF("App not in TX signing mode!\n"); + return APDU_RESPONSE_CONDITION_NOT_SATISFIED; + } + + if (p1 == P1_FIRST_CHUNK) { + if (g_tx_info != NULL) { + PRINTF("Error: TX info received when one was still in RAM!\n"); + gcs_cleanup(); + return APDU_RESPONSE_CONDITION_NOT_SATISFIED; + } + + if ((g_tx_info = mem_alloc(sizeof(*g_tx_info))) == NULL) { + return APDU_RESPONSE_INSUFFICIENT_MEMORY; + } + } + if (g_tx_info == NULL) { + return APDU_RESPONSE_INVALID_DATA; + } + if (!tlv_from_apdu(p1 == P1_FIRST_CHUNK, lc, payload, &handle_tlv_payload)) { + return APDU_RESPONSE_INVALID_DATA; + } + return APDU_RESPONSE_OK; +} + +void ui_gcs_cleanup(void); + +void gcs_cleanup(void) { + ui_gcs_cleanup(); + field_table_cleanup(); + if (g_tx_info != NULL) { + mem_dealloc(sizeof(*g_tx_info)); + g_tx_info = NULL; + } + calldata_cleanup(); +} + +#endif // HAVE_GENERIC_TX_PARSER diff --git a/src_features/generic_tx_parser/cmd_tx_info.h b/src_features/generic_tx_parser/cmd_tx_info.h new file mode 100644 index 000000000..a093669b4 --- /dev/null +++ b/src_features/generic_tx_parser/cmd_tx_info.h @@ -0,0 +1,9 @@ +#ifndef CMD_TX_INFO_H_ +#define CMD_TX_INFO_H_ + +#include + +uint16_t handle_tx_info(uint8_t p1, uint8_t p2, uint8_t lc, const uint8_t *payload); +void gcs_cleanup(void); + +#endif // !CMD_TX_INFO_H_ diff --git a/src_features/generic_tx_parser/gtp_data_path.c b/src_features/generic_tx_parser/gtp_data_path.c new file mode 100644 index 000000000..9749f0618 --- /dev/null +++ b/src_features/generic_tx_parser/gtp_data_path.c @@ -0,0 +1,311 @@ +#ifdef HAVE_GENERIC_TX_PARSER + +#include // memcpy / explicit_bzero +#include "os_print.h" +#include "gtp_data_path.h" +#include "read.h" +#include "utils.h" +#include "calldata.h" + +enum { + TAG_VERSION = 0x00, + TAG_TUPLE = 0x01, + TAG_ARRAY = 0x02, + TAG_REF = 0x03, + TAG_LEAF = 0x04, + TAG_SLICE = 0x05, +}; + +static bool handle_version(const s_tlv_data *data, s_data_path_context *context) { + if (data->length != sizeof(context->data_path->version)) { + return false; + } + context->data_path->version = data->value[0]; + return true; +} + +static bool handle_tuple(const s_tlv_data *data, s_data_path_context *context) { + uint8_t buf[sizeof(uint16_t)]; + + if (data->length > sizeof(buf)) { + return false; + } + buf_shrink_expand(data->value, data->length, buf, sizeof(buf)); + context->data_path->elements[context->data_path->size].type = ELEMENT_TYPE_TUPLE; + context->data_path->elements[context->data_path->size].tuple.value = read_u16_be(buf, 0); + + context->data_path->size += 1; + return true; +} + +static bool handle_array(const s_tlv_data *data, s_data_path_context *context) { + s_path_array_context ctx = {0}; + + ctx.args = &context->data_path->elements[context->data_path->size].array; + explicit_bzero(ctx.args, sizeof(*ctx.args)); + if (!tlv_parse(data->value, data->length, (f_tlv_data_handler) &handle_array_struct, &ctx)) + return false; + context->data_path->elements[context->data_path->size].type = ELEMENT_TYPE_ARRAY; + context->data_path->size += 1; + return true; +} + +static bool handle_ref(const s_tlv_data *data, s_data_path_context *context) { + if (data->length != 0) { + return false; + } + context->data_path->elements[context->data_path->size].type = ELEMENT_TYPE_REF; + + context->data_path->size += 1; + return true; +} + +static bool handle_leaf(const s_tlv_data *data, s_data_path_context *context) { + if (data->length != sizeof(e_path_leaf_type)) { + return false; + } + context->data_path->elements[context->data_path->size].type = ELEMENT_TYPE_LEAF; + context->data_path->elements[context->data_path->size].leaf.type = data->value[0]; + + context->data_path->size += 1; + return true; +} + +static bool handle_slice(const s_tlv_data *data, s_data_path_context *context) { + s_path_slice_context ctx = {0}; + + ctx.args = &context->data_path->elements[context->data_path->size].slice; + explicit_bzero(ctx.args, sizeof(*ctx.args)); + if (!tlv_parse(data->value, data->length, (f_tlv_data_handler) &handle_slice_struct, &ctx)) + return false; + context->data_path->elements[context->data_path->size].type = ELEMENT_TYPE_SLICE; + context->data_path->size += 1; + return true; +} + +bool handle_data_path_struct(const s_tlv_data *data, s_data_path_context *context) { + bool ret; + + if (data->tag != TAG_VERSION) { + if (context->data_path->size >= PATH_MAX_SIZE) { + return false; + } + } + switch (data->tag) { + case TAG_VERSION: + ret = handle_version(data, context); + break; + case TAG_TUPLE: + ret = handle_tuple(data, context); + break; + case TAG_ARRAY: + ret = handle_array(data, context); + break; + case TAG_REF: + ret = handle_ref(data, context); + break; + case TAG_LEAF: + ret = handle_leaf(data, context); + break; + case TAG_SLICE: + ret = handle_slice(data, context); + break; + default: + PRINTF(TLV_TAG_ERROR_MSG, data->tag); + ret = false; + } + return ret; +} + +static bool path_tuple(const s_tuple_args *tuple, uint32_t *offset, uint32_t *ref_offset) { + *ref_offset = *offset; + *offset += tuple->value; + return true; +} + +static bool path_ref(uint32_t *offset, uint32_t *ref_offset) { + uint8_t buf[sizeof(uint16_t)]; + const uint8_t *chunk; + + if ((chunk = calldata_get_chunk(*offset)) == NULL) { + return false; + } + buf_shrink_expand(chunk, CALLDATA_CHUNK_SIZE, buf, sizeof(buf)); + *offset = read_u16_be(buf, 0) / CALLDATA_CHUNK_SIZE; + *offset += *ref_offset; + return true; +} + +static bool path_leaf(const s_leaf_args *leaf, + uint32_t *offset, + s_parsed_value_collection *collection) { + uint8_t buf[sizeof(uint16_t)]; + const uint8_t *chunk; + + if (collection->size > MAX_VALUE_COLLECTION_SIZE) { + return false; + } + + switch (leaf->type) { + case LEAF_TYPE_STATIC: + collection->value[collection->size].length = CALLDATA_CHUNK_SIZE; + break; + + case LEAF_TYPE_DYNAMIC: + if ((chunk = calldata_get_chunk(*offset)) == NULL) { + return false; + } + // TODO: properly handle multi-chunk dynamic values once calldata compression + // is implemented + buf_shrink_expand(chunk, CALLDATA_CHUNK_SIZE, buf, sizeof(buf)); + collection->value[collection->size].length = read_u16_be(buf, 0); + *offset += 1; + break; + + default: + return false; + } + if ((chunk = calldata_get_chunk(*offset)) == NULL) { + return false; + } + collection->value[collection->size].ptr = chunk; + collection->size += 1; + return true; +} + +static bool path_slice(const s_slice_args *slice, s_parsed_value_collection *collection) { + uint16_t start; + uint16_t end; + uint16_t value_length; + + if (collection->size == 0) { + return false; + } + + value_length = collection->value[collection->size - 1].length; + if (slice->has_start) { + start = (slice->start < 0) ? ((int16_t) value_length + slice->start) : slice->start; + } else { + start = 0; + } + + if (slice->has_end) { + end = (slice->end < 0) ? ((int16_t) value_length + slice->end) : slice->end; + } else { + end = value_length; + } + + if ((start >= end) || (end > value_length)) { + return false; + } + collection->value[collection->size - 1].ptr += start; + collection->value[collection->size - 1].length = (end - start); + return true; +} + +#define MAX_ARRAYS 8 + +typedef struct { + uint8_t depth; + uint8_t passes_remaining[MAX_ARRAYS]; + uint8_t index; +} s_arrays_info; + +static bool path_array(const s_array_args *array, + uint32_t *offset, + uint32_t *ref_offset, + s_arrays_info *arrays_info) { + uint8_t buf[sizeof(uint16_t)]; + uint16_t array_size; + uint16_t idx; + uint16_t start; + uint16_t end; + const uint8_t *chunk; + + if (arrays_info->index >= MAX_ARRAYS) { + return false; + } + if ((chunk = calldata_get_chunk(*offset)) == NULL) { + return false; + } + buf_shrink_expand(chunk, CALLDATA_CHUNK_SIZE, buf, sizeof(buf)); + array_size = read_u16_be(buf, 0); + + if (array->has_start) { + start = (array->start < 0) ? ((int16_t) array_size + array->start) : array->start; + } else { + start = 0; + } + + if (array->has_end) { + end = (array->end < 0) ? ((int16_t) array_size + array->end) : array->end; + } else { + end = array_size; + } + + *offset += 1; + if (arrays_info->index == arrays_info->depth) { + // new depth + arrays_info->passes_remaining[arrays_info->index] = (end - start); + arrays_info->depth += 1; + } + idx = start + ((end - start) - arrays_info->passes_remaining[arrays_info->index]); + *ref_offset = *offset; + *offset += (idx * array->weight); + arrays_info->index += 1; + return true; +} + +static void arrays_update(s_arrays_info *arrays_info) { + while (arrays_info->depth > 0) { + if ((arrays_info->passes_remaining[arrays_info->depth - 1] -= 1) > 0) { + break; + } + arrays_info->depth -= 1; + } +} + +bool data_path_get(const s_data_path *data_path, s_parsed_value_collection *collection) { + bool ret; + uint32_t offset; + uint32_t ref_offset; + s_arrays_info arinf = {0}; + + do { + arinf.index = 0; + offset = 0; + ref_offset = offset; + for (int i = 0; i < data_path->size; ++i) { + switch (data_path->elements[i].type) { + case ELEMENT_TYPE_TUPLE: + ret = path_tuple(&data_path->elements[i].tuple, &offset, &ref_offset); + break; + + case ELEMENT_TYPE_ARRAY: + ret = path_array(&data_path->elements[i].array, &offset, &ref_offset, &arinf); + break; + + case ELEMENT_TYPE_REF: + ret = path_ref(&offset, &ref_offset); + break; + + case ELEMENT_TYPE_LEAF: + ret = path_leaf(&data_path->elements[i].leaf, &offset, collection); + break; + + case ELEMENT_TYPE_SLICE: + ret = path_slice(&data_path->elements[i].slice, collection); + break; + + default: + ret = false; + } + + if (!ret) return false; + } + arrays_update(&arinf); + } while (arinf.depth > 0); + return true; +} + +#endif // HAVE_GENERIC_TX_PARSER diff --git a/src_features/generic_tx_parser/gtp_data_path.h b/src_features/generic_tx_parser/gtp_data_path.h new file mode 100644 index 000000000..47f84bc3d --- /dev/null +++ b/src_features/generic_tx_parser/gtp_data_path.h @@ -0,0 +1,60 @@ +#ifndef GTP_DATA_PATH_H_ +#define GTP_DATA_PATH_H_ + +#include +#include +#include +#include "tlv.h" +#include "gtp_parsed_value.h" +#include "gtp_path_array.h" +#include "gtp_path_slice.h" + +#define PATH_MAX_SIZE 16 + +typedef enum { + ELEMENT_TYPE_TUPLE, + ELEMENT_TYPE_ARRAY, + ELEMENT_TYPE_REF, + ELEMENT_TYPE_LEAF, + ELEMENT_TYPE_SLICE, +} e_path_element_type; + +typedef enum { + LEAF_TYPE_ARRAY = 0x01, + LEAF_TYPE_TUPLE = 0x02, + LEAF_TYPE_STATIC = 0x03, + LEAF_TYPE_DYNAMIC = 0x04, +} e_path_leaf_type; + +typedef struct { + uint16_t value; +} s_tuple_args; + +typedef struct { + e_path_leaf_type type; +} s_leaf_args; + +typedef struct { + e_path_element_type type; + union { + s_tuple_args tuple; + s_array_args array; + s_leaf_args leaf; + s_slice_args slice; + }; +} s_path_element; + +typedef struct { + uint8_t version; + uint8_t size; + s_path_element elements[PATH_MAX_SIZE]; +} s_data_path; + +typedef struct { + s_data_path *data_path; +} s_data_path_context; + +bool handle_data_path_struct(const s_tlv_data *data, s_data_path_context *context); +bool data_path_get(const s_data_path *data_path, s_parsed_value_collection *collection); + +#endif // GTP_DATA_PATH_H_ diff --git a/src_features/generic_tx_parser/gtp_field.c b/src_features/generic_tx_parser/gtp_field.c new file mode 100644 index 000000000..9bec0c2f8 --- /dev/null +++ b/src_features/generic_tx_parser/gtp_field.c @@ -0,0 +1,247 @@ +#ifdef HAVE_GENERIC_TX_PARSER + +#include +#include "gtp_field.h" +#include "utils.h" +#include "os_print.h" + +enum { + BIT_VERSION = 0, + BIT_NAME, + BIT_PARAM_TYPE, + BIT_PARAM, +}; + +enum { + TAG_VERSION = 0x00, + TAG_NAME = 0x01, + PARAM_TYPE = 0x02, + PARAM = 0x03, +}; + +typedef union { + s_param_raw_context raw_ctx; + s_param_amount_context amount_ctx; + s_param_token_amount_context token_amount_ctx; +#ifdef HAVE_NFT_SUPPORT + s_param_nft_context nft_ctx; +#endif + s_param_datetime_context datetime_ctx; + s_param_duration_context duration_ctx; + s_param_unit_context unit_ctx; +#ifdef HAVE_ENUM_VALUE + s_param_enum_context enum_ctx; +#endif +#ifdef HAVE_TRUSTED_NAME + s_param_trusted_name_context trusted_name_ctx; +#endif +} u_param_context; + +static bool handle_version(const s_tlv_data *data, s_field_ctx *context) { + if (data->length != sizeof(context->field->version)) { + return false; + } + context->field->version = data->value[0]; + context->set_flags |= SET_BIT(BIT_VERSION); + return true; +} + +static bool handle_name(const s_tlv_data *data, s_field_ctx *context) { + str_cpy_explicit_trunc((const char *) data->value, + data->length, + context->field->name, + sizeof(context->field->name)); + context->set_flags |= SET_BIT(BIT_NAME); + return true; +} + +static bool handle_param_type(const s_tlv_data *data, s_field_ctx *context) { + if (data->length != sizeof(context->field->param_type)) { + return false; + } + if (context->set_flags & SET_BIT(BIT_PARAM_TYPE)) { + PRINTF("Error: More than one PARAM_TYPE in a FIELD struct!\n"); + return false; + } + context->field->param_type = data->value[0]; + switch (context->field->param_type) { + case PARAM_TYPE_RAW: + case PARAM_TYPE_AMOUNT: + case PARAM_TYPE_TOKEN_AMOUNT: +#ifdef HAVE_NFT_SUPPORT + case PARAM_TYPE_NFT: +#endif + case PARAM_TYPE_DATETIME: + case PARAM_TYPE_DURATION: + case PARAM_TYPE_UNIT: +#ifdef HAVE_ENUM_VALUE + case PARAM_TYPE_ENUM: +#endif +#ifdef HAVE_TRUSTED_NAME + case PARAM_TYPE_TRUSTED_NAME: +#endif + break; + default: + PRINTF("Error: Unsupported param type (%u)\n", context->field->param_type); + return false; + } + context->set_flags |= SET_BIT(BIT_PARAM_TYPE); + return true; +} + +static bool handle_param(const s_tlv_data *data, s_field_ctx *context) { + f_tlv_data_handler handler; + u_param_context param_ctx = {0}; + + if (context->set_flags & SET_BIT(BIT_PARAM)) { + PRINTF("Error: More than one PARAM in a FIELD struct!\n"); + return false; + } + if (!(context->set_flags & SET_BIT(BIT_PARAM_TYPE))) { + PRINTF("Error: Received PARAM without a previous PARAM_TYPE!\n"); + return false; + } + switch (context->field->param_type) { + case PARAM_TYPE_RAW: + handler = (f_tlv_data_handler) &handle_param_raw_struct; + param_ctx.raw_ctx.param = &context->field->param_raw; + break; + case PARAM_TYPE_AMOUNT: + handler = (f_tlv_data_handler) &handle_param_amount_struct; + param_ctx.amount_ctx.param = &context->field->param_amount; + break; + case PARAM_TYPE_TOKEN_AMOUNT: + handler = (f_tlv_data_handler) &handle_param_token_amount_struct; + param_ctx.token_amount_ctx.param = &context->field->param_token_amount; + break; +#ifdef HAVE_NFT_SUPPORT + case PARAM_TYPE_NFT: + handler = (f_tlv_data_handler) &handle_param_nft_struct; + param_ctx.nft_ctx.param = &context->field->param_nft; + break; +#endif + case PARAM_TYPE_DATETIME: + handler = (f_tlv_data_handler) &handle_param_datetime_struct; + param_ctx.datetime_ctx.param = &context->field->param_datetime; + break; + case PARAM_TYPE_DURATION: + handler = (f_tlv_data_handler) &handle_param_duration_struct; + param_ctx.duration_ctx.param = &context->field->param_duration; + break; + case PARAM_TYPE_UNIT: + handler = (f_tlv_data_handler) &handle_param_unit_struct; + param_ctx.unit_ctx.param = &context->field->param_unit; + break; +#ifdef HAVE_ENUM_VALUE + case PARAM_TYPE_ENUM: + handler = (f_tlv_data_handler) &handle_param_enum_struct; + param_ctx.enum_ctx.param = &context->field->param_enum; + break; +#endif +#ifdef HAVE_TRUSTED_NAME + case PARAM_TYPE_TRUSTED_NAME: + handler = (f_tlv_data_handler) &handle_param_trusted_name_struct; + param_ctx.trusted_name_ctx.param = &context->field->param_trusted_name; + break; +#endif + default: + return false; + } + context->set_flags |= SET_BIT(BIT_PARAM); + return tlv_parse(data->value, data->length, handler, ¶m_ctx); +} + +bool handle_field_struct(const s_tlv_data *data, s_field_ctx *context) { + bool ret; + + switch (data->tag) { + case TAG_VERSION: + ret = handle_version(data, context); + break; + case TAG_NAME: + ret = handle_name(data, context); + break; + case PARAM_TYPE: + ret = handle_param_type(data, context); + break; + case PARAM: + ret = handle_param(data, context); + break; + default: + PRINTF(TLV_TAG_ERROR_MSG, data->tag); + ret = false; + } + return ret; +} + +bool verify_field_struct(const s_field_ctx *context) { + uint8_t required_bits = 0; + + // check if struct version was provided + required_bits |= SET_BIT(BIT_VERSION); + if ((context->set_flags & required_bits) != required_bits) { + PRINTF("Error: no struct version specified!\n"); + return false; + } + + // verify required fields + switch (context->field->version) { + case 1: + required_bits |= SET_BIT(BIT_NAME) | SET_BIT(BIT_PARAM_TYPE) | SET_BIT(BIT_PARAM); + break; + default: + PRINTF("Error: unsupported field struct version (%u)\n", context->field->version); + return false; + } + + if ((context->set_flags & required_bits) != required_bits) { + PRINTF("Error: missing required field(s)\n"); + return false; + } + return true; +} + +bool format_field(const s_field *field) { + bool ret; + + switch (field->param_type) { + case PARAM_TYPE_RAW: + ret = format_param_raw(&field->param_raw, field->name); + break; + case PARAM_TYPE_AMOUNT: + ret = format_param_amount(&field->param_amount, field->name); + break; + case PARAM_TYPE_TOKEN_AMOUNT: + ret = format_param_token_amount(&field->param_token_amount, field->name); + break; +#ifdef HAVE_NFT_SUPPORT + case PARAM_TYPE_NFT: + ret = format_param_nft(&field->param_nft, field->name); + break; +#endif + case PARAM_TYPE_DATETIME: + ret = format_param_datetime(&field->param_datetime, field->name); + break; + case PARAM_TYPE_DURATION: + ret = format_param_duration(&field->param_duration, field->name); + break; + case PARAM_TYPE_UNIT: + ret = format_param_unit(&field->param_unit, field->name); + break; +#ifdef HAVE_ENUM_VALUE + case PARAM_TYPE_ENUM: + ret = format_param_enum(&field->param_enum, field->name); + break; +#endif +#ifdef HAVE_TRUSTED_NAME + case PARAM_TYPE_TRUSTED_NAME: + ret = format_param_trusted_name(&field->param_trusted_name, field->name); + break; +#endif + default: + ret = false; + } + return ret; +} + +#endif // HAVE_GENERIC_TX_PARSER diff --git a/src_features/generic_tx_parser/gtp_field.h b/src_features/generic_tx_parser/gtp_field.h new file mode 100644 index 000000000..2a5002f64 --- /dev/null +++ b/src_features/generic_tx_parser/gtp_field.h @@ -0,0 +1,61 @@ +#ifndef GTP_FIELD_H_ +#define GTP_FIELD_H_ + +#include +#include +#include "tlv.h" +#include "gtp_param_raw.h" +#include "gtp_param_amount.h" +#include "gtp_param_token_amount.h" +#include "gtp_param_nft.h" +#include "gtp_param_datetime.h" +#include "gtp_param_duration.h" +#include "gtp_param_unit.h" +#include "gtp_param_enum.h" +#include "gtp_param_trusted_name.h" + +typedef enum { + PARAM_TYPE_RAW = 0, + PARAM_TYPE_AMOUNT, + PARAM_TYPE_TOKEN_AMOUNT, + PARAM_TYPE_NFT, + PARAM_TYPE_DATETIME, + PARAM_TYPE_DURATION, + PARAM_TYPE_UNIT, + PARAM_TYPE_ENUM, + PARAM_TYPE_TRUSTED_NAME, +} e_param_type; + +typedef struct { + uint8_t version; + char name[21]; + e_param_type param_type; + union { + s_param_raw param_raw; + s_param_amount param_amount; + s_param_token_amount param_token_amount; +#ifdef HAVE_NFT_SUPPORT + s_param_nft param_nft; +#endif + s_param_datetime param_datetime; + s_param_duration param_duration; + s_param_unit param_unit; +#ifdef HAVE_ENUM_VALUE + s_param_enum param_enum; +#endif +#ifdef HAVE_TRUSTED_NAME + s_param_trusted_name param_trusted_name; +#endif + }; +} s_field; + +typedef struct { + s_field *field; + uint8_t set_flags; +} s_field_ctx; + +bool handle_field_struct(const s_tlv_data *data, s_field_ctx *context); +bool verify_field_struct(const s_field_ctx *context); +bool format_field(const s_field *field); + +#endif // !GTP_FIELD_H_ diff --git a/src_features/generic_tx_parser/gtp_field_table.c b/src_features/generic_tx_parser/gtp_field_table.c new file mode 100644 index 000000000..39569710a --- /dev/null +++ b/src_features/generic_tx_parser/gtp_field_table.c @@ -0,0 +1,111 @@ +#ifdef HAVE_GENERIC_TX_PARSER + +#include +#include "os_print.h" +#include "gtp_field_table.h" +#include "mem.h" + +// type (1) | key_length (1) | value_length (2) | key ($key_length) | value ($value_length) + +typedef struct { + const uint8_t *start; + size_t size; +} s_field_table; + +static s_field_table g_table; + +void field_table_init(void) { + explicit_bzero(&g_table, sizeof(g_table)); +} + +static const uint8_t *get_next_table_entry(const uint8_t *ptr, s_field_table_entry *entry) { + uint8_t key_len; + uint16_t value_len; + + if (ptr == NULL) { + return NULL; + } + if (entry != NULL) { + memcpy(&entry->type, ptr, sizeof(entry->type)); + } + ptr += sizeof(entry->type); + memcpy(&key_len, ptr, sizeof(key_len)); + ptr += sizeof(key_len); + memcpy(&value_len, ptr, sizeof(value_len)); + ptr += sizeof(value_len); + if (entry != NULL) { + entry->key = (char *) ptr; + } + ptr += key_len; + if (entry != NULL) { + entry->value = (char *) ptr; + } + ptr += value_len; + return ptr; +} + +// after this function, field_table_init() will have to be called before using any other field_table +// function +void field_table_cleanup(void) { + const uint8_t *ptr = g_table.start; + + if (ptr != NULL) { + for (size_t i = 0; i < g_table.size; ++i) { + ptr = get_next_table_entry(ptr, NULL); + } + mem_dealloc(ptr - g_table.start); + g_table.start = NULL; + } +} + +bool add_to_field_table(e_param_type type, const char *key, const char *value) { + int offset = 0; + uint8_t *ptr; + uint8_t key_len = strlen(key) + 1; + uint16_t value_len = strlen(value) + 1; + + if ((key == NULL) || (value == NULL)) { + PRINTF("Error: NULL key/value!\n"); + return false; + } + PRINTF(">>> \"%s\": \"%s\"\n", key, value); + if ((ptr = mem_alloc(sizeof(type) + sizeof(uint8_t) + sizeof(uint16_t) + key_len + + value_len)) == NULL) { + return false; + } + if ((g_table.start == NULL) && (g_table.size == 0)) { + g_table.start = ptr; + } + memcpy(&ptr[offset], &type, sizeof(type)); + offset += sizeof(type); + memcpy(&ptr[offset], &key_len, sizeof(key_len)); + offset += sizeof(key_len); + memcpy(&ptr[offset], &value_len, sizeof(value_len)); + offset += sizeof(value_len); + memcpy(&ptr[offset], key, key_len); + offset += key_len; + memcpy(&ptr[offset], value, value_len); + g_table.size += 1; + return true; +} + +size_t field_table_size(void) { + return g_table.size; +} + +bool get_from_field_table(int index, s_field_table_entry *entry) { + const uint8_t *ptr = g_table.start; + + if ((size_t) index >= g_table.size) { + return false; + } + if (ptr == NULL) { + return false; + } + for (int i = 0; i <= index; ++i) { + ptr = get_next_table_entry(ptr, entry); + } + return true; +} + +#endif // HAVE_GENERIC_TX_PARSER diff --git a/src_features/generic_tx_parser/gtp_field_table.h b/src_features/generic_tx_parser/gtp_field_table.h new file mode 100644 index 000000000..3b43890df --- /dev/null +++ b/src_features/generic_tx_parser/gtp_field_table.h @@ -0,0 +1,20 @@ +#ifndef GTP_FIELD_TABLE_H_ +#define GTP_FIELD_TABLE_H_ + +#include +#include +#include "gtp_field.h" + +typedef struct { + e_param_type type; + const char *key; + const char *value; +} s_field_table_entry; + +void field_table_init(void); +void field_table_cleanup(void); +bool add_to_field_table(e_param_type type, const char *key, const char *value); +size_t field_table_size(void); +bool get_from_field_table(int index, s_field_table_entry *entry); + +#endif // !GTP_FIELD_TABLE_H_ diff --git a/src_features/generic_tx_parser/gtp_param_amount.c b/src_features/generic_tx_parser/gtp_param_amount.c new file mode 100644 index 000000000..a7de6918f --- /dev/null +++ b/src_features/generic_tx_parser/gtp_param_amount.c @@ -0,0 +1,75 @@ +#ifdef HAVE_GENERIC_TX_PARSER + +#include "gtp_param_amount.h" +#include "network.h" +#include "common_utils.h" +#include "gtp_field_table.h" +#include "shared_context.h" + +enum { + TAG_VERSION = 0x00, + TAG_VALUE = 0x01, +}; + +static bool handle_version(const s_tlv_data *data, s_param_amount_context *context) { + if (data->length != sizeof(context->param->version)) { + return false; + } + context->param->version = data->value[0]; + return true; +} + +static bool handle_value(const s_tlv_data *data, s_param_amount_context *context) { + s_value_context ctx = {0}; + + ctx.value = &context->param->value; + explicit_bzero(ctx.value, sizeof(*ctx.value)); + return tlv_parse(data->value, data->length, (f_tlv_data_handler) &handle_value_struct, &ctx); +} + +bool handle_param_amount_struct(const s_tlv_data *data, s_param_amount_context *context) { + bool ret; + + switch (data->tag) { + case TAG_VERSION: + ret = handle_version(data, context); + break; + case TAG_VALUE: + ret = handle_value(data, context); + break; + default: + PRINTF(TLV_TAG_ERROR_MSG, data->tag); + ret = false; + } + return ret; +} + +bool format_param_amount(const s_param_amount *param, const char *name) { + uint64_t chain_id; + const char *ticker; + s_parsed_value_collection collec; + char *buf = strings.tmp.tmp; + size_t buf_size = sizeof(strings.tmp.tmp); + + if (!value_get(¶m->value, &collec)) { + return false; + } + chain_id = get_tx_chain_id(); + ticker = get_displayable_ticker(&chain_id, chainConfig); + for (int i = 0; i < collec.size; ++i) { + if (!amountToString(collec.value[i].ptr, + collec.value[i].length, + WEI_TO_ETHER, + ticker, + buf, + buf_size)) { + return false; + } + if (!add_to_field_table(PARAM_TYPE_AMOUNT, name, buf)) { + return false; + } + } + return true; +} + +#endif // HAVE_GENERIC_TX_PARSER diff --git a/src_features/generic_tx_parser/gtp_param_amount.h b/src_features/generic_tx_parser/gtp_param_amount.h new file mode 100644 index 000000000..e2c9eb81a --- /dev/null +++ b/src_features/generic_tx_parser/gtp_param_amount.h @@ -0,0 +1,21 @@ +#ifndef GTP_PARAM_AMOUNT_H_ +#define GTP_PARAM_AMOUNT_H_ + +#include +#include +#include "tlv.h" +#include "gtp_value.h" + +typedef struct { + uint8_t version; + s_value value; +} s_param_amount; + +typedef struct { + s_param_amount *param; +} s_param_amount_context; + +bool handle_param_amount_struct(const s_tlv_data *data, s_param_amount_context *context); +bool format_param_amount(const s_param_amount *param, const char *name); + +#endif // !GTP_PARAM_AMOUNT_H_ diff --git a/src_features/generic_tx_parser/gtp_param_datetime.c b/src_features/generic_tx_parser/gtp_param_datetime.c new file mode 100644 index 000000000..ca3fdade0 --- /dev/null +++ b/src_features/generic_tx_parser/gtp_param_datetime.c @@ -0,0 +1,104 @@ +#ifdef HAVE_GENERIC_TX_PARSER + +#include "gtp_param_datetime.h" +#include "gtp_field_table.h" +#include "read.h" +#include "time_format.h" +#include "utils.h" +#include "shared_context.h" + +enum { + TAG_VERSION = 0x00, + TAG_VALUE = 0x01, + TAG_TYPE = 0x02, +}; + +static bool handle_version(const s_tlv_data *data, s_param_datetime_context *context) { + if (data->length != sizeof(context->param->version)) { + return false; + } + context->param->version = data->value[0]; + return true; +} + +static bool handle_value(const s_tlv_data *data, s_param_datetime_context *context) { + s_value_context ctx = {0}; + + ctx.value = &context->param->value; + explicit_bzero(ctx.value, sizeof(*ctx.value)); + return tlv_parse(data->value, data->length, (f_tlv_data_handler) &handle_value_struct, &ctx); +} + +static bool handle_type(const s_tlv_data *data, s_param_datetime_context *context) { + if (data->length != sizeof(context->param->type)) { + return false; + } + switch (data->value[0]) { + case DT_UNIX: + case DT_BLOCKHEIGHT: + break; + default: + return false; + } + context->param->type = data->value[0]; + return true; +} + +bool handle_param_datetime_struct(const s_tlv_data *data, s_param_datetime_context *context) { + bool ret; + + switch (data->tag) { + case TAG_VERSION: + ret = handle_version(data, context); + break; + case TAG_VALUE: + ret = handle_value(data, context); + break; + case TAG_TYPE: + ret = handle_type(data, context); + break; + default: + PRINTF(TLV_TAG_ERROR_MSG, data->tag); + ret = false; + } + return ret; +} + +bool format_param_datetime(const s_param_datetime *param, const char *name) { + s_parsed_value_collection collec; + char *buf = strings.tmp.tmp; + size_t buf_size = sizeof(strings.tmp.tmp); + uint8_t time_buf[sizeof(uint32_t)]; + time_t timestamp; + uint256_t block_height; + + if (!value_get(¶m->value, &collec)) { + return false; + } + for (int i = 0; i < collec.size; ++i) { + switch (param->type) { + case DT_UNIX: + buf_shrink_expand(collec.value[i].ptr, + collec.value[i].length, + time_buf, + sizeof(time_buf)); + timestamp = read_u32_be(time_buf, 0); + if (!time_format_to_utc(×tamp, buf, buf_size)) { + return false; + } + break; + case DT_BLOCKHEIGHT: + convertUint256BE(collec.value[i].ptr, collec.value[i].length, &block_height); + if (!tostring256(&block_height, 10, buf, buf_size)) { + return false; + } + break; + } + if (!add_to_field_table(PARAM_TYPE_DATETIME, name, buf)) { + return false; + } + } + return true; +} + +#endif // HAVE_GENERIC_TX_PARSER diff --git a/src_features/generic_tx_parser/gtp_param_datetime.h b/src_features/generic_tx_parser/gtp_param_datetime.h new file mode 100644 index 000000000..0d3c955c2 --- /dev/null +++ b/src_features/generic_tx_parser/gtp_param_datetime.h @@ -0,0 +1,27 @@ +#ifndef GTP_PARAM_DATETIME_H_ +#define GTP_PARAM_DATETIME_H_ + +#include +#include +#include "tlv.h" +#include "gtp_value.h" + +typedef enum { + DT_UNIX = 0, + DT_BLOCKHEIGHT = 1, +} e_datetime_type; + +typedef struct { + uint8_t version; + s_value value; + e_datetime_type type; +} s_param_datetime; + +typedef struct { + s_param_datetime *param; +} s_param_datetime_context; + +bool handle_param_datetime_struct(const s_tlv_data *data, s_param_datetime_context *context); +bool format_param_datetime(const s_param_datetime *param, const char *name); + +#endif // !GTP_PARAM_DATETIME_H_ diff --git a/src_features/generic_tx_parser/gtp_param_duration.c b/src_features/generic_tx_parser/gtp_param_duration.c new file mode 100644 index 000000000..d55a94e58 --- /dev/null +++ b/src_features/generic_tx_parser/gtp_param_duration.c @@ -0,0 +1,107 @@ +#ifdef HAVE_GENERIC_TX_PARSER + +#include "gtp_param_duration.h" +#include "read.h" +#include "gtp_field_table.h" +#include "utils.h" +#include "shared_context.h" + +#define SECONDS_IN_MINUTE 60 +#define MINUTES_IN_HOUR 60 +#define SECONDS_IN_HOUR (SECONDS_IN_MINUTE * MINUTES_IN_HOUR) +#define HOURS_IN_DAY 24 +#define MINUTES_IN_DAY (HOURS_IN_DAY * MINUTES_IN_HOUR) +#define SECONDS_IN_DAY (MINUTES_IN_DAY * SECONDS_IN_MINUTE) + +enum { + TAG_VERSION = 0x00, + TAG_VALUE = 0x01, +}; + +static bool handle_version(const s_tlv_data *data, s_param_duration_context *context) { + if (data->length != sizeof(context->param->version)) { + return false; + } + context->param->version = data->value[0]; + return true; +} + +static bool handle_value(const s_tlv_data *data, s_param_duration_context *context) { + s_value_context ctx = {0}; + + ctx.value = &context->param->value; + explicit_bzero(ctx.value, sizeof(*ctx.value)); + return tlv_parse(data->value, data->length, (f_tlv_data_handler) &handle_value_struct, &ctx); +} + +bool handle_param_duration_struct(const s_tlv_data *data, s_param_duration_context *context) { + bool ret; + + switch (data->tag) { + case TAG_VERSION: + ret = handle_version(data, context); + break; + case TAG_VALUE: + ret = handle_value(data, context); + break; + default: + PRINTF(TLV_TAG_ERROR_MSG, data->tag); + ret = false; + } + return ret; +} + +bool format_param_duration(const s_param_duration *param, const char *name) { + s_parsed_value_collection collec; + char *buf = strings.tmp.tmp; + size_t buf_size = sizeof(strings.tmp.tmp); + uint16_t days; + uint8_t hours; + uint8_t minutes; + uint8_t seconds; + uint64_t remaining; + uint8_t raw_buf[sizeof(remaining)]; + int off; + + if (!value_get(¶m->value, &collec)) { + return false; + } + for (int i = 0; i < collec.size; ++i) { + off = 0; + buf_shrink_expand(collec.value[i].ptr, collec.value[i].length, raw_buf, sizeof(raw_buf)); + remaining = read_u64_be(raw_buf, 0); + + days = remaining / SECONDS_IN_DAY; + if (days > 0) { + snprintf(&buf[off], buf_size - off, "%dd", days); + off = strlen(buf); + } + remaining %= SECONDS_IN_DAY; + + hours = remaining / SECONDS_IN_HOUR; + if (hours > 0) { + snprintf(&buf[off], buf_size - off, "%02dh", hours); + off = strlen(buf); + } + remaining %= SECONDS_IN_HOUR; + + minutes = remaining / SECONDS_IN_MINUTE; + if (minutes > 0) { + snprintf(&buf[off], buf_size - off, "%02dm", minutes); + off = strlen(buf); + } + remaining %= SECONDS_IN_MINUTE; + + seconds = (uint8_t) remaining; + if ((seconds > 0) || (off == 0)) { + snprintf(&buf[off], buf_size - off, "%02ds", seconds); + } + + if (!add_to_field_table(PARAM_TYPE_DURATION, name, buf)) { + return false; + } + } + return true; +} + +#endif // HAVE_GENERIC_TX_PARSER diff --git a/src_features/generic_tx_parser/gtp_param_duration.h b/src_features/generic_tx_parser/gtp_param_duration.h new file mode 100644 index 000000000..c1fb74632 --- /dev/null +++ b/src_features/generic_tx_parser/gtp_param_duration.h @@ -0,0 +1,21 @@ +#ifndef GTP_PARAM_DURATION_H_ +#define GTP_PARAM_DURATION_H_ + +#include +#include +#include "tlv.h" +#include "gtp_value.h" + +typedef struct { + uint8_t version; + s_value value; +} s_param_duration; + +typedef struct { + s_param_duration *param; +} s_param_duration_context; + +bool handle_param_duration_struct(const s_tlv_data *data, s_param_duration_context *context); +bool format_param_duration(const s_param_duration *param, const char *name); + +#endif // !GTP_PARAM_DURATION_H_ diff --git a/src_features/generic_tx_parser/gtp_param_enum.c b/src_features/generic_tx_parser/gtp_param_enum.c new file mode 100644 index 000000000..15d90a2f4 --- /dev/null +++ b/src_features/generic_tx_parser/gtp_param_enum.c @@ -0,0 +1,91 @@ +#ifdef HAVE_GENERIC_TX_PARSER +#ifdef HAVE_ENUM_VALUE + +#include "gtp_param_enum.h" +#include "network.h" +#include "enum_value.h" +#include "gtp_field_table.h" +#include "calldata.h" +#include "shared_context.h" + +enum { + TAG_VERSION = 0x00, + TAG_ID = 0x01, + TAG_VALUE = 0x02, +}; + +static bool handle_version(const s_tlv_data *data, s_param_enum_context *context) { + if (data->length != sizeof(context->param->version)) { + return false; + } + context->param->version = data->value[0]; + return true; +} + +static bool handle_id(const s_tlv_data *data, s_param_enum_context *context) { + if (data->length != sizeof(context->param->id)) { + return false; + } + context->param->id = data->value[0]; + return true; +} + +static bool handle_value(const s_tlv_data *data, s_param_enum_context *context) { + s_value_context ctx = {0}; + + ctx.value = &context->param->value; + explicit_bzero(ctx.value, sizeof(*ctx.value)); + return tlv_parse(data->value, data->length, (f_tlv_data_handler) &handle_value_struct, &ctx); +} + +bool handle_param_enum_struct(const s_tlv_data *data, s_param_enum_context *context) { + bool ret; + + switch (data->tag) { + case TAG_VERSION: + ret = handle_version(data, context); + break; + case TAG_ID: + ret = handle_id(data, context); + break; + case TAG_VALUE: + ret = handle_value(data, context); + break; + default: + PRINTF(TLV_TAG_ERROR_MSG, data->tag); + ret = false; + } + return ret; +} + +bool format_param_enum(const s_param_enum *param, const char *name) { + uint64_t chain_id; + s_parsed_value_collection collec; + const char *enum_name; + uint8_t value; + const uint8_t *selector; + + if (!value_get(¶m->value, &collec)) { + return false; + } + chain_id = get_tx_chain_id(); + for (int i = 0; i < collec.size; ++i) { + if (collec.value[i].length == 0) return false; + value = collec.value[i].ptr[collec.value[i].length - 1]; + if ((selector = calldata_get_selector()) == NULL) { + return false; + } + enum_name = get_matching_enum_name(&chain_id, + txContext.content->destination, + selector, + param->id, + value); + if (!add_to_field_table(PARAM_TYPE_ENUM, name, enum_name)) { + return false; + } + } + return true; +} + +#endif // HAVE_ENUM_VALUE +#endif // HAVE_GENERIC_TX_PARSER diff --git a/src_features/generic_tx_parser/gtp_param_enum.h b/src_features/generic_tx_parser/gtp_param_enum.h new file mode 100644 index 000000000..2bfddaa45 --- /dev/null +++ b/src_features/generic_tx_parser/gtp_param_enum.h @@ -0,0 +1,26 @@ +#ifndef GTP_PARAM_ENUM_H_ +#define GTP_PARAM_ENUM_H_ + +#ifdef HAVE_ENUM_VALUE + +#include +#include +#include "tlv.h" +#include "gtp_value.h" + +typedef struct { + uint8_t version; + uint8_t id; + s_value value; +} s_param_enum; + +typedef struct { + s_param_enum *param; +} s_param_enum_context; + +bool handle_param_enum_struct(const s_tlv_data *data, s_param_enum_context *context); +bool format_param_enum(const s_param_enum *param, const char *name); + +#endif + +#endif // !GTP_PARAM_ENUM_H_ diff --git a/src_features/generic_tx_parser/gtp_param_nft.c b/src_features/generic_tx_parser/gtp_param_nft.c new file mode 100644 index 000000000..9a3d97b01 --- /dev/null +++ b/src_features/generic_tx_parser/gtp_param_nft.c @@ -0,0 +1,98 @@ +#ifdef HAVE_GENERIC_TX_PARSER +#ifdef HAVE_NFT_SUPPORT + +#include "gtp_param_nft.h" +#include "manage_asset_info.h" +#include "utils.h" +#include "gtp_field_table.h" + +enum { TAG_VERSION = 0x00, TAG_ID = 0x01, TAG_COLLECTION = 0x02 }; + +static bool handle_version(const s_tlv_data *data, s_param_nft_context *context) { + if (data->length != sizeof(context->param->version)) { + return false; + } + context->param->version = data->value[0]; + return true; +} + +static bool handle_id(const s_tlv_data *data, s_param_nft_context *context) { + s_value_context ctx = {0}; + + ctx.value = &context->param->id; + explicit_bzero(ctx.value, sizeof(*ctx.value)); + return tlv_parse(data->value, data->length, (f_tlv_data_handler) &handle_value_struct, &ctx); +} + +static bool handle_collection(const s_tlv_data *data, s_param_nft_context *context) { + s_value_context ctx = {0}; + + ctx.value = &context->param->collection; + explicit_bzero(ctx.value, sizeof(*ctx.value)); + return tlv_parse(data->value, data->length, (f_tlv_data_handler) &handle_value_struct, &ctx); +} + +bool handle_param_nft_struct(const s_tlv_data *data, s_param_nft_context *context) { + bool ret; + + switch (data->tag) { + case TAG_VERSION: + ret = handle_version(data, context); + break; + case TAG_ID: + ret = handle_id(data, context); + break; + case TAG_COLLECTION: + ret = handle_collection(data, context); + break; + default: + PRINTF(TLV_TAG_ERROR_MSG, data->tag); + ret = false; + } + return ret; +} + +bool format_param_nft(const s_param_nft *param, const char *name) { + s_parsed_value_collection collections; + s_parsed_value_collection ids; + const extraInfo_t *asset; + char *buf = strings.tmp.tmp; + size_t buf_size = sizeof(strings.tmp.tmp); + uint8_t collection_idx; + uint8_t addr_buf[ADDRESS_LENGTH]; + char tmp[80]; + + if (!value_get(¶m->collection, &collections)) { + return false; + } + if (!value_get(¶m->id, &ids)) { + return false; + } + if (collections.size == 0) { + return false; + } + if ((collections.size != 1) && (collections.size != ids.size)) { + return false; + } + for (int i = 0; i < ids.size; ++i) { + collection_idx = (i >= collections.size) ? 0 : i; + buf_shrink_expand(collections.value[collection_idx].ptr, + collections.value[collection_idx].length, + addr_buf, + sizeof(addr_buf)); + if ((asset = (const extraInfo_t *) get_asset_info_by_addr(addr_buf)) == NULL) { + return false; + } + if (!uint256_to_decimal(ids.value[i].ptr, ids.value[i].length, tmp, sizeof(tmp))) { + return false; + } + snprintf(buf, buf_size, "%s #%s", asset->nft.collectionName, tmp); + if (!add_to_field_table(PARAM_TYPE_NFT, name, buf)) { + return false; + } + } + return true; +} + +#endif // HAVE_NFT_SUPPORT +#endif // HAVE_GENERIC_TX_PARSER diff --git a/src_features/generic_tx_parser/gtp_param_nft.h b/src_features/generic_tx_parser/gtp_param_nft.h new file mode 100644 index 000000000..f7fcfc1ab --- /dev/null +++ b/src_features/generic_tx_parser/gtp_param_nft.h @@ -0,0 +1,26 @@ +#ifndef GTP_PARAM_NFT_H_ +#define GTP_PARAM_NFT_H_ + +#ifdef HAVE_NFT_SUPPORT + +#include +#include +#include "tlv.h" +#include "gtp_value.h" + +typedef struct { + uint8_t version; + s_value id; + s_value collection; +} s_param_nft; + +typedef struct { + s_param_nft *param; +} s_param_nft_context; + +bool handle_param_nft_struct(const s_tlv_data *data, s_param_nft_context *context); +bool format_param_nft(const s_param_nft *param, const char *name); + +#endif + +#endif // !GTP_PARAM_NFT_H_ diff --git a/src_features/generic_tx_parser/gtp_param_raw.c b/src_features/generic_tx_parser/gtp_param_raw.c new file mode 100644 index 000000000..3003835da --- /dev/null +++ b/src_features/generic_tx_parser/gtp_param_raw.c @@ -0,0 +1,188 @@ +#ifdef HAVE_GENERIC_TX_PARSER + +#include +#include "os_print.h" +#include "gtp_param_raw.h" +#include "uint256.h" +#include "read.h" +#include "gtp_field_table.h" +#include "utils.h" +#include "shared_context.h" + +enum { + TAG_VERSION = 0x00, + TAG_VALUE = 0x01, +}; + +static bool handle_version(const s_tlv_data *data, s_param_raw_context *context) { + if (data->length != sizeof(context->param->version)) { + return false; + } + context->param->version = data->value[0]; + return true; +} + +static bool handle_value(const s_tlv_data *data, s_param_raw_context *context) { + s_value_context ctx = {0}; + + ctx.value = &context->param->value; + explicit_bzero(ctx.value, sizeof(*ctx.value)); + return tlv_parse(data->value, data->length, (f_tlv_data_handler) &handle_value_struct, &ctx); +} + +bool handle_param_raw_struct(const s_tlv_data *data, s_param_raw_context *context) { + bool ret; + + switch (data->tag) { + case TAG_VERSION: + ret = handle_version(data, context); + break; + case TAG_VALUE: + ret = handle_value(data, context); + break; + default: + PRINTF(TLV_TAG_ERROR_MSG, data->tag); + ret = false; + } + return ret; +} + +bool format_uint(const s_value *def, const s_parsed_value *value, char *buf, size_t buf_size) { + uint256_t value256; + + (void) def; + convertUint256BE(value->ptr, value->length, &value256); + return tostring256(&value256, 10, buf, buf_size); +} + +bool format_int(const s_value *def, const s_parsed_value *value, char *buf, size_t buf_size) { + uint8_t tmp[32]; + bool ret; + union { + uint256_t value256; + uint128_t value128; + int64_t value64; + int32_t value32; + int16_t value16; + int8_t value8; + } uv; + + buf_shrink_expand(value->ptr, value->length, tmp, def->type_size); + switch (def->type_size * 8) { + case 256: + convertUint256BE(tmp, def->type_size, &uv.value256); + ret = tostring256_signed(&uv.value256, 10, buf, buf_size); + break; + case 128: + convertUint128BE(tmp, def->type_size, &uv.value128); + ret = tostring128_signed(&uv.value128, 10, buf, buf_size); + break; + case 64: + uv.value64 = read_u64_be(tmp, 0); + ret = snprintf(buf, buf_size, "%" PRId64, uv.value64) > 0; + break; + case 32: + uv.value32 = read_u32_be(tmp, 0); + ret = snprintf(buf, buf_size, "%" PRId32, uv.value32) > 0; + break; + case 16: + uv.value16 = read_u16_be(tmp, 0); + ret = snprintf(buf, buf_size, "%u" PRId16, uv.value16) > 0; + break; + case 8: + uv.value8 = value->ptr[0]; + ret = snprintf(buf, buf_size, "%u" PRId8, uv.value8) > 0; + break; + default: + ret = false; + } + return ret; +} + +static bool format_addr(const s_value *def, + const s_parsed_value *value, + char *buf, + size_t buf_size) { + uint8_t tmp[ADDRESS_LENGTH]; + + (void) def; + buf_shrink_expand(value->ptr, value->length, tmp, sizeof(tmp)); + return getEthDisplayableAddress(tmp, buf, buf_size, chainConfig->chainId); +} + +static bool format_bool(const s_value *def, + const s_parsed_value *value, + char *buf, + size_t buf_size) { + uint8_t tmp; + + (void) def; + buf_shrink_expand(value->ptr, value->length, &tmp, 1); + snprintf(buf, buf_size, "%s", tmp ? "true" : "false"); + return true; +} + +static bool format_bytes(const s_value *def, + const s_parsed_value *value, + char *buf, + size_t buf_size) { + (void) def; +#pragma GCC diagnostic ignored "-Wformat" + snprintf(buf, buf_size, "0x%.*h", value->length, value->ptr); +#pragma GCC diagnostic warning "-Wformat" + if ((2 + (value->length * 2) + 1) > buf_size) { + memmove(&buf[buf_size - 1 - 3], "...", 3); + } + return true; +} + +static bool format_string(const s_value *def, + const s_parsed_value *value, + char *buf, + size_t buf_size) { + (void) def; + str_cpy_explicit_trunc((char *) value->ptr, value->length, buf, buf_size); + return true; +} + +bool format_param_raw(const s_param_raw *param, const char *name) { + bool ret = false; + s_parsed_value_collection collec; + char *buf = strings.tmp.tmp; + size_t buf_size = sizeof(strings.tmp.tmp); + + if (!value_get(¶m->value, &collec)) { + return false; + } + for (int i = 0; i < collec.size; ++i) { + switch (param->value.type_family) { + case TF_UINT: + ret = format_uint(¶m->value, &collec.value[i], buf, buf_size); + break; + case TF_INT: + ret = format_int(¶m->value, &collec.value[i], buf, buf_size); + break; + case TF_ADDRESS: + ret = format_addr(¶m->value, &collec.value[i], buf, buf_size); + break; + case TF_BOOL: + ret = format_bool(¶m->value, &collec.value[i], buf, buf_size); + break; + case TF_BYTES: + ret = format_bytes(¶m->value, &collec.value[i], buf, buf_size); + break; + case TF_STRING: + ret = format_string(¶m->value, &collec.value[i], buf, buf_size); + break; + case TF_UFIXED: + case TF_FIXED: + default: + ret = false; + } + if (ret) ret = add_to_field_table(PARAM_TYPE_RAW, name, buf); + if (!ret) break; + } + return ret; +} + +#endif // HAVE_GENERIC_TX_PARSER diff --git a/src_features/generic_tx_parser/gtp_param_raw.h b/src_features/generic_tx_parser/gtp_param_raw.h new file mode 100644 index 000000000..9f73c5ade --- /dev/null +++ b/src_features/generic_tx_parser/gtp_param_raw.h @@ -0,0 +1,21 @@ +#ifndef GTP_PARAM_RAW_H_ +#define GTP_PARAM_RAW_H_ + +#include +#include +#include "tlv.h" +#include "gtp_value.h" + +typedef struct { + uint8_t version; + s_value value; +} s_param_raw; + +typedef struct { + s_param_raw *param; +} s_param_raw_context; + +bool handle_param_raw_struct(const s_tlv_data *data, s_param_raw_context *context); +bool format_param_raw(const s_param_raw *param, const char *name); + +#endif // !GTP_PARAM_RAW_H_ diff --git a/src_features/generic_tx_parser/gtp_param_token_amount.c b/src_features/generic_tx_parser/gtp_param_token_amount.c new file mode 100644 index 000000000..b68df1b31 --- /dev/null +++ b/src_features/generic_tx_parser/gtp_param_token_amount.c @@ -0,0 +1,185 @@ +#ifdef HAVE_GENERIC_TX_PARSER + +#include +#include "gtp_param_token_amount.h" +#include "network.h" +#include "utils.h" +#include "gtp_field_table.h" +#include "manage_asset_info.h" + +enum { + TAG_VERSION = 0x00, + TAG_VALUE = 0x01, + TAG_TOKEN = 0x02, + TAG_NATIVE_CURRENCY = 0x03, + TAG_THRESHOLD = 0x04, + TAG_ABOVE_THRESHOLD_MSG = 0x05, +}; + +static bool handle_version(const s_tlv_data *data, s_param_token_amount_context *context) { + if (data->length != sizeof(context->param->version)) { + return false; + } + context->param->version = data->value[0]; + return true; +} + +static bool handle_value(const s_tlv_data *data, s_param_token_amount_context *context) { + s_value_context ctx = {0}; + + ctx.value = &context->param->value; + explicit_bzero(ctx.value, sizeof(*ctx.value)); + return tlv_parse(data->value, data->length, (f_tlv_data_handler) &handle_value_struct, &ctx); +} + +static bool handle_token(const s_tlv_data *data, s_param_token_amount_context *context) { + s_value_context ctx = {0}; + + ctx.value = &context->param->token; + explicit_bzero(ctx.value, sizeof(*ctx.value)); + context->param->has_token = true; + return tlv_parse(data->value, data->length, (f_tlv_data_handler) &handle_value_struct, &ctx); +} + +static bool handle_native_currency(const s_tlv_data *data, s_param_token_amount_context *context) { + if (data->length > ADDRESS_LENGTH) { + return false; + } + if (context->param->native_addr_count == MAX_NATIVE_ADDRS) { + return false; + } + memcpy(&context->param + ->native_addrs[context->param->native_addr_count][ADDRESS_LENGTH - data->length], + data->value, + data->length); + context->param->native_addr_count += 1; + return true; +} + +static bool handle_threshold(const s_tlv_data *data, s_param_token_amount_context *context) { + if (data->length > sizeof(uint256_t)) { + return false; + } + convertUint256BE(data->value, data->length, &context->param->threshold); + return true; +} + +static bool handle_above_threshold_msg(const s_tlv_data *data, + s_param_token_amount_context *context) { + if (data->length >= sizeof(context->param->above_threshold_msg)) { + return false; + } + memcpy(context->param->above_threshold_msg, data->value, data->length); + context->param->above_threshold_msg[data->length] = '\0'; + return true; +} + +bool handle_param_token_amount_struct(const s_tlv_data *data, + s_param_token_amount_context *context) { + bool ret; + + switch (data->tag) { + case TAG_VERSION: + ret = handle_version(data, context); + break; + case TAG_VALUE: + ret = handle_value(data, context); + break; + case TAG_TOKEN: + ret = handle_token(data, context); + break; + case TAG_NATIVE_CURRENCY: + ret = handle_native_currency(data, context); + break; + case TAG_THRESHOLD: + ret = handle_threshold(data, context); + break; + case TAG_ABOVE_THRESHOLD_MSG: + ret = handle_above_threshold_msg(data, context); + break; + default: + PRINTF(TLV_TAG_ERROR_MSG, data->tag); + ret = false; + } + return ret; +} + +static bool match_native(const uint8_t *addr, const s_param_token_amount *param) { + for (int i = 0; i < param->native_addr_count; ++i) { + if (memcmp(addr, param->native_addrs[i], ADDRESS_LENGTH) == 0) { + return true; + } + } + return false; +} + +bool format_param_token_amount(const s_param_token_amount *param, const char *name) { + s_parsed_value_collection collec_value; + s_parsed_value_collection collec_token; + char *buf = strings.tmp.tmp; + size_t buf_size = sizeof(strings.tmp.tmp); + uint8_t addr_buf[ADDRESS_LENGTH]; + + if (!value_get(¶m->value, &collec_value)) { + return false; + } + if (param->has_token) { + if (!value_get(¶m->token, &collec_token)) { + return false; + } + if (collec_value.size != collec_token.size) { + PRINTF("Error: mismatch between counts of value & token!\n"); + return false; + } + } else { + explicit_bzero(&collec_token, sizeof(collec_token)); + } + uint64_t chain_id = get_tx_chain_id(); + const char *ticker = g_unknown_ticker; + uint8_t decimals = 0; + for (int i = 0; i < collec_value.size; ++i) { + if (param->has_token) { + buf_shrink_expand(collec_token.value[i].ptr, + collec_token.value[i].length, + addr_buf, + sizeof(addr_buf)); + if (match_native(addr_buf, param)) { + ticker = get_displayable_ticker(&chain_id, chainConfig); + decimals = WEI_TO_ETHER; + } else { + const tokenDefinition_t *token; + if ((token = (const tokenDefinition_t *) get_asset_info_by_addr(addr_buf)) != + NULL) { + ticker = token->ticker; + decimals = token->decimals; + } + } + } + uint256_t zero256 = {0}; + uint256_t val256; + + convertUint256BE(collec_value.value[i].ptr, collec_value.value[i].length, &val256); + if (!equal256(¶m->threshold, &zero256) && gte256(&val256, ¶m->threshold)) { + if (param->above_threshold_msg[0] != '\0') { + snprintf(buf, buf_size, "%s %s", param->above_threshold_msg, ticker); + } else { + snprintf(buf, buf_size, "Unlimited %s", ticker); + } + } else { + if (!amountToString(collec_value.value[i].ptr, + collec_value.value[i].length, + decimals, + ticker, + buf, + buf_size)) { + return false; + } + } + if (!add_to_field_table(PARAM_TYPE_AMOUNT, name, buf)) { + return false; + } + } + return true; +} + +#endif // HAVE_GENERIC_TX_PARSER diff --git a/src_features/generic_tx_parser/gtp_param_token_amount.h b/src_features/generic_tx_parser/gtp_param_token_amount.h new file mode 100644 index 000000000..fb29a8391 --- /dev/null +++ b/src_features/generic_tx_parser/gtp_param_token_amount.h @@ -0,0 +1,32 @@ +#ifndef GTP_PARAM_TOKEN_AMOUNT_H_ +#define GTP_PARAM_TOKEN_AMOUNT_H_ + +#include +#include +#include "uint256.h" +#include "tlv.h" +#include "gtp_value.h" +#include "common_utils.h" + +#define MAX_NATIVE_ADDRS 4 + +typedef struct { + uint8_t version; + s_value value; + bool has_token; + s_value token; + uint8_t native_addr_count; + uint8_t native_addrs[MAX_NATIVE_ADDRS][ADDRESS_LENGTH]; + uint256_t threshold; + char above_threshold_msg[21]; +} s_param_token_amount; + +typedef struct { + s_param_token_amount *param; +} s_param_token_amount_context; + +bool handle_param_token_amount_struct(const s_tlv_data *data, + s_param_token_amount_context *context); +bool format_param_token_amount(const s_param_token_amount *param, const char *name); + +#endif // !GTP_PARAM_TOKEN_AMOUNT_H_ diff --git a/src_features/generic_tx_parser/gtp_param_trusted_name.c b/src_features/generic_tx_parser/gtp_param_trusted_name.c new file mode 100644 index 000000000..5fffb9fe2 --- /dev/null +++ b/src_features/generic_tx_parser/gtp_param_trusted_name.c @@ -0,0 +1,111 @@ +#ifdef HAVE_GENERIC_TX_PARSER +#ifdef HAVE_TRUSTED_NAME + +#include "gtp_param_trusted_name.h" +#include "network.h" +#include "trusted_name.h" +#include "gtp_field_table.h" +#include "utils.h" +#include "shared_context.h" + +enum { + TAG_VERSION = 0x00, + TAG_VALUE = 0x01, + TAG_TYPES = 0x02, + TAG_SOURCES = 0x03, +}; + +static bool handle_version(const s_tlv_data *data, s_param_trusted_name_context *context) { + if (data->length != sizeof(context->param->version)) { + return false; + } + context->param->version = data->value[0]; + return true; +} + +static bool handle_value(const s_tlv_data *data, s_param_trusted_name_context *context) { + s_value_context ctx = {0}; + + ctx.value = &context->param->value; + explicit_bzero(ctx.value, sizeof(*ctx.value)); + return tlv_parse(data->value, data->length, (f_tlv_data_handler) &handle_value_struct, &ctx); +} + +static bool handle_types(const s_tlv_data *data, s_param_trusted_name_context *context) { + if (data->length > sizeof(context->param->types)) { + return false; + } + memcpy(context->param->types, data->value, data->length); + context->param->type_count = data->length; + return true; +} + +static bool handle_sources(const s_tlv_data *data, s_param_trusted_name_context *context) { + if (data->length > sizeof(context->param->sources)) { + return false; + } + memcpy(context->param->sources, data->value, data->length); + context->param->source_count = data->length; + return true; +} + +bool handle_param_trusted_name_struct(const s_tlv_data *data, + s_param_trusted_name_context *context) { + bool ret; + + switch (data->tag) { + case TAG_VERSION: + ret = handle_version(data, context); + break; + case TAG_VALUE: + ret = handle_value(data, context); + break; + case TAG_TYPES: + ret = handle_types(data, context); + break; + case TAG_SOURCES: + ret = handle_sources(data, context); + break; + default: + PRINTF(TLV_TAG_ERROR_MSG, data->tag); + ret = false; + } + return ret; +} + +bool format_param_trusted_name(const s_param_trusted_name *param, const char *name) { + s_parsed_value_collection values; + char *buf = strings.tmp.tmp; + size_t buf_size = sizeof(strings.tmp.tmp); + uint64_t chain_id; + uint8_t addr[ADDRESS_LENGTH]; + const char *tname; + e_param_type param_type; + + if (!value_get(¶m->value, &values)) { + return false; + } + chain_id = get_tx_chain_id(); + for (int i = 0; i < values.size; ++i) { + buf_shrink_expand(values.value[i].ptr, values.value[i].length, addr, sizeof(addr)); + if ((tname = get_trusted_name(param->type_count, + param->types, + param->source_count, + param->sources, + &chain_id, + addr)) != NULL) { + strlcpy(buf, tname, buf_size); + param_type = PARAM_TYPE_TRUSTED_NAME; + } else { + getEthDisplayableAddress(addr, buf, buf_size, chainConfig->chainId); + param_type = PARAM_TYPE_RAW; + } + if (!add_to_field_table(param_type, name, buf)) { + return false; + } + } + return true; +} + +#endif // HAVE_TRUSTED_NAME +#endif // HAVE_GENERIC_TX_PARSER diff --git a/src_features/generic_tx_parser/gtp_param_trusted_name.h b/src_features/generic_tx_parser/gtp_param_trusted_name.h new file mode 100644 index 000000000..3232d7ad5 --- /dev/null +++ b/src_features/generic_tx_parser/gtp_param_trusted_name.h @@ -0,0 +1,31 @@ +#ifndef GTP_PARAM_TRUSTED_NAME_H_ +#define GTP_PARAM_TRUSTED_NAME_H_ + +#ifdef HAVE_TRUSTED_NAME + +#include +#include +#include "tlv.h" +#include "gtp_value.h" +#include "trusted_name.h" + +typedef struct { + uint8_t version; + s_value value; + uint8_t type_count; + e_name_type types[TN_TYPE_COUNT]; + uint8_t source_count; + e_name_source sources[TN_SOURCE_COUNT]; +} s_param_trusted_name; + +typedef struct { + s_param_trusted_name *param; +} s_param_trusted_name_context; + +bool handle_param_trusted_name_struct(const s_tlv_data *data, + s_param_trusted_name_context *context); +bool format_param_trusted_name(const s_param_trusted_name *param, const char *name); + +#endif + +#endif // !GTP_PARAM_TRUSTED_NAME_H_ diff --git a/src_features/generic_tx_parser/gtp_param_unit.c b/src_features/generic_tx_parser/gtp_param_unit.c new file mode 100644 index 000000000..af00f84cb --- /dev/null +++ b/src_features/generic_tx_parser/gtp_param_unit.c @@ -0,0 +1,113 @@ +#ifdef HAVE_GENERIC_TX_PARSER + +#include "gtp_param_unit.h" +#include "gtp_field_table.h" +#include "shared_context.h" + +enum { + TAG_VERSION = 0x00, + TAG_VALUE = 0x01, + TAG_BASE = 0x02, + TAG_DECIMALS = 0x03, + TAG_PREFIX = 0x04, +}; + +static bool handle_version(const s_tlv_data *data, s_param_unit_context *context) { + if (data->length != sizeof(context->param->version)) { + return false; + } + context->param->version = data->value[0]; + return true; +} + +static bool handle_value(const s_tlv_data *data, s_param_unit_context *context) { + s_value_context ctx = {0}; + + ctx.value = &context->param->value; + explicit_bzero(ctx.value, sizeof(*ctx.value)); + return tlv_parse(data->value, data->length, (f_tlv_data_handler) &handle_value_struct, &ctx); +} + +static bool handle_base(const s_tlv_data *data, s_param_unit_context *context) { + if (data->length >= sizeof(context->param->base)) { + return false; + } + memcpy(context->param->base, data->value, data->length); + context->param->base[data->length] = '\0'; + return true; +} + +static bool handle_decimals(const s_tlv_data *data, s_param_unit_context *context) { + if (data->length != sizeof(context->param->decimals)) { + return false; + } + context->param->decimals = data->value[0]; + return true; +} + +static bool handle_prefix(const s_tlv_data *data, s_param_unit_context *context) { + if (data->length != sizeof(bool)) { + return false; + } + (void) context; + // unused for now + return true; +} + +bool handle_param_unit_struct(const s_tlv_data *data, s_param_unit_context *context) { + bool ret; + + switch (data->tag) { + case TAG_VERSION: + ret = handle_version(data, context); + break; + case TAG_VALUE: + ret = handle_value(data, context); + break; + case TAG_BASE: + ret = handle_base(data, context); + break; + case TAG_DECIMALS: + ret = handle_decimals(data, context); + break; + case TAG_PREFIX: + ret = handle_prefix(data, context); + break; + default: + PRINTF(TLV_TAG_ERROR_MSG, data->tag); + ret = false; + } + return ret; +} + +bool format_param_unit(const s_param_unit *param, const char *name) { + s_parsed_value_collection collec; + char *buf = strings.tmp.tmp; + size_t buf_size = sizeof(strings.tmp.tmp); + char tmp[80]; + size_t off; + + if (!value_get(¶m->value, &collec)) { + return false; + } + for (int i = 0; i < collec.size; ++i) { + if (!uint256_to_decimal(collec.value[i].ptr, collec.value[i].length, tmp, sizeof(tmp))) { + return false; + } + if (!adjustDecimals(tmp, strnlen(tmp, sizeof(tmp)), buf, buf_size, param->decimals)) { + return false; + } + if (param->base[0] == '\0') { + return false; + } + off = strlen(buf); + snprintf(&buf[off], buf_size - off, " %s", param->base); + + if (!add_to_field_table(PARAM_TYPE_UNIT, name, buf)) { + return false; + } + } + return true; +} + +#endif // HAVE_GENERIC_TX_PARSER diff --git a/src_features/generic_tx_parser/gtp_param_unit.h b/src_features/generic_tx_parser/gtp_param_unit.h new file mode 100644 index 000000000..9930ce312 --- /dev/null +++ b/src_features/generic_tx_parser/gtp_param_unit.h @@ -0,0 +1,23 @@ +#ifndef GTP_PARAM_UNIT_H_ +#define GTP_PARAM_UNIT_H_ + +#include +#include +#include "tlv.h" +#include "gtp_value.h" + +typedef struct { + uint8_t version; + s_value value; + char base[11]; + uint8_t decimals; +} s_param_unit; + +typedef struct { + s_param_unit *param; +} s_param_unit_context; + +bool handle_param_unit_struct(const s_tlv_data *data, s_param_unit_context *context); +bool format_param_unit(const s_param_unit *param, const char *name); + +#endif // !GTP_PARAM_UNIT_H_ diff --git a/src_features/generic_tx_parser/gtp_parsed_value.h b/src_features/generic_tx_parser/gtp_parsed_value.h new file mode 100644 index 000000000..2f7b4bcfc --- /dev/null +++ b/src_features/generic_tx_parser/gtp_parsed_value.h @@ -0,0 +1,18 @@ +#ifndef GTP_PARSED_VALUE_H_ +#define GTP_PARSED_VALUE_H_ + +#include + +typedef struct { + uint16_t length; + const uint8_t *ptr; +} s_parsed_value; + +#define MAX_VALUE_COLLECTION_SIZE 16 + +typedef struct { + uint8_t size; + s_parsed_value value[MAX_VALUE_COLLECTION_SIZE]; +} s_parsed_value_collection; + +#endif // !GTP_PARSED_VALUE_H_ diff --git a/src_features/generic_tx_parser/gtp_path_array.c b/src_features/generic_tx_parser/gtp_path_array.c new file mode 100644 index 000000000..84ef3af5c --- /dev/null +++ b/src_features/generic_tx_parser/gtp_path_array.c @@ -0,0 +1,62 @@ +#ifdef HAVE_GENERIC_TX_PARSER + +#include +#include +#include "gtp_path_array.h" +#include "read.h" +#include "os_print.h" +#include "utils.h" + +enum { + TAG_WEIGHT = 0x01, + TAG_START = 0x02, + TAG_END = 0x03, +}; + +static bool handle_weight(const s_tlv_data *data, s_path_array_context *context) { + if (data->length != sizeof(context->args->weight)) { + return false; + } + context->args->weight = data->value[0]; + return true; +} + +static bool handle_start(const s_tlv_data *data, s_path_array_context *context) { + if (data->length != sizeof(context->args->start)) { + return false; + } + context->args->start = read_u16_be(data->value, 0); + context->args->has_start = true; + return true; +} + +static bool handle_end(const s_tlv_data *data, s_path_array_context *context) { + if (data->length != sizeof(context->args->end)) { + return false; + } + context->args->end = read_u16_be(data->value, 0); + context->args->has_end = true; + return true; +} + +bool handle_array_struct(const s_tlv_data *data, s_path_array_context *context) { + bool ret; + + switch (data->tag) { + case TAG_WEIGHT: + ret = handle_weight(data, context); + break; + case TAG_START: + ret = handle_start(data, context); + break; + case TAG_END: + ret = handle_end(data, context); + break; + default: + PRINTF(TLV_TAG_ERROR_MSG, data->tag); + ret = false; + } + return ret; +} + +#endif // HAVE_GENERIC_TX_PARSER diff --git a/src_features/generic_tx_parser/gtp_path_array.h b/src_features/generic_tx_parser/gtp_path_array.h new file mode 100644 index 000000000..acbfe2af2 --- /dev/null +++ b/src_features/generic_tx_parser/gtp_path_array.h @@ -0,0 +1,22 @@ +#ifndef GTP_PATH_ARRAY_H_ +#define GTP_PATH_ARRAY_H_ + +#include +#include +#include "tlv.h" + +typedef struct { + uint8_t weight; + bool has_start; + int16_t start; + bool has_end; + int16_t end; +} s_array_args; + +typedef struct { + s_array_args *args; +} s_path_array_context; + +bool handle_array_struct(const s_tlv_data *data, s_path_array_context *context); + +#endif // !GTP_PATH_ARRAY_H_ diff --git a/src_features/generic_tx_parser/gtp_path_slice.c b/src_features/generic_tx_parser/gtp_path_slice.c new file mode 100644 index 000000000..ee5267ae0 --- /dev/null +++ b/src_features/generic_tx_parser/gtp_path_slice.c @@ -0,0 +1,47 @@ +#ifdef HAVE_GENERIC_TX_PARSER + +#include "gtp_path_slice.h" +#include "os_print.h" +#include "read.h" + +enum { + TAG_START = 0x01, + TAG_END = 0x02, +}; + +static bool handle_start(const s_tlv_data *data, s_path_slice_context *context) { + if (data->length != sizeof(context->args->start)) { + return false; + } + context->args->start = read_u16_be(data->value, 0); + context->args->has_start = true; + return true; +} + +static bool handle_end(const s_tlv_data *data, s_path_slice_context *context) { + if (data->length != sizeof(context->args->end)) { + return false; + } + context->args->end = read_u16_be(data->value, 0); + context->args->has_end = true; + return true; +} + +bool handle_slice_struct(const s_tlv_data *data, s_path_slice_context *context) { + bool ret; + + switch (data->tag) { + case TAG_START: + ret = handle_start(data, context); + break; + case TAG_END: + ret = handle_end(data, context); + break; + default: + PRINTF(TLV_TAG_ERROR_MSG, data->tag); + ret = false; + } + return ret; +} + +#endif // HAVE_GENERIC_TX_PARSER diff --git a/src_features/generic_tx_parser/gtp_path_slice.h b/src_features/generic_tx_parser/gtp_path_slice.h new file mode 100644 index 000000000..313c6aade --- /dev/null +++ b/src_features/generic_tx_parser/gtp_path_slice.h @@ -0,0 +1,21 @@ +#ifndef GTP_PATH_SLICE_H_ +#define GTP_PATH_SLICE_H_ + +#include +#include +#include "tlv.h" + +typedef struct { + bool has_start; + int16_t start; + bool has_end; + int16_t end; +} s_slice_args; + +typedef struct { + s_slice_args *args; +} s_path_slice_context; + +bool handle_slice_struct(const s_tlv_data *data, s_path_slice_context *context); + +#endif // !GTP_PATH_SLICE_H_ diff --git a/src_features/generic_tx_parser/gtp_tx_info.c b/src_features/generic_tx_parser/gtp_tx_info.c new file mode 100644 index 000000000..7913ab08d --- /dev/null +++ b/src_features/generic_tx_parser/gtp_tx_info.c @@ -0,0 +1,359 @@ +#ifdef HAVE_GENERIC_TX_PARSER + +#include +#include "gtp_tx_info.h" +#include "read.h" +#include "hash_bytes.h" +#include "network.h" // get_tx_chain_id +#include "shared_context.h" // txContext +#include "utils.h" +#include "time_format.h" +#include "calldata.h" +#include "public_keys.h" // LEDGER_SIGNATURE_PUBLIC_KEY + +enum { + BIT_VERSION = 0, + BIT_CHAIN_ID, + BIT_CONTRACT_ADDR, + BIT_SELECTOR, + BIT_FIELDS_HASH, + BIT_OPERATION_TYPE, + BIT_CREATOR_NAME, + BIT_CREATOR_LEGAL_NAME, + BIT_CREATOR_URL, + BIT_CONTRACT_NAME, + BIT_DEPLOY_DATE, + BIT_SIGNATURE, +}; + +enum { + TAG_VERSION = 0x00, + TAG_CHAIN_ID = 0x01, + TAG_CONTRACT_ADDR = 0x02, + TAG_SELECTOR = 0x03, + TAG_FIELDS_HASH = 0x04, + TAG_OPERATION_TYPE = 0x05, + TAG_CREATOR_NAME = 0x06, + TAG_CREATOR_LEGAL_NAME = 0x07, + TAG_CREATOR_URL = 0x08, + TAG_CONTRACT_NAME = 0x09, + TAG_DEPLOY_DATE = 0x0a, + TAG_SIGNATURE = 0xff, +}; + +s_tx_info *g_tx_info = NULL; +cx_sha3_t hash_ctx; + +static bool handle_version(const s_tlv_data *data, s_tx_info_ctx *context) { + if (data->length != sizeof(context->tx_info->version)) { + return false; + } + context->tx_info->version = data->value[0]; + context->set_flags |= SET_BIT(BIT_VERSION); + return true; +} + +static bool handle_chain_id(const s_tlv_data *data, s_tx_info_ctx *context) { + uint64_t chain_id; + uint8_t buf[sizeof(chain_id)]; + + if (data->length > sizeof(buf)) { + return false; + } + buf_shrink_expand(data->value, data->length, buf, sizeof(buf)); + chain_id = read_u64_be(buf, 0); + if (chain_id != get_tx_chain_id()) { + PRINTF("Error: chain ID mismatch!\n"); + return false; + } + context->tx_info->chain_id = chain_id; + context->set_flags |= SET_BIT(BIT_CHAIN_ID); + return true; +} + +static bool handle_contract_addr(const s_tlv_data *data, s_tx_info_ctx *context) { + uint8_t buf[ADDRESS_LENGTH]; + + if (data->length > sizeof(buf)) { + return false; + } + buf_shrink_expand(data->value, data->length, buf, sizeof(buf)); + if (memcmp(buf, txContext.content->destination, sizeof(buf)) != 0) { + PRINTF("Error: contract address mismatch!\n"); + return false; + } + memcpy(context->tx_info->contract_addr, buf, sizeof(buf)); + context->set_flags |= SET_BIT(BIT_CONTRACT_ADDR); + return true; +} + +static bool handle_selector(const s_tlv_data *data, s_tx_info_ctx *context) { + uint8_t buf[CALLDATA_SELECTOR_SIZE]; + const uint8_t *selector; + + if (data->length > sizeof(buf)) { + return false; + } + buf_shrink_expand(data->value, data->length, buf, sizeof(buf)); + if ((selector = calldata_get_selector()) == NULL) { + return false; + } + if (memcmp(selector, buf, sizeof(buf)) != 0) { + PRINTF("Error: selector mismatch!\n"); + return false; + } + memcpy(context->tx_info->selector, buf, sizeof(buf)); + context->set_flags |= SET_BIT(BIT_SELECTOR); + return true; +} + +static bool handle_fields_hash(const s_tlv_data *data, s_tx_info_ctx *context) { + if (data->length > sizeof(context->tx_info->fields_hash)) { + return false; + } + buf_shrink_expand(data->value, + data->length, + context->tx_info->fields_hash, + sizeof(context->tx_info->fields_hash)); + context->set_flags |= SET_BIT(BIT_FIELDS_HASH); + return true; +} + +static bool handle_operation_type(const s_tlv_data *data, s_tx_info_ctx *context) { + str_cpy_explicit_trunc((const char *) data->value, + data->length, + context->tx_info->operation_type, + sizeof(context->tx_info->operation_type)); + context->set_flags |= SET_BIT(BIT_OPERATION_TYPE); + return true; +} + +static bool handle_creator_name(const s_tlv_data *data, s_tx_info_ctx *context) { + str_cpy_explicit_trunc((const char *) data->value, + data->length, + context->tx_info->creator_name, + sizeof(context->tx_info->creator_name)); + context->set_flags |= SET_BIT(BIT_CREATOR_NAME); + return true; +} + +static bool handle_creator_legal_name(const s_tlv_data *data, s_tx_info_ctx *context) { + str_cpy_explicit_trunc((const char *) data->value, + data->length, + context->tx_info->creator_legal_name, + sizeof(context->tx_info->creator_legal_name)); + context->set_flags |= SET_BIT(BIT_CREATOR_LEGAL_NAME); + return true; +} + +static bool handle_creator_url(const s_tlv_data *data, s_tx_info_ctx *context) { + str_cpy_explicit_trunc((const char *) data->value, + data->length, + context->tx_info->creator_url, + sizeof(context->tx_info->creator_url)); + context->set_flags |= SET_BIT(BIT_CREATOR_URL); + return true; +} + +static bool handle_contract_name(const s_tlv_data *data, s_tx_info_ctx *context) { + str_cpy_explicit_trunc((const char *) data->value, + data->length, + context->tx_info->contract_name, + sizeof(context->tx_info->contract_name)); + context->set_flags |= SET_BIT(BIT_CONTRACT_NAME); + return true; +} + +static bool handle_deploy_date(const s_tlv_data *data, s_tx_info_ctx *context) { + uint8_t buf[sizeof(uint32_t)]; + time_t timestamp; + + if (data->length > sizeof(buf)) { + return false; + } + buf_shrink_expand(data->value, data->length, buf, sizeof(buf)); + timestamp = read_u32_be(buf, 0); + if (!time_format_to_yyyymmdd(×tamp, + context->tx_info->deploy_date, + sizeof(context->tx_info->deploy_date))) { + return false; + } + context->set_flags |= SET_BIT(BIT_DEPLOY_DATE); + return true; +} + +static bool handle_signature(const s_tlv_data *data, s_tx_info_ctx *context) { + if (data->length > sizeof(context->tx_info->signature)) { + return false; + } + memcpy(context->tx_info->signature, data->value, data->length); + context->tx_info->signature_len = data->length; + context->set_flags |= SET_BIT(BIT_SIGNATURE); + return true; +} + +bool handle_tx_info_struct(const s_tlv_data *data, s_tx_info_ctx *context) { + bool ret; + + switch (data->tag) { + case TAG_VERSION: + ret = handle_version(data, context); + break; + case TAG_CHAIN_ID: + ret = handle_chain_id(data, context); + break; + case TAG_CONTRACT_ADDR: + ret = handle_contract_addr(data, context); + break; + case TAG_SELECTOR: + ret = handle_selector(data, context); + break; + case TAG_FIELDS_HASH: + ret = handle_fields_hash(data, context); + break; + case TAG_OPERATION_TYPE: + ret = handle_operation_type(data, context); + break; + case TAG_CREATOR_NAME: + ret = handle_creator_name(data, context); + break; + case TAG_CREATOR_LEGAL_NAME: + ret = handle_creator_legal_name(data, context); + break; + case TAG_CREATOR_URL: + ret = handle_creator_url(data, context); + break; + case TAG_CONTRACT_NAME: + ret = handle_contract_name(data, context); + break; + case TAG_DEPLOY_DATE: + ret = handle_deploy_date(data, context); + break; + case TAG_SIGNATURE: + ret = handle_signature(data, context); + break; + default: + PRINTF(TLV_TAG_ERROR_MSG, data->tag); + ret = false; + } + if (ret && (data->tag != TAG_SIGNATURE)) { + hash_nbytes(data->raw, data->raw_size, (cx_hash_t *) &context->struct_hash); + } + return ret; +} + +bool verify_tx_info_struct(const s_tx_info_ctx *context) { + uint16_t required_bits = 0; + uint8_t hash[INT256_LENGTH]; + + // check if struct version was provided + required_bits |= SET_BIT(BIT_VERSION); + if ((context->set_flags & required_bits) != required_bits) { + PRINTF("Error: no struct version specified!\n"); + return false; + } + + // verify required fields + switch (context->tx_info->version) { + case 1: + required_bits |= + (SET_BIT(BIT_CHAIN_ID) | SET_BIT(BIT_CONTRACT_ADDR) | SET_BIT(BIT_SELECTOR) | + SET_BIT(BIT_FIELDS_HASH) | SET_BIT(BIT_OPERATION_TYPE) | SET_BIT(BIT_SIGNATURE)); + break; + default: + PRINTF("Error: unsupported tx info version (%u)\n", context->tx_info->version); + return false; + } + + if ((context->set_flags & required_bits) != required_bits) { + PRINTF("Error: missing required field(s)\n"); + return false; + } + + // verify signature + if (cx_hash_no_throw((cx_hash_t *) &context->struct_hash, + CX_LAST, + NULL, + 0, + hash, + sizeof(hash)) != CX_OK) { + PRINTF("Could not finalize struct hash!\n"); + return false; + } + + // TODO: change to LEDGER_CALLDATA_DESCRIPTOR key once available + if (check_signature_with_pubkey("TX info", + hash, + sizeof(hash), + LEDGER_SIGNATURE_PUBLIC_KEY, + sizeof(LEDGER_SIGNATURE_PUBLIC_KEY), +#ifdef HAVE_LEDGER_PKI + CERTIFICATE_PUBLIC_KEY_USAGE_COIN_META, +#endif + (uint8_t *) context->tx_info->signature, + context->tx_info->signature_len) != CX_OK) { + return false; + } + return true; +} + +const char *get_operation_type(void) { + if (g_tx_info->operation_type[0] == '\0') { + return NULL; + } + return g_tx_info->operation_type; +} + +const char *get_creator_name(void) { + if (g_tx_info->creator_name[0] == '\0') { + return NULL; + } + return g_tx_info->creator_name; +} + +const char *get_creator_legal_name(void) { + if (g_tx_info->creator_legal_name[0] == '\0') { + return NULL; + } + return g_tx_info->creator_legal_name; +} + +const char *get_creator_url(void) { + if (g_tx_info->creator_url[0] == '\0') { + return NULL; + } + return g_tx_info->creator_url; +} + +const char *get_contract_name(void) { + if (g_tx_info->contract_name[0] == '\0') { + return NULL; + } + return g_tx_info->contract_name; +} + +const uint8_t *get_contract_addr(void) { + return g_tx_info->contract_addr; +} + +const char *get_deploy_date(void) { + if (g_tx_info->deploy_date[0] == '\0') { + return NULL; + } + return g_tx_info->deploy_date; +} + +cx_hash_t *get_fields_hash_ctx(void) { + return (cx_hash_t *) &hash_ctx; +} + +bool validate_instruction_hash(void) { + uint8_t hash[sizeof(g_tx_info->fields_hash)]; + + if (cx_hash_no_throw((cx_hash_t *) &hash_ctx, CX_LAST, NULL, 0, hash, sizeof(hash)) != CX_OK) { + return false; + } + return memcmp(g_tx_info->fields_hash, hash, sizeof(hash)) == 0; +} + +#endif // HAVE_GENERIC_TX_PARSER diff --git a/src_features/generic_tx_parser/gtp_tx_info.h b/src_features/generic_tx_parser/gtp_tx_info.h new file mode 100644 index 000000000..443cfb374 --- /dev/null +++ b/src_features/generic_tx_parser/gtp_tx_info.h @@ -0,0 +1,49 @@ +#ifndef GTP_TX_INFO_H_ +#define GTP_TX_INFO_H_ + +#include +#include +#include "cx.h" +#include "common_utils.h" // ADDRESS_LENGTH, INT256_LENGTH +#include "calldata.h" +#include "tlv.h" + +typedef struct { + uint8_t version; + uint64_t chain_id; + uint8_t contract_addr[ADDRESS_LENGTH]; + uint8_t selector[CALLDATA_SELECTOR_SIZE]; + uint8_t fields_hash[INT256_LENGTH]; + char operation_type[31]; + char creator_name[23]; + char creator_legal_name[31]; + char creator_url[27]; + char contract_name[31]; + // YYYY-MM-DD\0 + char deploy_date[4 + 1 + 2 + 1 + 2 + 1]; + uint8_t signature_len; + uint8_t signature[73]; +} s_tx_info; + +typedef struct { + cx_sha256_t struct_hash; + uint16_t set_flags; + s_tx_info *tx_info; +} s_tx_info_ctx; + +extern s_tx_info *g_tx_info; + +bool handle_tx_info_struct(const s_tlv_data *data, s_tx_info_ctx *context); +bool verify_tx_info_struct(const s_tx_info_ctx *context); + +const char *get_operation_type(void); +const char *get_creator_name(void); +const char *get_creator_legal_name(void); +const char *get_creator_url(void); +const char *get_contract_name(void); +const uint8_t *get_contract_addr(void); +const char *get_deploy_date(void); +cx_hash_t *get_fields_hash_ctx(void); +bool validate_instruction_hash(void); + +#endif // !GTP_TX_INFO_H_ diff --git a/src_features/generic_tx_parser/gtp_value.c b/src_features/generic_tx_parser/gtp_value.c new file mode 100644 index 000000000..bc4c775cc --- /dev/null +++ b/src_features/generic_tx_parser/gtp_value.c @@ -0,0 +1,162 @@ +#ifdef HAVE_GENERIC_TX_PARSER + +#include "os_print.h" +#include "gtp_value.h" +#include "gtp_data_path.h" +#include "shared_context.h" // txContext +#include "apdu_constants.h" // APDU_RESPONSE_OK +#include "feature_signTx.h" // get_public_key +#include "gtp_parsed_value.h" + +enum { + TAG_VERSION = 0x00, + TAG_TYPE_FAMILY = 0x01, + TAG_TYPE_SIZE = 0x02, + TAG_DATA_PATH = 0x03, + TAG_CONTAINER_PATH = 0x04, + TAG_CONSTANT = 0x05, +}; + +static bool handle_version(const s_tlv_data *data, s_value_context *context) { + if (data->length != sizeof(uint8_t)) { + return false; + } + context->value->version = data->value[0]; + return true; +} + +static bool handle_type_family(const s_tlv_data *data, s_value_context *context) { + if (data->length != sizeof(e_type_family)) { + return false; + } + context->value->type_family = data->value[0]; + return true; +} + +static bool handle_type_size(const s_tlv_data *data, s_value_context *context) { + if (data->length != sizeof(uint8_t)) { + return false; + } + context->value->type_size = data->value[0]; + return (context->value->type_size > 0) && (context->value->type_size <= 32); +} + +static bool handle_data_path(const s_tlv_data *data, s_value_context *context) { + s_data_path_context ctx = {0}; + + ctx.data_path = &context->value->data_path; + explicit_bzero(ctx.data_path, sizeof(*ctx.data_path)); + if (!tlv_parse(data->value, + data->length, + (f_tlv_data_handler) &handle_data_path_struct, + &ctx)) { + return false; + } + context->value->source = SOURCE_CALLDATA; + return true; +} + +static bool handle_container_path(const s_tlv_data *data, s_value_context *context) { + if (data->length != sizeof(e_container_path)) { + return false; + } + context->value->container_path = data->value[0]; + context->value->source = SOURCE_RLP; + return true; +} + +static bool handle_constant(const s_tlv_data *data, s_value_context *context) { + if (data->length > sizeof(context->value->constant.buf)) { + return false; + } + context->value->constant.size = data->length; + memcpy(context->value->constant.buf, data->value, data->length); + context->value->source = SOURCE_CONSTANT; + return true; +} + +bool handle_value_struct(const s_tlv_data *data, s_value_context *context) { + bool ret; + + switch (data->tag) { + case TAG_VERSION: + ret = handle_version(data, context); + break; + case TAG_TYPE_FAMILY: + ret = handle_type_family(data, context); + break; + case TAG_TYPE_SIZE: + ret = handle_type_size(data, context); + break; + case TAG_DATA_PATH: + ret = handle_data_path(data, context); + break; + case TAG_CONTAINER_PATH: + ret = handle_container_path(data, context); + break; + case TAG_CONSTANT: + ret = handle_constant(data, context); + break; + default: + PRINTF(TLV_TAG_ERROR_MSG, data->tag); + ret = false; + } + return ret; +} + +// have to be declared here since it is not stored anywhere else +static uint8_t from_address[ADDRESS_LENGTH]; + +bool value_get(const s_value *value, s_parsed_value_collection *collection) { + bool ret; + + switch (value->source) { + case SOURCE_CALLDATA: + collection->size = 0; + ret = data_path_get(&value->data_path, collection); + break; + + case SOURCE_RLP: + switch (value->container_path) { + case CP_FROM: + if ((ret = get_public_key(from_address, sizeof(from_address)) == + APDU_RESPONSE_OK)) { + collection->value[0].ptr = from_address; + collection->value[0].length = sizeof(from_address); + collection->size = 1; + } + break; + + case CP_TO: + collection->value[0].ptr = tmpContent.txContent.destination; + collection->value[0].length = tmpContent.txContent.destinationLength; + collection->size = 1; + ret = true; + break; + + case CP_VALUE: + collection->value[0].ptr = tmpContent.txContent.value.value; + collection->value[0].length = tmpContent.txContent.value.length; + collection->size = 1; + ret = true; + break; + + default: + ret = false; + } + break; + + case SOURCE_CONSTANT: + collection->value[0].ptr = value->constant.buf; + collection->value[0].length = value->constant.size; + collection->size = 1; + ret = true; + break; + + default: + ret = false; + } + return ret; +} + +#endif // HAVE_GENERIC_TX_PARSER diff --git a/src_features/generic_tx_parser/gtp_value.h b/src_features/generic_tx_parser/gtp_value.h new file mode 100644 index 000000000..c6a49b376 --- /dev/null +++ b/src_features/generic_tx_parser/gtp_value.h @@ -0,0 +1,57 @@ +#ifndef GTP_VALUE_H_ +#define GTP_VALUE_H_ + +#include +#include +#include "tlv.h" +#include "gtp_data_path.h" +#include "calldata.h" + +typedef enum { + TF_UINT = 1, + TF_INT, + TF_UFIXED, + TF_FIXED, + TF_ADDRESS, + TF_BOOL, + TF_BYTES, + TF_STRING, +} e_type_family; + +typedef enum { + CP_FROM = 0, + CP_TO, + CP_VALUE, +} e_container_path; + +typedef enum { + SOURCE_CALLDATA, + SOURCE_RLP, + SOURCE_CONSTANT, +} e_value_source; + +typedef struct { + uint8_t size; + uint8_t buf[CALLDATA_CHUNK_SIZE]; +} s_constant; + +typedef struct { + uint8_t version; + e_type_family type_family; + uint8_t type_size; + union { + s_data_path data_path; + e_container_path container_path; + s_constant constant; + }; + e_value_source source; +} s_value; + +typedef struct { + s_value *value; +} s_value_context; + +bool handle_value_struct(const s_tlv_data *data, s_value_context *context); +bool value_get(const s_value *value, s_parsed_value_collection *collection); + +#endif // !GTP_VALUE_H_ diff --git a/src_features/provide_enum_value/cmd_enum_value.c b/src_features/provide_enum_value/cmd_enum_value.c new file mode 100644 index 000000000..33bc68bd8 --- /dev/null +++ b/src_features/provide_enum_value/cmd_enum_value.c @@ -0,0 +1,34 @@ +#ifdef HAVE_ENUM_VALUE + +#include +#include "cmd_enum_value.h" +#include "apdu_constants.h" +#include "read.h" +#include "mem.h" +#include "enum_value.h" +#include "tlv_apdu.h" +#include "tlv.h" + +bool handle_tlv_payload(const uint8_t *payload, uint16_t size, bool to_free) { + s_enum_value_ctx ctx = {0}; + + cx_sha256_init(&ctx.struct_hash); + if (!tlv_parse(payload, size, (f_tlv_data_handler) &handle_enum_value_struct, &ctx)) { + return false; + } + if (to_free) mem_dealloc(sizeof(size)); + if (!verify_enum_value_struct(&ctx)) { + return false; + } + return true; +} + +uint16_t handle_enum_value(uint8_t p1, uint8_t p2, uint8_t lc, const uint8_t *payload) { + (void) p2; + if (!tlv_from_apdu(p1 == P1_FIRST_CHUNK, lc, payload, &handle_tlv_payload)) { + return APDU_RESPONSE_INVALID_DATA; + } + return APDU_RESPONSE_OK; +} + +#endif // HAVE_ENUM_VALUE diff --git a/src_features/provide_enum_value/cmd_enum_value.h b/src_features/provide_enum_value/cmd_enum_value.h new file mode 100644 index 000000000..ea28eb98f --- /dev/null +++ b/src_features/provide_enum_value/cmd_enum_value.h @@ -0,0 +1,8 @@ +#ifndef CMD_ENUM_VALUE_H_ +#define CMD_ENUM_VALUE_H_ + +#include + +uint16_t handle_enum_value(uint8_t p1, uint8_t p2, uint8_t lc, const uint8_t *payload); + +#endif // !CMD_ENUM_VALUE_H_ diff --git a/src_features/provide_enum_value/enum_value.c b/src_features/provide_enum_value/enum_value.c new file mode 100644 index 000000000..64cf2fc30 --- /dev/null +++ b/src_features/provide_enum_value/enum_value.c @@ -0,0 +1,184 @@ +#ifdef HAVE_ENUM_VALUE + +#include +#include "enum_value.h" +#include "read.h" +#include "public_keys.h" +#include "utils.h" + +enum { + TAG_VERSION = 0x00, + TAG_CHAIN_ID = 0x01, + TAG_CONTRACT_ADDR = 0x02, + TAG_SELECTOR = 0x03, + TAG_ID = 0x04, + TAG_VALUE = 0x05, + TAG_NAME = 0x06, + TAG_SIGNATURE = 0xff, +}; + +static s_enum_value_entry g_enum_value = {0}; + +static bool handle_version(const s_tlv_data *data, s_enum_value_ctx *context) { + if (data->length != sizeof(context->enum_value.version)) { + return false; + } + context->enum_value.version = data->value[0]; + return true; +} + +static bool handle_chain_id(const s_tlv_data *data, s_enum_value_ctx *context) { + uint8_t buf[sizeof(context->enum_value.entry.chain_id)]; + + if (data->length > sizeof(buf)) { + return false; + } + buf_shrink_expand(data->value, data->length, buf, sizeof(buf)); + context->enum_value.entry.chain_id = read_u64_be(buf, 0); + return true; +} + +static bool handle_contract_addr(const s_tlv_data *data, s_enum_value_ctx *context) { + if (data->length > sizeof(context->enum_value.entry.contract_addr)) { + return false; + } + buf_shrink_expand(data->value, + data->length, + context->enum_value.entry.contract_addr, + sizeof(context->enum_value.entry.contract_addr)); + return true; +} + +static bool handle_selector(const s_tlv_data *data, s_enum_value_ctx *context) { + if (data->length > sizeof(context->enum_value.entry.selector)) { + return false; + } + buf_shrink_expand(data->value, + data->length, + context->enum_value.entry.selector, + sizeof(context->enum_value.entry.selector)); + return true; +} + +static bool handle_id(const s_tlv_data *data, s_enum_value_ctx *context) { + if (data->length != sizeof(context->enum_value.entry.id)) { + return false; + } + context->enum_value.entry.id = data->value[0]; + return true; +} + +static bool handle_value(const s_tlv_data *data, s_enum_value_ctx *context) { + if (data->length != sizeof(context->enum_value.entry.value)) { + return false; + } + context->enum_value.entry.value = data->value[0]; + return true; +} + +static bool handle_name(const s_tlv_data *data, s_enum_value_ctx *context) { + str_cpy_explicit_trunc((const char *) data->value, + data->length, + context->enum_value.entry.name, + sizeof(context->enum_value.entry.name)); + return true; +} + +static bool handle_signature(const s_tlv_data *data, s_enum_value_ctx *context) { + if (data->length > sizeof(context->enum_value.signature)) { + return false; + } + context->enum_value.signature_length = data->length; + memcpy(context->enum_value.signature, data->value, data->length); + return true; +} + +bool handle_enum_value_struct(const s_tlv_data *data, s_enum_value_ctx *context) { + bool ret; + + switch (data->tag) { + case TAG_VERSION: + ret = handle_version(data, context); + break; + case TAG_CHAIN_ID: + ret = handle_chain_id(data, context); + break; + case TAG_CONTRACT_ADDR: + ret = handle_contract_addr(data, context); + break; + case TAG_SELECTOR: + ret = handle_selector(data, context); + break; + case TAG_ID: + ret = handle_id(data, context); + break; + case TAG_VALUE: + ret = handle_value(data, context); + break; + case TAG_NAME: + ret = handle_name(data, context); + break; + case TAG_SIGNATURE: + ret = handle_signature(data, context); + break; + default: + PRINTF(TLV_TAG_ERROR_MSG, data->tag); + ret = false; + } + if (ret && (data->tag != TAG_SIGNATURE)) { + if (cx_hash_no_throw((cx_hash_t *) &context->struct_hash, + 0, + data->raw, + data->raw_size, + NULL, + 0) != CX_OK) { + return false; + } + } + return ret; +} + +bool verify_enum_value_struct(const s_enum_value_ctx *context) { + uint8_t hash[INT256_LENGTH]; + + if (cx_hash_no_throw((cx_hash_t *) &context->struct_hash, + CX_LAST, + NULL, + 0, + hash, + sizeof(hash)) != CX_OK) { + PRINTF("Could not finalize struct hash!\n"); + return false; + } + // TODO: change to LEDGER_CALLDATA_DESCRIPTOR key once available + if (check_signature_with_pubkey("enum value", + hash, + sizeof(hash), + LEDGER_SIGNATURE_PUBLIC_KEY, + sizeof(LEDGER_SIGNATURE_PUBLIC_KEY), +#ifdef HAVE_LEDGER_PKI + CERTIFICATE_PUBLIC_KEY_USAGE_COIN_META, +#endif + (uint8_t *) context->enum_value.signature, + context->enum_value.signature_length) != CX_OK) { + return false; + } + memcpy(&g_enum_value, &context->enum_value.entry, sizeof(g_enum_value)); + return true; +} + +const char *get_matching_enum_name(const uint64_t *chain_id, + const uint8_t *contract_addr, + const uint8_t *selector, + uint8_t id, + uint8_t value) { + if ((*chain_id == g_enum_value.chain_id) && + (memcmp(contract_addr, g_enum_value.contract_addr, ADDRESS_LENGTH) == 0) && + (memcmp(selector, g_enum_value.selector, SELECTOR_SIZE) == 0) && (id == g_enum_value.id) && + (value == g_enum_value.value)) { + return g_enum_value.name; + } + return NULL; +} + +#endif // HAVE_ENUM_VALUE diff --git a/src_features/provide_enum_value/enum_value.h b/src_features/provide_enum_value/enum_value.h new file mode 100644 index 000000000..0ce6a64c2 --- /dev/null +++ b/src_features/provide_enum_value/enum_value.h @@ -0,0 +1,39 @@ +#ifndef ENUM_VALUE_H_ +#define ENUM_VALUE_H_ + +#include +#include "common_utils.h" // ADDRESS_LENGTH +#include "plugin_utils.h" // SELECTOR_SIZE +#include "tlv.h" +#include "cx.h" + +typedef struct { + uint64_t chain_id; + uint8_t contract_addr[ADDRESS_LENGTH]; + uint8_t selector[SELECTOR_SIZE]; + uint8_t id; + uint8_t value; + char name[21]; +} s_enum_value_entry; + +typedef struct { + uint8_t version; + s_enum_value_entry entry; + uint8_t signature_length; + uint8_t signature[73]; +} s_enum_value; + +typedef struct { + s_enum_value enum_value; + cx_sha256_t struct_hash; +} s_enum_value_ctx; + +bool handle_enum_value_struct(const s_tlv_data *data, s_enum_value_ctx *context); +bool verify_enum_value_struct(const s_enum_value_ctx *context); +const char *get_matching_enum_name(const uint64_t *chain_id, + const uint8_t *contract_addr, + const uint8_t *selector, + uint8_t id, + uint8_t value); + +#endif // !ENUM_VALUE_H_ diff --git a/src_features/signMessageEIP712/context_712.h b/src_features/signMessageEIP712/context_712.h index 2b020973f..b01707484 100644 --- a/src_features/signMessageEIP712/context_712.h +++ b/src_features/signMessageEIP712/context_712.h @@ -4,7 +4,7 @@ #ifdef HAVE_EIP712_FULL_SUPPORT #include -#include "ethUstream.h" // ADDRESS_LENGTH +#include "common_utils.h" typedef struct { uint8_t contract_addr[ADDRESS_LENGTH]; diff --git a/src_features/signMessageEIP712/filtering.c b/src_features/signMessageEIP712/filtering.c index 5a03e8401..232043679 100644 --- a/src_features/signMessageEIP712/filtering.c +++ b/src_features/signMessageEIP712/filtering.c @@ -2,7 +2,6 @@ #include "filtering.h" #include "hash_bytes.h" -#include "ethUstream.h" // INT256_LENGTH #include "apdu_constants.h" // APDU return codes #include "public_keys.h" #include "manage_asset_info.h" diff --git a/src_features/signMessageEIP712/ui_logic.c b/src_features/signMessageEIP712/ui_logic.c index 901e9c637..d022fcb13 100644 --- a/src_features/signMessageEIP712/ui_logic.c +++ b/src_features/signMessageEIP712/ui_logic.c @@ -21,6 +21,7 @@ #include "filtering.h" #include "trusted_name.h" #include "network.h" +#include "time_format.h" #define AMOUNT_JOIN_FLAG_TOKEN (1 << 0) #define AMOUNT_JOIN_FLAG_VALUE (1 << 1) @@ -556,32 +557,9 @@ static bool ui_712_format_trusted_name(const uint8_t *data, uint8_t length) { * @return whether it was successful or not */ static bool ui_712_format_datetime(const uint8_t *data, uint8_t length) { - struct tm tstruct; - int shown_hour; time_t timestamp = u64_from_BE(data, length); - if (gmtime_r(×tamp, &tstruct) == NULL) { - return false; - } - if (tstruct.tm_hour == 0) { - shown_hour = 12; - } else { - shown_hour = tstruct.tm_hour; - if (shown_hour > 12) { - shown_hour -= 12; - } - } - snprintf(strings.tmp.tmp, - sizeof(strings.tmp.tmp), - "%04d-%02d-%02d\n%02d:%02d:%02d %s UTC", - tstruct.tm_year + 1900, - tstruct.tm_mon + 1, - tstruct.tm_mday, - shown_hour, - tstruct.tm_min, - tstruct.tm_sec, - (tstruct.tm_hour < 12) ? "AM" : "PM"); - return true; + return time_format_to_utc(×tamp, strings.tmp.tmp, sizeof(strings.tmp.tmp)); } /** diff --git a/src_features/signTx/cmd_signTx.c b/src_features/signTx/cmd_signTx.c index e74f5971f..0bece7eb7 100644 --- a/src_features/signTx/cmd_signTx.c +++ b/src_features/signTx/cmd_signTx.c @@ -2,82 +2,83 @@ #include "apdu_constants.h" #include "feature_signTx.h" #include "eth_plugin_interface.h" +#include "apdu_constants.h" +#ifdef HAVE_GENERIC_TX_PARSER +#include "gtp_tx_info.h" +#endif +#include "common_ui.h" -uint16_t handleSign(uint8_t p1, - uint8_t p2, - const uint8_t *workBuffer, - uint8_t dataLength, - unsigned int *flags) { - parserStatus_e txResult; - uint16_t sw = APDU_NO_RESPONSE; - cx_err_t error = CX_INTERNAL_ERROR; +typedef enum { + SIGN_MODE_BASIC = 0, + SIGN_MODE_STORE = 1, + SIGN_MODE_START_FLOW = 2, +} e_sign_mode; - if (p1 == P1_FIRST) { - if (appState != APP_STATE_IDLE) { - reset_app_context(); - } - appState = APP_STATE_SIGNING_TX; +static uint16_t handle_first_sign_chunk(const uint8_t *payload, + uint8_t length, + uint8_t *offset, + e_sign_mode mode) { + uint8_t length_tmp = length; + uint8_t tx_type; - workBuffer = parseBip32(workBuffer, &dataLength, &tmpCtx.transactionContext.bip32); - if (workBuffer == NULL) { - return APDU_RESPONSE_INVALID_DATA; - } + if (appState != APP_STATE_IDLE) { + reset_app_context(); + } + appState = APP_STATE_SIGNING_TX; - tmpContent.txContent.dataPresent = false; - dataContext.tokenContext.pluginStatus = ETH_PLUGIN_RESULT_UNAVAILABLE; + if (parseBip32(&payload[*offset], &length_tmp, &tmpCtx.transactionContext.bip32) == NULL) { + return APDU_RESPONSE_INVALID_DATA; + } + *offset += (length - length_tmp); - if (initTx(&txContext, &global_sha3, &tmpContent.txContent, customProcessor, NULL) == - false) { - return APDU_RESPONSE_INVALID_DATA; - } - if (dataLength < 1) { - PRINTF("Invalid data\n"); - return APDU_RESPONSE_INVALID_DATA; - } + tmpContent.txContent.dataPresent = false; + dataContext.tokenContext.pluginStatus = ETH_PLUGIN_RESULT_UNAVAILABLE; - // EIP 2718: TransactionType might be present before the TransactionPayload. - uint8_t txType = *workBuffer; - if (txType >= MIN_TX_TYPE && txType <= MAX_TX_TYPE) { - // Enumerate through all supported txTypes here... - if (txType == EIP2930 || txType == EIP1559) { - error = cx_hash_no_throw((cx_hash_t *) &global_sha3, 0, workBuffer, 1, NULL, 0); - if (error != CX_OK) { - return error; - } - txContext.txType = txType; - workBuffer++; - dataLength--; - } else { - PRINTF("Transaction type %d not supported\n", txType); - return APDU_RESPONSE_TX_TYPE_NOT_SUPPORTED; - } - } else { - txContext.txType = LEGACY; - } - PRINTF("TxType: %x\n", txContext.txType); - } else if (p1 != P1_MORE) { - return APDU_RESPONSE_INVALID_P1_P2; + if (init_tx(&txContext, &global_sha3, &tmpContent.txContent, mode == SIGN_MODE_STORE) == + false) { + return APDU_RESPONSE_INVALID_DATA; } - if (p2 != 0) { - return APDU_RESPONSE_INVALID_P1_P2; - } - if ((p1 == P1_MORE) && (appState != APP_STATE_SIGNING_TX)) { - PRINTF("Signature not initialized\n"); - return APDU_RESPONSE_CONDITION_NOT_SATISFIED; + if (*offset >= length) { + PRINTF("Invalid data\n"); + return APDU_RESPONSE_INVALID_DATA; } - if (txContext.currentField == RLP_NONE) { - PRINTF("Parser not initialized\n"); - return APDU_RESPONSE_CONDITION_NOT_SATISFIED; + + // EIP 2718: TransactionType might be present before the TransactionPayload. + tx_type = payload[*offset]; + if (tx_type <= MAX_TX_TYPE) { + switch (tx_type) { + case EIP1559: + case EIP2930: + break; + default: + PRINTF("Transaction type %d not supported\n", tx_type); + return APDU_RESPONSE_TX_TYPE_NOT_SUPPORTED; + } + if (cx_hash_no_throw((cx_hash_t *) &global_sha3, 0, &tx_type, sizeof(tx_type), NULL, 0) != + CX_OK) { + return APDU_RESPONSE_INVALID_DATA; + } + txContext.txType = tx_type; + *offset += sizeof(tx_type); + } else { + txContext.txType = LEGACY; } - txResult = processTx(&txContext, workBuffer, dataLength); - switch (txResult) { + PRINTF("TxType: %x\n", txContext.txType); + return APDU_NO_RESPONSE; +} + +uint16_t handle_parsing_status(parserStatus_e status) { + uint16_t sw = APDU_NO_RESPONSE; + + switch (status) { case USTREAM_SUSPENDED: break; case USTREAM_FINISHED: - sw = finalizeParsing(); + sw = finalize_parsing(&txContext); break; case USTREAM_PROCESSING: - return APDU_RESPONSE_OK; + sw = APDU_RESPONSE_OK; + break; case USTREAM_FAULT: if (G_called_from_swap) { // We have encountered an error while trying to sign a SWAP type transaction @@ -87,10 +88,82 @@ uint16_t handleSign(uint8_t p1, // unreachable os_sched_exit(0); } - return APDU_RESPONSE_INVALID_DATA; + sw = APDU_RESPONSE_INVALID_DATA; + break; default: PRINTF("Unexpected parser status\n"); - return APDU_RESPONSE_INVALID_DATA; + sw = APDU_RESPONSE_INVALID_DATA; + } + return sw; +} + +uint16_t handleSign(uint8_t p1, + uint8_t p2, + const uint8_t *payload, + uint8_t length, + unsigned int *flags) { + uint16_t sw = APDU_NO_RESPONSE; + uint8_t offset = 0; + + switch (p2) { + case SIGN_MODE_BASIC: +#ifdef HAVE_GENERIC_TX_PARSER + case SIGN_MODE_STORE: +#endif + switch (p1) { + case P1_FIRST: + if ((sw = handle_first_sign_chunk(payload, length, &offset, p2)) != + APDU_NO_RESPONSE) { + return sw; + } + break; + case P1_MORE: + if (appState != APP_STATE_SIGNING_TX) { + PRINTF("Signature not initialized\n"); + return APDU_RESPONSE_CONDITION_NOT_SATISFIED; + } + break; + default: + return APDU_RESPONSE_INVALID_P1_P2; + } + break; +#ifdef HAVE_GENERIC_TX_PARSER + case SIGN_MODE_START_FLOW: + if (appState != APP_STATE_SIGNING_TX) { + PRINTF("Signature not initialized\n"); + return APDU_RESPONSE_CONDITION_NOT_SATISFIED; + } + if (length != 0) { + return APDU_RESPONSE_INVALID_DATA; + } + if (!validate_instruction_hash()) { + PRINTF("Error: instructions hash mismatch!\n"); + return APDU_RESPONSE_INVALID_DATA; + } + if (!ui_gcs()) { + return APDU_RESPONSE_INTERNAL_ERROR; + } + *flags |= IO_ASYNCH_REPLY; + return APDU_NO_RESPONSE; +#endif + default: + return APDU_RESPONSE_INVALID_P1_P2; + } + + if (txContext.currentField == RLP_NONE) { + PRINTF("Parser not initialized\n"); + return APDU_RESPONSE_CONDITION_NOT_SATISFIED; + } + parserStatus_e pstatus = process_tx(&txContext, &payload[offset], length - offset); + sw = handle_parsing_status(pstatus); + if (p2 == SIGN_MODE_BASIC) { + if ((pstatus == USTREAM_FINISHED) && (sw == APDU_RESPONSE_OK)) { + // don't respond now, will be done after review + sw = APDU_NO_RESPONSE; + } + } + if (sw != APDU_NO_RESPONSE) { + return sw; } *flags |= IO_ASYNCH_REPLY; diff --git a/src/ethUstream.c b/src_features/signTx/ethUstream.c similarity index 91% rename from src/ethUstream.c rename to src_features/signTx/ethUstream.c index eebf8e993..d0bc6dbaa 100644 --- a/src/ethUstream.c +++ b/src_features/signTx/ethUstream.c @@ -21,9 +21,10 @@ #include "ethUstream.h" #include "rlp_utils.h" #include "common_utils.h" - -#define MAX_INT256 32 -#define MAX_ADDRESS 20 +#include "feature_signTx.h" +#ifdef HAVE_GENERIC_TX_PARSER +#include "calldata.h" +#endif static bool check_fields(txContext_t *context, const char *name, uint32_t length) { UNUSED(name); // Just for the case where DEBUG is not enabled @@ -56,17 +57,16 @@ static bool check_cmd_length(txContext_t *context, const char *name, uint32_t le return true; } -bool initTx(txContext_t *context, - cx_sha3_t *sha3, - txContent_t *content, - ustreamProcess_t customProcessor, - void *extra) { - memset(context, 0, sizeof(txContext_t)); +bool init_tx(txContext_t *context, cx_sha3_t *sha3, txContent_t *content, bool store_calldata) { + explicit_bzero(context, sizeof(*context)); context->sha3 = sha3; context->content = content; - context->customProcessor = customProcessor; - context->extra = extra; context->currentField = RLP_NONE + 1; +#ifdef HAVE_GENERIC_TX_PARSER + context->store_calldata = store_calldata; +#else + UNUSED(store_calldata); +#endif if (cx_keccak_init_no_throw(context->sha3, 256) != CX_OK) { return false; } @@ -150,7 +150,7 @@ static bool processAccessList(txContext_t *context) { } static bool processChainID(txContext_t *context) { - if (check_fields(context, "RLP_CHAINID", MAX_INT256) == false) { + if (check_fields(context, "RLP_CHAINID", INT256_LENGTH) == false) { return false; } @@ -170,7 +170,7 @@ static bool processChainID(txContext_t *context) { } static bool processNonce(txContext_t *context) { - if (check_fields(context, "RLP_NONCE", MAX_INT256) == false) { + if (check_fields(context, "RLP_NONCE", INT256_LENGTH) == false) { return false; } @@ -190,7 +190,7 @@ static bool processNonce(txContext_t *context) { } static bool processStartGas(txContext_t *context) { - if (check_fields(context, "RLP_STARTGAS", MAX_INT256) == false) { + if (check_fields(context, "RLP_STARTGAS", INT256_LENGTH) == false) { return false; } @@ -217,7 +217,7 @@ static bool processGasLimit(txContext_t *context) { } static bool processGasprice(txContext_t *context) { - if (check_fields(context, "RLP_GASPRICE", MAX_INT256) == false) { + if (check_fields(context, "RLP_GASPRICE", INT256_LENGTH) == false) { return false; } @@ -239,7 +239,7 @@ static bool processGasprice(txContext_t *context) { } static bool processValue(txContext_t *context) { - if (check_fields(context, "RLP_VALUE", MAX_INT256) == false) { + if (check_fields(context, "RLP_VALUE", INT256_LENGTH) == false) { return false; } @@ -261,7 +261,7 @@ static bool processValue(txContext_t *context) { } static bool processTo(txContext_t *context) { - if (check_fields(context, "RLP_TO", MAX_ADDRESS) == false) { + if (check_fields(context, "RLP_TO", ADDRESS_LENGTH) == false) { return false; } @@ -295,6 +295,16 @@ static bool processData(txContext_t *context) { if (copySize == 1 && *context->workBuffer == 0x00) { context->content->dataPresent = false; } +#ifdef HAVE_GENERIC_TX_PARSER + if (context->store_calldata) { + if (context->currentFieldPos == 0) { + if (!calldata_init(context->currentFieldLength)) { + return false; + } + } + calldata_append(context->workBuffer, copySize); + } +#endif if (copyTxData(context, NULL, copySize) == false) { return false; } @@ -540,22 +550,20 @@ static parserStatus_e processTxInternal(txContext_t *context) { return status; } } - if (context->customProcessor != NULL) { - customStatus = context->customProcessor(context); - PRINTF("After customprocessor\n"); - switch (customStatus) { - case CUSTOM_NOT_HANDLED: - case CUSTOM_HANDLED: - break; - case CUSTOM_SUSPENDED: - return USTREAM_SUSPENDED; - case CUSTOM_FAULT: - PRINTF("Custom processor aborted\n"); - return USTREAM_FAULT; - default: - PRINTF("Unhandled custom processor status\n"); - return USTREAM_FAULT; - } + customStatus = customProcessor(context); + PRINTF("After customprocessor\n"); + switch (customStatus) { + case CUSTOM_NOT_HANDLED: + case CUSTOM_HANDLED: + break; + case CUSTOM_SUSPENDED: + return USTREAM_SUSPENDED; + case CUSTOM_FAULT: + PRINTF("Custom processor aborted\n"); + return USTREAM_FAULT; + default: + PRINTF("Unhandled custom processor status\n"); + return USTREAM_FAULT; } if (customStatus == CUSTOM_NOT_HANDLED) { PRINTF("Current field: %d\n", context->currentField); @@ -584,8 +592,8 @@ static parserStatus_e processTxInternal(txContext_t *context) { PRINTF("end of here\n"); } -parserStatus_e processTx(txContext_t *context, const uint8_t *buffer, uint32_t length) { - context->workBuffer = buffer; +parserStatus_e process_tx(txContext_t *context, const uint8_t *payload, size_t length) { + context->workBuffer = payload; context->commandLength = length; return processTxInternal(context); } diff --git a/src/ethUstream.h b/src_features/signTx/ethUstream.h similarity index 89% rename from src/ethUstream.h rename to src_features/signTx/ethUstream.h index 3548af985..b30a5940c 100644 --- a/src/ethUstream.h +++ b/src_features/signTx/ethUstream.h @@ -25,8 +25,6 @@ #include "common_utils.h" #include "tx_content.h" -struct txContext_t; - typedef enum customStatus_e { CUSTOM_NOT_HANDLED, CUSTOM_HANDLED, @@ -34,8 +32,6 @@ typedef enum customStatus_e { CUSTOM_FAULT } customStatus_e; -typedef customStatus_e (*ustreamProcess_t)(struct txContext_t *context); - // First variant of every Tx enum. #define RLP_NONE 0 @@ -120,17 +116,16 @@ typedef struct txContext_t { uint32_t rlpBufferPos; const uint8_t *workBuffer; uint32_t commandLength; - ustreamProcess_t customProcessor; txContent_t *content; - void *extra; uint8_t txType; + bool rlp_size_known; + uint32_t remaining_rlp_size; +#ifdef HAVE_GENERIC_TX_PARSER + bool store_calldata; +#endif } txContext_t; -bool initTx(txContext_t *context, - cx_sha3_t *sha3, - txContent_t *content, - ustreamProcess_t customProcessor, - void *extra); -parserStatus_e processTx(txContext_t *context, const uint8_t *buffer, uint32_t length); +bool init_tx(txContext_t *context, cx_sha3_t *sha3, txContent_t *content, bool store_calldata); +parserStatus_e process_tx(txContext_t *context, const uint8_t *buffer, size_t length); parserStatus_e continueTx(txContext_t *context); bool copyTxData(txContext_t *context, uint8_t *out, uint32_t length); diff --git a/src_features/signTx/feature_signTx.h b/src_features/signTx/feature_signTx.h index f1c83df42..9f0af6371 100644 --- a/src_features/signTx/feature_signTx.h +++ b/src_features/signTx/feature_signTx.h @@ -1,7 +1,9 @@ #ifndef _SIGN_TX_H_ #define _SIGN_TX_H_ -#include "shared_context.h" +#include +#include +#include "ethUstream.h" // Error codes for swap, to be moved in SDK? #define ERROR_WRONG_AMOUNT 0x01 @@ -27,10 +29,19 @@ typedef enum { } plugin_ui_state_t; customStatus_e customProcessor(txContext_t *context); -uint16_t finalizeParsing(); +uint16_t finalize_parsing(const txContext_t *context); void ux_approve_tx(bool fromPlugin); void start_signature_flow(void); void send_swap_error(uint8_t error_code, app_code_t app_code, const char *str1, const char *str2); +uint16_t handle_parsing_status(parserStatus_e status); + +uint16_t get_public_key(uint8_t *out, uint8_t outLength); +bool max_transaction_fee_to_string(const txInt256_t *BEGasPrice, + const txInt256_t *BEGasLimit, + char *displayBuffer, + uint32_t displayBufferSize); +uint16_t get_network_as_string(char *out, size_t out_size); + #endif // _SIGN_TX_H_ diff --git a/src_features/signTx/logic_signTx.c b/src_features/signTx/logic_signTx.c index b50642ebd..1f9085346 100644 --- a/src_features/signTx/logic_signTx.c +++ b/src_features/signTx/logic_signTx.c @@ -13,6 +13,7 @@ #include "manage_asset_info.h" #include "handle_swap_sign_transaction.h" #include "os_math.h" +#include "calldata.h" static bool g_use_standard_ui; @@ -60,12 +61,18 @@ customStatus_e customProcessor(txContext_t *context) { // If contract debugging mode is activated, do not go through the plugin activation // as they wouldn't be displayed if the plugin consumes all data but fallbacks // Still go through plugin activation in Swap context - if (!N_storage.contractDetails || G_called_from_swap) { - eth_plugin_prepare_init(&pluginInit, - context->workBuffer, - context->currentFieldLength); - dataContext.tokenContext.pluginStatus = - eth_plugin_perform_init(tmpContent.txContent.destination, &pluginInit); +#ifdef HAVE_GENERIC_TX_PARSER + if (!context->store_calldata) { +#else + { +#endif + if (!N_storage.contractDetails || G_called_from_swap) { + eth_plugin_prepare_init(&pluginInit, + context->workBuffer, + context->currentFieldLength); + dataContext.tokenContext.pluginStatus = + eth_plugin_perform_init(tmpContent.txContent.destination, &pluginInit); + } } PRINTF("pluginstatus %d\n", dataContext.tokenContext.pluginStatus); eth_plugin_result_t status = dataContext.tokenContext.pluginStatus; @@ -87,10 +94,16 @@ customStatus_e customProcessor(txContext_t *context) { uint32_t copySize; uint32_t fieldPos = context->currentFieldPos; if (fieldPos == 0) { // not reached if a plugin is available - if (!N_storage.dataAllowed) { - PRINTF("Data field forbidden\n"); - ui_error_blind_signing(); - return CUSTOM_FAULT; +#ifdef HAVE_GENERIC_TX_PARSER + if (!context->store_calldata) { +#else + { +#endif + if (!N_storage.dataAllowed) { + PRINTF("Data field forbidden\n"); + ui_error_blind_signing(); + return CUSTOM_FAULT; + } } if (!N_storage.contractDetails) { return CUSTOM_NOT_HANDLED; @@ -247,10 +260,10 @@ static void raw_fee_to_string(uint256_t *rawFee, char *displayBuffer, uint32_t d // Compute the fees, transform it to a string, prepend a ticker to it and copy everything to // `displayBuffer` output -static bool max_transaction_fee_to_string(const txInt256_t *BEGasPrice, - const txInt256_t *BEGasLimit, - char *displayBuffer, - uint32_t displayBufferSize) { +bool max_transaction_fee_to_string(const txInt256_t *BEGasPrice, + const txInt256_t *BEGasLimit, + char *displayBuffer, + uint32_t displayBufferSize) { // Use temporary variables to convert values to uint256_t uint256_t gasPrice = {0}; uint256_t gasLimit = {0}; @@ -274,7 +287,7 @@ static void nonce_to_string(const txInt256_t *nonce, char *out, size_t out_size) tostring256(&nonce_uint256, 10, out, out_size); } -static uint16_t get_network_as_string(char *out, size_t out_size) { +uint16_t get_network_as_string(char *out, size_t out_size) { uint64_t chain_id = get_tx_chain_id(); const char *name = get_network_name_from_chain_id(&chain_id); @@ -290,7 +303,7 @@ static uint16_t get_network_as_string(char *out, size_t out_size) { return APDU_RESPONSE_OK; } -static uint16_t get_public_key(uint8_t *out, uint8_t outLength) { +uint16_t get_public_key(uint8_t *out, uint8_t outLength) { uint8_t raw_pubkey[65]; cx_err_t error = CX_INTERNAL_ERROR; @@ -375,7 +388,7 @@ __attribute__((noreturn)) void send_swap_error(uint8_t error_code, finalize_exchange_sign_transaction(false); } -__attribute__((noinline)) static uint16_t finalize_parsing_helper(void) { +__attribute__((noinline)) static uint16_t finalize_parsing_helper(const txContext_t *context) { char displayBuffer[50]; uint8_t decimals = WEI_TO_ETHER; uint64_t chain_id = get_tx_chain_id(); @@ -522,11 +535,18 @@ __attribute__((noinline)) static uint16_t finalize_parsing_helper(void) { } } - if (tmpContent.txContent.dataPresent && !N_storage.dataAllowed) { - PRINTF("Data is present but not allowed\n"); - report_finalize_error(); - ui_error_blind_signing(); - return false; +#ifdef HAVE_GENERIC_TX_PARSER + if (!context->store_calldata) { +#else + (void) context; + { +#endif + if (tmpContent.txContent.dataPresent && !N_storage.dataAllowed) { + PRINTF("Data is present but not allowed\n"); + report_finalize_error(); + ui_error_blind_signing(); + return false; + } } // Prepare destination address and amount to display @@ -633,28 +653,40 @@ void start_signature_flow(void) { } } -uint16_t finalizeParsing(void) { +uint16_t finalize_parsing(const txContext_t *context) { uint16_t sw = APDU_RESPONSE_UNKNOWN; g_use_standard_ui = true; - sw = finalize_parsing_helper(); + sw = finalize_parsing_helper(context); if (sw != APDU_RESPONSE_OK) { return sw; } - // If called from swap, the user has already validated a standard transaction - // And we have already checked the fields of this transaction above - if (G_called_from_swap && g_use_standard_ui) { - ui_idle(); - io_seproxyhal_touch_tx_ok(); +#ifdef HAVE_GENERIC_TX_PARSER + if (context->store_calldata) { + if (calldata_get_selector() == NULL) { + PRINTF("Asked to store calldata but none was provided!\n"); + return APDU_RESPONSE_INVALID_DATA; + } } else { +#else + (void) context; + { +#endif + // If called from swap, the user has already validated a standard transaction + // And we have already checked the fields of this transaction above + if (G_called_from_swap && g_use_standard_ui) { + ui_idle(); + io_seproxyhal_touch_tx_ok(); + } else { #ifdef HAVE_BAGL - // If blind-signing detected, start the warning flow beforehand - if (tmpContent.txContent.dataPresent) { - ui_warning_blind_signing(); - } else + // If blind-signing detected, start the warning flow beforehand + if (tmpContent.txContent.dataPresent) { + ui_warning_blind_signing(); + } else #endif - { - start_signature_flow(); + { + start_signature_flow(); + } } } return APDU_RESPONSE_OK; diff --git a/src/rlp_utils.c b/src_features/signTx/rlp_utils.c similarity index 100% rename from src/rlp_utils.c rename to src_features/signTx/rlp_utils.c diff --git a/src/rlp_utils.h b/src_features/signTx/rlp_utils.h similarity index 100% rename from src/rlp_utils.h rename to src_features/signTx/rlp_utils.h diff --git a/src_features/signTx/ui_common_signTx.c b/src_features/signTx/ui_common_signTx.c index c29f3672e..6a6cf7ea2 100644 --- a/src_features/signTx/ui_common_signTx.c +++ b/src_features/signTx/ui_common_signTx.c @@ -73,26 +73,12 @@ unsigned int io_seproxyhal_touch_tx_cancel(void) { } unsigned int io_seproxyhal_touch_data_ok(void) { - parserStatus_e txResult = USTREAM_FINISHED; - txResult = continueTx(&txContext); unsigned int err = 0; - switch (txResult) { - case USTREAM_SUSPENDED: - break; - case USTREAM_FINISHED: - err = finalizeParsing(); - break; - case USTREAM_PROCESSING: - err = io_seproxyhal_send_status(APDU_RESPONSE_OK, 0, false, true); - break; - case USTREAM_FAULT: - err = io_seproxyhal_send_status(APDU_RESPONSE_INVALID_DATA, 0, true, true); - break; - default: - PRINTF("Unexpected parser status\n"); - err = io_seproxyhal_send_status(APDU_RESPONSE_INVALID_DATA, 0, true, true); + parserStatus_e pstatus = continueTx(&txContext); + uint16_t sw = handle_parsing_status(pstatus); + if ((pstatus != USTREAM_SUSPENDED) && (pstatus != USTREAM_FINISHED)) { + err = io_seproxyhal_send_status(sw, 0, sw != APDU_RESPONSE_OK, true); } - return err; } diff --git a/src_nbgl/ui_blind_signing.c b/src_nbgl/ui_blind_signing.c index ba0a40fb8..d5ad753c8 100644 --- a/src_nbgl/ui_blind_signing.c +++ b/src_nbgl/ui_blind_signing.c @@ -1,7 +1,6 @@ #include #include "shared_context.h" #include "ui_callbacks.h" -#include "feature_signTx.h" #include "ui_nbgl.h" #include "apdu_constants.h" #include "context_712.h" diff --git a/src_nbgl/ui_gcs.c b/src_nbgl/ui_gcs.c new file mode 100644 index 000000000..8f842837d --- /dev/null +++ b/src_nbgl/ui_gcs.c @@ -0,0 +1,235 @@ +#ifdef HAVE_GENERIC_TX_PARSER + +#include +#include "ui_nbgl.h" +#include "gtp_tx_info.h" +#include "gtp_field_table.h" +#include "mem.h" +#include "mem_utils.h" +#include "network.h" +#include "ui_callbacks.h" +#include "feature_signTx.h" +#include "apdu_constants.h" + +static nbgl_layoutTagValueList_t g_pair_list; +static size_t g_alloc_size; + +static void review_choice(bool confirm) { + if (confirm) { + io_seproxyhal_touch_tx_ok(); + nbgl_useCaseReviewStatus(STATUS_TYPE_TRANSACTION_SIGNED, ui_idle); + } else { + io_seproxyhal_touch_tx_cancel(); + nbgl_useCaseReviewStatus(STATUS_TYPE_TRANSACTION_REJECTED, ui_idle); + } +} + +static char *_strdup(const char *src) { + char *dst; + size_t length = strlen(src) + 1; + + if ((dst = mem_alloc(length)) != NULL) { + memmove(dst, src, length); + } + return dst; +} + +static bool cleanup_on_error(const void *mem_before) { + mem_dealloc(mem_alloc(0) - mem_before); + return false; +} + +#define MAX_INFO_COUNT 3 + +static bool prepare_infos(nbgl_contentInfoList_t *infos) { + char *tmp_buf = strings.tmp.tmp; + size_t tmp_buf_size = sizeof(strings.tmp.tmp); + size_t off; + uint8_t count = 0; + const char **keys; + const char **values; + nbgl_contentValueExt_t *extensions; + const char *value; + int contract_idx = -1; + + if (((keys = mem_alloc_and_align(sizeof(*keys) * MAX_INFO_COUNT, __alignof__(*keys))) == + NULL) || + ((values = mem_alloc_and_align(sizeof(*values) * MAX_INFO_COUNT, __alignof__(*values))) == + NULL)) { + return false; + } + if ((value = get_creator_legal_name()) != NULL) { + snprintf(tmp_buf, tmp_buf_size, "Contract owner"); + if ((keys[count] = _strdup(tmp_buf)) == NULL) { + return false; + } + snprintf(tmp_buf, tmp_buf_size, "%s", value); + if ((value = get_creator_url()) != NULL) { + off = strlen(tmp_buf); + snprintf(tmp_buf + off, tmp_buf_size - off, "\n%s", value); + } + if ((values[count] = _strdup(tmp_buf)) == NULL) { + return false; + } + count += 1; + } + + if ((value = get_contract_name()) != NULL) { + snprintf(tmp_buf, tmp_buf_size, "Contract"); + if ((keys[count] = _strdup(tmp_buf)) == NULL) { + return false; + } + snprintf(tmp_buf, tmp_buf_size, "%s", value); + if ((values[count] = _strdup(tmp_buf)) == NULL) { + return false; + } + contract_idx = count; + count += 1; + } + + if ((value = get_deploy_date()) != NULL) { + snprintf(tmp_buf, tmp_buf_size, "Deployed on"); + if ((keys[count] = _strdup(tmp_buf)) == NULL) { + return false; + } + snprintf(tmp_buf, tmp_buf_size, "%s", value); + if ((values[count] = _strdup(tmp_buf)) == NULL) { + return false; + } + count += 1; + } + + if ((extensions = mem_alloc_and_align(sizeof(*extensions) * count, __alignof__(*extensions))) == + NULL) { + return false; + } + explicit_bzero(extensions, sizeof(*extensions) * count); + + if (contract_idx != -1) { + if (!getEthDisplayableAddress((uint8_t *) get_contract_addr(), + tmp_buf, + tmp_buf_size, + chainConfig->chainId)) { + return false; + } + if ((extensions[contract_idx].title = _strdup(tmp_buf)) == NULL) { + return false; + } + // Etherscan only for mainnet + if (get_tx_chain_id() == ETHEREUM_MAINNET_CHAINID) { + if ((extensions[contract_idx].explanation = _strdup("Scan to view on Etherscan")) == + NULL) { + return false; + } + snprintf(tmp_buf, + tmp_buf_size, + "https://etherscan.io/address/%s", + extensions[contract_idx].title); + if ((extensions[contract_idx].fullValue = _strdup(tmp_buf)) == NULL) { + return false; + } + } else { + extensions[contract_idx].fullValue = extensions[contract_idx].title; + } + extensions[contract_idx].aliasType = QR_CODE_ALIAS; + } + + infos->nbInfos = count; + infos->infoTypes = keys; + infos->infoContents = values; + infos->infoExtensions = extensions; + infos->withExtensions = true; + return true; +} + +bool ui_gcs(void) { + char *tmp_buf = strings.tmp.tmp; + size_t tmp_buf_size = sizeof(strings.tmp.tmp); + size_t off; + const char *review_title; + const char *sign_title; + nbgl_contentTagValue_t *pairs; + s_field_table_entry entry; + bool show_network; + nbgl_tipBox_t tip_box; + const void *mem_before = mem_alloc(0); + + snprintf(tmp_buf, tmp_buf_size, "Review transaction to %s", get_operation_type()); + if ((review_title = _strdup(tmp_buf)) == NULL) { + return cleanup_on_error(mem_before); + } + snprintf(tmp_buf, tmp_buf_size, "Sign transaction to %s?", get_operation_type()); + if ((sign_title = _strdup(tmp_buf)) == NULL) { + return cleanup_on_error(mem_before); + } + explicit_bzero(&tip_box, sizeof(tip_box)); + tip_box.icon = &C_review_info_button; + tip_box.text = NULL; + tip_box.modalTitle = "Contract information"; + tip_box.type = INFOS_LIST; + if (!prepare_infos(&tip_box.infos)) { + return cleanup_on_error(mem_before); + } + snprintf(tmp_buf, tmp_buf_size, "Interaction with a\nsmart contract"); + off = strlen(tmp_buf); + if (get_creator_name()) { + snprintf(tmp_buf + off, tmp_buf_size - off, " from:\n%s", get_creator_name()); + } + if ((tip_box.text = _strdup(tmp_buf)) == NULL) { + return cleanup_on_error(mem_before); + } + + g_pair_list.nbPairs = field_table_size() + 1; + show_network = get_tx_chain_id() != chainConfig->chainId; + if (show_network) { + g_pair_list.nbPairs += 1; + } + if ((pairs = mem_alloc_and_align(sizeof(*pairs) * g_pair_list.nbPairs, __alignof__(*pairs))) == + NULL) { + return cleanup_on_error(mem_before); + } + explicit_bzero(pairs, sizeof(*pairs) * g_pair_list.nbPairs); + + for (int i = 0; i < (int) field_table_size(); ++i) { + if (!get_from_field_table(i, &entry)) { + return cleanup_on_error(mem_before); + } + pairs[i].item = entry.key; + pairs[i].value = entry.value; + } + + if (show_network) { + pairs[g_pair_list.nbPairs - 2].item = _strdup("Network"); + if (get_network_as_string(tmp_buf, tmp_buf_size) != APDU_RESPONSE_OK) { + return cleanup_on_error(mem_before); + } + pairs[g_pair_list.nbPairs - 2].value = _strdup(tmp_buf); + } + + pairs[g_pair_list.nbPairs - 1].item = _strdup("Max fees"); + if (max_transaction_fee_to_string(&tmpContent.txContent.gasprice, + &tmpContent.txContent.startgas, + tmp_buf, + tmp_buf_size) == false) { + PRINTF("Error: Could not format the max fees!\n"); + } + pairs[g_pair_list.nbPairs - 1].value = _strdup(tmp_buf); + + g_pair_list.pairs = pairs; + nbgl_useCaseAdvancedReview(TYPE_TRANSACTION, + &g_pair_list, + get_tx_icon(), + review_title, + NULL, + sign_title, + &tip_box, + review_choice); + g_alloc_size = mem_alloc(0) - mem_before; + return true; +} + +void ui_gcs_cleanup(void) { + mem_dealloc(g_alloc_size); +} + +#endif // HAVE_GENERIC_TX_PARSER diff --git a/src_plugins/erc1155/erc1155_plugin.h b/src_plugins/erc1155/erc1155_plugin.h index d9615dfeb..56908b2e9 100644 --- a/src_plugins/erc1155/erc1155_plugin.h +++ b/src_plugins/erc1155/erc1155_plugin.h @@ -5,7 +5,6 @@ #include #include -#include "ethUstream.h" #include "uint256.h" #include "asset_info.h" #include "eth_plugin_interface.h" diff --git a/src_plugins/erc20/erc20_plugin.c b/src_plugins/erc20/erc20_plugin.c index 13daf29f2..c7a5af884 100644 --- a/src_plugins/erc20/erc20_plugin.c +++ b/src_plugins/erc20/erc20_plugin.c @@ -3,7 +3,6 @@ #include "eth_plugin_handler.h" #include "shared_context.h" #include "plugin_utils.h" -#include "ethUstream.h" #include "common_utils.h" typedef enum { ERC20_TRANSFER = 0, ERC20_APPROVE } erc20Selector_t; diff --git a/src_plugins/erc721/erc721_plugin.h b/src_plugins/erc721/erc721_plugin.h index 42f1df68e..bf1d1edeb 100644 --- a/src_plugins/erc721/erc721_plugin.h +++ b/src_plugins/erc721/erc721_plugin.h @@ -5,7 +5,6 @@ #include #include -#include "ethUstream.h" #include "asset_info.h" #include "eth_plugin_interface.h" diff --git a/tests/ragger/abis/1inch.abi.json b/tests/ragger/abis/1inch.abi.json new file mode 100644 index 000000000..7deb4b632 --- /dev/null +++ b/tests/ragger/abis/1inch.abi.json @@ -0,0 +1,2062 @@ +[ + { + "inputs" : [ + { + "internalType" : "contract IWETH", + "name" : "weth", + "type" : "address" + } + ], + "stateMutability" : "nonpayable", + "type" : "constructor" + }, + { + "inputs" : [], + "name" : "AdvanceEpochFailed", + "type" : "error" + }, + { + "inputs" : [], + "name" : "ArbitraryStaticCallFailed", + "type" : "error" + }, + { + "inputs" : [], + "name" : "BadCurveSwapSelector", + "type" : "error" + }, + { + "inputs" : [], + "name" : "BadPool", + "type" : "error" + }, + { + "inputs" : [], + "name" : "BadSignature", + "type" : "error" + }, + { + "inputs" : [], + "name" : "BitInvalidatedOrder", + "type" : "error" + }, + { + "inputs" : [], + "name" : "ETHTransferFailed", + "type" : "error" + }, + { + "inputs" : [], + "name" : "ETHTransferFailed", + "type" : "error" + }, + { + "inputs" : [], + "name" : "EnforcedPause", + "type" : "error" + }, + { + "inputs" : [], + "name" : "EpochManagerAndBitInvalidatorsAreIncompatible", + "type" : "error" + }, + { + "inputs" : [], + "name" : "EthDepositRejected", + "type" : "error" + }, + { + "inputs" : [], + "name" : "ExpectedPause", + "type" : "error" + }, + { + "inputs" : [], + "name" : "InsufficientBalance", + "type" : "error" + }, + { + "inputs" : [], + "name" : "InvalidMsgValue", + "type" : "error" + }, + { + "inputs" : [], + "name" : "InvalidMsgValue", + "type" : "error" + }, + { + "inputs" : [], + "name" : "InvalidPermit2Transfer", + "type" : "error" + }, + { + "inputs" : [], + "name" : "InvalidShortString", + "type" : "error" + }, + { + "inputs" : [], + "name" : "InvalidatedOrder", + "type" : "error" + }, + { + "inputs" : [], + "name" : "MakingAmountTooLow", + "type" : "error" + }, + { + "inputs" : [], + "name" : "MismatchArraysLengths", + "type" : "error" + }, + { + "inputs" : [], + "name" : "OrderExpired", + "type" : "error" + }, + { + "inputs" : [], + "name" : "OrderIsNotSuitableForMassInvalidation", + "type" : "error" + }, + { + "inputs" : [ + { + "internalType" : "address", + "name" : "owner", + "type" : "address" + } + ], + "name" : "OwnableInvalidOwner", + "type" : "error" + }, + { + "inputs" : [ + { + "internalType" : "address", + "name" : "account", + "type" : "address" + } + ], + "name" : "OwnableUnauthorizedAccount", + "type" : "error" + }, + { + "inputs" : [], + "name" : "PartialFillNotAllowed", + "type" : "error" + }, + { + "inputs" : [], + "name" : "Permit2TransferAmountTooHigh", + "type" : "error" + }, + { + "inputs" : [], + "name" : "PredicateIsNotTrue", + "type" : "error" + }, + { + "inputs" : [], + "name" : "PrivateOrder", + "type" : "error" + }, + { + "inputs" : [], + "name" : "ReentrancyDetected", + "type" : "error" + }, + { + "inputs" : [], + "name" : "RemainingInvalidatedOrder", + "type" : "error" + }, + { + "inputs" : [], + "name" : "ReservesCallFailed", + "type" : "error" + }, + { + "inputs" : [ + { + "internalType" : "uint256", + "name" : "result", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "minReturn", + "type" : "uint256" + } + ], + "name" : "ReturnAmountIsNotEnough", + "type" : "error" + }, + { + "inputs" : [], + "name" : "SafeTransferFailed", + "type" : "error" + }, + { + "inputs" : [], + "name" : "SafeTransferFromFailed", + "type" : "error" + }, + { + "inputs" : [ + { + "internalType" : "bool", + "name" : "success", + "type" : "bool" + }, + { + "internalType" : "bytes", + "name" : "res", + "type" : "bytes" + } + ], + "name" : "SimulationResults", + "type" : "error" + }, + { + "inputs" : [ + { + "internalType" : "string", + "name" : "str", + "type" : "string" + } + ], + "name" : "StringTooLong", + "type" : "error" + }, + { + "inputs" : [], + "name" : "SwapWithZeroAmount", + "type" : "error" + }, + { + "inputs" : [], + "name" : "TakingAmountExceeded", + "type" : "error" + }, + { + "inputs" : [], + "name" : "TakingAmountTooHigh", + "type" : "error" + }, + { + "inputs" : [], + "name" : "TransferFromMakerToTakerFailed", + "type" : "error" + }, + { + "inputs" : [], + "name" : "TransferFromTakerToMakerFailed", + "type" : "error" + }, + { + "inputs" : [], + "name" : "WrongSeriesNonce", + "type" : "error" + }, + { + "inputs" : [], + "name" : "ZeroAddress", + "type" : "error" + }, + { + "inputs" : [], + "name" : "ZeroMinReturn", + "type" : "error" + }, + { + "anonymous" : false, + "inputs" : [ + { + "indexed" : true, + "internalType" : "address", + "name" : "maker", + "type" : "address" + }, + { + "indexed" : false, + "internalType" : "uint256", + "name" : "slotIndex", + "type" : "uint256" + }, + { + "indexed" : false, + "internalType" : "uint256", + "name" : "slotValue", + "type" : "uint256" + } + ], + "name" : "BitInvalidatorUpdated", + "type" : "event" + }, + { + "anonymous" : false, + "inputs" : [], + "name" : "EIP712DomainChanged", + "type" : "event" + }, + { + "anonymous" : false, + "inputs" : [ + { + "indexed" : true, + "internalType" : "address", + "name" : "maker", + "type" : "address" + }, + { + "indexed" : false, + "internalType" : "uint256", + "name" : "series", + "type" : "uint256" + }, + { + "indexed" : false, + "internalType" : "uint256", + "name" : "newEpoch", + "type" : "uint256" + } + ], + "name" : "EpochIncreased", + "type" : "event" + }, + { + "anonymous" : false, + "inputs" : [ + { + "indexed" : false, + "internalType" : "bytes32", + "name" : "orderHash", + "type" : "bytes32" + } + ], + "name" : "OrderCancelled", + "type" : "event" + }, + { + "anonymous" : false, + "inputs" : [ + { + "indexed" : false, + "internalType" : "bytes32", + "name" : "orderHash", + "type" : "bytes32" + }, + { + "indexed" : false, + "internalType" : "uint256", + "name" : "remainingAmount", + "type" : "uint256" + } + ], + "name" : "OrderFilled", + "type" : "event" + }, + { + "anonymous" : false, + "inputs" : [ + { + "indexed" : true, + "internalType" : "address", + "name" : "previousOwner", + "type" : "address" + }, + { + "indexed" : true, + "internalType" : "address", + "name" : "newOwner", + "type" : "address" + } + ], + "name" : "OwnershipTransferred", + "type" : "event" + }, + { + "anonymous" : false, + "inputs" : [ + { + "indexed" : false, + "internalType" : "address", + "name" : "account", + "type" : "address" + } + ], + "name" : "Paused", + "type" : "event" + }, + { + "anonymous" : false, + "inputs" : [ + { + "indexed" : false, + "internalType" : "address", + "name" : "account", + "type" : "address" + } + ], + "name" : "Unpaused", + "type" : "event" + }, + { + "inputs" : [ + { + "internalType" : "uint96", + "name" : "series", + "type" : "uint96" + }, + { + "internalType" : "uint256", + "name" : "amount", + "type" : "uint256" + } + ], + "name" : "advanceEpoch", + "outputs" : [], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "uint256", + "name" : "offsets", + "type" : "uint256" + }, + { + "internalType" : "bytes", + "name" : "data", + "type" : "bytes" + } + ], + "name" : "and", + "outputs" : [ + { + "internalType" : "bool", + "name" : "", + "type" : "bool" + } + ], + "stateMutability" : "view", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "address", + "name" : "target", + "type" : "address" + }, + { + "internalType" : "bytes", + "name" : "data", + "type" : "bytes" + } + ], + "name" : "arbitraryStaticCall", + "outputs" : [ + { + "internalType" : "uint256", + "name" : "", + "type" : "uint256" + } + ], + "stateMutability" : "view", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "address", + "name" : "maker", + "type" : "address" + }, + { + "internalType" : "uint256", + "name" : "slot", + "type" : "uint256" + } + ], + "name" : "bitInvalidatorForOrder", + "outputs" : [ + { + "internalType" : "uint256", + "name" : "", + "type" : "uint256" + } + ], + "stateMutability" : "view", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "MakerTraits", + "name" : "makerTraits", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "additionalMask", + "type" : "uint256" + } + ], + "name" : "bitsInvalidateForOrder", + "outputs" : [], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "MakerTraits", + "name" : "makerTraits", + "type" : "uint256" + }, + { + "internalType" : "bytes32", + "name" : "orderHash", + "type" : "bytes32" + } + ], + "name" : "cancelOrder", + "outputs" : [], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "MakerTraits[]", + "name" : "makerTraits", + "type" : "uint256[]" + }, + { + "internalType" : "bytes32[]", + "name" : "orderHashes", + "type" : "bytes32[]" + } + ], + "name" : "cancelOrders", + "outputs" : [], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "bytes", + "name" : "predicate", + "type" : "bytes" + } + ], + "name" : "checkPredicate", + "outputs" : [ + { + "internalType" : "bool", + "name" : "", + "type" : "bool" + } + ], + "stateMutability" : "view", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "contract IClipperExchange", + "name" : "clipperExchange", + "type" : "address" + }, + { + "internalType" : "Address", + "name" : "srcToken", + "type" : "uint256" + }, + { + "internalType" : "contract IERC20", + "name" : "dstToken", + "type" : "address" + }, + { + "internalType" : "uint256", + "name" : "inputAmount", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "outputAmount", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "goodUntil", + "type" : "uint256" + }, + { + "internalType" : "bytes32", + "name" : "r", + "type" : "bytes32" + }, + { + "internalType" : "bytes32", + "name" : "vs", + "type" : "bytes32" + } + ], + "name" : "clipperSwap", + "outputs" : [ + { + "internalType" : "uint256", + "name" : "returnAmount", + "type" : "uint256" + } + ], + "stateMutability" : "payable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "contract IClipperExchange", + "name" : "clipperExchange", + "type" : "address" + }, + { + "internalType" : "address payable", + "name" : "recipient", + "type" : "address" + }, + { + "internalType" : "Address", + "name" : "srcToken", + "type" : "uint256" + }, + { + "internalType" : "contract IERC20", + "name" : "dstToken", + "type" : "address" + }, + { + "internalType" : "uint256", + "name" : "inputAmount", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "outputAmount", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "goodUntil", + "type" : "uint256" + }, + { + "internalType" : "bytes32", + "name" : "r", + "type" : "bytes32" + }, + { + "internalType" : "bytes32", + "name" : "vs", + "type" : "bytes32" + } + ], + "name" : "clipperSwapTo", + "outputs" : [ + { + "internalType" : "uint256", + "name" : "returnAmount", + "type" : "uint256" + } + ], + "stateMutability" : "payable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "address", + "name" : "", + "type" : "address" + }, + { + "internalType" : "address", + "name" : "", + "type" : "address" + }, + { + "internalType" : "address", + "name" : "inCoin", + "type" : "address" + }, + { + "internalType" : "uint256", + "name" : "dx", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "", + "type" : "uint256" + } + ], + "name" : "curveSwapCallback", + "outputs" : [], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "inputs" : [], + "name" : "eip712Domain", + "outputs" : [ + { + "internalType" : "bytes1", + "name" : "fields", + "type" : "bytes1" + }, + { + "internalType" : "string", + "name" : "name", + "type" : "string" + }, + { + "internalType" : "string", + "name" : "version", + "type" : "string" + }, + { + "internalType" : "uint256", + "name" : "chainId", + "type" : "uint256" + }, + { + "internalType" : "address", + "name" : "verifyingContract", + "type" : "address" + }, + { + "internalType" : "bytes32", + "name" : "salt", + "type" : "bytes32" + }, + { + "internalType" : "uint256[]", + "name" : "extensions", + "type" : "uint256[]" + } + ], + "stateMutability" : "view", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "address", + "name" : "maker", + "type" : "address" + }, + { + "internalType" : "uint96", + "name" : "series", + "type" : "uint96" + } + ], + "name" : "epoch", + "outputs" : [ + { + "internalType" : "uint256", + "name" : "", + "type" : "uint256" + } + ], + "stateMutability" : "view", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "address", + "name" : "maker", + "type" : "address" + }, + { + "internalType" : "uint256", + "name" : "series", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "makerEpoch", + "type" : "uint256" + } + ], + "name" : "epochEquals", + "outputs" : [ + { + "internalType" : "bool", + "name" : "", + "type" : "bool" + } + ], + "stateMutability" : "view", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "uint256", + "name" : "value", + "type" : "uint256" + }, + { + "internalType" : "bytes", + "name" : "data", + "type" : "bytes" + } + ], + "name" : "eq", + "outputs" : [ + { + "internalType" : "bool", + "name" : "", + "type" : "bool" + } + ], + "stateMutability" : "view", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "uint256", + "name" : "minReturn", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "dex", + "type" : "uint256" + } + ], + "name" : "ethUnoswap", + "outputs" : [ + { + "internalType" : "uint256", + "name" : "returnAmount", + "type" : "uint256" + } + ], + "stateMutability" : "payable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "uint256", + "name" : "minReturn", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "dex", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "dex2", + "type" : "uint256" + } + ], + "name" : "ethUnoswap2", + "outputs" : [ + { + "internalType" : "uint256", + "name" : "returnAmount", + "type" : "uint256" + } + ], + "stateMutability" : "payable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "uint256", + "name" : "minReturn", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "dex", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "dex2", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "dex3", + "type" : "uint256" + } + ], + "name" : "ethUnoswap3", + "outputs" : [ + { + "internalType" : "uint256", + "name" : "returnAmount", + "type" : "uint256" + } + ], + "stateMutability" : "payable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "Address", + "name" : "to", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "minReturn", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "dex", + "type" : "uint256" + } + ], + "name" : "ethUnoswapTo", + "outputs" : [ + { + "internalType" : "uint256", + "name" : "returnAmount", + "type" : "uint256" + } + ], + "stateMutability" : "payable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "Address", + "name" : "to", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "minReturn", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "dex", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "dex2", + "type" : "uint256" + } + ], + "name" : "ethUnoswapTo2", + "outputs" : [ + { + "internalType" : "uint256", + "name" : "returnAmount", + "type" : "uint256" + } + ], + "stateMutability" : "payable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "Address", + "name" : "to", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "minReturn", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "dex", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "dex2", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "dex3", + "type" : "uint256" + } + ], + "name" : "ethUnoswapTo3", + "outputs" : [ + { + "internalType" : "uint256", + "name" : "returnAmount", + "type" : "uint256" + } + ], + "stateMutability" : "payable", + "type" : "function" + }, + { + "inputs" : [ + { + "components" : [ + { + "internalType" : "uint256", + "name" : "salt", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "maker", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "receiver", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "makerAsset", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "takerAsset", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "makingAmount", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "takingAmount", + "type" : "uint256" + }, + { + "internalType" : "MakerTraits", + "name" : "makerTraits", + "type" : "uint256" + } + ], + "internalType" : "struct IOrderMixin.Order", + "name" : "order", + "type" : "tuple" + }, + { + "internalType" : "bytes", + "name" : "signature", + "type" : "bytes" + }, + { + "internalType" : "uint256", + "name" : "amount", + "type" : "uint256" + }, + { + "internalType" : "TakerTraits", + "name" : "takerTraits", + "type" : "uint256" + } + ], + "name" : "fillContractOrder", + "outputs" : [ + { + "internalType" : "uint256", + "name" : "", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "", + "type" : "uint256" + }, + { + "internalType" : "bytes32", + "name" : "", + "type" : "bytes32" + } + ], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "inputs" : [ + { + "components" : [ + { + "internalType" : "uint256", + "name" : "salt", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "maker", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "receiver", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "makerAsset", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "takerAsset", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "makingAmount", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "takingAmount", + "type" : "uint256" + }, + { + "internalType" : "MakerTraits", + "name" : "makerTraits", + "type" : "uint256" + } + ], + "internalType" : "struct IOrderMixin.Order", + "name" : "order", + "type" : "tuple" + }, + { + "internalType" : "bytes", + "name" : "signature", + "type" : "bytes" + }, + { + "internalType" : "uint256", + "name" : "amount", + "type" : "uint256" + }, + { + "internalType" : "TakerTraits", + "name" : "takerTraits", + "type" : "uint256" + }, + { + "internalType" : "bytes", + "name" : "args", + "type" : "bytes" + } + ], + "name" : "fillContractOrderArgs", + "outputs" : [ + { + "internalType" : "uint256", + "name" : "", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "", + "type" : "uint256" + }, + { + "internalType" : "bytes32", + "name" : "", + "type" : "bytes32" + } + ], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "inputs" : [ + { + "components" : [ + { + "internalType" : "uint256", + "name" : "salt", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "maker", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "receiver", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "makerAsset", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "takerAsset", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "makingAmount", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "takingAmount", + "type" : "uint256" + }, + { + "internalType" : "MakerTraits", + "name" : "makerTraits", + "type" : "uint256" + } + ], + "internalType" : "struct IOrderMixin.Order", + "name" : "order", + "type" : "tuple" + }, + { + "internalType" : "bytes32", + "name" : "r", + "type" : "bytes32" + }, + { + "internalType" : "bytes32", + "name" : "vs", + "type" : "bytes32" + }, + { + "internalType" : "uint256", + "name" : "amount", + "type" : "uint256" + }, + { + "internalType" : "TakerTraits", + "name" : "takerTraits", + "type" : "uint256" + } + ], + "name" : "fillOrder", + "outputs" : [ + { + "internalType" : "uint256", + "name" : "", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "", + "type" : "uint256" + }, + { + "internalType" : "bytes32", + "name" : "", + "type" : "bytes32" + } + ], + "stateMutability" : "payable", + "type" : "function" + }, + { + "inputs" : [ + { + "components" : [ + { + "internalType" : "uint256", + "name" : "salt", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "maker", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "receiver", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "makerAsset", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "takerAsset", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "makingAmount", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "takingAmount", + "type" : "uint256" + }, + { + "internalType" : "MakerTraits", + "name" : "makerTraits", + "type" : "uint256" + } + ], + "internalType" : "struct IOrderMixin.Order", + "name" : "order", + "type" : "tuple" + }, + { + "internalType" : "bytes32", + "name" : "r", + "type" : "bytes32" + }, + { + "internalType" : "bytes32", + "name" : "vs", + "type" : "bytes32" + }, + { + "internalType" : "uint256", + "name" : "amount", + "type" : "uint256" + }, + { + "internalType" : "TakerTraits", + "name" : "takerTraits", + "type" : "uint256" + }, + { + "internalType" : "bytes", + "name" : "args", + "type" : "bytes" + } + ], + "name" : "fillOrderArgs", + "outputs" : [ + { + "internalType" : "uint256", + "name" : "", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "", + "type" : "uint256" + }, + { + "internalType" : "bytes32", + "name" : "", + "type" : "bytes32" + } + ], + "stateMutability" : "payable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "uint256", + "name" : "value", + "type" : "uint256" + }, + { + "internalType" : "bytes", + "name" : "data", + "type" : "bytes" + } + ], + "name" : "gt", + "outputs" : [ + { + "internalType" : "bool", + "name" : "", + "type" : "bool" + } + ], + "stateMutability" : "view", + "type" : "function" + }, + { + "inputs" : [ + { + "components" : [ + { + "internalType" : "uint256", + "name" : "salt", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "maker", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "receiver", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "makerAsset", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "takerAsset", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "makingAmount", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "takingAmount", + "type" : "uint256" + }, + { + "internalType" : "MakerTraits", + "name" : "makerTraits", + "type" : "uint256" + } + ], + "internalType" : "struct IOrderMixin.Order", + "name" : "order", + "type" : "tuple" + } + ], + "name" : "hashOrder", + "outputs" : [ + { + "internalType" : "bytes32", + "name" : "", + "type" : "bytes32" + } + ], + "stateMutability" : "view", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "uint96", + "name" : "series", + "type" : "uint96" + } + ], + "name" : "increaseEpoch", + "outputs" : [], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "uint256", + "name" : "value", + "type" : "uint256" + }, + { + "internalType" : "bytes", + "name" : "data", + "type" : "bytes" + } + ], + "name" : "lt", + "outputs" : [ + { + "internalType" : "bool", + "name" : "", + "type" : "bool" + } + ], + "stateMutability" : "view", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "bytes", + "name" : "data", + "type" : "bytes" + } + ], + "name" : "not", + "outputs" : [ + { + "internalType" : "bool", + "name" : "", + "type" : "bool" + } + ], + "stateMutability" : "view", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "uint256", + "name" : "offsets", + "type" : "uint256" + }, + { + "internalType" : "bytes", + "name" : "data", + "type" : "bytes" + } + ], + "name" : "or", + "outputs" : [ + { + "internalType" : "bool", + "name" : "", + "type" : "bool" + } + ], + "stateMutability" : "view", + "type" : "function" + }, + { + "inputs" : [], + "name" : "owner", + "outputs" : [ + { + "internalType" : "address", + "name" : "", + "type" : "address" + } + ], + "stateMutability" : "view", + "type" : "function" + }, + { + "inputs" : [], + "name" : "pause", + "outputs" : [], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "inputs" : [], + "name" : "paused", + "outputs" : [ + { + "internalType" : "bool", + "name" : "", + "type" : "bool" + } + ], + "stateMutability" : "view", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "bytes", + "name" : "permit", + "type" : "bytes" + }, + { + "internalType" : "bytes", + "name" : "action", + "type" : "bytes" + } + ], + "name" : "permitAndCall", + "outputs" : [], + "stateMutability" : "payable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "address", + "name" : "maker", + "type" : "address" + }, + { + "internalType" : "bytes32", + "name" : "orderHash", + "type" : "bytes32" + } + ], + "name" : "rawRemainingInvalidatorForOrder", + "outputs" : [ + { + "internalType" : "uint256", + "name" : "", + "type" : "uint256" + } + ], + "stateMutability" : "view", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "address", + "name" : "maker", + "type" : "address" + }, + { + "internalType" : "bytes32", + "name" : "orderHash", + "type" : "bytes32" + } + ], + "name" : "remainingInvalidatorForOrder", + "outputs" : [ + { + "internalType" : "uint256", + "name" : "", + "type" : "uint256" + } + ], + "stateMutability" : "view", + "type" : "function" + }, + { + "inputs" : [], + "name" : "renounceOwnership", + "outputs" : [], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "contract IERC20", + "name" : "token", + "type" : "address" + }, + { + "internalType" : "uint256", + "name" : "amount", + "type" : "uint256" + } + ], + "name" : "rescueFunds", + "outputs" : [], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "address", + "name" : "target", + "type" : "address" + }, + { + "internalType" : "bytes", + "name" : "data", + "type" : "bytes" + } + ], + "name" : "simulate", + "outputs" : [], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "contract IAggregationExecutor", + "name" : "executor", + "type" : "address" + }, + { + "components" : [ + { + "internalType" : "contract IERC20", + "name" : "srcToken", + "type" : "address" + }, + { + "internalType" : "contract IERC20", + "name" : "dstToken", + "type" : "address" + }, + { + "internalType" : "address payable", + "name" : "srcReceiver", + "type" : "address" + }, + { + "internalType" : "address payable", + "name" : "dstReceiver", + "type" : "address" + }, + { + "internalType" : "uint256", + "name" : "amount", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "minReturnAmount", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "flags", + "type" : "uint256" + } + ], + "internalType" : "struct GenericRouter.SwapDescription", + "name" : "desc", + "type" : "tuple" + }, + { + "internalType" : "bytes", + "name" : "data", + "type" : "bytes" + } + ], + "name" : "swap", + "outputs" : [ + { + "internalType" : "uint256", + "name" : "returnAmount", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "spentAmount", + "type" : "uint256" + } + ], + "stateMutability" : "payable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "address", + "name" : "newOwner", + "type" : "address" + } + ], + "name" : "transferOwnership", + "outputs" : [], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "int256", + "name" : "amount0Delta", + "type" : "int256" + }, + { + "internalType" : "int256", + "name" : "amount1Delta", + "type" : "int256" + }, + { + "internalType" : "bytes", + "name" : "", + "type" : "bytes" + } + ], + "name" : "uniswapV3SwapCallback", + "outputs" : [], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "Address", + "name" : "token", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "amount", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "minReturn", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "dex", + "type" : "uint256" + } + ], + "name" : "unoswap", + "outputs" : [ + { + "internalType" : "uint256", + "name" : "returnAmount", + "type" : "uint256" + } + ], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "Address", + "name" : "token", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "amount", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "minReturn", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "dex", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "dex2", + "type" : "uint256" + } + ], + "name" : "unoswap2", + "outputs" : [ + { + "internalType" : "uint256", + "name" : "returnAmount", + "type" : "uint256" + } + ], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "Address", + "name" : "token", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "amount", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "minReturn", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "dex", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "dex2", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "dex3", + "type" : "uint256" + } + ], + "name" : "unoswap3", + "outputs" : [ + { + "internalType" : "uint256", + "name" : "returnAmount", + "type" : "uint256" + } + ], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "Address", + "name" : "to", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "token", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "amount", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "minReturn", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "dex", + "type" : "uint256" + } + ], + "name" : "unoswapTo", + "outputs" : [ + { + "internalType" : "uint256", + "name" : "returnAmount", + "type" : "uint256" + } + ], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "Address", + "name" : "to", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "token", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "amount", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "minReturn", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "dex", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "dex2", + "type" : "uint256" + } + ], + "name" : "unoswapTo2", + "outputs" : [ + { + "internalType" : "uint256", + "name" : "returnAmount", + "type" : "uint256" + } + ], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "Address", + "name" : "to", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "token", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "amount", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "minReturn", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "dex", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "dex2", + "type" : "uint256" + }, + { + "internalType" : "Address", + "name" : "dex3", + "type" : "uint256" + } + ], + "name" : "unoswapTo3", + "outputs" : [ + { + "internalType" : "uint256", + "name" : "returnAmount", + "type" : "uint256" + } + ], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "inputs" : [], + "name" : "unpause", + "outputs" : [], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "stateMutability" : "payable", + "type" : "receive" + } +] diff --git a/tests/ragger/abis/poap.abi.json b/tests/ragger/abis/poap.abi.json new file mode 100644 index 000000000..dba0f0f47 --- /dev/null +++ b/tests/ragger/abis/poap.abi.json @@ -0,0 +1,359 @@ +[ + { + "inputs" : [ + { + "internalType" : "address", + "name" : "_poapContractAddress", + "type" : "address" + }, + { + "internalType" : "address", + "name" : "_validSigner", + "type" : "address" + }, + { + "internalType" : "address payable", + "name" : "_feeReceiver", + "type" : "address" + }, + { + "internalType" : "uint256", + "name" : "_migrationFee", + "type" : "uint256" + } + ], + "stateMutability" : "nonpayable", + "type" : "constructor" + }, + { + "anonymous" : false, + "inputs" : [ + { + "indexed" : true, + "internalType" : "address", + "name" : "previousFeeReceiver", + "type" : "address" + }, + { + "indexed" : true, + "internalType" : "address", + "name" : "newFeeReceiver", + "type" : "address" + } + ], + "name" : "FeeReceiverChange", + "type" : "event" + }, + { + "anonymous" : false, + "inputs" : [ + { + "indexed" : true, + "internalType" : "uint256", + "name" : "previousFeeReceiver", + "type" : "uint256" + }, + { + "indexed" : true, + "internalType" : "uint256", + "name" : "newFeeReceiver", + "type" : "uint256" + } + ], + "name" : "MigrationFeeChange", + "type" : "event" + }, + { + "anonymous" : false, + "inputs" : [ + { + "indexed" : true, + "internalType" : "address", + "name" : "previousOwner", + "type" : "address" + }, + { + "indexed" : true, + "internalType" : "address", + "name" : "newOwner", + "type" : "address" + } + ], + "name" : "OwnershipTransferred", + "type" : "event" + }, + { + "anonymous" : false, + "inputs" : [ + { + "indexed" : false, + "internalType" : "address", + "name" : "account", + "type" : "address" + } + ], + "name" : "Paused", + "type" : "event" + }, + { + "anonymous" : false, + "inputs" : [ + { + "indexed" : false, + "internalType" : "address", + "name" : "account", + "type" : "address" + } + ], + "name" : "Unpaused", + "type" : "event" + }, + { + "anonymous" : false, + "inputs" : [ + { + "indexed" : true, + "internalType" : "address", + "name" : "previousValidSigner", + "type" : "address" + }, + { + "indexed" : true, + "internalType" : "address", + "name" : "newValidSigner", + "type" : "address" + } + ], + "name" : "ValidSignerChange", + "type" : "event" + }, + { + "anonymous" : false, + "inputs" : [ + { + "indexed" : false, + "internalType" : "bytes", + "name" : "_signature", + "type" : "bytes" + } + ], + "name" : "VerifiedSignature", + "type" : "event" + }, + { + "inputs" : [], + "name" : "NAME", + "outputs" : [ + { + "internalType" : "string", + "name" : "", + "type" : "string" + } + ], + "stateMutability" : "view", + "type" : "function" + }, + { + "inputs" : [], + "name" : "feeReceiver", + "outputs" : [ + { + "internalType" : "address payable", + "name" : "", + "type" : "address" + } + ], + "stateMutability" : "view", + "type" : "function" + }, + { + "inputs" : [], + "name" : "migrationFee", + "outputs" : [ + { + "internalType" : "uint256", + "name" : "", + "type" : "uint256" + } + ], + "stateMutability" : "view", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "uint256", + "name" : "eventId", + "type" : "uint256" + }, + { + "internalType" : "uint256", + "name" : "tokenId", + "type" : "uint256" + }, + { + "internalType" : "address", + "name" : "receiver", + "type" : "address" + }, + { + "internalType" : "uint256", + "name" : "expirationTime", + "type" : "uint256" + }, + { + "internalType" : "bytes", + "name" : "signature", + "type" : "bytes" + } + ], + "name" : "mintToken", + "outputs" : [ + { + "internalType" : "bool", + "name" : "", + "type" : "bool" + } + ], + "stateMutability" : "payable", + "type" : "function" + }, + { + "inputs" : [], + "name" : "owner", + "outputs" : [ + { + "internalType" : "address", + "name" : "", + "type" : "address" + } + ], + "stateMutability" : "view", + "type" : "function" + }, + { + "inputs" : [], + "name" : "pause", + "outputs" : [], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "inputs" : [], + "name" : "paused", + "outputs" : [ + { + "internalType" : "bool", + "name" : "", + "type" : "bool" + } + ], + "stateMutability" : "view", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "bytes", + "name" : "", + "type" : "bytes" + } + ], + "name" : "processed", + "outputs" : [ + { + "internalType" : "bool", + "name" : "", + "type" : "bool" + } + ], + "stateMutability" : "view", + "type" : "function" + }, + { + "inputs" : [], + "name" : "renounceOwnership", + "outputs" : [], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "inputs" : [], + "name" : "renouncePoapAdmin", + "outputs" : [], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "address payable", + "name" : "_feeReceiver", + "type" : "address" + } + ], + "name" : "setFeeReceiver", + "outputs" : [], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "uint256", + "name" : "_migrationFee", + "type" : "uint256" + } + ], + "name" : "setMigrationFee", + "outputs" : [], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "address", + "name" : "_validSigner", + "type" : "address" + } + ], + "name" : "setValidSigner", + "outputs" : [], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "inputs" : [ + { + "internalType" : "address", + "name" : "newOwner", + "type" : "address" + } + ], + "name" : "transferOwnership", + "outputs" : [], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "inputs" : [], + "name" : "unpause", + "outputs" : [], + "stateMutability" : "nonpayable", + "type" : "function" + }, + { + "inputs" : [], + "name" : "validSigner", + "outputs" : [ + { + "internalType" : "address", + "name" : "", + "type" : "address" + } + ], + "stateMutability" : "view", + "type" : "function" + } +] diff --git a/tests/ragger/snapshots/flex/test_1inch/00000.png b/tests/ragger/snapshots/flex/test_1inch/00000.png new file mode 100644 index 000000000..bb8112a26 Binary files /dev/null and b/tests/ragger/snapshots/flex/test_1inch/00000.png differ diff --git a/tests/ragger/snapshots/flex/test_1inch/00001.png b/tests/ragger/snapshots/flex/test_1inch/00001.png new file mode 100644 index 000000000..df3135811 Binary files /dev/null and b/tests/ragger/snapshots/flex/test_1inch/00001.png differ diff --git a/tests/ragger/snapshots/flex/test_1inch/00002.png b/tests/ragger/snapshots/flex/test_1inch/00002.png new file mode 100644 index 000000000..68b268c9f Binary files /dev/null and b/tests/ragger/snapshots/flex/test_1inch/00002.png differ diff --git a/tests/ragger/snapshots/flex/test_1inch/00003.png b/tests/ragger/snapshots/flex/test_1inch/00003.png new file mode 100644 index 000000000..e1a36532a Binary files /dev/null and b/tests/ragger/snapshots/flex/test_1inch/00003.png differ diff --git a/tests/ragger/snapshots/flex/test_1inch/00004.png b/tests/ragger/snapshots/flex/test_1inch/00004.png new file mode 100644 index 000000000..be51a9d55 Binary files /dev/null and b/tests/ragger/snapshots/flex/test_1inch/00004.png differ diff --git a/tests/ragger/snapshots/flex/test_1inch/00005.png b/tests/ragger/snapshots/flex/test_1inch/00005.png new file mode 100644 index 000000000..dabe7afea Binary files /dev/null and b/tests/ragger/snapshots/flex/test_1inch/00005.png differ diff --git a/tests/ragger/snapshots/flex/test_nft/00000.png b/tests/ragger/snapshots/flex/test_nft/00000.png new file mode 100644 index 000000000..dcae9dcaf Binary files /dev/null and b/tests/ragger/snapshots/flex/test_nft/00000.png differ diff --git a/tests/ragger/snapshots/flex/test_nft/00001.png b/tests/ragger/snapshots/flex/test_nft/00001.png new file mode 100644 index 000000000..a734438ee Binary files /dev/null and b/tests/ragger/snapshots/flex/test_nft/00001.png differ diff --git a/tests/ragger/snapshots/flex/test_nft/00002.png b/tests/ragger/snapshots/flex/test_nft/00002.png new file mode 100644 index 000000000..d0da7df40 Binary files /dev/null and b/tests/ragger/snapshots/flex/test_nft/00002.png differ diff --git a/tests/ragger/snapshots/flex/test_nft/00003.png b/tests/ragger/snapshots/flex/test_nft/00003.png new file mode 100644 index 000000000..f1eacdc13 Binary files /dev/null and b/tests/ragger/snapshots/flex/test_nft/00003.png differ diff --git a/tests/ragger/snapshots/flex/test_nft/00004.png b/tests/ragger/snapshots/flex/test_nft/00004.png new file mode 100644 index 000000000..3ade03afd Binary files /dev/null and b/tests/ragger/snapshots/flex/test_nft/00004.png differ diff --git a/tests/ragger/snapshots/flex/test_nft/00005.png b/tests/ragger/snapshots/flex/test_nft/00005.png new file mode 100644 index 000000000..b2c5fa98c Binary files /dev/null and b/tests/ragger/snapshots/flex/test_nft/00005.png differ diff --git a/tests/ragger/snapshots/flex/test_nft/00006.png b/tests/ragger/snapshots/flex/test_nft/00006.png new file mode 100644 index 000000000..be51a9d55 Binary files /dev/null and b/tests/ragger/snapshots/flex/test_nft/00006.png differ diff --git a/tests/ragger/snapshots/flex/test_nft/00007.png b/tests/ragger/snapshots/flex/test_nft/00007.png new file mode 100644 index 000000000..dabe7afea Binary files /dev/null and b/tests/ragger/snapshots/flex/test_nft/00007.png differ diff --git a/tests/ragger/snapshots/flex/test_poap/00000.png b/tests/ragger/snapshots/flex/test_poap/00000.png new file mode 100644 index 000000000..763cb990e Binary files /dev/null and b/tests/ragger/snapshots/flex/test_poap/00000.png differ diff --git a/tests/ragger/snapshots/flex/test_poap/00001.png b/tests/ragger/snapshots/flex/test_poap/00001.png new file mode 100644 index 000000000..782f76dc8 Binary files /dev/null and b/tests/ragger/snapshots/flex/test_poap/00001.png differ diff --git a/tests/ragger/snapshots/flex/test_poap/00002.png b/tests/ragger/snapshots/flex/test_poap/00002.png new file mode 100644 index 000000000..aa24355a9 Binary files /dev/null and b/tests/ragger/snapshots/flex/test_poap/00002.png differ diff --git a/tests/ragger/snapshots/flex/test_poap/00003.png b/tests/ragger/snapshots/flex/test_poap/00003.png new file mode 100644 index 000000000..0e2b5aa42 Binary files /dev/null and b/tests/ragger/snapshots/flex/test_poap/00003.png differ diff --git a/tests/ragger/snapshots/flex/test_poap/00004.png b/tests/ragger/snapshots/flex/test_poap/00004.png new file mode 100644 index 000000000..1913ce7c4 Binary files /dev/null and b/tests/ragger/snapshots/flex/test_poap/00004.png differ diff --git a/tests/ragger/snapshots/flex/test_poap/00005.png b/tests/ragger/snapshots/flex/test_poap/00005.png new file mode 100644 index 000000000..be51a9d55 Binary files /dev/null and b/tests/ragger/snapshots/flex/test_poap/00005.png differ diff --git a/tests/ragger/snapshots/flex/test_poap/00006.png b/tests/ragger/snapshots/flex/test_poap/00006.png new file mode 100644 index 000000000..dabe7afea Binary files /dev/null and b/tests/ragger/snapshots/flex/test_poap/00006.png differ diff --git a/tests/ragger/snapshots/nanosp/test_1inch/00000.png b/tests/ragger/snapshots/nanosp/test_1inch/00000.png new file mode 100644 index 000000000..44ea8bf93 Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_1inch/00000.png differ diff --git a/tests/ragger/snapshots/nanosp/test_1inch/00001.png b/tests/ragger/snapshots/nanosp/test_1inch/00001.png new file mode 100644 index 000000000..a9deb912b Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_1inch/00001.png differ diff --git a/tests/ragger/snapshots/nanosp/test_1inch/00002.png b/tests/ragger/snapshots/nanosp/test_1inch/00002.png new file mode 100644 index 000000000..82fbba418 Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_1inch/00002.png differ diff --git a/tests/ragger/snapshots/nanosp/test_1inch/00003.png b/tests/ragger/snapshots/nanosp/test_1inch/00003.png new file mode 100644 index 000000000..726dfe742 Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_1inch/00003.png differ diff --git a/tests/ragger/snapshots/nanosp/test_1inch/00004.png b/tests/ragger/snapshots/nanosp/test_1inch/00004.png new file mode 100644 index 000000000..3a6db3d75 Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_1inch/00004.png differ diff --git a/tests/ragger/snapshots/nanosp/test_1inch/00005.png b/tests/ragger/snapshots/nanosp/test_1inch/00005.png new file mode 100644 index 000000000..89b78dba9 Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_1inch/00005.png differ diff --git a/tests/ragger/snapshots/nanosp/test_1inch/00006.png b/tests/ragger/snapshots/nanosp/test_1inch/00006.png new file mode 100644 index 000000000..63db7647f Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_1inch/00006.png differ diff --git a/tests/ragger/snapshots/nanosp/test_1inch/00007.png b/tests/ragger/snapshots/nanosp/test_1inch/00007.png new file mode 100644 index 000000000..a58590b98 Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_1inch/00007.png differ diff --git a/tests/ragger/snapshots/nanosp/test_nft/00000.png b/tests/ragger/snapshots/nanosp/test_nft/00000.png new file mode 100644 index 000000000..550764e7e Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_nft/00000.png differ diff --git a/tests/ragger/snapshots/nanosp/test_nft/00001.png b/tests/ragger/snapshots/nanosp/test_nft/00001.png new file mode 100644 index 000000000..a9deb912b Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_nft/00001.png differ diff --git a/tests/ragger/snapshots/nanosp/test_nft/00002.png b/tests/ragger/snapshots/nanosp/test_nft/00002.png new file mode 100644 index 000000000..1ac2ab077 Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_nft/00002.png differ diff --git a/tests/ragger/snapshots/nanosp/test_nft/00003.png b/tests/ragger/snapshots/nanosp/test_nft/00003.png new file mode 100644 index 000000000..330755f9f Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_nft/00003.png differ diff --git a/tests/ragger/snapshots/nanosp/test_nft/00004.png b/tests/ragger/snapshots/nanosp/test_nft/00004.png new file mode 100644 index 000000000..5a3be0cc4 Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_nft/00004.png differ diff --git a/tests/ragger/snapshots/nanosp/test_nft/00005.png b/tests/ragger/snapshots/nanosp/test_nft/00005.png new file mode 100644 index 000000000..d32af33c7 Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_nft/00005.png differ diff --git a/tests/ragger/snapshots/nanosp/test_nft/00006.png b/tests/ragger/snapshots/nanosp/test_nft/00006.png new file mode 100644 index 000000000..20231dd47 Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_nft/00006.png differ diff --git a/tests/ragger/snapshots/nanosp/test_nft/00007.png b/tests/ragger/snapshots/nanosp/test_nft/00007.png new file mode 100644 index 000000000..f79d5f8db Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_nft/00007.png differ diff --git a/tests/ragger/snapshots/nanosp/test_nft/00008.png b/tests/ragger/snapshots/nanosp/test_nft/00008.png new file mode 100644 index 000000000..72505b863 Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_nft/00008.png differ diff --git a/tests/ragger/snapshots/nanosp/test_nft/00009.png b/tests/ragger/snapshots/nanosp/test_nft/00009.png new file mode 100644 index 000000000..a1611760d Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_nft/00009.png differ diff --git a/tests/ragger/snapshots/nanosp/test_nft/00010.png b/tests/ragger/snapshots/nanosp/test_nft/00010.png new file mode 100644 index 000000000..33d608bb9 Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_nft/00010.png differ diff --git a/tests/ragger/snapshots/nanosp/test_nft/00011.png b/tests/ragger/snapshots/nanosp/test_nft/00011.png new file mode 100644 index 000000000..a15e6bed2 Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_nft/00011.png differ diff --git a/tests/ragger/snapshots/nanosp/test_nft/00012.png b/tests/ragger/snapshots/nanosp/test_nft/00012.png new file mode 100644 index 000000000..52ee539dd Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_nft/00012.png differ diff --git a/tests/ragger/snapshots/nanosp/test_nft/00013.png b/tests/ragger/snapshots/nanosp/test_nft/00013.png new file mode 100644 index 000000000..89b78dba9 Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_nft/00013.png differ diff --git a/tests/ragger/snapshots/nanosp/test_nft/00014.png b/tests/ragger/snapshots/nanosp/test_nft/00014.png new file mode 100644 index 000000000..63db7647f Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_nft/00014.png differ diff --git a/tests/ragger/snapshots/nanosp/test_nft/00015.png b/tests/ragger/snapshots/nanosp/test_nft/00015.png new file mode 100644 index 000000000..a58590b98 Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_nft/00015.png differ diff --git a/tests/ragger/snapshots/nanosp/test_poap/00000.png b/tests/ragger/snapshots/nanosp/test_poap/00000.png new file mode 100644 index 000000000..2c10bec45 Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_poap/00000.png differ diff --git a/tests/ragger/snapshots/nanosp/test_poap/00001.png b/tests/ragger/snapshots/nanosp/test_poap/00001.png new file mode 100644 index 000000000..a9deb912b Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_poap/00001.png differ diff --git a/tests/ragger/snapshots/nanosp/test_poap/00002.png b/tests/ragger/snapshots/nanosp/test_poap/00002.png new file mode 100644 index 000000000..d68074544 Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_poap/00002.png differ diff --git a/tests/ragger/snapshots/nanosp/test_poap/00003.png b/tests/ragger/snapshots/nanosp/test_poap/00003.png new file mode 100644 index 000000000..d457393f7 Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_poap/00003.png differ diff --git a/tests/ragger/snapshots/nanosp/test_poap/00004.png b/tests/ragger/snapshots/nanosp/test_poap/00004.png new file mode 100644 index 000000000..2d6d92398 Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_poap/00004.png differ diff --git a/tests/ragger/snapshots/nanosp/test_poap/00005.png b/tests/ragger/snapshots/nanosp/test_poap/00005.png new file mode 100644 index 000000000..1c86e8202 Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_poap/00005.png differ diff --git a/tests/ragger/snapshots/nanosp/test_poap/00006.png b/tests/ragger/snapshots/nanosp/test_poap/00006.png new file mode 100644 index 000000000..cf2294404 Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_poap/00006.png differ diff --git a/tests/ragger/snapshots/nanosp/test_poap/00007.png b/tests/ragger/snapshots/nanosp/test_poap/00007.png new file mode 100644 index 000000000..86740bb5e Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_poap/00007.png differ diff --git a/tests/ragger/snapshots/nanosp/test_poap/00008.png b/tests/ragger/snapshots/nanosp/test_poap/00008.png new file mode 100644 index 000000000..7bb152b2b Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_poap/00008.png differ diff --git a/tests/ragger/snapshots/nanosp/test_poap/00009.png b/tests/ragger/snapshots/nanosp/test_poap/00009.png new file mode 100644 index 000000000..89b78dba9 Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_poap/00009.png differ diff --git a/tests/ragger/snapshots/nanosp/test_poap/00010.png b/tests/ragger/snapshots/nanosp/test_poap/00010.png new file mode 100644 index 000000000..63db7647f Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_poap/00010.png differ diff --git a/tests/ragger/snapshots/nanosp/test_poap/00011.png b/tests/ragger/snapshots/nanosp/test_poap/00011.png new file mode 100644 index 000000000..a58590b98 Binary files /dev/null and b/tests/ragger/snapshots/nanosp/test_poap/00011.png differ diff --git a/tests/ragger/snapshots/nanox/test_1inch/00000.png b/tests/ragger/snapshots/nanox/test_1inch/00000.png new file mode 100644 index 000000000..44ea8bf93 Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_1inch/00000.png differ diff --git a/tests/ragger/snapshots/nanox/test_1inch/00001.png b/tests/ragger/snapshots/nanox/test_1inch/00001.png new file mode 100644 index 000000000..a9deb912b Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_1inch/00001.png differ diff --git a/tests/ragger/snapshots/nanox/test_1inch/00002.png b/tests/ragger/snapshots/nanox/test_1inch/00002.png new file mode 100644 index 000000000..82fbba418 Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_1inch/00002.png differ diff --git a/tests/ragger/snapshots/nanox/test_1inch/00003.png b/tests/ragger/snapshots/nanox/test_1inch/00003.png new file mode 100644 index 000000000..726dfe742 Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_1inch/00003.png differ diff --git a/tests/ragger/snapshots/nanox/test_1inch/00004.png b/tests/ragger/snapshots/nanox/test_1inch/00004.png new file mode 100644 index 000000000..3a6db3d75 Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_1inch/00004.png differ diff --git a/tests/ragger/snapshots/nanox/test_1inch/00005.png b/tests/ragger/snapshots/nanox/test_1inch/00005.png new file mode 100644 index 000000000..89b78dba9 Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_1inch/00005.png differ diff --git a/tests/ragger/snapshots/nanox/test_1inch/00006.png b/tests/ragger/snapshots/nanox/test_1inch/00006.png new file mode 100644 index 000000000..63db7647f Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_1inch/00006.png differ diff --git a/tests/ragger/snapshots/nanox/test_1inch/00007.png b/tests/ragger/snapshots/nanox/test_1inch/00007.png new file mode 100644 index 000000000..a58590b98 Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_1inch/00007.png differ diff --git a/tests/ragger/snapshots/nanox/test_nft/00000.png b/tests/ragger/snapshots/nanox/test_nft/00000.png new file mode 100644 index 000000000..550764e7e Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_nft/00000.png differ diff --git a/tests/ragger/snapshots/nanox/test_nft/00001.png b/tests/ragger/snapshots/nanox/test_nft/00001.png new file mode 100644 index 000000000..a9deb912b Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_nft/00001.png differ diff --git a/tests/ragger/snapshots/nanox/test_nft/00002.png b/tests/ragger/snapshots/nanox/test_nft/00002.png new file mode 100644 index 000000000..1ac2ab077 Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_nft/00002.png differ diff --git a/tests/ragger/snapshots/nanox/test_nft/00003.png b/tests/ragger/snapshots/nanox/test_nft/00003.png new file mode 100644 index 000000000..330755f9f Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_nft/00003.png differ diff --git a/tests/ragger/snapshots/nanox/test_nft/00004.png b/tests/ragger/snapshots/nanox/test_nft/00004.png new file mode 100644 index 000000000..5a3be0cc4 Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_nft/00004.png differ diff --git a/tests/ragger/snapshots/nanox/test_nft/00005.png b/tests/ragger/snapshots/nanox/test_nft/00005.png new file mode 100644 index 000000000..d32af33c7 Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_nft/00005.png differ diff --git a/tests/ragger/snapshots/nanox/test_nft/00006.png b/tests/ragger/snapshots/nanox/test_nft/00006.png new file mode 100644 index 000000000..20231dd47 Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_nft/00006.png differ diff --git a/tests/ragger/snapshots/nanox/test_nft/00007.png b/tests/ragger/snapshots/nanox/test_nft/00007.png new file mode 100644 index 000000000..f79d5f8db Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_nft/00007.png differ diff --git a/tests/ragger/snapshots/nanox/test_nft/00008.png b/tests/ragger/snapshots/nanox/test_nft/00008.png new file mode 100644 index 000000000..72505b863 Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_nft/00008.png differ diff --git a/tests/ragger/snapshots/nanox/test_nft/00009.png b/tests/ragger/snapshots/nanox/test_nft/00009.png new file mode 100644 index 000000000..a1611760d Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_nft/00009.png differ diff --git a/tests/ragger/snapshots/nanox/test_nft/00010.png b/tests/ragger/snapshots/nanox/test_nft/00010.png new file mode 100644 index 000000000..33d608bb9 Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_nft/00010.png differ diff --git a/tests/ragger/snapshots/nanox/test_nft/00011.png b/tests/ragger/snapshots/nanox/test_nft/00011.png new file mode 100644 index 000000000..a15e6bed2 Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_nft/00011.png differ diff --git a/tests/ragger/snapshots/nanox/test_nft/00012.png b/tests/ragger/snapshots/nanox/test_nft/00012.png new file mode 100644 index 000000000..52ee539dd Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_nft/00012.png differ diff --git a/tests/ragger/snapshots/nanox/test_nft/00013.png b/tests/ragger/snapshots/nanox/test_nft/00013.png new file mode 100644 index 000000000..89b78dba9 Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_nft/00013.png differ diff --git a/tests/ragger/snapshots/nanox/test_nft/00014.png b/tests/ragger/snapshots/nanox/test_nft/00014.png new file mode 100644 index 000000000..63db7647f Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_nft/00014.png differ diff --git a/tests/ragger/snapshots/nanox/test_nft/00015.png b/tests/ragger/snapshots/nanox/test_nft/00015.png new file mode 100644 index 000000000..a58590b98 Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_nft/00015.png differ diff --git a/tests/ragger/snapshots/nanox/test_poap/00000.png b/tests/ragger/snapshots/nanox/test_poap/00000.png new file mode 100644 index 000000000..2c10bec45 Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_poap/00000.png differ diff --git a/tests/ragger/snapshots/nanox/test_poap/00001.png b/tests/ragger/snapshots/nanox/test_poap/00001.png new file mode 100644 index 000000000..a9deb912b Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_poap/00001.png differ diff --git a/tests/ragger/snapshots/nanox/test_poap/00002.png b/tests/ragger/snapshots/nanox/test_poap/00002.png new file mode 100644 index 000000000..d68074544 Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_poap/00002.png differ diff --git a/tests/ragger/snapshots/nanox/test_poap/00003.png b/tests/ragger/snapshots/nanox/test_poap/00003.png new file mode 100644 index 000000000..d457393f7 Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_poap/00003.png differ diff --git a/tests/ragger/snapshots/nanox/test_poap/00004.png b/tests/ragger/snapshots/nanox/test_poap/00004.png new file mode 100644 index 000000000..2d6d92398 Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_poap/00004.png differ diff --git a/tests/ragger/snapshots/nanox/test_poap/00005.png b/tests/ragger/snapshots/nanox/test_poap/00005.png new file mode 100644 index 000000000..1c86e8202 Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_poap/00005.png differ diff --git a/tests/ragger/snapshots/nanox/test_poap/00006.png b/tests/ragger/snapshots/nanox/test_poap/00006.png new file mode 100644 index 000000000..cf2294404 Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_poap/00006.png differ diff --git a/tests/ragger/snapshots/nanox/test_poap/00007.png b/tests/ragger/snapshots/nanox/test_poap/00007.png new file mode 100644 index 000000000..86740bb5e Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_poap/00007.png differ diff --git a/tests/ragger/snapshots/nanox/test_poap/00008.png b/tests/ragger/snapshots/nanox/test_poap/00008.png new file mode 100644 index 000000000..7bb152b2b Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_poap/00008.png differ diff --git a/tests/ragger/snapshots/nanox/test_poap/00009.png b/tests/ragger/snapshots/nanox/test_poap/00009.png new file mode 100644 index 000000000..89b78dba9 Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_poap/00009.png differ diff --git a/tests/ragger/snapshots/nanox/test_poap/00010.png b/tests/ragger/snapshots/nanox/test_poap/00010.png new file mode 100644 index 000000000..63db7647f Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_poap/00010.png differ diff --git a/tests/ragger/snapshots/nanox/test_poap/00011.png b/tests/ragger/snapshots/nanox/test_poap/00011.png new file mode 100644 index 000000000..a58590b98 Binary files /dev/null and b/tests/ragger/snapshots/nanox/test_poap/00011.png differ diff --git a/tests/ragger/snapshots/stax/test_1inch/00000.png b/tests/ragger/snapshots/stax/test_1inch/00000.png new file mode 100644 index 000000000..7034dc469 Binary files /dev/null and b/tests/ragger/snapshots/stax/test_1inch/00000.png differ diff --git a/tests/ragger/snapshots/stax/test_1inch/00001.png b/tests/ragger/snapshots/stax/test_1inch/00001.png new file mode 100644 index 000000000..0560e47b7 Binary files /dev/null and b/tests/ragger/snapshots/stax/test_1inch/00001.png differ diff --git a/tests/ragger/snapshots/stax/test_1inch/00002.png b/tests/ragger/snapshots/stax/test_1inch/00002.png new file mode 100644 index 000000000..82e93431f Binary files /dev/null and b/tests/ragger/snapshots/stax/test_1inch/00002.png differ diff --git a/tests/ragger/snapshots/stax/test_1inch/00003.png b/tests/ragger/snapshots/stax/test_1inch/00003.png new file mode 100644 index 000000000..392165d4f Binary files /dev/null and b/tests/ragger/snapshots/stax/test_1inch/00003.png differ diff --git a/tests/ragger/snapshots/stax/test_1inch/00004.png b/tests/ragger/snapshots/stax/test_1inch/00004.png new file mode 100644 index 000000000..339db1b45 Binary files /dev/null and b/tests/ragger/snapshots/stax/test_1inch/00004.png differ diff --git a/tests/ragger/snapshots/stax/test_nft/00000.png b/tests/ragger/snapshots/stax/test_nft/00000.png new file mode 100644 index 000000000..b0958508a Binary files /dev/null and b/tests/ragger/snapshots/stax/test_nft/00000.png differ diff --git a/tests/ragger/snapshots/stax/test_nft/00001.png b/tests/ragger/snapshots/stax/test_nft/00001.png new file mode 100644 index 000000000..9c132ca35 Binary files /dev/null and b/tests/ragger/snapshots/stax/test_nft/00001.png differ diff --git a/tests/ragger/snapshots/stax/test_nft/00002.png b/tests/ragger/snapshots/stax/test_nft/00002.png new file mode 100644 index 000000000..5c983c9d1 Binary files /dev/null and b/tests/ragger/snapshots/stax/test_nft/00002.png differ diff --git a/tests/ragger/snapshots/stax/test_nft/00003.png b/tests/ragger/snapshots/stax/test_nft/00003.png new file mode 100644 index 000000000..2a281fb07 Binary files /dev/null and b/tests/ragger/snapshots/stax/test_nft/00003.png differ diff --git a/tests/ragger/snapshots/stax/test_nft/00004.png b/tests/ragger/snapshots/stax/test_nft/00004.png new file mode 100644 index 000000000..67f63f953 Binary files /dev/null and b/tests/ragger/snapshots/stax/test_nft/00004.png differ diff --git a/tests/ragger/snapshots/stax/test_nft/00005.png b/tests/ragger/snapshots/stax/test_nft/00005.png new file mode 100644 index 000000000..392165d4f Binary files /dev/null and b/tests/ragger/snapshots/stax/test_nft/00005.png differ diff --git a/tests/ragger/snapshots/stax/test_nft/00006.png b/tests/ragger/snapshots/stax/test_nft/00006.png new file mode 100644 index 000000000..339db1b45 Binary files /dev/null and b/tests/ragger/snapshots/stax/test_nft/00006.png differ diff --git a/tests/ragger/snapshots/stax/test_poap/00000.png b/tests/ragger/snapshots/stax/test_poap/00000.png new file mode 100644 index 000000000..751240cb5 Binary files /dev/null and b/tests/ragger/snapshots/stax/test_poap/00000.png differ diff --git a/tests/ragger/snapshots/stax/test_poap/00001.png b/tests/ragger/snapshots/stax/test_poap/00001.png new file mode 100644 index 000000000..f73962481 Binary files /dev/null and b/tests/ragger/snapshots/stax/test_poap/00001.png differ diff --git a/tests/ragger/snapshots/stax/test_poap/00002.png b/tests/ragger/snapshots/stax/test_poap/00002.png new file mode 100644 index 000000000..829c038b4 Binary files /dev/null and b/tests/ragger/snapshots/stax/test_poap/00002.png differ diff --git a/tests/ragger/snapshots/stax/test_poap/00003.png b/tests/ragger/snapshots/stax/test_poap/00003.png new file mode 100644 index 000000000..40dc555ff Binary files /dev/null and b/tests/ragger/snapshots/stax/test_poap/00003.png differ diff --git a/tests/ragger/snapshots/stax/test_poap/00004.png b/tests/ragger/snapshots/stax/test_poap/00004.png new file mode 100644 index 000000000..392165d4f Binary files /dev/null and b/tests/ragger/snapshots/stax/test_poap/00004.png differ diff --git a/tests/ragger/snapshots/stax/test_poap/00005.png b/tests/ragger/snapshots/stax/test_poap/00005.png new file mode 100644 index 000000000..339db1b45 Binary files /dev/null and b/tests/ragger/snapshots/stax/test_poap/00005.png differ diff --git a/tests/ragger/test_gcs.py b/tests/ragger/test_gcs.py new file mode 100644 index 000000000..a05c0c066 --- /dev/null +++ b/tests/ragger/test_gcs.py @@ -0,0 +1,544 @@ +import struct +import json +import hashlib + +from ragger.backend import BackendInterface +from ragger.firmware import Firmware +from ragger.navigator import Navigator +from ragger.navigator.navigation_scenario import NavigateWithScenario + +import pytest +from web3 import Web3 + +import client.response_parser as ResponseParser +from client.client import EthAppClient, SignMode, TrustedNameType, TrustedNameSource +from client.utils import get_selector_from_data +from client.gcs import * + +from constants import ABIS_FOLDER + + +def test_nft(firmware: Firmware, + backend: BackendInterface, + scenario_navigator: NavigateWithScenario, + test_name: str): + app_client = EthAppClient(backend) + + if firmware == Firmware.NANOS: + pytest.skip("Not supported on LNS") + + with open(f"{ABIS_FOLDER}/erc1155.json", encoding="utf-8") as file: + contract = Web3().eth.contract( + abi=json.load(file), + address=None + ) + recipient_addr = bytes.fromhex("1111111111111111111111111111111111111111") + data = contract.encode_abi("safeBatchTransferFrom", [ + bytes.fromhex("Dad77910DbDFdE764fC21FCD4E74D71bBACA6D8D"), + recipient_addr, + [ + 0xff, + 0xffff, + 0xffffff, + 0xffffffff, + ], + [ + 1, + 2, + 3, + 4, + ], + bytes.fromhex("deadbeef1337cafe"), + ]) + tx_params = { + "nonce": 235, + "maxFeePerGas": Web3.to_wei(100, "gwei"), + "maxPriorityFeePerGas": Web3.to_wei(10, "gwei"), + "gas": 44001, + # OpenSea Shared Storefront + "to": bytes.fromhex("495f947276749ce646f68ac8c248420045cb7b5e"), + "data": data, + "chainId": 1 + } + with app_client.sign("m/44'/60'/0'/0/0", tx_params, SignMode.STORE): + pass + + fields = [ + Field( + 1, + "From", + ParamType.RAW, + ParamRaw( + 1, + Value( + 1, + TypeFamily.ADDRESS, + data_path=DataPath( + 1, + [ + PathTuple(0), + PathLeaf(PathLeafType.STATIC), + ] + ), + ) + ) + ), + Field( + 1, + "To", + ParamType.TRUSTED_NAME, + ParamTrustedName( + 1, + Value( + 1, + TypeFamily.ADDRESS, + data_path=DataPath( + 1, + [ + PathTuple(1), + PathLeaf(PathLeafType.STATIC), + ] + ), + ), + [ + TrustedNameType.ACCOUNT, + ], + [ + TrustedNameSource.UD, + TrustedNameSource.ENS, + TrustedNameSource.FN, + ], + ) + ), + Field( + 1, + "NFTs", + ParamType.NFT, + ParamNFT( + 1, + Value( + 1, + TypeFamily.UINT, + type_size=32, + data_path=DataPath( + 1, + [ + PathTuple(2), + PathRef(), + PathArray(1), + PathLeaf(PathLeafType.STATIC), + ] + ), + ), + Value( + 1, + TypeFamily.ADDRESS, + container_path=ContainerPath.TO + ) + ) + ), + Field( + 1, + "Values", + ParamType.RAW, + ParamRaw( + 1, + Value( + 1, + TypeFamily.UINT, + type_size=32, + data_path=DataPath( + 1, + [ + PathTuple(3), + PathRef(), + PathArray(1), + PathLeaf(PathLeafType.STATIC), + ] + ), + ) + ) + ), + Field( + 1, + "Data", + ParamType.RAW, + ParamRaw( + 1, + Value( + 1, + TypeFamily.BYTES, + data_path=DataPath( + 1, + [ + PathTuple(4), + PathRef(), + PathLeaf(PathLeafType.DYNAMIC), + ] + ), + ) + ) + ), + ] + + # compute instructions hash + inst_hash = hashlib.sha3_256() + for field in fields: + inst_hash.update(field.serialize()) + + tx_info = TxInfo( + 1, + tx_params["chainId"], + tx_params["to"], + get_selector_from_data(tx_params["data"]), + inst_hash.digest(), + "batch transfer NFTs", + ) + + app_client.provide_transaction_info(tx_info.serialize()) + challenge = ResponseParser.challenge(app_client.get_challenge().data) + app_client.provide_trusted_name_v2(recipient_addr, + "gerard.eth", + TrustedNameType.ACCOUNT, + TrustedNameSource.ENS, + tx_params["chainId"], + challenge=challenge) + app_client.provide_nft_metadata("OpenSea Shared Storefront", tx_params["to"], tx_params["chainId"]) + + for field in fields: + payload = field.serialize() + app_client.send_raw(0xe0, 0x28, 0x01, 0x00, struct.pack(">H", len(payload)) + payload) + + with app_client.send_raw_async(0xe0, 0x04, 0x00, 0x02, bytes()): + scenario_navigator.review_approve(test_name=test_name, custom_screen_text="Sign transaction") + + +def test_poap(firmware: Firmware, + backend: BackendInterface, + scenario_navigator: NavigateWithScenario, + test_name: str): + app_client = EthAppClient(backend) + + if firmware == Firmware.NANOS: + pytest.skip("Not supported on LNS") + + with open(f"{ABIS_FOLDER}/poap.abi.json", encoding="utf-8") as file: + contract = Web3().eth.contract( + abi=json.load(file), + address=None + ) + data = contract.encode_abi("mintToken", [ + 175676, + 7163978, + bytes.fromhex("Dad77910DbDFdE764fC21FCD4E74D71bBACA6D8D"), + 1730621615, + bytes.fromhex("8991da687cff5300959810a08c4ec183bb2a56dc82f5aac2b24f1106c2d983ac6f7a6b28700a236724d814000d0fd8c395fcf9f87c4424432ebf30c9479201d71c") + ]) + tx_params = { + "nonce": 235, + "maxFeePerGas": Web3.to_wei(100, "gwei"), + "maxPriorityFeePerGas": Web3.to_wei(10, "gwei"), + "gas": 44001, + # PoapBridge + "to": bytes.fromhex("0bb4D3e88243F4A057Db77341e6916B0e449b158"), + "data": data, + "chainId": 1 + } + with app_client.sign("m/44'/60'/0'/0/0", tx_params, SignMode.STORE): + pass + + fields = [ + Field( + 1, + "Event ID", + ParamType.RAW, + ParamRaw( + 1, + Value( + 1, + TypeFamily.UINT, + type_size=32, + data_path=DataPath( + 1, + [ + PathTuple(0), + PathLeaf(PathLeafType.STATIC), + ] + ), + ) + ) + ), + Field( + 1, + "Token ID", + ParamType.RAW, + ParamRaw( + 1, + Value( + 1, + TypeFamily.UINT, + type_size=32, + data_path=DataPath( + 1, + [ + PathTuple(1), + PathLeaf(PathLeafType.STATIC), + ] + ), + ) + ) + ), + Field( + 1, + "Receiver", + ParamType.RAW, + ParamRaw( + 1, + Value( + 1, + TypeFamily.ADDRESS, + data_path=DataPath( + 1, + [ + PathTuple(2), + PathLeaf(PathLeafType.STATIC), + ] + ), + ) + ) + ), + Field( + 1, + "Expiration time", + ParamType.DATETIME, + ParamDatetime( + 1, + Value( + 1, + TypeFamily.UINT, + type_size=32, + data_path=DataPath( + 1, + [ + PathTuple(3), + PathLeaf(PathLeafType.STATIC), + ] + ), + ), + DatetimeType.DT_UNIX + ) + ), + Field( + 1, + "Signature", + ParamType.RAW, + ParamRaw( + 1, + Value( + 1, + TypeFamily.BYTES, + data_path=DataPath( + 1, + [ + PathTuple(4), + PathRef(), + PathLeaf(PathLeafType.DYNAMIC), + ] + ), + ) + ) + ), + ] + + # compute instructions hash + inst_hash = hashlib.sha3_256() + for field in fields: + inst_hash.update(field.serialize()) + + tx_info = TxInfo( + 1, + tx_params["chainId"], + tx_params["to"], + get_selector_from_data(tx_params["data"]), + inst_hash.digest(), + "mint POAP", + creator_name="POAP", + creator_legal_name="Proof of Attendance Protocol", + creator_url="poap.xyz", + contract_name="PoapBridge", + deploy_date=1646305200 + ) + + app_client.provide_transaction_info(tx_info.serialize()) + + for field in fields: + payload = field.serialize() + app_client.send_raw(0xe0, 0x28, 0x01, 0x00, struct.pack(">H", len(payload)) + payload) + + with app_client.send_raw_async(0xe0, 0x04, 0x00, 0x02, bytes()): + scenario_navigator.review_approve(test_name=test_name, custom_screen_text="Sign transaction") + + +def test_1inch(firmware: Firmware, + backend: BackendInterface, + scenario_navigator: NavigateWithScenario, + test_name: str): + app_client = EthAppClient(backend) + + if firmware == Firmware.NANOS: + pytest.skip("Not supported on LNS") + + with open(f"{ABIS_FOLDER}/1inch.abi.json", encoding="utf-8") as file: + contract = Web3().eth.contract( + abi=json.load(file), + address=None + ) + data = contract.encode_abi("swap", [ + bytes.fromhex("F313B370D28760b98A2E935E56Be92Feb2c4EC04"), + [ + bytes.fromhex("EeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"), + bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), + bytes.fromhex("F313B370D28760b98A2E935E56Be92Feb2c4EC04"), + bytes.fromhex("Dad77910DbDFdE764fC21FCD4E74D71bBACA6D8D"), + Web3.to_wei(0.22, "ether"), + 682119805, + 0, + ], + bytes(), + ]) + tx_params = { + "nonce": 235, + "maxFeePerGas": Web3.to_wei(100, "gwei"), + "maxPriorityFeePerGas": Web3.to_wei(10, "gwei"), + "gas": 44001, + # Aggregation Router V6 + "to": bytes.fromhex("111111125421cA6dc452d289314280a0f8842A65"), + "data": data, + "chainId": 1 + } + with app_client.sign("m/44'/60'/0'/0/0", tx_params, SignMode.STORE): + pass + + fields = [ + Field( + 1, + "Executor", + ParamType.RAW, + ParamRaw( + 1, + Value( + 1, + TypeFamily.ADDRESS, + data_path=DataPath( + 1, + [ + PathTuple(0), + PathLeaf(PathLeafType.STATIC), + ] + ), + ) + ) + ), + Field( + 1, + "Send", + ParamType.TOKEN_AMOUNT, + ParamTokenAmount( + 1, + Value( + 1, + TypeFamily.UINT, + type_size=32, + data_path=DataPath( + 1, + [ + PathTuple(1), + PathTuple(4), + PathLeaf(PathLeafType.STATIC), + ] + ), + ), + token=Value( + 1, + TypeFamily.ADDRESS, + data_path=DataPath( + 1, + [ + PathTuple(1), + PathTuple(0), + PathLeaf(PathLeafType.STATIC), + ] + ), + ), + native_currency=[ + bytes.fromhex("EeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"), + ], + ) + ), + Field( + 1, + "Receive", + ParamType.TOKEN_AMOUNT, + ParamTokenAmount( + 1, + Value( + 1, + TypeFamily.UINT, + type_size=32, + data_path=DataPath( + 1, + [ + PathTuple(1), + PathTuple(5), + PathLeaf(PathLeafType.STATIC), + ] + ), + ), + token=Value( + 1, + TypeFamily.ADDRESS, + data_path=DataPath( + 1, + [ + PathTuple(1), + PathTuple(1), + PathLeaf(PathLeafType.STATIC), + ] + ), + ), + native_currency=[ + bytes.fromhex("EeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"), + ], + ) + ), + ] + + # compute instructions hash + inst_hash = hashlib.sha3_256() + for field in fields: + inst_hash.update(field.serialize()) + + tx_info = TxInfo( + 1, + tx_params["chainId"], + tx_params["to"], + get_selector_from_data(tx_params["data"]), + inst_hash.digest(), + "swap", + creator_name="1inch", + creator_legal_name="1inch Network", + creator_url="1inch.io", + contract_name="Aggregation Router V6", + deploy_date=1707724800 + ) + + app_client.provide_transaction_info(tx_info.serialize()) + + app_client.provide_token_metadata("USDC", bytes.fromhex("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), 6, 1) + + for field in fields: + payload = field.serialize() + app_client.send_raw(0xe0, 0x28, 0x01, 0x00, struct.pack(">H", len(payload)) + payload) + + with app_client.send_raw_async(0xe0, 0x04, 0x00, 0x02, bytes()): + scenario_navigator.review_approve(test_name=test_name, custom_screen_text="Sign transaction")