From 52e2ba8375ed7176c7f7afa36dbabddfc85b3b4d Mon Sep 17 00:00:00 2001 From: RostarMarek Date: Tue, 7 May 2024 14:00:09 +0200 Subject: [PATCH] feat(solana): add nullable tag --- common/defs/solana/programs.json | 3 +- .../solana/sign_tx.token_program.json | 42 +++++++++ core/src/apps/solana/layout.py | 13 ++- .../apps/solana/transaction/instruction.py | 14 +++ .../apps/solana/transaction/instructions.py | 85 +++++++++++++++++++ .../solana/transaction/instructions.py.mako | 5 ++ core/src/apps/solana/types.py | 2 + .../solana/construct/instructions.py.mako | 2 +- 8 files changed, 162 insertions(+), 4 deletions(-) diff --git a/common/defs/solana/programs.json b/common/defs/solana/programs.json index 7f176edc54a..84d540864ff 100644 --- a/common/defs/solana/programs.json +++ b/common/defs/solana/programs.json @@ -1573,7 +1573,8 @@ { "name": "new_authority", "type": "authority", - "optional": true + "optional": false, + "nullable": true } ], "references": [ diff --git a/common/tests/fixtures/solana/sign_tx.token_program.json b/common/tests/fixtures/solana/sign_tx.token_program.json index c187dc583ed..3e5ac433190 100644 --- a/common/tests/fixtures/solana/sign_tx.token_program.json +++ b/common/tests/fixtures/solana/sign_tx.token_program.json @@ -281,6 +281,48 @@ "expected_signature": "7a96fb6cc283a10e26c9d330e8f2b2892d23698e008c959c29eb124a9c835fc0125865af4ee2ee267fe5ed03e51029f4244b05a592fd5c7969f5a9890ceef902" } }, + { + "description": "Set Authority - null authority", + "parameters": { + "address": "m/44'/501'/0'/0'", + "construct": { + "version": null, + "header": { + "signers": 1, + "readonly_signers": 0, + "readonly_non_signers": 1 + }, + "accounts": [ + "14CCvQzQzHCVgZM3j9soPnXuJXh1RmCfwLVUcdfbZVBS", + "FUqrjRRtF1LiptdFqaFxipE8R3YfCE4k56xwm5n1piqX", + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + ], + "blockhash": "2p4rYZAaFfV5Uk5ugdG5KPNty9Uda9B3b4gWB8qnNqak", + "instructions": [ + { + "program_index": 2, + "accounts": { + "mint_account": 1, + "current_authority": 0, + "multisig_signers": [] + }, + "data": { + "instruction_id": 6, + "authority_type": 1, + "new_authority": { + "is_included": false, + "value": null + } + } + } + ], + "luts": [] + } + }, + "result": { + "expected_signature": "f983c4f3ac794bf02e67d3320c91e515c3de649b377c61c25546699fe6f551ffc8c022853823eb81cf785e71a5bbb78f5cbcfd49eb38480629bdc906c5c50202" + } + }, { "description": "Set Authority - Multisig", "parameters": { diff --git a/core/src/apps/solana/layout.py b/core/src/apps/solana/layout.py index 81aac811a45..0159bb498bd 100644 --- a/core/src/apps/solana/layout.py +++ b/core/src/apps/solana/layout.py @@ -80,15 +80,24 @@ async def confirm_instruction( continue if ui_property.default_value_to_hide == value: - continue + # currently we can define a default value to hide, + # but it needs to be of the type that the property is of (such as public key for new_authority) + # and for nullable values(such as new_authority) we want to display it to the user + if not property_template.is_nullable or value is not None: + continue + display_text = ( + "Not set" + if value is None + else property_template.format(instruction, value) + ) await confirm_properties( "confirm_instruction", f"{instruction_index}/{instructions_count}: {instruction.ui_name}", ( ( ui_property.display_name, - property_template.format(instruction, value), + display_text, ), ), ) diff --git a/core/src/apps/solana/transaction/instruction.py b/core/src/apps/solana/transaction/instruction.py index c108c35fbb7..e56279d8488 100644 --- a/core/src/apps/solana/transaction/instruction.py +++ b/core/src/apps/solana/transaction/instruction.py @@ -50,9 +50,23 @@ def parse_instruction_data( parsed_data = {} for property_template in property_templates: is_included = True + # Property cannot be both optional and nullable as if there are multiple such properties in 1 instruction + # it could lead to non-deterministic parsing + # it is validated by mako template + assert not (property_template.is_nullable and property_template.is_optional) + if property_template.is_optional: is_included = True if reader.get() == 1 else False + if property_template.is_nullable: + is_included = True if reader.get() == 1 else False + if not is_included: + # A default (null) value is included in the parsed message for nullable properties + # (e.g. new_authority in Set Authority instruction) even if the is_included flag is not set. + # We can safely discard this value. + if reader.remaining_count() != 0: + property_template.parse(reader) + parsed_data[property_template.name] = ( property_template.parse(reader) if is_included else None ) diff --git a/core/src/apps/solana/transaction/instructions.py b/core/src/apps/solana/transaction/instructions.py index 83532494243..cf01b1d4b2d 100644 --- a/core/src/apps/solana/transaction/instructions.py +++ b/core/src/apps/solana/transaction/instructions.py @@ -833,6 +833,7 @@ def get_instruction( "lamports", False, False, + False, read_uint64_le, format_lamports, ), @@ -840,6 +841,7 @@ def get_instruction( "space", False, False, + False, read_uint64_le, format_int, ), @@ -847,6 +849,7 @@ def get_instruction( "owner", True, False, + False, parse_pubkey, format_pubkey, ), @@ -904,6 +907,7 @@ def get_instruction( "owner", True, False, + False, parse_pubkey, format_pubkey, ), @@ -949,6 +953,7 @@ def get_instruction( "lamports", False, False, + False, read_uint64_le, format_lamports, ), @@ -1006,6 +1011,7 @@ def get_instruction( "base", False, False, + False, parse_pubkey, format_pubkey, ), @@ -1013,6 +1019,7 @@ def get_instruction( "seed", False, False, + False, parse_string, format_identity, ), @@ -1020,6 +1027,7 @@ def get_instruction( "lamports", False, False, + False, read_uint64_le, format_lamports, ), @@ -1027,6 +1035,7 @@ def get_instruction( "space", False, False, + False, read_uint64_le, format_int, ), @@ -1034,6 +1043,7 @@ def get_instruction( "owner", False, False, + False, parse_pubkey, format_pubkey, ), @@ -1143,6 +1153,7 @@ def get_instruction( "lamports", False, False, + False, read_uint64_le, format_lamports, ), @@ -1222,6 +1233,7 @@ def get_instruction( "nonce_authority", True, False, + False, parse_pubkey, format_pubkey, ), @@ -1277,6 +1289,7 @@ def get_instruction( "nonce_authority", True, False, + False, parse_pubkey, format_pubkey, ), @@ -1334,6 +1347,7 @@ def get_instruction( "space", False, False, + False, read_uint64_le, format_int, ), @@ -1379,6 +1393,7 @@ def get_instruction( "base", False, False, + False, parse_pubkey, format_pubkey, ), @@ -1386,6 +1401,7 @@ def get_instruction( "seed", False, False, + False, parse_string, format_identity, ), @@ -1393,6 +1409,7 @@ def get_instruction( "space", False, False, + False, read_uint64_le, format_int, ), @@ -1400,6 +1417,7 @@ def get_instruction( "owner", False, False, + False, parse_pubkey, format_pubkey, ), @@ -1450,6 +1468,7 @@ def get_instruction( "base", False, False, + False, parse_pubkey, format_pubkey, ), @@ -1457,6 +1476,7 @@ def get_instruction( "seed", False, False, + False, parse_string, format_identity, ), @@ -1464,6 +1484,7 @@ def get_instruction( "owner", False, False, + False, parse_pubkey, format_pubkey, ), @@ -1514,6 +1535,7 @@ def get_instruction( "lamports", False, False, + False, read_uint64_le, format_lamports, ), @@ -1521,6 +1543,7 @@ def get_instruction( "from_seed", False, False, + False, parse_string, format_identity, ), @@ -1528,6 +1551,7 @@ def get_instruction( "from_owner", False, False, + False, parse_pubkey, format_pubkey, ), @@ -1635,6 +1659,7 @@ def get_instruction( "staker", True, False, + False, parse_pubkey, format_pubkey, ), @@ -1642,6 +1667,7 @@ def get_instruction( "withdrawer", True, False, + False, parse_pubkey, format_pubkey, ), @@ -1649,6 +1675,7 @@ def get_instruction( "unix_timestamp", False, False, + False, read_uint64_le, format_unix_timestamp, ), @@ -1656,6 +1683,7 @@ def get_instruction( "epoch", False, False, + False, read_uint64_le, format_int, ), @@ -1663,6 +1691,7 @@ def get_instruction( "custodian", True, False, + False, parse_pubkey, format_pubkey, ), @@ -1741,6 +1770,7 @@ def get_instruction( "pubkey", False, False, + False, parse_pubkey, format_pubkey, ), @@ -1748,6 +1778,7 @@ def get_instruction( "stake_authorize", False, False, + False, read_uint32_le, format_StakeAuthorize, ), @@ -1898,6 +1929,7 @@ def get_instruction( "lamports", False, False, + False, read_uint64_le, format_lamports, ), @@ -1967,6 +1999,7 @@ def get_instruction( "lamports", False, False, + False, read_uint64_le, format_lamports, ), @@ -2098,6 +2131,7 @@ def get_instruction( "unix_timestamp", False, True, + False, read_uint64_le, format_unix_timestamp, ), @@ -2105,6 +2139,7 @@ def get_instruction( "epoch", False, True, + False, read_uint64_le, format_int, ), @@ -2112,6 +2147,7 @@ def get_instruction( "custodian", False, True, + False, parse_pubkey, format_pubkey, ), @@ -2247,6 +2283,7 @@ def get_instruction( "new_authorized_pubkey", False, False, + False, parse_pubkey, format_pubkey, ), @@ -2254,6 +2291,7 @@ def get_instruction( "stake_authorize", False, False, + False, read_uint32_le, format_StakeAuthorize, ), @@ -2261,6 +2299,7 @@ def get_instruction( "authority_seed", False, False, + False, parse_string, format_identity, ), @@ -2268,6 +2307,7 @@ def get_instruction( "authority_owner", False, False, + False, parse_pubkey, format_pubkey, ), @@ -2408,6 +2448,7 @@ def get_instruction( "stake_authorize", False, False, + False, read_uint32_le, format_StakeAuthorize, ), @@ -2494,6 +2535,7 @@ def get_instruction( "stake_authorize", False, False, + False, read_uint32_le, format_StakeAuthorize, ), @@ -2501,6 +2543,7 @@ def get_instruction( "authority_seed", False, False, + False, parse_string, format_identity, ), @@ -2508,6 +2551,7 @@ def get_instruction( "authority_owner", False, False, + False, parse_pubkey, format_pubkey, ), @@ -2594,6 +2638,7 @@ def get_instruction( "unix_timestamp", False, True, + False, read_uint64_le, format_unix_timestamp, ), @@ -2601,6 +2646,7 @@ def get_instruction( "epoch", False, True, + False, read_uint64_le, format_int, ), @@ -2692,6 +2738,7 @@ def get_instruction( "bytes", False, False, + False, read_uint32_le, format_int, ), @@ -2724,6 +2771,7 @@ def get_instruction( "units", False, False, + False, read_uint32_le, format_int, ), @@ -2756,6 +2804,7 @@ def get_instruction( "lamports", False, False, + False, read_uint64_le, format_int, ), @@ -2862,6 +2911,7 @@ def get_instruction( "number_of_signers", False, False, + False, parse_byte, format_int, ), @@ -2917,6 +2967,7 @@ def get_instruction( "amount", False, False, + False, read_uint64_le, format_int, ), @@ -2986,6 +3037,7 @@ def get_instruction( "amount", False, False, + False, read_uint64_le, format_int, ), @@ -3090,12 +3142,14 @@ def get_instruction( "authority_type", False, False, + False, parse_byte, format_AuthorityType, ), PropertyTemplate( "new_authority", True, + False, True, parse_pubkey, format_pubkey, @@ -3161,6 +3215,7 @@ def get_instruction( "amount", False, False, + False, read_uint64_le, format_int, ), @@ -3223,6 +3278,7 @@ def get_instruction( "amount", False, False, + False, read_uint64_le, format_int, ), @@ -3433,6 +3489,7 @@ def get_instruction( "amount", False, False, + False, read_uint64_le, format_token_amount, ), @@ -3440,6 +3497,7 @@ def get_instruction( "decimals", False, False, + False, parse_byte, format_int, ), @@ -3521,6 +3579,7 @@ def get_instruction( "amount", False, False, + False, read_uint64_le, format_token_amount, ), @@ -3528,6 +3587,7 @@ def get_instruction( "decimals", False, False, + False, parse_byte, format_int, ), @@ -3609,6 +3669,7 @@ def get_instruction( "amount", False, False, + False, read_uint64_le, format_token_amount, ), @@ -3616,6 +3677,7 @@ def get_instruction( "decimals", False, False, + False, parse_byte, format_int, ), @@ -3685,6 +3747,7 @@ def get_instruction( "amount", False, False, + False, read_uint64_le, format_token_amount, ), @@ -3692,6 +3755,7 @@ def get_instruction( "decimals", False, False, + False, parse_byte, format_int, ), @@ -3761,6 +3825,7 @@ def get_instruction( "owner", False, False, + False, parse_pubkey, format_pubkey, ), @@ -3853,6 +3918,7 @@ def get_instruction( "owner", False, False, + False, parse_pubkey, format_pubkey, ), @@ -4014,6 +4080,7 @@ def get_instruction( "number_of_signers", False, False, + False, parse_byte, format_int, ), @@ -4069,6 +4136,7 @@ def get_instruction( "amount", False, False, + False, read_uint64_le, format_int, ), @@ -4138,6 +4206,7 @@ def get_instruction( "amount", False, False, + False, read_uint64_le, format_int, ), @@ -4242,6 +4311,7 @@ def get_instruction( "authority_type", False, False, + False, parse_byte, format_AuthorityType, ), @@ -4249,6 +4319,7 @@ def get_instruction( "new_authority", True, True, + False, parse_pubkey, format_pubkey, ), @@ -4313,6 +4384,7 @@ def get_instruction( "amount", False, False, + False, read_uint64_le, format_int, ), @@ -4375,6 +4447,7 @@ def get_instruction( "amount", False, False, + False, read_uint64_le, format_int, ), @@ -4585,6 +4658,7 @@ def get_instruction( "amount", False, False, + False, read_uint64_le, format_token_amount, ), @@ -4592,6 +4666,7 @@ def get_instruction( "decimals", False, False, + False, parse_byte, format_int, ), @@ -4673,6 +4748,7 @@ def get_instruction( "amount", False, False, + False, read_uint64_le, format_token_amount, ), @@ -4680,6 +4756,7 @@ def get_instruction( "decimals", False, False, + False, parse_byte, format_int, ), @@ -4761,6 +4838,7 @@ def get_instruction( "amount", False, False, + False, read_uint64_le, format_token_amount, ), @@ -4768,6 +4846,7 @@ def get_instruction( "decimals", False, False, + False, parse_byte, format_int, ), @@ -4837,6 +4916,7 @@ def get_instruction( "amount", False, False, + False, read_uint64_le, format_token_amount, ), @@ -4844,6 +4924,7 @@ def get_instruction( "decimals", False, False, + False, parse_byte, format_int, ), @@ -4913,6 +4994,7 @@ def get_instruction( "owner", False, False, + False, parse_pubkey, format_pubkey, ), @@ -5005,6 +5087,7 @@ def get_instruction( "owner", False, False, + False, parse_pubkey, format_pubkey, ), @@ -5348,6 +5431,7 @@ def get_instruction( "memo", False, False, + False, parse_memo, format_identity, ), @@ -5408,6 +5492,7 @@ def get_instruction( "memo", False, False, + False, parse_memo, format_identity, ), diff --git a/core/src/apps/solana/transaction/instructions.py.mako b/core/src/apps/solana/transaction/instructions.py.mako index 5877a20627a..dccd00ccbc3 100644 --- a/core/src/apps/solana/transaction/instructions.py.mako +++ b/core/src/apps/solana/transaction/instructions.py.mako @@ -157,10 +157,15 @@ def get_instruction( ${getInstructionIdText(program, instruction)}, [ % for parameter in instruction["parameters"]: + <% + if parameter["optional"] and parameter.get("nullable", False): + raise Exception(f"Parameter \"{parameter['name']}\" in instruction \"{instruction['name']}\" is both optional and nullable.") + %> PropertyTemplate( "${parameter["name"]}", ${parameter["type"] == "authority"}, ${parameter["optional"]}, + ${parameter.get("nullable", False)}, ${programs["types"][parameter["type"]]["parse"]}, ${programs["types"][parameter["type"]]["format"]}, ), diff --git a/core/src/apps/solana/types.py b/core/src/apps/solana/types.py index 2c630dbcf6d..eb86dfb44de 100644 --- a/core/src/apps/solana/types.py +++ b/core/src/apps/solana/types.py @@ -40,12 +40,14 @@ def __init__( name: str, is_authority: bool, is_optional: bool, + is_nullable: bool, parse: Callable[[BufferReader], T], format: Callable[[Instruction, T], str], ): self.name = name self.is_authority = is_authority self.is_optional = is_optional + self.is_nullable = is_nullable self.parse = parse self.format = format diff --git a/tests/device_tests/solana/construct/instructions.py.mako b/tests/device_tests/solana/construct/instructions.py.mako index e099b0707d8..d7e6acd7b38 100644 --- a/tests/device_tests/solana/construct/instructions.py.mako +++ b/tests/device_tests/solana/construct/instructions.py.mako @@ -90,7 +90,7 @@ ${camelcase(program.name)}_${camelcase(instruction.name)} = Struct( "data" / CompactStruct( "instruction_id" / ${instruction_subcon(program, instruction)}, % for parameter in instruction.parameters: - % if parameter["optional"]: + % if parameter["optional"] or parameter.get("nullable", False): "${parameter["name"]}" / OptionalParameter(${CONSTRUCT_TYPES.get(parameter.type)}), % else: "${parameter["name"]}" / ${CONSTRUCT_TYPES.get(parameter.type, "Int64ul")},