1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-05-29 12:18:51 +00:00

refactor(core/solana): restructure programs.json and related code

This commit is contained in:
matejcik 2025-04-25 14:05:29 +02:00 committed by matejcik
parent e3af93e89f
commit e4e6d60e64
13 changed files with 1359 additions and 3416 deletions

File diff suppressed because it is too large Load Diff

View File

@ -12,14 +12,14 @@ _This file is generated by `programs.md.mako` via `make solana_templates`, do no
| Deposit | `lamports` | `lamports` | | Deposit | `lamports` | `lamports` |
| From | `funding_account` | `account` | | From | `funding_account` | `account` |
| _(not shown)_ | `space` | `u64` | | _(not shown)_ | `space` | `u64` |
| _(not shown)_ | `owner` | `authority` | | _(not shown)_ | `owner` | `pubkey` |
### (1) Assign ### (1) Assign
| Label | Value | Type | | Label | Value | Type |
|-------|-------|------| |-------|-------|------|
| Assign account | `assigned_account` | `account` | | Assign account | `assigned_account` | `account` |
| Assign account to program | `owner` | `authority` | | Assign account to program | `owner` | `pubkey` |
### (2) Transfer ### (2) Transfer
@ -66,7 +66,7 @@ _This file is generated by `programs.md.mako` via `make solana_templates`, do no
| Label | Value | Type | | Label | Value | Type |
|-------|-------|------| |-------|-------|------|
| Initialize nonce account | `nonce_account` | `account` | | Initialize nonce account | `nonce_account` | `account` |
| New authority | `nonce_authority` | `authority` | | New authority | `nonce_authority` | `pubkey` |
| _(not shown)_ | `recent_blockhashes_sysvar` | `account` | | _(not shown)_ | `recent_blockhashes_sysvar` | `account` |
| _(not shown)_ | `rent_sysvar` | `account` | | _(not shown)_ | `rent_sysvar` | `account` |
@ -75,7 +75,7 @@ _This file is generated by `programs.md.mako` via `make solana_templates`, do no
| Label | Value | Type | | Label | Value | Type |
|-------|-------|------| |-------|-------|------|
| Set nonce authority | `nonce_account` | `account` | | Set nonce authority | `nonce_account` | `account` |
| New authority | `nonce_authority` | `authority` | | New authority | `nonce_authority` | `pubkey` |
| Authorized by | `nonce_authority` | `account` | | Authorized by | `nonce_authority` | `account` |
### (8) Allocate ### (8) Allocate
@ -132,11 +132,11 @@ _This file is generated by `programs.md.mako` via `make solana_templates`, do no
| Label | Value | Type | | Label | Value | Type |
|-------|-------|------| |-------|-------|------|
| Initialize stake account | `uninitialized_stake_account` | `account` | | Initialize stake account | `uninitialized_stake_account` | `account` |
| New stake authority | `staker` | `authority` | | New stake authority | `staker` | `pubkey` |
| New withdraw authority | `withdrawer` | `authority` | | New withdraw authority | `withdrawer` | `pubkey` |
| Lockup time | `unix_timestamp` | `unix_timestamp` | | Lockup time | `unix_timestamp` | `unix_timestamp` |
| Lockup epoch | `epoch` | `u64` | | Lockup epoch | `epoch` | `u64` |
| Lockup authority | `custodian` | `authority` | | Lockup authority | `custodian` | `pubkey` |
| _(not shown)_ | `rent_sysvar` | `account` | | _(not shown)_ | `rent_sysvar` | `account` |
### (1) Authorize ### (1) Authorize
@ -340,7 +340,7 @@ _This file is generated by `programs.md.mako` via `make solana_templates`, do no
| Label | Value | Type | | Label | Value | Type |
|-------|-------|------| |-------|-------|------|
| Set authority for | `mint_account` | `account` | | Set authority for | `mint_account` | `account` |
| New authority | `new_authority` | `authority` | | New authority | `new_authority` | `pubkey` |
| Authority type | `authority_type` | `AuthorityType` | | Authority type | `authority_type` | `AuthorityType` |
| Current authority | `current_authority` | `account` | | Current authority | `current_authority` | `account` |
@ -509,7 +509,7 @@ _This file is generated by `programs.md.mako` via `make solana_templates`, do no
| Label | Value | Type | | Label | Value | Type |
|-------|-------|------| |-------|-------|------|
| Set authority for | `mint_account` | `account` | | Set authority for | `mint_account` | `account` |
| New authority | `new_authority` | `authority` | | New authority | `new_authority` | `pubkey` |
| Authority type | `authority_type` | `AuthorityType` | | Authority type | `authority_type` | `AuthorityType` |
| Current authority | `current_authority` | `account` | | Current authority | `current_authority` | `account` |

