diff --git a/.github/workflows/auto-author-assign.yml b/.github/workflows/auto-author-assign.yml index a6255c9..82adf17 100644 --- a/.github/workflows/auto-author-assign.yml +++ b/.github/workflows/auto-author-assign.yml @@ -1,3 +1,4 @@ +--- name: 'Auto Author Assign' on: diff --git a/.github/workflows/build-workflow.yml b/.github/workflows/build-workflow.yml deleted file mode 100644 index 1eb105d..0000000 --- a/.github/workflows/build-workflow.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Compilation - -on: - push: - branches: - - master - pull_request: - branches: - - master - - develop - workflow_dispatch: - -jobs: - nano_release_build: - name: Build release application for NanoS, X and S+ - strategy: - matrix: - sdk: ["$NANOS_SDK", "$NANOX_SDK", "$NANOSP_SDK"] - runs-on: ubuntu-latest - container: - image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest - - steps: - - name: Clone - uses: actions/checkout@v3 - - - name: Build Ethereum - run: | - make -j BOLOS_SDK=${{ matrix.sdk }} - - - name: Build an altcoin - run: | - make clean - make -j BOLOS_SDK=${{ matrix.sdk }} CHAIN=polygon diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 4700a0c..a15a896 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -1,39 +1,19 @@ +--- name: Tests on: + workflow_dispatch: push: - branches: - - master - pull_request: branches: - master - develop - workflow_dispatch: + pull_request: jobs: - scan-build: - name: Clang Static Analyzer - runs-on: ubuntu-latest - container: - image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest - steps: - - uses: actions/checkout@v3 - - - name: Build with Clang Static Analyzer - run: | - make clean - scan-build --use-cc=clang -analyze-headers -enable-checker security -enable-checker unix -enable-checker valist -o scan-build --status-bugs make default - - - uses: actions/upload-artifact@v3 - if: failure() - with: - name: scan-build - path: scan-build - -# ===================================================== -# ZEMU TESTS -# ===================================================== + # ===================================================== + # ZEMU TESTS + # ===================================================== building_for_e2e_zemu_tests: name: Building binaries for E2E Zemu tests @@ -97,9 +77,9 @@ jobs: - name: Run zemu tests run: cd tests/zemu/ && yarn test -# ===================================================== -# SPECULOS TESTS -# ===================================================== + # ===================================================== + # SPECULOS TESTS + # ===================================================== building_for_e2e_speculos_tests: @@ -162,9 +142,9 @@ jobs: pytest --model ${{ matrix.model }} --path ./elfs/${{ matrix.model }}.elf --display headless -# ===================================================== -# RAGGER TESTS -# ===================================================== + # ===================================================== + # RAGGER TESTS + # ===================================================== build_ragger_elfs: name: Build app for Ragger tests @@ -180,3 +160,43 @@ jobs: with: download_app_binaries_artifact: "ragger_elfs" test_dir: tests/ragger + + # ===================================================== + # STATIC ANALYSIS + # ===================================================== + + # Static analysis on the main ETH chain is covered by the guidelines enforcer + scan-build: + name: Clang Static Analyzer on altcoin + runs-on: ubuntu-latest + container: + image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder:latest + strategy: + fail-fast: false + matrix: + device: ["nanos", "nanos2", "nanox", "stax"] + + steps: + - name: Clone + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Build with Clang Static Analyzer + run: | + eval "BOLOS_SDK=\$$(echo ${{ matrix.device }} | tr [:lower:] [:upper:])_SDK" && \ + echo "BOLOS_SDK value will be: ${BOLOS_SDK}" && \ + make -j ENABLE_SDK_WERROR=1 BOLOS_SDK=${BOLOS_SDK} CHAIN=polygon scan-build + + - uses: actions/upload-artifact@v3 + if: failure() + with: + name: scan-build + path: scan-build + + - name: Upload scan result + if: failure() + uses: actions/upload-artifact@v3 + with: + name: scan-build + path: scan-build diff --git a/.github/workflows/lint-workflow.yml b/.github/workflows/lint-workflow.yml index 7b98075..ca32687 100644 --- a/.github/workflows/lint-workflow.yml +++ b/.github/workflows/lint-workflow.yml @@ -1,26 +1,26 @@ +--- name: Code style check +# This workflow will run linting checks to ensure a level of uniformization among all Ledger applications. +# +# The presence of this workflow is mandatory as a minimal level of linting is required. +# You are however free to modify the content of the .clang-format file and thus the coding style of your application. +# We simply ask you to not diverge too much from the linting of the Boilerplate application. + on: + workflow_dispatch: push: branches: - - master + - master + - main + - develop pull_request: - branches: - - master - - develop jobs: - job_lint: - name: Lint - runs-on: ubuntu-latest - - steps: - - name: Clone - uses: actions/checkout@v3 - - - name: Lint - uses: DoozyX/clang-format-lint-action@v0.14 - with: - source: "./" - extensions: "h,c" - clangFormatVersion: 12.0.1 + check_linting: + name: Check linting using the reusable workflow + uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_lint.yml@v1 + with: + source: './' + extensions: 'h,c' + version: 12 diff --git a/.github/workflows/sdk-generation.yml b/.github/workflows/sdk-generation.yml index 9d26eb3..b563e0f 100644 --- a/.github/workflows/sdk-generation.yml +++ b/.github/workflows/sdk-generation.yml @@ -1,3 +1,4 @@ +--- name: Updating the SDK on: diff --git a/.github/workflows/swap-ci-workflow.yml b/.github/workflows/swap-ci-workflow.yml index 3999224..022fe5a 100644 --- a/.github/workflows/swap-ci-workflow.yml +++ b/.github/workflows/swap-ci-workflow.yml @@ -1,11 +1,12 @@ +--- name: Swap functional tests on: workflow_dispatch: push: branches: - - master - - develop + - master + - develop pull_request: jobs: diff --git a/src/eth_plugin_interface.h b/src/eth_plugin_interface.h index 5bf5499..fc8f239 100644 --- a/src/eth_plugin_interface.h +++ b/src/eth_plugin_interface.h @@ -1,3 +1,5 @@ +// clang-format off + #ifndef _ETH_PLUGIN_INTERFACE_H_ #define _ETH_PLUGIN_INTERFACE_H_ @@ -7,30 +9,43 @@ #include "tokens.h" #include "shared_context.h" -// Interface version. To be updated everytime we introduce breaking changes to the plugin interface. -typedef enum { +/************************************************************************************************* + * Comments provided in this file are quick reminders on the usage of the plugin interface * + * Reading the real plugin documentation is GREATLY recommended. * + * You can find the latest version here: * + * https://github.com/LedgerHQ/app-ethereum/blob/develop/doc/ethapp_plugins.adoc * + *************************************************************************************************/ + +// Interface version. Will be updated every time a breaking change in the interface is introduced. +typedef enum eth_plugin_interface_version_e { ETH_PLUGIN_INTERFACE_VERSION_1 = 1, ETH_PLUGIN_INTERFACE_VERSION_2 = 2, ETH_PLUGIN_INTERFACE_VERSION_3 = 3, ETH_PLUGIN_INTERFACE_VERSION_4 = 4, ETH_PLUGIN_INTERFACE_VERSION_5 = 5, - ETH_PLUGIN_INTERFACE_VERSION_LATEST = 6 + ETH_PLUGIN_INTERFACE_VERSION_LATEST = 6, } eth_plugin_interface_version_t; -typedef enum { +// Codes for the different requests Ethereum can send to the plugin +// The dispatch is handled by the SDK itself, the plugin code does not have to handle it +typedef enum eth_plugin_msg_e { + // Codes for actions the Ethereum app can ask the plugin to perform ETH_PLUGIN_INIT_CONTRACT = 0x0101, ETH_PLUGIN_PROVIDE_PARAMETER = 0x0102, ETH_PLUGIN_FINALIZE = 0x0103, ETH_PLUGIN_PROVIDE_INFO = 0x0104, ETH_PLUGIN_QUERY_CONTRACT_ID = 0x0105, ETH_PLUGIN_QUERY_CONTRACT_UI = 0x0106, - ETH_PLUGIN_CHECK_PRESENCE = 0x01FF + // Special request: the Ethereum app is checking if we are installed on the device + ETH_PLUGIN_CHECK_PRESENCE = 0x01FF, } eth_plugin_msg_t; -typedef enum { - // Unsuccesful return values + +// Reply codes when responding to the Ethereum application +typedef enum eth_plugin_result_e { + // Unsuccessful return values ETH_PLUGIN_RESULT_ERROR = 0x00, ETH_PLUGIN_RESULT_UNAVAILABLE = 0x01, ETH_PLUGIN_RESULT_UNSUCCESSFUL = 0x02, // Used for comparison @@ -39,38 +54,58 @@ typedef enum { ETH_PLUGIN_RESULT_SUCCESSFUL = 0x03, // Used for comparison ETH_PLUGIN_RESULT_OK = 0x04, ETH_PLUGIN_RESULT_OK_ALIAS = 0x05, - ETH_PLUGIN_RESULT_FALLBACK = 0x06 - + ETH_PLUGIN_RESULT_FALLBACK = 0x06, } eth_plugin_result_t; -typedef enum { +// Format of UI the Ethereum application has to use for this plugin +typedef enum eth_ui_type_e { + // If uiType is UI_AMOUNT_ADDRESS, Ethereum will use the amount/address UI + // the amount and address provided by the plugin will be used + // If tokenLookup1 is set, the amount is provided for this token ETH_UI_TYPE_AMOUNT_ADDRESS = 0x01, - ETH_UI_TYPE_GENERIC = 0x02 + // If uiType is UI_TYPE_GENERIC, Ethereum will use the dedicated ETH plugin UI + // the ETH application provides tokens if requested then prompts for each UI field + // The first field is forced by the ETH app to be the name + version of the plugin handling the + // request. The last field is the fee amount + ETH_UI_TYPE_GENERIC = 0x02, } eth_ui_type_t; -typedef void (*PluginCall)(int, void *); -// Shared objects, read-write - -typedef struct ethPluginSharedRW_t { +// Scratch objects and utilities available to the plugin READ-WRITE +typedef struct ethPluginSharedRW_s { cx_sha3_t *sha3; - } ethPluginSharedRW_t; -// Shared objects, read-only -typedef struct ethPluginSharedRO_t { +// Transaction data available to the plugin READ-ONLY +typedef struct ethPluginSharedRO_s { txContent_t *txContent; - } ethPluginSharedRO_t; + +// Plugin-only memory allocated by the Ethereum application and used by the plugin. +#define PLUGIN_CONTEXT_SIZE (5 * INT256_LENGTH) +// It is recommended to cast the raw uin8_t array to a structure meaningfull for your plugin +// Helper to check that the actual plugin context structure is not bigger than the allocated memory +#define ASSERT_SIZEOF_PLUGIN_CONTEXT(s) \ + _Static_assert(sizeof(s) <= PLUGIN_CONTEXT_SIZE, "Plugin context structure is too big.") + + +/* + * HANDLERS AND PARAMETERS + * Parameters associated with the requests the Ethereum application can ask the plugin to perform + * The plugin SDK will automatically call the relevant handler for the received code, so the plugin + * has to define each of the handler functions declared below. + */ + + // Init Contract -typedef struct ethPluginInitContract_t { - uint8_t interfaceVersion; - uint8_t result; +typedef struct ethPluginInitContract_s { + eth_plugin_interface_version_t interfaceVersion; + eth_plugin_result_t result; // in ethPluginSharedRW_t *pluginSharedRW; @@ -83,26 +118,30 @@ typedef struct ethPluginInitContract_t { char *alias; // 29 bytes alias if ETH_PLUGIN_RESULT_OK_ALIAS set } ethPluginInitContract_t; +// void handle_init_contract(ethPluginInitContract_t *parameters); + // Provide parameter -typedef struct ethPluginProvideParameter_t { +typedef struct ethPluginProvideParameter_s { ethPluginSharedRW_t *pluginSharedRW; ethPluginSharedRO_t *pluginSharedRO; - uint8_t *pluginContext; + uint8_t *pluginContext; // PLUGIN_CONTEXT_SIZE const uint8_t *parameter; // 32 bytes parameter uint32_t parameterOffset; - uint8_t result; + eth_plugin_result_t result; } ethPluginProvideParameter_t; +// void handle_provide_parameter(ethPluginProvideParameter_t *parameters); + // Finalize -typedef struct ethPluginFinalize_t { +typedef struct ethPluginFinalize_s { ethPluginSharedRW_t *pluginSharedRW; ethPluginSharedRO_t *pluginSharedRO; - uint8_t *pluginContext; + uint8_t *pluginContext; // PLUGIN_CONTEXT_SIZE uint8_t *tokenLookup1; // set by the plugin if a token should be looked up uint8_t *tokenLookup2; @@ -111,26 +150,20 @@ typedef struct ethPluginFinalize_t { const 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 - uint8_t uiType; + eth_ui_type_t uiType; uint8_t numScreens; // ignored if uiType is UI_AMOUNT_ADDRESS - uint8_t result; + eth_plugin_result_t result; } ethPluginFinalize_t; +// void handle_finalize(ethPluginFinalize_t *parameters); -// If uiType is UI_AMOUNT_ADDRESS, the amount and address provided by the plugin will be used -// If tokenLookup1 is set, the amount is provided for this token - -// if uiType is UI_TYPE_GENERIC, the ETH application provides tokens if requested then prompts -// for each UI field -// The first field is forced by the ETH app to be the name + version of the plugin handling the -// request The last field is the fee amount // Provide token -typedef struct ethPluginProvideInfo_t { +typedef struct ethPluginProvideInfo_s { ethPluginSharedRW_t *pluginSharedRW; ethPluginSharedRO_t *pluginSharedRO; - uint8_t *pluginContext; + uint8_t *pluginContext; // PLUGIN_CONTEXT_SIZE union extraInfo_t *item1; // set by the ETH application, to be saved by the plugin union extraInfo_t *item2; @@ -138,37 +171,41 @@ typedef struct ethPluginProvideInfo_t { 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; + eth_plugin_result_t result; } ethPluginProvideInfo_t; +// void handle_provide_token(ethPluginProvideInfo_t *parameters); + // Query Contract name and version // This is always called on the non aliased contract -typedef struct ethQueryContractID_t { +typedef struct ethQueryContractID_s { ethPluginSharedRW_t *pluginSharedRW; ethPluginSharedRO_t *pluginSharedRO; - uint8_t *pluginContext; + uint8_t *pluginContext; // PLUGIN_CONTEXT_SIZE char *name; size_t nameLength; char *version; size_t versionLength; - uint8_t result; + eth_plugin_result_t result; } ethQueryContractID_t; +// void handle_query_contract_id(ethQueryContractID_t *parameters); + // Query Contract UI -typedef struct ethQueryContractUI_t { +typedef struct ethQueryContractUI_s { 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 *pluginContext; // PLUGIN_CONTEXT_SIZE uint8_t screenIndex; char *title; @@ -176,8 +213,11 @@ typedef struct ethQueryContractUI_t { char *msg; size_t msgLength; - uint8_t result; + eth_plugin_result_t result; } ethQueryContractUI_t; +// void handle_query_contract_ui(ethQueryContractUI_t *parameters); #endif // _ETH_PLUGIN_INTERFACE_H_ + +// clang-format on diff --git a/src/eth_plugin_internal.h b/src/eth_plugin_internal.h index 2d2ca03..f7031ec 100644 --- a/src/eth_plugin_internal.h +++ b/src/eth_plugin_internal.h @@ -21,6 +21,7 @@ bool U2BE_from_parameter(const uint8_t* parameter, uint16_t* value); bool U4BE_from_parameter(const uint8_t* parameter, uint32_t* value); typedef bool (*PluginAvailableCheck)(void); +typedef void (*PluginCall)(int, void*); typedef struct internalEthPlugin_t { PluginAvailableCheck availableCheck; diff --git a/src_plugin_sdk/standard_plugin.mk b/src_plugin_sdk/standard_plugin.mk new file mode 100644 index 0000000..6a2023f --- /dev/null +++ b/src_plugin_sdk/standard_plugin.mk @@ -0,0 +1,73 @@ +# **************************************************************************** +# Ledger Ethereum Plugin SDK +# (c) 2023 Ledger SAS. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# **************************************************************************** + +ifeq ($(BOLOS_SDK),) +$(error Environment variable BOLOS_SDK is not set) +endif + +# Prevent compilation of BAGL/NBGL +# Has to be before any SDK include +DISABLE_UI = 1 + +include $(BOLOS_SDK)/Makefile.defines + +APPVERSION ?= "$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)" + +# Application source files +APP_SOURCE_PATH += src ethereum-plugin-sdk + +# Application icons following guidelines: +# https://developers.ledger.com/docs/embedded-app/design-requirements/#device-icon +NORMAL_NAME ?= $(shell echo -n "$(APPNAME)" | tr " ." "_" | tr "[:upper:]" "[:lower:]") +ICON_NANOS = icons/nanos_app_$(NORMAL_NAME).gif +ICON_NANOX = icons/nanox_app_$(NORMAL_NAME).gif +ICON_NANOSP = $(ICON_NANOX) +ICON_STAX = icons/stax_app_$(NORMAL_NAME).gif + +ifeq ($(TARGET_NAME),TARGET_STAX) + DEFINES += ICONGLYPH=C_stax_$(NORMAL_NAME)_64px + DEFINES += ICONBITMAP=C_stax_$(NORMAL_NAME)_64px_bitmap +endif + +CURVE_APP_LOAD_PARAMS = secp256k1 + +PATH_APP_LOAD_PARAMS ?= "44'/60'" + +VARIANT_PARAM = COIN +VARIANTS_VALUES ?= $(NORMAL_NAME) + +HAVE_APPLICATION_FLAG_LIBRARY = 1 + +DISABLE_STANDARD_APP_FILES = 1 +DISABLE_STANDARD_SNPRINTF = 1 +DISABLE_STANDARD_USB = 1 +DISABLE_STANDARD_WEBUSB = 1 +DISABLE_STANDARD_BAGL_UX_FLOW = 1 + +# Required for PRINTFs to compile +ifeq ($(DEBUG),0) + DISABLE_STANDARD_SEPROXYHAL = 1 +endif + +# So the plugin can still access the necessary NBGL types and pass its icon to +# the Ethereum app +ifeq ($(TARGET_NAME),TARGET_STAX) + DEFINES += HAVE_NBGL + INCLUDES_PATH += $(BOLOS_SDK)/lib_nbgl/include $(BOLOS_SDK)/lib_ux_stax +endif + +include $(BOLOS_SDK)/Makefile.standard_app diff --git a/tools/build_sdk.py b/tools/build_sdk.py index 9f5775c..dfda9be 100755 --- a/tools/build_sdk.py +++ b/tools/build_sdk.py @@ -220,6 +220,7 @@ if __name__ == "__main__": files_to_copy = [ "CHANGELOG.md", "README.md", + "standard_plugin.mk", ] for file in files_to_copy: shutil.copyfile("src_plugin_sdk/" + file,