mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-22 14:28:07 +00:00
feat(solana): add solana templates
- code is broken because depending modules are added in the next commit
This commit is contained in:
parent
bf45d51af6
commit
b3f4b6ac2b
10
Makefile
10
Makefile
@ -112,6 +112,12 @@ templates: icons ## rebuild coin lists from definitions in common
|
||||
templates_check: ## check that coin lists are up to date
|
||||
./core/tools/build_templates --check
|
||||
|
||||
solana_templates: ## rebuild Solana instruction template file
|
||||
./core/tools/build_solana_templates
|
||||
|
||||
solana_templates_check: ## check that Solana instruction template file is up to date
|
||||
./core/tools/build_solana_templates --check
|
||||
|
||||
icons: ## generate FIDO service icons
|
||||
python3 core/tools/build_icons.py
|
||||
|
||||
@ -138,6 +144,6 @@ vendorheader: ## generate vendor header
|
||||
vendorheader_check: ## check that vendor header is up to date
|
||||
./core/embed/vendorheader/generate.sh --quiet --check
|
||||
|
||||
gen: mocks icons templates protobuf ci_docs vendorheader ## regenerate auto-generated files from sources
|
||||
gen: mocks icons templates protobuf ci_docs vendorheader solana_templates ## regenerate auto-generated files from sources
|
||||
|
||||
gen_check: mocks_check icons_check templates_check protobuf_check ci_docs_check vendorheader_check ## check validity of auto-generated files
|
||||
gen_check: mocks_check icons_check templates_check protobuf_check ci_docs_check vendorheader_check solana_templates_check ## check validity of auto-generated files
|
||||
|
@ -166,6 +166,12 @@ templates: ## render Mako templates (for lists of coins, tokens, etc.)
|
||||
templates_check: ## check that Mako-rendered files match their templates
|
||||
./tools/build_templates --check
|
||||
|
||||
solana_templates: ## rebuild Solana instruction template file
|
||||
./tools/build_solana_templates
|
||||
|
||||
solana_templates_check: ## check that Solana instruction template file is up to date
|
||||
./tools/build_solana_templates --check
|
||||
|
||||
## build commands:
|
||||
|
||||
build: build_boardloader build_bootloader build_firmware build_prodtest build_unix ## build all
|
||||
|
@ -689,6 +689,8 @@ if not utils.BITCOIN_ONLY:
|
||||
import apps.solana.get_public_key
|
||||
apps.solana.sign_tx
|
||||
import apps.solana.sign_tx
|
||||
apps.solana.transaction.instructions
|
||||
import apps.solana.transaction.instructions
|
||||
apps.stellar
|
||||
import apps.stellar
|
||||
apps.stellar.consts
|
||||
|
5370
core/src/apps/solana/transaction/instructions.py
Normal file
5370
core/src/apps/solana/transaction/instructions.py
Normal file
File diff suppressed because it is too large
Load Diff
225
core/src/apps/solana/transaction/instructions.py.mako
Normal file
225
core/src/apps/solana/transaction/instructions.py.mako
Normal file
@ -0,0 +1,225 @@
|
||||
# generated from instructions.py.mako
|
||||
# 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 name="getReferenceName(reference)">${"_".join(reference["name"].lower().split(" "))}</%def>\
|
||||
<%def name="getReferenceOptionalType(reference)">\
|
||||
% if reference["optional"]:
|
||||
| None\
|
||||
% endif
|
||||
</%def>\
|
||||
<%def name="getReferenceOptionalTemplate(reference)">\
|
||||
% if reference["optional"]:
|
||||
, True\
|
||||
% else:
|
||||
, False\
|
||||
% endif
|
||||
</%def>\
|
||||
<%def name="getPythonType(type)">\
|
||||
% if type in ("u32", "u64", "i32", "i64", "timestamp", "lamports", "token_amount"):
|
||||
int\
|
||||
% elif type in ("pubKey", "authority"):
|
||||
Account\
|
||||
% elif type in ("string", "memo"):
|
||||
str\
|
||||
% else:
|
||||
int\
|
||||
% endif
|
||||
</%def>\
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from trezor.wire import DataError
|
||||
|
||||
from apps.common.readers import read_uint32_le, read_uint64_le
|
||||
|
||||
from ..types import AccountTemplate, PropertyTemplate, UIProperty
|
||||
from ..format import (
|
||||
format_int,
|
||||
format_lamports,
|
||||
format_pubkey,
|
||||
format_identity,
|
||||
format_token_amount,
|
||||
format_unix_timestamp,
|
||||
)
|
||||
from .instruction import Instruction
|
||||
from .parse import (
|
||||
parse_byte,
|
||||
parse_memo,
|
||||
parse_pubkey,
|
||||
parse_string,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Type
|
||||
|
||||
from ..types import Account, InstructionId, InstructionData
|
||||
|
||||
% for program in programs["programs"]:
|
||||
${getProgramId(program)} = "${program["id"]}"
|
||||
% endfor
|
||||
|
||||
% for program in programs["programs"]:
|
||||
% for instruction in program["instructions"]:
|
||||
${getInstructionIdText(program, instruction)} = ${instruction["id"]}
|
||||
% endfor
|
||||
% endfor
|
||||
|
||||
def __getattr__(name: str) -> Type[Instruction]:
|
||||
def get_id(name: str) -> tuple[str, InstructionId]:
|
||||
%for program in programs["programs"]:
|
||||
%for instruction in program["instructions"]:
|
||||
if name == "${getClassName(program, instruction)}":
|
||||
return ("${program["id"]}", ${instruction["id"]})
|
||||
%endfor
|
||||
%endfor
|
||||
raise AttributeError # Unknown instruction
|
||||
|
||||
id = get_id(name)
|
||||
|
||||
class FakeClass(Instruction):
|
||||
@classmethod
|
||||
def is_type_of(cls, ins: Any):
|
||||
return ins.program_id == id[0] and ins.instruction_id == id[1]
|
||||
|
||||
return FakeClass
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
% for program in programs["programs"]:
|
||||
## generates classes for instructions
|
||||
% for instruction in program["instructions"]:
|
||||
class ${getClassName(program, instruction)}(Instruction):
|
||||
## generates properties for instruction parameters
|
||||
% for parameter in instruction["parameters"]:
|
||||
${parameter["name"]}: ${getPythonType(parameter["type"])}
|
||||
% endfor
|
||||
|
||||
## generates properties for reference accounts
|
||||
% for reference in instruction["references"]:
|
||||
${getReferenceName(reference)}: Account${getReferenceOptionalType(reference)}
|
||||
% endfor
|
||||
% endfor
|
||||
% endfor
|
||||
|
||||
def get_instruction_id_length(program_id: str) -> int:
|
||||
% for program in programs["programs"]:
|
||||
if program_id == ${getProgramId(program)}:
|
||||
return ${program["instruction_id_length"]}
|
||||
% endfor
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
% for _, type in programs["types"].items():
|
||||
% if "is_enum" in type and type["is_enum"]:
|
||||
def ${type["format"]}(_: Instruction, value: int) -> str:
|
||||
% for variant in type["fields"]:
|
||||
if value == ${variant["value"]}:
|
||||
return "${variant["name"]}"
|
||||
% endfor
|
||||
raise DataError("Unknown value")
|
||||
% endif
|
||||
% 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.
|
||||
for program in programs["programs"]:
|
||||
for instruction in program["instructions"]:
|
||||
for parameter in instruction["parameters"]:
|
||||
if "required_parameters" in programs["types"][parameter["type"]]:
|
||||
for required_parameter in programs["types"][parameter["type"]]["required_parameters"]:
|
||||
instruction_parameter_names = [parameter["name"] for parameter in instruction["parameters"]]
|
||||
if required_parameter not in instruction_parameter_names:
|
||||
raise Exception(f"Instruction \"{instruction['name']}\" is missing the required parameter \"{required_parameter}\" from paremeter \"{parameter['name']}\".")
|
||||
%>
|
||||
|
||||
def get_instruction(
|
||||
program_id: str, instruction_id: InstructionId, instruction_accounts: list[Account], instruction_data: InstructionData
|
||||
) -> Instruction:
|
||||
% for program in programs["programs"]:
|
||||
% if len(program["instructions"]) > 0:
|
||||
if program_id == ${getProgramId(program)}:
|
||||
% for instruction in program["instructions"]:
|
||||
if instruction_id == ${getInstructionIdText(program, instruction)}:
|
||||
return Instruction(
|
||||
instruction_data,
|
||||
program_id,
|
||||
instruction_accounts,
|
||||
${getInstructionIdText(program, instruction)},
|
||||
[
|
||||
% for parameter in instruction["parameters"]:
|
||||
PropertyTemplate(
|
||||
"${parameter["name"]}",
|
||||
${parameter["type"] == "authority"},
|
||||
${parameter["optional"]},
|
||||
${programs["types"][parameter["type"]]["parse"]},
|
||||
${programs["types"][parameter["type"]]["format"]},
|
||||
),
|
||||
% endfor
|
||||
],
|
||||
[
|
||||
% for reference in instruction["references"]:
|
||||
AccountTemplate(
|
||||
"${reference["name"]}",
|
||||
${reference["is_authority"]},
|
||||
${reference["optional"]},
|
||||
),
|
||||
% endfor
|
||||
],
|
||||
[
|
||||
% for ui_property in instruction["ui_properties"]:
|
||||
UIProperty(
|
||||
${getOptionalString(ui_property, "parameter")},
|
||||
${getOptionalString(ui_property, "account")},
|
||||
"${ui_property["display_name"]}",
|
||||
${ui_property["is_authority"] if "is_authority" in ui_property else False},
|
||||
${ui_property["default_value_to_hide"] if "default_value_to_hide" in ui_property else None},
|
||||
),
|
||||
% endfor
|
||||
],
|
||||
"${program["name"]}: ${instruction["name"]}",
|
||||
True,
|
||||
True,
|
||||
${instruction["is_multisig"]},
|
||||
${getOptionalString(instruction, "is_deprecated_warning")},
|
||||
)
|
||||
% endfor
|
||||
return Instruction(
|
||||
instruction_data,
|
||||
program_id,
|
||||
instruction_accounts,
|
||||
instruction_id,
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
"${program["name"]}",
|
||||
True,
|
||||
False,
|
||||
False
|
||||
)
|
||||
% endif
|
||||
% endfor
|
||||
return Instruction(
|
||||
instruction_data,
|
||||
program_id,
|
||||
instruction_accounts,
|
||||
0,
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
"Unsupported program",
|
||||
False,
|
||||
False,
|
||||
False
|
||||
)
|
||||
|
3255
core/src/apps/solana/transaction/programs.json
Normal file
3255
core/src/apps/solana/transaction/programs.json
Normal file
File diff suppressed because it is too large
Load Diff
70
core/tools/build_solana_templates
Executable file
70
core/tools/build_solana_templates
Executable file
@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
CWD=`dirname "$0"`
|
||||
RENDER="python3 $CWD/build_solana_templates.py"
|
||||
|
||||
PROGRAMS_FILE_PATH="$CWD/../src/apps/solana/transaction/programs.json"
|
||||
|
||||
FW_PATH="$CWD/../src/apps/solana/transaction"
|
||||
FW_TEMPLATE_PATH="$FW_PATH/instructions.py.mako"
|
||||
FW_OUTPUT_PATH="$FW_PATH/instructions.py"
|
||||
|
||||
TESTS_PATH="$CWD/../../tests/device_tests/solana/construct"
|
||||
TESTS_TEMPLATE_PATH="$TESTS_PATH/instructions.py.mako"
|
||||
TESTS_OUTPUT_PATH="$TESTS_PATH/instructions.py"
|
||||
|
||||
format() {
|
||||
isort $1 -q
|
||||
black $1 -q
|
||||
flake8 $1 -q
|
||||
}
|
||||
|
||||
check_results() {
|
||||
TEMPLATE_PATH=$1
|
||||
OUTPUT_PATH=$2
|
||||
|
||||
CHECK_FAIL=0
|
||||
|
||||
TMP="./core/tools/solana_template_check$(date +%s).py"
|
||||
touch $TMP
|
||||
|
||||
TARGET=$OUTPUT_PATH
|
||||
$RENDER $TEMPLATE_PATH -p $PROGRAMS_FILE_PATH -o $TMP
|
||||
format $TMP
|
||||
|
||||
if ! diff -u "$TARGET" "$TMP"; then
|
||||
CHECK_FAIL=1
|
||||
fi
|
||||
|
||||
rm $TMP
|
||||
|
||||
exit $CHECK_FAIL
|
||||
}
|
||||
|
||||
set_output_timestamp() {
|
||||
TEMPLATE_PATH=$1
|
||||
OUTPUT_PATH=$2
|
||||
|
||||
PROGRAMS_FILE_TIMESTAMP=$(date -r $PROGRAMS_FILE_PATH)
|
||||
TEMPLATE_TIMESTAMP=$(date -r $TEMPLATE_PATH)
|
||||
|
||||
if [[ "$PROGRAMS_FILE_TIMESTAMP" > "$TEMPLATE_TIMESTAMP" ]]; then
|
||||
touch $OUTPUT_PATH -r $PROGRAMS_FILE_PATH
|
||||
else
|
||||
touch $OUTPUT_PATH -r $TEMPLATE_PATH
|
||||
fi
|
||||
}
|
||||
|
||||
if [ "$1" = "--check" ]; then
|
||||
check_results $FW_PATH $FW_OUTPUT_PATH
|
||||
check_results $TESTS_PATH $TESTS_OUTPUT_PATH
|
||||
else
|
||||
$RENDER $FW_PATH -p $PROGRAMS_FILE_PATH -o $FW_OUTPUT_PATH
|
||||
format $FW_OUTPUT_PATH
|
||||
set_output_timestamp $FW_TEMPLATE_PATH $FW_OUTPUT_PATH
|
||||
|
||||
$RENDER $TESTS_PATH -p "$PROGRAMS_FILE_PATH" -o $TESTS_OUTPUT_PATH
|
||||
format $TESTS_OUTPUT_PATH
|
||||
set_output_timestamp $TESTS_TEMPLATE_PATH $TESTS_OUTPUT_PATH
|
||||
fi
|
22
core/tools/build_solana_templates.py
Executable file
22
core/tools/build_solana_templates.py
Executable file
@ -0,0 +1,22 @@
|
||||
# !/usr/bin/env python3
|
||||
from json import load
|
||||
|
||||
import click
|
||||
from mako.template import Template
|
||||
from munch import munchify
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.argument("template_path", type=str)
|
||||
@click.option("-p", "--programs-file", type=click.File(mode="r"), default="-")
|
||||
@click.option("-o", "--out-file", type=click.File(mode="w"), default="-")
|
||||
def render(template_path, programs_file, out_file):
|
||||
programs = munchify(load(programs_file))
|
||||
|
||||
template = Template(filename=f"{template_path}/instructions.py.mako")
|
||||
|
||||
out_file.write(template.render(programs=programs))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
render()
|
@ -4,8 +4,8 @@ set -e
|
||||
CWD=`dirname "$0"`
|
||||
RENDER="$CWD/../vendor/trezor-common/tools/cointool.py render"
|
||||
|
||||
# Search both in `core/src` and `core/embed`
|
||||
FIND_TEMPLATES="find $CWD/.. -name *.mako -not -name _proto*"
|
||||
# Search both in `core/src` and `core/embed` - Solana templates are excluded since those are handled separately
|
||||
FIND_TEMPLATES="find $CWD/.. -name *.mako -not -name _proto* -not -path *solana*"
|
||||
|
||||
check_results() {
|
||||
CHECK_FAIL=0
|
||||
|
1404
tests/device_tests/solana/construct/instructions.py
Normal file
1404
tests/device_tests/solana/construct/instructions.py
Normal file
File diff suppressed because it is too large
Load Diff
123
tests/device_tests/solana/construct/instructions.py.mako
Normal file
123
tests/device_tests/solana/construct/instructions.py.mako
Normal file
@ -0,0 +1,123 @@
|
||||
# generated from __init__.py.mako
|
||||
# do not edit manually!
|
||||
|
||||
<%
|
||||
CONSTRUCT_TYPES = {
|
||||
"u64": "Int64ul",
|
||||
"i64": "Int64ul",
|
||||
"unix_timestamp": "Int64ul",
|
||||
"u32": "Int32ul",
|
||||
"i32": "Int32ul",
|
||||
"StakeAuthorize": "Int32ul",
|
||||
"u8": "Byte",
|
||||
"AuthorityType": "Byte",
|
||||
"pubkey": "PublicKey",
|
||||
"authority": "PublicKey",
|
||||
"string": "String",
|
||||
"memo": "Memo",
|
||||
}
|
||||
INSTRUCTION_TYPES = {
|
||||
0: "Pass",
|
||||
1: "Byte",
|
||||
4: "Int32ul",
|
||||
}
|
||||
def upper_snake_case(name):
|
||||
return "_".join(name.split(" ")).upper()
|
||||
def camelcase(name):
|
||||
return "".join([word.capitalize() for word in name.split(" ")])
|
||||
def instruction_id(instruction):
|
||||
return "INS_" + upper_snake_case(instruction.name)
|
||||
def instruction_struct_name(program, instruction):
|
||||
return camelcase(program.name) + "_" + camelcase(instruction.name) + "_Instruction"
|
||||
def instruction_subcon(program, instruction):
|
||||
if instruction.id is None:
|
||||
return "Pass"
|
||||
instruction_id_type = INSTRUCTION_TYPES[program.instruction_id_length]
|
||||
return f"Const({instruction.id}, {instruction_id_type})"
|
||||
%>\
|
||||
from enum import Enum
|
||||
from construct import (
|
||||
Byte,
|
||||
Const,
|
||||
GreedyBytes,
|
||||
GreedyRange,
|
||||
Int32ul,
|
||||
Int64ul,
|
||||
Optional,
|
||||
Pass,
|
||||
Select,
|
||||
Struct,
|
||||
)
|
||||
from .custom_constructs import (
|
||||
CompactArray,
|
||||
CompactStruct,
|
||||
HexStringAdapter,
|
||||
Memo,
|
||||
OptionalParameter,
|
||||
PublicKey,
|
||||
String,
|
||||
)
|
||||
|
||||
class Program(Enum):
|
||||
% for program in programs.programs:
|
||||
${upper_snake_case(program.name)} = "${program.id}"
|
||||
% endfor
|
||||
|
||||
% for program in programs.programs:
|
||||
|
||||
${"#"} ${program.name} begin
|
||||
|
||||
class ${camelcase(program.name)}Instruction(Enum):
|
||||
% for instruction in program.instructions:
|
||||
${upper_snake_case(instruction.name)} = ${instruction.id}
|
||||
% endfor
|
||||
|
||||
% for instruction in program.instructions:
|
||||
${camelcase(program.name)}_${camelcase(instruction.name)} = Struct(
|
||||
"program_index" / Byte,
|
||||
"accounts" / CompactStruct(
|
||||
% for reference in instruction.references:
|
||||
% if reference.optional:
|
||||
"${reference.name}" / Optional(Byte),
|
||||
% else:
|
||||
"${reference.name}" / Byte,
|
||||
% endif
|
||||
% endfor
|
||||
% if instruction.is_multisig:
|
||||
"multisig_signers" / Optional(GreedyRange(Byte))
|
||||
% endif
|
||||
),
|
||||
"data" / CompactStruct(
|
||||
"instruction_id" / ${instruction_subcon(program, instruction)},
|
||||
% for parameter in instruction.parameters:
|
||||
% if parameter["optional"]:
|
||||
"${parameter["name"]}" / OptionalParameter(${CONSTRUCT_TYPES.get(parameter.type)}),
|
||||
% else:
|
||||
"${parameter["name"]}" / ${CONSTRUCT_TYPES.get(parameter.type, "Int64ul")},
|
||||
% endif
|
||||
% endfor
|
||||
),
|
||||
)
|
||||
|
||||
% endfor
|
||||
|
||||
${camelcase(program.name)}_Instruction = Select(
|
||||
%for instruction in program.instructions:
|
||||
${camelcase(program.name)}_${camelcase(instruction.name)},
|
||||
%endfor
|
||||
)
|
||||
|
||||
${"#"} ${program.name} end
|
||||
% endfor
|
||||
|
||||
PROGRAMS = {
|
||||
% for program in programs.programs:
|
||||
"${program.id}": ${camelcase(program.name)}_Instruction,
|
||||
%endfor
|
||||
}
|
||||
|
||||
UnknownInstruction = Struct(
|
||||
"program_index" / Byte,
|
||||
"accounts" / CompactArray(Byte),
|
||||
"data" / HexStringAdapter(GreedyBytes),
|
||||
)
|
Loading…
Reference in New Issue
Block a user