View File

@ -9,7 +9,7 @@ ${'##'} ${program.name}
${'###'} (${instruction.id}) ${instruction.name} ${'###'} (${instruction.id}) ${instruction.name}
<% <%
all_params = { param.name: param for param in instruction.parameters } all_params = { param.name: param for param in instruction.parameters }
all_accounts = [ref.name for ref in instruction.references] all_accounts = list(instruction.references)
%> %>
| Label | Value | Type | | Label | Value | Type |
|-------|-------|------| |-------|-------|------|

View File

@ -168,14 +168,22 @@ The `programs.json` file serves as a structured configuration file in the Solana
- `name`: The parameter name. - `name`: The parameter name.
- `type`: The data type of the parameter, such as `u64` for 64-bit unsigned integers. - `type`: The data type of the parameter, such as `u64` for 64-bit unsigned integers.
- `optional`: Indicates whether the parameter is optional. - `optional`: Indicates whether the parameter is optional.
- `references`: Defines the references to accounts that this instruction requires. - `args`: An optional dict of arguments for the formatter, see explanation below.
- `name`: The account name. - `references`: An array of account names that are used by the instruction.
- `is_authority`: A boolean specifying whether the account is considered an authority for this instruction. - `references_required`: The number of references required by the instruction. If more `references` are specified than required, the extra ones are optional, and may or may not be present in the transaction.
- `optional`: Indicates whether the account is optional.
- `ui_properties`: Contains user interface-related information for this instruction. - `ui_properties`: Contains user interface-related information for this instruction.
- `account`: Reference to one account in the references list identified by its `name` - `account`: Reference to one account in the references list identified by its `name`
- `parameter`: Reference to one parameter in the parameters list identified by its `name` - `parameter`: Reference to one parameter in the parameters list identified by its `name`
- `display_name`: A human-readable label for the parameter or account, suitable for user interfaces. - `display_name`: A human-readable label for the parameter or account, suitable for user interfaces.
- `default_value_to_hide`: Optional. If this value is found in the account / parameter, the UI property will not be shown for confirmation. This is useful when the default value is considered safe. In particular, if the value of the property is a public key, and the special word `"signer"` is used for `default_value_to_hide`, the UI property will be hidden if the public key matches the Trezor's account.
Certain types of parameters, specified in `types` dict of the `programs.json` file, have special formatting capabilities.
In particular, the type `token_amount` is a regular `u64` type, but the formatter function accepts additional parameters:
* a special parameter `#definitions` that will be pre-set to the loadable definitions manager
* a parameter `decimals` that specifies the number of decimals of the token
* a parameter `mint` that specifies the mint address of the token
The corresponding parameter of `token_amount` type must provide the `args` dict, mapping the `decimals` and `mint` arguments to fields of the instruction. E.g.: in a hypothetical Swap instruction, you would have two parameters of `token_amount` type. On the first one, the `args` dict would map `decimals` to the `from_amount_decimals` field and `mint` to the `from_amount_mint` field. On the second one, the mapping would go to the `to_amount_decimals` and `to_amount_mint` fields.
After the message has been parsed, the Solana app utilizes the Trezor UI engine to present all the necessary information to the user for review and confirmation. If all the programs and instructions contained within the message are recognized and known, the software ensures that all the relevant information is displayed to the user. Each piece of data, including parameters, account references, and instruction details, is presented on the Trezor's user interface for user confirmation. After the message has been parsed, the Solana app utilizes the Trezor UI engine to present all the necessary information to the user for review and confirmation. If all the programs and instructions contained within the message are recognized and known, the software ensures that all the relevant information is displayed to the user. Each piece of data, including parameters, account references, and instruction details, is presented on the Trezor's user interface for user confirmation.

