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>
8
Makefile
@@ -300,12 +300,18 @@ ifneq ($(ALLOW_DATA),0)
|
||||
DEFINES += HAVE_ALLOW_DATA
|
||||
endif
|
||||
|
||||
# Bypass the signature verification for setExternalPlugin and provideERC20TokenInfo calls
|
||||
# Bypass the signature verification for setExternalPlugin, setPlugin, provideERC20TokenInfo and provideNFTInfo calls
|
||||
BYPASS_SIGNATURES:=0
|
||||
ifneq ($(BYPASS_SIGNATURES),0)
|
||||
DEFINES += HAVE_BYPASS_SIGNATURES
|
||||
endif
|
||||
|
||||
# Enable the NFT testing key
|
||||
NFT_TESTING_KEY:=0
|
||||
ifneq ($(NFT_TESTING_KEY),0)
|
||||
DEFINES += HAVE_NFT_TESTING_KEY
|
||||
endif
|
||||
|
||||
|
||||
# Enabling debug PRINTF
|
||||
DEBUG:=0
|
||||
|
||||
@@ -23,6 +23,9 @@ Application version 1.5.0 - 25th of September 2020
|
||||
## 1.7.6
|
||||
- Add SET EXTERNAL PLUGIN
|
||||
|
||||
## 1.9.13
|
||||
- Add SET PLUGIN
|
||||
|
||||
## About
|
||||
|
||||
This application describes the APDU messages interface to communicate with the Ethereum application.
|
||||
@@ -240,6 +243,49 @@ signed by the following secp256k1 public key 0482bbf2f34f367b2e5bc21847b6566f21f
|
||||
|
||||
None
|
||||
|
||||
### PROVIDE NFT INFORMATION
|
||||
|
||||
#### Description
|
||||
|
||||
This commands provides a trusted description of an NFT to associate a contract address with a collectionName.
|
||||
|
||||
It shall be run immediately before performing a transaction involving a contract calling this contract address to display the proper nft information to the user if necessary, as marked in GET APP CONFIGURATION flags.
|
||||
|
||||
The signature is computed on
|
||||
|
||||
type || version || len(collectionName) || collectionName || address || chainId || keyId || algorithmId || len(signature) || signature
|
||||
|
||||
#### Coding
|
||||
|
||||
'Command'
|
||||
|
||||
[width="80%"]
|
||||
|==============================================================================================================================
|
||||
| *CLA* | *INS* | *P1* | *P2* | *Lc* | *Le*
|
||||
| E0 | 14 | 00 | 00 | variable | 00
|
||||
|==============================================================================================================================
|
||||
|
||||
'Input data'
|
||||
|
||||
[width="80%"]
|
||||
|==============================================================================================================================
|
||||
| *Description* | *Length*
|
||||
| Type | 1
|
||||
| Version | 1
|
||||
| Collection Name Length | 1
|
||||
| Collection Name | variable
|
||||
| Address | 20
|
||||
| Chain ID | 8
|
||||
| KeyID | 1
|
||||
| Algorithm ID | 1
|
||||
| Signature Length | 1
|
||||
| Signature | variable
|
||||
|==============================================================================================================================
|
||||
|
||||
'Output data'
|
||||
|
||||
None
|
||||
|
||||
|
||||
### SET EXTERNAL PLUGIN
|
||||
|
||||
@@ -283,6 +329,56 @@ signed by the following secp256k1 public key 0482bbf2f34f367b2e5bc21847b6566f21f
|
||||
|
||||
None
|
||||
|
||||
### SET PLUGIN
|
||||
|
||||
#### Description
|
||||
|
||||
This commands provides the name of a trusted binding of a plugin with a contract address and a supported method selector. This plugin will be called to interpret contract data in the following transaction signing command.
|
||||
|
||||
It can be used to set both internal and external plugins.
|
||||
|
||||
It shall be run immediately before performing a transaction involving a contract supported by this plugin to display the proper information to the user if necessary.
|
||||
|
||||
The function returns an error sw (0x6984) if the plugin requested is not installed on the device, 0x9000 otherwise.
|
||||
|
||||
The plugin names `ERC20`, `ERC721` and `ERC1155` are reserved. Additional plugin names might be added to this list in the future.
|
||||
|
||||
The signature is computed on
|
||||
|
||||
type || version || len(pluginName) || pluginName || address || selector || chainId || keyId || algorithmId || len(signature) || signature
|
||||
|
||||
#### Coding
|
||||
|
||||
'Command'
|
||||
|
||||
[width="80%"]
|
||||
|==============================================================================================================================
|
||||
| *CLA* | *INS* | *P1* | *P2* | *Lc* | *Le*
|
||||
| E0 | 16 | 00 | 00 | variable | 00
|
||||
|==============================================================================================================================
|
||||
|
||||
'Input data'
|
||||
|
||||
[width="80%"]
|
||||
|==============================================================================================================================
|
||||
| *Description* | *Length*
|
||||
| Type | 1
|
||||
| Version | 1
|
||||
| Plugin Name Length | 1
|
||||
| Plugin Name | variable
|
||||
| Address | 20
|
||||
| Selector | 4
|
||||
| Chain ID | 8
|
||||
| KeyID | 1
|
||||
| Algorithm ID | 1
|
||||
| Signature Length | 1
|
||||
| Signature | variable
|
||||
|==============================================================================================================================
|
||||
|
||||
'Output data'
|
||||
|
||||
None
|
||||
|
||||
### GET APP CONFIGURATION
|
||||
|
||||
#### Description
|
||||
|
||||
@@ -145,8 +145,8 @@ typedef struct ethPluginFinalize_t {
|
||||
ethPluginSharedRO_t *pluginSharedRO;
|
||||
uint8_t *pluginContext;
|
||||
|
||||
uint8_t *tokenLookup1; // set by the plugin if a token should be looked up
|
||||
uint8_t *tokenLookup2;
|
||||
uint8_t *itemLookup1; // set by the plugin if a token or an nft should be looked up
|
||||
uint8_t *itemLookup2;
|
||||
|
||||
uint8_t *amount; // set an uint256 pointer if uiType is UI_AMOUNT_ADDRESS
|
||||
uint8_t *address; // set to the destination address if uiType is UI_AMOUNT_ADDRESS. Set to the user's address if uiType is UI_TYPE_GENERIC
|
||||
@@ -161,8 +161,8 @@ typedef struct ethPluginFinalize_t {
|
||||
|
||||
This message is sent when the data field has been fully parsed. The following specific fields can be filled by the plugin :
|
||||
|
||||
* tokenLookup1 : the pointer shall be set to a 20 bytes address to look up an ERC 20 token descriptor if needed by the plugin
|
||||
* tokenLookup2 : the pointer shall be set to a 20 bytes address to look up an ERC 20 token descriptor if needed by the plugin
|
||||
* itemLookup1 : the pointer shall be set to a 20 bytes address to look up an ERC20 token or NFT if needed by the plugin
|
||||
* itemLookup2 : the pointer shall be set to a 20 bytes address to look up an ERC20 token or NFT if needed by the plugin
|
||||
* uiType : set to either ETH_UI_TYPE_AMOUNT_ADDRESS for an amount/address UI or ETH_UI_TYPE_GENERIC for a generic UI
|
||||
|
||||
The following specific fields are filled by the plugin when returning an amount/address UI :
|
||||
@@ -179,32 +179,32 @@ The following return codes are expected, any other will abort the signing proces
|
||||
* ETH_PLUGIN_RESULT_OK : if the plugin can be successfully initialized
|
||||
* ETH_PLUGIN_RESULT_FALLBACK : if the signing logic should fallback to the generic one
|
||||
|
||||
### ETH_PLUGIN_PROVIDE_TOKEN
|
||||
### ETH_PLUGIN_PROVIDE_INFO
|
||||
|
||||
[source,C]
|
||||
----
|
||||
|
||||
typedef struct ethPluginProvideToken_t {
|
||||
typedef struct ethPluginProvideInfo_t {
|
||||
|
||||
ethPluginSharedRW_t *pluginSharedRW;
|
||||
ethPluginSharedRO_t *pluginSharedRO;
|
||||
uint8_t *pluginContext;
|
||||
|
||||
tokenDefinition_t *token1; // set by the ETH application, to be saved by the plugin
|
||||
tokenDefinition_t *token2;
|
||||
union extraInfo *item1; // set by the ETH application, to be saved by the plugin
|
||||
union extraInfo *item2;
|
||||
|
||||
uint8_t additionalScreens; // Used by the plugin if it needs to display additional screens based on the information received from the token definitions.
|
||||
uint8_t additionalScreens; // Used by the plugin if it needs to display additional screens based on the information received.
|
||||
|
||||
uint8_t result;
|
||||
|
||||
} ethPluginProvideToken_t;
|
||||
} ethPluginProvideInfo_t;
|
||||
|
||||
----
|
||||
|
||||
This message is sent if a token lookup was required by the plugin when parsing a finalize message. The following specific fields are filled when the plugin is called :
|
||||
This message is sent if an information lookup was required by the plugin when parsing a finalize message. The following specific fields are filled when the plugin is called :
|
||||
|
||||
* token1 : pointer to a token definition matching tokenLookup1, or NULL if not found
|
||||
* token2 : pointer to a token definition matching tokenLookup2, or NULL if not found or not requested
|
||||
* item1 : pointer to an union matching itemLookup1, or NULL if not found
|
||||
* item2 : pointer to an union matching itemLookup2, or NULL if not found
|
||||
|
||||
The following return codes are expected, any other will abort the signing process :
|
||||
|
||||
@@ -233,7 +233,7 @@ typedef struct ethQueryContractID_t {
|
||||
|
||||
----
|
||||
|
||||
This message is sent after the parsing finalization and token lookups if requested if a generic UI is used. The following specific fields are provided when the plugin is called :
|
||||
This message is sent after the parsing finalization and information lookups if requested if a generic UI is used. The following specific fields are provided when the plugin is called :
|
||||
|
||||
* name : pointer to the name of the plugin, to be filled by the plugin
|
||||
* nameLength : maximum name length
|
||||
@@ -253,6 +253,9 @@ typedef struct ethQueryContractUI_t {
|
||||
|
||||
ethPluginSharedRW_t *pluginSharedRW;
|
||||
ethPluginSharedRO_t *pluginSharedRO;
|
||||
union extraInfo_t *item1;
|
||||
union extraInfo_t *item2;
|
||||
char network_ticker[MAX_TICKER_LEN];
|
||||
uint8_t *pluginContext;
|
||||
uint8_t screenIndex;
|
||||
char *title;
|
||||
@@ -268,6 +271,10 @@ typedef struct ethQueryContractUI_t {
|
||||
|
||||
This message is sent when a plugin screen shall be displayed if a generic UI is used. The following specific fields are provided when the plugin is called :
|
||||
|
||||
|
||||
* item1 : pointer to token / nft information
|
||||
* item2 : pointer to token / nft information
|
||||
* network_ticker : string that holds the network ticker
|
||||
* screenIndex : index of the screen to display, starting from 0
|
||||
* title : pointer to the first line of the screen, to be filled by the plugin
|
||||
* titleLength : maximum title length
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#define INS_GET_ETH2_PUBLIC_KEY 0x0E
|
||||
#define INS_SET_ETH2_WITHDRAWAL_INDEX 0x10
|
||||
#define INS_SET_EXTERNAL_PLUGIN 0x12
|
||||
#define INS_PROVIDE_NFT_INFORMATION 0x14
|
||||
#define INS_SET_PLUGIN 0x16
|
||||
#define P1_CONFIRM 0x01
|
||||
#define P1_NON_CONFIRM 0x00
|
||||
#define P2_NO_CHAINCODE 0x00
|
||||
@@ -64,6 +66,12 @@ void handleProvideErc20TokenInformation(uint8_t p1,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx);
|
||||
void handleProvideNFTInformation(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *dataBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx);
|
||||
void handleSign(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *dataBuffer,
|
||||
@@ -96,6 +104,13 @@ void handleSetExternalPlugin(uint8_t p1,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx);
|
||||
|
||||
void handleSetPlugin(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *workBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx);
|
||||
|
||||
#ifdef HAVE_ETH2
|
||||
|
||||
void handleGetEth2PublicKey(uint8_t p1,
|
||||
|
||||
@@ -22,8 +22,8 @@ void eth_plugin_prepare_finalize(ethPluginFinalize_t *finalize) {
|
||||
memset((uint8_t *) finalize, 0, sizeof(ethPluginFinalize_t));
|
||||
}
|
||||
|
||||
void eth_plugin_prepare_provide_token(ethPluginProvideToken_t *provideToken) {
|
||||
memset((uint8_t *) provideToken, 0, sizeof(ethPluginProvideToken_t));
|
||||
void eth_plugin_prepare_provide_info(ethPluginProvideInfo_t *provideToken) {
|
||||
memset((uint8_t *) provideToken, 0, sizeof(ethPluginProvideInfo_t));
|
||||
}
|
||||
|
||||
void eth_plugin_prepare_query_contract_ID(ethQueryContractID_t *queryContractID,
|
||||
@@ -45,6 +45,23 @@ void eth_plugin_prepare_query_contract_UI(ethQueryContractUI_t *queryContractUI,
|
||||
char *msg,
|
||||
uint32_t msgLength) {
|
||||
memset((uint8_t *) queryContractUI, 0, sizeof(ethQueryContractUI_t));
|
||||
|
||||
// If no extra information was found, set the pointer to NULL
|
||||
if (allzeroes(&tmpCtx.transactionContext.extraInfo[1], sizeof(union extraInfo_t))) {
|
||||
queryContractUI->item1 = NULL;
|
||||
} else {
|
||||
queryContractUI->item1 = &tmpCtx.transactionContext.extraInfo[1];
|
||||
}
|
||||
|
||||
// If no extra information was found, set the pointer to NULL
|
||||
if (allzeroes(&tmpCtx.transactionContext.extraInfo[0], sizeof(union extraInfo_t))) {
|
||||
queryContractUI->item2 = NULL;
|
||||
} else {
|
||||
queryContractUI->item2 = &tmpCtx.transactionContext.extraInfo[0];
|
||||
}
|
||||
|
||||
strlcpy(queryContractUI->network_ticker, get_network_ticker(), MAX_TICKER_LEN);
|
||||
|
||||
queryContractUI->screenIndex = screenIndex;
|
||||
strlcpy(queryContractUI->network_ticker,
|
||||
get_network_ticker(),
|
||||
@@ -62,52 +79,66 @@ eth_plugin_result_t eth_plugin_perform_init(uint8_t *contractAddress,
|
||||
dataContext.tokenContext.pluginStatus = ETH_PLUGIN_RESULT_UNAVAILABLE;
|
||||
|
||||
PRINTF("Selector %.*H\n", 4, init->selector);
|
||||
if (externalPluginIsSet) {
|
||||
// check if the registered external plugin matches the TX contract address / method selector
|
||||
if (memcmp(contractAddress,
|
||||
dataContext.tokenContext.contract_address,
|
||||
sizeof(dataContext.tokenContext.contract_address)) != 0) {
|
||||
PRINTF("Got contract: %.*H\n", ADDRESS_LENGTH, contractAddress);
|
||||
PRINTF("Expected contract: %.*H\n",
|
||||
ADDRESS_LENGTH,
|
||||
dataContext.tokenContext.contract_address);
|
||||
os_sched_exit(0);
|
||||
}
|
||||
if (memcmp(init->selector,
|
||||
dataContext.tokenContext.method_selector,
|
||||
sizeof(dataContext.tokenContext.method_selector)) != 0) {
|
||||
PRINTF("Got selector: %.*H\n", SELECTOR_SIZE, init->selector);
|
||||
PRINTF("Expected selector: %.*H\n",
|
||||
SELECTOR_SIZE,
|
||||
dataContext.tokenContext.method_selector);
|
||||
os_sched_exit(0);
|
||||
}
|
||||
PRINTF("External plugin will be used\n");
|
||||
dataContext.tokenContext.pluginStatus = ETH_PLUGIN_RESULT_OK;
|
||||
contractAddress = NULL;
|
||||
} else {
|
||||
// Search internal plugin list
|
||||
for (i = 0;; i++) {
|
||||
uint8_t j;
|
||||
selectors = (const uint8_t **) PIC(INTERNAL_ETH_PLUGINS[i].selectors);
|
||||
if (selectors == NULL) {
|
||||
break;
|
||||
switch (pluginType) {
|
||||
case NOT_OLD_INTERNAL:
|
||||
case ERC1155:
|
||||
case ERC721:
|
||||
case EXTERNAL: {
|
||||
// check if the registered external plugin matches the TX contract address / selector
|
||||
if (memcmp(contractAddress,
|
||||
dataContext.tokenContext.contractAddress,
|
||||
sizeof(dataContext.tokenContext.contractAddress)) != 0) {
|
||||
PRINTF("Got contract: %.*H\n", ADDRESS_LENGTH, contractAddress);
|
||||
PRINTF("Expected contract: %.*H\n",
|
||||
ADDRESS_LENGTH,
|
||||
dataContext.tokenContext.contractAddress);
|
||||
os_sched_exit(0);
|
||||
}
|
||||
for (j = 0; ((j < INTERNAL_ETH_PLUGINS[i].num_selectors) && (contractAddress != NULL));
|
||||
j++) {
|
||||
if (memcmp(init->selector, (const void *) PIC(selectors[j]), SELECTOR_SIZE) == 0) {
|
||||
if ((INTERNAL_ETH_PLUGINS[i].availableCheck == NULL) ||
|
||||
((PluginAvailableCheck) PIC(INTERNAL_ETH_PLUGINS[i].availableCheck))()) {
|
||||
strlcpy(dataContext.tokenContext.pluginName,
|
||||
INTERNAL_ETH_PLUGINS[i].alias,
|
||||
PLUGIN_ID_LENGTH);
|
||||
dataContext.tokenContext.pluginStatus = ETH_PLUGIN_RESULT_OK;
|
||||
contractAddress = NULL;
|
||||
break;
|
||||
if (memcmp(init->selector,
|
||||
dataContext.tokenContext.methodSelector,
|
||||
sizeof(dataContext.tokenContext.methodSelector)) != 0) {
|
||||
PRINTF("Got selector: %.*H\n", SELECTOR_SIZE, init->selector);
|
||||
PRINTF("Expected selector: %.*H\n",
|
||||
SELECTOR_SIZE,
|
||||
dataContext.tokenContext.methodSelector);
|
||||
os_sched_exit(0);
|
||||
}
|
||||
PRINTF("Plugin will be used\n");
|
||||
// TODO: Add check for chainid.
|
||||
dataContext.tokenContext.pluginStatus = ETH_PLUGIN_RESULT_OK;
|
||||
contractAddress = NULL;
|
||||
} break;
|
||||
case OLD_INTERNAL: {
|
||||
// Search internal plugin list
|
||||
for (i = 0;; i++) {
|
||||
uint8_t j;
|
||||
selectors = (const uint8_t **) PIC(INTERNAL_ETH_PLUGINS[i].selectors);
|
||||
if (selectors == NULL) {
|
||||
break;
|
||||
}
|
||||
for (j = 0;
|
||||
((j < INTERNAL_ETH_PLUGINS[i].num_selectors) && (contractAddress != NULL));
|
||||
j++) {
|
||||
if (memcmp(init->selector, (const void *) PIC(selectors[j]), SELECTOR_SIZE) ==
|
||||
0) {
|
||||
if ((INTERNAL_ETH_PLUGINS[i].availableCheck == NULL) ||
|
||||
((PluginAvailableCheck) PIC(
|
||||
INTERNAL_ETH_PLUGINS[i].availableCheck))()) {
|
||||
strlcpy(dataContext.tokenContext.pluginName,
|
||||
INTERNAL_ETH_PLUGINS[i].alias,
|
||||
PLUGIN_ID_LENGTH);
|
||||
dataContext.tokenContext.pluginStatus = ETH_PLUGIN_RESULT_OK;
|
||||
contractAddress = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
PRINTF("Unsupported pluginType %d\n", pluginType);
|
||||
os_sched_exit(0);
|
||||
break;
|
||||
}
|
||||
|
||||
// Do not handle a plugin if running in swap mode
|
||||
@@ -140,7 +171,6 @@ eth_plugin_result_t eth_plugin_call(int method, void *parameter) {
|
||||
ethPluginSharedRO_t pluginRO;
|
||||
char *alias;
|
||||
uint8_t i;
|
||||
uint8_t internalPlugin = 0;
|
||||
|
||||
pluginRW.sha3 = &global_sha3;
|
||||
pluginRO.txContent = &tmpContent.txContent;
|
||||
@@ -183,12 +213,12 @@ eth_plugin_result_t eth_plugin_call(int method, void *parameter) {
|
||||
((ethPluginFinalize_t *) parameter)->pluginContext =
|
||||
(uint8_t *) &dataContext.tokenContext.pluginContext;
|
||||
break;
|
||||
case ETH_PLUGIN_PROVIDE_TOKEN:
|
||||
PRINTF("-- PLUGIN PROVIDE TOKEN --\n");
|
||||
((ethPluginProvideToken_t *) parameter)->result = ETH_PLUGIN_RESULT_UNAVAILABLE;
|
||||
((ethPluginProvideToken_t *) parameter)->pluginSharedRW = &pluginRW;
|
||||
((ethPluginProvideToken_t *) parameter)->pluginSharedRO = &pluginRO;
|
||||
((ethPluginProvideToken_t *) parameter)->pluginContext =
|
||||
case ETH_PLUGIN_PROVIDE_INFO:
|
||||
PRINTF("-- PLUGIN PROVIDE INFO --\n");
|
||||
((ethPluginProvideInfo_t *) parameter)->result = ETH_PLUGIN_RESULT_UNAVAILABLE;
|
||||
((ethPluginProvideInfo_t *) parameter)->pluginSharedRW = &pluginRW;
|
||||
((ethPluginProvideInfo_t *) parameter)->pluginSharedRO = &pluginRO;
|
||||
((ethPluginProvideInfo_t *) parameter)->pluginContext =
|
||||
(uint8_t *) &dataContext.tokenContext.pluginContext;
|
||||
break;
|
||||
case ETH_PLUGIN_QUERY_CONTRACT_ID:
|
||||
@@ -211,35 +241,52 @@ eth_plugin_result_t eth_plugin_call(int method, void *parameter) {
|
||||
return ETH_PLUGIN_RESULT_UNAVAILABLE;
|
||||
}
|
||||
|
||||
// Perform the call
|
||||
|
||||
for (i = 0;; i++) {
|
||||
if (INTERNAL_ETH_PLUGINS[i].alias[0] == 0) {
|
||||
switch (pluginType) {
|
||||
case NOT_OLD_INTERNAL:
|
||||
break;
|
||||
case EXTERNAL: {
|
||||
uint32_t params[3];
|
||||
params[0] = (uint32_t) alias;
|
||||
params[1] = method;
|
||||
params[2] = (uint32_t) parameter;
|
||||
BEGIN_TRY {
|
||||
TRY {
|
||||
os_lib_call(params);
|
||||
}
|
||||
CATCH_OTHER(e) {
|
||||
PRINTF("Plugin call exception for %s\n", alias);
|
||||
}
|
||||
FINALLY {
|
||||
}
|
||||
}
|
||||
END_TRY;
|
||||
break;
|
||||
}
|
||||
if (strcmp(alias, INTERNAL_ETH_PLUGINS[i].alias) == 0) {
|
||||
internalPlugin = 1;
|
||||
((PluginCall) PIC(INTERNAL_ETH_PLUGINS[i].impl))(method, parameter);
|
||||
case ERC721: {
|
||||
erc721_plugin_call(method, parameter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!internalPlugin) {
|
||||
uint32_t params[3];
|
||||
params[0] = (uint32_t) alias;
|
||||
params[1] = method;
|
||||
params[2] = (uint32_t) parameter;
|
||||
BEGIN_TRY {
|
||||
TRY {
|
||||
os_lib_call(params);
|
||||
}
|
||||
CATCH_OTHER(e) {
|
||||
PRINTF("Plugin call exception for %s\n", alias);
|
||||
}
|
||||
FINALLY {
|
||||
}
|
||||
case ERC1155: {
|
||||
erc1155_plugin_call(method, parameter);
|
||||
break;
|
||||
}
|
||||
case OLD_INTERNAL: {
|
||||
// Perform the call
|
||||
for (i = 0;; i++) {
|
||||
if (INTERNAL_ETH_PLUGINS[i].alias[0] == 0) {
|
||||
break;
|
||||
}
|
||||
if (strcmp(alias, INTERNAL_ETH_PLUGINS[i].alias) == 0) {
|
||||
((PluginCall) PIC(INTERNAL_ETH_PLUGINS[i].impl))(method, parameter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
PRINTF("Error with pluginType: %d\n", pluginType);
|
||||
return ETH_PLUGIN_RESULT_ERROR;
|
||||
}
|
||||
END_TRY;
|
||||
}
|
||||
|
||||
// Check the call result
|
||||
@@ -277,9 +324,9 @@ eth_plugin_result_t eth_plugin_call(int method, void *parameter) {
|
||||
return ETH_PLUGIN_RESULT_UNAVAILABLE;
|
||||
}
|
||||
break;
|
||||
case ETH_PLUGIN_PROVIDE_TOKEN:
|
||||
PRINTF("RESULT: %d\n", ((ethPluginProvideToken_t *) parameter)->result);
|
||||
switch (((ethPluginProvideToken_t *) parameter)->result) {
|
||||
case ETH_PLUGIN_PROVIDE_INFO:
|
||||
PRINTF("RESULT: %d\n", ((ethPluginProvideInfo_t *) parameter)->result);
|
||||
switch (((ethPluginProvideInfo_t *) parameter)->result) {
|
||||
case ETH_PLUGIN_RESULT_OK:
|
||||
case ETH_PLUGIN_RESULT_FALLBACK:
|
||||
break;
|
||||
|
||||
@@ -7,7 +7,7 @@ void eth_plugin_prepare_provide_parameter(ethPluginProvideParameter_t *providePa
|
||||
uint8_t *parameter,
|
||||
uint32_t parameterOffset);
|
||||
void eth_plugin_prepare_finalize(ethPluginFinalize_t *finalize);
|
||||
void eth_plugin_prepare_provide_token(ethPluginProvideToken_t *provideToken);
|
||||
void eth_plugin_prepare_provide_info(ethPluginProvideInfo_t *provideToken);
|
||||
void eth_plugin_prepare_query_contract_ID(ethQueryContractID_t *queryContractID,
|
||||
char *name,
|
||||
uint32_t nameLength,
|
||||
@@ -24,7 +24,6 @@ eth_plugin_result_t eth_plugin_perform_init(uint8_t *contractAddress,
|
||||
ethPluginInitContract_t *init);
|
||||
// NULL for cached address, or base contract address
|
||||
eth_plugin_result_t eth_plugin_call(int method, void *parameter);
|
||||
int compound_plugin_call(uint8_t *contractAddress, int method, void *parameter);
|
||||
|
||||
void plugin_ui_start(void);
|
||||
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
#include "cx.h"
|
||||
#include "ethUstream.h"
|
||||
#include "tokens.h"
|
||||
|
||||
#define PLUGIN_ID_LENGTH 30
|
||||
#include "shared_context.h"
|
||||
|
||||
// Interface version. To be updated everytime we introduce breaking changes to the plugin interface.
|
||||
typedef enum {
|
||||
ETH_PLUGIN_INTERFACE_VERSION_1 = 1, // Version 1
|
||||
ETH_PLUGIN_INTERFACE_VERSION_1 = 1,
|
||||
ETH_PLUGIN_INTERFACE_VERSION_2 = 2,
|
||||
ETH_PLUGIN_INTERFACE_VERSION_LATEST = 3,
|
||||
ETH_PLUGIN_INTERFACE_VERSION_3 = 3,
|
||||
ETH_PLUGIN_INTERFACE_VERSION_LATEST = 4,
|
||||
} eth_plugin_interface_version_t;
|
||||
|
||||
typedef enum {
|
||||
@@ -21,7 +21,7 @@ typedef enum {
|
||||
ETH_PLUGIN_INIT_CONTRACT = 0x0101,
|
||||
ETH_PLUGIN_PROVIDE_PARAMETER = 0x0102,
|
||||
ETH_PLUGIN_FINALIZE = 0x0103,
|
||||
ETH_PLUGIN_PROVIDE_TOKEN = 0x0104,
|
||||
ETH_PLUGIN_PROVIDE_INFO = 0x0104,
|
||||
ETH_PLUGIN_QUERY_CONTRACT_ID = 0x0105,
|
||||
ETH_PLUGIN_QUERY_CONTRACT_UI = 0x0106,
|
||||
ETH_PLUGIN_CHECK_PRESENCE = 0x01FF
|
||||
@@ -126,20 +126,20 @@ typedef struct ethPluginFinalize_t {
|
||||
|
||||
// Provide token
|
||||
|
||||
typedef struct ethPluginProvideToken_t {
|
||||
typedef struct ethPluginProvideInfo_t {
|
||||
ethPluginSharedRW_t *pluginSharedRW;
|
||||
ethPluginSharedRO_t *pluginSharedRO;
|
||||
uint8_t *pluginContext;
|
||||
|
||||
tokenDefinition_t *token1; // set by the ETH application, to be saved by the plugin
|
||||
tokenDefinition_t *token2;
|
||||
union extraInfo_t *item1; // set by the ETH application, to be saved by the plugin
|
||||
union extraInfo_t *item2;
|
||||
|
||||
uint8_t additionalScreens; // Used by the plugin if it needs to display additional screens
|
||||
// based on the information received from the token definitions.
|
||||
|
||||
uint8_t result;
|
||||
|
||||
} ethPluginProvideToken_t;
|
||||
} ethPluginProvideInfo_t;
|
||||
|
||||
// Query Contract name and version
|
||||
|
||||
@@ -164,9 +164,11 @@ typedef struct ethQueryContractID_t {
|
||||
typedef struct ethQueryContractUI_t {
|
||||
ethPluginSharedRW_t *pluginSharedRW;
|
||||
ethPluginSharedRO_t *pluginSharedRO;
|
||||
union extraInfo_t *item1;
|
||||
union extraInfo_t *item2;
|
||||
char network_ticker[MAX_TICKER_LEN];
|
||||
uint8_t *pluginContext;
|
||||
uint8_t screenIndex;
|
||||
char network_ticker[MAX_TICKER_LEN];
|
||||
|
||||
char *title;
|
||||
size_t titleLength;
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
#include "eth_plugin_internal.h"
|
||||
|
||||
bool erc20_plugin_available_check(void);
|
||||
bool erc721_plugin_available_check(void);
|
||||
|
||||
void erc20_plugin_call(int message, void* parameters);
|
||||
void erc721_plugin_call(int message, void* parameters);
|
||||
void compound_plugin_call(int message, void* parameters);
|
||||
|
||||
void copy_address(uint8_t* dst, uint8_t* parameter, uint8_t dst_size) {
|
||||
uint8_t copy_size = MIN(dst_size, ADDRESS_LENGTH);
|
||||
memmove(dst, parameter + PARAMETER_LENGTH - copy_size, copy_size);
|
||||
}
|
||||
|
||||
void copy_parameter(uint8_t* dst, uint8_t* parameter, uint8_t dst_size) {
|
||||
uint8_t copy_size = MIN(dst_size, PARAMETER_LENGTH);
|
||||
memmove(dst, parameter, copy_size);
|
||||
}
|
||||
|
||||
#ifdef HAVE_STARKWARE
|
||||
void starkware_plugin_call(int message, void* parameters);
|
||||
#endif
|
||||
@@ -19,10 +28,6 @@ static const uint8_t ERC20_APPROVE_SELECTOR[SELECTOR_SIZE] = {0x09, 0x5e, 0xa7,
|
||||
const uint8_t* const ERC20_SELECTORS[NUM_ERC20_SELECTORS] = {ERC20_TRANSFER_SELECTOR,
|
||||
ERC20_APPROVE_SELECTOR};
|
||||
|
||||
static const uint8_t ERC721_APPROVE_SELECTOR[SELECTOR_SIZE] = {0x09, 0x5e, 0xa7, 0xb3};
|
||||
|
||||
const uint8_t* const ERC721_SELECTORS[NUM_ERC721_SELECTORS] = {ERC721_APPROVE_SELECTOR};
|
||||
|
||||
static const uint8_t COMPOUND_REDEEM_UNDERLYING_SELECTOR[SELECTOR_SIZE] = {0x85, 0x2a, 0x12, 0xe3};
|
||||
static const uint8_t COMPOUND_REDEEM_SELECTOR[SELECTOR_SIZE] = {0xdb, 0x00, 0x6a, 0x75};
|
||||
static const uint8_t COMPOUND_MINT_SELECTOR[SELECTOR_SIZE] = {0xa0, 0x71, 0x2d, 0x68};
|
||||
@@ -105,12 +110,6 @@ const internalEthPlugin_t INTERNAL_ETH_PLUGINS[] = {
|
||||
"-erc20",
|
||||
erc20_plugin_call},
|
||||
|
||||
{erc721_plugin_available_check,
|
||||
(const uint8_t**) ERC721_SELECTORS,
|
||||
NUM_ERC721_SELECTORS,
|
||||
"-er721",
|
||||
erc721_plugin_call},
|
||||
|
||||
{NULL,
|
||||
(const uint8_t**) COMPOUND_SELECTORS,
|
||||
NUM_COMPOUND_SELECTORS,
|
||||
|
||||
@@ -6,6 +6,13 @@
|
||||
#define PARAMETER_LENGTH 32
|
||||
#define RUN_APPLICATION 1
|
||||
|
||||
void copy_address(uint8_t* dst, uint8_t* parameter, uint8_t dst_size);
|
||||
|
||||
void copy_parameter(uint8_t* dst, uint8_t* parameter, uint8_t dst_size);
|
||||
|
||||
void erc721_plugin_call(int message, void* parameters);
|
||||
void erc1155_plugin_call(int message, void* parameters);
|
||||
|
||||
typedef bool (*PluginAvailableCheck)(void);
|
||||
|
||||
typedef struct internalEthPlugin_t {
|
||||
@@ -19,9 +26,6 @@ typedef struct internalEthPlugin_t {
|
||||
#define NUM_ERC20_SELECTORS 2
|
||||
extern const uint8_t* const ERC20_SELECTORS[NUM_ERC20_SELECTORS];
|
||||
|
||||
#define NUM_ERC721_SELECTORS 1
|
||||
extern const uint8_t* const ERC721_SELECTORS[NUM_ERC721_SELECTORS];
|
||||
|
||||
#define NUM_COMPOUND_SELECTORS 4
|
||||
extern const uint8_t* const COMPOUND_SELECTORS[NUM_COMPOUND_SELECTORS];
|
||||
|
||||
|
||||
54
src/main.c
@@ -50,7 +50,7 @@ cx_sha3_t global_sha3;
|
||||
|
||||
uint8_t appState;
|
||||
bool called_from_swap;
|
||||
bool externalPluginIsSet;
|
||||
pluginType_t pluginType;
|
||||
#ifdef HAVE_STARKWARE
|
||||
bool quantumSet;
|
||||
#endif
|
||||
@@ -72,7 +72,7 @@ void reset_app_context() {
|
||||
// PRINTF("!!RESET_APP_CONTEXT\n");
|
||||
appState = APP_STATE_IDLE;
|
||||
called_from_swap = false;
|
||||
externalPluginIsSet = false;
|
||||
pluginType = OLD_INTERNAL;
|
||||
#ifdef HAVE_STARKWARE
|
||||
quantumSet = false;
|
||||
#endif
|
||||
@@ -155,8 +155,8 @@ unsigned short io_exchange_al(unsigned char channel, unsigned short tx_len) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
tokenDefinition_t *getKnownToken(uint8_t *contractAddress) {
|
||||
tokenDefinition_t *currentToken = NULL;
|
||||
extraInfo_t *getKnownToken(uint8_t *contractAddress) {
|
||||
union extraInfo_t *currentItem = NULL;
|
||||
#ifdef HAVE_TOKENS_LIST
|
||||
uint32_t numTokens = 0;
|
||||
uint32_t i;
|
||||
@@ -380,12 +380,22 @@ tokenDefinition_t *getKnownToken(uint8_t *contractAddress) {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
for (size_t i = 0; i < MAX_TOKEN; i++) {
|
||||
currentToken = &tmpCtx.transactionContext.tokens[i];
|
||||
//
|
||||
for (uint8_t i = 0; i < MAX_ITEMS; i++) {
|
||||
currentItem = (union extraInfo_t *) &tmpCtx.transactionContext.extraInfo[i].token;
|
||||
if (tmpCtx.transactionContext.tokenSet[i] &&
|
||||
(memcmp(currentToken->address, contractAddress, ADDRESS_LENGTH) == 0)) {
|
||||
(memcmp(currentItem->token.address, contractAddress, ADDRESS_LENGTH) == 0)) {
|
||||
PRINTF("Token found at index %d\n", i);
|
||||
return currentToken;
|
||||
return currentItem;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < MAX_ITEMS; i++) {
|
||||
currentItem = (union extraInfo_t *) &tmpCtx.transactionContext.extraInfo[i].token;
|
||||
if (tmpCtx.transactionContext.tokenSet[i] &&
|
||||
(memcmp(currentItem->nft.contractAddress, contractAddress, ADDRESS_LENGTH) == 0)) {
|
||||
PRINTF("Token found at index %d\n", i);
|
||||
return currentItem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,7 +495,7 @@ void handleApdu(unsigned int *flags, unsigned int *tx) {
|
||||
|
||||
switch (G_io_apdu_buffer[OFFSET_INS]) {
|
||||
case INS_GET_PUBLIC_KEY:
|
||||
memset(tmpCtx.transactionContext.tokenSet, 0, MAX_TOKEN);
|
||||
memset(tmpCtx.transactionContext.tokenSet, 0, MAX_ITEMS);
|
||||
handleGetPublicKey(G_io_apdu_buffer[OFFSET_P1],
|
||||
G_io_apdu_buffer[OFFSET_P2],
|
||||
G_io_apdu_buffer + OFFSET_CDATA,
|
||||
@@ -503,6 +513,15 @@ void handleApdu(unsigned int *flags, unsigned int *tx) {
|
||||
tx);
|
||||
break;
|
||||
|
||||
case INS_PROVIDE_NFT_INFORMATION:
|
||||
handleProvideNFTInformation(G_io_apdu_buffer[OFFSET_P1],
|
||||
G_io_apdu_buffer[OFFSET_P2],
|
||||
G_io_apdu_buffer + OFFSET_CDATA,
|
||||
G_io_apdu_buffer[OFFSET_LC],
|
||||
flags,
|
||||
tx);
|
||||
break;
|
||||
|
||||
case INS_SET_EXTERNAL_PLUGIN:
|
||||
handleSetExternalPlugin(G_io_apdu_buffer[OFFSET_P1],
|
||||
G_io_apdu_buffer[OFFSET_P2],
|
||||
@@ -512,6 +531,15 @@ void handleApdu(unsigned int *flags, unsigned int *tx) {
|
||||
tx);
|
||||
break;
|
||||
|
||||
case INS_SET_PLUGIN:
|
||||
handleSetPlugin(G_io_apdu_buffer[OFFSET_P1],
|
||||
G_io_apdu_buffer[OFFSET_P2],
|
||||
G_io_apdu_buffer + OFFSET_CDATA,
|
||||
G_io_apdu_buffer[OFFSET_LC],
|
||||
flags,
|
||||
tx);
|
||||
break;
|
||||
|
||||
case INS_SIGN:
|
||||
handleSign(G_io_apdu_buffer[OFFSET_P1],
|
||||
G_io_apdu_buffer[OFFSET_P2],
|
||||
@@ -531,7 +559,7 @@ void handleApdu(unsigned int *flags, unsigned int *tx) {
|
||||
break;
|
||||
|
||||
case INS_SIGN_PERSONAL_MESSAGE:
|
||||
memset(tmpCtx.transactionContext.tokenSet, 0, MAX_TOKEN);
|
||||
memset(tmpCtx.transactionContext.tokenSet, 0, MAX_ITEMS);
|
||||
handleSignPersonalMessage(G_io_apdu_buffer[OFFSET_P1],
|
||||
G_io_apdu_buffer[OFFSET_P2],
|
||||
G_io_apdu_buffer + OFFSET_CDATA,
|
||||
@@ -541,7 +569,7 @@ void handleApdu(unsigned int *flags, unsigned int *tx) {
|
||||
break;
|
||||
|
||||
case INS_SIGN_EIP_712_MESSAGE:
|
||||
memset(tmpCtx.transactionContext.tokenSet, 0, MAX_TOKEN);
|
||||
memset(tmpCtx.transactionContext.tokenSet, 0, MAX_ITEMS);
|
||||
handleSignEIP712Message(G_io_apdu_buffer[OFFSET_P1],
|
||||
G_io_apdu_buffer[OFFSET_P2],
|
||||
G_io_apdu_buffer + OFFSET_CDATA,
|
||||
@@ -553,7 +581,7 @@ void handleApdu(unsigned int *flags, unsigned int *tx) {
|
||||
#ifdef HAVE_ETH2
|
||||
|
||||
case INS_GET_ETH2_PUBLIC_KEY:
|
||||
memset(tmpCtx.transactionContext.tokenSet, 0, MAX_TOKEN);
|
||||
memset(tmpCtx.transactionContext.tokenSet, 0, MAX_ITEMS);
|
||||
handleGetEth2PublicKey(G_io_apdu_buffer[OFFSET_P1],
|
||||
G_io_apdu_buffer[OFFSET_P2],
|
||||
G_io_apdu_buffer + OFFSET_CDATA,
|
||||
@@ -762,7 +790,7 @@ void coin_main(chain_config_t *coin_config) {
|
||||
chainConfig = coin_config;
|
||||
}
|
||||
reset_app_context();
|
||||
tmpCtx.transactionContext.currentTokenIndex = 0;
|
||||
tmpCtx.transactionContext.currentItemIndex = 0;
|
||||
|
||||
for (;;) {
|
||||
UX_INIT();
|
||||
|
||||
14
src/nft.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#include "tokens.h"
|
||||
|
||||
// An `nftInfo_t` must be the same size as a `tokenDefinition_t`. This is because both will be held
|
||||
// in a `extraInfo_t` which is a union of a `nftInfo_t` and a `tokenDefinition_t`. By having both
|
||||
// struct the same size, we know they will be aligned, which facilitates accessing the items.
|
||||
|
||||
// We defined the collection name max length to be the size of a `tokenDefinition_t` and remove the
|
||||
// `ADDRESS_LENGTH` which corresponds to `sizeof(contractAddress`).
|
||||
#define COLLECTION_NAME_MAX_LEN sizeof(tokenDefinition_t) - ADDRESS_LENGTH
|
||||
|
||||
typedef struct nftInfo_t {
|
||||
char collectionName[COLLECTION_NAME_MAX_LEN];
|
||||
char contractAddress[ADDRESS_LENGTH];
|
||||
} nftInfo_t;
|
||||
@@ -14,16 +14,16 @@
|
||||
#include "uint256.h"
|
||||
#include "tokens.h"
|
||||
#include "chainConfig.h"
|
||||
#include "eth_plugin_interface.h"
|
||||
#include "nft.h"
|
||||
|
||||
#define MAX_BIP32_PATH 10
|
||||
|
||||
#define MAX_TOKEN 2
|
||||
|
||||
#define WEI_TO_ETHER 18
|
||||
|
||||
#define SELECTOR_LENGTH 4
|
||||
|
||||
#define PLUGIN_ID_LENGTH 30
|
||||
|
||||
#define N_storage (*(volatile internalStorage_t *) PIC(&N_storage_real))
|
||||
|
||||
typedef struct internalStorage_t {
|
||||
@@ -62,8 +62,8 @@ typedef struct tokenContext_t {
|
||||
|
||||
union {
|
||||
struct {
|
||||
uint8_t contract_address[ADDRESS_LENGTH];
|
||||
uint8_t method_selector[SELECTOR_LENGTH];
|
||||
uint8_t contractAddress[ADDRESS_LENGTH];
|
||||
uint8_t methodSelector[SELECTOR_LENGTH];
|
||||
};
|
||||
uint8_t pluginContext[5 * INT256_LENGTH];
|
||||
};
|
||||
@@ -84,13 +84,18 @@ typedef struct publicKeyContext_t {
|
||||
bool getChaincode;
|
||||
} publicKeyContext_t;
|
||||
|
||||
typedef union extraInfo_t {
|
||||
tokenDefinition_t token;
|
||||
nftInfo_t nft;
|
||||
} extraInfo_t;
|
||||
|
||||
typedef struct transactionContext_t {
|
||||
uint8_t pathLength;
|
||||
uint32_t bip32Path[MAX_BIP32_PATH];
|
||||
uint8_t hash[INT256_LENGTH];
|
||||
tokenDefinition_t tokens[MAX_TOKEN];
|
||||
uint8_t tokenSet[MAX_TOKEN];
|
||||
uint8_t currentTokenIndex;
|
||||
union extraInfo_t extraInfo[MAX_ITEMS];
|
||||
uint8_t tokenSet[MAX_ITEMS];
|
||||
uint8_t currentItemIndex;
|
||||
} transactionContext_t;
|
||||
|
||||
typedef struct messageSigningContext_t {
|
||||
@@ -137,6 +142,7 @@ typedef struct starkContext_t {
|
||||
|
||||
typedef union {
|
||||
tokenContext_t tokenContext;
|
||||
|
||||
#ifdef HAVE_STARKWARE
|
||||
starkContext_t starkContext;
|
||||
#endif
|
||||
@@ -166,7 +172,7 @@ typedef enum {
|
||||
|
||||
typedef struct txStringProperties_t {
|
||||
char fullAddress[43];
|
||||
char fullAmount[67];
|
||||
char fullAmount[79]; // 2^256 is 78 digits long
|
||||
char maxFee[50];
|
||||
char nonce[8]; // 10M tx per account ought to be enough for everybody
|
||||
char network_name[NETWORK_STRING_MAX_SIZE];
|
||||
@@ -196,7 +202,17 @@ extern cx_sha3_t global_sha3;
|
||||
extern const internalStorage_t N_storage_real;
|
||||
|
||||
extern bool called_from_swap;
|
||||
extern bool externalPluginIsSet;
|
||||
|
||||
typedef enum {
|
||||
EXTERNAL, // External plugin, set by setExternalPlugin.
|
||||
ERC721, // Specific ERC721 internal plugin, set by setPlugin.
|
||||
ERC1155, // Specific ERC1155 internal plugin, set by setPlugin
|
||||
OLD_INTERNAL, // Old internal plugin, not set by any command.
|
||||
NOT_OLD_INTERNAL, // Do not treat this tx as an old internal transaction.
|
||||
} pluginType_t;
|
||||
|
||||
extern pluginType_t pluginType;
|
||||
|
||||
extern uint8_t appState;
|
||||
#ifdef HAVE_STARKWARE
|
||||
extern bool quantumSet;
|
||||
|
||||
@@ -68,7 +68,7 @@ void stark_get_amount_string(uint8_t *contractAddress,
|
||||
decimals = WEI_TO_ETHER;
|
||||
PRINTF("stark_get_amount_string - ETH\n");
|
||||
} else {
|
||||
tokenDefinition_t *token = getKnownToken(contractAddress);
|
||||
tokenDefinition_t *token = &getKnownToken(contractAddress)->token;
|
||||
if (token == NULL) { // caught earlier
|
||||
THROW(0x6A80);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "ethUstream.h"
|
||||
|
||||
#define MAX_TICKER_LEN 12 // 10 characters + ' ' + '\0'
|
||||
#define MAX_ITEMS 2
|
||||
|
||||
typedef struct tokenDefinition_t {
|
||||
#ifdef HAVE_CONTRACT_NAME_IN_DESCRIPTOR
|
||||
@@ -29,6 +30,9 @@ typedef struct tokenDefinition_t {
|
||||
#endif
|
||||
uint8_t address[ADDRESS_LENGTH];
|
||||
char ticker[MAX_TICKER_LEN];
|
||||
char nft_pad[20]; // Adding some padding because the `nftInfo_t` is based on the size of a
|
||||
// `tokenDefinition_t`. By adding some padding here we give more space to the
|
||||
// collection name in the `nftInfo_t`. See `nftInfo_t` for more information.
|
||||
uint8_t decimals;
|
||||
} tokenDefinition_t;
|
||||
|
||||
|
||||
@@ -21,4 +21,4 @@ void ui_warning_contract_data(void);
|
||||
void io_seproxyhal_send_status(uint32_t sw);
|
||||
void format_signature_out(const uint8_t *signature);
|
||||
void finalizeParsing(bool direct);
|
||||
tokenDefinition_t *getKnownToken(uint8_t *contractAddress);
|
||||
extraInfo_t *getKnownToken(uint8_t *contractAddress);
|
||||
|
||||
@@ -254,6 +254,7 @@ static void processTo(txContext_t *context) {
|
||||
}
|
||||
|
||||
static void processData(txContext_t *context) {
|
||||
PRINTF("PROCESS DATA\n");
|
||||
if (context->currentFieldIsList) {
|
||||
PRINTF("Invalid type for RLP_DATA\n");
|
||||
THROW(EXCEPTION);
|
||||
@@ -268,6 +269,7 @@ static void processData(txContext_t *context) {
|
||||
copyTxData(context, NULL, copySize);
|
||||
}
|
||||
if (context->currentFieldPos == context->currentFieldLength) {
|
||||
PRINTF("incrementing field\n");
|
||||
context->currentField++;
|
||||
context->processingField = false;
|
||||
}
|
||||
@@ -506,6 +508,7 @@ static parserStatus_e processTxInternal(txContext_t *context) {
|
||||
customStatus_e customStatus = CUSTOM_NOT_HANDLED;
|
||||
// EIP 155 style transaction
|
||||
if (PARSING_IS_DONE(context)) {
|
||||
PRINTF("parsing is done\n");
|
||||
return USTREAM_FINISHED;
|
||||
}
|
||||
// Old style transaction (pre EIP-155). Transations could just skip `v,r,s` so we needed to
|
||||
@@ -518,9 +521,11 @@ static parserStatus_e processTxInternal(txContext_t *context) {
|
||||
if ((context->txType == LEGACY && context->currentField == LEGACY_RLP_V) &&
|
||||
(context->commandLength == 0)) {
|
||||
context->content->vLength = 0;
|
||||
PRINTF("finished\n");
|
||||
return USTREAM_FINISHED;
|
||||
}
|
||||
if (context->commandLength == 0) {
|
||||
PRINTF("Command length done\n");
|
||||
return USTREAM_PROCESSING;
|
||||
}
|
||||
if (!context->processingField) {
|
||||
@@ -531,6 +536,7 @@ static parserStatus_e processTxInternal(txContext_t *context) {
|
||||
}
|
||||
if (context->customProcessor != NULL) {
|
||||
customStatus = context->customProcessor(context);
|
||||
PRINTF("After customprocessor\n");
|
||||
switch (customStatus) {
|
||||
case CUSTOM_NOT_HANDLED:
|
||||
case CUSTOM_HANDLED:
|
||||
@@ -571,11 +577,12 @@ static parserStatus_e processTxInternal(txContext_t *context) {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
PRINTF("Transaction type %u is not supported\n", context->txType);
|
||||
PRINTF("Transaction type %d is not supported\n", context->txType);
|
||||
return USTREAM_FAULT;
|
||||
}
|
||||
}
|
||||
}
|
||||
PRINTF("end of here\n");
|
||||
}
|
||||
|
||||
parserStatus_e processTx(txContext_t *context,
|
||||
@@ -589,6 +596,7 @@ parserStatus_e processTx(txContext_t *context,
|
||||
context->commandLength = length;
|
||||
context->processingFlags = processingFlags;
|
||||
result = processTxInternal(context);
|
||||
PRINTF("result: %d\n");
|
||||
}
|
||||
CATCH_OTHER(e) {
|
||||
result = USTREAM_FAULT;
|
||||
|
||||
@@ -23,10 +23,10 @@ void handleProvideErc20TokenInformation(uint8_t p1,
|
||||
|
||||
cx_sha256_init(&sha256);
|
||||
|
||||
tmpCtx.transactionContext.currentTokenIndex =
|
||||
(tmpCtx.transactionContext.currentTokenIndex + 1) % MAX_TOKEN;
|
||||
tmpCtx.transactionContext.currentItemIndex =
|
||||
(tmpCtx.transactionContext.currentItemIndex + 1) % MAX_ITEMS;
|
||||
tokenDefinition_t *token =
|
||||
&tmpCtx.transactionContext.tokens[tmpCtx.transactionContext.currentTokenIndex];
|
||||
&tmpCtx.transactionContext.tokens[tmpCtx.transactionContext.currentItemIndex];
|
||||
|
||||
if (dataLength < 1) {
|
||||
THROW(0x6A80);
|
||||
@@ -93,7 +93,7 @@ void handleProvideErc20TokenInformation(uint8_t p1,
|
||||
THROW(0x6A80);
|
||||
#endif
|
||||
}
|
||||
tmpCtx.transactionContext.tokenSet[tmpCtx.transactionContext.currentTokenIndex] = 1;
|
||||
tmpCtx.transactionContext.tokenSet[tmpCtx.transactionContext.currentItemIndex] = 1;
|
||||
THROW(0x9000);
|
||||
}
|
||||
|
||||
@@ -114,12 +114,12 @@ void handleProvideErc20TokenInformation(uint8_t p1,
|
||||
uint8_t hash[INT256_LENGTH];
|
||||
cx_ecfp_public_key_t tokenKey;
|
||||
|
||||
tmpCtx.transactionContext.currentTokenIndex =
|
||||
(tmpCtx.transactionContext.currentTokenIndex + 1) % MAX_TOKEN;
|
||||
tmpCtx.transactionContext.currentItemIndex =
|
||||
(tmpCtx.transactionContext.currentItemIndex + 1) % MAX_ITEMS;
|
||||
tokenDefinition_t *token =
|
||||
&tmpCtx.transactionContext.tokens[tmpCtx.transactionContext.currentTokenIndex];
|
||||
&tmpCtx.transactionContext.extraInfo[tmpCtx.transactionContext.currentItemIndex].token;
|
||||
|
||||
PRINTF("Provisioning currentTokenIndex %d\n", tmpCtx.transactionContext.currentTokenIndex);
|
||||
PRINTF("Provisioning currentItemIndex %d\n", tmpCtx.transactionContext.currentItemIndex);
|
||||
|
||||
if (dataLength < 1) {
|
||||
THROW(0x6A80);
|
||||
@@ -204,7 +204,7 @@ void handleProvideErc20TokenInformation(uint8_t p1,
|
||||
}
|
||||
#endif
|
||||
|
||||
tmpCtx.transactionContext.tokenSet[tmpCtx.transactionContext.currentTokenIndex] = 1;
|
||||
tmpCtx.transactionContext.tokenSet[tmpCtx.transactionContext.currentItemIndex] = 1;
|
||||
THROW(0x9000);
|
||||
}
|
||||
|
||||
|
||||
219
src_features/provideNFTInformation/cmd_provideNFTInfo.c
Normal file
@@ -0,0 +1,219 @@
|
||||
#include "shared_context.h"
|
||||
#include "apdu_constants.h"
|
||||
#include "ui_flow.h"
|
||||
#include "tokens.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define TYPE_SIZE 1
|
||||
#define VERSION_SIZE 1
|
||||
#define NAME_LENGTH_SIZE 1
|
||||
#define HEADER_SIZE TYPE_SIZE + VERSION_SIZE + NAME_LENGTH_SIZE
|
||||
|
||||
#define CHAIN_ID_SIZE 8
|
||||
#define KEY_ID_SIZE 1
|
||||
#define ALGORITHM_ID_SIZE 1
|
||||
#define SIGNATURE_LENGTH_SIZE 1
|
||||
#define MIN_DER_SIG_SIZE 67
|
||||
#define MAX_DER_SIG_SIZE 72
|
||||
|
||||
#define TESTING_KEY 0
|
||||
#define NFT_METADATA_KEY_1 1
|
||||
|
||||
#define ALGORITHM_ID_1 1
|
||||
|
||||
#define TYPE_1 1
|
||||
|
||||
#define VERSION_1 1
|
||||
|
||||
#ifdef HAVE_NFT_TESTING_KEY
|
||||
static const uint8_t LEDGER_NFT_PUBLIC_KEY[] = {
|
||||
0x04, 0xf5, 0x70, 0x0c, 0xa1, 0xe8, 0x74, 0x24, 0xc7, 0xc7, 0xd1, 0x19, 0xe7,
|
||||
0xe3, 0xc1, 0x89, 0xb1, 0x62, 0x50, 0x94, 0xdb, 0x6e, 0xa0, 0x40, 0x87, 0xc8,
|
||||
0x30, 0x00, 0x7d, 0x0b, 0x46, 0x9a, 0x53, 0x11, 0xee, 0x6a, 0x1a, 0xcd, 0x1d,
|
||||
0xa5, 0xaa, 0xb0, 0xf5, 0xc6, 0xdf, 0x13, 0x15, 0x8d, 0x28, 0xcc, 0x12, 0xd1,
|
||||
0xdd, 0xa6, 0xec, 0xe9, 0x46, 0xb8, 0x9d, 0x5c, 0x05, 0x49, 0x92, 0x59, 0xc4};
|
||||
|
||||
#else
|
||||
static const uint8_t LEDGER_NFT_PUBLIC_KEY[] = {};
|
||||
#endif
|
||||
|
||||
typedef bool verificationAlgo(const cx_ecfp_public_key_t *,
|
||||
int,
|
||||
cx_md_t,
|
||||
const unsigned char *,
|
||||
unsigned int,
|
||||
unsigned char *,
|
||||
unsigned int);
|
||||
|
||||
void handleProvideNFTInformation(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *workBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx) {
|
||||
UNUSED(p1);
|
||||
UNUSED(p2);
|
||||
UNUSED(tx);
|
||||
UNUSED(flags);
|
||||
uint8_t hash[INT256_LENGTH];
|
||||
cx_ecfp_public_key_t nftKey;
|
||||
PRINTF("In handle provide NFTInformation");
|
||||
|
||||
tmpCtx.transactionContext.currentItemIndex =
|
||||
(tmpCtx.transactionContext.currentItemIndex + 1) % MAX_ITEMS;
|
||||
nftInfo_t *nft =
|
||||
&tmpCtx.transactionContext.extraInfo[tmpCtx.transactionContext.currentItemIndex].nft;
|
||||
|
||||
PRINTF("Provisioning currentItemIndex %d\n", tmpCtx.transactionContext.currentItemIndex);
|
||||
|
||||
uint8_t offset = 0;
|
||||
|
||||
if (dataLength <= HEADER_SIZE) {
|
||||
PRINTF("Data too small for headers: expected at least %d, got %d\n",
|
||||
HEADER_SIZE,
|
||||
dataLength);
|
||||
THROW(0x6A80);
|
||||
}
|
||||
|
||||
uint8_t type = workBuffer[offset];
|
||||
switch (type) {
|
||||
case TYPE_1:
|
||||
break;
|
||||
default:
|
||||
PRINTF("Unsupported type %d\n", type);
|
||||
THROW(0x6a80);
|
||||
break;
|
||||
}
|
||||
offset += TYPE_SIZE;
|
||||
|
||||
uint8_t version = workBuffer[offset];
|
||||
switch (version) {
|
||||
case VERSION_1:
|
||||
break;
|
||||
default:
|
||||
PRINTF("Unsupported version %d\n", version);
|
||||
THROW(0x6a80);
|
||||
break;
|
||||
}
|
||||
offset += VERSION_SIZE;
|
||||
|
||||
uint8_t collectionNameLength = workBuffer[offset];
|
||||
offset += NAME_LENGTH_SIZE;
|
||||
|
||||
// Size of the payload (everything except the signature)
|
||||
uint8_t payloadSize = HEADER_SIZE + collectionNameLength + ADDRESS_LENGTH + CHAIN_ID_SIZE +
|
||||
KEY_ID_SIZE + ALGORITHM_ID_SIZE;
|
||||
if (dataLength < payloadSize) {
|
||||
PRINTF("Data too small for payload: expected at least %d, got %d\n",
|
||||
payloadSize,
|
||||
dataLength);
|
||||
THROW(0x6A80);
|
||||
}
|
||||
|
||||
if (collectionNameLength + 1 > sizeof(nft->collectionName)) {
|
||||
PRINTF("CollectionName too big: expected max %d, got %d\n",
|
||||
sizeof(nft->collectionName),
|
||||
collectionNameLength + 1);
|
||||
THROW(0x6A80);
|
||||
}
|
||||
|
||||
// Safe because we've checked the size before.
|
||||
memcpy(nft->collectionName, workBuffer + offset, collectionNameLength);
|
||||
nft->collectionName[collectionNameLength] = '\0';
|
||||
|
||||
PRINTF("Length: %d\n", collectionNameLength);
|
||||
PRINTF("CollectionName: %s\n", nft->collectionName);
|
||||
offset += collectionNameLength;
|
||||
|
||||
memcpy(nft->contractAddress, workBuffer + offset, ADDRESS_LENGTH);
|
||||
PRINTF("Address: %.*H\n", ADDRESS_LENGTH, workBuffer + offset);
|
||||
offset += ADDRESS_LENGTH;
|
||||
|
||||
// TODO: store chainID and assert that tx is using the same chainid.
|
||||
// uint64_t chainid = u64_from_BE(workBuffer + offset, CHAIN_ID_SIZE);
|
||||
// PRINTF("ChainID: %.*H\n", sizeof(chainid), &chainid);
|
||||
offset += CHAIN_ID_SIZE;
|
||||
|
||||
uint8_t keyId = workBuffer[offset];
|
||||
uint8_t *rawKey;
|
||||
uint8_t rawKeyLen;
|
||||
|
||||
PRINTF("KeyID: %d\n", keyId);
|
||||
switch (keyId) {
|
||||
#ifdef HAVE_NFT_TESTING_KEY
|
||||
case TESTING_KEY:
|
||||
#endif
|
||||
case NFT_METADATA_KEY_1:
|
||||
rawKey = (uint8_t *) LEDGER_NFT_PUBLIC_KEY;
|
||||
rawKeyLen = sizeof(LEDGER_NFT_PUBLIC_KEY);
|
||||
break;
|
||||
default:
|
||||
PRINTF("KeyID %d not supported\n", keyId);
|
||||
THROW(0x6A80);
|
||||
break;
|
||||
}
|
||||
PRINTF("RawKey: %.*H\n", rawKeyLen, rawKey);
|
||||
offset += KEY_ID_SIZE;
|
||||
|
||||
uint8_t algorithmId = workBuffer[offset];
|
||||
PRINTF("Algorithm: %d\n", algorithmId);
|
||||
cx_curve_t curve;
|
||||
verificationAlgo *verificationFn;
|
||||
cx_md_t hashId;
|
||||
|
||||
switch (algorithmId) {
|
||||
case ALGORITHM_ID_1:
|
||||
curve = CX_CURVE_256K1;
|
||||
verificationFn = (verificationAlgo*)cx_ecdsa_verify;
|
||||
hashId = CX_SHA256;
|
||||
break;
|
||||
default:
|
||||
PRINTF("Incorrect algorithmId %d\n", algorithmId);
|
||||
THROW(0x6a80);
|
||||
break;
|
||||
}
|
||||
offset += ALGORITHM_ID_SIZE;
|
||||
PRINTF("hashing: %.*H\n", payloadSize, workBuffer);
|
||||
cx_hash_sha256(workBuffer, payloadSize, hash, sizeof(hash));
|
||||
|
||||
if (dataLength < payloadSize + SIGNATURE_LENGTH_SIZE) {
|
||||
PRINTF("Data too short to hold signature length\n");
|
||||
THROW(0x6a80);
|
||||
}
|
||||
|
||||
uint8_t signatureLen = workBuffer[offset];
|
||||
PRINTF("Sigature len: %d\n", signatureLen);
|
||||
if (signatureLen < MIN_DER_SIG_SIZE || signatureLen > MAX_DER_SIG_SIZE) {
|
||||
PRINTF("SignatureLen too big or too small. Must be between %d and %d, got %d\n",
|
||||
MIN_DER_SIG_SIZE,
|
||||
MAX_DER_SIG_SIZE,
|
||||
signatureLen);
|
||||
THROW(0x6a80);
|
||||
}
|
||||
offset += SIGNATURE_LENGTH_SIZE;
|
||||
|
||||
if (dataLength < payloadSize + SIGNATURE_LENGTH_SIZE + signatureLen) {
|
||||
PRINTF("Signature could not fit in data\n");
|
||||
THROW(0x6a80);
|
||||
}
|
||||
|
||||
cx_ecfp_init_public_key(curve, rawKey, rawKeyLen, &nftKey);
|
||||
if (!verificationFn(&nftKey,
|
||||
CX_LAST,
|
||||
hashId,
|
||||
hash,
|
||||
sizeof(hash),
|
||||
workBuffer + offset,
|
||||
signatureLen)) {
|
||||
#ifndef HAVE_BYPASS_SIGNATURES
|
||||
PRINTF("Invalid NFT signature\n");
|
||||
THROW(0x6A80);
|
||||
#endif
|
||||
}
|
||||
// Set this to `NOT_OLD_INTERNAL` because otherwise the tx might be treated as an
|
||||
// internal plugin and we might get a collision and hence some BIG problems.
|
||||
pluginType = NOT_OLD_INTERNAL;
|
||||
|
||||
tmpCtx.transactionContext.tokenSet[tmpCtx.transactionContext.currentItemIndex] = 1;
|
||||
THROW(0x9000);
|
||||
}
|
||||
@@ -2,8 +2,8 @@
|
||||
#include "apdu_constants.h"
|
||||
#include "ui_flow.h"
|
||||
#include "tokens.h"
|
||||
|
||||
#define SELECTOR_SIZE 4
|
||||
#include "eth_plugin_interface.h"
|
||||
#include "eth_plugin_internal.h"
|
||||
|
||||
void handleSetExternalPlugin(uint8_t p1,
|
||||
uint8_t p2,
|
||||
@@ -14,17 +14,22 @@ void handleSetExternalPlugin(uint8_t p1,
|
||||
UNUSED(p1);
|
||||
UNUSED(p2);
|
||||
UNUSED(flags);
|
||||
PRINTF("Handling set External Plugin\n");
|
||||
uint8_t hash[32];
|
||||
PRINTF("Handling set Plugin\n");
|
||||
uint8_t hash[INT256_LENGTH];
|
||||
cx_ecfp_public_key_t tokenKey;
|
||||
uint8_t pluginNameLength = *workBuffer;
|
||||
PRINTF("plugin Name Length: %d\n", pluginNameLength);
|
||||
const size_t payload_size = 1 + pluginNameLength + ADDRESS_LENGTH + SELECTOR_SIZE;
|
||||
|
||||
if (dataLength <= payload_size) {
|
||||
PRINTF("data too small: expected at least %d got %d\n", payload_size, dataLength);
|
||||
THROW(0x6A80);
|
||||
}
|
||||
|
||||
if (pluginNameLength + 1 > sizeof(dataContext.tokenContext.pluginName)) {
|
||||
PRINTF("name length too big: expected max %d, got %d\n",
|
||||
sizeof(dataContext.tokenContext.pluginName),
|
||||
pluginNameLength + 1);
|
||||
THROW(0x6A80);
|
||||
}
|
||||
|
||||
@@ -41,8 +46,8 @@ void handleSetExternalPlugin(uint8_t p1,
|
||||
sizeof(hash),
|
||||
workBuffer + payload_size,
|
||||
dataLength - payload_size)) {
|
||||
PRINTF("Invalid external plugin signature %.*H\n", payload_size, workBuffer);
|
||||
#ifndef HAVE_BYPASS_SIGNATURES
|
||||
PRINTF("Invalid plugin signature %.*H\n", payload_size, workBuffer);
|
||||
THROW(0x6A80);
|
||||
#endif
|
||||
}
|
||||
@@ -77,10 +82,11 @@ void handleSetExternalPlugin(uint8_t p1,
|
||||
|
||||
PRINTF("Plugin found\n");
|
||||
|
||||
memmove(dataContext.tokenContext.contract_address, workBuffer, ADDRESS_LENGTH);
|
||||
memmove(dataContext.tokenContext.contractAddress, workBuffer, ADDRESS_LENGTH);
|
||||
workBuffer += ADDRESS_LENGTH;
|
||||
memmove(dataContext.tokenContext.method_selector, workBuffer, SELECTOR_SIZE);
|
||||
externalPluginIsSet = true;
|
||||
memmove(dataContext.tokenContext.methodSelector, workBuffer, SELECTOR_SIZE);
|
||||
|
||||
pluginType = EXTERNAL;
|
||||
|
||||
G_io_apdu_buffer[(*tx)++] = 0x90;
|
||||
G_io_apdu_buffer[(*tx)++] = 0x00;
|
||||
|
||||
294
src_features/setPlugin/cmd_setPlugin.c
Normal file
@@ -0,0 +1,294 @@
|
||||
#include "shared_context.h"
|
||||
#include "apdu_constants.h"
|
||||
#include "ui_flow.h"
|
||||
#include "tokens.h"
|
||||
#include "eth_plugin_interface.h"
|
||||
#include "eth_plugin_internal.h"
|
||||
|
||||
// Supported internal plugins
|
||||
#define ERC721_STR "ERC721"
|
||||
#define ERC1155_STR "ERC1155"
|
||||
|
||||
#define TYPE_SIZE 1
|
||||
#define VERSION_SIZE 1
|
||||
#define PLUGIN_NAME_LENGTH_SIZE 1
|
||||
#define CHAIN_ID_SIZE 8
|
||||
#define KEY_ID_SIZE 1
|
||||
#define ALGORITHM_ID_SIZE 1
|
||||
#define SIGNATURE_LENGTH_SIZE 1
|
||||
|
||||
#define HEADER_SIZE TYPE_SIZE + VERSION_SIZE + PLUGIN_NAME_LENGTH_SIZE
|
||||
|
||||
#define MIN_DER_SIG_SIZE 67
|
||||
#define MAX_DER_SIG_SIZE 72
|
||||
|
||||
typedef enum Type {
|
||||
ETH_PLUGIN = 0x01,
|
||||
} Type;
|
||||
|
||||
typedef enum Version {
|
||||
VERSION_1 = 0x01,
|
||||
} Version;
|
||||
|
||||
typedef enum KeyId {
|
||||
TEST_KEY = 0x00,
|
||||
PERSO_V2_KEY_1 = 0x01,
|
||||
// Must ONLY be used with ERC721 and ERC1155 plugin
|
||||
AWS_PLUGIN_KEY_1 = 0x02,
|
||||
} KeyId;
|
||||
|
||||
// Algorithm Id consists of a Key spec and an algorithm spec.
|
||||
// Format is: KEYSPEC__ALGOSPEC
|
||||
typedef enum AlgorithmID {
|
||||
ECC_SECG_P256K1__ECDSA_SHA_256 = 0x01,
|
||||
} AlgorithmID;
|
||||
|
||||
#ifdef HAVE_NFT_TESTING_KEY
|
||||
static const uint8_t LEDGER_TESTING_KEY[] = {
|
||||
0x04, 0xf5, 0x70, 0x0c, 0xa1, 0xe8, 0x74, 0x24, 0xc7, 0xc7, 0xd1, 0x19, 0xe7,
|
||||
0xe3, 0xc1, 0x89, 0xb1, 0x62, 0x50, 0x94, 0xdb, 0x6e, 0xa0, 0x40, 0x87, 0xc8,
|
||||
0x30, 0x00, 0x7d, 0x0b, 0x46, 0x9a, 0x53, 0x11, 0xee, 0x6a, 0x1a, 0xcd, 0x1d,
|
||||
0xa5, 0xaa, 0xb0, 0xf5, 0xc6, 0xdf, 0x13, 0x15, 0x8d, 0x28, 0xcc, 0x12, 0xd1,
|
||||
0xdd, 0xa6, 0xec, 0xe9, 0x46, 0xb8, 0x9d, 0x5c, 0x05, 0x49, 0x92, 0x59, 0xc4};
|
||||
#endif
|
||||
static const uint8_t LEDGER_PERSO_V2_PUBLIC_KEY[] = {};
|
||||
|
||||
// Only used for signing NFT plugins (ERC721 and ERC1155)
|
||||
static const uint8_t LEDGER_NFT_SELECTOR_PUBLIC_KEY[] = {};
|
||||
|
||||
// Verification function used to verify the signature
|
||||
typedef bool verificationAlgo(const cx_ecfp_public_key_t *,
|
||||
int,
|
||||
cx_md_t,
|
||||
const unsigned char *,
|
||||
unsigned int,
|
||||
unsigned char *,
|
||||
unsigned int);
|
||||
|
||||
// Returns the plugin type of a given plugin name.
|
||||
// If the plugin name is not a specific known internal plugin, this function default return value is
|
||||
// `EXERNAL`.
|
||||
static pluginType_t getPluginType(char *pluginName, uint8_t pluginNameLength) {
|
||||
if (pluginNameLength == sizeof(ERC721_STR) - 1 &&
|
||||
strncmp(pluginName, ERC721_STR, pluginNameLength) == 0) {
|
||||
return ERC721;
|
||||
} else if (pluginNameLength == sizeof(ERC1155_STR) - 1 &&
|
||||
strncmp(pluginName, ERC1155_STR, pluginNameLength) == 0) {
|
||||
return ERC1155;
|
||||
} else {
|
||||
return EXTERNAL;
|
||||
}
|
||||
}
|
||||
|
||||
void handleSetPlugin(uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t *workBuffer,
|
||||
uint16_t dataLength,
|
||||
unsigned int *flags,
|
||||
unsigned int *tx) {
|
||||
UNUSED(p1);
|
||||
UNUSED(p2);
|
||||
UNUSED(flags);
|
||||
PRINTF("Handling set Plugin\n");
|
||||
uint8_t hash[INT256_LENGTH] = {0};
|
||||
cx_ecfp_public_key_t pluginKey = {0};
|
||||
tokenContext_t *tokenContext = &dataContext.tokenContext;
|
||||
|
||||
uint8_t offset = 0;
|
||||
|
||||
if (dataLength <= HEADER_SIZE) {
|
||||
PRINTF("Data too small for headers: expected at least %d, got %d\n",
|
||||
HEADER_SIZE,
|
||||
dataLength);
|
||||
THROW(0x6A80);
|
||||
}
|
||||
|
||||
enum Type type = workBuffer[offset];
|
||||
PRINTF("Type: %d\n", type);
|
||||
switch (type) {
|
||||
case ETH_PLUGIN:
|
||||
break;
|
||||
default:
|
||||
PRINTF("Unsupported type %d\n", type);
|
||||
THROW(0x6a80);
|
||||
break;
|
||||
}
|
||||
offset += TYPE_SIZE;
|
||||
|
||||
uint8_t version = workBuffer[offset];
|
||||
PRINTF("version: %d\n", version);
|
||||
switch (version) {
|
||||
case VERSION_1:
|
||||
break;
|
||||
default:
|
||||
PRINTF("Unsupported version %d\n", version);
|
||||
THROW(0x6a80);
|
||||
break;
|
||||
}
|
||||
offset += VERSION_SIZE;
|
||||
|
||||
uint8_t pluginNameLength = workBuffer[offset];
|
||||
offset += PLUGIN_NAME_LENGTH_SIZE;
|
||||
|
||||
// Size of the payload (everything except the signature)
|
||||
uint8_t payloadSize = HEADER_SIZE + pluginNameLength + ADDRESS_LENGTH + SELECTOR_SIZE +
|
||||
CHAIN_ID_SIZE + KEY_ID_SIZE + ALGORITHM_ID_SIZE;
|
||||
if (dataLength < payloadSize) {
|
||||
PRINTF("Data too small for payload: expected at least %d, got %d\n",
|
||||
payloadSize,
|
||||
dataLength);
|
||||
THROW(0x6A80);
|
||||
}
|
||||
|
||||
// `+ 1` because we want to add a null terminating character.
|
||||
if (pluginNameLength + 1 > sizeof(tokenContext->pluginName)) {
|
||||
PRINTF("plugin name too big: expected max %d, got %d\n",
|
||||
sizeof(dataContext.tokenContext.pluginName),
|
||||
pluginNameLength + 1);
|
||||
THROW(0x6A80);
|
||||
}
|
||||
|
||||
// Safe because we've checked the size before.
|
||||
memcpy(tokenContext->pluginName, workBuffer + offset, pluginNameLength);
|
||||
tokenContext->pluginName[pluginNameLength] = '\0';
|
||||
|
||||
PRINTF("Length: %d\n", pluginNameLength);
|
||||
PRINTF("plugin name: %s\n", tokenContext->pluginName);
|
||||
offset += pluginNameLength;
|
||||
|
||||
memcpy(tokenContext->contractAddress, workBuffer + offset, ADDRESS_LENGTH);
|
||||
PRINTF("Address: %.*H\n", ADDRESS_LENGTH, workBuffer + offset);
|
||||
offset += ADDRESS_LENGTH;
|
||||
|
||||
memcpy(tokenContext->methodSelector, workBuffer + offset, SELECTOR_SIZE);
|
||||
PRINTF("Selector: %.*H\n", SELECTOR_SIZE, tokenContext->methodSelector);
|
||||
offset += SELECTOR_SIZE;
|
||||
|
||||
// TODO: store chainID and assert that tx is using the same chainid.
|
||||
// uint64_t chainid = u64_from_BE(workBuffer + offset, CHAIN_ID_SIZE);
|
||||
// PRINTF("ChainID: %.*H\n", sizeof(chainid), &chainid);
|
||||
offset += CHAIN_ID_SIZE;
|
||||
|
||||
enum KeyId keyId = workBuffer[offset];
|
||||
uint8_t const *rawKey;
|
||||
uint8_t rawKeyLen;
|
||||
|
||||
PRINTF("KeyID: %d\n", keyId);
|
||||
switch (keyId) {
|
||||
#ifdef HAVE_NFT_TESTING_KEY
|
||||
case TEST_KEY:
|
||||
rawKey = LEDGER_TESTING_KEY;
|
||||
rawKeyLen = sizeof(LEDGER_TESTING_KEY);
|
||||
break;
|
||||
#endif
|
||||
case PERSO_V2_KEY_1:
|
||||
rawKey = LEDGER_PERSO_V2_PUBLIC_KEY;
|
||||
rawKeyLen = sizeof(LEDGER_PERSO_V2_PUBLIC_KEY);
|
||||
break;
|
||||
case AWS_PLUGIN_KEY_1:
|
||||
rawKey = LEDGER_NFT_SELECTOR_PUBLIC_KEY;
|
||||
rawKeyLen = sizeof(LEDGER_NFT_SELECTOR_PUBLIC_KEY);
|
||||
break;
|
||||
default:
|
||||
PRINTF("KeyID %d not supported\n", keyId);
|
||||
THROW(0x6A80);
|
||||
break;
|
||||
}
|
||||
|
||||
PRINTF("RawKey: %.*H\n", rawKeyLen, rawKey);
|
||||
offset += KEY_ID_SIZE;
|
||||
|
||||
uint8_t algorithmId = workBuffer[offset];
|
||||
PRINTF("Algorithm: %d\n", algorithmId);
|
||||
cx_curve_t curve;
|
||||
verificationAlgo *verificationFn;
|
||||
cx_md_t hashId;
|
||||
|
||||
switch (algorithmId) {
|
||||
case ECC_SECG_P256K1__ECDSA_SHA_256:
|
||||
curve = CX_CURVE_256K1;
|
||||
verificationFn = (verificationAlgo*)cx_ecdsa_verify;
|
||||
hashId = CX_SHA256;
|
||||
break;
|
||||
default:
|
||||
PRINTF("Incorrect algorithmId %d\n", algorithmId);
|
||||
THROW(0x6a80);
|
||||
break;
|
||||
}
|
||||
offset += ALGORITHM_ID_SIZE;
|
||||
PRINTF("hashing: %.*H\n", payloadSize, workBuffer);
|
||||
cx_hash_sha256(workBuffer, payloadSize, hash, sizeof(hash));
|
||||
|
||||
if (dataLength < payloadSize + SIGNATURE_LENGTH_SIZE) {
|
||||
PRINTF("Data too short to hold signature length\n");
|
||||
THROW(0x6a80);
|
||||
}
|
||||
|
||||
uint8_t signatureLen = workBuffer[offset];
|
||||
PRINTF("Sigature len: %d\n", signatureLen);
|
||||
if (signatureLen < MIN_DER_SIG_SIZE || signatureLen > MAX_DER_SIG_SIZE) {
|
||||
PRINTF("SignatureLen too big or too small. Must be between %d and %d, got %d\n",
|
||||
MIN_DER_SIG_SIZE,
|
||||
MAX_DER_SIG_SIZE,
|
||||
signatureLen);
|
||||
THROW(0x6a80);
|
||||
}
|
||||
offset += SIGNATURE_LENGTH_SIZE;
|
||||
|
||||
if (dataLength < payloadSize + SIGNATURE_LENGTH_SIZE + signatureLen) {
|
||||
PRINTF("Signature could not fit in data\n");
|
||||
THROW(0x6a80);
|
||||
}
|
||||
|
||||
cx_ecfp_init_public_key(curve, rawKey, rawKeyLen, &pluginKey);
|
||||
if (!verificationFn(&pluginKey,
|
||||
CX_LAST,
|
||||
hashId,
|
||||
hash,
|
||||
sizeof(hash),
|
||||
workBuffer + offset,
|
||||
signatureLen)) {
|
||||
#ifndef HAVE_BYPASS_SIGNATURES
|
||||
PRINTF("Invalid NFT signature\n");
|
||||
THROW(0x6A80);
|
||||
#endif
|
||||
}
|
||||
|
||||
pluginType = getPluginType(tokenContext->pluginName, pluginNameLength);
|
||||
if (keyId == AWS_PLUGIN_KEY_1) {
|
||||
if (pluginType != ERC721 && pluginType != ERC1155) {
|
||||
PRINTF("AWS key must only be used to set NFT internal plugins\n");
|
||||
THROW(0x6A80);
|
||||
}
|
||||
}
|
||||
|
||||
switch (pluginType) {
|
||||
case EXTERNAL: {
|
||||
PRINTF("Check external plugin %s\n", tokenContext->pluginName);
|
||||
|
||||
// Check if the plugin is present on the device
|
||||
uint32_t params[2];
|
||||
params[0] = (uint32_t) tokenContext->pluginName;
|
||||
params[1] = ETH_PLUGIN_CHECK_PRESENCE;
|
||||
BEGIN_TRY {
|
||||
TRY {
|
||||
os_lib_call(params);
|
||||
}
|
||||
CATCH_OTHER(e) {
|
||||
PRINTF("%s external plugin is not present\n", tokenContext->pluginName);
|
||||
memset(tokenContext->pluginName, 0, sizeof(tokenContext->pluginName));
|
||||
THROW(0x6984);
|
||||
}
|
||||
FINALLY {
|
||||
}
|
||||
}
|
||||
END_TRY;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
G_io_apdu_buffer[(*tx)++] = 0x90;
|
||||
G_io_apdu_buffer[(*tx)++] = 0x00;
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "apdu_constants.h"
|
||||
#include "ui_flow.h"
|
||||
#include "feature_signTx.h"
|
||||
#include "eth_plugin_interface.h"
|
||||
|
||||
void handleSign(uint8_t p1,
|
||||
uint8_t p2,
|
||||
@@ -53,7 +54,7 @@ void handleSign(uint8_t p1,
|
||||
workBuffer++;
|
||||
dataLength--;
|
||||
} else {
|
||||
PRINTF("Transaction type not supported\n");
|
||||
PRINTF("Transaction type %d not supported\n", txType);
|
||||
THROW(0x6501);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -65,6 +65,7 @@ customStatus_e customProcessor(txContext_t *context) {
|
||||
PRINTF("pluginstatus %d\n", dataContext.tokenContext.pluginStatus);
|
||||
eth_plugin_result_t status = dataContext.tokenContext.pluginStatus;
|
||||
if (status == ETH_PLUGIN_RESULT_ERROR) {
|
||||
PRINTF("Plugin error\n");
|
||||
return CUSTOM_FAULT;
|
||||
} else if (status >= ETH_PLUGIN_RESULT_SUCCESSFUL) {
|
||||
dataContext.tokenContext.fieldIndex = 0;
|
||||
@@ -113,6 +114,7 @@ customStatus_e customProcessor(txContext_t *context) {
|
||||
copySize);
|
||||
|
||||
if (context->currentFieldPos == context->currentFieldLength) {
|
||||
PRINTF("\n\nIncrementing one\n");
|
||||
context->currentField++;
|
||||
context->processingField = false;
|
||||
}
|
||||
@@ -238,7 +240,6 @@ static void feesToString(uint256_t *rawFee, char *displayBuffer, uint32_t displa
|
||||
i++;
|
||||
}
|
||||
displayBuffer[tickerOffset + i] = '\0';
|
||||
PRINTF("Displayed fees: %s\n", displayBuffer);
|
||||
}
|
||||
|
||||
// Compute the fees, transform it to a string, prepend a ticker to it and copy everything to
|
||||
@@ -301,7 +302,6 @@ void finalizeParsing(bool direct) {
|
||||
|
||||
// Verify the chain
|
||||
if (chainConfig->chainId != ETHEREUM_MAINNET_CHAINID) {
|
||||
// TODO: Could we remove above check?
|
||||
uint64_t id = get_chain_id();
|
||||
|
||||
if (chainConfig->chainId != id) {
|
||||
@@ -338,24 +338,24 @@ void finalizeParsing(bool direct) {
|
||||
}
|
||||
}
|
||||
// Lookup tokens if requested
|
||||
ethPluginProvideToken_t pluginProvideToken;
|
||||
eth_plugin_prepare_provide_token(&pluginProvideToken);
|
||||
ethPluginProvideInfo_t pluginProvideInfo;
|
||||
eth_plugin_prepare_provide_info(&pluginProvideInfo);
|
||||
if ((pluginFinalize.tokenLookup1 != NULL) || (pluginFinalize.tokenLookup2 != NULL)) {
|
||||
if (pluginFinalize.tokenLookup1 != NULL) {
|
||||
PRINTF("Lookup1: %.*H\n", ADDRESS_LENGTH, pluginFinalize.tokenLookup1);
|
||||
pluginProvideToken.token1 = getKnownToken(pluginFinalize.tokenLookup1);
|
||||
if (pluginProvideToken.token1 != NULL) {
|
||||
PRINTF("Token1 ticker: %s\n", pluginProvideToken.token1->ticker);
|
||||
pluginProvideInfo.item1 = getKnownToken(pluginFinalize.tokenLookup1);
|
||||
if (pluginProvideInfo.item1 != NULL) {
|
||||
PRINTF("Token1 ticker: %s\n", pluginProvideInfo.item1->token.ticker);
|
||||
}
|
||||
}
|
||||
if (pluginFinalize.tokenLookup2 != NULL) {
|
||||
PRINTF("Lookup2: %.*H\n", ADDRESS_LENGTH, pluginFinalize.tokenLookup2);
|
||||
pluginProvideToken.token2 = getKnownToken(pluginFinalize.tokenLookup2);
|
||||
if (pluginProvideToken.token2 != NULL) {
|
||||
PRINTF("Token2 ticker: %s\n", pluginProvideToken.token2->ticker);
|
||||
pluginProvideInfo.item2 = getKnownToken(pluginFinalize.tokenLookup2);
|
||||
if (pluginProvideInfo.item2 != NULL) {
|
||||
PRINTF("Token2 ticker: %s\n", pluginProvideInfo.item2->token.ticker);
|
||||
}
|
||||
}
|
||||
if (eth_plugin_call(ETH_PLUGIN_PROVIDE_TOKEN, (void *) &pluginProvideToken) <=
|
||||
if (eth_plugin_call(ETH_PLUGIN_PROVIDE_INFO, (void *) &pluginProvideInfo) <=
|
||||
ETH_PLUGIN_RESULT_UNSUCCESSFUL) {
|
||||
PRINTF("Plugin provide token call failed\n");
|
||||
reportFinalizeError(direct);
|
||||
@@ -363,7 +363,7 @@ void finalizeParsing(bool direct) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
pluginFinalize.result = pluginProvideToken.result;
|
||||
pluginFinalize.result = pluginProvideInfo.result;
|
||||
}
|
||||
if (pluginFinalize.result != ETH_PLUGIN_RESULT_FALLBACK) {
|
||||
// Handle the right interface
|
||||
@@ -373,7 +373,7 @@ void finalizeParsing(bool direct) {
|
||||
// Add the number of screens + the number of additional screens to get the total
|
||||
// number of screens needed.
|
||||
dataContext.tokenContext.pluginUiMaxItems =
|
||||
pluginFinalize.numScreens + pluginProvideToken.additionalScreens;
|
||||
pluginFinalize.numScreens + pluginProvideInfo.additionalScreens;
|
||||
break;
|
||||
case ETH_UI_TYPE_AMOUNT_ADDRESS:
|
||||
genericUI = true;
|
||||
@@ -389,9 +389,9 @@ void finalizeParsing(bool direct) {
|
||||
tmpContent.txContent.value.length = 32;
|
||||
memmove(tmpContent.txContent.destination, pluginFinalize.address, 20);
|
||||
tmpContent.txContent.destinationLength = 20;
|
||||
if (pluginProvideToken.token1 != NULL) {
|
||||
decimals = pluginProvideToken.token1->decimals;
|
||||
ticker = pluginProvideToken.token1->ticker;
|
||||
if (pluginProvideInfo.item1 != NULL) {
|
||||
decimals = pluginProvideInfo.item1->token.decimals;
|
||||
ticker = pluginProvideInfo.item1->token.ticker;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -37,19 +37,19 @@ void handleStarkwareProvideQuantum(uint8_t p1,
|
||||
addressZero = allzeroes(dataBuffer, 20);
|
||||
}
|
||||
if ((p1 != STARK_QUANTUM_ETH) && !addressZero) {
|
||||
for (i = 0; i < MAX_TOKEN; i++) {
|
||||
currentToken = &tmpCtx.transactionContext.tokens[i];
|
||||
for (i = 0; i < MAX_ITEMS; i++) {
|
||||
currentToken = &tmpCtx.transactionContext.extraInfo[i].token;
|
||||
if (tmpCtx.transactionContext.tokenSet[i] &&
|
||||
(memcmp(currentToken->address, dataBuffer, 20) == 0)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == MAX_TOKEN) {
|
||||
if (i == MAX_ITEMS) {
|
||||
PRINTF("Associated token not found\n");
|
||||
THROW(0x6A80);
|
||||
}
|
||||
} else {
|
||||
i = MAX_TOKEN;
|
||||
i = MAX_ITEMS;
|
||||
}
|
||||
memmove(dataContext.tokenContext.quantum, dataBuffer + 20, 32);
|
||||
if (p1 != STARK_QUANTUM_LEGACY) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
136
src_plugins/erc1155/erc1155_plugin.c
Normal 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;
|
||||
}
|
||||
}
|
||||
49
src_plugins/erc1155/erc1155_plugin.h
Normal 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);
|
||||
106
src_plugins/erc1155/erc1155_provide_parameters.c
Normal 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;
|
||||
}
|
||||
}
|
||||
135
src_plugins/erc1155/erc1155_ui.c
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
42
src_plugins/erc721/erc721_plugin.h
Normal 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);
|
||||
93
src_plugins/erc721/erc721_provide_parameters.c
Normal 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;
|
||||
}
|
||||
}
|
||||
166
src_plugins/erc721/erc721_ui.c
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
# FILL THESE WITH YOUR OWN SDKs PATHS
|
||||
# NANOS_SDK=
|
||||
# NANOX_SDK=
|
||||
NANOS_SDK=$TWO
|
||||
NANOX_SDK=$X
|
||||
|
||||
# list of apps required by tests that we want to build here
|
||||
appnames=("ethereum" "ethereum_classic")
|
||||
appnames=("ethereum")
|
||||
|
||||
# create elfs folder if it doesn't exist
|
||||
mkdir -p elfs
|
||||
@@ -18,17 +18,17 @@ for app in "${appnames[@]}"
|
||||
do
|
||||
echo "**Building $app for Nano S..."
|
||||
make clean BOLOS_SDK=$NANOS_SDK
|
||||
make -j DEBUG=1 ALLOW_DATA=1 BOLOS_SDK=$NANOS_SDK CHAIN=$app
|
||||
make -j ALLOW_DATA=1 NFT_TESTING_KEY=1 DEBUG=1 BOLOS_SDK=$NANOS_SDK CHAIN=$app
|
||||
cp bin/app.elf "tests/elfs/${app}_nanos.elf"
|
||||
done
|
||||
|
||||
echo "*Building elfs for Nano X..."
|
||||
for app in "${appnames[@]}"
|
||||
do
|
||||
echo "**Building $app for Nano X..."
|
||||
make clean BOLOS_SDK=$NANOX_SDK
|
||||
make -j DEBUG=1 ALLOW_DATA=1 BOLOS_SDK=$NANOX_SDK CHAIN=$app
|
||||
cp bin/app.elf "tests/elfs/${app}_nanox.elf"
|
||||
done
|
||||
# echo "*Building elfs for Nano X..."
|
||||
# for app in "${appnames[@]}"
|
||||
# do
|
||||
# echo "**Building $app for Nano X..."
|
||||
# make clean BOLOS_SDK=$NANOX_SDK
|
||||
# make -j DEBUG=1 BOLOS_SDK=$NANOX_SDK CHAIN=$app
|
||||
# cp bin/app.elf "tests/elfs/${app}_nanox.elf"
|
||||
# done
|
||||
|
||||
echo "done"
|
||||
|
||||
BIN
tests/snapshots/nanos_erc721_approval_for_all/00000.png
Normal file
|
After Width: | Height: | Size: 541 B |
BIN
tests/snapshots/nanos_erc721_approval_for_all/00001.png
Normal file
|
After Width: | Height: | Size: 444 B |
BIN
tests/snapshots/nanos_erc721_approval_for_all/00002.png
Normal file
|
After Width: | Height: | Size: 748 B |
BIN
tests/snapshots/nanos_erc721_approval_for_all/00003.png
Normal file
|
After Width: | Height: | Size: 764 B |
BIN
tests/snapshots/nanos_erc721_approval_for_all/00004.png
Normal file
|
After Width: | Height: | Size: 540 B |
BIN
tests/snapshots/nanos_erc721_approval_for_all/00005.png
Normal file
|
After Width: | Height: | Size: 630 B |
BIN
tests/snapshots/nanos_erc721_approval_for_all/00006.png
Normal file
|
After Width: | Height: | Size: 835 B |
BIN
tests/snapshots/nanos_erc721_approval_for_all/00007.png
Normal file
|
After Width: | Height: | Size: 838 B |
BIN
tests/snapshots/nanos_erc721_approval_for_all/00008.png
Normal file
|
After Width: | Height: | Size: 679 B |
BIN
tests/snapshots/nanos_erc721_approval_for_all/00009.png
Normal file
|
After Width: | Height: | Size: 511 B |
BIN
tests/snapshots/nanos_erc721_approval_for_all/00010.png
Normal file
|
After Width: | Height: | Size: 798 B |
BIN
tests/snapshots/nanos_erc721_approval_for_all/00011.png
Normal file
|
After Width: | Height: | Size: 501 B |
BIN
tests/snapshots/nanos_erc721_approval_for_all/00012.png
Normal file
|
After Width: | Height: | Size: 582 B |
BIN
tests/snapshots/nanos_erc721_approval_for_all/00013.png
Normal file
|
After Width: | Height: | Size: 531 B |
BIN
tests/snapshots/nanos_erc721_safe_transfer/00000.png
Normal file
|
After Width: | Height: | Size: 541 B |
BIN
tests/snapshots/nanos_erc721_safe_transfer/00001.png
Normal file
|
After Width: | Height: | Size: 408 B |
BIN
tests/snapshots/nanos_erc721_safe_transfer/00002.png
Normal file
|
After Width: | Height: | Size: 693 B |
BIN
tests/snapshots/nanos_erc721_safe_transfer/00003.png
Normal file
|
After Width: | Height: | Size: 739 B |
BIN
tests/snapshots/nanos_erc721_safe_transfer/00004.png
Normal file
|
After Width: | Height: | Size: 481 B |
BIN
tests/snapshots/nanos_erc721_safe_transfer/00005.png
Normal file
|
After Width: | Height: | Size: 639 B |
BIN
tests/snapshots/nanos_erc721_safe_transfer/00006.png
Normal file
|
After Width: | Height: | Size: 835 B |
BIN
tests/snapshots/nanos_erc721_safe_transfer/00007.png
Normal file
|
After Width: | Height: | Size: 838 B |
BIN
tests/snapshots/nanos_erc721_safe_transfer/00008.png
Normal file
|
After Width: | Height: | Size: 679 B |
BIN
tests/snapshots/nanos_erc721_safe_transfer/00009.png
Normal file
|
After Width: | Height: | Size: 532 B |
BIN
tests/snapshots/nanos_erc721_safe_transfer/00010.png
Normal file
|
After Width: | Height: | Size: 582 B |
BIN
tests/snapshots/nanos_erc721_safe_transfer/00011.png
Normal file
|
After Width: | Height: | Size: 531 B |
BIN
tests/snapshots/nanos_erc721_transfer/00000.png
Normal file
|
After Width: | Height: | Size: 541 B |
BIN
tests/snapshots/nanos_erc721_transfer/00001.png
Normal file
|
After Width: | Height: | Size: 408 B |
BIN
tests/snapshots/nanos_erc721_transfer/00002.png
Normal file
|
After Width: | Height: | Size: 688 B |
BIN
tests/snapshots/nanos_erc721_transfer/00003.png
Normal file
|
After Width: | Height: | Size: 698 B |
BIN
tests/snapshots/nanos_erc721_transfer/00004.png
Normal file
|
After Width: | Height: | Size: 526 B |
BIN
tests/snapshots/nanos_erc721_transfer/00005.png
Normal file
|
After Width: | Height: | Size: 679 B |
BIN
tests/snapshots/nanos_erc721_transfer/00006.png
Normal file
|
After Width: | Height: | Size: 840 B |
BIN
tests/snapshots/nanos_erc721_transfer/00007.png
Normal file
|
After Width: | Height: | Size: 888 B |
BIN
tests/snapshots/nanos_erc721_transfer/00008.png
Normal file
|
After Width: | Height: | Size: 629 B |
BIN
tests/snapshots/nanos_erc721_transfer/00009.png
Normal file
|
After Width: | Height: | Size: 511 B |
BIN
tests/snapshots/nanos_erc721_transfer/00010.png
Normal file
|
After Width: | Height: | Size: 796 B |
BIN
tests/snapshots/nanos_erc721_transfer/00011.png
Normal file
|
After Width: | Height: | Size: 492 B |
BIN
tests/snapshots/nanos_erc721_transfer/00012.png
Normal file
|
After Width: | Height: | Size: 582 B |
BIN
tests/snapshots/nanos_erc721_transfer/00013.png
Normal file
|
After Width: | Height: | Size: 531 B |
BIN
tests/snapshots/nanos_erc721_transfer_with_eth/00000.png
Normal file
|
After Width: | Height: | Size: 541 B |
BIN
tests/snapshots/nanos_erc721_transfer_with_eth/00001.png
Normal file
|
After Width: | Height: | Size: 408 B |
BIN
tests/snapshots/nanos_erc721_transfer_with_eth/00002.png
Normal file
|
After Width: | Height: | Size: 688 B |
BIN
tests/snapshots/nanos_erc721_transfer_with_eth/00003.png
Normal file
|
After Width: | Height: | Size: 698 B |
BIN
tests/snapshots/nanos_erc721_transfer_with_eth/00004.png
Normal file
|
After Width: | Height: | Size: 526 B |
BIN
tests/snapshots/nanos_erc721_transfer_with_eth/00005.png
Normal file
|
After Width: | Height: | Size: 679 B |
BIN
tests/snapshots/nanos_erc721_transfer_with_eth/00006.png
Normal file
|
After Width: | Height: | Size: 840 B |
BIN
tests/snapshots/nanos_erc721_transfer_with_eth/00007.png
Normal file
|
After Width: | Height: | Size: 888 B |
BIN
tests/snapshots/nanos_erc721_transfer_with_eth/00008.png
Normal file
|
After Width: | Height: | Size: 629 B |
BIN
tests/snapshots/nanos_erc721_transfer_with_eth/00009.png
Normal file
|
After Width: | Height: | Size: 404 B |
BIN
tests/snapshots/nanos_erc721_transfer_with_eth/00010.png
Normal file
|
After Width: | Height: | Size: 511 B |
BIN
tests/snapshots/nanos_erc721_transfer_with_eth/00011.png
Normal file
|
After Width: | Height: | Size: 796 B |
BIN
tests/snapshots/nanos_erc721_transfer_with_eth/00012.png
Normal file
|
After Width: | Height: | Size: 492 B |
BIN
tests/snapshots/nanos_erc721_transfer_with_eth/00013.png
Normal file
|
After Width: | Height: | Size: 582 B |
BIN
tests/snapshots/nanos_erc721_transfer_with_eth/00014.png
Normal file
|
After Width: | Height: | Size: 531 B |
27
tests/src/erc1155.test .js
Normal file
@@ -0,0 +1,27 @@
|
||||
import "core-js/stable";
|
||||
import "regenerator-runtime/runtime";
|
||||
import { waitForAppScreen, zemu, txFromEtherscan } from './test.fixture';
|
||||
import { TransportStatusError } from "@ledgerhq/errors";
|
||||
|
||||
// -------------------
|
||||
// TODO: Actually write the tests
|
||||
|
||||
test.skip('[Nano S] Transfer 1155', zemu("nanos", async (sim, eth) => {
|
||||
|
||||
const rawTx = ""
|
||||
const serializedTx = txFromEtherscan(rawTx);
|
||||
|
||||
// with ETH need to test
|
||||
// const serializedTx = txFromEtherscan("0x02f901350182022f8459682f0085246ad7eb3182de2994424db67b40b15ed85475c3f29dedf601b6ee75b283424242b8c4f242432a000000000000000000000000dcdb88f3754b2841093d9348a2d02df8cf06314c000000000000000000000000df9fb2eff1f2871caeeb94bf262ffba84efddddc0000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000c001a0c4283f86dc852e43e9fd1077b448c63fec76bdeb44dfac977730725e41fa3676a0543b2d2f99f65fb20cd548964eee94b1c1865919f4574c7089d8b95678b667c2");
|
||||
|
||||
const tx = eth.signTransaction(
|
||||
"44'/60'/1'/0/0",
|
||||
serializedTx,
|
||||
);
|
||||
|
||||
await waitForAppScreen(sim);
|
||||
await sim.navigateAndCompareSnapshots('.', 'nanos_erc721_transfer_ethereum', [12, 0]);
|
||||
|
||||
await expect(tx).resolves.toEqual({
|
||||
});
|
||||
}));
|
||||
130
tests/src/erc721.test.js
Normal file
@@ -0,0 +1,130 @@
|
||||
import "core-js/stable";
|
||||
import "regenerator-runtime/runtime";
|
||||
import { waitForAppScreen, zemu, txFromEtherscan } from './test.fixture';
|
||||
|
||||
test('[Nano S] Transfer erc721', zemu("nanos", async (sim, eth) => {
|
||||
|
||||
// https://etherscan.io/tx/0x73cec4fc07de3a24ba42e8756e13b7ddfa9bd449126c37640881195e8ea9e679
|
||||
// Modified to put a bigger token id
|
||||
const rawTx = "0x02f8d101058459682f0085233da9943e8301865b94bd3531da5cf5857e7cfaa92426877b022e612cf880b86423b872dd0000000000000000000000004cc568b73c0dcf8e90db26d7fd3a6cfadca108a3000000000000000000000000d4c9b20950c3eca38fc1f33f54bdf9694e488799ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc080a094c8632fe7277aa8c54cea9d81a15911cfa4970a2bf7356d14d04cc5afbcdab7a013a77b8c79e5d9b2b35edb3c44db3bb41b92f5c463ff126bf19d213b2b9ba8b5"
|
||||
const serializedTx = txFromEtherscan(rawTx);
|
||||
|
||||
const tx = eth.signTransaction(
|
||||
"44'/60'/1'/0/0",
|
||||
serializedTx,
|
||||
);
|
||||
|
||||
await waitForAppScreen(sim);
|
||||
await sim.navigateAndCompareSnapshots('.', 'nanos_erc721_transfer', [12, 0]);
|
||||
|
||||
await expect(tx).resolves.toEqual({
|
||||
"r": "59f6a9769cff66eed8be8716c44d39808d1e43f3aa0bb97538e124dba4bc4565",
|
||||
"s": "662990a841c663a165ba9a83e5cc95c03a999b851e0bd6d296aa70a0f7c96c1a",
|
||||
"v": "01",
|
||||
});
|
||||
}));
|
||||
|
||||
test('[Nano S] Transfer erc721 with attached ETH', zemu("nanos", async (sim, eth) => {
|
||||
|
||||
const rawTx = "0x02f8d601058459682f0085233da9943e8301865b94bd3531da5cf5857e7cfaa92426877b022e612cf8854242424242b86423b872dd0000000000000000000000004cc568b73c0dcf8e90db26d7fd3a6cfadca108a3000000000000000000000000d4c9b20950c3eca38fc1f33f54bdf9694e4887990000000000000000000000000000000000000000000000000000000000000f21c080a094c8632fe7277aa8c54cea9d81a15911cfa4970a2bf7356d14d04cc5afbcdab7a013a77b8c79e5d9b2b35edb3c44db3bb41b92f5c463ff126bf19d213b2b9ba8b5"
|
||||
const serializedTx = txFromEtherscan(rawTx);
|
||||
|
||||
|
||||
const tx = eth.signTransaction(
|
||||
"44'/60'/1'/0/0",
|
||||
serializedTx,
|
||||
);
|
||||
|
||||
await waitForAppScreen(sim);
|
||||
await sim.navigateAndCompareSnapshots('.', 'nanos_erc721_transfer_with_eth', [13, 0]);
|
||||
|
||||
await expect(tx).resolves.toEqual({
|
||||
"r": "9c42e10b49f3ee315ab2d5f7ad96f1068c75578734b66504716cc279ead27d47",
|
||||
"s": "45dde78470ad75ffdb27a799b87e4934e2e10e98dbc6f88bc4a9bc19c4de86bc",
|
||||
"v": "00",
|
||||
});
|
||||
}));
|
||||
|
||||
test('[Nano S] set approval for all erc721', zemu("nanos", async (sim, eth) => {
|
||||
|
||||
// https://etherscan.io/tx/0x86b936db53c19fddf26b8d145f165e1c7fdff3c0f8b14b7758a38f0400cfd93f
|
||||
const rawTx = "0x02f8b0010c8459682f00852cfbb00ee682b54294d4e4078ca3495de5b1d4db434bebc5a98619778280b844a22cb4650000000000000000000000002efcb1e8d4472d35356b9747bea8a051eac2e3f50000000000000000000000000000000000000000000000000000000000000001c001a0c5b8c024c15ca1452ce8a13eacfcdc25f1c6f581bb3ce570e82f08f1b792b3aca03be4dba0302ae190618a72eb1202ce3af3e17afd7d8a94345a48cae5cad15541";
|
||||
const serializedTx = txFromEtherscan(rawTx);
|
||||
|
||||
|
||||
const tx = eth.signTransaction(
|
||||
"44'/60'/1'/0/0",
|
||||
serializedTx,
|
||||
);
|
||||
|
||||
await waitForAppScreen(sim);
|
||||
await sim.navigateAndCompareSnapshots('.', 'nanos_erc721_approval_for_all', [12, 0]);
|
||||
|
||||
await expect(tx).resolves.toEqual({
|
||||
"r": "8b6a70a1fe76d8e9b1250531a17eb1e367936732d4dfb9befc81a5031b271dc8",
|
||||
"s": "7658d7151bba0d8504cea2013bead64cb8407dc6be1fca829bb9594b56f679af",
|
||||
"v": "00",
|
||||
});
|
||||
}));
|
||||
|
||||
// NOT DONE
|
||||
test.skip('[Nano S] approval erc721', zemu("nanos", async (sim, eth) => {
|
||||
|
||||
// INCORRECT, need to find / create an approval tx
|
||||
const rawTx = "";
|
||||
const serializedTx = txFromEtherscan(rawTx);
|
||||
|
||||
|
||||
const tx = eth.signTransaction(
|
||||
"44'/60'/1'/0/0",
|
||||
serializedTx,
|
||||
);
|
||||
|
||||
await waitForAppScreen(sim);
|
||||
await sim.navigateAndCompareSnapshots('.', 'nanos_erc721_approval', [12, 0]);
|
||||
|
||||
await expect(tx).resolves.toEqual({
|
||||
});
|
||||
}));
|
||||
|
||||
test('[Nano S] safe transfer erc721', zemu("nanos", async (sim, eth) => {
|
||||
|
||||
// https://etherscan.io/tx/0x1ee6ce9be1c9fe6f030ff124ba8c88a410223c022816547e4b3fedd3a4d2dc1e
|
||||
const rawTx = "0xf8cc82028585077359400083061a8094d4e4078ca3495de5b1d4db434bebc5a98619778280b86442842e0e000000000000000000000000c352b534e8b987e036a93539fd6897f53488e56a0000000000000000000000000a9287d9339c175cd3ea0ad4228f734a9f75ee6200000000000000000000000000000000000000000000000000000000000000621ca08250f4b2c8f28c5e4ef621dba4682990d1faf930c8cb6d032c6e7278e8951d92a03c1e1f6d63ed339041f69f24c6c0968ba26f244f779cb4fa7a468f3ba3d3e06e";
|
||||
const serializedTx = txFromEtherscan(rawTx);
|
||||
|
||||
|
||||
const tx = eth.signTransaction(
|
||||
"44'/60'/1'/0/0",
|
||||
serializedTx,
|
||||
);
|
||||
|
||||
await waitForAppScreen(sim);
|
||||
await sim.navigateAndCompareSnapshots('.', 'nanos_erc721_safe_transfer', [10, 0]);
|
||||
|
||||
await expect(tx).resolves.toEqual({
|
||||
"r": "b936684d5d0e99e09701021fb73ae9403f2ec79414d822d42c5bd1c0a2118f1a",
|
||||
"s": "23e517c6cac998f392d179be2fe7c3225f0e0a165b1af85548da5d6acaa73c4f",
|
||||
"v": "25",
|
||||
});
|
||||
}));
|
||||
|
||||
// NOT DONE
|
||||
test.skip('[Nano S] safe transfer with data erc721', zemu("nanos", async (sim, eth) => {
|
||||
|
||||
// need to find or create a safe transfer with data on etherscan?
|
||||
const rawTx = "";
|
||||
const serializedTx = txFromEtherscan(rawTx);
|
||||
|
||||
|
||||
const tx = eth.signTransaction(
|
||||
"44'/60'/1'/0/0",
|
||||
serializedTx,
|
||||
);
|
||||
|
||||
await waitForAppScreen(sim);
|
||||
await sim.navigateAndCompareSnapshots('.', 'nanos_erc721_safe_transfer_with_data', [12, 0]);
|
||||
|
||||
await expect(tx).resolves.toEqual({
|
||||
});
|
||||
}));
|
||||
@@ -1,5 +1,6 @@
|
||||
import Zemu from '@zondax/zemu';
|
||||
import Eth from '@ledgerhq/hw-app-eth';
|
||||
import {RLP} from "ethers/lib/utils";
|
||||
|
||||
const transactionUploadDelay = 60000;
|
||||
|
||||
@@ -36,6 +37,35 @@ const NANOX_CLONE_ELF_PATH = Resolve("elfs/ethereum_classic_nanox.elf");
|
||||
|
||||
const TIMEOUT = 1000000;
|
||||
|
||||
// Generates a serializedTransaction from a rawHexTransaction copy pasted from etherscan.
|
||||
function txFromEtherscan(rawTx) {
|
||||
// Remove 0x prefix
|
||||
rawTx = rawTx.slice(2);
|
||||
|
||||
let txType = rawTx.slice(0, 2);
|
||||
if (txType == "02" || txType == "01") {
|
||||
// Remove "02" prefix
|
||||
rawTx = rawTx.slice(2);
|
||||
} else {
|
||||
txType = "";
|
||||
}
|
||||
|
||||
let decoded = RLP.decode("0x" + rawTx);
|
||||
if (txType != "") {
|
||||
decoded = decoded.slice(0, decoded.length - 3); // remove v, r, s
|
||||
} else {
|
||||
decoded[decoded.length - 1] = "0x"; // empty
|
||||
decoded[decoded.length - 2] = "0x"; // empty
|
||||
decoded[decoded.length - 3] = "0x01"; // chainID 1
|
||||
}
|
||||
|
||||
// Encode back the data, drop the '0x' prefix
|
||||
let encoded = RLP.encode(decoded).slice(2);
|
||||
|
||||
// Don't forget to prepend the txtype
|
||||
return txType + encoded;
|
||||
}
|
||||
|
||||
function zemu(device, func) {
|
||||
return async () => {
|
||||
jest.setTimeout(TIMEOUT);
|
||||
@@ -71,5 +101,6 @@ module.exports = {
|
||||
NANOX_CLONE_ELF_PATH,
|
||||
sim_options_nanos,
|
||||
sim_options_nanox,
|
||||
TIMEOUT
|
||||
TIMEOUT,
|
||||
txFromEtherscan,
|
||||
}
|
||||