feat(solana): add solana templates

- code is broken because depending modules are added in the next commit
pull/3443/head
gabrielkerekes 7 months ago committed by matejcik
parent bf45d51af6
commit b3f4b6ac2b

@ -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

File diff suppressed because it is too large Load Diff

@ -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
)

File diff suppressed because it is too large Load Diff

@ -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

@ -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

File diff suppressed because it is too large Load Diff

@ -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…
Cancel
Save