feat(solana): add solana templates

- code is broken because depending modules are added in the next commit
vacuum_feat_solana
gabrielkerekes 7 months ago
parent 66578e487d
commit ff1317e174

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

@ -687,6 +687,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,232 @@
# generated from __init__.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="getReferenceTypeTemplate(reference)">\
% if reference["signer"]:
% if reference["access"] == "w":
ADDRESS_SIG\
% else:
ADDRESS_SIG_READ_ONLY\
% endif
% else:
% if reference["access"] == "w":
ADDRESS_RW\
% else:
ADDRESS_READ_ONLY\
% endif
% 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"):
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 ProcessError
from ..types import AccountTemplate, InstructionIdFormat, PropertyTemplate, UIProperty
from .instruction import Instruction
if TYPE_CHECKING:
from typing import Any, Type, TypeGuard
from ..types import Account
% 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]:
ids = {
%for program in programs["programs"]:
%for instruction in program["instructions"]:
"${getClassName(program, instruction)}": ("${program["id"]}", ${instruction["id"]}),
%endfor
%endfor
}
id = ids[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):
PROGRAM_ID = ${getProgramId(program)}
INSTRUCTION_ID = ${getInstructionIdText(program, 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
@classmethod
def is_type_of(cls, ins: Any) -> TypeGuard["${getClassName(program, instruction)}"]:
return (
ins.program_id == cls.PROGRAM_ID
and ins.instruction_id == cls.INSTRUCTION_ID
)
% endfor
% endfor
def get_instruction_id_length(program_id: str) -> InstructionIdFormat:
% for program in programs["programs"]:
if program_id == ${getProgramId(program)}:
return InstructionIdFormat(${program["instruction_id_format"]["length"]}, ${program["instruction_id_format"]["is_included_if_zero"]})
% endfor
return InstructionIdFormat(0, False)
<%def name="getOptionalString(obj, string)">\
% if string in obj:
"${obj[string]}"\
%else:
None\
% endif
</%def>\
def get_instruction(
program_id: str, instruction_id: int, instruction_accounts: list[Account], instruction_data: bytes
) -> 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"]}",
${parameter["optional"]},
),
% 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},
),
% 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
)
% for param in programs["parameters"]:
% if param["family"] == "enum":
class ${param["name"]}:
@classmethod
def type(cls) -> str:
return "${param["type"]}"
@classmethod
def from_int(cls, value: int) -> str:
% for variant in param["fields"]:
if value == ${variant["value"]}:
return "${variant["name"]}"
% endfor
raise ProcessError(f"Unknown value: {value}")
% endif
% endfor
def enum_type_to_class(enum_type: str):
% for param in programs["parameters"]:
% if param["family"] == "enum":
if enum_type == "${param["name"]}":
return ${param["name"]}
% endif
% endfor
raise ProcessError(f"Unknown enum type: {enum_type}")

File diff suppressed because it is too large Load Diff

@ -0,0 +1,45 @@
#!/usr/bin/env bash
set -e
CWD=`dirname "$0"`
RENDER="python3 $CWD/build_solana_templates.py"
PROGRAMS_PATH="$CWD/../src/apps/solana/transaction"
FW_PATH=$PROGRAMS_PATH
FW_OUTPUT_PATH="$FW_PATH/instructions.py"
TESTS_PATH="$CWD/../../tests/device_tests/solana/construct"
TESTS_OUTPUT_PATH="$TESTS_PATH/instructions.py"
format() {
isort $1 -q
black $1 -q
flake8 $1 -q
}
check_results() {
OUTPUT_PATH=$1
CHECK_FAIL=0
TMP=`mktemp`
TARGET=$OUTPUT_PATH
$RENDER "$PROGRAMS_PATH" -o $TMP
format $TMP
if ! diff -u "$TARGET" "$TMP"; then
CHECK_FAIL=1
fi
exit $CHECK_FAIL
}
if [ "$1" = "--check" ]; then
check_results $FW_OUTPUT_PATH
check_results $TESTS_OUTPUT_PATH
else
$RENDER $FW_PATH -p $PROGRAMS_PATH -o $FW_OUTPUT_PATH
format $FW_OUTPUT_PATH
$RENDER $TESTS_PATH -p "$PROGRAMS_PATH" -o $TESTS_OUTPUT_PATH
format $TESTS_OUTPUT_PATH
fi

