diff --git a/doc/ethapp_plugins.asc b/doc/ethapp_plugins.asc index 0c320d6..5b67b68 100644 --- a/doc/ethapp_plugins.asc +++ b/doc/ethapp_plugins.asc @@ -3,6 +3,9 @@ Ethereum application Plugins : Technical Specifications Ledger Firmware Team Specification version 1.0 - 24th of September 2020 +## 2.0 + - Add field in pluginSharedRO, breaking change. + ## 1.0 - Initial release diff --git a/src/eth_plugin_ui.c b/src/eth_plugin_ui.c index 0f23262..681ef66 100644 --- a/src/eth_plugin_ui.c +++ b/src/eth_plugin_ui.c @@ -5,175 +5,11 @@ #include "ui_callbacks.h" #include "eth_plugin_handler.h" #include "ux.h" - -typedef enum { - - PLUGIN_UI_INSIDE = 0, - PLUGIN_UI_OUTSIDE - -} plugin_ui_state_t; - -void computeFees(char *displayBuffer, uint32_t displayBufferSize); - -void plugin_ui_get_id() { - ethQueryContractID_t pluginQueryContractID; - eth_plugin_prepare_query_contract_ID(&pluginQueryContractID, - strings.tmp.tmp, - sizeof(strings.tmp.tmp), - strings.tmp.tmp2, - sizeof(strings.tmp.tmp2)); - // Query the original contract for ID if it's not an internal alias - if (!eth_plugin_call(ETH_PLUGIN_QUERY_CONTRACT_ID, (void *) &pluginQueryContractID)) { - PRINTF("Plugin query contract ID call failed\n"); - io_seproxyhal_touch_tx_cancel(NULL); - } -} - -void plugin_ui_get_item() { - ethQueryContractUI_t pluginQueryContractUI; - eth_plugin_prepare_query_contract_UI(&pluginQueryContractUI, - dataContext.tokenContext.pluginUiCurrentItem, - strings.tmp.tmp, - sizeof(strings.tmp.tmp), - strings.tmp.tmp2, - sizeof(strings.tmp.tmp2)); - if (!eth_plugin_call(ETH_PLUGIN_QUERY_CONTRACT_UI, (void *) &pluginQueryContractUI)) { - PRINTF("Plugin query contract UI call failed\n"); - io_seproxyhal_touch_tx_cancel(NULL); - } -} - -void display_next_plugin_item(bool entering) { - if (entering) { - if (dataContext.tokenContext.pluginUiState == PLUGIN_UI_OUTSIDE) { - dataContext.tokenContext.pluginUiState = PLUGIN_UI_INSIDE; - dataContext.tokenContext.pluginUiCurrentItem = 0; - plugin_ui_get_item(); - ux_flow_next(); - } else { - if (dataContext.tokenContext.pluginUiCurrentItem > 0) { - dataContext.tokenContext.pluginUiCurrentItem--; - plugin_ui_get_item(); - ux_flow_next(); - } else { - dataContext.tokenContext.pluginUiState = PLUGIN_UI_OUTSIDE; - dataContext.tokenContext.pluginUiCurrentItem = 0; - ux_flow_prev(); - } - } - } else { - if (dataContext.tokenContext.pluginUiState == PLUGIN_UI_OUTSIDE) { - dataContext.tokenContext.pluginUiState = PLUGIN_UI_INSIDE; - plugin_ui_get_item(); - ux_flow_prev(); - } else { - if (dataContext.tokenContext.pluginUiCurrentItem < - dataContext.tokenContext.pluginUiMaxItems - 1) { - dataContext.tokenContext.pluginUiCurrentItem++; - plugin_ui_get_item(); - ux_flow_prev(); - // Reset multi page layout to the first page - G_ux.layout_paging.current = 0; -#ifdef TARGET_NANOS - ux_layout_paging_redisplay_by_addr(G_ux.stack_count - 1); -#else - ux_layout_bnnn_paging_redisplay(0); -#endif - } else { - dataContext.tokenContext.pluginUiState = PLUGIN_UI_OUTSIDE; - ux_flow_next(); - } - } - } -} - -void plugin_ui_compute_fees() { - computeFees(strings.common.maxFee, sizeof(strings.common.maxFee)); -} - -// clang-format off -UX_FLOW_DEF_NOCB( - ux_plugin_approval_intro_step, - pnn, - { - &C_icon_eye, - "Review", - "contract call", - }); - -UX_STEP_NOCB_INIT( - ux_plugin_approval_id_step, - bnnn_paging, - plugin_ui_get_id(), - { - .title = strings.tmp.tmp, - .text = strings.tmp.tmp2 - }); - -UX_STEP_INIT( - ux_plugin_approval_before_step, - NULL, - NULL, - { - display_next_plugin_item(true); - }); - -UX_FLOW_DEF_NOCB( - ux_plugin_approval_display_step, - bnnn_paging, - { - .title = strings.tmp.tmp, - .text = strings.tmp.tmp2 - }); - -UX_STEP_INIT( - ux_plugin_approval_after_step, - NULL, - NULL, - { - display_next_plugin_item(false); - }); - -UX_STEP_NOCB_INIT( - ux_plugin_approval_fees_step, - bnnn_paging, - plugin_ui_compute_fees(), - { - .title = "Max Fees", - .text = strings.common.maxFee - }); - -UX_FLOW_DEF_VALID( - ux_plugin_approval_ok_step, - pbb, - io_seproxyhal_touch_tx_ok(NULL), - { - &C_icon_validate_14, - "Accept", - "and send", - }); -UX_FLOW_DEF_VALID( - ux_plugin_approval_cancel_step, - pb, - io_seproxyhal_touch_tx_cancel(NULL), - { - &C_icon_crossmark, - "Reject", - }); -// clang-format on - -UX_FLOW(ux_plugin_approval_flow, - &ux_plugin_approval_intro_step, - &ux_plugin_approval_id_step, - &ux_plugin_approval_before_step, - &ux_plugin_approval_display_step, - &ux_plugin_approval_after_step, - &ux_plugin_approval_fees_step, - &ux_plugin_approval_ok_step, - &ux_plugin_approval_cancel_step); +#include "feature_signTx.h" void plugin_ui_start() { dataContext.tokenContext.pluginUiState = PLUGIN_UI_OUTSIDE; dataContext.tokenContext.pluginUiCurrentItem = 0; - ux_flow_init(0, ux_plugin_approval_flow, NULL); + + ux_approve_tx(true); } diff --git a/src_features/signTx/feature_signTx.h b/src_features/signTx/feature_signTx.h index a25a650..ff9be11 100644 --- a/src_features/signTx/feature_signTx.h +++ b/src_features/signTx/feature_signTx.h @@ -1,4 +1,14 @@ #include "shared_context.h" +typedef enum { + + PLUGIN_UI_INSIDE = 0, + PLUGIN_UI_OUTSIDE + +} plugin_ui_state_t; + customStatus_e customProcessor(txContext_t *context); void finalizeParsing(bool direct); +void prepareFeeDisplay(); +void prepareChainIdDisplay(); +void ux_approve_tx(bool fromPlugin); diff --git a/src_features/signTx/logic_signTx.c b/src_features/signTx/logic_signTx.c index 4c41b1f..dd6f6f8 100644 --- a/src_features/signTx/logic_signTx.c +++ b/src_features/signTx/logic_signTx.c @@ -194,7 +194,8 @@ void reportFinalizeError(bool direct) { } } -void computeFees(const txInt256_t *BEgasPrice, const txInt256_t *BEgasLimit, uint256_t *output) { +// Convert `BEgasPrice` and `BEgasLimit` to Uint256 and then stores the multiplication of both in `output`. +static void computeFees(txInt256_t *BEgasPrice, txInt256_t *BEgasLimit, uint256_t *output) { uint256_t gasPrice = {0}; uint256_t gasLimit = {0}; @@ -213,7 +214,7 @@ void computeFees(const txInt256_t *BEgasPrice, const txInt256_t *BEgasLimit, uin mul256(&gasPrice, &gasLimit, output); } -void feesToString(const uint256_t *rawFee, char *displayBuffer, uint32_t displayBufferSize) { +static void feesToString(uint256_t *rawFee, char *displayBuffer, uint32_t displayBufferSize) { uint8_t *feeTicker = (uint8_t *) PIC(chainConfig->coinName); uint8_t tickerOffset = 0; uint32_t i; @@ -244,13 +245,13 @@ void feesToString(const uint256_t *rawFee, char *displayBuffer, uint32_t display } // Compute the fees, transform it to a string, prepend a ticker to it and copy everything to `displayBuffer`. -void prepareFees(const txInt256_t *BEGasPrice, const txInt256_t *BEGasLimit, char *displayBuffer, uint32_t displayBufferSize) { +void prepareAndCopyFees(txInt256_t *BEGasPrice, txInt256_t *BEGasLimit, char *displayBuffer, uint32_t displayBufferSize) { uint256_t rawFee = {0}; computeFees(BEGasPrice, BEGasLimit, &rawFee); feesToString(&rawFee, displayBuffer, displayBufferSize); } -void prepareAndCopyDetailedFees(char *displayBuffer, uint32_t displayBufferSize) { +static void prepareAndCopyDetailedFees() { uint256_t rawPriorityFee = {0}; uint256_t rawMaxFee = {0}; uint256_t rawBaseFee = {0}; @@ -263,15 +264,44 @@ void prepareAndCopyDetailedFees(char *displayBuffer, uint32_t displayBufferSize) // Transform priorityFee to string (with a ticker). PRINTF("Computing priority fee\n"); - feesToString(&rawPriorityFee, displayBuffer, displayBufferSize); - // Copy it to destination. - compareOrCopy(strings.common.priorityFee, displayBuffer, called_from_swap); + feesToString(&rawPriorityFee, strings.common.priorityFee, sizeof(strings.common.priorityFee)); PRINTF("Computing base fee\n"); // Transform priorityFee to string (with a ticker). - feesToString(&rawBaseFee, displayBuffer, displayBufferSize); - // Copy it to destination. - compareOrCopy(strings.common.maxFee, displayBuffer, called_from_swap); + feesToString(&rawBaseFee, strings.common.maxFee, sizeof(strings.common.maxFee)); +} + +void prepareFeeDisplay() { + if (N_storage.displayFeeDetails) { + prepareAndCopyDetailedFees(); + } else { + prepareAndCopyFees(&tmpContent.txContent.gasprice, &tmpContent.txContent.startgas, strings.common.maxFee, sizeof(strings.common.maxFee)); + } +} + +uint32_t get_chainID() { + uint32_t chain_id = 0; + + if (txContext.txType == LEGACY) { + chain_id = u32_from_BE(txContext.content->v, txContext.content->vLength, true); + } else if (txContext.txType == EIP2930 || txContext.txType == EIP1559) { + chain_id = u32_from_BE(tmpContent.txContent.chainID.value, tmpContent.txContent.chainID.length, true); + } + else { + PRINTF("Txtype `%u` not supported while generating chainID\n", txContext.txType); + } + PRINTF("ChainID: %d\n", chain_id); + return chain_id; +} + +void prepareChainIdDisplay() { + uint32_t chainID = get_chainID(); + uint8_t res = snprintf(strings.common.chainID, sizeof(strings.common.chainID), "%d", chainID); + if (res >= sizeof(strings.common.chainID)) { + // If the return value is higher or equal to the size passed in as parameter, then + // the output was truncated. Return the appropriate error code. + THROW(0x6502); + } } void finalizeParsing(bool direct) { @@ -435,37 +465,12 @@ void finalizeParsing(bool direct) { } // Compute maximum fee if (genericUI) { - if (N_storage.displayFeeDetails) { - prepareAndCopyDetailedFees(displayBuffer, sizeof(displayBuffer)); - } else { - prepareFees(&tmpContent.txContent.gasprice, &tmpContent.txContent.startgas, displayBuffer, sizeof(displayBuffer)); - compareOrCopy(strings.common.maxFee, displayBuffer, called_from_swap); - } + prepareFeeDisplay(); } // Prepare chainID field if (genericUI) { - if (txContext.txType == LEGACY) { - uint32_t id = u32_from_BE(txContext.content->v, txContext.content->vLength, true); - PRINTF("Chain ID: %u\n", id); - uint8_t res = - snprintf(strings.common.chainID, sizeof(strings.common.chainID), "%d", id); - if (res >= sizeof(strings.common.chainID)) { - // If the return value is higher or equal to the size passed in as parameter, then - // the output was truncated. Return the appropriate error code. - THROW(0x6502); - } - } else if (txContext.txType == EIP2930 || txContext.txType == EIP1559) { - uint256_t chainID; - convertUint256BE(tmpContent.txContent.chainID.value, - tmpContent.txContent.chainID.length, - &chainID); - tostring256(&chainID, 10, displayBuffer, sizeof(displayBuffer)); - strncpy(strings.common.chainID, displayBuffer, sizeof(strings.common.chainID)); - } else { - PRINTF("Txtype `%u` not supported while generating chainID\n", txContext.txType); - return; - } + prepareChainIdDisplay(); } bool no_consent; @@ -480,7 +485,7 @@ void finalizeParsing(bool direct) { io_seproxyhal_touch_tx_ok(NULL); } else { if (genericUI) { - ux_approve_tx(tmpContent.txContent.dataPresent); + ux_approve_tx(false); } else { plugin_ui_start(); } diff --git a/src_features/signTx/ui_flow_signTx.c b/src_features/signTx/ui_flow_signTx.c index f88e34a..c1a4fa4 100644 --- a/src_features/signTx/ui_flow_signTx.c +++ b/src_features/signTx/ui_flow_signTx.c @@ -2,6 +2,80 @@ #include "ui_callbacks.h" #include "chainConfig.h" #include "utils.h" +#include "feature_signTx.h" +#include "eth_plugin_handler.h" + +void plugin_ui_get_id() { + ethQueryContractID_t pluginQueryContractID; + eth_plugin_prepare_query_contract_ID(&pluginQueryContractID, + strings.common.fullAddress, + sizeof(strings.common.fullAddress), + strings.common.fullAmount, + sizeof(strings.common.fullAmount)); + // Query the original contract for ID if it's not an internal alias + if (!eth_plugin_call(ETH_PLUGIN_QUERY_CONTRACT_ID, (void *) &pluginQueryContractID)) { + PRINTF("Plugin query contract ID call failed\n"); + io_seproxyhal_touch_tx_cancel(NULL); + } +} + +void plugin_ui_get_item() { + ethQueryContractUI_t pluginQueryContractUI; + eth_plugin_prepare_query_contract_UI(&pluginQueryContractUI, + dataContext.tokenContext.pluginUiCurrentItem, + strings.common.fullAddress, + sizeof(strings.common.fullAddress), + strings.common.fullAmount, + sizeof(strings.common.fullAmount)); + if (!eth_plugin_call(ETH_PLUGIN_QUERY_CONTRACT_UI, (void *) &pluginQueryContractUI)) { + PRINTF("Plugin query contract UI call failed\n"); + io_seproxyhal_touch_tx_cancel(NULL); + } +} + +void display_next_plugin_item(bool entering) { + if (entering) { + if (dataContext.tokenContext.pluginUiState == PLUGIN_UI_OUTSIDE) { + dataContext.tokenContext.pluginUiState = PLUGIN_UI_INSIDE; + dataContext.tokenContext.pluginUiCurrentItem = 0; + plugin_ui_get_item(); + ux_flow_next(); + } else { + if (dataContext.tokenContext.pluginUiCurrentItem > 0) { + dataContext.tokenContext.pluginUiCurrentItem--; + plugin_ui_get_item(); + ux_flow_next(); + } else { + dataContext.tokenContext.pluginUiState = PLUGIN_UI_OUTSIDE; + dataContext.tokenContext.pluginUiCurrentItem = 0; + ux_flow_prev(); + } + } + } else { + if (dataContext.tokenContext.pluginUiState == PLUGIN_UI_OUTSIDE) { + dataContext.tokenContext.pluginUiState = PLUGIN_UI_INSIDE; + plugin_ui_get_item(); + ux_flow_prev(); + } else { + if (dataContext.tokenContext.pluginUiCurrentItem < + dataContext.tokenContext.pluginUiMaxItems - 1) { + dataContext.tokenContext.pluginUiCurrentItem++; + plugin_ui_get_item(); + ux_flow_prev(); + // Reset multi page layout to the first page + G_ux.layout_paging.current = 0; +#ifdef TARGET_NANOS + ux_layout_paging_redisplay_by_addr(G_ux.stack_count - 1); +#else + ux_layout_bnnn_paging_redisplay(0); +#endif + } else { + dataContext.tokenContext.pluginUiState = PLUGIN_UI_OUTSIDE; + ux_flow_next(); + } + } + } +} // clang-format off UX_STEP_NOCB( @@ -108,6 +182,40 @@ UX_STEP_NOCB( .title = "Address", .text = strings.common.fullAddress, }); + +UX_STEP_NOCB_INIT( + ux_plugin_approval_id_step, + bnnn_paging, + plugin_ui_get_id(), + { + .title = strings.common.fullAddress, + .text = strings.common.fullAmount + }); + +UX_STEP_INIT( + ux_plugin_approval_before_step, + NULL, + NULL, + { + display_next_plugin_item(true); + }); + +UX_FLOW_DEF_NOCB( + ux_plugin_approval_display_step, + bnnn_paging, + { + .title = strings.common.fullAddress, + .text = strings.common.fullAmount + }); + +UX_STEP_INIT( + ux_plugin_approval_after_step, + NULL, + NULL, + { + display_next_plugin_item(false); + }); + UX_STEP_NOCB( ux_approval_base_fee_step, bnnn_paging, @@ -136,6 +244,7 @@ UX_STEP_NOCB( .title = "Chain ID", .text = strings.common.chainID, }); + UX_STEP_CB( ux_approval_accept_step, pbb, @@ -171,16 +280,28 @@ UX_STEP_NOCB(ux_approval_data_warning_step, }); // clang-format on -const ux_flow_step_t *ux_approval_tx_flow[12]; +const ux_flow_step_t *ux_approval_tx_flow[15]; -void ux_approve_tx(bool dataPresent) { +void ux_approve_tx(bool fromPlugin) { int step = 0; ux_approval_tx_flow[step++] = &ux_approval_review_step; + if (tmpContent.txContent.dataPresent && !N_storage.contractDetails) { ux_approval_tx_flow[step++] = &ux_approval_data_warning_step; } - ux_approval_tx_flow[step++] = &ux_approval_amount_step; - ux_approval_tx_flow[step++] = &ux_approval_address_step; + + if (fromPlugin) { + prepareChainIdDisplay(); + prepareFeeDisplay(); + ux_approval_tx_flow[step++] = &ux_plugin_approval_id_step; + ux_approval_tx_flow[step++] = &ux_plugin_approval_before_step; + ux_approval_tx_flow[step++] = &ux_plugin_approval_display_step; + ux_approval_tx_flow[step++] = &ux_plugin_approval_after_step; + } else { + ux_approval_tx_flow[step++] = &ux_approval_amount_step; + ux_approval_tx_flow[step++] = &ux_approval_address_step; + } + if (N_storage.displayNonce) { ux_approval_tx_flow[step++] = &ux_approval_nonce_step; }