diff --git a/Makefile b/Makefile index 01d89c2..866a50c 100644 --- a/Makefile +++ b/Makefile @@ -300,6 +300,16 @@ test: install_tests run_tests unit-test: make -C tests/unit +ifeq ($(TARGET_NAME),TARGET_STAX) + NETWORK_ICONS_FILE = $(GEN_SRC_DIR)/net_icons.gen.c + NETWORK_ICONS_DIR = $(shell dirname "$(NETWORK_ICONS_FILE)") + +$(NETWORK_ICONS_FILE): + $(shell python3 tools/gen_networks.py "$(NETWORK_ICONS_DIR)") + + APP_SOURCE_FILES += $(NETWORK_ICONS_FILE) +endif + # import generic rules from the sdk include $(BOLOS_SDK)/Makefile.rules diff --git a/src_nbgl/network_icons.c b/src_nbgl/network_icons.c new file mode 100644 index 0000000..8f7a453 --- /dev/null +++ b/src_nbgl/network_icons.c @@ -0,0 +1,20 @@ +#include "os_utils.h" +#include "os_pic.h" +#include "net_icons.gen.h" + +/** + * Get the network icon from a given chain ID + * + * Loops onto the generated \ref g_network_icons array until a chain ID matches. + * + * @param[in] chain_id network's chain ID + * @return the network icon if found, \ref NULL otherwise + */ +const nbgl_icon_details_t *get_network_icon_from_chain_id(const uint64_t *chain_id) { + for (size_t i = 0; i < ARRAYLEN(g_network_icons); ++i) { + if ((uint64_t) PIC(g_network_icons[i].chain_id) == *chain_id) { + return PIC(g_network_icons[i].icon); + } + } + return NULL; +} diff --git a/src_nbgl/network_icons.h b/src_nbgl/network_icons.h new file mode 100644 index 0000000..0a85afe --- /dev/null +++ b/src_nbgl/network_icons.h @@ -0,0 +1,9 @@ +#ifndef NETWORK_ICONS_H_ +#define NETWORK_ICONS_H_ + +#include +#include "nbgl_types.h" + +const nbgl_icon_details_t *get_network_icon_from_chain_id(const uint64_t *chain_id); + +#endif // NETWORK_ICONS_H_ diff --git a/src_nbgl/ui_approve_tx.c b/src_nbgl/ui_approve_tx.c index a6253c3..b3ddffb 100644 --- a/src_nbgl/ui_approve_tx.c +++ b/src_nbgl/ui_approve_tx.c @@ -7,6 +7,7 @@ #include "ui_signing.h" #include "plugins.h" #include "domain_name.h" +#include "network_icons.h" #define TEXT_TX "transaction" // 1 more than actually displayed on screen, because of calculations in StaticReview @@ -190,7 +191,12 @@ static const nbgl_icon_details_t *get_tx_icon(void) { } } } else { - icon = get_app_icon(false); + uint64_t chain_id = get_tx_chain_id(); + if (chain_id == chainConfig->chainId) { + icon = get_app_icon(false); + } else { + icon = get_network_icon_from_chain_id(&chain_id); + } } return icon; } diff --git a/src_nbgl/ui_get_public_key.c b/src_nbgl/ui_get_public_key.c index 0ee3283..b3c4953 100644 --- a/src_nbgl/ui_get_public_key.c +++ b/src_nbgl/ui_get_public_key.c @@ -3,6 +3,7 @@ #include "ui_callbacks.h" #include "ui_nbgl.h" #include "network.h" +#include "network_icons.h" static void cancel_send(void) { io_seproxyhal_touch_address_cancel(NULL); @@ -34,6 +35,8 @@ static void display_addr(void) { } void ui_display_public_key(const uint64_t *chain_id) { + const nbgl_icon_details_t *icon; + // - if a chain_id is given and it's - known, we specify its network name // - unknown, we don't specify anything // - if no chain_id is given we specify the APPNAME (legacy behaviour) @@ -45,14 +48,11 @@ void ui_display_public_key(const uint64_t *chain_id) { sizeof(g_stax_shared_buffer)); strlcat(g_stax_shared_buffer, "\n", sizeof(g_stax_shared_buffer)); } + icon = get_network_icon_from_chain_id(chain_id); } else { strlcat(g_stax_shared_buffer, APPNAME "\n", sizeof(g_stax_shared_buffer)); + icon = get_app_icon(false); } strlcat(g_stax_shared_buffer, "address", sizeof(g_stax_shared_buffer)); - nbgl_useCaseReviewStart(get_app_icon(false), - g_stax_shared_buffer, - NULL, - "Cancel", - display_addr, - reject_addr); + nbgl_useCaseReviewStart(icon, g_stax_shared_buffer, NULL, "Cancel", display_addr, reject_addr); } diff --git a/tests/ragger/test_domain_name.py b/tests/ragger/test_domain_name.py index 8db6fca..0740514 100644 --- a/tests/ragger/test_domain_name.py +++ b/tests/ragger/test_domain_name.py @@ -24,10 +24,12 @@ GAS_PRICE = 13 GAS_LIMIT = 21000 AMOUNT = 1.22 + @pytest.fixture(params=[False, True]) def verbose(request) -> bool: return request.param + def common(app_client: EthAppClient) -> int: if app_client._client.firmware.device == "nanos": pytest.skip("Not supported on LNS") diff --git a/tools/gen_networks.py b/tools/gen_networks.py new file mode 100755 index 0000000..99b378d --- /dev/null +++ b/tools/gen_networks.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 + +import os +import sys +import re +import argparse + + +class Network: + chain_id: int + name: str + ticker: str + + def __init__(self, chain_id: int, name: str, ticker: str): + self.chain_id = chain_id + self.name = name + self.ticker = ticker + + +def get_network_glyph_name(net: Network) -> str: + return "stax_chain_%u_64px" % (net.chain_id) + + +def get_header() -> str: + return """\ +/* + * Generated by %s + */ + +""" % (sys.argv[0]) + + +def gen_icons_array_inc(networks: list[Network], path: str) -> bool: + with open(path + ".h", "w") as out: + print(get_header() + """\ +#ifndef NETWORK_ICONS_GENERATED_H_ +#define NETWORK_ICONS_GENERATED_H_ + +#include +#include "nbgl_types.h" + +typedef struct { + uint64_t chain_id; + const nbgl_icon_details_t *icon; +} network_icon_t; + +extern const network_icon_t g_network_icons[%u]; + +#endif // NETWORK_ICONS_GENERATED_H_ \ +""" % (len(networks)), file=out) + return True + + +def gen_icons_array_src(networks: list[Network], path: str) -> bool: + with open(path + ".c", "w") as out: + print(get_header() + """\ +#include "glyphs.h" +#include "%s.h" + +const network_icon_t g_network_icons[%u] = {\ +""" % (os.path.basename(path), len(networks)), file=out) + + for net in networks: + glyph_name = get_network_glyph_name(net) + if os.path.isfile("glyphs/%s.gif" % (glyph_name)): + print(" "*4, end="", file=out) + print("{.chain_id = %u, .icon = &C_%s}, // %s" % (net.chain_id, + glyph_name, + net.name), + file=out) + + print("};", file=out) + return True + + +def gen_icons_array(networks: list[Network], path: str) -> bool: + path += "/net_icons.gen" + if not gen_icons_array_inc(networks, path) or \ + not gen_icons_array_src(networks, path): + return False + return True + + +def network_icon_exists(net: Network) -> bool: + return os.path.isfile("glyphs/%s.gif" % (get_network_glyph_name(net))) + + +def main(output_dir: str) -> bool: + networks: list[Network] = list() + + # get chain IDs and network names + expr = r"{\.chain_id = ([0-9]*), \.name = \"(.*)\", \.ticker = \"(.*)\"}," + with open("src_common/network.c") as f: + for line in f.readlines(): + line = line.strip() + if line.startswith("{") and line.endswith("},"): + m = re.search(expr, + line) + assert(m.lastindex == 3) + networks.append(Network(int(m.group(1)), + m.group(2), + m.group(3))) + + networks.sort(key=lambda x: x.chain_id) + + if not gen_icons_array(list(filter(network_icon_exists, networks)), + output_dir): + return False + return True + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("OUTPUT_DIR") + args = parser.parse_args() + assert os.path.isdir(args.OUTPUT_DIR) + quit(0 if main(args.OUTPUT_DIR) else 1)