Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Token2022 (extensions) support #9

Open
wants to merge 24 commits into
base: compute-budget-rf-v2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0b73d10
src: Improve crypto code using SDK crypto_helpers and LEDGER_ASSERT
Dec 12, 2023
ad37ff9
src: Cleanup unused parameter from BLE_power()
Dec 12, 2023
8198345
src: Drop deprectaed check_api_level()
Dec 12, 2023
d4098be
Merge pull request #82 from LedgerHQ/xch/fix-ci-warnings
xchapron-ledger Dec 14, 2023
cfac20e
workflows/sonarcloud.yml: Update workflow following app-bitcoin-new e…
Dec 14, 2023
9f5b1cd
Merge pull request #83 from LedgerHQ/xch/sonarcloud
xchapron-ledger Dec 14, 2023
71d0904
[auto] Update screenshot
Jan 10, 2024
2d1f62f
Merge pull request #84 from LedgerHQ/auto-update-screenshots
sgliner-ledger Jan 11, 2024
33e3cc8
[auto] Update screenshot
Feb 21, 2024
d769b7a
Merge pull request #86 from LedgerHQ/auto-update-screenshots
sgliner-ledger Feb 21, 2024
93ae038
Add support for Token2022 instructions(extensions).
0xMMBD Feb 22, 2024
527df19
Added Token2022 unit tests
0xMMBD Feb 22, 2024
2d4f805
[auto] Update screenshot
sgliner-ledger Feb 27, 2024
5d6fdac
Fix invalid handling of the Token2022 instructions.
0xMMBD Mar 18, 2024
2118779
Add compute budget support (#7)
0xMMBD Mar 29, 2024
437603d
Add warning screen for the user when token2022 extension is present
0xMMBD Apr 2, 2024
e73a9cf
Added split-stake instruction support (#9) (#10)
0xMMBD Apr 7, 2024
3dfa53e
[auto] Update screenshot
Apr 8, 2024
5c5e1b6
Merge pull request #87 from LedgerHQ/auto-update-screenshots
sgliner-ledger Apr 8, 2024
ce8ca77
Release v1.4.3
fbeutin-ledger Apr 24, 2024
5138865
Merge pull request #88 from LedgerHQ/fbe/release_v1.4.3
fbeutin-ledger Apr 24, 2024
2edf8a2
Merge branch 'LedgerHQ:develop' into develop
0xMMBD May 9, 2024
a4e6bb2
Merge branch 'develop' into pr/token2022-support-v3
0xMMBD May 9, 2024
028cc29
Add warning to the TransferChecked instruction when Token2022 program…
0xMMBD May 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
- [ ] Target branch is `develop` <!-- unless you have a very good reason -->
- [ ] Application version has been bumped <!-- required if your changes are to be deployed -->

<!-- Make sure you followed the process described in https://developers.ledger.com/docs/embedded-app/maintenance/ before opening your Pull Request.
<!-- Make sure you followed the process described in https://developers.ledger.com/docs/device-app/deliver/maintenance before opening your Pull Request.
Don't hesitate to contact us directly on Discord if you have any questions ! https://developers.ledger.com/discord -->
73 changes: 28 additions & 45 deletions .github/workflows/sonarcloud.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,54 +8,37 @@ on:
pull_request:

jobs:
build:
name: SonarQube analyze
sonarcloud:
runs-on: ubuntu-latest
container:
image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-legacy:latest

env:
SONAR_SCANNER_VERSION: 4.7.0.2747
SONAR_SERVER_URL: "https://sonarcloud.io"
BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory # Directory where build-wrapper output will be placed
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Download and set up sonar-scanner
env:
SONAR_SCANNER_DOWNLOAD_URL: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${{ env.SONAR_SCANNER_VERSION }}-linux.zip
run: |
apt-get update -y
apt-get upgrade -y
DEBIAN_FRONTEND=noninteractive apt-get install -y tzdata
curl -sL https://deb.nodesource.com/setup_16.x | bash -
apt-get install -y gcovr nodejs unzip lcov
mkdir -p $HOME/.sonar
curl -sSLo $HOME/.sonar/sonar-scanner.zip ${{ env.SONAR_SCANNER_DOWNLOAD_URL }}
unzip -o $HOME/.sonar/sonar-scanner.zip -d $HOME/.sonar/
echo "$HOME/.sonar/sonar-scanner-${{ env.SONAR_SCANNER_VERSION }}-linux/bin" >> $GITHUB_PATH
- name: Download and set up build-wrapper
env:
BUILD_WRAPPER_DOWNLOAD_URL: ${{ env.SONAR_SERVER_URL }}/static/cpp/build-wrapper-linux-x86.zip
run: |
curl -sSLo $HOME/.sonar/build-wrapper-linux-x86.zip ${{ env.BUILD_WRAPPER_DOWNLOAD_URL }}
unzip -o $HOME/.sonar/build-wrapper-linux-x86.zip -d $HOME/.sonar/
echo "$HOME/.sonar/build-wrapper-linux-x86" >> $GITHUB_PATH
- name: Generate code coverage
working-directory: libsol
run: |
make COVERAGE=1
gcovr --root .. --sonarqube ../coverage.xml
- name: Run build-wrapper
run: |
build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} make clean all
- name: Run sonar-scanner
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: |
sonar-scanner --define sonar.host.url="${{ env.SONAR_SERVER_URL }}" --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}"
- uses: actions/checkout@v3
with:
# Disabling shallow clone is recommended for improving relevancy of reporting
fetch-depth: 0
- name: Install dependencies
run: |
apt-get update -y
apt-get upgrade -y
DEBIAN_FRONTEND=noninteractive apt-get install -y tzdata
apt-get install -y libcmocka-dev gcovr unzip
- name: Install sonar-scanner and build-wrapper
uses: sonarsource/sonarcloud-github-c-cpp@v2