View File

@ -3,10 +3,10 @@ from typing import TYPE_CHECKING
from trezor.strings import format_amount, format_timestamp from trezor.strings import format_amount, format_timestamp
if TYPE_CHECKING: if TYPE_CHECKING:
from .transaction.instructions import Instruction from .definitions import Definitions
def format_pubkey(_: Instruction, value: bytes | None) -> str: def format_pubkey(value: bytes | None) -> str:
from trezor.crypto import base58 from trezor.crypto import base58
if value is None: if value is None:
@ -15,25 +15,31 @@ def format_pubkey(_: Instruction, value: bytes | None) -> str:
return base58.encode(value) return base58.encode(value)
def format_lamports(_: Instruction, value: int) -> str: def format_lamports(value: int) -> str:
formatted = format_amount(value, decimals=9) formatted = format_amount(value, decimals=9)
return f"{formatted} SOL" return f"{formatted} SOL"
def format_token_amount(instruction: Instruction, value: int) -> str: def format_token_amount(
assert hasattr(instruction, "decimals") # enforced in instructions.py.mako value: int, definitions: Definitions, decimals: int, mint: bytes
) -> str:
formatted = format_amount(value, decimals=decimals)
token = definitions.get_token(mint)
if token:
symbol = token.symbol
else:
symbol = "[UNKN]"
formatted = format_amount(value, decimals=instruction.decimals) return f"{formatted} {symbol}"
return f"{formatted}"
def format_unix_timestamp(_: Instruction, value: int) -> str: def format_unix_timestamp(value: int) -> str:
return format_timestamp(value) return format_timestamp(value)
def format_int(_: Instruction, value: int) -> str: def format_int(value: int) -> str:
return str(value) return str(value)
def format_identity(_: Instruction, value: str) -> str: def format_identity(value: str) -> str:
return value return value

View File