@ -0,0 +1,25 @@
# !/usr/bin/env python3
from json import load
import click
from mako.template import Template
@click.command()
@click.argument("template_path", type=str)
@click.option("-p", "--programs-path", type=str, default=None)
@click.option("-o", "--out-file", type=click.File(mode="w"), default="-")
def render(template_path, programs_path, out_file):
if programs_path is None:
programs_path = template_path
with open(f"{programs_path}/programs.json", "r") as file:
programs = load(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,113 @@
# generated from __init__.py.mako
# do not edit manually!
<%def name="getProgramId(program)">${"_".join(program["name"].upper().split(" ") + ["ID"])}</%def>\
<%def name="getInstructionIdText(instruction)">${"_".join(["INS"] + instruction["name"].upper().split(" "))}</%def>\
<%def name="getProgramInstructionsEnumName(program)">${program["name"].replace(" ", "")}Instruction</%def>\
<%def name="getProgramInstructionsConstructName(program)">${program["name"].replace(" ","")}_Instruction</%def>\
<%def name="getInstructionConstructName(program, instruction)">${program["name"].replace(" ","")}_${instruction["name"].replace(" ", "")}_Instruction</%def>\
<%def name="getConstructType(type)">\
% if type in ("u64", "i64"):
Int64ul\
% elif type in ("u32", "i32"):
Int32ul\
% elif type == "u8":
Byte\
% elif type in ("pubKey", "authority"):
PublicKey\
% elif type == "string":
String\
% elif type == "memo":
Memo\
% else:
Int64ul\
% endif
</%def>\
from enum import IntEnum
from construct import (
Byte,
GreedyBytes,
GreedyRange,
Int32ul,
Int64ul,
Optional,
Struct,
Switch,
)
from .custom_constructs import (
CompactArray,
CompactStruct,
HexStringAdapter,
InstructionIdAdapter,
Memo,
PublicKey,
String,
)
class Program:
% for program in programs["programs"]:
${getProgramId(program)} = "${program["id"]}"
% endfor
INSTRUCTION_ID_FORMATS = {
% for program in programs["programs"]:
Program.${getProgramId(program)}: ${program["instruction_id_format"]},
% endfor
}
% for program in programs["programs"]:
${"#"} ${program["name"]} begin
class ${getProgramInstructionsEnumName(program)}(IntEnum):
% for instruction in program["instructions"]:
${getInstructionIdText(instruction)} = ${instruction["id"]}
% endfor
% for instruction in program["instructions"]:
${getInstructionConstructName(program, instruction)} = Struct(
"program_index" / Byte,
"accounts" / CompactStruct(
% for reference in instruction["references"]:
"${reference["name"]}" / Byte,
% endfor
% if instruction["is_multisig"]:
"multisig_signers" / Optional(GreedyRange(Byte))
% endif
),
"data" / CompactStruct(
"instruction_id" / InstructionIdAdapter(GreedyBytes),
% for parameter in instruction["parameters"]:
"${parameter["name"]}" / ${getConstructType(parameter["type"])},
% endfor
),
)
% endfor
${getProgramInstructionsConstructName(program)} = Switch(
lambda this: this.instruction_id,
{
%for instruction in program["instructions"]:
${getProgramInstructionsEnumName(program)}.${getInstructionIdText(instruction)}: ${getInstructionConstructName(program, instruction)},
%endfor
},
)
${"#"} ${program["name"]} end
% endfor
Instruction = Switch(
lambda this: this.program_id,
{
% for program in programs["programs"]:
Program.${getProgramId(program)}: ${getProgramInstructionsConstructName(program)},
%endfor
},
# unknown instruction
Struct(
"program_index" / Byte,
"accounts" / CompactArray(Byte),
"data" / HexStringAdapter(GreedyBytes),
)
)
Loading…
Cancel
Save