- name: Generate code coverage
working-directory: libsol
run: |
make COVERAGE=1
gcovr --root .. --sonarqube ../coverage.xml
- name: Run build-wrapper
run: |
build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} make clean all
- name: Run sonar-scanner
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: sonar-scanner --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" #Consult https://docs.sonarcloud.io/advanced-setup/ci-based-analysis/sonarscanner-cli/ for more information and options
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ APP_LOAD_PARAMS += $(COMMON_LOAD_PARAMS)
APPNAME = "Solana"
APPVERSION_M = 1
APPVERSION_N = 4
APPVERSION_P = 2
APPVERSION_P = 3
APPVERSION = "$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)"

ifeq ($(TARGET_NAME),TARGET_NANOS)
Expand Down Expand Up @@ -131,6 +131,9 @@ include $(BOLOS_SDK)/Makefile.glyphs
APP_SOURCE_PATH += src
SDK_SOURCE_PATH += lib_stusb lib_stusb_impl

# Allow usage of function from lib_standard_app/crypto_helpers.c
APP_SOURCE_FILES += ${BOLOS_SDK}/lib_standard_app/crypto_helpers.c

ifneq ($(TARGET_NAME),TARGET_STAX)
SDK_SOURCE_PATH += lib_ux
endif
Expand Down
4 changes: 4 additions & 0 deletions libsol/common_byte_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
0x06, 0xdd, 0xf6, 0xe1, 0xd7, 0x65, 0xa1, 0x93, 0xd9, 0xcb, 0xe1, 0x46, 0xce, 0xeb, 0x79, \
0xac, 0x1c, 0xb4, 0x85, 0xed, 0x5f, 0x5b, 0x37, 0x91, 0x3a, 0x8c, 0xf5, 0x85, 0x7e, 0xff, \
0x00, 0xa9
#define PROGRAM_ID_SPL_TOKEN_2022 /* TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb */ \
0x06, 0xdd, 0xf6, 0xe1, 0xee, 0x75, 0x8f, 0xde, 0x18, 0x42, 0x5d, 0xbc, 0xe4, 0x6c, 0xcd, \
0xda, 0xb6, 0x1a, 0xfc, 0x4d, 0x83, 0xb9, 0x0d, 0x27, 0xfe, 0xbd, 0xf9, 0x28, 0xd8, 0xa1, \
0x8b, 0xfc
#define PROGRAM_ID_SYSTEM BYTES32_BS58_1
#define PROGRAM_ID_STAKE /* "Stake11111111111111111111111111111111111111" */ \
0x06, 0xa1, 0xd8, 0x17, 0x91, 0x37, 0x54, 0x2a, 0x98, 0x34, 0x37, 0xbd, 0xfe, 0x2a, 0x7a, \
Expand Down
81 changes: 81 additions & 0 deletions libsol/include/spl/token.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ typedef struct Token_COption_Pubkey {

/**
* Instructions supported by the token program.
* List is compliant with the program source code at:
* https://github.com/solana-labs/solana-program-library/blob/master/token/program-2022/src/instruction.rs
*/
typedef enum Token_TokenInstruction_Tag {
/**
Expand Down Expand Up @@ -439,6 +441,85 @@ typedef enum Token_TokenInstruction_Tag {
* 0. `[writable]` The native token account to sync with its underlying lamports.
*/
Token_TokenInstruction_SyncNative,

/**
* The common instruction prefix for Transfer Fee extension instructions.
* See `extension::transfer_fee::instruction::TransferFeeInstruction` for
* further details about the extended instructions that share this
* instruction prefix
*/
Token_TokenExtensionInstruction_TransferFeeExtension = 26,

/**
* The common instruction prefix for Confidential Transfer extension instructions.
* See `extension::confidential_transfer::instruction::ConfidentialTransferInstruction` for
* further details about the extended instructions that share this
* instruction prefix
*/
Token_TokenExtensionInstruction_ConfidentialTransferExtension = 27,

/**
* The common instruction prefix for Default Account State extension instructions.
* See `extension::default_account_state::instruction::DefaultAccountStateInstruction` for
* further details about the extended instructions that share this instruction prefix
*/
Token_TokenExtensionInstruction_DefaultAccountStateExtension = 28,

/**
* The common instruction prefix for Memo Transfer account extension instructions.
* See `extension::memo_transfer::instruction::RequiredMemoTransfersInstruction` for
* further details about the extended instructions that share this instruction prefix
*/
Token_TokenExtensionInstruction_MemoTransferExtension = 30,

/**
* The common instruction prefix for Interest Bearing extension instructions.
* See `extension::interest_bearing_mint::instruction::InterestBearingMintInstruction` for
* further details about the extended instructions that share this instruction prefix
*/
Token_TokenExtensionInstruction_InterestBearingMintExtension = 33,

/**
* The common instruction prefix for CPI Guard account extension instructions.
* See `extension::cpi_guard::instruction::CpiGuardInstruction` for
* further details about the extended instructions that share this instruction prefix
*/
Token_TokenExtensionInstruction_CpiGuardExtension = 34,

/**
* The common instruction prefix for transfer hook extension instructions.
* See `extension::transfer_hook::instruction::TransferHookInstruction`
* for further details about the extended instructions that share this instruction prefix
*/
Token_TokenExtensionInstruction_TransferHookExtension = 36,

/**
* The common instruction prefix for the confidential transfer fee extension instructions.
* See `extension::confidential_transfer_fee::instruction::ConfidentialTransferFeeInstruction`
* for further details about the extended instructions that share this instruction prefix
*/
Token_TokenExtensionInstruction_ConfidentialTransferFeeExtension = 37,

/**
* The common instruction prefix for metadata pointer extension instructions.
* See `extension::metadata_pointer::instruction::MetadataPointerInstruction`
* for further details about the extended instructions that share this instruction prefix
*/
Token_TokenExtensionInstruction_MetadataPointerExtension = 39,

/**
* The common instruction prefix for group pointer extension instructions.
* See `extension::group_pointer::instruction::GroupPointerInstruction`
* for further details about the extended instructions that share this instruction prefix
*/
Token_TokenExtensionInstruction_GroupPointerExtension = 40,

/**
* The common instruction prefix for group member pointer extension instructions.
* See `extension::group_member_pointer::instruction::GroupMemberPointerInstruction`
* for further details about the extended instructions that share this instruction prefix
*/
Token_TokenExtensionInstruction_GroupMemberPointerExtension = 41
} Token_TokenInstruction_Tag;

typedef struct Token_TokenInstruction_Token_InitializeMint_Body {
Expand Down
3 changes: 3 additions & 0 deletions libsol/instruction.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "serum_assert_owner_instruction.h"
#include "spl_memo_instruction.h"
#include "spl_token_instruction.h"
#include "spl_token2022_instruction.h"
#include "compute_budget_instruction.h"
#include "stake_instruction.h"
#include "system_instruction.h"
Expand All @@ -18,6 +19,8 @@ enum ProgramId instruction_program_id(const Instruction* instruction, const Mess
return ProgramIdVote;
} else if (memcmp(program_id, &spl_token_program_id, PUBKEY_SIZE) == 0) {
return ProgramIdSplToken;
} else if(memcmp(program_id, &spl_token2022_program_id, PUBKEY_SIZE) == 0) {
return ProgramIdSplToken;//Treat the Token2022 exactly the same as the SplToken

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the decoding resulting from this used in the user signing loop?
If so this may cause issues, as the user may be unaware that he is signing transactions using the token22 program that may contain transfer fee or transfer hooks with unintended consequences

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main reason behind this is the code thats prints transaction details. If I detect token2022 as something different then it gets more messy to handle basic token instruction like mint, burn etc. At this point, if we use for example TransferFeeExtension we just ignore it - we don't display any additional screens.

What we can do is to throw error(instruction not supported) when we get any extension instruction. This way there will be no chance to sign something that you are not able to verify visually on the screen. Otherwise we would need to redesign this function https://github.com/blockydevs/app-solana/blob/develop/libsol/transaction_printers.c#L535 https://github.com/blockydevs/app-solana/blob/develop/libsol/transaction_printers.c#L535 because it is not able to handle anything with more than 3 instructions and we dont know what extensions someone will send and in what order

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, like main idea in this PR is to allow transactions that use the token2022 program id to be able to perform basic instructions that are also compatible with the old token

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, like main idea in this PR is to allow transactions that use the token2022 program id to be able to perform basic instructions that are also compatible with the old token

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a slight mix up between extension setup and the extension usage.

The TransferFeeExtension instruction is only called to setup the extension, so not supporting it here makes total sense. However, if its setup, any calls to Transfer are going to need extra accounts to correctly execute the transfer with the fee.

The way the decoding is currently setup, any Token22 mint with extensions such as Transfer Fees or Transfer Hooks enabled, are going to execute completely blind to the decoding operation due to the Transfer instruction being ABI compatible between both programs (extra accounts needed by extensions are appended at the end, everything else maintains the same binary structure).

At the very least, during decoding, the user should be aware that its signing a Transfer for a mint with such extensions enabled (fees can go all the way up to 100% and can change over time, on the other end of the spectrum transfer hooks can enable bad actors to effectively wipe accounts).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This simple approach seems reasonable at that stage of token2022 support.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its much better.

However, if I understood this correctly, the warning message only pops up if there are Extensions related instructions on the TX right? For that scenario its the right behaviour.

However, if there is only a TransferChecked instruction in the transaction and it is of a token mint with possible damaging extensions enabled (high transfer fees, draining transfer hook), the user will think he's signing a token transfer from the old Token Program without any side effects as none of that is present in the transaction.

IMO there needs to be another warning on any T22 TransferChecked like:
"Token Extensions" "Cannot verify mint extensions"
"Verify mint issuer before signing"
(not sure of character limits, but something along these lines)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, any extension thats coming from T22 generates this warning, the idea behind this PR was to allow users to use the old backward compatible features.
Due to the current code design we are a little bit limited in terms of detecting any complex cases with different instructions. Transaction printers contains hardcoded switch case that does not allow a lot of flexibility unfortunately.

Can you specify what exact cases should trigger that additional warning? Is it for transactions that only contains single TransferChecked instruction (https://solana-labs.github.io/solana-program-library/token/js/functions/createTransferCheckedInstruction.html) or are they any other cases?

And there is also another issue. On the older nanos devices we are really close to the memory limit, so adding long strings in to the binary may cause problems too, some of them appear only in specific features so maybe we just reuse the same warning text?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The additional warning should be triggered on TransferChecked when programId == token22, regardless of how many instructions are present in the transaction.

I'd say you can recycle the title part of the warnings.

Title: "Extension warning"
Messages: "Can't check mint extensions" -> "Check explorer"

Is the memory available enough for those? If not we can rework these warnings to further reuse strings.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tiago18c ok, I've added additional warning screens and i've also merged latest develop changes to verify that everything is still working as planned.
I did few tests on the nanos model and it looks that we are still within memory limits

} else if (memcmp(program_id, &spl_associated_token_account_program_id, PUBKEY_SIZE) == 0) {
return ProgramIdSplAssociatedTokenAccount;
} else if (is_serum_assert_owner_program_id(program_id)) {
Expand Down
19 changes: 18 additions & 1 deletion libsol/message.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ int process_message_body(const uint8_t* message_body,
InstructionInfo instruction_info[MAX_INSTRUCTIONS];
explicit_bzero(instruction_info, sizeof(InstructionInfo) * MAX_INSTRUCTIONS);

//Track if given transaction contains token2022 extensions that are not fully supported
//Needed to display user proper warning
SplTokenExtensionsMetadata token_extensions_metadata = {false};

size_t display_instruction_count = 0;
InstructionInfo* display_instruction_info[MAX_INSTRUCTIONS];

Expand All @@ -36,6 +40,8 @@ int process_message_body(const uint8_t* message_body,
BAIL_IF(instruction_validate(&instruction, header));

InstructionInfo* info = &instruction_info[instruction_count];
bool ignore_instruction_info = false;

enum ProgramId program_id = instruction_program_id(&instruction, header);
switch (program_id) {
case ProgramIdSerumAssertOwner: {
Expand All @@ -58,7 +64,12 @@ int process_message_body(const uint8_t* message_body,
break;
}
case ProgramIdSplToken:
if (parse_spl_token_instructions(&instruction, header, &info->spl_token) == 0) {
if (parse_spl_token_instructions(&instruction,
header,
&info->spl_token,
&token_extensions_metadata,
&ignore_instruction_info) == 0) {
info->spl_token.is_token2022_kind = is_token2022_instruction(&instruction, header);
info->kind = program_id;
}
break;
Expand Down Expand Up @@ -89,6 +100,9 @@ int process_message_body(const uint8_t* message_body,
case ProgramIdUnknown:
break;
}
if(ignore_instruction_info){
continue;
}
switch (info->kind) {
case ProgramIdSplAssociatedTokenAccount:
case ProgramIdSplToken:
Expand Down Expand Up @@ -120,5 +134,8 @@ int process_message_body(const uint8_t* message_body,
BAIL_IF(instruction_info[i].kind == ProgramIdUnknown);
}

if (token_extensions_metadata.generate_extension_warning) {
BAIL_IF(print_spl_token_extension_warning());
}
return print_transaction(print_config, display_instruction_info, display_instruction_count);
}
5 changes: 5 additions & 0 deletions libsol/spl_token2022_instruction.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#include "sol/print_config.h"
#include "common_byte_strings.h"


const Pubkey spl_token2022_program_id = {{PROGRAM_ID_SPL_TOKEN_2022}};
12 changes: 12 additions & 0 deletions libsol/spl_token2022_instruction.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#pragma once

#include "sol/parser.h"
#include "sol/print_config.h"
#include "sol/transaction_summary.h"
#include "spl/token.h"
#include "instruction.h"

#define SplTokenExtensionKind(b) Token_TokenExtensionInstruction_##b

extern const Pubkey spl_token2022_program_id;

Loading
Loading