@ -84,45 +84,57 @@ async def confirm_instruction(
property_template = instruction.get_property_template(ui_property.parameter) property_template = instruction.get_property_template(ui_property.parameter)
value = instruction.parsed_data[ui_property.parameter] value = instruction.parsed_data[ui_property.parameter]
if property_template.is_authority and signer_public_key == value: if property_template.optional and value is None:
continue
if property_template.is_optional and value is None:
continue continue
if ui_property.default_value_to_hide == value: if ui_property.default_value_to_hide == value:
continue continue
if (
property_template.is_pubkey()
and ui_property.default_value_to_hide == "signer"
and signer_public_key == value
):
continue
args = []
for arg in property_template.args:
if arg == "#definitions":
args.append(definitions)
elif arg in instruction.parsed_data:
args.append(instruction.parsed_data[arg])
elif arg in instruction.parsed_accounts:
args.append(instruction.parsed_accounts[arg][0])
else:
raise ValueError # Invalid property template
await confirm_properties( await confirm_properties(
"confirm_instruction", "confirm_instruction",
f"{instruction_index}/{instructions_count}: {instruction.ui_name}", f"{instruction_index}/{instructions_count}: {instruction.ui_name}",
( (
( (
ui_property.display_name, ui_property.display_name,
property_template.format(instruction, value), property_template.format(value, *args),
), ),
), ),
) )
elif ui_property.account is not None: elif ui_property.account is not None:
account_template = instruction.get_account_template(ui_property.account)
# optional account, skip if not present # optional account, skip if not present
if ui_property.account not in instruction.parsed_accounts: if ui_property.account not in instruction.parsed_accounts:
continue continue
account_value = instruction.parsed_accounts[ui_property.account] account_value = instruction.parsed_accounts[ui_property.account]
if account_template.is_authority: if ui_property.default_value_to_hide == "signer" and signer_public_key == account_value[0]:
if signer_public_key == account_value[0]: continue
continue
account_data: list[tuple[str, str]] = [] account_data: list[tuple[str, str]] = []
# account included in the transaction directly # account included in the transaction directly
if len(account_value) == 2: if len(account_value) == 2:
account_description = f"{base58.encode(account_value[0])}" account_description = f"{base58.encode(account_value[0])}"
if account_template.is_token_mint: token = definitions.get_token(account_value[0])
token = definitions.get_token(account_value[0]) if token is not None:
account_description = f"{token.symbol}\n{account_description}" account_description = f"{token.name}\n{account_description}"
elif account_value[0] == signer_public_key: elif account_value[0] == signer_public_key:
account_description = f"{account_description} ({TR.words__signer})" account_description = f"{account_description} ({TR.words__signer})"

View File

@ -5,25 +5,20 @@ if TYPE_CHECKING:
from typing_extensions import Self from typing_extensions import Self
from ..types import ( from ..types import Account, InstructionData, PropertyTemplate, UIProperty
Account,
AccountTemplate,
InstructionData,
PropertyTemplate,
UIProperty,
)
class Instruction: class Instruction:
program_id: str program_id: str
instruction_id: int | None instruction_id: int | None
property_templates: list[PropertyTemplate] property_templates: tuple[PropertyTemplate, ...]
accounts_template: list[AccountTemplate] accounts_required: int
account_templates: tuple[str, ...]
ui_name: str ui_name: str
ui_properties: list[UIProperty] ui_properties: tuple[UIProperty, ...]
parsed_data: dict[str, Any] parsed_data: dict[str, Any]
parsed_accounts: dict[str, Account] parsed_accounts: dict[str, Account]
@ -40,7 +35,8 @@ class Instruction:
@staticmethod @staticmethod
def parse_instruction_data( def parse_instruction_data(
instruction_data: InstructionData, property_templates: list[PropertyTemplate] instruction_data: InstructionData,
property_templates: tuple[PropertyTemplate, ...],
) -> dict[str, Any]: ) -> dict[str, Any]:
from trezor.utils import BufferReader from trezor.utils import BufferReader
from trezor.wire import DataError from trezor.wire import DataError
@ -50,7 +46,7 @@ class Instruction:
parsed_data = {} parsed_data = {}
for property_template in property_templates: for property_template in property_templates:
is_included = True is_included = True
if property_template.is_optional: if property_template.optional:
is_included = True if reader.get() == 1 else False is_included = True if reader.get() == 1 else False
parsed_data[property_template.name] = ( parsed_data[property_template.name] = (
@ -64,18 +60,19 @@ class Instruction:
@staticmethod @staticmethod
def parse_instruction_accounts( def parse_instruction_accounts(
accounts: list[Account], accounts_template: list[AccountTemplate] accounts: list[Account],
accounts_required: int,
account_templates: tuple[str, ...],
) -> dict[str, Account]: ) -> dict[str, Account]:
parsed_account = {} parsed_accounts = {}
for i, account_template in enumerate(accounts_template): if len(accounts) < accounts_required:
if i >= len(accounts): raise ValueError # "Account is missing
if account_template.optional:
continue
else:
raise ValueError # "Account is missing
parsed_account[account_template.name] = accounts[i] for i, account_name in enumerate(account_templates):
return parsed_account if i >= len(accounts):
break
parsed_accounts[account_name] = accounts[i]
return parsed_accounts
def __init__( def __init__(
self, self,
@ -83,9 +80,10 @@ class Instruction:
program_id: str, program_id: str,
accounts: list[Account], accounts: list[Account],
instruction_id: int | None, instruction_id: int | None,
property_templates: list[PropertyTemplate], property_templates: tuple[PropertyTemplate, ...],
accounts_template: list[AccountTemplate], accounts_required: int,
ui_properties: list[UIProperty], account_templates: tuple[str, ...],
ui_properties: tuple[UIProperty, ...],
ui_name: str, ui_name: str,
is_program_supported: bool = True, is_program_supported: bool = True,
is_instruction_supported: bool = True, is_instruction_supported: bool = True,
@ -97,7 +95,8 @@ class Instruction:
self.instruction_id = instruction_id self.instruction_id = instruction_id
self.property_templates = property_templates self.property_templates = property_templates
self.accounts_template = accounts_template self.accounts_required = accounts_required
self.account_templates = account_templates
self.ui_name = ui_name self.ui_name = ui_name
@ -118,10 +117,12 @@ class Instruction:
) )
self.parsed_accounts = self.parse_instruction_accounts( self.parsed_accounts = self.parse_instruction_accounts(
accounts, accounts_template accounts,
accounts_required,
account_templates,
) )
self.multisig_signers = accounts[len(accounts_template) :] self.multisig_signers = accounts[len(account_templates) :]
if self.multisig_signers and not supports_multisig: if self.multisig_signers and not supports_multisig:
raise ValueError # Multisig not supported raise ValueError # Multisig not supported
else: else:
@ -144,13 +145,6 @@ class Instruction:
raise ValueError # Property not found raise ValueError # Property not found
def get_account_template(self, account_name: str) -> AccountTemplate:
for account_template in self.accounts_template:
if account_template.name == account_name:
return account_template
raise ValueError # Account not found
@classmethod @classmethod
def is_type_of(cls, ins: Any) -> TypeGuard[Self]: def is_type_of(cls, ins: Any) -> TypeGuard[Self]:
# gets overridden in `instructions.py` `FakeClass` # gets overridden in `instructions.py` `FakeClass`

File diff suppressed because it is too large Load Diff

View File

@ -1,32 +1,41 @@
# generated from instructions.py.mako # generated from instructions.py.mako
# (by running `make solana_templates` in root)
# do not edit manually! # do not edit manually!
<%def name="getProgramId(program)">${"_" + "_".join(program["name"].upper().split(" ") + ["ID"])}</%def>\
<%def name="getInstructionIdText(program, instruction)">${"_".join([getProgramId(program)] + ["INS"] + instruction["name"].upper().split(" "))}</%def>\ <%
<%def name="getClassName(program, instruction)">${program["name"].replace(" ", "")}${instruction["name"].replace(" ", "")}Instruction</%def>\ def getProgramId(program):
<%def name="getReferenceName(reference)">${"_".join(reference["name"].lower().split(" "))}</%def>\ return "_" + "_".join(program["name"].upper().split(" ") + ["ID"])
<%def name="getReferenceOptionalType(reference)">\
% if reference["optional"]: def getInstructionIdText(program, instruction):
| None\ return "_".join([getProgramId(program)] + ["INS"] + instruction["name"].upper().split(" "))
% endif
</%def>\ def getClassName(program, instruction):
<%def name="getReferenceOptionalTemplate(reference)">\ return program["name"].replace(" ", "") + instruction["name"].replace(" ", "") + "Instruction"
% if reference["optional"]:
, True\ INT_TYPES = ("u8", "u32", "u64", "i32", "i64", "timestamp", "lamports", "token_amount", "unix_timestamp")
% else:
, False\ def getPythonType(type):
% endif if type in INT_TYPES:
</%def>\ return "int"
<%def name="getPythonType(type)">\ elif type in ("pubkey", "authority"):
% if type in ("u32", "u64", "i32", "i64", "timestamp", "lamports", "token_amount"): return "Account"
int\ elif type in ("string", "memo"):
% elif type in ("pubKey", "authority"): return "str"
Account\ elif type in programs["types"] and programs["types"][type].get("is_enum"):
% elif type in ("string", "memo"): return "int"
str\ else:
% else: raise Exception(f"Unknown type: {type}")
int\
% endif def args_tuple(required_parameters, args_dict):
</%def>\ args = []
for required_parameter in required_parameters:
if required_parameter.startswith("#"):
args.append(required_parameter)
else:
args.append(args_dict[required_parameter])
return repr(tuple(args))
%>\
from micropython import const from micropython import const
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
@ -34,7 +43,7 @@ from trezor.wire import DataError
from apps.common.readers import read_uint32_le, read_uint64_le from apps.common.readers import read_uint32_le, read_uint64_le
from ..types import AccountTemplate, PropertyTemplate, UIProperty from ..types import PropertyTemplate, UIProperty
from ..format import ( from ..format import (
format_int, format_int,
format_lamports, format_lamports,
@ -106,8 +115,11 @@ if TYPE_CHECKING:
% endfor % endfor
## generates properties for reference accounts ## generates properties for reference accounts
% for reference in instruction["references"]: % for reference in instruction["references"][:instruction["references_required"]]:
${getReferenceName(reference)}: Account${getReferenceOptionalType(reference)} ${reference}: Account
% endfor
% for reference in instruction["references"][instruction["references_required"]:]:
${reference}: Account | None
% endfor % endfor
% endfor % endfor
% endfor % endfor
@ -123,7 +135,7 @@ def get_instruction_id_length(program_id: str) -> int:
% for _, type in programs["types"].items(): % for _, type in programs["types"].items():
% if "is_enum" in type and type["is_enum"]: % if "is_enum" in type and type["is_enum"]:
def ${type["format"]}(_: Instruction, value: int) -> str: def ${type["format"]}(value: int) -> str:
% for variant in type["fields"]: % for variant in type["fields"]:
if value == ${variant["value"]}: if value == ${variant["value"]}:
return "${variant["name"]}" return "${variant["name"]}"
@ -132,23 +144,24 @@ def ${type["format"]}(_: Instruction, value: int) -> str:
% endif % endif
% endfor % endfor
<%def name="getOptionalString(obj, string)">\
% if string in obj:
"${obj[string]}"\
%else:
None\
% endif
</%def>\
<% <%
# Make sure that all required parameters are present in the instruction. # Make sure that all required parameters are present in the instruction.
for program in programs["programs"]: for program in programs["programs"]:
for instruction in program["instructions"]: for instruction in program["instructions"]:
param_names = [parameter["name"] for parameter in instruction["parameters"]]
for parameter in instruction["parameters"]: for parameter in instruction["parameters"]:
if "required_parameters" in programs["types"][parameter["type"]]: required_parameters = programs["types"][parameter["type"]].get("required_parameters")
for required_parameter in programs["types"][parameter["type"]]["required_parameters"]: if not required_parameters:
instruction_parameter_names = [parameter["name"] for parameter in instruction["parameters"]] continue
if required_parameter not in instruction_parameter_names: args = parameter.get("args", {})
raise Exception(f"Instruction \"{instruction['name']}\" is missing the required parameter \"{required_parameter}\" from paremeter \"{parameter['name']}\".") for required_parameter in required_parameters:
if required_parameter.startswith("#"):
continue
if required_parameter not in args:
raise Exception(f"Parameter \"{parameter['name']}\" is missing the required argument \"{required_parameter}\".")
target = args[required_parameter]
if target not in param_names and target not in instruction["references"]:
raise Exception(f"Instruction \"{instruction['name']}\" is missing the required parameter \"{required_parameter}\" from parameter \"{parameter['name']}\".")
%> %>
def get_instruction( def get_instruction(
@ -164,44 +177,35 @@ def get_instruction(
program_id, program_id,
instruction_accounts, instruction_accounts,
${getInstructionIdText(program, instruction)}, ${getInstructionIdText(program, instruction)},
[ (
% for parameter in instruction["parameters"]: % for parameter in instruction["parameters"]:
PropertyTemplate( PropertyTemplate(
"${parameter["name"]}", "${parameter["name"]}",
${parameter["type"] == "authority"},
${parameter["optional"]}, ${parameter["optional"]},
${programs["types"][parameter["type"]]["parse"]}, ${programs["types"][parameter["type"]]["parse"]},
${programs["types"][parameter["type"]]["format"]}, ${programs["types"][parameter["type"]]["format"]},
${args_tuple(programs["types"][parameter["type"]].get("required_parameters", []), parameter.get("args", {}))},
), ),
% endfor % endfor
], ),
[ ${instruction["references_required"]},
% for reference in instruction["references"]: ${repr(tuple(instruction["references"]))},
AccountTemplate( (
"${reference["name"]}",
${reference["is_authority"]},
${reference["optional"]},
${reference.get("is_token_mint", False)},
),
% endfor
],
[
% for ui_property in instruction["ui_properties"]: % for ui_property in instruction["ui_properties"]:
UIProperty( UIProperty(
${getOptionalString(ui_property, "parameter")}, ${repr(ui_property.get("parameter"))},
${getOptionalString(ui_property, "account")}, ${repr(ui_property.get("account"))},
"${ui_property["display_name"]}", "${ui_property["display_name"]}",
${ui_property["is_authority"] if "is_authority" in ui_property else False}, ${repr(ui_property.get("default_value_to_hide"))},
${ui_property["default_value_to_hide"] if "default_value_to_hide" in ui_property else None},
), ),
% endfor % endfor
], ),
"${program["name"]}: ${instruction["name"]}", "${program["name"]}: ${instruction["name"]}",
True, True,
True, True,
${instruction.get("is_ui_hidden", False)}, ${instruction.get("is_ui_hidden", False)},
${instruction["is_multisig"]}, ${instruction["is_multisig"]},
${getOptionalString(instruction, "is_deprecated_warning")}, ${repr(instruction.get("is_deprecated_warning"))},
) )
% endfor % endfor
return Instruction( return Instruction(
@ -209,9 +213,10 @@ def get_instruction(
program_id, program_id,
instruction_accounts, instruction_accounts,
instruction_id, instruction_id,
[], (),
[], 0,
[], (),
(),
"${program["name"]}", "${program["name"]}",
True, True,
False, False,
@ -225,9 +230,10 @@ def get_instruction(
program_id, program_id,
instruction_accounts, instruction_accounts,
0, 0,
[], (),
[], 0,
[], (),
(),
"Unsupported program", "Unsupported program",
False, False,
False, False,

View File

@ -1,6 +1,7 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from .definitions import Definitions from .definitions import Definitions
from .transaction.parse import parse_pubkey
if TYPE_CHECKING: if TYPE_CHECKING:
from enum import IntEnum from enum import IntEnum
@ -10,8 +11,6 @@ if TYPE_CHECKING:
from trezor.utils import BufferReader from trezor.utils import BufferReader
from typing_extensions import Self from typing_extensions import Self
from .transaction import Instruction
Address = tuple[bytes, "AddressType"] Address = tuple[bytes, "AddressType"]
AddressReference = tuple[bytes, int, "AddressType"] AddressReference = tuple[bytes, int, "AddressType"]
Account = Address | AddressReference Account = Address | AddressReference
@ -42,26 +41,19 @@ class PropertyTemplate(Generic[T]):
def __init__( def __init__(
self, self,
name: str, name: str,
is_authority: bool, optional: bool,
is_optional: bool,
parse: Callable[[BufferReader], T], parse: Callable[[BufferReader], T],
format: Callable[[Instruction, T], str], format: Callable[..., str],
args: tuple[str, ...],
) -> None: ) -> None:
self.name = name self.name = name
self.is_authority = is_authority self.optional = optional
self.is_optional = is_optional
self.parse = parse self.parse = parse
self.format = format self.format = format
self.args = args
def is_pubkey(self) -> bool:
class AccountTemplate: return self.parse is parse_pubkey
def __init__(
self, name: str, is_authority: bool, optional: bool, is_token_mint: bool
) -> None:
self.name = name
self.is_authority = is_authority
self.optional = optional
self.is_token_mint = is_token_mint
class UIProperty: class UIProperty:
@ -70,13 +62,11 @@ class UIProperty:
parameter: str | None, parameter: str | None,
account: str | None, account: str | None,
display_name: str, display_name: str,
is_authority: bool,
default_value_to_hide: Any | None, default_value_to_hide: Any | None,
) -> None: ) -> None:
self.parameter = parameter self.parameter = parameter
self.account = account self.account = account
self.display_name = display_name self.display_name = display_name
self.is_authority = is_authority
self.default_value_to_hide = default_value_to_hide self.default_value_to_hide = default_value_to_hide

View File

@ -99,7 +99,8 @@ def create_mock_instruction(
accounts=[], accounts=[],
instruction_id=instruction_id, instruction_id=instruction_id,
property_templates=[], property_templates=[],
accounts_template=[], accounts_required=0,
account_templates=[],
ui_properties=[], ui_properties=[],
ui_name="", ui_name="",
is_program_supported=True, is_program_supported=True,

View File

@ -28,7 +28,8 @@ def create_mock_instruction(
accounts=[], accounts=[],
instruction_id=instruction_id, instruction_id=instruction_id,
property_templates=[], property_templates=[],
accounts_template=[], accounts_required=0,
account_templates=[],
ui_properties=[], ui_properties=[],
ui_name="", ui_name="",
is_program_supported=True, is_program_supported=True,

View File

@ -16,24 +16,31 @@ CONSTRUCT_TYPES = {
"string": "String", "string": "String",
"memo": "Memo", "memo": "Memo",
} }
INSTRUCTION_TYPES = { INSTRUCTION_TYPES = {
0: "Pass", 0: "Pass",
1: "Byte", 1: "Byte",
4: "Int32ul", 4: "Int32ul",
} }
def upper_snake_case(name): def upper_snake_case(name):
return "_".join(name.split(" ")).upper() return "_".join(name.split(" ")).upper()
def camelcase(name): def camelcase(name):
return "".join([word.capitalize() for word in name.split(" ")]) return "".join([word.capitalize() for word in name.split(" ")])
def instruction_id(instruction): def instruction_id(instruction):
return "INS_" + upper_snake_case(instruction.name) return "INS_" + upper_snake_case(instruction.name)
def instruction_struct_name(program, instruction): def instruction_struct_name(program, instruction):
return camelcase(program.name) + "_" + camelcase(instruction.name) + "_Instruction" return camelcase(program.name) + "_" + camelcase(instruction.name) + "_Instruction"
def instruction_subcon(program, instruction): def instruction_subcon(program, instruction):
if instruction.id is None: if instruction.id is None:
return "Pass" return "Pass"
instruction_id_type = INSTRUCTION_TYPES[program.instruction_id_length] instruction_id_type = INSTRUCTION_TYPES[program.instruction_id_length]
return f"Const({instruction.id}, {instruction_id_type})" return f"Const({instruction.id}, {instruction_id_type})"
%>\ %>\
from enum import Enum from enum import Enum
from construct import ( from construct import (
@ -76,12 +83,11 @@ class ${camelcase(program.name)}Instruction(Enum):
${camelcase(program.name)}_${camelcase(instruction.name)} = Struct( ${camelcase(program.name)}_${camelcase(instruction.name)} = Struct(
"program_index" / Byte, "program_index" / Byte,
"accounts" / CompactStruct( "accounts" / CompactStruct(
% for reference in instruction.references: % for reference in instruction.references[:instruction.references_required]:
% if reference.optional: "${reference}" / Byte,
"${reference.name}" / Optional(Byte), % endfor
% else: % for reference in instruction.references[instruction.references_required:]:
"${reference.name}" / Byte, "${reference}" / Optional(Byte),
% endif
% endfor % endfor
% if instruction.is_multisig: % if instruction.is_multisig:
"multisig_signers" / Optional(GreedyRange(Byte)) "multisig_signers" / Optional(GreedyRange(Byte))