Unify plugin ui with standard UI; add prepareFeeDisplay and prepareChainIdDisplay

This commit is contained in:
pscott
2021-06-11 20:41:08 +02:00
parent 3fe18f0e18
commit 036091bb63
5 changed files with 184 additions and 209 deletions

View File

@@ -3,6 +3,9 @@ Ethereum application Plugins : Technical Specifications
Ledger Firmware Team <hello@ledger.fr>
Specification version 1.0 - 24th of September 2020
## 2.0
- Add field in pluginSharedRO, breaking change.
## 1.0
- Initial release

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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;
}