Unify plugin ui with standard UI; add prepareFeeDisplay and prepareChainIdDisplay
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user