From daf570ec0ddf940020e8e9a4bd87bda90328973e Mon Sep 17 00:00:00 2001 From: Daniel McGregor Date: Wed, 4 Dec 2024 11:53:10 +0800 Subject: [PATCH] fix: don't attempt to assemble contracts that aren't explicitly selected for compilation and may not have template variables defined --- src/puya/compile.py | 11 ++++++++--- src/puya/ir/main.py | 17 ++++++++++++----- src/puya/ir/models.py | 3 ++- src/puya/mir/main.py | 4 ++-- src/puya/mir/models.py | 3 ++- src/puya/models.py | 29 +++++++++++++++++++++++++++++ src/puya/teal/main.py | 2 +- src/puya/teal/models.py | 4 ++-- tests/test_assemble.py | 10 ++++++++-- tests/test_cli.py | 11 +++++++++++ 10 files changed, 77 insertions(+), 17 deletions(-) diff --git a/src/puya/compile.py b/src/puya/compile.py index 6274524de3..21ac20de1d 100644 --- a/src/puya/compile.py +++ b/src/puya/compile.py @@ -33,6 +33,7 @@ ContractReference, DebugInfo, LogicSignatureMetaData, + LogicSigProgramReference, LogicSigReference, TemplateValue, ) @@ -194,7 +195,7 @@ def _dummy_program() -> _CompiledProgram: return _CompiledProgram( teal=TealProgram( - id="", + ref=LogicSigProgramReference(LogicSigReference()), avm_version=0, main=TealSubroutine( is_main=True, @@ -256,16 +257,20 @@ def _logic_sig_to_teal( def _compile_program(context: CompileContext, program: TealProgram) -> _CompiledProgram: + # if bytecode isn't required for this program, then only produce debug info + debug_only = ( + not context.options.output_bytecode or program.ref.reference not in context.compilation_set + ) assembled = assemble_program( context, program, template_variables={k: (v, None) for k, v in context.options.template_variables.items()}, - debug_only=not context.options.output_bytecode, + debug_only=debug_only, ) return _CompiledProgram( teal=program, teal_src=emit_teal(context, program), - bytecode=assembled.bytecode if context.options.output_bytecode else None, + bytecode=assembled.bytecode if not debug_only else None, debug_info=assembled.debug_info, template_variables=assembled.template_variables, ) diff --git a/src/puya/ir/main.py b/src/puya/ir/main.py index 56466162f6..9adef1258e 100644 --- a/src/puya/ir/main.py +++ b/src/puya/ir/main.py @@ -39,8 +39,11 @@ ARC4Struct, ARC4StructField, ContractMetaData, + ContractProgramReference, ContractState, LogicSignatureMetaData, + LogicSigProgramReference, + ProgramReference, StateTotals, ) from puya.parse import SourceLocation @@ -274,7 +277,9 @@ def _build_ir(ctx: IRBuildContext, contract: awst_nodes.Contract) -> Contract: *(ctx.subroutines[ref] for ref in approval_subs_srefs), *ctx.embedded_funcs_lookup.values(), ), - program_id=".".join((contract.id, contract.approval_program.short_name)), + ref=ContractProgramReference( + reference=contract.id, program_name=contract.approval_program.short_name + ), avm_version=avm_version, ) clear_state_ir = _make_program( @@ -284,7 +289,9 @@ def _build_ir(ctx: IRBuildContext, contract: awst_nodes.Contract) -> Contract: *(ctx.subroutines[ref] for ref in clear_subs_srefs), *ctx.embedded_funcs_lookup.values(), ), - program_id=".".join((contract.id, contract.clear_program.short_name)), + ref=ContractProgramReference( + reference=contract.id, program_name=contract.clear_program.short_name + ), avm_version=avm_version, ) result = Contract( @@ -336,7 +343,7 @@ def _build_logic_sig_ir( *(ctx.subroutines[ref] for ref in program_sub_refs), *ctx.embedded_funcs_lookup.values(), ), - program_id=logic_sig.id, + ref=LogicSigProgramReference(reference=logic_sig.id), avm_version=coalesce(logic_sig.avm_version, ctx.options.target_avm_version), ) result = LogicSignature( @@ -421,7 +428,7 @@ def _make_program( main: awst_nodes.Function, references: Iterable[Subroutine], *, - program_id: str, + ref: ProgramReference, avm_version: int, ) -> Program: if main.args: @@ -439,7 +446,7 @@ def _make_program( ) FunctionIRBuilder.build_body(ctx, function=main, subroutine=main_sub) return Program( - id=program_id, + ref=ref, main=main_sub, subroutines=tuple(references), avm_version=avm_version, diff --git a/src/puya/ir/models.py b/src/puya/ir/models.py index d8032c78fc..dd9355c7b0 100644 --- a/src/puya/ir/models.py +++ b/src/puya/ir/models.py @@ -19,6 +19,7 @@ ContractReference, LogicSignatureMetaData, LogicSigReference, + ProgramReference, ) from puya.parse import SourceLocation from puya.utils import unique @@ -935,7 +936,7 @@ class Program(Context): # return # # ie, just omit the subroutine header, and replace any&all retsub ops with a return instead - id: str + ref: ProgramReference main: Subroutine subroutines: Sequence[Subroutine] source_location: SourceLocation | None = None diff --git a/src/puya/mir/main.py b/src/puya/mir/main.py index b85c9d029c..a31de3b610 100644 --- a/src/puya/mir/main.py +++ b/src/puya/mir/main.py @@ -16,8 +16,8 @@ def program_ir_to_mir( ctx = attrs_extend(ProgramMIRContext, context, program=program_ir) result = models.Program( - id=program_ir.id, - main=_lower_subroutine_to_mir(ctx, program_ir.main, is_main=True, name=program_ir.id), + ref=program_ir.ref, + main=_lower_subroutine_to_mir(ctx, program_ir.main, is_main=True, name=program_ir.ref.id), subroutines=[ _lower_subroutine_to_mir(ctx, ir_sub, is_main=False, name=ir_sub.full_name) for ir_sub in program_ir.subroutines diff --git a/src/puya/mir/models.py b/src/puya/mir/models.py index 5ec0536961..dd1293e15f 100644 --- a/src/puya/mir/models.py +++ b/src/puya/mir/models.py @@ -10,6 +10,7 @@ from puya.avm_type import AVMType from puya.errors import InternalError from puya.ir.utils import format_bytes, format_error_comment +from puya.models import ProgramReference if t.TYPE_CHECKING: from collections.abc import Iterable, Iterator, Mapping, Sequence @@ -622,7 +623,7 @@ def get_block(self, block_name: str) -> MemoryBasicBlock: @attrs.define class Program: - id: str + ref: ProgramReference main: MemorySubroutine subroutines: list[MemorySubroutine] avm_version: int diff --git a/src/puya/models.py b/src/puya/models.py index 40faa8069c..6b05443520 100644 --- a/src/puya/models.py +++ b/src/puya/models.py @@ -21,6 +21,35 @@ class LogicSigReference(str): # can't use typing.NewType with pattern matching __slots__ = () +class ProgramReference(abc.ABC): + @property + @abc.abstractmethod + def reference(self) -> ContractReference | LogicSigReference: ... + + @property + @abc.abstractmethod + def id(self) -> str: ... + + +@attrs.frozen +class LogicSigProgramReference(ProgramReference): + reference: LogicSigReference + + @property + def id(self) -> str: + return self.reference + + +@attrs.frozen +class ContractProgramReference(ProgramReference): + reference: ContractReference + program_name: str + + @property + def id(self) -> str: + return f"{self.reference}.{self.program_name}" + + # values and names are matched to AVM definitions class OnCompletionAction(enum.IntEnum): NoOp = 0 diff --git a/src/puya/teal/main.py b/src/puya/teal/main.py index 7f844a9f28..63b8979a7c 100644 --- a/src/puya/teal/main.py +++ b/src/puya/teal/main.py @@ -38,7 +38,7 @@ def _get_all_stack_manipulations( def _build_teal(mir_program: mir.Program) -> teal_models.TealProgram: program = teal_models.TealProgram( - id=mir_program.id, + ref=mir_program.ref, avm_version=mir_program.avm_version, main=TealBuilder.build_subroutine(mir_program.main), subroutines=[TealBuilder.build_subroutine(mir_sub) for mir_sub in mir_program.subroutines], diff --git a/src/puya/teal/models.py b/src/puya/teal/models.py index 52a5f47dbd..a0db53349a 100644 --- a/src/puya/teal/models.py +++ b/src/puya/teal/models.py @@ -7,7 +7,7 @@ from puya.ir.types_ import AVMBytesEncoding from puya.ir.utils import format_bytes, format_error_comment from puya.mir.models import Signature -from puya.models import OnCompletionAction, TransactionType +from puya.models import OnCompletionAction, ProgramReference, TransactionType from puya.parse import SourceLocation from puya.utils import valid_bytes, valid_int64 @@ -518,7 +518,7 @@ class TealSubroutine: @attrs.define class TealProgram: - id: str + ref: ProgramReference avm_version: int main: TealSubroutine subroutines: list[TealSubroutine] diff --git a/tests/test_assemble.py b/tests/test_assemble.py index fc9dd8fd89..4b7a2f59d1 100644 --- a/tests/test_assemble.py +++ b/tests/test_assemble.py @@ -8,7 +8,13 @@ from puya.context import CompileContext from puya.mir.models import Signature -from puya.models import CompiledContract, CompiledLogicSig, CompiledProgram +from puya.models import ( + CompiledContract, + CompiledLogicSig, + CompiledProgram, + LogicSigProgramReference, + LogicSigReference, +) from puya.options import PuyaOptions from puya.teal import models as teal from puya.ussemble.main import assemble_program @@ -122,7 +128,7 @@ def test_assemble_last_op_jump() -> None: sources_by_path={}, ), program=teal.TealProgram( - id="", + ref=LogicSigProgramReference(LogicSigReference()), avm_version=10, main=teal.TealSubroutine( is_main=True, diff --git a/tests/test_cli.py b/tests/test_cli.py index 64b2f2db17..4fbe03a658 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -87,6 +87,17 @@ def test_run_single_file() -> None: run_puyapy([TEST_CASES_DIR / Path("simple") / "contract.py"]) +def test_run_single_file_with_other_references(tmpdir: Path) -> None: + run_puyapy( + [ + TEST_CASES_DIR / "compile" / "factory.py", + "--output-bytecode", + "--out-dir", + str(tmpdir), + ] + ) + + def test_run_multiple_files() -> None: run_puyapy( [