diff --git a/.gitmodules b/.gitmodules index 4318fd4..60c0a2f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "ethereum-plugin-sdk"] path = ethereum-plugin-sdk url = git@github.com:LedgerHQ/ethereum-plugin-sdk.git +[submodule "app-ethereum"] + path = app-ethereum + url = git@github.com:LedgerHQ/app-ethereum.git diff --git a/app-ethereum b/app-ethereum new file mode 160000 index 0000000..985dd92 --- /dev/null +++ b/app-ethereum @@ -0,0 +1 @@ +Subproject commit 985dd92cc2b23c65e609bf577a4f46624a75198d diff --git a/src/handle_finalize.c b/src/handle_finalize.c index d88f0d9..bdcdd89 100644 --- a/src/handle_finalize.c +++ b/src/handle_finalize.c @@ -25,19 +25,18 @@ void handle_finalize(ethPluginFinalize_t *msg) { msg->numScreens = 3; msg->result = ETH_PLUGIN_RESULT_OK; break; - case KILN_LR_QUEUE_WITHDRAWALS: - msg->numScreens = 1; - - lr_queue_withdrawals_t *params = &context->param_data.lr_queue_withdrawals; - params->queued_withdrawals_nb = 0; - for (size_t i = 0; i < LR_STRATEGIES_COUNT; i += 1) { - if (params->strategies[i]) { - msg->numScreens += 1; - params->queued_withdrawals_nb += 1; - } + case KILN_LR_QUEUE_WITHDRAWALS: { + { + lr_queue_withdrawals_t *params = &context->param_data.lr_queue_withdrawals; + // function + withdrawer screens + msg->numScreens = 2; + // one screen per withdrawal + msg->numScreens += params->strategies_count; + PRINTF("--- Queue Withdrawals: %d\n", params->strategies_count); } msg->result = ETH_PLUGIN_RESULT_OK; break; + } case KILN_LR_COMPLETE_QUEUED_WITHDRAWAL: msg->numScreens = 1; msg->result = ETH_PLUGIN_RESULT_OK; diff --git a/src/handle_provide_parameter.c b/src/handle_provide_parameter.c index 722c476..7a0c187 100644 --- a/src/handle_provide_parameter.c +++ b/src/handle_provide_parameter.c @@ -20,11 +20,11 @@ bool compare_addresses(const char a[ADDRESS_STR_LEN], const char b[ADDRESS_STR_L /* * If address is a known erc20, update lr display context with its name - * otherwise set it to unkwown (-1) + * otherwise set it to unkwown (UNKNOW_LR_STRATEGY) * * @param address: address to compare * - * @returns index of the erc20 in the context or -1 if not found + * @returns index of the erc20 in the context or UNKNOW_LR_STRATEGY if not found */ int find_lr_known_erc20(const char address[ADDRESS_STR_LEN]) { for (size_t i = 0; i < LR_STRATEGIES_COUNT; i++) { @@ -33,16 +33,16 @@ int find_lr_known_erc20(const char address[ADDRESS_STR_LEN]) { } } // if unknown erc20, indicate it - return -1; + return UNKNOW_LR_STRATEGY; } /* * If address is a known strategy, update lr display context with its name - * otherwise set it to unkwown (-1) + * otherwise set it to unkwown (UNKNOW_LR_STRATEGY) * * @param address: address to compare * - * @returns index of the strategy in the context or -1 if not found + * @returns index of the strategy in the context or UNKNOW_LR_STRATEGY if not found */ int find_lr_known_strategy(const char address[ADDRESS_STR_LEN]) { for (size_t i = 0; i < LR_STRATEGIES_COUNT; i++) { @@ -51,7 +51,7 @@ int find_lr_known_strategy(const char address[ADDRESS_STR_LEN]) { } } // if unknown strategy, indicate it - return -1; + return UNKNOW_LR_STRATEGY; } /* @@ -99,20 +99,22 @@ void handle_lr_deposit_into_strategy(ethPluginProvideParameter_t *msg, context_t void handle_lr_queue_withdrawals(ethPluginProvideParameter_t *msg, context_t *context) { // queuedWithdrawals = (address strategies[],uint256 shares[],address withdrawer) // queueWithdrawals(queuedWithdrawals[]) - // example for queue withdrawals with 2 strategies + // example for 2 queue withdrawals with 2 strategies each (2x2 dimension) // [ 0] selector - // [ 36] queuedWithdrawals_offset - // [ 68] queuedWithdrawals_length - // [100] queuedWithdrawals_0 - // [100] strategies_offset - // [132] shares_offset - // [164] withdrawer - // [196] strategies_length - // [228] strategies_0 - // [260] strategies_1 - // [292] shares_length - // [324] shares_0 - // [356] shares_1 + // [ 4] queuedWithdrawals_offset + // [ 36] queuedWithdrawals_length + // [ 68] queuedWithdrawals_0_offset + // [100] queuedWithdrawals_1_offset + // [132] queuedWithdrawals_0 + // [132] strategies_offset + // [164] shares_offset + // [196] withdrawer + // [228] strategies_length + // [260] strategies_0 + // [292] strategies_1 + // [324] shares_length + // [356] shares_0 + // [388] shares_1 // [388] queuedWithdrawals_1 // [388] strategies_offset // [420] shares_offset @@ -133,16 +135,32 @@ void handle_lr_queue_withdrawals(ethPluginProvideParameter_t *msg, context_t *co context->next_param = LR_QUEUE_WITHDRAWALS_QWITHDRAWALS_LENGTH; break; case LR_QUEUE_WITHDRAWALS_QWITHDRAWALS_LENGTH: - params->queued_withdrawals_nb = U2BE(msg->parameter, PARAMETER_LENGTH); - context->next_param = LR_QUEUE_WITHDRAWALS__QWITHDRAWALS_STRATEGIES_OFFSET; + U2BE_from_parameter(msg->parameter, ¶ms->queued_withdrawals_count); + params->current_item_count = params->queued_withdrawals_count; + context->next_param = LR_QUEUE_WITHDRAWALS__QWITHDRAWALS_STRUCT_OFFSET; break; // 2. entering a queuedWithdrawal + case LR_QUEUE_WITHDRAWALS__QWITHDRAWALS_STRUCT_OFFSET: + // we skip all the queuewithdrawal structs offsets + PRINTF("CURRENT ITEM COUNT: %d\n", params->current_item_count); + if (params->current_item_count > 0) { + params->current_item_count -= 1; + } + + if (params->current_item_count == 0) { + context->next_param = LR_QUEUE_WITHDRAWALS__QWITHDRAWALS_STRATEGIES_OFFSET; + } + break; case LR_QUEUE_WITHDRAWALS__QWITHDRAWALS_STRATEGIES_OFFSET: context->next_param = LR_QUEUE_WITHDRAWALS__QWITHDRAWALS_SHARES_OFFSET; break; case LR_QUEUE_WITHDRAWALS__QWITHDRAWALS_SHARES_OFFSET: - context->next_param = LR_QUEUE_WITHDRAWALS__QWITHDRAWALS_WITHDRAWER; + if (params->withdrawer[0] == '\0') { + context->next_param = LR_QUEUE_WITHDRAWALS__QWITHDRAWALS_WITHDRAWER; + } else { + context->next_param = LR_QUEUE_WITHDRAWALS__QWITHDRAWALS_STRATEGIES_LENGTH; + } break; case LR_QUEUE_WITHDRAWALS__QWITHDRAWALS_WITHDRAWER: // EigenLayer contract does not allow withdrawer to be different than msg.sender @@ -157,7 +175,8 @@ void handle_lr_queue_withdrawals(ethPluginProvideParameter_t *msg, context_t *co break; case LR_QUEUE_WITHDRAWALS__QWITHDRAWALS_STRATEGIES_LENGTH: // get number of item to parse - params->current_item_nb = U2BE(msg->parameter, PARAMETER_LENGTH); + U2BE_from_parameter(msg->parameter, ¶ms->current_item_count); + context->next_param = LR_QUEUE_WITHDRAWALS__QWITHDRAWALS__STRATEGIES_ITEM; break; case LR_QUEUE_WITHDRAWALS__QWITHDRAWALS__STRATEGIES_ITEM: @@ -167,39 +186,42 @@ void handle_lr_queue_withdrawals(ethPluginProvideParameter_t *msg, context_t *co getEthDisplayableAddress(buffer, address_buffer, sizeof(address_buffer), 0); int strategy_index = find_lr_known_strategy(address_buffer); - if (strategy_index != -1) { - params->strategies[strategy_index] = true; - } + params->strategies[params->strategies_count] = + (strategy_index != UNKNOW_LR_STRATEGY) ? strategy_index + 1 : UNKNOW_LR_STRATEGY; + PRINTF("STRATEGY #: %d STRATEGY: %d\n", params->strategies_count, strategy_index); // we just processed one strategy item - params->current_item_nb -= 1; - if (params->current_item_nb == 0) { + params->strategies_count += 1; + params->current_item_count -= 1; + if (params->current_item_count == 0) { // when we arrive at the end of the strategies array we go to the shares array context->next_param = LR_QUEUE_WITHDRAWALS__QWITHDRAWALS_SHARES_LENGTH; } break; case LR_QUEUE_WITHDRAWALS__QWITHDRAWALS_SHARES_LENGTH: - // get number of item to parse - params->current_item_nb = U2BE(msg->parameter, PARAMETER_LENGTH); + // get number of items to parse + U2BE_from_parameter(msg->parameter, ¶ms->current_item_count); context->next_param = LR_QUEUE_WITHDRAWALS__QWITHDRAWALS__SHARES_ITEM; break; case LR_QUEUE_WITHDRAWALS__QWITHDRAWALS__SHARES_ITEM: // we skip parsing shares item as they are not needed for clearsigning // as not having ETH / ERC20 amount to display would confuse users - params->current_item_nb -= 1; - if (params->current_item_nb == 0) { + if (params->current_item_count > 0) { + params->current_item_count -= 1; + } + if (params->current_item_count == 0) { // here we arrive at the end of the queuedWithdrawal array element // check if there are other queuedWithdrawals to parse - params->queued_withdrawals_nb -= 1; - if (params->queued_withdrawals_nb == 0) { + params->queued_withdrawals_count -= 1; + if (params->queued_withdrawals_count == 0) { // if not we finished parsing context->next_param = LR_QUEUE_WITHDRAWALS_UNEXPECTED_PARAMETER; } else { - // if there are other queuedWithdrawals we go back to parsing the strategies - // offset - context->next_param = LR_QUEUE_WITHDRAWALS__QWITHDRAWALS_STRATEGIES_OFFSET; + // if there are other queuedWithdrawals we go back to parsing the + // next queueWithdrawal struct offset + context->next_param = LR_QUEUE_WITHDRAWALS__QWITHDRAWALS_STRUCT_OFFSET; } } break; diff --git a/src/handle_query_contract_ui.c b/src/handle_query_contract_ui.c index 08d1798..7ef2c94 100644 --- a/src/handle_query_contract_ui.c +++ b/src/handle_query_contract_ui.c @@ -185,7 +185,7 @@ static bool deposit_into_stragey_ui_lr(ethQueryContractUI_t *msg, context_t *con break; case 1: strlcpy(msg->title, "Strategy", msg->titleLength); - if (params->strategy_to_display == -1 || + if (params->strategy_to_display == UNKNOW_LR_STRATEGY || params->strategy_to_display >= LR_STRATEGIES_COUNT) { strlcpy(msg->msg, "UNKNOWN", msg->msgLength); } else { @@ -195,15 +195,15 @@ static bool deposit_into_stragey_ui_lr(ethQueryContractUI_t *msg, context_t *con break; case 2: strlcpy(msg->title, "Amount", msg->titleLength); - amountToString( - params->erc20_amount_to_display, - sizeof(params->erc20_amount_to_display), - ERC20_DECIMALS, - params->erc20_to_display == -1 || params->erc20_to_display >= LR_STRATEGIES_COUNT - ? "UNKNOWN" - : lr_tickers[params->erc20_to_display], - msg->msg, - msg->msgLength); + amountToString(params->erc20_amount_to_display, + sizeof(params->erc20_amount_to_display), + ERC20_DECIMALS, + params->erc20_to_display == UNKNOW_LR_STRATEGY || + params->erc20_to_display >= LR_STRATEGIES_COUNT + ? "UNKNOWN" + : lr_tickers[params->erc20_to_display], + msg->msg, + msg->msgLength); ret = true; break; default: @@ -224,27 +224,40 @@ static bool queue_withdrawals_ui_lr(ethQueryContractUI_t *msg, context_t *contex ret = true; break; - default: - if (params->queued_withdrawals_nb > 0) { - strlcpy(msg->title, "Strategy", msg->titleLength); - - // process the first displayable strategy and remove it from the list - for (size_t i = 0; i < LR_STRATEGIES_COUNT; i += 1) { - if (params->strategies[i]) { - strlcpy(msg->msg, lr_tickers[i], msg->msgLength); - params->strategies[i] = false; - break; + case 1: + strlcpy(msg->title, "Withdrawer", msg->titleLength); + strlcpy(msg->msg, params->withdrawer, msg->msgLength); + ret = true; + break; + + default: { + { + // removing the first screen to current screen index + // to get the index of the withdrawal + uint8_t withdrawal_index = msg->screenIndex - 2; + + if (withdrawal_index < params->strategies_count) { + strlcpy(msg->title, "Strategy", msg->titleLength); + uint8_t strategy = params->strategies[withdrawal_index]; + + if (strategy == UNKNOW_LR_STRATEGY || strategy - 1 >= LR_STRATEGIES_COUNT) { + char x[4] = {65 + strategy, + 65 + withdrawal_index, + 65 + params->strategies_count, + '\0'}; + + strlcpy(msg->msg, x, msg->msgLength); + } else { + strlcpy(msg->msg, lr_tickers[strategy - 1], msg->msgLength); } } - params->queued_withdrawals_nb -= 1; - ret = true; break; } - PRINTF("Received an invalid screenIndex\n"); break; + } } return ret; } diff --git a/src/kiln_plugin.h b/src/kiln_plugin.h index d81d6a0..4478d85 100644 --- a/src/kiln_plugin.h +++ b/src/kiln_plugin.h @@ -81,6 +81,7 @@ typedef enum { typedef enum { LR_QUEUE_WITHDRAWALS_QWITHDRAWALS_OFFSET = 0, LR_QUEUE_WITHDRAWALS_QWITHDRAWALS_LENGTH, + LR_QUEUE_WITHDRAWALS__QWITHDRAWALS_STRUCT_OFFSET, LR_QUEUE_WITHDRAWALS__QWITHDRAWALS_STRATEGIES_OFFSET, LR_QUEUE_WITHDRAWALS__QWITHDRAWALS_SHARES_OFFSET, LR_QUEUE_WITHDRAWALS__QWITHDRAWALS_WITHDRAWER, @@ -125,13 +126,23 @@ typedef struct { uint8_t erc20_amount_to_display[INT256_LENGTH]; } lr_deposit_t; +#define UNKNOW_LR_STRATEGY 255 + typedef struct { - // utils - uint16_t queued_withdrawals_nb; - uint16_t current_item_nb; - // display + // -- utils + uint16_t queued_withdrawals_count; + uint16_t current_item_count; + // -- display + uint8_t strategies_count; char withdrawer[ADDRESS_STR_LEN]; - bool strategies[LR_STRATEGIES_COUNT]; + // list of strategies indexes **INCREMENTED BY 1** to display in the UI + // 0 is reserved for end of array + // UNKNOW_LR_STRATEGY is used to display the "unknown" strategy + // (i) in practice, we should not encounter more than + // LR_STRATEGIES_COUNT +~ a few potential unsupported + // strategies in the plugin. So * 3 should be a good enough buffer. + // (ii) in practice there should not be more than (2 ** 8) - 2 known strategies + uint8_t strategies[LR_STRATEGIES_COUNT * 3]; } lr_queue_withdrawals_t; typedef struct { diff --git a/tests/build_local_test_elfs.sh b/tests/build_local_test_elfs.sh index 19b583b..f87a185 100755 --- a/tests/build_local_test_elfs.sh +++ b/tests/build_local_test_elfs.sh @@ -3,7 +3,7 @@ # FILL THESE WITH YOUR OWN SDKs PATHS and APP-ETHEREUM's ROOT NANOS_SDK=$NANOS_SDK NANOX_SDK=$NANOX_SDK -APP_ETHEREUM=${APP_ETHEREUM:-"/plugin_dev/app-ethereum"} +APP_ETHEREUM=${APP_ETHEREUM:-"../app-ethereum"} set -e diff --git a/tests/cal/b2c.json b/tests/cal/b2c.json index 418129b..0c14430 100644 --- a/tests/cal/b2c.json +++ b/tests/cal/b2c.json @@ -79,8 +79,8 @@ "method": "depositIntoStrategy", "plugin": "Kiln" }, - "0xf123991e": { - "method": "queueWithdrawal", + "0x0dd8dd02": { + "method": "queueWithdrawals", "plugin": "Kiln" }, "0xf3be65d3": { @@ -103,7 +103,6 @@ } } } - } -], -"name": "Kiln" + ], + "name": "Kiln" } \ No newline at end of file diff --git a/tests/src/lrQueueWithdrawals.test.js b/tests/src/lrQueueWithdrawals.test.js index 88c74fd..3b1706d 100644 --- a/tests/src/lrQueueWithdrawals.test.js +++ b/tests/src/lrQueueWithdrawals.test.js @@ -1,21 +1,12 @@ import 'core-js/stable'; import 'regenerator-runtime/runtime'; -import { - waitForAppScreen, - kilnJSON, - zemu, - genericTx, - nano_models, - SPECULOS_ADDRESS, - txFromEtherscan, -} from './test.fixture'; +import { waitForAppScreen, zemu, genericTx, nano_models } from './test.fixture'; import { ethers } from 'ethers'; -import { parseEther, parseUnits } from 'ethers/lib/utils'; +import { parseEther } from 'ethers/lib/utils'; import { ledgerService } from '@ledgerhq/hw-app-eth'; const contractAddr = '0x858646372cc42e1a627fce94aa7a7033e7cf075a'; // strategy manager -const pluginName = 'Kiln'; const abi_path = `../cal/abis/${contractAddr}.json`; const abi = require(abi_path); @@ -74,18 +65,18 @@ nano_models.forEach(function (model) { const { data } = await contract.populateTransaction.queueWithdrawals([ { strategies: [ - '0x54945180dB7943c0ed0FEE7EdaB2Bd24620256bc', // cbETH - '0x13760F50a9d7377e4F20CB8CF9e4c26586c658ff', // ankrETH - '0x57ba429517c3473B6d34CA9aCd56c0e735b94c02', // osETH + '0x54945180dB7943c0ed0FEE7EdaB2Bd24620256bc', // cbETH + '0x13760F50a9d7377e4F20CB8CF9e4c26586c658ff', // ankrETH + '0x57ba429517c3473B6d34CA9aCd56c0e735b94c02', // osETH ], shares: [parseEther('0.1'), parseEther('0.2'), parseEther('0.3')], withdrawer: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', }, { strategies: [ - '0xd5F7838F5C461fefF7FE49ea5ebaF7728bB0ADfa', // mETH - '0x54945180dB7943c0ed0FEE7EdaB2Bd24620256bc', // cbETH - '0x856c4Efb76C1D1AE02e20CEB03A2A6a08b0b8dC3', // OETH + '0xd5F7838F5C461fefF7FE49ea5ebaF7728bB0ADfa', // mETH + '0x54945180dB7943c0ed0FEE7EdaB2Bd24620256bc', // cbETH + '0x856c4Efb76C1D1AE02e20CEB03A2A6a08b0b8dC3', // OETH ], shares: [parseEther('0.5'), parseEther('0.1'), parseEther('0.2')], withdrawer: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', @@ -118,6 +109,61 @@ nano_models.forEach(function (model) { [right_clicks, 0] ); await tx; - }), + }) + ); + + test( + '[Nano ' + model.letter + '] LR Queue Withdrawals unknown strategy', + zemu(model, async (sim, eth) => { + const contract = new ethers.Contract(contractAddr, abi); + + const { data } = await contract.populateTransaction.queueWithdrawals([ + { + strategies: [ + '0x54945180dB7943c0ed0FEE7EdaB2Bd24620256bc', // cbETH + '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', // unkwown strategy + '0x57ba429517c3473B6d34CA9aCd56c0e735b94c02', // osETH + ], + shares: [parseEther('0.1'), parseEther('0.2'), parseEther('0.3')], + withdrawer: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', + }, + { + strategies: [ + '0xd5F7838F5C461fefF7FE49ea5ebaF7728bB0ADfa', // mETH + '0x856c4Efb76C1D1AE02e20CEB03A2A6a08b0b8dC3', // OETH + '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', // unkwown strategy + ], + shares: [parseEther('0.5'), parseEther('0.1'), parseEther('0.2')], + withdrawer: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', + }, + ]); + let unsignedTx = genericTx; + + unsignedTx.to = contractAddr; + unsignedTx.data = data; + unsignedTx.value = parseEther('0'); + + const serializedTx = ethers.utils + .serializeTransaction(unsignedTx) + .slice(2); + const resolution = await ledgerService.resolveTransaction( + serializedTx, + eth.loadConfig, + { + externalPlugins: true, + } + ); + const tx = eth.signTransaction("44'/60'/0'/0", serializedTx, resolution); + const right_clicks = model.letter === 'S' ? 9 : 7; + + await waitForAppScreen(sim); + await sim.navigateAndCompareSnapshots( + '.', + model.name + '_lrQueueWithdrawals_2_3_dimension_with_unknowns', + [right_clicks, 0] + ); + await tx; + }) + ); });