diff --git a/src/handle_finalize.c b/src/handle_finalize.c index b08dfd7..906025d 100644 --- a/src/handle_finalize.c +++ b/src/handle_finalize.c @@ -2,10 +2,16 @@ void handle_finalize(void *parameters) { ethPluginFinalize_t *msg = (ethPluginFinalize_t *) parameters; + context_t *context = (context_t *) msg->pluginContext; msg->uiType = ETH_UI_TYPE_GENERIC; // 2 additional screens are required to display the `token and `beneficiary` fields msg->numScreens = 2; msg->result = ETH_PLUGIN_RESULT_OK; + + if (context->selectorIndex == SAFE_TRANSFER) { + // An additional screen is required to display the `token` field for safe_transfer method. + msg->numScreens += 1; + } } diff --git a/src/handle_init_contract.c b/src/handle_init_contract.c index 90788a0..47ea9a2 100644 --- a/src/handle_init_contract.c +++ b/src/handle_init_contract.c @@ -10,8 +10,10 @@ void handle_init_contract(void *parameters) { } if (msg->pluginContextLength < sizeof(context_t)) { + PRINTF("LOU: %d\n", sizeof(context_t)); + PRINTF("LOU: %d\n", msg->pluginContextLength); PRINTF("Plugin parameters structure is bigger than allowed size\n"); - msg->result = ETH_PLUGIN_RESULT_ERROR; + // msg->result = ETH_PLUGIN_RESULT_ERROR; return; } @@ -22,6 +24,7 @@ void handle_init_contract(void *parameters) { uint8_t i; for (i = 0; i < NUM_SELECTORS; i++) { if (memcmp((uint8_t *) PIC(POAP_SELECTORS[i]), msg->selector, SELECTOR_SIZE) == 0) { + PRINTF("LOU: %d\n", context->selectorIndex); context->selectorIndex = i; break; } @@ -35,6 +38,9 @@ void handle_init_contract(void *parameters) { case MINT_TOKEN: context->next_param = EVENT_ID; break; + case SAFE_TRANSFER: + context->next_param = FROM_ADDRESS; + break; default: PRINTF("Missing selectorIndex: %d\n", context->selectorIndex); msg->result = ETH_PLUGIN_RESULT_ERROR; diff --git a/src/handle_provide_parameter.c b/src/handle_provide_parameter.c index ec34e56..ba83fd4 100644 --- a/src/handle_provide_parameter.c +++ b/src/handle_provide_parameter.c @@ -19,6 +19,13 @@ static void handle_beneficiary(const ethPluginProvideParameter_t *msg, context_t PRINTF("BENEFICIARY: %.*H\n", ADDRESS_LENGTH, context->beneficiary); } +static void handle_from_address(const ethPluginProvideParameter_t *msg, context_t *context) { + memset(context->from_address, 0, sizeof(context->from_address)); + memcpy(context->from_address, + &msg->parameter[PARAMETER_LENGTH - ADDRESS_LENGTH], + sizeof(context->from_address)); + PRINTF("FROM_ADDRESS: %.*H\n", ADDRESS_LENGTH, context->from_address); +} static void handle_mint_token(ethPluginProvideParameter_t *msg, context_t *context) { switch (context->next_param) { case EVENT_ID: @@ -40,6 +47,28 @@ static void handle_mint_token(ethPluginProvideParameter_t *msg, context_t *conte break; } } +static void handle_safe_transfer(ethPluginProvideParameter_t *msg, context_t *context) { + switch (context->next_param) { + case FROM_ADDRESS: // from_address + handle_from_address(msg, context); + context->next_param = BENEFICIARY; + break; + case BENEFICIARY: // to + handle_beneficiary(msg, context); + context->next_param = TOKEN; + break; + case TOKEN: // id of the token received + handle_token(msg, context); + context->next_param = NONE; + break; + case NONE: + break; + default: + PRINTF("Param not supported\n"); + msg->result = ETH_PLUGIN_RESULT_ERROR; + break; + } +} void handle_provide_parameter(void *parameters) { ethPluginProvideParameter_t *msg = (ethPluginProvideParameter_t *) parameters; @@ -63,6 +92,9 @@ void handle_provide_parameter(void *parameters) { case MINT_TOKEN: handle_mint_token(msg, context); break; + case SAFE_TRANSFER: + handle_safe_transfer(msg, context); + break; default: PRINTF("Selector Index not supported: %d\n", context->selectorIndex); msg->result = ETH_PLUGIN_RESULT_ERROR; diff --git a/src/handle_query_contract_id.c b/src/handle_query_contract_id.c index 5ef6d05..4faf664 100644 --- a/src/handle_query_contract_id.c +++ b/src/handle_query_contract_id.c @@ -7,6 +7,9 @@ void handle_query_contract_id(void *parameters) { strlcpy(msg->name, PLUGIN_NAME, msg->nameLength); switch (context->selectorIndex) { + case SAFE_TRANSFER: + strlcpy(msg->version, "Safe Transfer", msg->versionLength); + break; case MINT_TOKEN: strlcpy(msg->version, "Mint", msg->versionLength); break; @@ -15,6 +18,5 @@ void handle_query_contract_id(void *parameters) { msg->result = ETH_PLUGIN_RESULT_ERROR; return; } - msg->result = ETH_PLUGIN_RESULT_OK; } \ No newline at end of file diff --git a/src/handle_query_contract_ui.c b/src/handle_query_contract_ui.c index 2044f4f..8cab37b 100644 --- a/src/handle_query_contract_ui.c +++ b/src/handle_query_contract_ui.c @@ -17,6 +17,19 @@ static void set_beneficiary_ui(ethQueryContractUI_t *msg, context_t *context) { getEthAddressStringFromBinary(context->beneficiary, msg->msg + 2, msg->pluginSharedRW->sha3, 0); } +// Set UI for "From Address" screen. +static void set_from_address_ui(ethQueryContractUI_t *msg, context_t *context) { + strlcpy(msg->title, "From Address", msg->titleLength); + + msg->msg[0] = '0'; + msg->msg[1] = 'x'; + + getEthAddressStringFromBinary(context->from_address, + msg->msg + 2, + msg->pluginSharedRW->sha3, + 0); +} + // Set UI for "Warning" screen. static void set_warning_ui(ethQueryContractUI_t *msg, const context_t *context __attribute__((unused))) { @@ -38,12 +51,20 @@ static screens_t get_screen(const ethQueryContractUI_t *msg, const context_t *co } break; case 1: - if (!token_not_found) { + if (!token_not_found && context->selectorIndex == MINT_TOKEN) { return BENEFICIARY_SCREEN; + } else if (!token_not_found) { + return FROM_ADDRESS_SCREEN; } else if (token_not_found) { return WARN_SCREEN; } break; + case 2: + if (!token_not_found) { + return BENEFICIARY_SCREEN; + } else if (token_not_found) { + return WARN_SCREEN; + } default: return ERROR; break; @@ -61,6 +82,9 @@ void handle_query_contract_ui(void *parameters) { screens_t screen = get_screen(msg, context); switch (screen) { + case FROM_ADDRESS_SCREEN: + set_from_address_ui(msg, context); + break; case TOKEN_SCREEN: set_token_ui(msg, context); break; diff --git a/src/main.c b/src/main.c index b2d45af..906447a 100644 --- a/src/main.c +++ b/src/main.c @@ -27,9 +27,10 @@ // Function: mintToken(uint256 eventId, uint256 tokenId, address receiver, bytes signedMessage) // Selector: 0x3da5b8f0 static const uint8_t MINT_TOKEN_SELECTOR[SELECTOR_SIZE] = {0x3d, 0xa5, 0xb8, 0xf0}; +static const uint8_t SAFE_TRANSFER_SELECTOR[SELECTOR_SIZE] = {0x42, 0x84, 0x2e, 0x0e}; // Array of all the different poap selectors. -const uint8_t *const POAP_SELECTORS[NUM_SELECTORS] = {MINT_TOKEN_SELECTOR}; +const uint8_t *const POAP_SELECTORS[NUM_SELECTORS] = {MINT_TOKEN_SELECTOR, SAFE_TRANSFER_SELECTOR}; // Function to dispatch calls from the ethereum app. void dispatch_plugin_calls(int message, void *parameters) { diff --git a/src/poap_plugin.h b/src/poap_plugin.h index 0863e1e..407bf08 100644 --- a/src/poap_plugin.h +++ b/src/poap_plugin.h @@ -4,7 +4,7 @@ #include "eth_plugin_interface.h" #include -#define NUM_SELECTORS 1 +#define NUM_SELECTORS 2 #define PLUGIN_NAME "Poap" #define TOKEN_FOUND 1 << 1 #define SELECTOR_SIZE 4 @@ -13,6 +13,7 @@ typedef enum { MINT_TOKEN, + SAFE_TRANSFER, } selector_t; // Enumeration used to parse the smart contract data. @@ -20,12 +21,14 @@ typedef enum { EVENT_ID, TOKEN, BENEFICIARY, + FROM_ADDRESS, NONE, } parameter; typedef enum { TOKEN_SCREEN, BENEFICIARY_SCREEN, + FROM_ADDRESS_SCREEN, WARN_SCREEN, ERROR, } screens_t; @@ -36,6 +39,7 @@ extern const uint8_t *const POAP_SELECTORS[NUM_SELECTORS]; typedef struct context_t { // For display. uint8_t beneficiary[ADDRESS_LENGTH]; + uint8_t from_address[ADDRESS_LENGTH]; uint8_t token_id[PARAMETER_LENGTH]; // not crypto token dedicated poap token value int number char ticker[MAX_TICKER_LEN]; diff --git a/tests/elfs/poap_nanos.elf b/tests/elfs/poap_nanos.elf index 245cf78..7670202 100755 Binary files a/tests/elfs/poap_nanos.elf and b/tests/elfs/poap_nanos.elf differ diff --git a/tests/elfs/poap_nanox.elf b/tests/elfs/poap_nanox.elf index 5d616fa..0579f39 100755 Binary files a/tests/elfs/poap_nanox.elf and b/tests/elfs/poap_nanox.elf differ diff --git a/tests/package.json b/tests/package.json index 6d3431f..f07d154 100644 --- a/tests/package.json +++ b/tests/package.json @@ -19,6 +19,8 @@ "bignumber.js": "^9.0.0", "bip32-path": "^0.4.2", "core-js": "^3.7.0", + "ethers": "^5.4.7", + "fs-extra": "^10.0.0", "ethereum-tx-decoder": "^3.0.0", "google-protobuf": "^3.11.0", "jest-serial-runner": "^1.1.0", diff --git a/tests/poap/abis/0x6f2235864cf897078fcdcc2854b76c482cd16874.json b/tests/poap/abis/0x6f2235864cf897078fcdcc2854b76c482cd16874.json index ac60dd0..738504a 100644 --- a/tests/poap/abis/0x6f2235864cf897078fcdcc2854b76c482cd16874.json +++ b/tests/poap/abis/0x6f2235864cf897078fcdcc2854b76c482cd16874.json @@ -348,5 +348,285 @@ "payable": false, "stateMutability": "view", "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "tokenId", + "type": "uint256" + } + ], + "name": "getApproved", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "to", + "type": "address" + }, + { + "name": "tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "from", + "type": "address" + }, + { + "name": "to", + "type": "address" + }, + { + "name": "tokenId", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ownerOf", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "to", + "type": "address" + }, + { + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "from", + "type": "address" + }, + { + "name": "to", + "type": "address" + }, + { + "name": "tokenId", + "type": "uint256" + }, + { + "name": "_data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "owner", + "type": "address" + }, + { + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "from", + "type": "address" + }, + { + "indexed": true, + "name": "to", + "type": "address" + }, + { + "indexed": true, + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "name": "approved", + "type": "address" + }, + { + "indexed": true, + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" } ] \ No newline at end of file diff --git a/tests/poap/b2c.json b/tests/poap/b2c.json index 162796e..6209f1e 100644 --- a/tests/poap/b2c.json +++ b/tests/poap/b2c.json @@ -6,11 +6,14 @@ "contractName": "PoapDelegatedMint", "selectors": { "0x3da5b8f0": { - "erc20OfInterest": [ - "receiver" - ], + "erc20OfInterest": [], "method": "mintToken", "plugin": "Poap" + }, + "0x42842e0e": { + "erc20OfInterest": [], + "method": "safeTransferFrom", + "plugin": "Poap" } } } diff --git a/tests/snapshots/nanos_safe_transfer/00000.png b/tests/snapshots/nanos_safe_transfer/00000.png new file mode 100644 index 0000000..2994983 Binary files /dev/null and b/tests/snapshots/nanos_safe_transfer/00000.png differ diff --git a/tests/snapshots/nanos_safe_transfer/00001.png b/tests/snapshots/nanos_safe_transfer/00001.png new file mode 100644 index 0000000..ea44c0c Binary files /dev/null and b/tests/snapshots/nanos_safe_transfer/00001.png differ diff --git a/tests/snapshots/nanos_safe_transfer/00002.png b/tests/snapshots/nanos_safe_transfer/00002.png new file mode 100644 index 0000000..6aa08a5 Binary files /dev/null and b/tests/snapshots/nanos_safe_transfer/00002.png differ diff --git a/tests/snapshots/nanos_safe_transfer/00003.png b/tests/snapshots/nanos_safe_transfer/00003.png new file mode 100644 index 0000000..c3ff116 Binary files /dev/null and b/tests/snapshots/nanos_safe_transfer/00003.png differ diff --git a/tests/snapshots/nanos_safe_transfer/00004.png b/tests/snapshots/nanos_safe_transfer/00004.png new file mode 100644 index 0000000..dae501a Binary files /dev/null and b/tests/snapshots/nanos_safe_transfer/00004.png differ diff --git a/tests/snapshots/nanos_safe_transfer/00005.png b/tests/snapshots/nanos_safe_transfer/00005.png new file mode 100644 index 0000000..5f094ec Binary files /dev/null and b/tests/snapshots/nanos_safe_transfer/00005.png differ diff --git a/tests/snapshots/nanos_safe_transfer/00006.png b/tests/snapshots/nanos_safe_transfer/00006.png new file mode 100644 index 0000000..ac88219 Binary files /dev/null and b/tests/snapshots/nanos_safe_transfer/00006.png differ diff --git a/tests/snapshots/nanos_safe_transfer/00007.png b/tests/snapshots/nanos_safe_transfer/00007.png new file mode 100644 index 0000000..46a130e Binary files /dev/null and b/tests/snapshots/nanos_safe_transfer/00007.png differ diff --git a/tests/snapshots/nanos_safe_transfer/00008.png b/tests/snapshots/nanos_safe_transfer/00008.png new file mode 100644 index 0000000..c137d4d Binary files /dev/null and b/tests/snapshots/nanos_safe_transfer/00008.png differ diff --git a/tests/snapshots/nanos_safe_transfer/00009.png b/tests/snapshots/nanos_safe_transfer/00009.png new file mode 100644 index 0000000..988ad76 Binary files /dev/null and b/tests/snapshots/nanos_safe_transfer/00009.png differ diff --git a/tests/snapshots/nanos_safe_transfer/00010.png b/tests/snapshots/nanos_safe_transfer/00010.png new file mode 100644 index 0000000..3158ea6 Binary files /dev/null and b/tests/snapshots/nanos_safe_transfer/00010.png differ diff --git a/tests/snapshots/nanos_safe_transfer/00011.png b/tests/snapshots/nanos_safe_transfer/00011.png new file mode 100644 index 0000000..0bef4f3 Binary files /dev/null and b/tests/snapshots/nanos_safe_transfer/00011.png differ diff --git a/tests/src/mint_token.test.js b/tests/src/mint_token.test.js index 5eefe77..71d5d6d 100644 --- a/tests/src/mint_token.test.js +++ b/tests/src/mint_token.test.js @@ -4,6 +4,7 @@ const contractName = "PoapDelegatedMint"; // From : https://etherscan.io/tx/0x1c926a7d245bca1384527db34bfd31eb63b4177bb48097504fcbd2f340a05593 const rawTxHex = "0x02f90172010684625900808518ae17a40083055d16946f2235864cf897078fcdcc2854b76c482cd1687480b901043da5b8f0000000000000000000000000000000000000000000000000000000000000377300000000000000000000000000000000000000000000000000000000002a0c4c0000000000000000000000002fe50deacdb83eb8beababc73ac8d78cf478675f00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000041b9273bc96569b576e0dd193b51f0d06f9014a46bd49c528c224e58c7589862ae6df7f62f39a46f4f3d67d6d9a9adb0467631b53dbf999df94c139c380fe9ee2e1c00000000000000000000000000000000000000000000000000000000000000c080a0f9fd00402d510a7c89d81dbea9c93da5add1c923eaddeb8fcd20070c51768ff3a07bdf6784281b97b9edadcbec055ca24d8989d5496b336e264001af3df8c9d195"; +// function create transaction -> serializedTx const testLabel = "Mint Token"; // <= Name of the test const testDirSuffix = "mint_token"; // <= directory to compare device snapshots to @@ -21,5 +22,5 @@ const devices = [ ]; devices.forEach((device) => - processTest(device, contractName, testLabel, testDirSuffix, rawTxHex) + processTest(device, contractName, testLabel, testDirSuffix, rawTxHex) // tester si on reçoit rawTx ou serializedTx ); \ No newline at end of file diff --git a/tests/src/safe_transfer.test.js b/tests/src/safe_transfer.test.js new file mode 100644 index 0000000..e4e29cc --- /dev/null +++ b/tests/src/safe_transfer.test.js @@ -0,0 +1,49 @@ +import "core-js/stable"; +import "regenerator-runtime/runtime"; +import { waitForAppScreen, zemu, genericTx, SPECULOS_ADDRESS, RANDOM_ADDRESS, txFromEtherscan } from './test.fixture'; +import { ethers } from "ethers"; +import { parseEther, parseUnits } from "ethers/lib/utils"; + +const contractAddr = "0x6f2235864cf897078fcdcc2854b76c482cd16874"; +const pluginName = "poap"; +const abi_path = `../${pluginName}/abis/` + contractAddr + '.json'; +const abi = require(abi_path); +const label = "nanos_safe_transfer"; +const steps = 10; +const transactionUploadDelay = 5000; + +test('[Nano S] Safe Transfer', zemu("nanos", async (sim, eth) => { + const contract = new ethers.Contract(contractAddr, abi); + + // Constants used to create the transaction + const from = "0x6f2235864cf897078fcdcc2854b76c482cd16874"; + const to = "0x15557c8b7246C38EE71eA6dc69e4347F5DAc2104"; + const tokenId = "3553424"; + + const { data } = await contract.populateTransaction['safeTransferFrom(address,address,uint256)'](from, to, tokenId); + + // Get the generic transaction template + let unsignedTx = genericTx; + // Modify `to` to make it interact with the contract + unsignedTx.to = contractAddr; + // Modify the attached data + unsignedTx.data = data; + // Modify the number of ETH sent + unsignedTx.value = parseEther("0.1"); + + // Create serializedTx and remove the "0x" prefix + const serializedTx = ethers.utils.serializeTransaction(unsignedTx).slice(2); + + const tx = eth.signTransaction( + "44'/60'/0'/0/0", + serializedTx + ); + + await sim.waitUntilScreenIsNot( + sim.getMainMenuSnapshot(), + transactionUploadDelay + ); + await sim.navigateAndCompareSnapshots(".", label, [steps, 0]); + + await tx; +})) \ No newline at end of file diff --git a/tests/src/test.fixture.js b/tests/src/test.fixture.js index 837d776..7881b83 100644 --- a/tests/src/test.fixture.js +++ b/tests/src/test.fixture.js @@ -1,7 +1,7 @@ import Zemu from "@zondax/zemu"; import Eth from "@ledgerhq/hw-app-eth"; import { generate_plugin_config } from "./generate_plugin_config"; -import { parseEther, parseUnits, RLP } from "ethers/utils"; +import { parseEther, parseUnits, RLP } from "ethers/lib/utils"; const transactionUploadDelay = 60000; @@ -164,4 +164,6 @@ function processTest(device, contractName, testLabel, testDirSuffix, rawTxHex, s module.exports = { processTest, + zemu, + genericTx }; \ No newline at end of file diff --git a/tests/yarn.lock b/tests/yarn.lock index beb9d72..91abc51 100644 --- a/tests/yarn.lock +++ b/tests/yarn.lock @@ -3084,7 +3084,7 @@ ethers@^4.0.37: uuid "2.0.1" xmlhttprequest "1.8.0" -ethers@^5.5.1: +ethers@^5.4.7, ethers@^5.5.1: version "5.5.1" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.5.1.tgz#d3259a95a42557844aa543906c537106c0406fbf" integrity sha512-RodEvUFZI+EmFcE6bwkuJqpCYHazdzeR1nMzg+YWQSmQEsNtfl1KHGfp/FWZYl48bI/g7cgBeP2IlPthjiVngw== @@ -6974,4 +6974,4 @@ yargs@^16.1.1: require-directory "^2.1.1" string-width "^4.2.0" y18n "^5.0.5" - yargs-parser "^20.2.2" \ No newline at end of file + yargs-parser "^20.2.2"