Add support for ERC-721 and ERC-1155 (v3) (#218)

* First draft for erc721 token allowance

* Split ui and provide parameters into their own files

* Print txtype when not supported

* fix compilation for erc721

* Use pluginType

* Add debug statement in compound plugin

* add debug error msg in plugin error

* Add parameter parsing for all methods

* Remove debug logs

* Add SET_APPROVAL_FOR_ALL; Add correct parsing method on contract init

* Add dst_size parameter to copy functions

* Add query contract id code

* format

* Add UIs

* update ethapp.asc

* Change setExternalPlugin to setPlugin; Add support for ERC721

* clang-format

* Fix typo Unconsistent -> Inconsistent

* Add support for 721; use extraInfo

* Add extraInfo to ethpluginQueryConractUI

* Rename extraInfo to item

* Add txFromEtherscan to tests

* Add nft key and temp padding

* Remove comments around HAVE_BYPASS_SIGNATURES

* Rename TESTING_KEY to NFT_TESTING_KEY

* Add comments regarding value of queryContractUI->item

* Fix comment regarding method selector

* Rename provideToken to provideInfo; Update plugin doc

* fix caps of eth_plugin_prepare_provide_info

* fix caps of handle_provide_info

* Use verificationFn insead of hardcoded cx_ecdsa_verify

* Add comments about nftInfo_t and tokenDefinition_t

* Add erc721 test

* Remove comment from plugin interface version

* Fix network_ticker duplicate

* Add setPlugin and provideNFTInfo to doc.asc

* Add back setExternalPlugin; implement new setPlugin

* Update plugin sdk

* Call setPlugin instead of setExternalPlugin

* setPlugin work without checking sig

* Remove printf of displayed fees

* Add working 721 test

* Finalize ERC721 and add simple test

* Display NFT address on set approval and operator

* Support set approval for all for erc721

* Finish UI for set approval for all erc721

* Move copy_parameter and copy_address to eth_plugin_internal; Add tests for erc721

* update plugin sdk

* Add erc1155 plugin and 1155 tests placeholder

* Add restriction for AWS key and setPlugin

* Add NOT_OLD_INTERNAL variant; Add erc_1155_plugin_call

* Fixed compilation warnings (function pointer casting)

Co-authored-by: pscott <scott.piriou@ledger.fr>
This commit is contained in:
apaillier-ledger
2021-11-22 14:39:36 +01:00
committed by GitHub
parent a490532605
commit fcc3dd6d31
94 changed files with 2026 additions and 349 deletions

View File

@@ -85,6 +85,7 @@ void compound_plugin_call(int message, void *parameters) {
// enforce that ETH amount should be 0, except in ceth.mint case
if (!allzeroes(msg->pluginSharedRO->txContent->value.value, 32)) {
if (context->selectorIndex != CETH_MINT) {
PRINTF("Eth amount is not zero and token minted is not CETH!\n");
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
@@ -148,12 +149,12 @@ void compound_plugin_call(int message, void *parameters) {
msg->result = ETH_PLUGIN_RESULT_OK;
} break;
case ETH_PLUGIN_PROVIDE_TOKEN: {
ethPluginProvideToken_t *msg = (ethPluginProvideToken_t *) parameters;
case ETH_PLUGIN_PROVIDE_INFO: {
ethPluginProvideInfo_t *msg = (ethPluginProvideInfo_t *) parameters;
compound_parameters_t *context = (compound_parameters_t *) msg->pluginContext;
PRINTF("compound plugin provide token: %d\n", (msg->token1 != NULL));
if (msg->token1 != NULL) {
strlcpy(context->ticker_1, msg->token1->ticker, MAX_TICKER_LEN);
PRINTF("compound plugin provide token: %d\n", (msg->item1 != NULL));
if (msg->item1 != NULL) {
strlcpy(context->ticker_1, msg->item1->token.ticker, MAX_TICKER_LEN);
switch (context->selectorIndex) {
case COMPOUND_REDEEM_UNDERLYING:
case COMPOUND_MINT:
@@ -166,7 +167,7 @@ void compound_plugin_call(int message, void *parameters) {
// Only case where we use the compound contract decimals
case COMPOUND_REDEEM:
context->decimals = msg->token1->decimals;
context->decimals = msg->item1->token.decimals;
msg->result = ETH_PLUGIN_RESULT_OK;
break;

View File

@@ -0,0 +1,136 @@
#include "erc1155_plugin.h"
#include "eth_plugin_internal.h"
static const uint8_t ERC1155_APPROVE_FOR_ALL_SELECTOR[SELECTOR_SIZE] = {0xa2, 0x2c, 0xb4, 0x65};
static const uint8_t ERC1155_SAFE_TRANSFER_SELECTOR[SELECTOR_SIZE] = {0xf2, 0x42, 0x43, 0x2a};
static const uint8_t ERC1155_SAFE_BATCH_TRANSFER[SELECTOR_SIZE] = {0xf2, 0x42, 0x43, 0x2a};
const uint8_t *const ERC1155_SELECTORS[NUM_ERC1155_SELECTORS] = {
ERC1155_APPROVE_FOR_ALL_SELECTOR,
ERC1155_SAFE_TRANSFER_SELECTOR,
ERC1155_SAFE_BATCH_TRANSFER,
};
static void handle_init_contract(void *parameters) {
ethPluginInitContract_t *msg = (ethPluginInitContract_t *) parameters;
erc1155_context_t *context = (erc1155_context_t *) msg->pluginContext;
uint8_t i;
for (i = 0; i < NUM_ERC1155_SELECTORS; i++) {
if (memcmp((uint8_t *) PIC(ERC1155_SELECTORS[i]), msg->selector, SELECTOR_SIZE) == 0) {
context->selectorIndex = i;
break;
}
}
// No selector found.
if (i == NUM_ERC1155_SELECTORS) {
PRINTF("Unknown erc1155 selector %.*H\n", SELECTOR_SIZE, msg->selector);
msg->result = ETH_PLUGIN_RESULT_FALLBACK;
return;
}
msg->result = ETH_PLUGIN_RESULT_OK;
switch (context->selectorIndex) {
case SAFE_TRANSFER:
case SAFE_BATCH_TRANSFER:
context->next_param = FROM;
break;
case SET_APPROVAL_FOR_ALL:
context->next_param = OPERATOR;
break;
default:
PRINTF("Unsupported selector index: %d\n", context->selectorIndex);
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}
static void handle_finalize(void *parameters) {
ethPluginFinalize_t *msg = (ethPluginFinalize_t *) parameters;
erc1155_context_t *context = (erc1155_context_t *) msg->pluginContext;
if (context->selectorIndex != SAFE_BATCH_TRANSFER) {
msg->tokenLookup1 = msg->pluginSharedRO->txContent->destination;
} else {
msg->tokenLookup1 = NULL;
}
msg->tokenLookup2 = NULL;
switch (context->selectorIndex) {
case SAFE_TRANSFER:
msg->numScreens = 4;
break;
case SET_APPROVAL_FOR_ALL:
case SAFE_BATCH_TRANSFER:
msg->numScreens = 3;
break;
default:
msg->result = ETH_PLUGIN_RESULT_ERROR;
return;
}
// Check if some ETH is attached to this tx
if (!allzeroes((void *) &msg->pluginSharedRO->txContent->value,
sizeof(msg->pluginSharedRO->txContent->value))) {
// Those functions are not payable so return an error.
msg->result = ETH_PLUGIN_RESULT_ERROR;
return;
}
msg->uiType = ETH_UI_TYPE_GENERIC;
msg->result = ETH_PLUGIN_RESULT_OK;
}
static void handle_provide_info(void *parameters) {
ethPluginProvideInfo_t *msg = (ethPluginProvideInfo_t *) parameters;
msg->result = ETH_PLUGIN_RESULT_OK;
}
static void handle_query_contract_id(void *parameters) {
ethQueryContractID_t *msg = (ethQueryContractID_t *) parameters;
erc1155_context_t *context = (erc1155_context_t *) msg->pluginContext;
msg->result = ETH_PLUGIN_RESULT_OK;
strlcpy(msg->name, "NFT", msg->nameLength);
switch (context->selectorIndex) {
case SET_APPROVAL_FOR_ALL:
strlcpy(msg->version, "Allowance", msg->versionLength);
break;
case SAFE_TRANSFER:
case SAFE_BATCH_TRANSFER:
strlcpy(msg->version, "Transfer", msg->versionLength);
break;
default:
PRINTF("Unsupported selector %d\n", context->selectorIndex);
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}
void erc1155_plugin_call(int message, void *parameters) {
switch (message) {
case ETH_PLUGIN_INIT_CONTRACT: {
handle_init_contract(parameters);
} break;
case ETH_PLUGIN_PROVIDE_PARAMETER: {
handle_provide_parameter_1155(parameters);
} break;
case ETH_PLUGIN_FINALIZE: {
handle_finalize(parameters);
} break;
case ETH_PLUGIN_PROVIDE_INFO: {
handle_provide_info(parameters);
} break;
case ETH_PLUGIN_QUERY_CONTRACT_ID: {
handle_query_contract_id(parameters);
} break;
case ETH_PLUGIN_QUERY_CONTRACT_UI: {
handle_query_contract_ui_1155(parameters);
} break;
default:
PRINTF("Unhandled message %d\n", message);
break;
}
}

View File

@@ -0,0 +1,49 @@
#pragma once
#include <string.h>
#include "eth_plugin_handler.h"
#include "shared_context.h"
#include "ethUtils.h"
#include "utils.h"
// Internal plugin for EIP 1155: https://eips.ethereum.org/EIPS/eip-1155
#define NUM_ERC1155_SELECTORS 3
typedef enum {
SET_APPROVAL_FOR_ALL,
SAFE_TRANSFER,
SAFE_BATCH_TRANSFER,
} erc1155_selector_t;
typedef enum {
FROM,
TO,
TOKEN_IDS_OFFSET,
TOKEN_IDS_LENGTH,
TOKEN_ID,
VALUE_OFFSET,
VALUE_LENGTH,
VALUE,
OPERATOR,
APPROVED,
NONE,
} erc1155_selector_field;
typedef struct erc1155_context_t {
uint8_t address[ADDRESS_LENGTH];
uint8_t tokenId[INT256_LENGTH];
uint8_t value[INT256_LENGTH];
uint32_t valueOffset;
uint32_t tokenIdsOffset;
uint32_t targetOffset;
bool approved;
erc1155_selector_field next_param;
uint8_t selectorIndex;
} erc1155_context_t;
// TODO: Find out why there is a duplicate if we remove 1155 suffix
void handle_provide_parameter_1155(void *parameters);
void handle_query_contract_ui_1155(void *parameters);

View File

@@ -0,0 +1,106 @@
#include "erc1155_plugin.h"
#include "eth_plugin_internal.h"
static void handle_safe_transfer(ethPluginProvideParameter_t *msg, erc1155_context_t *context) {
switch (context->next_param) {
case FROM:
context->next_param = TO;
break;
case TO:
copy_address(context->address, msg->parameter, sizeof(context->address));
context->next_param = TOKEN_ID;
break;
case TOKEN_ID:
copy_parameter(context->tokenId, msg->parameter, sizeof(context->tokenId));
context->next_param = VALUE;
break;
case VALUE:
copy_parameter(context->value, msg->parameter, sizeof(context->value));
context->next_param = NONE;
break;
default:
// Some extra data might be present so don't error.
break;
}
}
static void handle_batch_transfer(ethPluginProvideParameter_t *msg, erc1155_context_t *context) {
switch (context->next_param) {
case FROM:
context->next_param = TO;
break;
case TO:
copy_address(context->address, msg->parameter, sizeof(context->address));
context->next_param = TOKEN_ID;
break;
case TOKEN_IDS_OFFSET:
context->tokenIdsOffset = U4BE(msg->parameter, PARAMETER_LENGTH - 4);
context->next_param = VALUE_OFFSET;
break;
case VALUE_OFFSET:
context->targetOffset = context->tokenIdsOffset;
context->next_param = TOKEN_ID;
break;
case TOKEN_ID:
copy_parameter(context->tokenId, msg->parameter, sizeof(context->tokenId));
context->targetOffset = context->valueOffset;
context->next_param = VALUE;
break;
case VALUE:
copy_parameter(context->value, msg->parameter, sizeof(context->value));
context->targetOffset = 0;
context->next_param = NONE;
default:
// Some extra data might be present so don't error.
break;
}
}
static void handle_approval_for_all(ethPluginProvideParameter_t *msg, erc1155_context_t *context) {
switch (context->next_param) {
case OPERATOR:
context->next_param = APPROVED;
copy_address(context->address, msg->parameter, sizeof(context->address));
break;
case APPROVED:
context->approved = msg->parameter[PARAMETER_LENGTH - 1];
context->next_param = NONE;
break;
default:
PRINTF("Param %d not supported\n", context->next_param);
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}
void handle_provide_parameter_1155(void *parameters) {
ethPluginProvideParameter_t *msg = (ethPluginProvideParameter_t *) parameters;
erc1155_context_t *context = (erc1155_context_t *) msg->pluginContext;
PRINTF("erc1155 plugin provide parameter %d %.*H\n",
msg->parameterOffset,
PARAMETER_LENGTH,
msg->parameter);
msg->result = ETH_PLUGIN_RESULT_SUCCESSFUL;
if (context->targetOffset > SELECTOR_SIZE &&
context->targetOffset != msg->parameterOffset - SELECTOR_SIZE) {
return;
}
switch (context->selectorIndex) {
case SAFE_TRANSFER:
handle_safe_transfer(msg, context);
break;
case SAFE_BATCH_TRANSFER:
handle_batch_transfer(msg, context);
break;
case SET_APPROVAL_FOR_ALL:
handle_approval_for_all(msg, context);
break;
default:
PRINTF("Selector index %d not supported\n", context->selectorIndex);
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}

View File

@@ -0,0 +1,135 @@
#include "erc1155_plugin.h"
static void set_approval_for_all_ui(ethQueryContractUI_t *msg, erc1155_context_t *context) {
switch (msg->screenIndex) {
case 0:
if (context->approved) {
strlcpy(msg->title, "Allow", msg->titleLength);
} else {
strlcpy(msg->title, "Revoke", msg->titleLength);
}
getEthDisplayableAddress(context->address,
msg->msg,
msg->msgLength,
&global_sha3,
chainConfig->chainId);
break;
case 1:
strlcpy(msg->title, "To Manage ALL", msg->titleLength);
if (msg->item1) {
strlcpy(msg->msg, (const char *) &msg->item1->nft.collectionName, msg->msgLength);
} else {
strlcpy(msg->msg, "Not found", msg->msgLength);
}
break;
case 2:
strlcpy(msg->title, "NFT Address", msg->titleLength);
getEthDisplayableAddress(msg->pluginSharedRO->txContent->destination,
msg->msg,
msg->msgLength,
&global_sha3,
chainConfig->chainId);
break;
default:
PRINTF("Unsupported screen index %d\n", msg->screenIndex);
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}
static void set_transfer_ui(ethQueryContractUI_t *msg, erc1155_context_t *context) {
switch (msg->screenIndex) {
case 0:
// What will be displayed on the screen is:
// | Send |
// | `X` `COLLECTION_NAME` |
// where `X` is `value`
strlcpy(msg->title, "Send", msg->titleLength);
uint256_to_decimal(context->value, sizeof(context->value), msg->msg, msg->msgLength);
strlcat(msg->msg, " ", msg->msgLength);
if (msg->item1) {
strlcat(msg->msg, (const char *) &msg->item1->nft.collectionName, msg->msgLength);
} else {
strlcat(msg->msg, "Items", msg->msgLength);
}
break;
case 1:
strlcpy(msg->title, "To", msg->titleLength);
getEthDisplayableAddress(context->address,
msg->msg,
msg->msgLength,
&global_sha3,
chainConfig->chainId);
break;
case 2:
strlcpy(msg->title, "NFT Address", msg->titleLength);
getEthDisplayableAddress(msg->pluginSharedRO->txContent->destination,
msg->msg,
msg->msgLength,
&global_sha3,
chainConfig->chainId);
break;
case 3:
strlcpy(msg->title, "NFT ID", msg->titleLength);
uint256_to_decimal(context->tokenId,
sizeof(context->tokenId),
msg->msg,
msg->msgLength);
break;
default:
PRINTF("Unsupported screen index %d\n", msg->screenIndex);
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}
static void set_batch_transfer_ui(ethQueryContractUI_t *msg, erc1155_context_t *context) {
switch (msg->screenIndex) {
case 0:
strlcpy(msg->title, "Send NFTs From", msg->titleLength);
uint256_to_decimal(context->value, sizeof(context->value), msg->msg, msg->msgLength);
strlcat(msg->msg, " Different Collections", msg->msgLength);
break;
case 1:
strlcpy(msg->title, "To", msg->titleLength);
getEthDisplayableAddress(context->address,
msg->msg,
msg->msgLength,
&global_sha3,
chainConfig->chainId);
case 2:
strlcpy(msg->title, "NFT Address", msg->titleLength);
getEthDisplayableAddress(msg->pluginSharedRO->txContent->destination,
msg->msg,
msg->msgLength,
&global_sha3,
chainConfig->chainId);
break;
default:
PRINTF("Unsupported screen index %d\n", msg->screenIndex);
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}
void handle_query_contract_ui_1155(void *parameters) {
ethQueryContractUI_t *msg = (ethQueryContractUI_t *) parameters;
erc1155_context_t *context = (erc1155_context_t *) msg->pluginContext;
msg->result = ETH_PLUGIN_RESULT_OK;
switch (context->selectorIndex) {
case SET_APPROVAL_FOR_ALL:
set_approval_for_all_ui(msg, context);
break;
case SAFE_TRANSFER:
set_transfer_ui(msg, context);
break;
case SAFE_BATCH_TRANSFER:
set_batch_transfer_ui(msg, context);
break;
default:
msg->result = ETH_PLUGIN_RESULT_ERROR;
PRINTF("Unsupported selector index %d\n", context->selectorIndex);
break;
}
}

View File

@@ -159,16 +159,16 @@ void erc20_plugin_call(int message, void *parameters) {
}
} break;
case ETH_PLUGIN_PROVIDE_TOKEN: {
ethPluginProvideToken_t *msg = (ethPluginProvideToken_t *) parameters;
case ETH_PLUGIN_PROVIDE_INFO: {
ethPluginProvideInfo_t *msg = (ethPluginProvideInfo_t *) parameters;
erc20_parameters_t *context = (erc20_parameters_t *) msg->pluginContext;
PRINTF("erc20 plugin provide token 1: %d - 2: %d\n",
(msg->token1 != NULL),
(msg->token2 != NULL));
if (msg->token1 != NULL) {
(msg->item1 != NULL),
(msg->item2 != NULL));
if (msg->item1 != NULL) {
context->target = TARGET_ADDRESS;
strlcpy(context->ticker, msg->token1->ticker, MAX_TICKER_LEN);
context->decimals = msg->token1->decimals;
strlcpy(context->ticker, msg->item1->token.ticker, MAX_TICKER_LEN);
context->decimals = msg->item1->token.decimals;
if (context->selectorIndex == ERC20_APPROVE) {
if (check_contract(context)) {
context->target = TARGET_CONTRACT;

View File

@@ -1,151 +1,146 @@
#include <string.h>
#include "erc721_plugin.h"
#include "eth_plugin_internal.h"
#include "eth_plugin_handler.h"
#include "shared_context.h"
#include "ethUtils.h"
#include "utils.h"
typedef struct erc721_parameters_t {
uint8_t selectorIndex;
uint8_t address[ADDRESS_LENGTH];
uint8_t tokenId[INT256_LENGTH];
// tokenDefinition_t *tokenSelf;
// tokenDefinition_t *tokenAddress;
} erc721_parameters_t;
static const uint8_t ERC721_APPROVE_SELECTOR[SELECTOR_SIZE] = {0x13, 0x37, 0x42, 0x42};
static const uint8_t ERC721_APPROVE_FOR_ALL_SELECTOR[SELECTOR_SIZE] = {0xa2, 0x2c, 0xb4, 0x65};
static const uint8_t ERC721_TRANSFER_SELECTOR[SELECTOR_SIZE] = {0x23, 0xb8, 0x72, 0xdd};
static const uint8_t ERC721_SAFE_TRANSFER_SELECTOR[SELECTOR_SIZE] = {0x42, 0x84, 0x2e, 0x0e};
static const uint8_t ERC721_SAFE_TRANSFER_DATA_SELECTOR[SELECTOR_SIZE] = {0xf2, 0x42, 0x43, 0x2a};
bool erc721_plugin_available_check() {
#ifdef HAVE_STARKWARE
if (quantumSet) {
switch (dataContext.tokenContext.quantumType) {
case STARK_QUANTUM_ERC721:
case STARK_QUANTUM_MINTABLE_ERC721:
return true;
default:
return false;
const uint8_t *const ERC721_SELECTORS[NUM_ERC721_SELECTORS] = {
ERC721_APPROVE_SELECTOR,
ERC721_APPROVE_FOR_ALL_SELECTOR,
ERC721_TRANSFER_SELECTOR,
ERC721_SAFE_TRANSFER_SELECTOR,
ERC721_SAFE_TRANSFER_DATA_SELECTOR,
};
static void handle_init_contract(void *parameters) {
ethPluginInitContract_t *msg = (ethPluginInitContract_t *) parameters;
erc721_context_t *context = (erc721_context_t *) msg->pluginContext;
uint8_t i;
for (i = 0; i < NUM_ERC721_SELECTORS; i++) {
if (memcmp((uint8_t *) PIC(ERC721_SELECTORS[i]), msg->selector, SELECTOR_SIZE) == 0) {
context->selectorIndex = i;
break;
}
}
return false;
#endif
// No selector found.
if (i == NUM_ERC721_SELECTORS) {
PRINTF("Unknown erc721 selector %.*H\n", SELECTOR_SIZE, msg->selector);
msg->result = ETH_PLUGIN_RESULT_FALLBACK;
return;
}
msg->result = ETH_PLUGIN_RESULT_OK;
switch (context->selectorIndex) {
case SET_APPROVAL_FOR_ALL:
case APPROVE:
context->next_param = OPERATOR;
break;
case SAFE_TRANSFER:
case SAFE_TRANSFER_DATA:
case TRANSFER:
context->next_param = FROM;
break;
default:
PRINTF("Unsupported selector index: %d\n", context->selectorIndex);
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}
static void handle_finalize(void *parameters) {
ethPluginFinalize_t *msg = (ethPluginFinalize_t *) parameters;
erc721_context_t *context = (erc721_context_t *) msg->pluginContext;
msg->tokenLookup1 = msg->pluginSharedRO->txContent->destination;
msg->tokenLookup2 = NULL;
switch (context->selectorIndex) {
case TRANSFER:
case SAFE_TRANSFER:
case SAFE_TRANSFER_DATA:
case SET_APPROVAL_FOR_ALL:
msg->numScreens = 3;
break;
case APPROVE:
msg->numScreens = 4;
break;
default:
msg->result = ETH_PLUGIN_RESULT_ERROR;
return;
}
// Check if some ETH is attached to this tx
if (!allzeroes((void *) &msg->pluginSharedRO->txContent->value,
sizeof(msg->pluginSharedRO->txContent->value))) {
// Set Approval for All is not payable
if (context->selectorIndex == SET_APPROVAL_FOR_ALL) {
msg->result = ETH_PLUGIN_RESULT_ERROR;
return;
} else {
// Add an additional screen
msg->numScreens++;
}
}
msg->uiType = ETH_UI_TYPE_GENERIC;
msg->result = ETH_PLUGIN_RESULT_OK;
}
static void handle_provide_info(void *parameters) {
ethPluginProvideInfo_t *msg = (ethPluginProvideInfo_t *) parameters;
msg->result = ETH_PLUGIN_RESULT_OK;
}
static void handle_query_contract_id(void *parameters) {
ethQueryContractID_t *msg = (ethQueryContractID_t *) parameters;
erc721_context_t *context = (erc721_context_t *) msg->pluginContext;
msg->result = ETH_PLUGIN_RESULT_OK;
strlcpy(msg->name, "NFT", msg->nameLength);
switch (context->selectorIndex) {
case SET_APPROVAL_FOR_ALL:
case APPROVE:
strlcpy(msg->version, "Allowance", msg->versionLength);
break;
case SAFE_TRANSFER:
case SAFE_TRANSFER_DATA:
case TRANSFER:
strlcpy(msg->version, "Transfer", msg->versionLength);
break;
default:
PRINTF("Unsupported selector %d\n", context->selectorIndex);
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}
void erc721_plugin_call(int message, void *parameters) {
switch (message) {
case ETH_PLUGIN_INIT_CONTRACT: {
ethPluginInitContract_t *msg = (ethPluginInitContract_t *) parameters;
erc721_parameters_t *context = (erc721_parameters_t *) msg->pluginContext;
// enforce that ETH amount should be 0
if (!allzeroes(msg->pluginSharedRO->txContent->value.value, 32)) {
PRINTF("Err: Transaction amount is not 0 for erc721 approval\n");
msg->result = ETH_PLUGIN_RESULT_ERROR;
} else {
size_t i;
for (i = 0; i < NUM_ERC721_SELECTORS; i++) {
if (memcmp((uint8_t *) PIC(ERC721_SELECTORS[i]),
msg->selector,
SELECTOR_SIZE) == 0) {
context->selectorIndex = i;
break;
}
}
if (i == NUM_ERC721_SELECTORS) {
PRINTF("Unknown erc721 selector %.*H\n", SELECTOR_SIZE, msg->selector);
break;
}
if (msg->dataSize != 4 + 32 + 32) {
PRINTF("Invalid erc721 approval data size %d\n", msg->dataSize);
break;
}
PRINTF("erc721 plugin init\n");
msg->result = ETH_PLUGIN_RESULT_OK;
}
handle_init_contract(parameters);
} break;
case ETH_PLUGIN_PROVIDE_PARAMETER: {
ethPluginProvideParameter_t *msg = (ethPluginProvideParameter_t *) parameters;
erc721_parameters_t *context = (erc721_parameters_t *) msg->pluginContext;
PRINTF("erc721 plugin provide parameter %d %.*H\n",
msg->parameterOffset,
32,
msg->parameter);
switch (msg->parameterOffset) {
case 4:
memmove(context->address, msg->parameter + 32 - 20, 20);
msg->result = ETH_PLUGIN_RESULT_OK;
break;
case 4 + 32:
memmove(context->tokenId, msg->parameter, 32);
msg->result = ETH_PLUGIN_RESULT_OK;
break;
default:
PRINTF("Unhandled parameter offset\n");
break;
}
handle_provide_parameter(parameters);
} break;
case ETH_PLUGIN_FINALIZE: {
ethPluginFinalize_t *msg = (ethPluginFinalize_t *) parameters;
erc721_parameters_t *context = (erc721_parameters_t *) msg->pluginContext;
PRINTF("erc721 plugin finalize\n");
msg->tokenLookup1 = msg->pluginSharedRO->txContent->destination;
msg->tokenLookup2 = context->address;
msg->numScreens = 3;
msg->uiType = ETH_UI_TYPE_GENERIC;
msg->result = ETH_PLUGIN_RESULT_OK;
handle_finalize(parameters);
} break;
case ETH_PLUGIN_PROVIDE_TOKEN: {
ethPluginProvideToken_t *msg = (ethPluginProvideToken_t *) parameters;
PRINTF("erc721 plugin provide token dest: %d - address: %d\n",
(msg->token1 != NULL),
(msg->token2 != NULL));
// context->tokenSelf = msg->token1;
// context->tokenAddress = msg->token2;
msg->result = ETH_PLUGIN_RESULT_OK;
case ETH_PLUGIN_PROVIDE_INFO: {
handle_provide_info(parameters);
} break;
case ETH_PLUGIN_QUERY_CONTRACT_ID: {
ethQueryContractID_t *msg = (ethQueryContractID_t *) parameters;
strlcpy(msg->name, "Allowance", msg->nameLength);
strlcpy(msg->version, "", msg->versionLength);
msg->result = ETH_PLUGIN_RESULT_OK;
handle_query_contract_id(parameters);
} break;
case ETH_PLUGIN_QUERY_CONTRACT_UI: {
ethQueryContractUI_t *msg = (ethQueryContractUI_t *) parameters;
erc721_parameters_t *context = (erc721_parameters_t *) msg->pluginContext;
switch (msg->screenIndex) {
case 0:
strlcpy(msg->title, "Contract Name", msg->titleLength);
getEthDisplayableAddress(tmpContent.txContent.destination,
msg->msg,
msg->msgLength,
&global_sha3,
chainConfig->chainId);
msg->result = ETH_PLUGIN_RESULT_OK;
break;
case 1:
strlcpy(msg->title, "NFT Contract", msg->titleLength);
getEthDisplayableAddress(context->address,
msg->msg,
msg->msgLength,
&global_sha3,
chainConfig->chainId);
msg->result = ETH_PLUGIN_RESULT_OK;
break;
case 2:
strlcpy(msg->title, "TokenID", msg->titleLength);
snprintf(msg->msg, 70, "0x%.*H", 32, context->tokenId);
msg->result = ETH_PLUGIN_RESULT_OK;
break;
default:
break;
}
handle_query_contract_ui(parameters);
} break;
default:
PRINTF("Unhandled message %d\n", message);
break;
}
}

View File

@@ -0,0 +1,42 @@
#pragma once
#include <string.h>
#include "eth_plugin_handler.h"
#include "shared_context.h"
#include "ethUtils.h"
#include "utils.h"
// Internal plugin for EIP 721: https://eips.ethereum.org/EIPS/eip-721
#define NUM_ERC721_SELECTORS 5
typedef enum {
APPROVE,
SET_APPROVAL_FOR_ALL,
TRANSFER,
SAFE_TRANSFER,
SAFE_TRANSFER_DATA,
} erc721_selector_t;
typedef enum {
FROM,
TO,
DATA,
TOKEN_ID,
OPERATOR,
APPROVED,
NONE,
} erc721_selector_field;
typedef struct erc721_context_t {
uint8_t address[ADDRESS_LENGTH];
uint8_t tokenId[INT256_LENGTH];
bool approved;
erc721_selector_field next_param;
uint8_t selectorIndex;
} erc721_context_t;
void handle_provide_parameter(void *parameters);
void handle_query_contract_ui(void *parameters);

View File

@@ -0,0 +1,93 @@
#include "erc721_plugin.h"
#include "eth_plugin_internal.h"
static void handle_approve(ethPluginProvideParameter_t *msg, erc721_context_t *context) {
switch (context->next_param) {
case OPERATOR:
copy_address(context->address, msg->parameter, sizeof(context->address));
context->next_param = TOKEN_ID;
break;
case TOKEN_ID:
copy_parameter(context->tokenId, msg->parameter, sizeof(context->tokenId));
context->next_param = NONE;
break;
default:
PRINTF("Unhandled parameter offset\n");
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}
// `strict` will set msg->result to ERROR if parsing continues after `TOKEN_ID` has been parsed.
static void handle_transfer(ethPluginProvideParameter_t *msg,
erc721_context_t *context,
bool strict) {
switch (context->next_param) {
case FROM:
context->next_param = TO;
break;
case TO:
copy_address(context->address, msg->parameter, sizeof(context->address));
context->next_param = TOKEN_ID;
break;
case TOKEN_ID:
copy_parameter(context->tokenId, msg->parameter, sizeof(context->tokenId));
context->next_param = NONE;
break;
default:
if (strict) {
PRINTF("Param %d not supported\n", context->next_param);
msg->result = ETH_PLUGIN_RESULT_ERROR;
}
break;
}
}
static void handle_approval_for_all(ethPluginProvideParameter_t *msg, erc721_context_t *context) {
switch (context->next_param) {
case OPERATOR:
context->next_param = APPROVED;
copy_address(context->address, msg->parameter, sizeof(context->address));
break;
case APPROVED:
context->next_param = NONE;
context->approved = msg->parameter[PARAMETER_LENGTH - 1];
break;
default:
PRINTF("Param %d not supported\n", context->next_param);
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}
void handle_provide_parameter(void *parameters) {
ethPluginProvideParameter_t *msg = (ethPluginProvideParameter_t *) parameters;
erc721_context_t *context = (erc721_context_t *) msg->pluginContext;
PRINTF("erc721 plugin provide parameter %d %.*H\n",
msg->parameterOffset,
PARAMETER_LENGTH,
msg->parameter);
msg->result = ETH_PLUGIN_RESULT_SUCCESSFUL;
switch (context->selectorIndex) {
case APPROVE:
handle_approve(msg, context);
break;
case SAFE_TRANSFER:
case TRANSFER:
handle_transfer(msg, context, true);
break;
case SAFE_TRANSFER_DATA:
// Set `strict` to `false` because additional data might be present.
handle_transfer(msg, context, false);
break;
case SET_APPROVAL_FOR_ALL:
handle_approval_for_all(msg, context);
break;
default:
PRINTF("Selector index %d not supported\n", context->selectorIndex);
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}

View File

@@ -0,0 +1,166 @@
#include "erc721_plugin.h"
static void set_approval_ui(ethQueryContractUI_t *msg, erc721_context_t *context) {
switch (msg->screenIndex) {
case 0:
strlcpy(msg->title, "Allow", msg->titleLength);
getEthDisplayableAddress(context->address,
msg->msg,
msg->msgLength,
&global_sha3,
chainConfig->chainId);
break;
case 1:
strlcpy(msg->title, "To Spend Your", msg->titleLength);
if (msg->item1) {
strlcpy(msg->msg, (const char *) &msg->item1->nft.collectionName, msg->msgLength);
} else {
strlcpy(msg->msg, "Not found", msg->msgLength);
}
break;
case 2:
strlcpy(msg->title, "NFT Address", msg->titleLength);
getEthDisplayableAddress(msg->pluginSharedRO->txContent->destination,
msg->msg,
msg->msgLength,
&global_sha3,
chainConfig->chainId);
break;
case 3:
strlcpy(msg->title, "NFT ID", msg->titleLength);
uint256_to_decimal(context->tokenId,
sizeof(context->tokenId),
msg->msg,
msg->msgLength);
break;
case 4:
strlcpy(msg->title, "And send", msg->titleLength);
amountToString((uint8_t *) &msg->pluginSharedRO->txContent->value,
sizeof(msg->pluginSharedRO->txContent->value),
WEI_TO_ETHER,
msg->network_ticker,
msg->msg,
msg->msgLength);
default:
PRINTF("Unsupported screen index %d\n", msg->screenIndex);
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}
static void set_approval_for_all_ui(ethQueryContractUI_t *msg, erc721_context_t *context) {
switch (msg->screenIndex) {
case 0:
if (context->approved) {
strlcpy(msg->title, "Allow", msg->titleLength);
} else {
strlcpy(msg->title, "Revoke", msg->titleLength);
}
getEthDisplayableAddress(context->address,
msg->msg,
msg->msgLength,
&global_sha3,
chainConfig->chainId);
break;
case 1:
strlcpy(msg->title, "To Manage ALL", msg->titleLength);
if (msg->item1) {
strlcpy(msg->msg, (const char *) &msg->item1->nft.collectionName, msg->msgLength);
} else {
strlcpy(msg->msg, "Not found", msg->msgLength);
}
break;
case 2:
strlcpy(msg->title, "NFT Address", msg->titleLength);
getEthDisplayableAddress(msg->pluginSharedRO->txContent->destination,
msg->msg,
msg->msgLength,
&global_sha3,
chainConfig->chainId);
break;
case 3:
strlcpy(msg->title, "And send", msg->titleLength);
amountToString((uint8_t *) &msg->pluginSharedRO->txContent->value,
sizeof(msg->pluginSharedRO->txContent->value),
WEI_TO_ETHER,
msg->network_ticker,
msg->msg,
msg->msgLength);
default:
PRINTF("Unsupported screen index %d\n", msg->screenIndex);
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}
static void set_transfer_ui(ethQueryContractUI_t *msg, erc721_context_t *context) {
switch (msg->screenIndex) {
case 0:
strlcpy(msg->title, "To", msg->titleLength);
getEthDisplayableAddress(context->address,
msg->msg,
msg->msgLength,
&global_sha3,
chainConfig->chainId);
break;
case 1:
strlcpy(msg->title, "Collection Name", msg->titleLength);
if (msg->item1) {
strlcpy(msg->msg, (const char *) &msg->item1->nft.collectionName, msg->msgLength);
} else {
strlcpy(msg->msg, "Not Found", msg->msgLength);
}
break;
case 2:
strlcpy(msg->title, "NFT Address", msg->titleLength);
getEthDisplayableAddress(msg->pluginSharedRO->txContent->destination,
msg->msg,
msg->msgLength,
&global_sha3,
chainConfig->chainId);
break;
case 3:
strlcpy(msg->title, "NFT ID", msg->titleLength);
uint256_to_decimal(context->tokenId,
sizeof(context->tokenId),
msg->msg,
msg->msgLength);
break;
case 4:
strlcpy(msg->title, "And send", msg->titleLength);
amountToString((uint8_t *) &msg->pluginSharedRO->txContent->value,
sizeof(msg->pluginSharedRO->txContent->value),
WEI_TO_ETHER,
msg->network_ticker,
msg->msg,
msg->msgLength);
default:
PRINTF("Unsupported screen index %d\n", msg->screenIndex);
msg->result = ETH_PLUGIN_RESULT_ERROR;
break;
}
}
void handle_query_contract_ui(void *parameters) {
ethQueryContractUI_t *msg = (ethQueryContractUI_t *) parameters;
erc721_context_t *context = (erc721_context_t *) msg->pluginContext;
msg->result = ETH_PLUGIN_RESULT_OK;
switch (context->selectorIndex) {
case APPROVE:
set_approval_ui(msg, context);
break;
case SET_APPROVAL_FOR_ALL:
set_approval_for_all_ui(msg, context);
break;
case SAFE_TRANSFER_DATA:
case SAFE_TRANSFER:
case TRANSFER:
set_transfer_ui(msg, context);
break;
default:
msg->result = ETH_PLUGIN_RESULT_ERROR;
PRINTF("Unsupported selector index %d\n", context->selectorIndex);
break;
}
}

View File

@@ -188,8 +188,9 @@ bool starkware_verify_asset_id(uint8_t *tmp32, uint8_t *tokenId, bool assetTypeO
if (quantumSet) {
cx_sha3_t sha3;
tokenDefinition_t *currentToken = NULL;
if (dataContext.tokenContext.quantumIndex != MAX_TOKEN) {
currentToken = &tmpCtx.transactionContext.tokens[dataContext.tokenContext.quantumIndex];
if (dataContext.tokenContext.quantumIndex != MAX_ITEMS) {
currentToken =
&tmpCtx.transactionContext.extraInfo[dataContext.tokenContext.quantumIndex].token;
}
cx_keccak_init(&sha3, 256);
compute_token_id(&sha3,
@@ -214,9 +215,9 @@ bool starkware_verify_asset_id(uint8_t *tmp32, uint8_t *tokenId, bool assetTypeO
bool starkware_verify_token(uint8_t *token) {
if (quantumSet) {
if (dataContext.tokenContext.quantumIndex != MAX_TOKEN) {
if (dataContext.tokenContext.quantumIndex != MAX_ITEMS) {
tokenDefinition_t *currentToken =
&tmpCtx.transactionContext.tokens[dataContext.tokenContext.quantumIndex];
&tmpCtx.transactionContext.extraInfo[dataContext.tokenContext.quantumIndex].token;
if (memcmp(token + 32 - 20, currentToken->address, 20) != 0) {
PRINTF("Token not matching got %.*H\n", 20, token + 32 - 20);
PRINTF("Current token %.*H\n", 20, currentToken->address);
@@ -235,7 +236,7 @@ bool starkware_verify_token(uint8_t *token) {
bool starkware_verify_quantum(uint8_t *quantum) {
if (quantumSet) {
if (dataContext.tokenContext.quantumIndex != MAX_TOKEN) {
if (dataContext.tokenContext.quantumIndex != MAX_ITEMS) {
if (memcmp(quantum, dataContext.tokenContext.quantum, 32) != 0) {
PRINTF("Quantum not matching got %.*H\n", 32, quantum);
PRINTF("Current quantum %.*H\n", 32, dataContext.tokenContext.quantum);
@@ -301,7 +302,7 @@ void starkware_print_amount(uint8_t *amountData,
char *ticker = chainConfig->coinName;
if ((amountData == NULL) ||
(forEscape && (dataContext.tokenContext.quantumIndex == MAX_TOKEN))) {
(forEscape && (dataContext.tokenContext.quantumIndex == MAX_ITEMS))) {
decimals = WEI_TO_ETHER;
if (!forEscape) {
convertUint256BE(tmpContent.txContent.value.value,
@@ -312,7 +313,7 @@ void starkware_print_amount(uint8_t *amountData,
}
} else {
tokenDefinition_t *token =
&tmpCtx.transactionContext.tokens[dataContext.tokenContext.quantumIndex];
&tmpCtx.transactionContext.extraInfo[dataContext.tokenContext.quantumIndex].token;
decimals = token->decimals;
ticker = token->ticker;
readu256BE(amountData, &amountPre);
@@ -334,9 +335,9 @@ void starkware_print_amount(uint8_t *amountData,
void starkware_print_ticker(char *destination, size_t destinationLength) {
char *ticker = chainConfig->coinName;
if (dataContext.tokenContext.quantumIndex != MAX_TOKEN) {
if (dataContext.tokenContext.quantumIndex != MAX_ITEMS) {
tokenDefinition_t *token =
&tmpCtx.transactionContext.tokens[dataContext.tokenContext.quantumIndex];
&tmpCtx.transactionContext.extraInfo[dataContext.tokenContext.quantumIndex].token;
ticker = token->ticker;
}
strlcpy(destination, ticker, destinationLength);
@@ -345,9 +346,9 @@ void starkware_print_ticker(char *destination, size_t destinationLength) {
// TODO : rewrite as independant code
void starkware_print_asset_contract(char *destination, size_t destinationLength) {
// token has been validated to be present previously
if (dataContext.tokenContext.quantumIndex != MAX_TOKEN) {
if (dataContext.tokenContext.quantumIndex != MAX_ITEMS) {
tokenDefinition_t *token =
&tmpCtx.transactionContext.tokens[dataContext.tokenContext.quantumIndex];
&tmpCtx.transactionContext.extraInfo[dataContext.tokenContext.quantumIndex].token;
getEthDisplayableAddress(token->address,
destination,
destinationLength,