diff --git a/src/utils.c b/src/utils.c index ab39c8ec3..7f0d63de3 100644 --- a/src/utils.c +++ b/src/utils.c @@ -47,3 +47,22 @@ bool check_name(const uint8_t *name, uint16_t len) { } return true; } + +/** + * @brief Checks if a string contains only displayable ASCII characters. + * + * This function determines if all characters in the provided string are within the + * range of displayable ASCII characters (from 0x20 to 0x7E). + * + * @param str A pointer to the string to be checked. + * @param len The length of the string to be checked. + */ + +bool is_displayable_ascii(const char *str, size_t len) { + for (size_t i = 0; i < len; i++) { + if (str[i] < 0x20 || str[i] > 0x7E) { + return false; + } + } + return true; +} diff --git a/src/utils.h b/src/utils.h index 17f3eb48f..5814e565e 100644 --- a/src/utils.h +++ b/src/utils.h @@ -8,3 +8,4 @@ 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); bool check_name(const uint8_t *name, uint16_t len); +bool is_displayable_ascii(const char *str, size_t len); \ No newline at end of file diff --git a/src_plugins/erc20/erc20_plugin.c b/src_plugins/erc20/erc20_plugin.c index 9aa523b0e..517161b4c 100644 --- a/src_plugins/erc20/erc20_plugin.c +++ b/src_plugins/erc20/erc20_plugin.c @@ -4,10 +4,13 @@ #include "shared_context.h" #include "plugin_utils.h" #include "common_utils.h" +#include "format.h" +#include "utils.h" typedef enum { ERC20_TRANSFER = 0, ERC20_APPROVE } erc20Selector_t; #define MAX_CONTRACT_NAME_LEN 15 +#define MAX_EXTRA_DATA_SLOTS 2 typedef struct erc20_parameters_t { uint8_t selectorIndex; @@ -16,6 +19,8 @@ typedef struct erc20_parameters_t { char ticker[MAX_TICKER_LEN]; uint8_t decimals; char contract_name[MAX_CONTRACT_NAME_LEN]; + char extra_data[MAX_EXTRA_DATA_SLOTS * 32 + 1]; + uint8_t extra_data_len; } erc20_parameters_t; void erc20_plugin_call(int message, void *parameters) { @@ -23,6 +28,10 @@ void erc20_plugin_call(int message, void *parameters) { case ETH_PLUGIN_INIT_CONTRACT: { ethPluginInitContract_t *msg = (ethPluginInitContract_t *) parameters; erc20_parameters_t *context = (erc20_parameters_t *) msg->pluginContext; + + memset(context->extra_data, 0, sizeof(context->extra_data)); + context->extra_data_len = 0; + // enforce that ETH amount should be 0 if (!allzeroes(msg->pluginSharedRO->txContent->value.value, 32)) { PRINTF("Err: Transaction amount is not 0\n"); @@ -63,8 +72,18 @@ void erc20_plugin_call(int message, void *parameters) { msg->result = ETH_PLUGIN_RESULT_OK; break; default: - PRINTF("Unhandled parameter offset\n"); - msg->result = ETH_PLUGIN_RESULT_ERROR; + if (msg->parameterOffset <= 4 + 32 + MAX_EXTRA_DATA_SLOTS * 32) { + // store extra data for possible later use + size_t extra_data_offset = msg->parameterOffset - (4 + 32 + 32); + memmove(context->extra_data + extra_data_offset, msg->parameter, 32); + context->extra_data[extra_data_offset + 32] = '\0'; + context->extra_data_len += 32; + msg->result = ETH_PLUGIN_RESULT_OK; + } else { + PRINTF("Extra data too long to buffer\n"); + context->extra_data_len = 0; + msg->result = ETH_PLUGIN_RESULT_ERROR; + } break; } } break; @@ -73,15 +92,15 @@ void erc20_plugin_call(int message, void *parameters) { ethPluginFinalize_t *msg = (ethPluginFinalize_t *) parameters; erc20_parameters_t *context = (erc20_parameters_t *) msg->pluginContext; PRINTF("erc20 plugin finalize\n"); - if (context->selectorIndex == ERC20_TRANSFER) { + if (context->selectorIndex == ERC20_TRANSFER & context->extra_data_len == 0) { msg->tokenLookup1 = msg->pluginSharedRO->txContent->destination; msg->amount = context->amount; msg->address = context->destinationAddress; msg->uiType = ETH_UI_TYPE_AMOUNT_ADDRESS; msg->result = ETH_PLUGIN_RESULT_OK; - } else if (context->selectorIndex == ERC20_APPROVE) { + } else { msg->tokenLookup1 = msg->pluginSharedRO->txContent->destination; - msg->numScreens = 2; + msg->numScreens = (context->extra_data_len == 0 ? 2 : 3); msg->uiType = ETH_UI_TYPE_GENERIC; msg->result = ETH_PLUGIN_RESULT_OK; } @@ -104,8 +123,15 @@ void erc20_plugin_call(int message, void *parameters) { case ETH_PLUGIN_QUERY_CONTRACT_ID: { ethQueryContractID_t *msg = (ethQueryContractID_t *) parameters; + erc20_parameters_t *context = (erc20_parameters_t *) msg->pluginContext; + strlcpy(msg->name, "ERC20 token", msg->nameLength); - strlcpy(msg->version, "Approve", msg->versionLength); + + if (context->selectorIndex == ERC20_TRANSFER) { + strlcpy(msg->version, "Send", msg->versionLength); + } else { + strlcpy(msg->version, "Approve", msg->versionLength); + } msg->result = ETH_PLUGIN_RESULT_OK; } break; @@ -114,7 +140,11 @@ void erc20_plugin_call(int message, void *parameters) { erc20_parameters_t *context = (erc20_parameters_t *) msg->pluginContext; switch (msg->screenIndex) { case 0: - strlcpy(msg->title, "Amount", msg->titleLength); + if (context->selectorIndex == ERC20_TRANSFER) { + strlcpy(msg->title, "Send", msg->titleLength); + } else { + strlcpy(msg->title, "Approve", msg->titleLength); + } if (ismaxint(context->amount, sizeof(context->amount))) { strlcpy(msg->msg, "Unlimited ", msg->msgLength); strlcat(msg->msg, context->ticker, msg->msgLength); @@ -132,7 +162,12 @@ void erc20_plugin_call(int message, void *parameters) { msg->result = ETH_PLUGIN_RESULT_OK; break; case 1: - strlcpy(msg->title, "Approve to", msg->titleLength); + if (context->selectorIndex == ERC20_TRANSFER) { + strlcpy(msg->title, "To", msg->titleLength); + } else { + strlcpy(msg->title, "Approve to", msg->titleLength); + } + if (!getEthDisplayableAddress(context->destinationAddress, msg->msg, msg->msgLength, @@ -141,6 +176,52 @@ void erc20_plugin_call(int message, void *parameters) { } msg->result = ETH_PLUGIN_RESULT_OK; break; + case 2: { + uint8_t extra_data_len = strnlen(context->extra_data, context->extra_data_len); + + PRINTF("Extra Data Length %d\n", extra_data_len); + PRINTF("Message Length %d\n", msg->msgLength); + + if (extra_data_len == 0) { + // should not happen + msg->result = ETH_PLUGIN_RESULT_ERROR; + break; + } + + strlcpy(msg->title, "Extra Data", msg->titleLength); + + if (is_displayable_ascii(context->extra_data, extra_data_len)) { + // display as string + PRINTF("Display as ASCII string\n"); + + if (extra_data_len >= msg->msgLength) { + // truncate + extra_data_len = msg->msgLength - 1; + context->extra_data[extra_data_len] = '\0'; + } + strlcpy(msg->msg, context->extra_data, msg->msgLength); + } else { + // display as hex + PRINTF("Display as Hex string\n"); + + if (extra_data_len * 2 + 3 > msg->msgLength) { + // truncate + extra_data_len = (msg->msgLength - 3) / 2; + } + msg->msg[0] = '0'; + msg->msg[1] = 'x'; + if (format_hex((const uint8_t *) context->extra_data, + extra_data_len, + msg->msg + 2, + msg->msgLength - 2) < 0) { + msg->result = ETH_PLUGIN_RESULT_ERROR; + break; + } + } + msg->result = ETH_PLUGIN_RESULT_OK; + break; + } + default: break; }