Merge pull request #267 from LedgerHQ/develop_1.9.17
Add Non-Fungible Token (ERC 721 & 1155) support
29
CHANGELOG.md
@@ -5,13 +5,40 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [1.9.17](https://github.com/ledgerhq/app-ethereum/compare/1.9.16...1.9.17) - 2022-01-14
|
||||
|
||||
### Added
|
||||
|
||||
- Support for Non-Fungible Tokens (ERC-721 & ERC-1155)
|
||||
|
||||
## [1.9.16](https://github.com/ledgerhq/app-ethereum/compare/1.9.14...1.9.16) - 2022-01-13
|
||||
|
||||
### Added
|
||||
|
||||
- Shyft variant
|
||||
|
||||
## [1.9.14](https://github.com/ledgerhq/app-ethereum/compare/1.9.13...1.9.14) - 2021-11-30
|
||||
|
||||
### Added
|
||||
|
||||
- Added Moonriver BIP44 1285
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed stark order signature on LNS
|
||||
|
||||
## [1.9.13](https://github.com/ledgerhq/app-ethereum/compare/1.9.12...1.9.13) - 2021-11-17
|
||||
|
||||
### Changed
|
||||
|
||||
- Small improvement in app size
|
||||
|
||||
## [1.9.12](https://github.com/ledgerhq/app-ethereum/compare/1.9.11...1.9.12) - 2021-11-12
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed stark order signature on LNX
|
||||
|
||||
|
||||
## [1.9.11](https://github.com/ledgerhq/app-ethereum/compare/1.9.10...1.9.11) - 2021-10-12
|
||||
|
||||
### Added
|
||||
|
||||
36
Makefile
Executable file → Normal file
@@ -30,7 +30,7 @@ APP_LOAD_PARAMS += --path "1517992542'/1101353413'"
|
||||
|
||||
APPVERSION_M=1
|
||||
APPVERSION_N=9
|
||||
APPVERSION_P=16
|
||||
APPVERSION_P=17
|
||||
APPVERSION=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)
|
||||
APP_LOAD_FLAGS= --appFlags 0x240 --dep Ethereum:$(APPVERSION)
|
||||
|
||||
@@ -248,10 +248,10 @@ APP_LOAD_PARAMS += $(APP_LOAD_FLAGS) --path "44'/1'"
|
||||
DEFINES += $(DEFINES_LIB)
|
||||
|
||||
#prepare hsm generation
|
||||
ifeq ($(TARGET_NAME), TARGET_NANOX)
|
||||
ICONNAME=icons/nanox_app_$(CHAIN).gif
|
||||
else
|
||||
ifeq ($(TARGET_NAME),TARGET_NANOS)
|
||||
ICONNAME=icons/nanos_app_$(CHAIN).gif
|
||||
else
|
||||
ICONNAME=icons/nanox_app_$(CHAIN).gif
|
||||
endif
|
||||
|
||||
################
|
||||
@@ -283,19 +283,21 @@ DEFINES += HAVE_UX_FLOW
|
||||
DEFINES += HAVE_WEBUSB WEBUSB_URL_SIZE_B=0 WEBUSB_URL=""
|
||||
|
||||
ifeq ($(TARGET_NAME),TARGET_NANOX)
|
||||
DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=300
|
||||
DEFINES += HAVE_BLE BLE_COMMAND_TIMEOUT_MS=2000
|
||||
DEFINES += HAVE_BLE_APDU # basic ledger apdu transport over BLE
|
||||
endif
|
||||
|
||||
ifeq ($(TARGET_NAME),TARGET_NANOS)
|
||||
DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=72
|
||||
DEFINES += HAVE_WALLET_ID_SDK
|
||||
else
|
||||
DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=300
|
||||
DEFINES += HAVE_GLO096
|
||||
DEFINES += HAVE_BAGL BAGL_WIDTH=128 BAGL_HEIGHT=64
|
||||
DEFINES += HAVE_BAGL_ELLIPSIS # long label truncation feature
|
||||
DEFINES += HAVE_BAGL_FONT_OPEN_SANS_REGULAR_11PX
|
||||
DEFINES += HAVE_BAGL_FONT_OPEN_SANS_EXTRABOLD_11PX
|
||||
DEFINES += HAVE_BAGL_FONT_OPEN_SANS_LIGHT_16PX
|
||||
else
|
||||
DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=72
|
||||
DEFINES += HAVE_WALLET_ID_SDK
|
||||
endif
|
||||
|
||||
# Enables direct data signing without having to specify it in the settings. Useful when testing with speculos.
|
||||
@@ -304,21 +306,31 @@ 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
|
||||
|
||||
# NFTs
|
||||
ifneq ($(TARGET_NAME),TARGET_NANOS)
|
||||
DEFINES += HAVE_NFT_SUPPORT
|
||||
# Enable the NFT testing key
|
||||
NFT_TESTING_KEY:=0
|
||||
ifneq ($(NFT_TESTING_KEY),0)
|
||||
DEFINES += HAVE_NFT_TESTING_KEY
|
||||
endif
|
||||
endif
|
||||
|
||||
|
||||
# Enabling debug PRINTF
|
||||
DEBUG:=0
|
||||
ifneq ($(DEBUG),0)
|
||||
DEFINES += HAVE_STACK_OVERFLOW_CHECK
|
||||
ifeq ($(TARGET_NAME),TARGET_NANOX)
|
||||
DEFINES += HAVE_PRINTF PRINTF=mcu_usb_printf
|
||||
else
|
||||
ifeq ($(TARGET_NAME),TARGET_NANOS)
|
||||
DEFINES += HAVE_PRINTF PRINTF=screen_printf
|
||||
else
|
||||
DEFINES += HAVE_PRINTF PRINTF=mcu_usb_printf
|
||||
endif
|
||||
else
|
||||
DEFINES += PRINTF\(...\)=
|
||||
|
||||
@@ -4,7 +4,7 @@ Ledger Blue is not maintained anymore, but the app can still be compiled for thi
|
||||
|
||||
This app follows the specification available in the `doc/` folder.
|
||||
|
||||
To compile it and load it on a device, please check out our [developer portal](https://developers.ledger.com/docs/NA/start_here/).
|
||||
To compile it and load it on a device, please check out our [developer portal](https://developers.ledger.com/docs/nano-app/introduction/).
|
||||
|
||||
# Plugins
|
||||
|
||||
@@ -20,7 +20,7 @@ First [install yarn](https://classic.yarnpkg.com/en/docs/install/#debian-stable)
|
||||
Open `tests/build_local_test_elfs.sh` and add your BOLOS SDKs path to `NANOS_SDK` and `NANOX_SDK`.
|
||||
This helper script will build the applications required by the test suite and move them at the right place.
|
||||
```
|
||||
cd test
|
||||
cd tests
|
||||
./build_local_test_elfs.sh
|
||||
```
|
||||
Then you can install the project by simply running:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "eth_plugin_handler.h"
|
||||
#include "eth_plugin_internal.h"
|
||||
#include "shared_context.h"
|
||||
#include "network.h"
|
||||
|
||||
void eth_plugin_prepare_init(ethPluginInitContract_t *init, uint8_t *selector, uint32_t dataSize) {
|
||||
memset((uint8_t *) init, 0, sizeof(ethPluginInitContract_t));
|
||||
@@ -21,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,
|
||||
@@ -44,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(),
|
||||
@@ -54,61 +72,83 @@ void eth_plugin_prepare_query_contract_UI(ethQueryContractUI_t *queryContractUI,
|
||||
queryContractUI->msgLength = msgLength;
|
||||
}
|
||||
|
||||
eth_plugin_result_t eth_plugin_perform_init(uint8_t *contractAddress,
|
||||
static void eth_plugin_perform_init_default(uint8_t *contractAddress,
|
||||
ethPluginInitContract_t *init) {
|
||||
uint8_t i;
|
||||
const uint8_t **selectors;
|
||||
dataContext.tokenContext.pluginStatus = ETH_PLUGIN_RESULT_UNAVAILABLE;
|
||||
// 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);
|
||||
}
|
||||
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");
|
||||
dataContext.tokenContext.pluginStatus = ETH_PLUGIN_RESULT_OK;
|
||||
}
|
||||
|
||||
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);
|
||||
static bool eth_plugin_perform_init_old_internal(uint8_t *contractAddress,
|
||||
ethPluginInitContract_t *init) {
|
||||
uint8_t i, j;
|
||||
const uint8_t **selectors;
|
||||
|
||||
// Search internal plugin list
|
||||
for (i = 0;; i++) {
|
||||
selectors = (const uint8_t **) PIC(INTERNAL_ETH_PLUGINS[i].selectors);
|
||||
if (selectors == NULL) {
|
||||
break;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
eth_plugin_result_t eth_plugin_perform_init(uint8_t *contractAddress,
|
||||
ethPluginInitContract_t *init) {
|
||||
dataContext.tokenContext.pluginStatus = ETH_PLUGIN_RESULT_UNAVAILABLE;
|
||||
|
||||
PRINTF("Selector %.*H\n", 4, init->selector);
|
||||
switch (pluginType) {
|
||||
#ifdef HAVE_NFT_SUPPORT
|
||||
case ERC1155:
|
||||
case ERC721:
|
||||
#endif // HAVE_NFT_SUPPORT
|
||||
case EXTERNAL:
|
||||
eth_plugin_perform_init_default(contractAddress, init);
|
||||
contractAddress = NULL;
|
||||
break;
|
||||
case OLD_INTERNAL:
|
||||
if (eth_plugin_perform_init_old_internal(contractAddress, init)) {
|
||||
contractAddress = NULL;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
PRINTF("Unsupported pluginType %d\n", pluginType);
|
||||
os_sched_exit(0);
|
||||
break;
|
||||
}
|
||||
|
||||
// Do not handle a plugin if running in swap mode
|
||||
if (called_from_swap && (contractAddress != NULL)) {
|
||||
PRINTF("eth_plug_init aborted in swap mode\n");
|
||||
@@ -139,7 +179,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;
|
||||
@@ -182,12 +221,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:
|
||||
@@ -210,35 +249,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 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);
|
||||
#ifdef HAVE_NFT_SUPPORT
|
||||
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;
|
||||
}
|
||||
#endif // HAVE_NFT_SUPPORT
|
||||
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
|
||||
@@ -276,9 +332,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];
|
||||
|
||||
|
||||
58
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;
|
||||
@@ -386,12 +386,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,7 +427,7 @@ void handleGetWalletId(volatile unsigned int *tx) {
|
||||
// pubkey -> sha512
|
||||
cx_hash_sha512(pub.W, sizeof(pub.W), t, sizeof(t));
|
||||
// ! cookie !
|
||||
os_memmove(G_io_apdu_buffer, t, 64);
|
||||
memmove(G_io_apdu_buffer, t, 64);
|
||||
*tx = 64;
|
||||
THROW(0x9000);
|
||||
}
|
||||
@@ -491,7 +501,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,
|
||||
@@ -509,6 +519,17 @@ void handleApdu(unsigned int *flags, unsigned int *tx) {
|
||||
tx);
|
||||
break;
|
||||
|
||||
#ifdef HAVE_NFT_SUPPORT
|
||||
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;
|
||||
#endif // HAVE_NFT_SUPPORT
|
||||
|
||||
case INS_SET_EXTERNAL_PLUGIN:
|
||||
handleSetExternalPlugin(G_io_apdu_buffer[OFFSET_P1],
|
||||
G_io_apdu_buffer[OFFSET_P2],
|
||||
@@ -518,6 +539,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],
|
||||
@@ -537,7 +567,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,
|
||||
@@ -547,7 +577,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,
|
||||
@@ -559,7 +589,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,
|
||||
@@ -768,7 +798,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,16 @@ 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.
|
||||
} 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);
|
||||
|
||||
@@ -171,7 +171,7 @@ UX_STEP_CB(
|
||||
"Error",
|
||||
"Blind signing must be enabled in Settings",
|
||||
});
|
||||
#elif defined(TARGET_NANOX)
|
||||
#elif defined(TARGET_NANOX) || defined(TARGET_NANOS2)
|
||||
UX_STEP_CB(
|
||||
ux_warning_contract_data_step,
|
||||
pnn,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
230
src_features/provideNFTInformation/cmd_provideNFTInfo.c
Normal file
@@ -0,0 +1,230 @@
|
||||
#ifdef HAVE_NFT_SUPPORT
|
||||
|
||||
#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 TEST_NFT_METADATA_KEY 0
|
||||
#define PROD_NFT_METADATA_KEY 1
|
||||
|
||||
#define ALGORITHM_ID_1 1
|
||||
|
||||
#define TYPE_1 1
|
||||
|
||||
#define VERSION_1 1
|
||||
|
||||
static const uint8_t LEDGER_NFT_METADATA_PUBLIC_KEY[] = {
|
||||
#ifdef HAVE_NFT_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
|
||||
|
||||
#else
|
||||
0x04, 0x98, 0x8d, 0xa6, 0xb2, 0x46, 0xf2, 0x8e, 0x77, 0xc1, 0xba, 0xb6, 0x75, 0xcb,
|
||||
0x2a, 0x27, 0x44, 0xf7, 0xf5, 0xce, 0xc5, 0x6a, 0xe6, 0xe0, 0x32, 0x23, 0x33, 0x7b,
|
||||
0x57, 0x94, 0xcd, 0x6a, 0xe0, 0x7d, 0x48, 0xb3, 0x0d, 0xb9, 0xcc, 0xb4, 0x0f, 0x5a,
|
||||
0x02, 0xa1, 0x1a, 0x3a, 0xb9, 0x9d, 0x5f, 0x59, 0x5a, 0x3d, 0x50, 0xa0, 0xe1, 0x30,
|
||||
0x23, 0xfd, 0x0d, 0x95, 0x87, 0x92, 0xd7, 0x97, 0x01
|
||||
#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;
|
||||
|
||||
uint64_t chainId = u64_from_BE(workBuffer + offset, CHAIN_ID_SIZE);
|
||||
// this prints raw data, so to have a more meaningful print, display
|
||||
// the buffer before the endianness swap
|
||||
PRINTF("ChainID: %.*H\n", sizeof(chainId), (workBuffer + offset));
|
||||
if ((chainConfig->chainId != 0) && (chainConfig->chainId != chainId)) {
|
||||
PRINTF("Chain ID token mismatch\n");
|
||||
THROW(0x6A80);
|
||||
}
|
||||
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 TEST_NFT_METADATA_KEY:
|
||||
#endif
|
||||
case PROD_NFT_METADATA_KEY:
|
||||
rawKey = (uint8_t *) LEDGER_NFT_METADATA_PUBLIC_KEY;
|
||||
rawKeyLen = sizeof(LEDGER_NFT_METADATA_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
|
||||
}
|
||||
|
||||
tmpCtx.transactionContext.tokenSet[tmpCtx.transactionContext.currentItemIndex] = 1;
|
||||
THROW(0x9000);
|
||||
}
|
||||
|
||||
#endif // HAVE_NFT_SUPPORT
|
||||
@@ -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;
|
||||
|
||||
296
src_features/setPlugin/cmd_setPlugin.c
Normal file
@@ -0,0 +1,296 @@
|
||||
#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"
|
||||
#include "utils.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_PLUGIN_KEY = 0x00,
|
||||
// Must ONLY be used with ERC721 and ERC1155 plugin
|
||||
PROD_PLUGIN_KEY = 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;
|
||||
|
||||
// Only used for signing NFT plugins (ERC721 and ERC1155)
|
||||
static const uint8_t LEDGER_NFT_SELECTOR_PUBLIC_KEY[] = {
|
||||
#ifdef HAVE_NFT_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
|
||||
#else
|
||||
0x04, 0xd8, 0x62, 0x6e, 0x01, 0x9e, 0x55, 0x3e, 0x19, 0x69, 0x56, 0xf1, 0x17, 0x4d,
|
||||
0xcd, 0xb8, 0x9a, 0x1c, 0xda, 0xc4, 0x93, 0x90, 0x08, 0xbc, 0x79, 0x77, 0x33, 0x6d,
|
||||
0x78, 0x24, 0xee, 0xe3, 0xa2, 0x62, 0x24, 0x1a, 0x62, 0x73, 0x52, 0x3b, 0x09, 0xb8,
|
||||
0xd0, 0xce, 0x0d, 0x39, 0xe8, 0x60, 0xc9, 0x4d, 0x02, 0x53, 0x58, 0xdb, 0xdc, 0x25,
|
||||
0x92, 0xc7, 0xc6, 0x48, 0x0d, 0x39, 0xce, 0xbb, 0xa3
|
||||
#endif
|
||||
};
|
||||
|
||||
// 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;
|
||||
|
||||
uint64_t chainId = u64_from_BE(workBuffer + offset, CHAIN_ID_SIZE);
|
||||
// this prints raw data, so to have a more meaningful print, display
|
||||
// the buffer before the endianness swap
|
||||
PRINTF("ChainID: %.*H\n", sizeof(chainId), (workBuffer + offset));
|
||||
if ((chainConfig->chainId != 0) && (chainConfig->chainId != chainId)) {
|
||||
PRINTF("Chain ID token mismatch\n");
|
||||
THROW(0x6A80);
|
||||
}
|
||||
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_PLUGIN_KEY:
|
||||
#endif
|
||||
case PROD_PLUGIN_KEY:
|
||||
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 == PROD_PLUGIN_KEY) {
|
||||
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,
|
||||
@@ -12,6 +13,11 @@ void handleSign(uint8_t p1,
|
||||
UNUSED(tx);
|
||||
parserStatus_e txResult;
|
||||
uint32_t i;
|
||||
|
||||
if (os_global_pin_is_validated() != BOLOS_UX_OK) {
|
||||
PRINTF("Device is PIN-locked");
|
||||
THROW(0x6982);
|
||||
}
|
||||
if (p1 == P1_FIRST) {
|
||||
if (dataLength < 1) {
|
||||
PRINTF("Invalid data\n");
|
||||
@@ -53,7 +59,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;
|
||||
|
||||
|
||||
142
src_plugins/erc1155/erc1155_plugin.c
Normal file
@@ -0,0 +1,142 @@
|
||||
#ifdef HAVE_NFT_SUPPORT
|
||||
|
||||
#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] = {0x2e, 0xb2, 0xc2, 0xd6};
|
||||
|
||||
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 = 5;
|
||||
break;
|
||||
case SET_APPROVAL_FOR_ALL:
|
||||
case SAFE_BATCH_TRANSFER:
|
||||
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))) {
|
||||
// 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:
|
||||
strlcpy(msg->version, "Transfer", msg->versionLength);
|
||||
break;
|
||||
case SAFE_BATCH_TRANSFER:
|
||||
strlcpy(msg->version, "Batch 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;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // HAVE_NFT_SUPPORT
|
||||
55
src_plugins/erc1155/erc1155_plugin.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#ifdef HAVE_NFT_SUPPORT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string.h>
|
||||
#include "eth_plugin_handler.h"
|
||||
#include "shared_context.h"
|
||||
#include "ethUtils.h"
|
||||
#include "utils.h"
|
||||
#include "uint256.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];
|
||||
uint256_t value;
|
||||
|
||||
uint16_t ids_array_len;
|
||||
uint32_t ids_offset;
|
||||
uint16_t values_array_len;
|
||||
uint32_t values_offset;
|
||||
uint16_t array_index;
|
||||
|
||||
bool approved;
|
||||
erc1155_selector_field next_param;
|
||||
uint8_t selectorIndex;
|
||||
} erc1155_context_t;
|
||||
|
||||
void handle_provide_parameter_1155(void *parameters);
|
||||
void handle_query_contract_ui_1155(void *parameters);
|
||||
|
||||
#endif // HAVE_NFT_SUPPORT
|
||||
147
src_plugins/erc1155/erc1155_provide_parameters.c
Normal file
@@ -0,0 +1,147 @@
|
||||
#ifdef HAVE_NFT_SUPPORT
|
||||
|
||||
#include "erc1155_plugin.h"
|
||||
#include "eth_plugin_internal.h"
|
||||
|
||||
static void handle_safe_transfer(ethPluginProvideParameter_t *msg, erc1155_context_t *context) {
|
||||
uint8_t new_value[INT256_LENGTH];
|
||||
|
||||
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(new_value, msg->parameter, sizeof(new_value));
|
||||
convertUint256BE(new_value, INT256_LENGTH, &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) {
|
||||
uint256_t new_value;
|
||||
|
||||
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_IDS_OFFSET;
|
||||
break;
|
||||
case TOKEN_IDS_OFFSET:
|
||||
context->ids_offset =
|
||||
U4BE(msg->parameter, PARAMETER_LENGTH - sizeof(context->ids_offset)) + 4;
|
||||
context->next_param = VALUE_OFFSET;
|
||||
break;
|
||||
case VALUE_OFFSET:
|
||||
context->values_offset =
|
||||
U4BE(msg->parameter, PARAMETER_LENGTH - sizeof(context->values_offset)) + 4;
|
||||
context->next_param = TOKEN_IDS_LENGTH;
|
||||
break;
|
||||
case TOKEN_IDS_LENGTH:
|
||||
if ((msg->parameterOffset + PARAMETER_LENGTH) > context->ids_offset) {
|
||||
context->ids_array_len =
|
||||
U2BE(msg->parameter, PARAMETER_LENGTH - sizeof(context->ids_array_len));
|
||||
context->next_param = TOKEN_ID;
|
||||
// set to zero for next step
|
||||
context->array_index = 0;
|
||||
}
|
||||
break;
|
||||
case TOKEN_ID:
|
||||
// don't copy anything since we won't display it
|
||||
if (--context->ids_array_len == 0) {
|
||||
context->next_param = VALUE_LENGTH;
|
||||
}
|
||||
context->array_index++;
|
||||
break;
|
||||
case VALUE_LENGTH:
|
||||
if ((msg->parameterOffset + PARAMETER_LENGTH) > context->values_offset) {
|
||||
context->values_array_len =
|
||||
U2BE(msg->parameter, PARAMETER_LENGTH - sizeof(context->values_array_len));
|
||||
if (context->values_array_len != context->array_index) {
|
||||
PRINTF("Token ids and values array sizes mismatch!");
|
||||
}
|
||||
context->next_param = VALUE;
|
||||
// set to zero for next step
|
||||
context->array_index = 0;
|
||||
explicit_bzero(&context->value, sizeof(context->value));
|
||||
}
|
||||
break;
|
||||
case VALUE:
|
||||
// put it temporarily in token id since we don't use it in batch transfer
|
||||
copy_parameter(context->tokenId, msg->parameter, sizeof(context->value));
|
||||
convertUint256BE(context->tokenId, sizeof(context->tokenId), &new_value);
|
||||
add256(&context->value, &new_value, &context->value);
|
||||
if (--context->values_array_len == 0) {
|
||||
context->next_param = NONE;
|
||||
}
|
||||
context->array_index++;
|
||||
break;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // HAVE_NFT_SUPPORT
|
||||
152
src_plugins/erc1155/erc1155_ui.c
Normal file
@@ -0,0 +1,152 @@
|
||||
#ifdef HAVE_NFT_SUPPORT
|
||||
|
||||
#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:
|
||||
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((uint8_t *) msg->item1->nft.contractAddress,
|
||||
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, "Quantity", msg->titleLength);
|
||||
tostring256(&context->value, 10, 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) {
|
||||
char quantity_str[48];
|
||||
|
||||
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((uint8_t *) msg->item1->nft.contractAddress,
|
||||
msg->msg,
|
||||
msg->msgLength,
|
||||
&global_sha3,
|
||||
chainConfig->chainId);
|
||||
break;
|
||||
case 3:
|
||||
strlcpy(msg->title, "Total Quantity", msg->titleLength);
|
||||
tostring256(&context->value, 10, &quantity_str[0], sizeof(quantity_str));
|
||||
snprintf(msg->msg,
|
||||
msg->msgLength,
|
||||
"%s from %d NFT IDs",
|
||||
quantity_str,
|
||||
context->array_index);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // HAVE_NFT_SUPPORT
|
||||
@@ -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,150 @@
|
||||
#include <string.h>
|
||||
#ifdef HAVE_NFT_SUPPORT
|
||||
|
||||
#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] = {0x09, 0x5e, 0xa7, 0xb3};
|
||||
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] = {0xb8, 0x8d, 0x4f, 0xde};
|
||||
|
||||
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 APPROVE:
|
||||
msg->numScreens = 4;
|
||||
break;
|
||||
case SET_APPROVAL_FOR_ALL:
|
||||
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))) {
|
||||
// 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_721(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_721(parameters);
|
||||
} break;
|
||||
|
||||
default:
|
||||
PRINTF("Unhandled message %d\n", message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // HAVE_NFT_SUPPORT
|
||||
|
||||
46
src_plugins/erc721/erc721_plugin.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#ifdef HAVE_NFT_SUPPORT
|
||||
|
||||
#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_721(void *parameters);
|
||||
void handle_query_contract_ui_721(void *parameters);
|
||||
|
||||
#endif // HAVE_NFT_SUPPORT
|
||||
97
src_plugins/erc721/erc721_provide_parameters.c
Normal file
@@ -0,0 +1,97 @@
|
||||
#ifdef HAVE_NFT_SUPPORT
|
||||
|
||||
#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_721(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;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // HAVE_NFT_SUPPORT
|
||||
170
src_plugins/erc721/erc721_ui.c
Normal file
@@ -0,0 +1,170 @@
|
||||
#ifdef HAVE_NFT_SUPPORT
|
||||
|
||||
#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((uint8_t *) msg->item1->nft.contractAddress,
|
||||
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_721(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;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // HAVE_NFT_SUPPORT
|
||||
@@ -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,34 +1,44 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
TESTS_FULL_PATH=$(dirname "$(realpath "$0")")
|
||||
|
||||
# FILL THESE WITH YOUR OWN SDKs PATHS
|
||||
# NANOS_SDK=
|
||||
# NANOX_SDK=
|
||||
|
||||
# list of apps required by tests that we want to build here
|
||||
appnames=("ethereum" "ethereum_classic")
|
||||
APPS=("ethereum" "ethereum_classic")
|
||||
|
||||
# create elfs folder if it doesn't exist
|
||||
# list of SDKS
|
||||
NANO_SDKS=("$NANOS_SDK" "$NANOX_SDK")
|
||||
# list of target elf file name suffix
|
||||
FILE_SUFFIXES=("nanos" "nanox")
|
||||
|
||||
# move to the tests directory
|
||||
cd "$TESTS_FULL_PATH" || exit 1
|
||||
|
||||
# Do it only now since before the cd command, we might not have been inside the repository
|
||||
GIT_REPO_ROOT=$(git rev-parse --show-toplevel)
|
||||
TESTS_REL_PATH=$(realpath --relative-to="$GIT_REPO_ROOT" "$TESTS_FULL_PATH")
|
||||
|
||||
# create elfs directory if it doesn't exist
|
||||
mkdir -p elfs
|
||||
|
||||
# move to repo's root to build apps
|
||||
cd ..
|
||||
cd "$GIT_REPO_ROOT" || exit 1
|
||||
|
||||
echo "*Building elfs for Nano S..."
|
||||
for app in "${appnames[@]}"
|
||||
for ((sdk_idx=0; sdk_idx < "${#NANO_SDKS[@]}"; sdk_idx++))
|
||||
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
|
||||
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"
|
||||
nano_sdk="${NANO_SDKS[$sdk_idx]}"
|
||||
elf_suffix="${FILE_SUFFIXES[$sdk_idx]}"
|
||||
echo "* Building elfs for $(basename "$nano_sdk")..."
|
||||
for appname in "${APPS[@]}"
|
||||
do
|
||||
echo "** Building app $appname..."
|
||||
make clean BOLOS_SDK="$nano_sdk"
|
||||
make -j DEBUG=1 NFT_TESTING_KEY=1 BOLOS_SDK="$nano_sdk" CHAIN="$appname"
|
||||
cp bin/app.elf "$TESTS_REL_PATH/elfs/${appname}_${elf_suffix}.elf"
|
||||
done
|
||||
done
|
||||
|
||||
echo "done"
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"bip32-path": "^0.4.2",
|
||||
"core-js": "^3.7.0",
|
||||
"ethereum-tx-decoder": "^3.0.0",
|
||||
"ethers": "^5.5.1",
|
||||
"fs-extra": "^10.0.0",
|
||||
"google-protobuf": "^3.11.0",
|
||||
"jest-serial-runner": "^1.1.0",
|
||||
|
||||
BIN
tests/snapshots/nanos_enable_blind_signing/00000.png
Normal file
|
After Width: | Height: | Size: 531 B |
BIN
tests/snapshots/nanos_enable_blind_signing/00001.png
Normal file
|
After Width: | Height: | Size: 344 B |
BIN
tests/snapshots/nanos_enable_blind_signing/00002.png
Normal file
|
After Width: | Height: | Size: 480 B |
BIN
tests/snapshots/nanos_enable_blind_signing/00003.png
Normal file
|
After Width: | Height: | Size: 566 B |
BIN
tests/snapshots/nanos_enable_blind_signing/00004.png
Normal file
|
After Width: | Height: | Size: 514 B |
BIN
tests/snapshots/nanos_enable_blind_signing/00005.png
Normal file
|
After Width: | Height: | Size: 614 B |
BIN
tests/snapshots/nanos_enable_blind_signing/00006.png
Normal file
|
After Width: | Height: | Size: 628 B |
BIN
tests/snapshots/nanos_enable_blind_signing/00007.png
Normal file
|
After Width: | Height: | Size: 338 B |
BIN
tests/snapshots/nanos_enable_blind_signing/00008.png
Normal file
|
After Width: | Height: | Size: 531 B |
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 |