From ca75dbf1ab2251997b592fa38e5a874e78b9ce34 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 14 Feb 2022 11:30:10 -0600 Subject: [PATCH 01/85] Fresh Start: Dynamic Scratch Variables --- tests/dynamic_scratch.py | 79 ++++++++++++++++++++++++ tests/teal/dynamic_scratch_expected.teal | 42 +++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 tests/dynamic_scratch.py create mode 100644 tests/teal/dynamic_scratch_expected.teal diff --git a/tests/dynamic_scratch.py b/tests/dynamic_scratch.py new file mode 100644 index 000000000..abef333e3 --- /dev/null +++ b/tests/dynamic_scratch.py @@ -0,0 +1,79 @@ +from ast import Sub +from cmath import exp +from pathlib import Path + +from pyteal import * +from pyteal.types import require_type + + +def compile_and_save(approval): + teal = Path.cwd() / "tests" / "teal" + compiled = compileTeal(approval(), mode=Mode.Application, version=6) + name = approval.__name__ + with open(teal / (name + ".teal"), "w") as f: + f.write(compiled) + return teal, name, compiled + + +def mismatch_ligature(expected, actual): + la, le = len(actual), len(expected) + mm_idx = -1 + for i in range(min(la, le)): + if expected[i] != actual[i]: + mm_idx = i + break + if mm_idx < 0: + return "" + return " " * (mm_idx) + "X" + "-" * (max(la, le) - mm_idx - 1) + + +def assert_teal_as_expected(path2actual, path2expected): + + with open(path2actual, "r") as fa, open(path2expected, "r") as fe: + alines = fa.read().split("\n") + elines = fe.read().split("\n") + + assert len(elines) == len( + alines + ), f"""EXPECTED {len(elines)} lines for {path2expected} +but ACTUALLY got {len(alines)} lines in {path2actual}""" + + for i, actual in enumerate(alines): + expected = elines[i] + assert expected.startswith( + actual + ), f"""ACTUAL line +LINE{i+1}: +{actual} +{mismatch_ligature(expected, actual)} +DOES NOT prefix the EXPECTED (which should have been actual + some commentary): +LINE{i+1}: +{expected} +{mismatch_ligature(expected, actual)} +""" + + +def dynamic_scratch(): + current_player = ScratchVar(TealType.uint64, 128) + player_score = ScratchVar(TealType.uint64, current_player.load()) + i = ScratchVar(TealType.uint64, 0) + return Seq( + current_player.store(Int(129)), + For(i.store(Int(1)), i.load() <= Int(10), i.store(i.load() + Int(1))).Do( + Seq( + current_player.store(Int(129) + (i.load() - Int(1)) % Int(5)), + Log(Concat(Bytes("Current player: "), Itob(current_player.load()))), + Log(Concat(Bytes("Player score index: "), Itob(player_score.index()))), + player_score.store(player_score.load() + i.load()), + ) + ), + Int(1), + ) + + +if __name__ == "__main__": + teal_dir, name, compiled = compile_and_save(dynamic_scratch) + path2actual = teal_dir / (name + ".teal") + path2expected = teal_dir / (name + "_expected.teal") + + assert_teal_as_expected(path2actual, path2expected) diff --git a/tests/teal/dynamic_scratch_expected.teal b/tests/teal/dynamic_scratch_expected.teal new file mode 100644 index 000000000..a75e1a6ff --- /dev/null +++ b/tests/teal/dynamic_scratch_expected.teal @@ -0,0 +1,42 @@ +#pragma version 6 +int 129 +store 128 128: 129 +int 1 >1 +store 0 0: 1 +main_l1: ... after 10 iters: 0: 11; 128: 133; 129: 7, 130: 9; ... ; 133: 15 +load 0 >1 >11 +int 10 >1,10 >11,10 +<= >1 >0 +bz main_l3 (DON'T BRANCH) (GOTO main_l3) +int 129 >129 +load 0 >129,1 +int 1 >129,1,1 +- >129,0 +int 5 >129,0,5 +% >129,9 ++ >129 +store 128 128: 129 +byte "Current player: " +load 128 +itob +concat >"Current player: 129" +log +byte "Player score index: " +load 128 +itob +concat >"Player score index: 129" +log +load 128 >129 +load 128 >129,129 +loads >129,0 +load 0 >129,0,1 ++ >129,1 +stores 129:1 +load 0 >1 +int 1 >1,1 ++ >2 +store 0 0: 2 +b main_l1 ... now repeat until 0: 11; 128: 133 (== 129+(10-1)%5); 129: 7, 130: 9, ... , 133: 15 +main_l3: ... at the end: 0: 11; 128: 133; 129: 7, 130: 9; ... ; 133: 15 +int 1 >1 +return SUCCESS \ No newline at end of file From 21ea0269d3aef8a81ddd02bd99fda7020f2a48aa Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 14 Feb 2022 12:22:16 -0600 Subject: [PATCH 02/85] TDD. Stuck at dynamic_scratch.py: line 58 --- pyteal/ast/scratchvar.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pyteal/ast/scratchvar.py b/pyteal/ast/scratchvar.py index 23d4b1444..6c03c3fb4 100644 --- a/pyteal/ast/scratchvar.py +++ b/pyteal/ast/scratchvar.py @@ -15,8 +15,29 @@ class ScratchVar: myvar.store(Int(5)), Assert(myvar.load() == Int(5)) ]) + + Example of Dynamic Scratch space whereby the slot index is picked up from the stack: + .. code-block:: python + player_index = ScratchVar(TealType.uint64, 128) + player_score = ScratchVar(TealType.uint64, player_index.load()) + Seq( + player_index.store(Int(129)), // Wilt Chamberlain + player_score.store(Int(100)), + player_index.store(Int(130)), // Kobe Bryant + player_score.store(Int(81)), + player_index.store(Int(131)), // David Thompson + player_score.store(Int(73)), + Assert(player_score.load() == Int(73)), + Assert(player_score.index() == Int(131)), + player_score.store(player_score.load() - Int(2)), // back to Wilt: + Assert(player_score.load() == Int(100)), + Assert(player_score.index() == Int(129)), + ) """ + # TODO: In the case that a DYNAMIC scratch variable is detected, we should limit the auto-assigned slot indices to less than < 128 + # and suggest a best practice of requiring (without checking) that dynamic slot expressions should be in the range [128-255) + def __init__(self, type: TealType = TealType.anytype, slotId: int = None): """Create a new ScratchVar with an optional type. From ccd3a8b7744c8761b5678b1072ce08e8cef0aa33 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 14 Feb 2022 13:00:07 -0600 Subject: [PATCH 03/85] Next, get past dynamic_scratch.py: line 66 - need an index() --- pyteal/__init__.pyi | 245 ++++++++++++++++++++------------------- pyteal/ast/__init__.py | 221 ++++++++++++++++++----------------- pyteal/ast/scratch.py | 89 ++++++++++---- pyteal/ast/scratchvar.py | 11 +- 4 files changed, 311 insertions(+), 255 deletions(-) diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index 420f674aa..025e352bb 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -17,154 +17,155 @@ from .errors import TealInternalError, TealTypeError, TealInputError, TealCompil from .config import MAX_GROUP_SIZE, NUM_SLOTS __all__ = [ - "Expr", - "LeafExpr", + "AccountParam", + "Add", "Addr", - "Bytes", - "Int", - "EnumInt", - "MethodSignature", - "Arg", - "TxnType", - "TxnField", - "TxnExpr", - "TxnaExpr", - "TxnArray", - "TxnObject", - "Txn", - "GtxnExpr", - "GtxnaExpr", - "TxnGroup", - "Gtxn", - "GeneratedID", - "ImportScratchValue", - "Global", - "GlobalField", + "And", "App", "AppField", - "OnComplete", "AppParam", + "Approve", + "Arg", + "Array", + "Assert", "AssetHolding", "AssetParam", - "AccountParam", - "InnerTxnBuilder", - "InnerTxn", - "InnerTxnAction", - "Gitxn", - "GitxnExpr", - "GitxnaExpr", - "InnerTxnGroup", - "Array", - "Tmpl", - "Nonce", - "UnaryExpr", - "Btoi", - "Itob", - "Len", - "BitLen", - "Sha256", - "Sha512_256", - "Keccak256", - "Not", - "BitwiseNot", - "Sqrt", - "Pop", "Balance", - "MinBalance", "BinaryExpr", - "Add", - "Minus", - "Mul", - "Div", - "Mod", - "Exp", + "BitLen", "BitwiseAnd", + "BitwiseNot", "BitwiseOr", "BitwiseXor", - "ShiftLeft", - "ShiftRight", - "Eq", - "Neq", - "Lt", - "Le", - "Gt", - "Ge", - "GetBit", - "GetByte", - "Ed25519Verify", - "Substring", - "Extract", - "Suffix", - "SetBit", - "SetByte", - "NaryExpr", - "And", - "Or", - "Concat", - "WideRatio", - "If", - "Cond", - "Seq", - "Assert", - "Err", - "Return", - "Approve", - "Reject", - "Subroutine", - "SubroutineDefinition", - "SubroutineDeclaration", - "SubroutineCall", - "SubroutineFnWrapper", - "ScratchSlot", - "ScratchLoad", - "ScratchStore", - "ScratchStackStore", - "ScratchVar", - "MaybeValue", + "Break", + "Btoi", + "Bytes", "BytesAdd", - "BytesMinus", - "BytesDiv", - "BytesMul", - "BytesMod", "BytesAnd", - "BytesOr", - "BytesXor", + "BytesDiv", "BytesEq", - "BytesNeq", - "BytesLt", - "BytesLe", - "BytesGt", "BytesGe", + "BytesGt", + "BytesLe", + "BytesLt", + "BytesMinus", + "BytesMod", + "BytesMul", + "BytesNeq", "BytesNot", + "BytesOr", "BytesSqrt", + "BytesXor", "BytesZero", + "CompileOptions", + "compileTeal", + "Concat", + "Cond", + "Continue", + "DEFAULT_TEAL_VERSION", + "Div", + "DynamicSlot", + "Ed25519Verify", + "EnumInt", + "Eq", + "Err", + "Exp", + "Expr", + "Extract", "ExtractUint16", "ExtractUint32", "ExtractUint64", - "Log", - "While", "For", - "Break", - "Continue", - "Op", + "Ge", + "GeneratedID", + "GetBit", + "GetByte", + "Gitxn", + "GitxnaExpr", + "GitxnExpr", + "Global", + "GlobalField", + "Gt", + "Gtxn", + "GtxnaExpr", + "GtxnExpr", + "If", + "ImportScratchValue", + "InnerTxn", + "InnerTxnAction", + "InnerTxnBuilder", + "InnerTxnGroup", + "Int", + "Itob", + "Keccak256", + "LabelReference", + "Le", + "LeafExpr", + "Len", + "Log", + "Lt", + "MAX_GROUP_SIZE", + "MAX_TEAL_VERSION", + "MaybeValue", + "MethodSignature", + "MIN_TEAL_VERSION", + "MinBalance", + "Minus", + "Mod", "Mode", + "Mul", + "NaryExpr", + "Neq", + "Nonce", + "Not", + "NUM_SLOTS", + "OnComplete", + "Op", + "Or", + "Pop", + "Reject", + "Return", + "ScratchLoad", + "ScratchSlot", + "ScratchStackStore", + "ScratchStore", + "ScratchVar", + "Seq", + "SetBit", + "SetByte", + "Sha256", + "Sha512_256", + "ShiftLeft", + "ShiftRight", + "Sqrt", + "Subroutine", + "SubroutineCall", + "SubroutineDeclaration", + "SubroutineDefinition", + "SubroutineFnWrapper", + "Substring", + "Suffix", + "TealBlock", + "TealCompileError", "TealComponent", - "TealOp", + "TealConditionalBlock", + "TealInputError", + "TealInternalError", "TealLabel", - "TealBlock", + "TealOp", "TealSimpleBlock", - "TealConditionalBlock", - "LabelReference", - "MAX_TEAL_VERSION", - "MIN_TEAL_VERSION", - "DEFAULT_TEAL_VERSION", - "CompileOptions", - "compileTeal", "TealType", - "TealInternalError", "TealTypeError", - "TealInputError", - "TealCompileError", - "MAX_GROUP_SIZE", - "NUM_SLOTS", + "Tmpl", + "Txn", + "TxnaExpr", + "TxnArray", + "TxnExpr", + "TxnField", + "TxnGroup", + "TxnObject", + "TxnType", + "UnaryExpr", + "While", + "WideRatio", ] diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index 03cb4daeb..6dc51656f 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -120,138 +120,145 @@ # misc -from .scratch import ScratchSlot, ScratchLoad, ScratchStore, ScratchStackStore +from .scratch import ( + DynamicSlot, + ScratchSlot, + ScratchLoad, + ScratchStore, + ScratchStackStore, +) from .scratchvar import ScratchVar from .maybe import MaybeValue __all__ = [ - "Expr", - "LeafExpr", + "AccountParam", + "Add", "Addr", - "Bytes", - "Int", - "EnumInt", - "MethodSignature", - "Arg", - "TxnType", - "TxnField", - "TxnExpr", - "TxnaExpr", - "TxnArray", - "TxnObject", - "Txn", - "GtxnExpr", - "GtxnaExpr", - "TxnGroup", - "Gtxn", - "GeneratedID", - "ImportScratchValue", - "Global", - "GlobalField", + "And", "App", "AppField", - "OnComplete", "AppParam", + "Approve", + "Arg", + "Array", + "Assert", "AssetHolding", "AssetParam", - "AccountParam", - "InnerTxnBuilder", - "InnerTxn", - "InnerTxnAction", - "Gitxn", - "GitxnExpr", - "GitxnaExpr", - "InnerTxnGroup", - "Array", - "Tmpl", - "Nonce", - "UnaryExpr", - "Btoi", - "Itob", - "Len", - "BitLen", - "Sha256", - "Sha512_256", - "Keccak256", - "Not", - "BitwiseNot", - "Sqrt", - "Pop", "Balance", - "MinBalance", "BinaryExpr", - "Add", - "Minus", - "Mul", - "Div", - "Mod", - "Exp", + "BitLen", "BitwiseAnd", + "BitwiseNot", "BitwiseOr", "BitwiseXor", - "ShiftLeft", - "ShiftRight", - "Eq", - "Neq", - "Lt", - "Le", - "Gt", - "Ge", - "GetBit", - "GetByte", - "Ed25519Verify", - "Substring", - "Extract", - "Suffix", - "SetBit", - "SetByte", - "NaryExpr", - "And", - "Or", - "Concat", - "WideRatio", - "If", - "Cond", - "Seq", - "Assert", - "Err", - "Return", - "Approve", - "Reject", - "Subroutine", - "SubroutineDefinition", - "SubroutineDeclaration", - "SubroutineCall", - "SubroutineFnWrapper", - "ScratchSlot", - "ScratchLoad", - "ScratchStore", - "ScratchStackStore", - "ScratchVar", - "MaybeValue", + "Break", + "Btoi", + "Bytes", "BytesAdd", - "BytesMinus", - "BytesDiv", - "BytesMul", - "BytesMod", "BytesAnd", - "BytesOr", - "BytesXor", + "BytesDiv", "BytesEq", - "BytesNeq", - "BytesLt", - "BytesLe", - "BytesGt", "BytesGe", + "BytesGt", + "BytesLe", + "BytesLt", + "BytesMinus", + "BytesMod", + "BytesMul", + "BytesNeq", "BytesNot", + "BytesOr", "BytesSqrt", + "BytesXor", "BytesZero", + "Concat", + "Cond", + "Continue", + "Div", + "DynamicSlot", + "Ed25519Verify", + "EnumInt", + "Eq", + "Err", + "Exp", + "Expr", + "Extract", "ExtractUint16", "ExtractUint32", "ExtractUint64", + "For", + "Ge", + "GeneratedID", + "GetBit", + "GetByte", + "Gitxn", + "GitxnaExpr", + "GitxnExpr", + "Global", + "GlobalField", + "Gt", + "Gtxn", + "GtxnaExpr", + "GtxnExpr", + "If", + "ImportScratchValue", + "InnerTxn", + "InnerTxnAction", + "InnerTxnBuilder", + "InnerTxnGroup", + "Int", + "Itob", + "Keccak256", + "Le", + "LeafExpr", + "Len", "Log", + "Lt", + "MaybeValue", + "MethodSignature", + "MinBalance", + "Minus", + "Mod", + "Mul", + "NaryExpr", + "Neq", + "Nonce", + "Not", + "OnComplete", + "Or", + "Pop", + "Reject", + "Return", + "ScratchLoad", + "ScratchSlot", + "ScratchStackStore", + "ScratchStore", + "ScratchVar", + "Seq", + "SetBit", + "SetByte", + "Sha256", + "Sha512_256", + "ShiftLeft", + "ShiftRight", + "Sqrt", + "Subroutine", + "SubroutineCall", + "SubroutineDeclaration", + "SubroutineDefinition", + "SubroutineFnWrapper", + "Substring", + "Suffix", + "Tmpl", + "Txn", + "TxnaExpr", + "TxnArray", + "TxnExpr", + "TxnField", + "TxnGroup", + "TxnObject", + "TxnType", + "UnaryExpr", "While", - "For", - "Break", - "Continue", + "WideRatio", ] diff --git a/pyteal/ast/scratch.py b/pyteal/ast/scratch.py index 7efae9d5f..150fdc278 100644 --- a/pyteal/ast/scratch.py +++ b/pyteal/ast/scratch.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Union from ..types import TealType from ..config import NUM_SLOTS @@ -9,7 +9,45 @@ from ..compiler import CompileOptions -class ScratchSlot: +class Slot: + """Abstract base closs for ScratchSlot and DynamicSlot""" + + def __init__(self): + self.id: Union[int, Expr] = None + self.isReservedSlot: bool = None + + def store(self, value: Expr = None) -> Expr: + """Get an expression to store a value in this slot. + + Args: + value (optional): The value to store in this slot. If not included, the last value on + the stack will be stored. NOTE: storing the last value on the stack breaks the typical + semantics of PyTeal, only use if you know what you're doing. + """ + if value is not None: + return ScratchStore(self, value) + return ScratchStackStore(self) + + def load(self, type: TealType = TealType.anytype) -> "ScratchLoad": + """Get an expression to load a value from this slot. + + Args: + type (optional): The type being loaded from this slot, if known. Defaults to + TealType.anytype. + """ + return ScratchLoad(self, type) + + def __str__(self): + return "slot#{}".format(self.id) + + def __hash__(self): + return hash(self.id) + + +Slot.__modlue__ = "pyteal" + + +class ScratchSlot(Slot): """Represents the allocation of a scratch space slot.""" # Unique identifier for the compiler to automatically assign slots @@ -24,6 +62,8 @@ def __init__(self, requestedSlotId: int = None): requestedSlotId (optional): A scratch slot id that the compiler must store the value. This id may be a Python int in the range [0-256). """ + super().__init__() + if requestedSlotId is None: self.id = ScratchSlot.nextSlotId ScratchSlot.nextSlotId += 1 @@ -38,38 +78,39 @@ def __init__(self, requestedSlotId: int = None): self.id = requestedSlotId self.isReservedSlot = True - def store(self, value: Expr = None) -> Expr: - """Get an expression to store a value in this slot. + def __repr__(self): + return "ScratchSlot({})".format(self.id) - Args: - value (optional): The value to store in this slot. If not included, the last value on - the stack will be stored. NOTE: storing the last value on the stack breaks the typical - semantics of PyTeal, only use if you know what you're doing. - """ - if value is not None: - return ScratchStore(self, value) - return ScratchStackStore(self) - def load(self, type: TealType = TealType.anytype) -> "ScratchLoad": - """Get an expression to load a value from this slot. +ScratchSlot.__module__ = "pyteal" + + +class DynamicSlot(Slot): + """Represents the allocation of a scratch space slot.""" + + # Unique identifier for the compiler to automatically assign slots + # The id field is used by the compiler to map to an actual slot in the source code + # Slot ids under 256 are manually reserved slots + nextSlotId = NUM_SLOTS + + def __init__(self, slotExpr: Expr): + """Initializes a scratch slot with a particular id Args: - type (optional): The type being loaded from this slot, if known. Defaults to - TealType.anytype. + requestedSlotId (optional): A scratch slot id that the compiler must store the value. + This id may be a Python int in the range [0-256). """ - return ScratchLoad(self, type) + super().__init__() + + # TODO: Zeph to add assertions + self.id = slotExpr + self.isReservedSlot = False def __repr__(self): return "ScratchSlot({})".format(self.id) - def __str__(self): - return "slot#{}".format(self.id) - def __hash__(self): - return hash(self.id) - - -ScratchSlot.__module__ = "pyteal" +DynamicSlot.__module__ = "pyteal" class ScratchLoad(Expr): diff --git a/pyteal/ast/scratchvar.py b/pyteal/ast/scratchvar.py index 6c03c3fb4..e341fba8a 100644 --- a/pyteal/ast/scratchvar.py +++ b/pyteal/ast/scratchvar.py @@ -1,6 +1,6 @@ from ..types import TealType, require_type from .expr import Expr -from .scratch import ScratchSlot, ScratchLoad +from .scratch import ScratchSlot, ScratchLoad, DynamicSlot class ScratchVar: @@ -48,7 +48,14 @@ def __init__(self, type: TealType = TealType.anytype, slotId: int = None): slotId (optional): A scratch slot id that the compiler must store the value. This id may be a Python int in the range [0-256). """ - self.slot = ScratchSlot(requestedSlotId=slotId) + + # TODO: Zeph to add assertions + + self.slot = ( + ScratchSlot(requestedSlotId=slotId) + if not isinstance(slotId, Expr) + else DynamicSlot(slotId) + ) self.type = type def storage_type(self) -> TealType: From 91e10e4feeaf66a1d8266f9e0f01841e4c3b4f71 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 14 Feb 2022 13:53:48 -0600 Subject: [PATCH 04/85] compiler doesn't recognize DynamicSlot - and it shouldn't have to --- pyteal/ast/scratch.py | 11 +++++++---- pyteal/ast/scratchvar.py | 5 +++++ pyteal/compiler/compiler.py | 2 -- tests/dynamic_scratch.py | 20 ++++++++++++++++++++ 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/pyteal/ast/scratch.py b/pyteal/ast/scratch.py index 150fdc278..169545661 100644 --- a/pyteal/ast/scratch.py +++ b/pyteal/ast/scratch.py @@ -37,9 +37,6 @@ def load(self, type: TealType = TealType.anytype) -> "ScratchLoad": """ return ScratchLoad(self, type) - def __str__(self): - return "slot#{}".format(self.id) - def __hash__(self): return hash(self.id) @@ -81,6 +78,9 @@ def __init__(self, requestedSlotId: int = None): def __repr__(self): return "ScratchSlot({})".format(self.id) + def __str__(self): + return "slot#{}".format(self.id) + ScratchSlot.__module__ = "pyteal" @@ -107,7 +107,10 @@ def __init__(self, slotExpr: Expr): self.isReservedSlot = False def __repr__(self): - return "ScratchSlot({})".format(self.id) + return "DynamicSlot({})".format(self.id) + + def __str__(self): + return "dslot#{}".format(self.id) DynamicSlot.__module__ = "pyteal" diff --git a/pyteal/ast/scratchvar.py b/pyteal/ast/scratchvar.py index e341fba8a..fe578f092 100644 --- a/pyteal/ast/scratchvar.py +++ b/pyteal/ast/scratchvar.py @@ -1,5 +1,6 @@ from ..types import TealType, require_type from .expr import Expr +from .int import Int from .scratch import ScratchSlot, ScratchLoad, DynamicSlot @@ -75,5 +76,9 @@ def load(self) -> ScratchLoad: """Load value from Scratch Space""" return self.slot.load(self.type) + def index(self) -> Expr: + """TODO: Zeph - this probably doesn't work for regular ScratchSlot's without fixed int slotId""" + return self.slot.id if isinstance(self.slot, DynamicSlot) else Int(self.slot.id) + ScratchVar.__module__ = "pyteal" diff --git a/pyteal/compiler/compiler.py b/pyteal/compiler/compiler.py index dc1199e45..8e0a6b9c0 100644 --- a/pyteal/compiler/compiler.py +++ b/pyteal/compiler/compiler.py @@ -5,7 +5,6 @@ Expr, Return, Seq, - ScratchSlot, SubroutineDefinition, SubroutineDeclaration, ) @@ -16,7 +15,6 @@ from .flatten import flattenBlocks, flattenSubroutines from .scratchslots import assignScratchSlotsToSubroutines from .subroutines import ( - findRecursionPoints, spillLocalSlotsDuringRecursion, resolveSubroutines, ) diff --git a/tests/dynamic_scratch.py b/tests/dynamic_scratch.py index abef333e3..cff909cb1 100644 --- a/tests/dynamic_scratch.py +++ b/tests/dynamic_scratch.py @@ -71,9 +71,29 @@ def dynamic_scratch(): ) +def dynamic_scratch_2(): + current_player = ScratchVar(TealType.uint64) + player_score = ScratchVar(TealType.uint64, current_player.load()) + i = ScratchVar(TealType.uint64, 0) + return Seq( + current_player.store(Int(129)), + For(i.store(Int(1)), i.load() <= Int(10), i.store(i.load() + Int(1))).Do( + Seq( + current_player.store(Int(129) + (i.load() - Int(1)) % Int(5)), + Log(Concat(Bytes("Current player: "), Itob(current_player.load()))), + Log(Concat(Bytes("Player score index: "), Itob(player_score.index()))), + player_score.store(player_score.load() + i.load()), + ) + ), + Int(1), + ) + + if __name__ == "__main__": teal_dir, name, compiled = compile_and_save(dynamic_scratch) path2actual = teal_dir / (name + ".teal") path2expected = teal_dir / (name + "_expected.teal") assert_teal_as_expected(path2actual, path2expected) + + teal_dir, name, compiled = compile_and_save(dynamic_scratch_2) From 26ce3af3ce55f5a7d646c91e03315876199e2e95 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 14 Feb 2022 14:00:46 -0600 Subject: [PATCH 05/85] fixed load but store still reaches compiler --- pyteal/ast/scratch.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pyteal/ast/scratch.py b/pyteal/ast/scratch.py index 169545661..348bdb073 100644 --- a/pyteal/ast/scratch.py +++ b/pyteal/ast/scratch.py @@ -2,7 +2,7 @@ from ..types import TealType from ..config import NUM_SLOTS -from ..errors import TealInputError +from ..errors import TealInputError, TealInternalError from .expr import Expr if TYPE_CHECKING: @@ -137,8 +137,15 @@ def __str__(self): def __teal__(self, options: "CompileOptions"): from ..ir import TealOp, Op, TealBlock - op = TealOp(self, Op.load, self.slot) - return TealBlock.FromOp(options, op) + if isinstance(self.slot, ScratchSlot): + op = TealOp(self, Op.load, self.slot) + return TealBlock.FromOp(options, op) + + if not isinstance(self.slot, DynamicSlot): + raise TealInternalError("unrecognized slot type {}".format(type(self.slot))) + + op = TealOp(self, Op.loads) + return TealBlock.FromOp(options, op, self.slot.id) def type_of(self): return self.type From b395734b141b42f7a9b29769a42985c98085f3bb Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 14 Feb 2022 14:23:59 -0600 Subject: [PATCH 06/85] pass the TDD and even a more ambitions dynamic_scratch_2 case --- pyteal/ast/scratch.py | 11 ++++-- tests/dynamic_scratch.py | 31 +++++++++++----- tests/teal/dynamic_scratch_2_expected.teal | 42 ++++++++++++++++++++++ 3 files changed, 74 insertions(+), 10 deletions(-) create mode 100644 tests/teal/dynamic_scratch_2_expected.teal diff --git a/pyteal/ast/scratch.py b/pyteal/ast/scratch.py index 348bdb073..4d741a10a 100644 --- a/pyteal/ast/scratch.py +++ b/pyteal/ast/scratch.py @@ -177,8 +177,15 @@ def __str__(self): def __teal__(self, options: "CompileOptions"): from ..ir import TealOp, Op, TealBlock - op = TealOp(self, Op.store, self.slot) - return TealBlock.FromOp(options, op, self.value) + if isinstance(self.slot, ScratchSlot): + op = TealOp(self, Op.store, self.slot) + return TealBlock.FromOp(options, op, self.value) + + if not isinstance(self.slot, DynamicSlot): + raise TealInternalError("unrecognized slot type {}".format(type(self.slot))) + + op = TealOp(self, Op.stores) + return TealBlock.FromOp(options, op, self.slot.id, self.value) def type_of(self): return TealType.none diff --git a/tests/dynamic_scratch.py b/tests/dynamic_scratch.py index cff909cb1..ffa75b6b3 100644 --- a/tests/dynamic_scratch.py +++ b/tests/dynamic_scratch.py @@ -6,6 +6,9 @@ from pyteal.types import require_type +# HELPERS: + + def compile_and_save(approval): teal = Path.cwd() / "tests" / "teal" compiled = compileTeal(approval(), mode=Mode.Application, version=6) @@ -42,17 +45,32 @@ def assert_teal_as_expected(path2actual, path2expected): expected = elines[i] assert expected.startswith( actual - ), f"""ACTUAL line + ), f"""ACTUAL line in {path2actual} LINE{i+1}: {actual} {mismatch_ligature(expected, actual)} -DOES NOT prefix the EXPECTED (which should have been actual + some commentary): +DOES NOT prefix the EXPECTED (which should have been actual + some commentary) in {path2expected}: LINE{i+1}: {expected} {mismatch_ligature(expected, actual)} """ +def assert_new_v_old(dynamic_scratch): + teal_dir, name, compiled = compile_and_save(dynamic_scratch) + print( + f"""Compilation resulted in TEAL program of length {len(compiled)}. +Inspect output {name}.teal in {teal_dir}""" + ) + + path2actual = teal_dir / (name + ".teal") + path2expected = teal_dir / (name + "_expected.teal") + assert_teal_as_expected(path2actual, path2expected) + + +# PYTEALS: + + def dynamic_scratch(): current_player = ScratchVar(TealType.uint64, 128) player_score = ScratchVar(TealType.uint64, current_player.load()) @@ -74,7 +92,7 @@ def dynamic_scratch(): def dynamic_scratch_2(): current_player = ScratchVar(TealType.uint64) player_score = ScratchVar(TealType.uint64, current_player.load()) - i = ScratchVar(TealType.uint64, 0) + i = ScratchVar(TealType.uint64) return Seq( current_player.store(Int(129)), For(i.store(Int(1)), i.load() <= Int(10), i.store(i.load() + Int(1))).Do( @@ -90,10 +108,7 @@ def dynamic_scratch_2(): if __name__ == "__main__": - teal_dir, name, compiled = compile_and_save(dynamic_scratch) - path2actual = teal_dir / (name + ".teal") - path2expected = teal_dir / (name + "_expected.teal") - - assert_teal_as_expected(path2actual, path2expected) + assert_new_v_old(dynamic_scratch) + assert_new_v_old(dynamic_scratch_2) teal_dir, name, compiled = compile_and_save(dynamic_scratch_2) diff --git a/tests/teal/dynamic_scratch_2_expected.teal b/tests/teal/dynamic_scratch_2_expected.teal new file mode 100644 index 000000000..011c43267 --- /dev/null +++ b/tests/teal/dynamic_scratch_2_expected.teal @@ -0,0 +1,42 @@ +#pragma version 6 +int 129 +store 0 +int 1 +store 1 +main_l1: +load 1 +int 10 +<= +bz main_l3 +int 129 +load 1 +int 1 +- +int 5 +% ++ +store 0 +byte "Current player: " +load 0 +itob +concat +log +byte "Player score index: " +load 0 +itob +concat +log +load 0 +load 0 +loads +load 1 ++ +stores +load 1 +int 1 ++ +store 1 +b main_l1 +main_l3: +int 1 +return \ No newline at end of file From becd971dd6b26808a8a16fb8aba3413aae184f7d Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 14 Feb 2022 14:39:21 -0600 Subject: [PATCH 07/85] fix incorrect comment, and make the wilt comment into a real test --- pyteal/ast/scratch.py | 2 +- pyteal/ast/scratchvar.py | 10 +++--- tests/dynamic_scratch.py | 28 ++++++++++++++--- tests/teal/wilt_the_stilt_expected.teal | 42 +++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 10 deletions(-) create mode 100644 tests/teal/wilt_the_stilt_expected.teal diff --git a/pyteal/ast/scratch.py b/pyteal/ast/scratch.py index 4d741a10a..34b482127 100644 --- a/pyteal/ast/scratch.py +++ b/pyteal/ast/scratch.py @@ -41,7 +41,7 @@ def __hash__(self): return hash(self.id) -Slot.__modlue__ = "pyteal" +Slot.__module__ = "pyteal" class ScratchSlot(Slot): diff --git a/pyteal/ast/scratchvar.py b/pyteal/ast/scratchvar.py index fe578f092..428d2bd4e 100644 --- a/pyteal/ast/scratchvar.py +++ b/pyteal/ast/scratchvar.py @@ -19,18 +19,18 @@ class ScratchVar: Example of Dynamic Scratch space whereby the slot index is picked up from the stack: .. code-block:: python - player_index = ScratchVar(TealType.uint64, 128) + player_index = ScratchVar(TealType.uint64) player_score = ScratchVar(TealType.uint64, player_index.load()) Seq( - player_index.store(Int(129)), // Wilt Chamberlain + player_index.store(Int(129)), # Wilt Chamberlain player_score.store(Int(100)), - player_index.store(Int(130)), // Kobe Bryant + player_index.store(Int(130)), # Kobe Bryant player_score.store(Int(81)), - player_index.store(Int(131)), // David Thompson + player_index.store(Int(131)), # David Thompson player_score.store(Int(73)), Assert(player_score.load() == Int(73)), Assert(player_score.index() == Int(131)), - player_score.store(player_score.load() - Int(2)), // back to Wilt: + player_score.store(player_score.load() - Int(2)), # back to Wilt: Assert(player_score.load() == Int(100)), Assert(player_score.index() == Int(129)), ) diff --git a/tests/dynamic_scratch.py b/tests/dynamic_scratch.py index ffa75b6b3..d9e96d3c8 100644 --- a/tests/dynamic_scratch.py +++ b/tests/dynamic_scratch.py @@ -60,7 +60,8 @@ def assert_new_v_old(dynamic_scratch): teal_dir, name, compiled = compile_and_save(dynamic_scratch) print( f"""Compilation resulted in TEAL program of length {len(compiled)}. -Inspect output {name}.teal in {teal_dir}""" +To view output SEE <{name}.teal> in ({teal_dir}) +--------------""" ) path2actual = teal_dir / (name + ".teal") @@ -107,8 +108,27 @@ def dynamic_scratch_2(): ) +def wilt_the_stilt(): + player_index = ScratchVar(TealType.uint64) + player_score = ScratchVar(TealType.uint64, player_index.load()) + return Seq( + player_index.store(Int(129)), # Wilt Chamberlain + player_score.store(Int(100)), + player_index.store(Int(130)), # Kobe Bryant + player_score.store(Int(81)), + player_index.store(Int(131)), # David Thompson + player_score.store(Int(73)), + Assert(player_score.load() == Int(73)), + Assert(player_score.index() == Int(131)), + player_score.store(player_score.load() - Int(2)), # back to Wilt: + Assert(player_score.load() == Int(100)), + Assert(player_score.index() == Int(129)), + Int(100), + ) + + if __name__ == "__main__": - assert_new_v_old(dynamic_scratch) - assert_new_v_old(dynamic_scratch_2) + for pt in (dynamic_scratch, dynamic_scratch_2, wilt_the_stilt): + assert_new_v_old(pt) - teal_dir, name, compiled = compile_and_save(dynamic_scratch_2) + teal_dir, name, compiled = compile_and_save(wilt_the_stilt) diff --git a/tests/teal/wilt_the_stilt_expected.teal b/tests/teal/wilt_the_stilt_expected.teal new file mode 100644 index 000000000..62c643996 --- /dev/null +++ b/tests/teal/wilt_the_stilt_expected.teal @@ -0,0 +1,42 @@ +#pragma version 6 +int 129 +store 0 +load 0 +int 100 +stores +int 130 +store 0 +load 0 +int 81 +stores +int 131 +store 0 +load 0 +int 73 +stores +load 0 +loads +int 73 +== +assert +load 0 +int 131 +== +assert +load 0 +load 0 +loads +int 2 +- +stores +load 0 +loads +int 100 +== +assert +load 0 +int 129 +== +assert +int 100 +return \ No newline at end of file From c1aaa45535fa5ce66d1517a067fa9b7622e1a049 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 14 Feb 2022 16:17:13 -0600 Subject: [PATCH 08/85] pass mypy... but did we cast too much? --- pyteal/ast/scratch.py | 21 +++++++++++++-------- pyteal/ast/scratchvar.py | 10 ++++++++-- pyteal/compiler/scratchslots.py | 18 ++++++++++++++++-- pyteal/ir/tealblock.py | 4 ++-- 4 files changed, 39 insertions(+), 14 deletions(-) diff --git a/pyteal/ast/scratch.py b/pyteal/ast/scratch.py index 34b482127..217c64df3 100644 --- a/pyteal/ast/scratch.py +++ b/pyteal/ast/scratch.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Union +from typing import cast, TYPE_CHECKING, Union from ..types import TealType from ..config import NUM_SLOTS @@ -119,7 +119,7 @@ def __str__(self): class ScratchLoad(Expr): """Expression to load a value from scratch space.""" - def __init__(self, slot: ScratchSlot, type: TealType = TealType.anytype): + def __init__(self, slot: Slot, type: TealType = TealType.anytype): """Create a new ScratchLoad expression. Args: @@ -145,7 +145,7 @@ def __teal__(self, options: "CompileOptions"): raise TealInternalError("unrecognized slot type {}".format(type(self.slot))) op = TealOp(self, Op.loads) - return TealBlock.FromOp(options, op, self.slot.id) + return TealBlock.FromOp(options, op, cast(Expr, self.slot.id)) def type_of(self): return self.type @@ -160,7 +160,7 @@ def has_return(self): class ScratchStore(Expr): """Expression to store a value in scratch space.""" - def __init__(self, slot: ScratchSlot, value: Expr): + def __init__(self, slot: Slot, value: Expr): """Create a new ScratchStore expression. Args: @@ -185,7 +185,7 @@ def __teal__(self, options: "CompileOptions"): raise TealInternalError("unrecognized slot type {}".format(type(self.slot))) op = TealOp(self, Op.stores) - return TealBlock.FromOp(options, op, self.slot.id, self.value) + return TealBlock.FromOp(options, op, cast(Expr, self.slot.id), self.value) def type_of(self): return TealType.none @@ -204,22 +204,27 @@ class ScratchStackStore(Expr): doing. """ - def __init__(self, slot: ScratchSlot): + def __init__(self, slot: Slot): """Create a new ScratchStackStore expression. Args: slot: The slot to store the value in. """ super().__init__() + + if isinstance(slot, DynamicSlot): + raise TealInternalError( + "ScratchStackStore is not setup to handle DynamicSlot's" + ) self.slot = slot def __str__(self): - return "(StackStore {})".format(self.slot) + return "(ScratchStackStore {})".format(self.slot) def __teal__(self, options: "CompileOptions"): from ..ir import TealOp, Op, TealBlock - op = TealOp(self, Op.store, self.slot) + op = TealOp(self, Op.store, cast(ScratchSlot, self.slot)) return TealBlock.FromOp(options, op) def type_of(self): diff --git a/pyteal/ast/scratchvar.py b/pyteal/ast/scratchvar.py index 428d2bd4e..4173061fb 100644 --- a/pyteal/ast/scratchvar.py +++ b/pyteal/ast/scratchvar.py @@ -1,3 +1,5 @@ +from typing import cast + from ..types import TealType, require_type from .expr import Expr from .int import Int @@ -55,7 +57,7 @@ def __init__(self, type: TealType = TealType.anytype, slotId: int = None): self.slot = ( ScratchSlot(requestedSlotId=slotId) if not isinstance(slotId, Expr) - else DynamicSlot(slotId) + else DynamicSlot(cast(Expr, slotId)) ) self.type = type @@ -78,7 +80,11 @@ def load(self) -> ScratchLoad: def index(self) -> Expr: """TODO: Zeph - this probably doesn't work for regular ScratchSlot's without fixed int slotId""" - return self.slot.id if isinstance(self.slot, DynamicSlot) else Int(self.slot.id) + return ( + cast(Expr, self.slot.id) + if isinstance(self.slot, DynamicSlot) + else Int(cast(int, self.slot.id)) + ) ScratchVar.__module__ = "pyteal" diff --git a/pyteal/compiler/scratchslots.py b/pyteal/compiler/scratchslots.py index 04fd781dd..67aa855c0 100644 --- a/pyteal/compiler/scratchslots.py +++ b/pyteal/compiler/scratchslots.py @@ -106,7 +106,14 @@ def assignScratchSlotsToSubroutines( raise TealInternalError( "Slot ID {} has been assigned multiple times".format(slot.id) ) - slotIds.add(slot.id) + + if not isinstance(slot.id, int): + raise TealInternalError( + "slot id {} was expected to be an int but was instead of type {}".format( + slot.id, type(slot.id) + ) + ) + slotIds.add(cast(int, slot.id)) nextSlotIndex = 0 for slot in sorted(allSlots, key=lambda slot: slot.id): @@ -115,8 +122,15 @@ def assignScratchSlotsToSubroutines( nextSlotIndex += 1 if slot.isReservedSlot: + if not isinstance(slot.id, int): + raise TealInternalError( + "slot id {} was expected to be an int but was instead of type {}".format( + slot.id, type(slot.id) + ) + ) + # Slot ids under 256 are manually reserved slots - slotAssignments[slot] = slot.id + slotAssignments[slot] = cast(int, slot.id) else: slotAssignments[slot] = nextSlotIndex slotIds.add(nextSlotIndex) diff --git a/pyteal/ir/tealblock.py b/pyteal/ir/tealblock.py index 27b8d778a..b35dadff9 100644 --- a/pyteal/ir/tealblock.py +++ b/pyteal/ir/tealblock.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Optional, List, Tuple, Set, Iterator, cast, TYPE_CHECKING +from typing import Union, List, Tuple, Set, Iterator, cast, TYPE_CHECKING from .tealop import TealOp, Op from ..errors import TealCompileError @@ -85,7 +85,7 @@ def addIncoming( def validateSlots( self, slotsInUse: Set["ScratchSlot"] = None, - visited: Set[Tuple[int, ...]] = None, + visited: Set[Tuple[Union[int, Expr], ...]] = None, ) -> List[TealCompileError]: if visited is None: visited = set() From 8e9659e3cc9f3d69ad0ac65a174a13847e6dd0e5 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 14 Feb 2022 17:37:32 -0600 Subject: [PATCH 09/85] more assertive and precise type casting --- pyteal/ast/scratch.py | 16 ++++++++++------ pyteal/compiler/scratchslots.py | 4 ++-- pyteal/ir/tealblock.py | 17 ++++++++++++++--- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/pyteal/ast/scratch.py b/pyteal/ast/scratch.py index 217c64df3..54edc9c99 100644 --- a/pyteal/ast/scratch.py +++ b/pyteal/ast/scratch.py @@ -26,7 +26,15 @@ def store(self, value: Expr = None) -> Expr: """ if value is not None: return ScratchStore(self, value) - return ScratchStackStore(self) + + if not isinstance(self, ScratchSlot): + raise TealInternalError( + "ScratchStackStore is only setup to handle ScratchSlot's but was about to supply type {}".format( + type(self) + ) + ) + + return ScratchStackStore(cast(ScratchSlot, self)) def load(self, type: TealType = TealType.anytype) -> "ScratchLoad": """Get an expression to load a value from this slot. @@ -204,7 +212,7 @@ class ScratchStackStore(Expr): doing. """ - def __init__(self, slot: Slot): + def __init__(self, slot: ScratchSlot): """Create a new ScratchStackStore expression. Args: @@ -212,10 +220,6 @@ def __init__(self, slot: Slot): """ super().__init__() - if isinstance(slot, DynamicSlot): - raise TealInternalError( - "ScratchStackStore is not setup to handle DynamicSlot's" - ) self.slot = slot def __str__(self): diff --git a/pyteal/compiler/scratchslots.py b/pyteal/compiler/scratchslots.py index 67aa855c0..7f519e702 100644 --- a/pyteal/compiler/scratchslots.py +++ b/pyteal/compiler/scratchslots.py @@ -113,7 +113,7 @@ def assignScratchSlotsToSubroutines( slot.id, type(slot.id) ) ) - slotIds.add(cast(int, slot.id)) + slotIds.add(slot.id) nextSlotIndex = 0 for slot in sorted(allSlots, key=lambda slot: slot.id): @@ -130,7 +130,7 @@ def assignScratchSlotsToSubroutines( ) # Slot ids under 256 are manually reserved slots - slotAssignments[slot] = cast(int, slot.id) + slotAssignments[slot] = slot.id else: slotAssignments[slot] = nextSlotIndex slotIds.add(nextSlotIndex) diff --git a/pyteal/ir/tealblock.py b/pyteal/ir/tealblock.py index b35dadff9..836b3ebe1 100644 --- a/pyteal/ir/tealblock.py +++ b/pyteal/ir/tealblock.py @@ -2,7 +2,7 @@ from typing import Union, List, Tuple, Set, Iterator, cast, TYPE_CHECKING from .tealop import TealOp, Op -from ..errors import TealCompileError +from ..errors import TealCompileError, TealInputError, TealInternalError if TYPE_CHECKING: from ..ast import Expr, ScratchSlot @@ -82,10 +82,18 @@ def addIncoming( for b in self.getOutgoing(): b.addIncoming(self, visited) + @classmethod + def force_cast_int(cls, si: object, msg: str) -> int: + if not isinstance(si, int): + raise TealInternalError( + "expected {} of type {} to be an int: {}".format(si, type(si), msg) + ) + return cast(int, si) + def validateSlots( self, slotsInUse: Set["ScratchSlot"] = None, - visited: Set[Tuple[Union[int, Expr], ...]] = None, + visited: Set[Tuple[int, ...]] = None, ) -> List[TealCompileError]: if visited is None: visited = set() @@ -110,7 +118,10 @@ def validateSlots( errors.append(e) if not self.isTerminal(): - sortedSlots = sorted(slot.id for slot in currentSlotsInUse) + sortedSlots = sorted( + self.force_cast_int(slot.id, "compiled slots should have int id's") + for slot in currentSlotsInUse + ) for block in self.getOutgoing(): visitedKey = (id(block), *sortedSlots) if visitedKey in visited: From 126ac76bcef9d541b86c89e1fa7a201a236aa3f9 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 14 Feb 2022 17:52:44 -0600 Subject: [PATCH 10/85] new e2e circle test --- tests/compile_asserts.py | 64 ++++++++++++++ ...mic_scratch.py => dynamic_scratch_test.py} | 86 +++---------------- 2 files changed, 78 insertions(+), 72 deletions(-) create mode 100644 tests/compile_asserts.py rename tests/{dynamic_scratch.py => dynamic_scratch_test.py} (53%) diff --git a/tests/compile_asserts.py b/tests/compile_asserts.py new file mode 100644 index 000000000..d4df719e5 --- /dev/null +++ b/tests/compile_asserts.py @@ -0,0 +1,64 @@ +from pathlib import Path + +from pyteal.compiler import compileTeal +from pyteal.ir import Mode + + +def compile_and_save(approval): + teal = Path.cwd() / "tests" / "teal" + compiled = compileTeal(approval(), mode=Mode.Application, version=6) + name = approval.__name__ + with open(teal / (name + ".teal"), "w") as f: + f.write(compiled) + return teal, name, compiled + + +def mismatch_ligature(expected, actual): + la, le = len(actual), len(expected) + mm_idx = -1 + for i in range(min(la, le)): + if expected[i] != actual[i]: + mm_idx = i + break + if mm_idx < 0: + return "" + return " " * (mm_idx) + "X" + "-" * (max(la, le) - mm_idx - 1) + + +def assert_teal_as_expected(path2actual, path2expected): + + with open(path2actual, "r") as fa, open(path2expected, "r") as fe: + alines = fa.read().split("\n") + elines = fe.read().split("\n") + + assert len(elines) == len( + alines + ), f"""EXPECTED {len(elines)} lines for {path2expected} +but ACTUALLY got {len(alines)} lines in {path2actual}""" + + for i, actual in enumerate(alines): + expected = elines[i] + assert expected.startswith( + actual + ), f"""ACTUAL line in {path2actual} +LINE{i+1}: +{actual} +{mismatch_ligature(expected, actual)} +DOES NOT prefix the EXPECTED (which should have been actual + some commentary) in {path2expected}: +LINE{i+1}: +{expected} +{mismatch_ligature(expected, actual)} +""" + + +def assert_new_v_old(dynamic_scratch): + teal_dir, name, compiled = compile_and_save(dynamic_scratch) + print( + f"""Compilation resulted in TEAL program of length {len(compiled)}. +To view output SEE <{name}.teal> in ({teal_dir}) +--------------""" + ) + + path2actual = teal_dir / (name + ".teal") + path2expected = teal_dir / (name + "_expected.teal") + assert_teal_as_expected(path2actual, path2expected) diff --git a/tests/dynamic_scratch.py b/tests/dynamic_scratch_test.py similarity index 53% rename from tests/dynamic_scratch.py rename to tests/dynamic_scratch_test.py index d9e96d3c8..927aaeba8 100644 --- a/tests/dynamic_scratch.py +++ b/tests/dynamic_scratch_test.py @@ -1,75 +1,6 @@ -from ast import Sub -from cmath import exp -from pathlib import Path - from pyteal import * -from pyteal.types import require_type - - -# HELPERS: - - -def compile_and_save(approval): - teal = Path.cwd() / "tests" / "teal" - compiled = compileTeal(approval(), mode=Mode.Application, version=6) - name = approval.__name__ - with open(teal / (name + ".teal"), "w") as f: - f.write(compiled) - return teal, name, compiled - - -def mismatch_ligature(expected, actual): - la, le = len(actual), len(expected) - mm_idx = -1 - for i in range(min(la, le)): - if expected[i] != actual[i]: - mm_idx = i - break - if mm_idx < 0: - return "" - return " " * (mm_idx) + "X" + "-" * (max(la, le) - mm_idx - 1) - -def assert_teal_as_expected(path2actual, path2expected): - - with open(path2actual, "r") as fa, open(path2expected, "r") as fe: - alines = fa.read().split("\n") - elines = fe.read().split("\n") - - assert len(elines) == len( - alines - ), f"""EXPECTED {len(elines)} lines for {path2expected} -but ACTUALLY got {len(alines)} lines in {path2actual}""" - - for i, actual in enumerate(alines): - expected = elines[i] - assert expected.startswith( - actual - ), f"""ACTUAL line in {path2actual} -LINE{i+1}: -{actual} -{mismatch_ligature(expected, actual)} -DOES NOT prefix the EXPECTED (which should have been actual + some commentary) in {path2expected}: -LINE{i+1}: -{expected} -{mismatch_ligature(expected, actual)} -""" - - -def assert_new_v_old(dynamic_scratch): - teal_dir, name, compiled = compile_and_save(dynamic_scratch) - print( - f"""Compilation resulted in TEAL program of length {len(compiled)}. -To view output SEE <{name}.teal> in ({teal_dir}) ---------------""" - ) - - path2actual = teal_dir / (name + ".teal") - path2expected = teal_dir / (name + "_expected.teal") - assert_teal_as_expected(path2actual, path2expected) - - -# PYTEALS: +from .compile_asserts import assert_new_v_old, compile_and_save def dynamic_scratch(): @@ -127,8 +58,19 @@ def wilt_the_stilt(): ) -if __name__ == "__main__": - for pt in (dynamic_scratch, dynamic_scratch_2, wilt_the_stilt): +TEST_CASES = (dynamic_scratch, dynamic_scratch_2, wilt_the_stilt) + + +def test_all(): + for pt in TEST_CASES: assert_new_v_old(pt) + +def test_new(): teal_dir, name, compiled = compile_and_save(wilt_the_stilt) + print( + f"""Successfuly tested approval program {name} having +compiled it into {len(compiled)} characters. See the results in: +{teal_dir} +""" + ) From 1159ba62c3eed03322152f2e993cee7a545d6c0b Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 14 Feb 2022 19:41:28 -0600 Subject: [PATCH 11/85] ready for review... but it's also time for unit tests --- tests/dynamic_scratch_test.py | 135 ++++++++++++++- tests/teal/subroutines_expected.teal | 244 +++++++++++++++++++++++++++ 2 files changed, 376 insertions(+), 3 deletions(-) create mode 100644 tests/teal/subroutines_expected.teal diff --git a/tests/dynamic_scratch_test.py b/tests/dynamic_scratch_test.py index 927aaeba8..a582f3b6e 100644 --- a/tests/dynamic_scratch_test.py +++ b/tests/dynamic_scratch_test.py @@ -58,7 +58,136 @@ def wilt_the_stilt(): ) -TEST_CASES = (dynamic_scratch, dynamic_scratch_2, wilt_the_stilt) +@Subroutine(TealType.bytes) +def logcat(some_bytes, an_int): + catted = ScratchVar(TealType.bytes) + return Seq( + catted.store(Concat(some_bytes, Itob(an_int))), + Log(catted.load()), + catted.load(), + ) + + +@Subroutine(TealType.bytes) +def logcat_dynamic(some_bytes, an_int): + catted = ScratchVar(TealType.bytes, Int(42)) + return Seq( + catted.store(Concat(some_bytes, Itob(an_int))), + Log(catted.load()), + catted.load(), + ) + + +@Subroutine(TealType.uint64) +def slow_fibonacci(n): + return ( + If(n <= Int(1)) + .Then(n) + .Else(slow_fibonacci(n - Int(2)) + slow_fibonacci(n - Int(1))) + ) + + +@Subroutine(TealType.uint64) +def fast_fibonacci(n): + i = ScratchVar(TealType.uint64) + a = ScratchVar(TealType.uint64) + b = ScratchVar(TealType.uint64) + return Seq( + a.store(Int(0)), + b.store(Int(1)), + For(i.store(Int(1)), i.load() <= n, i.store(i.load() + Int(1))).Do( + Seq( + b.store(a.load() + b.load()), + a.store(b.load() - a.load()), + ) + ), + a.load(), + ) + + +@Subroutine(TealType.uint64) +def fast_fibonacci_mixed(n): + i = ScratchVar(TealType.uint64, Int(42)) + a = ScratchVar( + TealType.uint64, Int(42) * Int(1337) + ) # yes, this is too big - but the compiler can't figure that out + b = ScratchVar(TealType.uint64) + return Seq( + a.store(Int(0)), + b.store(Int(1)), + For(i.store(Int(1)), i.load() <= n, i.store(i.load() + Int(1))).Do( + Seq( + b.store(a.load() + b.load()), + a.store(b.load() - a.load()), + ) + ), + a.load(), + ) + + +def subroutines(): + selected = ScratchVar(TealType.bytes, Int(1337)) + hello = ScratchVar( + TealType.bytes, + ) + hello_dyn = ScratchVar(TealType.bytes, Int(747)) + x_reg = ScratchVar(TealType.uint64) + x_dyn = ScratchVar(TealType.uint64, Int(777)) + return Seq( + hello.store(Bytes("hello there")), + hello_dyn.store(Bytes("goodbye")), + x_reg.store(Int(1_000_000)), + x_dyn.store(Int(1_000_000_000)), + selected.store(Bytes("fast_fibonacci_mixed")), + If(selected.load() == Bytes("logcat")) + .Then( + Seq( + Pop(logcat(hello.load(), Int(17))), + Int(100), + ) + ) + .ElseIf( + Concat(Bytes("logcat"), Bytes("_"), Bytes("dynamic")) == selected.load() + ) + .Then( + Seq( + Pop(logcat_dynamic(Bytes("yo"), Int(117))), + Pop(logcat_dynamic(hello.load(), x_reg.load())), + Pop(logcat_dynamic(hello_dyn.load(), x_dyn.load())), + Int(101), + ) + ) + .ElseIf(selected.load() == Bytes("slow_fibonacci")) + .Then( + Seq( + Pop(slow_fibonacci(Int(217))), + Pop(slow_fibonacci(x_reg.load())), + slow_fibonacci(x_dyn.load()), + ) + ) + .ElseIf(selected.load() == Bytes("fast_fibonacci")) + .Then( + Seq( + Pop(fast_fibonacci(Int(317))), + Pop(fast_fibonacci(x_reg.load())), + fast_fibonacci(x_dyn.load()), + ) + ) + .ElseIf(selected.load() == Bytes("fast_fibonacci_mixed")) + .Then( + Seq( + Pop(fast_fibonacci_mixed(Int(417))), + Pop(fast_fibonacci_mixed(x_reg.load())), + fast_fibonacci_mixed(x_dyn.load()), + ) + ) + .Else( + Err(), + ), + ) + + +TEST_CASES = (dynamic_scratch, dynamic_scratch_2, wilt_the_stilt, subroutines) def test_all(): @@ -66,8 +195,8 @@ def test_all(): assert_new_v_old(pt) -def test_new(): - teal_dir, name, compiled = compile_and_save(wilt_the_stilt) +def test_generate_another(): + teal_dir, name, compiled = compile_and_save(subroutines) print( f"""Successfuly tested approval program {name} having compiled it into {len(compiled)} characters. See the results in: diff --git a/tests/teal/subroutines_expected.teal b/tests/teal/subroutines_expected.teal new file mode 100644 index 000000000..a7994d28d --- /dev/null +++ b/tests/teal/subroutines_expected.teal @@ -0,0 +1,244 @@ +#pragma version 6 +byte "hello there" +store 0 +int 747 +byte "goodbye" +stores // yep. 747: "goodbye" +int 1000000 +store 1 +int 777 +int 1000000000 +stores // yep. 777: 1_000_000_000 +int 1337 +byte "fast_fibonacci_mixed" +stores // yep. 1337: "fast_fibonacci_mixed" +int 1337 +loads +byte "logcat" +== +bnz main_l10 +byte "logcat" +byte "_" +concat +byte "dynamic" +concat +int 1337 +loads +== +bnz main_l9 +int 1337 +loads +byte "slow_fibonacci" +== +bnz main_l8 +int 1337 +loads +byte "fast_fibonacci" +== +bnz main_l7 +int 1337 +loads +byte "fast_fibonacci_mixed" +== +bnz main_l6 +err +main_l6: +int 417 +callsub fastfibonaccimixed_4 +pop +load 1 +callsub fastfibonaccimixed_4 +pop +int 777 +loads +callsub fastfibonaccimixed_4 +b main_l11 +main_l7: +int 317 +callsub fastfibonacci_3 +pop +load 1 +callsub fastfibonacci_3 +pop +int 777 +loads +callsub fastfibonacci_3 +b main_l11 +main_l8: +int 217 +callsub slowfibonacci_2 +pop +load 1 +callsub slowfibonacci_2 +pop +int 777 +loads +callsub slowfibonacci_2 +b main_l11 +main_l9: +byte "yo" +int 117 +callsub logcatdynamic_1 +pop +load 0 +load 1 +callsub logcatdynamic_1 +pop +int 747 +loads +int 777 +loads +callsub logcatdynamic_1 +pop +int 101 +b main_l11 +main_l10: +load 0 +int 17 +callsub logcat_0 +pop +int 100 +main_l11: +return + +// logcat +logcat_0: +store 3 +store 2 +load 2 +load 3 +itob +concat +store 4 +load 4 +log +load 4 +retsub + +// logcat_dynamic +logcatdynamic_1: +store 6 +store 5 +int 42 +load 5 +load 6 +itob +concat +stores +int 42 +loads +log +int 42 +loads +retsub + +// slow_fibonacci +slowfibonacci_2: +store 7 +load 7 +int 1 >>> n,1 +<= +bnz slowfibonacci_2_l2 +load 7 +int 2 +- +load 7 +swap +callsub slowfibonacci_2 +swap +store 7 +load 7 +int 1 +- +load 7 +swap +callsub slowfibonacci_2 +swap +store 7 ++ +b slowfibonacci_2_l3 +slowfibonacci_2_l2: +load 7 +slowfibonacci_2_l3: +retsub + +// fast_fibonacci +fastfibonacci_3: +store 8 +int 0 +store 10 +int 1 +store 11 +int 1 +store 9 +fastfibonacci_3_l1: +load 9 +load 8 >>> 1,n +<= +bz fastfibonacci_3_l3 +load 10 +load 11 ++ +store 11 +load 11 +load 10 +- +store 10 +load 9 +int 1 ++ +store 9 +b fastfibonacci_3_l1 +fastfibonacci_3_l3: +load 10 +retsub + +// fast_fibonacci_mixed +fastfibonaccimixed_4: +store 12 +int 42 +int 1337 // yep too large. 42*1337: 0 +* +int 0 +stores +int 1 +store 13 +int 42 +int 1 +stores +fastfibonaccimixed_4_l1: +int 42 +loads +load 12 +<= +bz fastfibonaccimixed_4_l3 +int 42 +int 1337 +* +loads +load 13 ++ +store 13 +int 42 +int 1337 +* +load 13 +int 42 +int 1337 +* +loads +- +stores +int 42 +int 42 +loads +int 1 ++ +stores +b fastfibonaccimixed_4_l1 +fastfibonaccimixed_4_l3: +int 42 +int 1337 +* +loads +retsub \ No newline at end of file From 0e5bcccc5e78a817780cc5bbbc0480fd5363cb49 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 14 Feb 2022 20:16:06 -0600 Subject: [PATCH 12/85] fix test logic error, plus better comments in wilt TEAL --- tests/dynamic_scratch_test.py | 2 +- tests/teal/wilt_the_stilt_expected.teal | 30 ++++++++++++------------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/tests/dynamic_scratch_test.py b/tests/dynamic_scratch_test.py index a582f3b6e..9c3711f2d 100644 --- a/tests/dynamic_scratch_test.py +++ b/tests/dynamic_scratch_test.py @@ -51,7 +51,7 @@ def wilt_the_stilt(): player_score.store(Int(73)), Assert(player_score.load() == Int(73)), Assert(player_score.index() == Int(131)), - player_score.store(player_score.load() - Int(2)), # back to Wilt: + player_index.store(player_index.load() - Int(2)), # back to Wilt: Assert(player_score.load() == Int(100)), Assert(player_score.index() == Int(129)), Int(100), diff --git a/tests/teal/wilt_the_stilt_expected.teal b/tests/teal/wilt_the_stilt_expected.teal index 62c643996..4a9796a05 100644 --- a/tests/teal/wilt_the_stilt_expected.teal +++ b/tests/teal/wilt_the_stilt_expected.teal @@ -1,19 +1,19 @@ #pragma version 6 int 129 -store 0 +store 0 // set the roaming index variable to 129, aka Wilt load 0 -int 100 -stores -int 130 -store 0 +int 100 // give Wilt 100 points +stores // Done with Wilt +int 130 +store 0 // set the roaming index variable to 130, aka Kobe load 0 int 81 -stores -int 131 -store 0 +stores // Done with Kobe +int 131 +store 0 // set the roaming index variable to 131, David Thompson, aka D.T. load 0 int 73 -stores +stores // Done with D.T. load 0 loads int 73 @@ -22,16 +22,14 @@ assert load 0 int 131 == -assert -load 0 +assert // asserting that player_score.Index() == Int(131) load 0 -loads int 2 - -stores -load 0 -loads -int 100 +store 0 // now the player index should be 129 aka Wilt's score +load 0 +loads // Wilt's score +int 100 == assert load 0 From a0a351df5ee42797c280263f1f4f97c4a03b5452 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 14 Feb 2022 20:31:47 -0600 Subject: [PATCH 13/85] unnecessary todo - given successful e2e tests --- pyteal/ast/scratchvar.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyteal/ast/scratchvar.py b/pyteal/ast/scratchvar.py index 4173061fb..a083f5f24 100644 --- a/pyteal/ast/scratchvar.py +++ b/pyteal/ast/scratchvar.py @@ -79,7 +79,6 @@ def load(self) -> ScratchLoad: return self.slot.load(self.type) def index(self) -> Expr: - """TODO: Zeph - this probably doesn't work for regular ScratchSlot's without fixed int slotId""" return ( cast(Expr, self.slot.id) if isinstance(self.slot, DynamicSlot) From e6b9f0bf2db27c77f8b04769b5e1391539e28768 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 14 Feb 2022 22:41:18 -0600 Subject: [PATCH 14/85] TDD: Only type Expr is allowed - so need to expand the subroutine param types --- tests/pass_by_ref_test.py | 45 ++++++++++++++++++++++++++++++++ tests/teal/swapper_expected.teal | 37 ++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 tests/pass_by_ref_test.py create mode 100644 tests/teal/swapper_expected.teal diff --git a/tests/pass_by_ref_test.py b/tests/pass_by_ref_test.py new file mode 100644 index 000000000..d81d63aab --- /dev/null +++ b/tests/pass_by_ref_test.py @@ -0,0 +1,45 @@ +from pyteal import * + +from .compile_asserts import assert_new_v_old, compile_and_save + + +@Subroutine(TealType.none) +def swap(x: ScratchVar, y: ScratchVar): + z = ScratchVar(TealType.anytype) + return Seq( + z.store(x.load()), + x.store(y.load()), + y.store(z.load()), + Int(101), + ) + + +def swapper(): + a = ScratchVar(TealType.bytes) + b = ScratchVar(TealType.bytes) + return Seq( + a.store(Bytes("hello")), + b.store(Bytes("goodbye")), + Pop(swap(a, b)), + Assert(a.load() == Bytes("goodbye")), + Assert(b.load() == Bytes("hello")), + Int(1000), + ) + + +TEST_CASES = (swapper,) + + +def test_all(): + for pt in TEST_CASES: + assert_new_v_old(pt) + + +def test_generate_another(): + teal_dir, name, compiled = compile_and_save(swapper) + print( + f"""Successfuly tested approval program {name} having +compiled it into {len(compiled)} characters. See the results in: +{teal_dir} +""" + ) diff --git a/tests/teal/swapper_expected.teal b/tests/teal/swapper_expected.teal new file mode 100644 index 000000000..97eb5f6db --- /dev/null +++ b/tests/teal/swapper_expected.teal @@ -0,0 +1,37 @@ +#pragma version 6 +byte "hello" +store 0 0: hello // x +byte "goodbye" +store 1 1: goodbye // y +int 0 +int 1 +callsub swap_0 >0,1 +pop <> +int 0 +loads >goodbye +byte "goodbye" +== +assert <> +int 1 +loads >hello +byte "goodbye" +== +assert <> +int 1000 >1000 + +// swap +swap_0: +store 3 3: 1 // y +store 2 2: 0 // x +load 2 >0 +loads >hello +store 4 4: hello +load 2 >0 +load 3 >0,1 +loads >0,goodbye +stores 0: goodbye +load 3 >1 +load 4 >1,hello +stores 1: hello +int 101 >101 +retsub \ No newline at end of file From c35f017756def4af2e09addb3c8d6da5fb5e4616 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 14 Feb 2022 23:31:22 -0600 Subject: [PATCH 15/85] got through a couple more exceptions hurdles --- pyteal/ast/subroutine.py | 51 ++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 0bf688d28..2ac46fdad 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -1,5 +1,5 @@ -from typing import Callable, List, Optional, TYPE_CHECKING -from inspect import Parameter, signature +from typing import Callable, List, Optional, Union, TYPE_CHECKING +from inspect import isclass, Parameter, signature from ..types import TealType from ..ir import TealOp, Op, TealBlock @@ -13,6 +13,7 @@ class SubroutineDefinition: + PARAM_TYPES = (Expr, ScratchVar) nextSubroutineId = 0 @@ -50,12 +51,19 @@ def __init__( ) for var, var_type in implementation.__annotations__.items(): - if var_type is not Expr: - stub = "Return" if var == "return" else ("parameter " + var) + if var == "return" and not ( + isclass(var_type) and issubclass(var_type, Expr) + ): + raise TealInputError( + "Function has return of disallowed type {}. Only subtype of Expr is allowed".format( + var_type + ) + ) + if var != "return" and var_type not in self.PARAM_TYPES: raise TealInputError( - "Function has {} of disallowed type {}. Only type Expr is allowed".format( - stub, var_type + "Function has parameter {} of disallowed type {}. Only the types {} are allowed".format( + var, var_type, self.param_type_names() ) ) @@ -87,10 +95,10 @@ def invoke(self, args: List[Expr]) -> "SubroutineCall": ) for i, arg in enumerate(args): - if not isinstance(arg, Expr): + if not isinstance(arg, self.PARAM_TYPES): raise TealInputError( - "Argument at index {} of subroutine call is not a PyTeal expression: {}".format( - i, arg + "Argument {} at index {} of subroutine call has incorrect type {}".format( + i, arg, type(arg) ) ) @@ -136,16 +144,33 @@ def has_return(self): class SubroutineCall(Expr): - def __init__(self, subroutine: SubroutineDefinition, args: List[Expr]) -> None: + def __init__( + self, subroutine: SubroutineDefinition, args: List[Union[Expr, ScratchVar]] + ) -> None: super().__init__() self.subroutine = subroutine self.args = args for i, arg in enumerate(args): - if arg.type_of() == TealType.none: + arg_type = None + + if not isinstance(arg, (Expr, ScratchVar)): + raise TealInputError( + "Subroutine argument {} at index {} was of unexpected Python type {}".format( + arg, i, type(arg) + ) + ) + + if isinstance(arg, Expr): + arg_type = arg.type_of() + else: + assert isinstance(arg, ScratchVar) + arg_type = arg.type + + if arg_type == TealType.none: raise TealInputError( - "Subroutine argument at index {} evaluates to TealType.none".format( - i + "Subroutine argument {} at index {} evaluates to TealType.none".format( + arg, i ) ) From 1d365f36c891edbaf685148c823a751c17eb254a Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 14 Feb 2022 23:59:20 -0600 Subject: [PATCH 16/85] almost ready to load the scratchvar's --- pyteal/ast/subroutine.py | 6 +----- tests/pass_by_ref_test.py | 7 +++++-- tests/teal/swapper_expected.teal | 2 -- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 2ac46fdad..cd635bd54 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -161,11 +161,7 @@ def __init__( ) ) - if isinstance(arg, Expr): - arg_type = arg.type_of() - else: - assert isinstance(arg, ScratchVar) - arg_type = arg.type + arg_type = arg.type_of() if isinstance(arg, Expr) else arg.type if arg_type == TealType.none: raise TealInputError( diff --git a/tests/pass_by_ref_test.py b/tests/pass_by_ref_test.py index d81d63aab..8bf09c909 100644 --- a/tests/pass_by_ref_test.py +++ b/tests/pass_by_ref_test.py @@ -10,7 +10,6 @@ def swap(x: ScratchVar, y: ScratchVar): z.store(x.load()), x.store(y.load()), y.store(z.load()), - Int(101), ) @@ -20,7 +19,7 @@ def swapper(): return Seq( a.store(Bytes("hello")), b.store(Bytes("goodbye")), - Pop(swap(a, b)), + swap(a, b), Assert(a.load() == Bytes("goodbye")), Assert(b.load() == Bytes("hello")), Int(1000), @@ -43,3 +42,7 @@ def test_generate_another(): {teal_dir} """ ) + + +if __name__ == "__main__": + test_generate_another() diff --git a/tests/teal/swapper_expected.teal b/tests/teal/swapper_expected.teal index 97eb5f6db..c683cda05 100644 --- a/tests/teal/swapper_expected.teal +++ b/tests/teal/swapper_expected.teal @@ -6,7 +6,6 @@ store 1 1: goodbye // y int 0 int 1 callsub swap_0 >0,1 -pop <> int 0 loads >goodbye byte "goodbye" @@ -33,5 +32,4 @@ stores 0: goodbye load 3 >1 load 4 >1,hello stores 1: hello -int 101 >101 retsub \ No newline at end of file From e94e9cde5c823d2eb8169a32a5ec0020cd0d5e40 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 15 Feb 2022 21:36:18 -0600 Subject: [PATCH 17/85] commit before selectively rolling back --- pyteal/__init__.pyi | 1 + pyteal/ast/__init__.py | 3 ++- pyteal/ast/scratch.py | 39 +++++++++++++++++++++++++++ pyteal/ast/scratchvar.py | 39 +++++++++++++++++++++++---- pyteal/ast/subroutine.py | 42 +++++++++++++++++++++++++---- tests/compile_asserts.py | 24 ++++++++++------- tests/dynamic_scratch_test.py | 10 +++++-- tests/pass_by_ref_test.py | 29 ++++++++++++++++++-- tests/teal/fac_by_ref_expected.teal | 5 ++++ tests/teal/swapper_expected.teal | 9 +++---- 10 files changed, 171 insertions(+), 30 deletions(-) create mode 100644 tests/teal/fac_by_ref_expected.teal diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index 025e352bb..7a4594eae 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -122,6 +122,7 @@ __all__ = [ "OnComplete", "Op", "Or", + "PassByRefScratchVar", "Pop", "Reject", "Return", diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index 6dc51656f..e081195b2 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -127,7 +127,7 @@ ScratchStore, ScratchStackStore, ) -from .scratchvar import ScratchVar +from .scratchvar import PassByRefScratchVar, ScratchVar from .maybe import MaybeValue __all__ = [ @@ -226,6 +226,7 @@ "Not", "OnComplete", "Or", + "PassByRefScratchVar", "Pop", "Reject", "Return", diff --git a/pyteal/ast/scratch.py b/pyteal/ast/scratch.py index 54edc9c99..8a528aec4 100644 --- a/pyteal/ast/scratch.py +++ b/pyteal/ast/scratch.py @@ -45,6 +45,9 @@ def load(self, type: TealType = TealType.anytype) -> "ScratchLoad": """ return ScratchLoad(self, type) + def index(self) -> "ScratchIndex": + return ScratchIndex(self) + def __hash__(self): return hash(self.id) @@ -124,6 +127,42 @@ def __str__(self): DynamicSlot.__module__ = "pyteal" +class ScratchIndex(Expr): + def __init__(self, slot: Slot): + super().__init__() + self.slot = slot + + def __str__(self): + return "(ScratchIndex {})".format(self.slot) + + def __hash__(self): + return hash(self.slot.id) + + def type_of(self): + return TealType.uint64 + + def has_return(self): + return False + + def __teal__(self, options: "CompileOptions"): + from ..ir import TealOp, Op, TealBlock + + op = TealOp(self, Op.int, self.slot) + return TealBlock.FromOp(options, op) + + # if isinstance(self.slot, ScratchSlot): + # op = TealOp(self, Op.int, self.slot) + # return TealBlock.FromOp(options, op) + + # if not isinstance(self.slot, DynamicSlot): + # raise TealInternalError("unrecognized slot type {}".format(type(self.slot))) + + # return self.slot.id + + +ScratchIndex.__module__ = "pyteal" + + class ScratchLoad(Expr): """Expression to load a value from scratch space.""" diff --git a/pyteal/ast/scratchvar.py b/pyteal/ast/scratchvar.py index a083f5f24..76793c582 100644 --- a/pyteal/ast/scratchvar.py +++ b/pyteal/ast/scratchvar.py @@ -1,3 +1,4 @@ +from operator import index from typing import cast from ..types import TealType, require_type @@ -79,11 +80,39 @@ def load(self) -> ScratchLoad: return self.slot.load(self.type) def index(self) -> Expr: - return ( - cast(Expr, self.slot.id) - if isinstance(self.slot, DynamicSlot) - else Int(cast(int, self.slot.id)) - ) + return self.slot.index() + # return ( + # cast(Expr, self.slot.id) + # if isinstance(self.slot, DynamicSlot) + # else Int(cast(int, self.slot.id)) + # ) ScratchVar.__module__ = "pyteal" + + +class PassByRefScratchVar(ScratchVar): + def __init__(self, sv: ScratchVar, index_for_store: bool = False): + # self.ref: ScratchVar = ScratchVar(sv.type, sv.index()) + self.ref: ScratchVar = sv + self.slot = self.ref.slot + self.index_for_store = index_for_store + + def store(self, value: Expr) -> Expr: + # if self.index_for_store: + # return self.ref.index() + + dynsv = ScratchVar(TealType.uint64, self.ref.load()) + return dynsv.store(value) + + def load(self) -> ScratchLoad: + # if self.index_for_store: + # return self.ref.index() + dynsv = ScratchVar(TealType.uint64, self.ref.load()) + return dynsv.load() + + def index(self) -> Expr: + return self.ref.index() + + +PassByRefScratchVar.__module__ = "pyteal" diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index cd635bd54..a35377cc5 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -1,4 +1,4 @@ -from typing import Callable, List, Optional, Union, TYPE_CHECKING +from typing import Callable, List, Optional, Set, Union, TYPE_CHECKING from inspect import isclass, Parameter, signature from ..types import TealType @@ -6,7 +6,7 @@ from ..errors import TealInputError, verifyTealVersion from .expr import Expr from .seq import Seq -from .scratchvar import ScratchVar +from .scratchvar import PassByRefScratchVar, ScratchVar if TYPE_CHECKING: from ..compiler import CompileOptions @@ -27,6 +27,8 @@ def __init__( self.id = SubroutineDefinition.nextSubroutineId SubroutineDefinition.nextSubroutineId += 1 + self.by_ref_args: Set[str] = set() + if not callable(implementation): raise TealInputError("Input to SubroutineDefinition is not callable") @@ -66,6 +68,8 @@ def __init__( var, var_type, self.param_type_names() ) ) + if var_type is ScratchVar: + self.by_ref_args.add(var) self.implementation = implementation self.implementationParams = sig.parameters @@ -178,7 +182,19 @@ def __teal__(self, options: "CompileOptions"): ) op = TealOp(self, Op.callsub, self.subroutine) - return TealBlock.FromOp(options, op, *self.args) + + def handle_arg(arg): + if isinstance(arg, PassByRefScratchVar): + raise "shouldn't handle PassByRefScratchVar over here" + # return arg.index() + + if isinstance(arg, ScratchVar): + return arg.load() + + return arg + + arg_handler = map(handle_arg, self.args) + return TealBlock.FromOp(options, op, *arg_handler) def __str__(self): ret_str = '(SubroutineCall "' + self.subroutine.name() + '" (' @@ -269,8 +285,22 @@ def __call__(self, fnImplementation: Callable[..., Expr]) -> SubroutineFnWrapper def evaluateSubroutine(subroutine: SubroutineDefinition) -> SubroutineDeclaration: - argumentVars = [ScratchVar() for _ in range(subroutine.argumentCount())] - loadedArgs = [var.load() for var in argumentVars] + argumentVars = [] + loadedArgs = [] + + for arg in subroutine.implementationParams.keys(): + new_sv = ScratchVar() + if arg in subroutine.by_ref_args: + argVar = PassByRefScratchVar(new_sv, index_for_store=True) + # argVar = new_sv + # argVar.store(new_sv.index()) + # loadedArgs.append(new_sv) + loadedArgs.append(PassByRefScratchVar(new_sv)) # , index_for_store=True)) + else: + argVar = new_sv + loadedArgs.append(new_sv.load()) + + argumentVars.append(argVar) subroutineBody = subroutine.implementation(*loadedArgs) @@ -281,6 +311,8 @@ def evaluateSubroutine(subroutine: SubroutineDefinition) -> SubroutineDeclaratio ) ) + # supp the r'th variable is byref + # then argumentVars[r] needs to have _the index_ of the # need to reverse order of argumentVars because the last argument will be on top of the stack bodyOps = [var.slot.store() for var in argumentVars[::-1]] bodyOps.append(subroutineBody) diff --git a/tests/compile_asserts.py b/tests/compile_asserts.py index d4df719e5..28696f4de 100644 --- a/tests/compile_asserts.py +++ b/tests/compile_asserts.py @@ -51,14 +51,18 @@ def assert_teal_as_expected(path2actual, path2expected): """ -def assert_new_v_old(dynamic_scratch): - teal_dir, name, compiled = compile_and_save(dynamic_scratch) - print( - f"""Compilation resulted in TEAL program of length {len(compiled)}. -To view output SEE <{name}.teal> in ({teal_dir}) ---------------""" - ) +def assert_new_v_old(approve_func): + try: + teal_dir, name, compiled = compile_and_save(approve_func) - path2actual = teal_dir / (name + ".teal") - path2expected = teal_dir / (name + "_expected.teal") - assert_teal_as_expected(path2actual, path2expected) + print( + f"""Compilation resulted in TEAL program of length {len(compiled)}. + To view output SEE <{name}.teal> in ({teal_dir}) + --------------""" + ) + + path2actual = teal_dir / (name + ".teal") + path2expected = teal_dir / (name + "_expected.teal") + assert_teal_as_expected(path2actual, path2expected) + except Exception as e: + assert not e, f"failed to ASSERT NEW v OLD for {approve_func.__name__}: {e}" diff --git a/tests/dynamic_scratch_test.py b/tests/dynamic_scratch_test.py index 9c3711f2d..1059edc2d 100644 --- a/tests/dynamic_scratch_test.py +++ b/tests/dynamic_scratch_test.py @@ -187,7 +187,9 @@ def subroutines(): ) -TEST_CASES = (dynamic_scratch, dynamic_scratch_2, wilt_the_stilt, subroutines) +# TEST_CASES = (dynamic_scratch, dynamic_scratch_2, wilt_the_stilt, subroutines) + +TEST_CASES = (wilt_the_stilt,) def test_all(): @@ -198,8 +200,12 @@ def test_all(): def test_generate_another(): teal_dir, name, compiled = compile_and_save(subroutines) print( - f"""Successfuly tested approval program {name} having + f"""Successfuly tested approval program <<{name}>> having compiled it into {len(compiled)} characters. See the results in: {teal_dir} """ ) + + +if __name__ == "__main__": + test_all() diff --git a/tests/pass_by_ref_test.py b/tests/pass_by_ref_test.py index 8bf09c909..fd58e46fe 100644 --- a/tests/pass_by_ref_test.py +++ b/tests/pass_by_ref_test.py @@ -1,6 +1,6 @@ from pyteal import * -from .compile_asserts import assert_new_v_old, compile_and_save +from compile_asserts import assert_new_v_old, compile_and_save @Subroutine(TealType.none) @@ -26,6 +26,31 @@ def swapper(): ) +@Subroutine(TealType.none) +def factorial(n: ScratchVar): + tmp = ScratchVar(TealType.uint64) + return ( + If(n.load() <= 1) + .Then(n.store(Int(1))) + .Else( + Seq( + tmp.store(n.load() - Int(1)), + factorial(tmp), + n.store(n * tmp.load()), + ) + ) + ) + + +def fac_by_ref(): + n = ScratchVar(TealType.uint64) + return Seq( + n.store(Int(42)), + factorial(n), + n.load(), + ) + + TEST_CASES = (swapper,) @@ -37,7 +62,7 @@ def test_all(): def test_generate_another(): teal_dir, name, compiled = compile_and_save(swapper) print( - f"""Successfuly tested approval program {name} having + f"""Successfuly tested approval program <<{name}>> having compiled it into {len(compiled)} characters. See the results in: {teal_dir} """ diff --git a/tests/teal/fac_by_ref_expected.teal b/tests/teal/fac_by_ref_expected.teal new file mode 100644 index 000000000..a405663fa --- /dev/null +++ b/tests/teal/fac_by_ref_expected.teal @@ -0,0 +1,5 @@ +#pragma version 6 +int 42 +store 0 +int 0 + diff --git a/tests/teal/swapper_expected.teal b/tests/teal/swapper_expected.teal index c683cda05..c5c197269 100644 --- a/tests/teal/swapper_expected.teal +++ b/tests/teal/swapper_expected.teal @@ -6,17 +6,16 @@ store 1 1: goodbye // y int 0 int 1 callsub swap_0 >0,1 -int 0 -loads >goodbye +load 0 >goodbye byte "goodbye" == assert <> -int 1 -loads >hello -byte "goodbye" +load 1 >hello +byte "hello" == assert <> int 1000 >1000 +return // swap swap_0: From 07a98e01229feccd1bd2a3211fe0d867cac5953d Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Wed, 16 Feb 2022 19:14:49 -0600 Subject: [PATCH 18/85] a bunch of comments --- pyteal/ast/scratchvar.py | 14 +---- pyteal/ast/subroutine.py | 89 +++++++++++++++++++---------- tests/compile_asserts.py | 6 ++ tests/pass_by_ref_test.py | 27 ++++----- tests/teal/fac_by_ref_expected.teal | 42 +++++++++++++- tests/teal/swapper_expected.teal | 13 +++++ 6 files changed, 133 insertions(+), 58 deletions(-) diff --git a/pyteal/ast/scratchvar.py b/pyteal/ast/scratchvar.py index 76793c582..66568d9ab 100644 --- a/pyteal/ast/scratchvar.py +++ b/pyteal/ast/scratchvar.py @@ -81,33 +81,21 @@ def load(self) -> ScratchLoad: def index(self) -> Expr: return self.slot.index() - # return ( - # cast(Expr, self.slot.id) - # if isinstance(self.slot, DynamicSlot) - # else Int(cast(int, self.slot.id)) - # ) ScratchVar.__module__ = "pyteal" class PassByRefScratchVar(ScratchVar): - def __init__(self, sv: ScratchVar, index_for_store: bool = False): - # self.ref: ScratchVar = ScratchVar(sv.type, sv.index()) + def __init__(self, sv: ScratchVar): self.ref: ScratchVar = sv self.slot = self.ref.slot - self.index_for_store = index_for_store def store(self, value: Expr) -> Expr: - # if self.index_for_store: - # return self.ref.index() - dynsv = ScratchVar(TealType.uint64, self.ref.load()) return dynsv.store(value) def load(self) -> ScratchLoad: - # if self.index_for_store: - # return self.ref.index() dynsv = ScratchVar(TealType.uint64, self.ref.load()) return dynsv.load() diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index a35377cc5..775364c23 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -90,6 +90,9 @@ def name(self) -> str: def argumentCount(self) -> int: return len(self.implementationParams) + def arguments(self) -> List[str]: + return list(self.implementationParams.keys()) + def invoke(self, args: List[Expr]) -> "SubroutineCall": if len(args) != self.argumentCount(): raise TealInputError( @@ -175,6 +178,17 @@ def __init__( ) def __teal__(self, options: "CompileOptions"): + """ + Generate the subroutine's start and end teal blocks. + The subroutine's arguments put on the stack to be picked up into local scratch variables. + There are 2 cases for the arg expression put on the stack: + + 1. (by-value) In the case of typical arguments of type Expr, the expression ITSELF is evaluated for the stack + and will be stored in a local ScratchVar when beginning the subroutine execution + + 2. (by-reference) In the case of a by-reference argument of type ScratchVar, its SLOT INDEX is put on the stack + and will be stored in a local PassByRefScratchVar when beginning the subroutine execution + """ verifyTealVersion( Op.callsub.min_version, options.version, @@ -183,18 +197,10 @@ def __teal__(self, options: "CompileOptions"): op = TealOp(self, Op.callsub, self.subroutine) - def handle_arg(arg): - if isinstance(arg, PassByRefScratchVar): - raise "shouldn't handle PassByRefScratchVar over here" - # return arg.index() - - if isinstance(arg, ScratchVar): - return arg.load() + def handle(arg): + return arg.index() if isinstance(arg, ScratchVar) else arg - return arg - - arg_handler = map(handle_arg, self.args) - return TealBlock.FromOp(options, op, *arg_handler) + return TealBlock.FromOp(options, op, *(handle(x) for x in self.args)) def __str__(self): ret_str = '(SubroutineCall "' + self.subroutine.name() + '" (' @@ -285,23 +291,49 @@ def __call__(self, fnImplementation: Callable[..., Expr]) -> SubroutineFnWrapper def evaluateSubroutine(subroutine: SubroutineDefinition) -> SubroutineDeclaration: - argumentVars = [] - loadedArgs = [] - - for arg in subroutine.implementationParams.keys(): - new_sv = ScratchVar() - if arg in subroutine.by_ref_args: - argVar = PassByRefScratchVar(new_sv, index_for_store=True) - # argVar = new_sv - # argVar.store(new_sv.index()) - # loadedArgs.append(new_sv) - loadedArgs.append(PassByRefScratchVar(new_sv)) # , index_for_store=True)) - else: - argVar = new_sv - loadedArgs.append(new_sv.load()) - - argumentVars.append(argVar) + """ + Puts together the data necessary to define the code for a subroutine. + "evaluate" is used here to connote evaluating the PyTEAL AST into a SubroutineDeclaration, + but not actually placing it at call locations. The trickiest part here is managing the subroutine's arguments. + The arguments are needed in two different ways, and there are 2 different argument types to consider: + + 2 Argument Usages + - -------- ------ + Usage (A) for run-time: "argumentVars" --reverse--> "bodyOps" + These are "store" expressions that pick up parameters that have been pre-placed on the stack prior to subroutine invocation. + These are stored into local scratch space to be used by the TEAL subroutine. + + Usage (B) for compile-time: "loadedArgs" + These are expressions supplied to the user-defined PyTEAL function. + These are invoked to by the subroutine create a self-contained AST which will then define the TEAL subroutine. + + In both usage cases, we need to handle + + 2 Argument Types + - -------- ----- + Type 1 (by-value): these have python type Expr + Type 2 (by-reference): these hae python type ScratchVar + + Usage (A) "argumentVars" --reverse--> "bodyOps" for storing pre-placed stack variables into local scratch space: + Type 1. (by-value) use ScratchVar.store() to pick the actual value into a local scratch space + Type 2. (by-reference) ALSO use ScratchVar.store() to pick up from the stack + NOTE: SubroutineCall.__teal__() has placed the _SLOT INDEX_ on the stack so this is stored into the local scratch space + + Usage (B) "loadedArgs" - Storing pre-placed stack variables into local scratch-variables: + Type 1. (by-value) use ScratchVar.load() to have an Expr that can be compiled in python by the PyTEAL subroutine + Type 2. (by-reference) use a PassByRefScratchVar as the user will have written the PyTEAL in a way that satifies + the ScratchVar API. I.e., the user will write `x.load()` instead of `x` as they would have for by-value variables. + """ + + def loadedArg(argVar, param): + if param in subroutine.by_ref_args: + return PassByRefScratchVar(argVar) + return argVar.load() + + argumentVars = [ScratchVar() for _ in range(subroutine.argumentCount())] + loadedArgs = [loadedArg(a, p) for a, p in zip(argumentVars, subroutine.arguments())] + # Arg usage "B" supplied to build an AST from the user-defined PyTEAL function: subroutineBody = subroutine.implementation(*loadedArgs) if not isinstance(subroutineBody, Expr): @@ -311,8 +343,7 @@ def evaluateSubroutine(subroutine: SubroutineDefinition) -> SubroutineDeclaratio ) ) - # supp the r'th variable is byref - # then argumentVars[r] needs to have _the index_ of the + # Arg usage "A" to be pick up and store in scratch parameters that have been placed on the stack # need to reverse order of argumentVars because the last argument will be on top of the stack bodyOps = [var.slot.store() for var in argumentVars[::-1]] bodyOps.append(subroutineBody) diff --git a/tests/compile_asserts.py b/tests/compile_asserts.py index 28696f4de..e178697e1 100644 --- a/tests/compile_asserts.py +++ b/tests/compile_asserts.py @@ -10,6 +10,12 @@ def compile_and_save(approval): name = approval.__name__ with open(teal / (name + ".teal"), "w") as f: f.write(compiled) + print( + f"""Successfuly tested approval program <<{name}>> having +compiled it into {len(compiled)} characters. See the results in: +{teal} +""" + ) return teal, name, compiled diff --git a/tests/pass_by_ref_test.py b/tests/pass_by_ref_test.py index fd58e46fe..37943ac6f 100644 --- a/tests/pass_by_ref_test.py +++ b/tests/pass_by_ref_test.py @@ -13,12 +13,18 @@ def swap(x: ScratchVar, y: ScratchVar): ) +@Subroutine(TealType.none) +def cat(x, y): + return Pop(Concat(x, y)) + + def swapper(): a = ScratchVar(TealType.bytes) b = ScratchVar(TealType.bytes) return Seq( a.store(Bytes("hello")), b.store(Bytes("goodbye")), + cat(a.load(), b.load()), swap(a, b), Assert(a.load() == Bytes("goodbye")), Assert(b.load() == Bytes("hello")), @@ -30,13 +36,13 @@ def swapper(): def factorial(n: ScratchVar): tmp = ScratchVar(TealType.uint64) return ( - If(n.load() <= 1) + If(n.load() <= Int(1)) .Then(n.store(Int(1))) .Else( Seq( tmp.store(n.load() - Int(1)), factorial(tmp), - n.store(n * tmp.load()), + n.store(n.load() * tmp.load()), ) ) ) @@ -54,20 +60,15 @@ def fac_by_ref(): TEST_CASES = (swapper,) +def test_swapper(): + compile_and_save(swapper) + + def test_all(): for pt in TEST_CASES: assert_new_v_old(pt) -def test_generate_another(): - teal_dir, name, compiled = compile_and_save(swapper) - print( - f"""Successfuly tested approval program <<{name}>> having -compiled it into {len(compiled)} characters. See the results in: -{teal_dir} -""" - ) - - if __name__ == "__main__": - test_generate_another() + test_swapper() + test_all() diff --git a/tests/teal/fac_by_ref_expected.teal b/tests/teal/fac_by_ref_expected.teal index a405663fa..d51621c2c 100644 --- a/tests/teal/fac_by_ref_expected.teal +++ b/tests/teal/fac_by_ref_expected.teal @@ -1,5 +1,41 @@ #pragma version 6 -int 42 -store 0 -int 0 +int 4 +store 0 0: 4 +int 0 >0 +callsub factorial_0 +load 0 +return +// factorial +factorial_0: >0 >0,3,2 >0,3,2,2,2 >0,3,2,2,2,1,2 +store 1 1: 0 1: 2 1: 2 1: 2 +load 1 >0 >0,3,2 >0,3,2,2,2 >0,3,2,2,2,1,2,1 +loads >4 >0,3,3 >0,3,2,2,2 >0,3,2,2,2,1,2,2 +int 1 >4,1 >0,3,3,1 >0,3,2,2,2,1 >0,3,2,2,2,1,2,1 +<= >0 >0,3,0 >0,3,2,2,0 >0,3,2,2,2,1,2,1,1 +bnz factorial_0_l2 >0,3 >0,3,2,2 >0,3,2,2,2,1,2,1 +load 1 >0 >0,3,2 >0,3,2,2,2 +loads >4 >0,3,3 >0,3,2,2,2 +int 1 >0,3,3,1 >0,3,2,2,2,1 +- >0,3,2 >0,3,2,2,1 +store 2 2: 3 2: 2 2: 1 +int 2 >2 >0,3,2 >0,3,2,2,2 +load 1 >2,0 >0,3,2,2 >0,3,2,2,2,2 +load 2 >2,0,3 >0,3,2,2,2 >0,3,2,2,2,2,1 +uncover 2 >0,3,2 >0,3,2,2,2 >0,3,2,2,2,1,2 +callsub factorial_0 >0,3,2,2,2,1,2,1,2,1 >0,3,2,2,2,1,2,1 +store 2 2: 1 2: 1 +store 1 1: 2 1: 2 +load 1 >0,3,2,2,2,1,2,1,2 >0,3,2,2,2,1,2 +load 1 >0,3,2,2,2,1,2,1,2,2 >0,3,2,2,2,1,2,2 +loads >0,3,2,2,2,1,2,1,2,1 >0,3,2,2,2,1,2,1 +load 2 >0,3,2,2,2,1,2,1,2,1,1 +* >0,3,2,2,2,1,2,1 +stores 2:1 +b factorial_0_l3: +factorial_0_l2: >0,3,2,2,2,1,2,1 +load 1 >0,3,2,2,2,1,2,1,2 +int 1 >0,3,2,2,2,1,2,1,2,1 +stores 2: 1 +factorial_0_l3: +retsub \ No newline at end of file diff --git a/tests/teal/swapper_expected.teal b/tests/teal/swapper_expected.teal index c5c197269..9ee39e388 100644 --- a/tests/teal/swapper_expected.teal +++ b/tests/teal/swapper_expected.teal @@ -3,6 +3,9 @@ byte "hello" store 0 0: hello // x byte "goodbye" store 1 1: goodbye // y +load 0 +load 1 +callsub cat_1 int 0 int 1 callsub swap_0 >0,1 @@ -31,4 +34,14 @@ stores 0: goodbye load 3 >1 load 4 >1,hello stores 1: hello +retsub + +// cat +cat_1: +store 6 +store 5 +load 5 +load 6 +concat +pop retsub \ No newline at end of file From 4fa0bfd7d15ed043cebea4c8c60e1a2d5da26763 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Wed, 16 Feb 2022 19:27:33 -0600 Subject: [PATCH 19/85] final touches before pause --- pyteal/ast/scratch.py | 9 --------- pyteal/ast/subroutine.py | 7 +++---- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/pyteal/ast/scratch.py b/pyteal/ast/scratch.py index 8a528aec4..f0c2b315a 100644 --- a/pyteal/ast/scratch.py +++ b/pyteal/ast/scratch.py @@ -150,15 +150,6 @@ def __teal__(self, options: "CompileOptions"): op = TealOp(self, Op.int, self.slot) return TealBlock.FromOp(options, op) - # if isinstance(self.slot, ScratchSlot): - # op = TealOp(self, Op.int, self.slot) - # return TealBlock.FromOp(options, op) - - # if not isinstance(self.slot, DynamicSlot): - # raise TealInternalError("unrecognized slot type {}".format(type(self.slot))) - - # return self.slot.id - ScratchIndex.__module__ = "pyteal" diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 775364c23..25232b368 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -195,12 +195,11 @@ def __teal__(self, options: "CompileOptions"): "TEAL version too low to use SubroutineCall expression", ) - op = TealOp(self, Op.callsub, self.subroutine) - - def handle(arg): + def handle_arg(arg): return arg.index() if isinstance(arg, ScratchVar) else arg - return TealBlock.FromOp(options, op, *(handle(x) for x in self.args)) + op = TealOp(self, Op.callsub, self.subroutine) + return TealBlock.FromOp(options, op, *(handle_arg(x) for x in self.args)) def __str__(self): ret_str = '(SubroutineCall "' + self.subroutine.name() + '" (' From 968b9b2466fefba30064c0780798c7222ad7e0c2 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 11:23:44 -0600 Subject: [PATCH 20/85] good place to commit - working... but need to refactor --- pyteal/__init__.pyi | 2 +- pyteal/ast/__init__.py | 4 +- pyteal/ast/scratchvar.py | 99 +++++++++++++++++-------- pyteal/ast/subroutine.py | 16 ++-- tests/pass_by_ref_test.py | 25 ++++++- tests/teal/swapper_expected.teal | 56 +++++++------- tests/teal/wilt_the_stilt_expected.teal | 52 +++++++------ 7 files changed, 160 insertions(+), 94 deletions(-) diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index 7a4594eae..48f2dca67 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -122,7 +122,7 @@ __all__ = [ "OnComplete", "Op", "Or", - "PassByRefScratchVar", + "DynamicScratchVar", "Pop", "Reject", "Return", diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index e081195b2..35ad8f762 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -127,7 +127,7 @@ ScratchStore, ScratchStackStore, ) -from .scratchvar import PassByRefScratchVar, ScratchVar +from .scratchvar import DynamicScratchVar, ScratchVar from .maybe import MaybeValue __all__ = [ @@ -226,7 +226,7 @@ "Not", "OnComplete", "Or", - "PassByRefScratchVar", + "DynamicScratchVar", "Pop", "Reject", "Return", diff --git a/pyteal/ast/scratchvar.py b/pyteal/ast/scratchvar.py index 66568d9ab..87664f4f3 100644 --- a/pyteal/ast/scratchvar.py +++ b/pyteal/ast/scratchvar.py @@ -1,7 +1,9 @@ from operator import index from typing import cast +from ..errors import TealInputError from ..types import TealType, require_type + from .expr import Expr from .int import Int from .scratch import ScratchSlot, ScratchLoad, DynamicSlot @@ -19,29 +21,8 @@ class ScratchVar: myvar.store(Int(5)), Assert(myvar.load() == Int(5)) ]) - - Example of Dynamic Scratch space whereby the slot index is picked up from the stack: - .. code-block:: python - player_index = ScratchVar(TealType.uint64) - player_score = ScratchVar(TealType.uint64, player_index.load()) - Seq( - player_index.store(Int(129)), # Wilt Chamberlain - player_score.store(Int(100)), - player_index.store(Int(130)), # Kobe Bryant - player_score.store(Int(81)), - player_index.store(Int(131)), # David Thompson - player_score.store(Int(73)), - Assert(player_score.load() == Int(73)), - Assert(player_score.index() == Int(131)), - player_score.store(player_score.load() - Int(2)), # back to Wilt: - Assert(player_score.load() == Int(100)), - Assert(player_score.index() == Int(129)), - ) """ - # TODO: In the case that a DYNAMIC scratch variable is detected, we should limit the auto-assigned slot indices to less than < 128 - # and suggest a best practice of requiring (without checking) that dynamic slot expressions should be in the range [128-255) - def __init__(self, type: TealType = TealType.anytype, slotId: int = None): """Create a new ScratchVar with an optional type. @@ -86,21 +67,81 @@ def index(self) -> Expr: ScratchVar.__module__ = "pyteal" -class PassByRefScratchVar(ScratchVar): - def __init__(self, sv: ScratchVar): - self.ref: ScratchVar = sv - self.slot = self.ref.slot +class DynamicScratchVar: + """ + Example of Dynamic Scratch space whereby the slot index is picked up from the stack: + .. code-block:: python + player_index = ScratchVar(TealType.uint64) + player_score = ScratchVar(TealType.uint64, player_index.load()) + Seq( + player_index.store(Int(129)), # Wilt Chamberlain + player_score.store(Int(100)), + player_index.store(Int(130)), # Kobe Bryant + player_score.store(Int(81)), + player_index.store(Int(131)), # David Thompson + player_score.store(Int(73)), + Assert(player_score.load() == Int(73)), + Assert(player_score.index() == Int(131)), + player_score.store(player_score.load() - Int(2)), # back to Wilt: + Assert(player_score.load() == Int(100)), + Assert(player_score.index() == Int(129)), + ) + """ + + def __init__( + self, + ttype: TealType = TealType.anytype, + indexer: ScratchVar = None, + # slotId: int = None, + ): + # if (indexer is None) == (slotId is None): + # raise TealInputError( + # "must either provide an indexer or slotId and not both" + # ) + + self.type = ttype + if indexer is None: + indexer = ScratchVar(TealType.uint64) + # indexer.store(Int(slotId)) + + self.indexer: ScratchVar + self.slot: ScratchSlot + self._set_indexer(indexer) + + def _set_indexer(self, indexer: ScratchVar) -> None: + if not isinstance(indexer, ScratchVar): + raise TealInputError( + "indexer must be a ScratchVar but had python type {}".format( + type(indexer) + ) + ) + + if indexer.type != TealType.uint64: + raise TealInputError( + "indexer must have teal type uint64 but was {} instead".format( + indexer.type + ) + ) + + self.indexer = indexer + self.slot = self.indexer.slot + + def set_index(self, indexVar: ScratchVar) -> Expr: + return self.indexer.store(indexVar.index()) def store(self, value: Expr) -> Expr: - dynsv = ScratchVar(TealType.uint64, self.ref.load()) + dynsv = ScratchVar(TealType.uint64, self.indexer.load()) return dynsv.store(value) def load(self) -> ScratchLoad: - dynsv = ScratchVar(TealType.uint64, self.ref.load()) + dynsv = ScratchVar(TealType.uint64, self.indexer.load()) return dynsv.load() def index(self) -> Expr: - return self.ref.index() + return self.indexer.load() + + def internal_index(self) -> Expr: + return self.indexer.index() -PassByRefScratchVar.__module__ = "pyteal" +DynamicScratchVar.__module__ = "pyteal" diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 25232b368..21a4f603a 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -6,7 +6,7 @@ from ..errors import TealInputError, verifyTealVersion from .expr import Expr from .seq import Seq -from .scratchvar import PassByRefScratchVar, ScratchVar +from .scratchvar import DynamicScratchVar, ScratchVar if TYPE_CHECKING: from ..compiler import CompileOptions @@ -324,13 +324,17 @@ def evaluateSubroutine(subroutine: SubroutineDefinition) -> SubroutineDeclaratio the ScratchVar API. I.e., the user will write `x.load()` instead of `x` as they would have for by-value variables. """ - def loadedArg(argVar, param): + def var_n_loaded(param): if param in subroutine.by_ref_args: - return PassByRefScratchVar(argVar) - return argVar.load() + argVar = ScratchVar(TealType.uint64) + loaded = DynamicScratchVar(indexer=argVar) + else: + argVar = ScratchVar(TealType.anytype) + loaded = argVar.load() - argumentVars = [ScratchVar() for _ in range(subroutine.argumentCount())] - loadedArgs = [loadedArg(a, p) for a, p in zip(argumentVars, subroutine.arguments())] + return argVar, loaded + + argumentVars, loadedArgs = zip(*map(var_n_loaded, subroutine.arguments())) # Arg usage "B" supplied to build an AST from the user-defined PyTEAL function: subroutineBody = subroutine.implementation(*loadedArgs) diff --git a/tests/pass_by_ref_test.py b/tests/pass_by_ref_test.py index 37943ac6f..f7e73b7af 100644 --- a/tests/pass_by_ref_test.py +++ b/tests/pass_by_ref_test.py @@ -3,6 +3,29 @@ from compile_asserts import assert_new_v_old, compile_and_save +def wilt_the_stilt(): + player_score = DynamicScratchVar(TealType.uint64) + + wilt = ScratchVar(TealType.uint64, 129) + kobe = ScratchVar(TealType.uint64) + dt = ScratchVar(TealType.uint64, 131) + + return Seq( + player_score.set_index(wilt), + player_score.store(Int(100)), + player_score.set_index(kobe), + player_score.store(Int(81)), + player_score.set_index(dt), + player_score.store(Int(73)), + Assert(player_score.load() == Int(73)), + Assert(player_score.index() == Int(131)), + player_score.set_index(wilt), + Assert(player_score.load() == Int(100)), + Assert(player_score.index() == Int(129)), + Int(100), + ) + + @Subroutine(TealType.none) def swap(x: ScratchVar, y: ScratchVar): z = ScratchVar(TealType.anytype) @@ -57,7 +80,7 @@ def fac_by_ref(): ) -TEST_CASES = (swapper,) +TEST_CASES = (swapper, wilt_the_stilt) def test_swapper(): diff --git a/tests/teal/swapper_expected.teal b/tests/teal/swapper_expected.teal index 9ee39e388..cd690b1ff 100644 --- a/tests/teal/swapper_expected.teal +++ b/tests/teal/swapper_expected.teal @@ -1,47 +1,47 @@ #pragma version 6 byte "hello" -store 0 0: hello // x +store 5 5: hello // x byte "goodbye" -store 1 1: goodbye // y -load 0 -load 1 -callsub cat_1 -int 0 -int 1 -callsub swap_0 >0,1 -load 0 >goodbye +store 6 6: goodbye // y +load 5 +load 6 +callsub cat_1 <> +int 5 +int 6 +callsub swap_0 >5,6 +load 5 >goodbye byte "goodbye" == assert <> -load 1 >hello +load 6 >hello byte "hello" == assert <> int 1000 >1000 -return +return <> // swap swap_0: -store 3 3: 1 // y -store 2 2: 0 // x -load 2 >0 +store 1 1: 6 // @y +store 0 0: 5 // @x +load 0 >5 loads >hello -store 4 4: hello -load 2 >0 -load 3 >0,1 -loads >0,goodbye -stores 0: goodbye -load 3 >1 -load 4 >1,hello -stores 1: hello +store 2 2: hello // z +load 0 >5 +load 1 >5,6 +loads >5,goodbye +stores 5: goodbye +load 1 >6 +load 2 >6,hello +stores 6: hello retsub // cat cat_1: -store 6 -store 5 -load 5 -load 6 -concat -pop +store 4 4: goodbye +store 3 3: hello +load 3 >hello +load 4 >hello,goodbye +concat >hellogoodbye +pop > retsub \ No newline at end of file diff --git a/tests/teal/wilt_the_stilt_expected.teal b/tests/teal/wilt_the_stilt_expected.teal index 4a9796a05..053289e7f 100644 --- a/tests/teal/wilt_the_stilt_expected.teal +++ b/tests/teal/wilt_the_stilt_expected.teal @@ -1,40 +1,38 @@ #pragma version 6 int 129 -store 0 // set the roaming index variable to 129, aka Wilt +store 0 0: @129 // pointer to wilt's address load 0 -int 100 // give Wilt 100 points -stores // Done with Wilt -int 130 -store 0 // set the roaming index variable to 130, aka Kobe +int 100 +stores 129: 100 // set wilt's value +int 1 +store 0 0: @1 // pointer to kobe's address (compiler assigned) load 0 int 81 -stores // Done with Kobe -int 131 -store 0 // set the roaming index variable to 131, David Thompson, aka D.T. +stores 1: 81 // set kobe's value +int 131 +store 0 0: @131 // pointer to dt's address load 0 int 73 -stores // Done with D.T. +stores 131: 73 // set dt's value load 0 loads -int 73 -== -assert -load 0 -int 131 -== -assert // asserting that player_score.Index() == Int(131) -load 0 -int 2 -- -store 0 // now the player index should be 129 aka Wilt's score +int 73 >73,73 +== >1 +assert <> load 0 -loads // Wilt's score -int 100 -== -assert -load 0 +int 131 >131,131 +== >1 +assert <> int 129 -== -assert +store 0 0: @129 +load 0 +loads +int 100 >100,100 +== >1 +assert <> +load 0 +int 129 >129,129 +== >1 +assert <> int 100 return \ No newline at end of file From ca22c639c828ccae2528c1fc3637d2fe082846e1 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 11:30:15 -0600 Subject: [PATCH 21/85] cleanup --- pyteal/ast/scratchvar.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/pyteal/ast/scratchvar.py b/pyteal/ast/scratchvar.py index 87664f4f3..7530a65ae 100644 --- a/pyteal/ast/scratchvar.py +++ b/pyteal/ast/scratchvar.py @@ -70,21 +70,27 @@ def index(self) -> Expr: class DynamicScratchVar: """ Example of Dynamic Scratch space whereby the slot index is picked up from the stack: - .. code-block:: python - player_index = ScratchVar(TealType.uint64) - player_score = ScratchVar(TealType.uint64, player_index.load()) - Seq( - player_index.store(Int(129)), # Wilt Chamberlain + .. code-block:: python1 + + player_score = DynamicScratchVar(TealType.uint64) + + wilt = ScratchVar(TealType.uint64, 129) + kobe = ScratchVar(TealType.uint64) + dt = ScratchVar(TealType.uint64, 131) + + seq = Seq( + player_score.set_index(wilt), player_score.store(Int(100)), - player_index.store(Int(130)), # Kobe Bryant + player_score.set_index(kobe), player_score.store(Int(81)), - player_index.store(Int(131)), # David Thompson + player_score.set_index(dt), player_score.store(Int(73)), Assert(player_score.load() == Int(73)), Assert(player_score.index() == Int(131)), - player_score.store(player_score.load() - Int(2)), # back to Wilt: + player_score.set_index(wilt), Assert(player_score.load() == Int(100)), Assert(player_score.index() == Int(129)), + Int(100), ) """ @@ -92,17 +98,11 @@ def __init__( self, ttype: TealType = TealType.anytype, indexer: ScratchVar = None, - # slotId: int = None, ): - # if (indexer is None) == (slotId is None): - # raise TealInputError( - # "must either provide an indexer or slotId and not both" - # ) self.type = ttype if indexer is None: indexer = ScratchVar(TealType.uint64) - # indexer.store(Int(slotId)) self.indexer: ScratchVar self.slot: ScratchSlot From 097a2ac722bf38003ac041c995840bf0443768c5 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 11:49:25 -0600 Subject: [PATCH 22/85] add logcat test, remove stale tests, and hopefully pass on circle --- tests/dynamic_scratch_test.py | 211 ------------------ tests/pass_by_ref_test.py | 19 +- tests/teal/dynamic_scratch_2_expected.teal | 42 ---- tests/teal/dynamic_scratch_expected.teal | 42 ---- tests/teal/sub_logcat_expected.teal | 21 ++ tests/teal/subroutines_expected.teal | 244 --------------------- 6 files changed, 39 insertions(+), 540 deletions(-) delete mode 100644 tests/dynamic_scratch_test.py delete mode 100644 tests/teal/dynamic_scratch_2_expected.teal delete mode 100644 tests/teal/dynamic_scratch_expected.teal create mode 100644 tests/teal/sub_logcat_expected.teal delete mode 100644 tests/teal/subroutines_expected.teal diff --git a/tests/dynamic_scratch_test.py b/tests/dynamic_scratch_test.py deleted file mode 100644 index 1059edc2d..000000000 --- a/tests/dynamic_scratch_test.py +++ /dev/null @@ -1,211 +0,0 @@ -from pyteal import * - -from .compile_asserts import assert_new_v_old, compile_and_save - - -def dynamic_scratch(): - current_player = ScratchVar(TealType.uint64, 128) - player_score = ScratchVar(TealType.uint64, current_player.load()) - i = ScratchVar(TealType.uint64, 0) - return Seq( - current_player.store(Int(129)), - For(i.store(Int(1)), i.load() <= Int(10), i.store(i.load() + Int(1))).Do( - Seq( - current_player.store(Int(129) + (i.load() - Int(1)) % Int(5)), - Log(Concat(Bytes("Current player: "), Itob(current_player.load()))), - Log(Concat(Bytes("Player score index: "), Itob(player_score.index()))), - player_score.store(player_score.load() + i.load()), - ) - ), - Int(1), - ) - - -def dynamic_scratch_2(): - current_player = ScratchVar(TealType.uint64) - player_score = ScratchVar(TealType.uint64, current_player.load()) - i = ScratchVar(TealType.uint64) - return Seq( - current_player.store(Int(129)), - For(i.store(Int(1)), i.load() <= Int(10), i.store(i.load() + Int(1))).Do( - Seq( - current_player.store(Int(129) + (i.load() - Int(1)) % Int(5)), - Log(Concat(Bytes("Current player: "), Itob(current_player.load()))), - Log(Concat(Bytes("Player score index: "), Itob(player_score.index()))), - player_score.store(player_score.load() + i.load()), - ) - ), - Int(1), - ) - - -def wilt_the_stilt(): - player_index = ScratchVar(TealType.uint64) - player_score = ScratchVar(TealType.uint64, player_index.load()) - return Seq( - player_index.store(Int(129)), # Wilt Chamberlain - player_score.store(Int(100)), - player_index.store(Int(130)), # Kobe Bryant - player_score.store(Int(81)), - player_index.store(Int(131)), # David Thompson - player_score.store(Int(73)), - Assert(player_score.load() == Int(73)), - Assert(player_score.index() == Int(131)), - player_index.store(player_index.load() - Int(2)), # back to Wilt: - Assert(player_score.load() == Int(100)), - Assert(player_score.index() == Int(129)), - Int(100), - ) - - -@Subroutine(TealType.bytes) -def logcat(some_bytes, an_int): - catted = ScratchVar(TealType.bytes) - return Seq( - catted.store(Concat(some_bytes, Itob(an_int))), - Log(catted.load()), - catted.load(), - ) - - -@Subroutine(TealType.bytes) -def logcat_dynamic(some_bytes, an_int): - catted = ScratchVar(TealType.bytes, Int(42)) - return Seq( - catted.store(Concat(some_bytes, Itob(an_int))), - Log(catted.load()), - catted.load(), - ) - - -@Subroutine(TealType.uint64) -def slow_fibonacci(n): - return ( - If(n <= Int(1)) - .Then(n) - .Else(slow_fibonacci(n - Int(2)) + slow_fibonacci(n - Int(1))) - ) - - -@Subroutine(TealType.uint64) -def fast_fibonacci(n): - i = ScratchVar(TealType.uint64) - a = ScratchVar(TealType.uint64) - b = ScratchVar(TealType.uint64) - return Seq( - a.store(Int(0)), - b.store(Int(1)), - For(i.store(Int(1)), i.load() <= n, i.store(i.load() + Int(1))).Do( - Seq( - b.store(a.load() + b.load()), - a.store(b.load() - a.load()), - ) - ), - a.load(), - ) - - -@Subroutine(TealType.uint64) -def fast_fibonacci_mixed(n): - i = ScratchVar(TealType.uint64, Int(42)) - a = ScratchVar( - TealType.uint64, Int(42) * Int(1337) - ) # yes, this is too big - but the compiler can't figure that out - b = ScratchVar(TealType.uint64) - return Seq( - a.store(Int(0)), - b.store(Int(1)), - For(i.store(Int(1)), i.load() <= n, i.store(i.load() + Int(1))).Do( - Seq( - b.store(a.load() + b.load()), - a.store(b.load() - a.load()), - ) - ), - a.load(), - ) - - -def subroutines(): - selected = ScratchVar(TealType.bytes, Int(1337)) - hello = ScratchVar( - TealType.bytes, - ) - hello_dyn = ScratchVar(TealType.bytes, Int(747)) - x_reg = ScratchVar(TealType.uint64) - x_dyn = ScratchVar(TealType.uint64, Int(777)) - return Seq( - hello.store(Bytes("hello there")), - hello_dyn.store(Bytes("goodbye")), - x_reg.store(Int(1_000_000)), - x_dyn.store(Int(1_000_000_000)), - selected.store(Bytes("fast_fibonacci_mixed")), - If(selected.load() == Bytes("logcat")) - .Then( - Seq( - Pop(logcat(hello.load(), Int(17))), - Int(100), - ) - ) - .ElseIf( - Concat(Bytes("logcat"), Bytes("_"), Bytes("dynamic")) == selected.load() - ) - .Then( - Seq( - Pop(logcat_dynamic(Bytes("yo"), Int(117))), - Pop(logcat_dynamic(hello.load(), x_reg.load())), - Pop(logcat_dynamic(hello_dyn.load(), x_dyn.load())), - Int(101), - ) - ) - .ElseIf(selected.load() == Bytes("slow_fibonacci")) - .Then( - Seq( - Pop(slow_fibonacci(Int(217))), - Pop(slow_fibonacci(x_reg.load())), - slow_fibonacci(x_dyn.load()), - ) - ) - .ElseIf(selected.load() == Bytes("fast_fibonacci")) - .Then( - Seq( - Pop(fast_fibonacci(Int(317))), - Pop(fast_fibonacci(x_reg.load())), - fast_fibonacci(x_dyn.load()), - ) - ) - .ElseIf(selected.load() == Bytes("fast_fibonacci_mixed")) - .Then( - Seq( - Pop(fast_fibonacci_mixed(Int(417))), - Pop(fast_fibonacci_mixed(x_reg.load())), - fast_fibonacci_mixed(x_dyn.load()), - ) - ) - .Else( - Err(), - ), - ) - - -# TEST_CASES = (dynamic_scratch, dynamic_scratch_2, wilt_the_stilt, subroutines) - -TEST_CASES = (wilt_the_stilt,) - - -def test_all(): - for pt in TEST_CASES: - assert_new_v_old(pt) - - -def test_generate_another(): - teal_dir, name, compiled = compile_and_save(subroutines) - print( - f"""Successfuly tested approval program <<{name}>> having -compiled it into {len(compiled)} characters. See the results in: -{teal_dir} -""" - ) - - -if __name__ == "__main__": - test_all() diff --git a/tests/pass_by_ref_test.py b/tests/pass_by_ref_test.py index f7e73b7af..8771e363e 100644 --- a/tests/pass_by_ref_test.py +++ b/tests/pass_by_ref_test.py @@ -3,6 +3,23 @@ from compile_asserts import assert_new_v_old, compile_and_save +@Subroutine(TealType.bytes) +def logcat(some_bytes, an_int): + catted = ScratchVar(TealType.bytes) + return Seq( + catted.store(Concat(some_bytes, Itob(an_int))), + Log(catted.load()), + catted.load(), + ) + + +def sub_logcat(): + return Seq( + Pop(logcat(Bytes("hello"), Int(42))), + Int(1), + ) + + def wilt_the_stilt(): player_score = DynamicScratchVar(TealType.uint64) @@ -80,7 +97,7 @@ def fac_by_ref(): ) -TEST_CASES = (swapper, wilt_the_stilt) +TEST_CASES = (sub_logcat, swapper, wilt_the_stilt) def test_swapper(): diff --git a/tests/teal/dynamic_scratch_2_expected.teal b/tests/teal/dynamic_scratch_2_expected.teal deleted file mode 100644 index 011c43267..000000000 --- a/tests/teal/dynamic_scratch_2_expected.teal +++ /dev/null @@ -1,42 +0,0 @@ -#pragma version 6 -int 129 -store 0 -int 1 -store 1 -main_l1: -load 1 -int 10 -<= -bz main_l3 -int 129 -load 1 -int 1 -- -int 5 -% -+ -store 0 -byte "Current player: " -load 0 -itob -concat -log -byte "Player score index: " -load 0 -itob -concat -log -load 0 -load 0 -loads -load 1 -+ -stores -load 1 -int 1 -+ -store 1 -b main_l1 -main_l3: -int 1 -return \ No newline at end of file diff --git a/tests/teal/dynamic_scratch_expected.teal b/tests/teal/dynamic_scratch_expected.teal deleted file mode 100644 index a75e1a6ff..000000000 --- a/tests/teal/dynamic_scratch_expected.teal +++ /dev/null @@ -1,42 +0,0 @@ -#pragma version 6 -int 129 -store 128 128: 129 -int 1 >1 -store 0 0: 1 -main_l1: ... after 10 iters: 0: 11; 128: 133; 129: 7, 130: 9; ... ; 133: 15 -load 0 >1 >11 -int 10 >1,10 >11,10 -<= >1 >0 -bz main_l3 (DON'T BRANCH) (GOTO main_l3) -int 129 >129 -load 0 >129,1 -int 1 >129,1,1 -- >129,0 -int 5 >129,0,5 -% >129,9 -+ >129 -store 128 128: 129 -byte "Current player: " -load 128 -itob -concat >"Current player: 129" -log -byte "Player score index: " -load 128 -itob -concat >"Player score index: 129" -log -load 128 >129 -load 128 >129,129 -loads >129,0 -load 0 >129,0,1 -+ >129,1 -stores 129:1 -load 0 >1 -int 1 >1,1 -+ >2 -store 0 0: 2 -b main_l1 ... now repeat until 0: 11; 128: 133 (== 129+(10-1)%5); 129: 7, 130: 9, ... , 133: 15 -main_l3: ... at the end: 0: 11; 128: 133; 129: 7, 130: 9; ... ; 133: 15 -int 1 >1 -return SUCCESS \ No newline at end of file diff --git a/tests/teal/sub_logcat_expected.teal b/tests/teal/sub_logcat_expected.teal new file mode 100644 index 000000000..0a0dcd842 --- /dev/null +++ b/tests/teal/sub_logcat_expected.teal @@ -0,0 +1,21 @@ +#pragma version 6 +byte "hello" >"hello" +int 42 >"hello",42 +callsub logcat_0 >"hello42" +pop +int 1 >1 +return <> + +// logcat +logcat_0: >"hello",42 +store 1 1: 42 +store 0 0: "hello" +load 0 >"hello" +load 1 >"hello",42 +itob >"hello","42" +concat >"hello42" +store 2 2: "hello42" +load 2 >"hello42" +log +load 2 >"hello42" +retsub \ No newline at end of file diff --git a/tests/teal/subroutines_expected.teal b/tests/teal/subroutines_expected.teal deleted file mode 100644 index a7994d28d..000000000 --- a/tests/teal/subroutines_expected.teal +++ /dev/null @@ -1,244 +0,0 @@ -#pragma version 6 -byte "hello there" -store 0 -int 747 -byte "goodbye" -stores // yep. 747: "goodbye" -int 1000000 -store 1 -int 777 -int 1000000000 -stores // yep. 777: 1_000_000_000 -int 1337 -byte "fast_fibonacci_mixed" -stores // yep. 1337: "fast_fibonacci_mixed" -int 1337 -loads -byte "logcat" -== -bnz main_l10 -byte "logcat" -byte "_" -concat -byte "dynamic" -concat -int 1337 -loads -== -bnz main_l9 -int 1337 -loads -byte "slow_fibonacci" -== -bnz main_l8 -int 1337 -loads -byte "fast_fibonacci" -== -bnz main_l7 -int 1337 -loads -byte "fast_fibonacci_mixed" -== -bnz main_l6 -err -main_l6: -int 417 -callsub fastfibonaccimixed_4 -pop -load 1 -callsub fastfibonaccimixed_4 -pop -int 777 -loads -callsub fastfibonaccimixed_4 -b main_l11 -main_l7: -int 317 -callsub fastfibonacci_3 -pop -load 1 -callsub fastfibonacci_3 -pop -int 777 -loads -callsub fastfibonacci_3 -b main_l11 -main_l8: -int 217 -callsub slowfibonacci_2 -pop -load 1 -callsub slowfibonacci_2 -pop -int 777 -loads -callsub slowfibonacci_2 -b main_l11 -main_l9: -byte "yo" -int 117 -callsub logcatdynamic_1 -pop -load 0 -load 1 -callsub logcatdynamic_1 -pop -int 747 -loads -int 777 -loads -callsub logcatdynamic_1 -pop -int 101 -b main_l11 -main_l10: -load 0 -int 17 -callsub logcat_0 -pop -int 100 -main_l11: -return - -// logcat -logcat_0: -store 3 -store 2 -load 2 -load 3 -itob -concat -store 4 -load 4 -log -load 4 -retsub - -// logcat_dynamic -logcatdynamic_1: -store 6 -store 5 -int 42 -load 5 -load 6 -itob -concat -stores -int 42 -loads -log -int 42 -loads -retsub - -// slow_fibonacci -slowfibonacci_2: -store 7 -load 7 -int 1 >>> n,1 -<= -bnz slowfibonacci_2_l2 -load 7 -int 2 -- -load 7 -swap -callsub slowfibonacci_2 -swap -store 7 -load 7 -int 1 -- -load 7 -swap -callsub slowfibonacci_2 -swap -store 7 -+ -b slowfibonacci_2_l3 -slowfibonacci_2_l2: -load 7 -slowfibonacci_2_l3: -retsub - -// fast_fibonacci -fastfibonacci_3: -store 8 -int 0 -store 10 -int 1 -store 11 -int 1 -store 9 -fastfibonacci_3_l1: -load 9 -load 8 >>> 1,n -<= -bz fastfibonacci_3_l3 -load 10 -load 11 -+ -store 11 -load 11 -load 10 -- -store 10 -load 9 -int 1 -+ -store 9 -b fastfibonacci_3_l1 -fastfibonacci_3_l3: -load 10 -retsub - -// fast_fibonacci_mixed -fastfibonaccimixed_4: -store 12 -int 42 -int 1337 // yep too large. 42*1337: 0 -* -int 0 -stores -int 1 -store 13 -int 42 -int 1 -stores -fastfibonaccimixed_4_l1: -int 42 -loads -load 12 -<= -bz fastfibonaccimixed_4_l3 -int 42 -int 1337 -* -loads -load 13 -+ -store 13 -int 42 -int 1337 -* -load 13 -int 42 -int 1337 -* -loads -- -stores -int 42 -int 42 -loads -int 1 -+ -stores -b fastfibonaccimixed_4_l1 -fastfibonaccimixed_4_l3: -int 42 -int 1337 -* -loads -retsub \ No newline at end of file From 8bff58b4bfb134ce6dd204d8314f9c7dab244bd9 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 14:31:35 -0600 Subject: [PATCH 23/85] ready to refactor --- pyteal/ast/scratchvar.py | 4 +- pyteal/ast/subroutine.py | 2 +- tests/pass_by_ref_test.py | 93 +++++++++++++++++++-- tests/teal/sub_even_expected.teal | 35 ++++++++ tests/teal/sub_fastfib_expected.teal | 35 ++++++++ tests/teal/sub_logcat_dynamic_expected.teal | 28 +++++++ 6 files changed, 189 insertions(+), 8 deletions(-) create mode 100644 tests/teal/sub_even_expected.teal create mode 100644 tests/teal/sub_fastfib_expected.teal create mode 100644 tests/teal/sub_logcat_dynamic_expected.teal diff --git a/pyteal/ast/scratchvar.py b/pyteal/ast/scratchvar.py index 7530a65ae..858a06da6 100644 --- a/pyteal/ast/scratchvar.py +++ b/pyteal/ast/scratchvar.py @@ -130,11 +130,11 @@ def set_index(self, indexVar: ScratchVar) -> Expr: return self.indexer.store(indexVar.index()) def store(self, value: Expr) -> Expr: - dynsv = ScratchVar(TealType.uint64, self.indexer.load()) + dynsv = ScratchVar(self.type, self.indexer.load()) return dynsv.store(value) def load(self) -> ScratchLoad: - dynsv = ScratchVar(TealType.uint64, self.indexer.load()) + dynsv = ScratchVar(self.type, self.indexer.load()) return dynsv.load() def index(self) -> Expr: diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 21a4f603a..787e7f40f 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -327,7 +327,7 @@ def evaluateSubroutine(subroutine: SubroutineDefinition) -> SubroutineDeclaratio def var_n_loaded(param): if param in subroutine.by_ref_args: argVar = ScratchVar(TealType.uint64) - loaded = DynamicScratchVar(indexer=argVar) + loaded = DynamicScratchVar(TealType.anytype, indexer=argVar) else: argVar = ScratchVar(TealType.anytype) loaded = argVar.load() diff --git a/tests/pass_by_ref_test.py b/tests/pass_by_ref_test.py index 8771e363e..5fb843a32 100644 --- a/tests/pass_by_ref_test.py +++ b/tests/pass_by_ref_test.py @@ -2,6 +2,8 @@ from compile_asserts import assert_new_v_old, compile_and_save +#### TESTS FOR PyTEAL THAT PREDATES PASS-BY-REF + @Subroutine(TealType.bytes) def logcat(some_bytes, an_int): @@ -15,7 +17,81 @@ def logcat(some_bytes, an_int): def sub_logcat(): return Seq( - Pop(logcat(Bytes("hello"), Int(42))), + Assert(logcat(Bytes("hello"), Int(42)) == Bytes("hello42")), + Int(1), + ) + + +@Subroutine(TealType.uint64) +def slow_fibonacci(n): + return ( + If(n <= Int(1)) + .Then(n) + .Else(slow_fibonacci(n - Int(2)) + slow_fibonacci(n - Int(1))) + ) + + +def sub_slowfib(): + return slow_fibonacci(Int(3)) + + +@Subroutine(TealType.uint64) +def fast_fibonacci(n): + i = ScratchVar(TealType.uint64) + a = ScratchVar(TealType.uint64) + b = ScratchVar(TealType.uint64) + return Seq( + a.store(Int(0)), + b.store(Int(1)), + For(i.store(Int(1)), i.load() <= n, i.store(i.load() + Int(1))).Do( + Seq( + b.store(a.load() + b.load()), + a.store(b.load() - a.load()), + ) + ), + a.load(), + ) + + +def sub_fastfib(): + return fast_fibonacci(Int(3)) + + +@Subroutine(TealType.uint64) +def recursiveIsEven(i): + return ( + If(i == Int(0)) + .Then(Int(1)) + .ElseIf(i == Int(1)) + .Then(Int(0)) + .Else(recursiveIsEven(i - Int(2))) + ) + + +def sub_even(): + return Seq( + Pop(recursiveIsEven(Int(1000))), + recursiveIsEven(Int(1001)), + ) + + +#### TESTS FOR NEW PyTEAL THAT USES PASS-BY-REF / DYNAMIC + + +@Subroutine(TealType.none) +def logcat_dynamic(first: ScratchVar, an_int): + return Seq( + first.store(Concat(first.load(), Itob(an_int))), + Log(first.load()), + ) + + +def sub_logcat_dynamic(): + first = ScratchVar(TealType.bytes) + return Seq( + first.store(Bytes("hello")), + logcat_dynamic(first, Int(42)), + Assert(Bytes("hello42") == first.load()), Int(1), ) @@ -97,18 +173,25 @@ def fac_by_ref(): ) -TEST_CASES = (sub_logcat, swapper, wilt_the_stilt) +OLD_CASES = (sub_logcat, sub_slowfib, sub_fastfib, sub_even) +NEW_CASES = (sub_logcat_dynamic, swapper, wilt_the_stilt) def test_swapper(): compile_and_save(swapper) -def test_all(): - for pt in TEST_CASES: +def test_old(): + for pt in OLD_CASES: + assert_new_v_old(pt) + + +def test_new(): + for pt in OLD_CASES: assert_new_v_old(pt) if __name__ == "__main__": test_swapper() - test_all() + test_old() + test_new() diff --git a/tests/teal/sub_even_expected.teal b/tests/teal/sub_even_expected.teal new file mode 100644 index 000000000..8cd15e13b --- /dev/null +++ b/tests/teal/sub_even_expected.teal @@ -0,0 +1,35 @@ +#pragma version 6 +int 1000 +callsub recursiveIsEven_0 +pop +int 1001 +callsub recursiveIsEven_0 +return + +// recursiveIsEven +recursiveIsEven_0: +store 0 +load 0 +int 0 +== +bnz recursiveIsEven_0_l4 +load 0 +int 1 +== +bnz recursiveIsEven_0_l3 +load 0 +int 2 +- +load 0 +swap +callsub recursiveIsEven_0 +swap +store 0 +b recursiveIsEven_0_l5 +recursiveIsEven_0_l3: +int 0 +b recursiveIsEven_0_l5 +recursiveIsEven_0_l4: +int 1 +recursiveIsEven_0_l5: +retsub \ No newline at end of file diff --git a/tests/teal/sub_fastfib_expected.teal b/tests/teal/sub_fastfib_expected.teal new file mode 100644 index 000000000..dea466e36 --- /dev/null +++ b/tests/teal/sub_fastfib_expected.teal @@ -0,0 +1,35 @@ +#pragma version 6 +int 3 +callsub fastfibonacci_0 +return + +// fast_fibonacci +fastfibonacci_0: +store 0 +int 0 +store 2 +int 1 +store 3 +int 1 +store 1 +fastfibonacci_0_l1: +load 1 +load 0 +<= +bz fastfibonacci_0_l3 +load 2 +load 3 ++ +store 3 +load 3 +load 2 +- +store 2 +load 1 +int 1 ++ +store 1 +b fastfibonacci_0_l1 +fastfibonacci_0_l3: +load 2 +retsub \ No newline at end of file diff --git a/tests/teal/sub_logcat_dynamic_expected.teal b/tests/teal/sub_logcat_dynamic_expected.teal new file mode 100644 index 000000000..9cc5660f1 --- /dev/null +++ b/tests/teal/sub_logcat_dynamic_expected.teal @@ -0,0 +1,28 @@ +#pragma version 6 +byte "hello" +store 0 0: "hello" +int 0 +int 42 >@0,42 +callsub logcatdynamic_0 <> +byte "hello42" >"hello42" +load 0 >"hello42","hello42" +== >1 +assert <> +int 1 >1 +return < + +// logcat_dynamic +logcatdynamic_0: >@0,42 +store 2 2: 42 +store 1 1: @0 +load 1 +load 1 >@0,@0 +loads >@0,"hello" +load 2 >@0,"hello",42 +itob >@0,"hello","42" +concat >@0,"hello42" +stores 0: "hello42" +load 1 >@0 +loads >"hello42" +log +retsub \ No newline at end of file From a7ac27559b563147569926e911f7121a4b34908d Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 16:36:21 -0600 Subject: [PATCH 24/85] rip out before clean up --- pyteal/ast/__init__.py | 4 +- pyteal/ast/scratch.py | 217 ++++++++++++++++++--------- pyteal/ast/scratchvar.py | 25 +-- tests/pass_by_ref_test.py | 4 +- tests/teal/fac_by_ref_expected.teal | 78 +++++----- tests/teal/sub_logcat_expected.teal | 4 +- tests/teal/sub_slowfib_expected.teal | 34 +++++ 7 files changed, 239 insertions(+), 127 deletions(-) create mode 100644 tests/teal/sub_slowfib_expected.teal diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index 35ad8f762..533634498 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -121,7 +121,7 @@ # misc from .scratch import ( - DynamicSlot, + # DynamicSlot, ScratchSlot, ScratchLoad, ScratchStore, @@ -175,7 +175,7 @@ "Cond", "Continue", "Div", - "DynamicSlot", + # "DynamicSlot", "Ed25519Verify", "EnumInt", "Eq", diff --git a/pyteal/ast/scratch.py b/pyteal/ast/scratch.py index f0c2b315a..84e62d681 100644 --- a/pyteal/ast/scratch.py +++ b/pyteal/ast/scratch.py @@ -9,53 +9,53 @@ from ..compiler import CompileOptions -class Slot: - """Abstract base closs for ScratchSlot and DynamicSlot""" +# class Slot: +# """Abstract base closs for ScratchSlot and DynamicSlot""" - def __init__(self): - self.id: Union[int, Expr] = None - self.isReservedSlot: bool = None +# def __init__(self): +# self.id: Union[int, Expr] = None +# self.isReservedSlot: bool = None - def store(self, value: Expr = None) -> Expr: - """Get an expression to store a value in this slot. +# def store(self, value: Expr = None) -> Expr: +# """Get an expression to store a value in this slot. - Args: - value (optional): The value to store in this slot. If not included, the last value on - the stack will be stored. NOTE: storing the last value on the stack breaks the typical - semantics of PyTeal, only use if you know what you're doing. - """ - if value is not None: - return ScratchStore(self, value) +# Args: +# value (optional): The value to store in this slot. If not included, the last value on +# the stack will be stored. NOTE: storing the last value on the stack breaks the typical +# semantics of PyTeal, only use if you know what you're doing. +# """ +# if value is not None: +# return ScratchStore(self, value) - if not isinstance(self, ScratchSlot): - raise TealInternalError( - "ScratchStackStore is only setup to handle ScratchSlot's but was about to supply type {}".format( - type(self) - ) - ) +# if not isinstance(self, ScratchSlot): +# raise TealInternalError( +# "ScratchStackStore is only setup to handle ScratchSlot's but was about to supply type {}".format( +# type(self) +# ) +# ) - return ScratchStackStore(cast(ScratchSlot, self)) +# return ScratchStackStore(cast(ScratchSlot, self)) - def load(self, type: TealType = TealType.anytype) -> "ScratchLoad": - """Get an expression to load a value from this slot. +# def load(self, type: TealType = TealType.anytype) -> "ScratchLoad": +# """Get an expression to load a value from this slot. - Args: - type (optional): The type being loaded from this slot, if known. Defaults to - TealType.anytype. - """ - return ScratchLoad(self, type) +# Args: +# type (optional): The type being loaded from this slot, if known. Defaults to +# TealType.anytype. +# """ +# return ScratchLoad(self, type) - def index(self) -> "ScratchIndex": - return ScratchIndex(self) +# def index(self) -> "ScratchIndex": +# return ScratchIndex(self) - def __hash__(self): - return hash(self.id) +# def __hash__(self): +# return hash(self.id) -Slot.__module__ = "pyteal" +# Slot.__module__ = "pyteal" -class ScratchSlot(Slot): +class ScratchSlot: """Represents the allocation of a scratch space slot.""" # Unique identifier for the compiler to automatically assign slots @@ -63,6 +63,9 @@ class ScratchSlot(Slot): # Slot ids under 256 are manually reserved slots nextSlotId = NUM_SLOTS + # def __init__(self): + # self.id: Union[int, Expr] = None + # self.isReservedSlot: bool = None def __init__(self, requestedSlotId: int = None): """Initializes a scratch slot with a particular id @@ -70,7 +73,7 @@ def __init__(self, requestedSlotId: int = None): requestedSlotId (optional): A scratch slot id that the compiler must store the value. This id may be a Python int in the range [0-256). """ - super().__init__() + # super().__init__() if requestedSlotId is None: self.id = ScratchSlot.nextSlotId @@ -86,6 +89,41 @@ def __init__(self, requestedSlotId: int = None): self.id = requestedSlotId self.isReservedSlot = True + def store(self, value: Expr = None) -> Expr: + """Get an expression to store a value in this slot. + + Args: + value (optional): The value to store in this slot. If not included, the last value on + the stack will be stored. NOTE: storing the last value on the stack breaks the typical + semantics of PyTeal, only use if you know what you're doing. + """ + if value is not None: + return ScratchStore(self, value) + + if not isinstance(self, ScratchSlot): + raise TealInternalError( + "ScratchStackStore is only setup to handle ScratchSlot's but was about to supply type {}".format( + type(self) + ) + ) + + return ScratchStackStore(cast(ScratchSlot, self)) + + def load(self, type: TealType = TealType.anytype) -> "ScratchLoad": + """Get an expression to load a value from this slot. + + Args: + type (optional): The type being loaded from this slot, if known. Defaults to + TealType.anytype. + """ + return ScratchLoad(self, type) + + def index(self) -> "ScratchIndex": + return ScratchIndex(self) + + def __hash__(self): + return hash(self.id) + def __repr__(self): return "ScratchSlot({})".format(self.id) @@ -96,39 +134,39 @@ def __str__(self): ScratchSlot.__module__ = "pyteal" -class DynamicSlot(Slot): - """Represents the allocation of a scratch space slot.""" +# class DynamicSlot(Slot): +# """Represents the allocation of a scratch space slot.""" - # Unique identifier for the compiler to automatically assign slots - # The id field is used by the compiler to map to an actual slot in the source code - # Slot ids under 256 are manually reserved slots - nextSlotId = NUM_SLOTS +# # Unique identifier for the compiler to automatically assign slots +# # The id field is used by the compiler to map to an actual slot in the source code +# # Slot ids under 256 are manually reserved slots +# nextSlotId = NUM_SLOTS - def __init__(self, slotExpr: Expr): - """Initializes a scratch slot with a particular id +# def __init__(self, slotExpr: Expr): +# """Initializes a scratch slot with a particular id - Args: - requestedSlotId (optional): A scratch slot id that the compiler must store the value. - This id may be a Python int in the range [0-256). - """ - super().__init__() +# Args: +# requestedSlotId (optional): A scratch slot id that the compiler must store the value. +# This id may be a Python int in the range [0-256). +# """ +# super().__init__() - # TODO: Zeph to add assertions - self.id = slotExpr - self.isReservedSlot = False +# # TODO: Zeph to add assertions +# self.id = slotExpr +# self.isReservedSlot = False - def __repr__(self): - return "DynamicSlot({})".format(self.id) +# def __repr__(self): +# return "DynamicSlot({})".format(self.id) - def __str__(self): - return "dslot#{}".format(self.id) +# def __str__(self): +# return "dslot#{}".format(self.id) -DynamicSlot.__module__ = "pyteal" +# DynamicSlot.__module__ = "pyteal" class ScratchIndex(Expr): - def __init__(self, slot: Slot): + def __init__(self, slot: ScratchSlot): super().__init__() self.slot = slot @@ -157,7 +195,12 @@ def __teal__(self, options: "CompileOptions"): class ScratchLoad(Expr): """Expression to load a value from scratch space.""" - def __init__(self, slot: Slot, type: TealType = TealType.anytype): + def __init__( + self, + slot: ScratchSlot, + type: TealType = TealType.anytype, + index_expression: Expr = None, + ): """Create a new ScratchLoad expression. Args: @@ -166,8 +209,15 @@ def __init__(self, slot: Slot, type: TealType = TealType.anytype): TealType.anytype. """ super().__init__() + + if (slot is None) == (index_expression is None): + raise TealInternalError( + "Exactly one of slot or index_expressions must be provided" + ) + self.slot = slot self.type = type + self.index_expression = index_expression def __str__(self): return "(Load {})".format(self.slot) @@ -175,15 +225,22 @@ def __str__(self): def __teal__(self, options: "CompileOptions"): from ..ir import TealOp, Op, TealBlock - if isinstance(self.slot, ScratchSlot): - op = TealOp(self, Op.load, self.slot) - return TealBlock.FromOp(options, op) + if self.index_expression is not None: + op = TealOp(self, Op.loads) + return TealBlock.FromOp(options, op, self.index_expression) - if not isinstance(self.slot, DynamicSlot): - raise TealInternalError("unrecognized slot type {}".format(type(self.slot))) + if not isinstance(self.slot, ScratchSlot): + raise TealInternalError( + "cannot handle slot of type {}".format(type(self.slot)) + ) + op = TealOp(self, Op.load, self.slot) + return TealBlock.FromOp(options, op) - op = TealOp(self, Op.loads) - return TealBlock.FromOp(options, op, cast(Expr, self.slot.id)) + # if not isinstance(self.slot, DynamicSlot): + # raise TealInternalError("unrecognized slot type {}".format(type(self.slot))) + + # op = TealOp(self, Op.loads) + # return TealBlock.FromOp(options, op, cast(Expr, self.slot.id)) def type_of(self): return self.type @@ -198,7 +255,7 @@ def has_return(self): class ScratchStore(Expr): """Expression to store a value in scratch space.""" - def __init__(self, slot: Slot, value: Expr): + def __init__(self, slot: ScratchSlot, value: Expr, index_expression: Expr = None): """Create a new ScratchStore expression. Args: @@ -206,8 +263,15 @@ def __init__(self, slot: Slot, value: Expr): value: The value to store. """ super().__init__() + + if (slot is None) == (index_expression is None): + raise TealInternalError( + "Exactly one of slot or index_expressions must be provided" + ) + self.slot = slot self.value = value + self.index_expression = index_expression def __str__(self): return "(Store {} {})".format(self.slot, self.value) @@ -215,15 +279,22 @@ def __str__(self): def __teal__(self, options: "CompileOptions"): from ..ir import TealOp, Op, TealBlock - if isinstance(self.slot, ScratchSlot): - op = TealOp(self, Op.store, self.slot) - return TealBlock.FromOp(options, op, self.value) + if self.index_expression is not None: + op = TealOp(self, Op.stores) + return TealBlock.FromOp(options, op, self.index_expression, self.value) + + if not isinstance(self.slot, ScratchSlot): + raise TealInternalError( + "cannot handle slot of type {}".format(type(self.slot)) + ) + op = TealOp(self, Op.store, self.slot) + return TealBlock.FromOp(options, op, self.value) - if not isinstance(self.slot, DynamicSlot): - raise TealInternalError("unrecognized slot type {}".format(type(self.slot))) + # if not isinstance(self.slot, DynamicSlot): + # raise TealInternalError("unrecognized slot type {}".format(type(self.slot))) - op = TealOp(self, Op.stores) - return TealBlock.FromOp(options, op, cast(Expr, self.slot.id), self.value) + # op = TealOp(self, Op.stores) + # return TealBlock.FromOp(options, op, cast(Expr, self.slot.id), self.value) def type_of(self): return TealType.none diff --git a/pyteal/ast/scratchvar.py b/pyteal/ast/scratchvar.py index 858a06da6..dae05add1 100644 --- a/pyteal/ast/scratchvar.py +++ b/pyteal/ast/scratchvar.py @@ -6,7 +6,7 @@ from .expr import Expr from .int import Int -from .scratch import ScratchSlot, ScratchLoad, DynamicSlot +from .scratch import ScratchSlot, ScratchLoad, ScratchStore # DynamicSlot class ScratchVar: @@ -36,11 +36,12 @@ def __init__(self, type: TealType = TealType.anytype, slotId: int = None): # TODO: Zeph to add assertions - self.slot = ( - ScratchSlot(requestedSlotId=slotId) - if not isinstance(slotId, Expr) - else DynamicSlot(cast(Expr, slotId)) - ) + self.slot = ScratchSlot(requestedSlotId=slotId) + # self.slot = ( + # ScratchSlot(requestedSlotId=slotId) + # if not isinstance(slotId, Expr) + # else DynamicSlot(cast(Expr, slotId)) + # ) self.type = type def storage_type(self) -> TealType: @@ -130,12 +131,16 @@ def set_index(self, indexVar: ScratchVar) -> Expr: return self.indexer.store(indexVar.index()) def store(self, value: Expr) -> Expr: - dynsv = ScratchVar(self.type, self.indexer.load()) - return dynsv.store(value) + index = ScratchLoad(self.indexer.slot, TealType.uint64) + return ScratchStore(slot=None, value=value, index_expression=index) + # dynsv = ScratchVar(self.type, self.indexer.load()) + # return dynsv.store(value) def load(self) -> ScratchLoad: - dynsv = ScratchVar(self.type, self.indexer.load()) - return dynsv.load() + index = ScratchLoad(self.indexer.slot, TealType.uint64) + return ScratchLoad(slot=None, type=self.type, index_expression=index) + # dynsv = ScratchVar(self.type, self.indexer.load()) + # return dynsv.load() def index(self) -> Expr: return self.indexer.load() diff --git a/tests/pass_by_ref_test.py b/tests/pass_by_ref_test.py index 5fb843a32..07502a371 100644 --- a/tests/pass_by_ref_test.py +++ b/tests/pass_by_ref_test.py @@ -174,7 +174,7 @@ def fac_by_ref(): OLD_CASES = (sub_logcat, sub_slowfib, sub_fastfib, sub_even) -NEW_CASES = (sub_logcat_dynamic, swapper, wilt_the_stilt) +NEW_CASES = (sub_logcat_dynamic, swapper, wilt_the_stilt, fac_by_ref) def test_swapper(): @@ -187,7 +187,7 @@ def test_old(): def test_new(): - for pt in OLD_CASES: + for pt in NEW_CASES: assert_new_v_old(pt) diff --git a/tests/teal/fac_by_ref_expected.teal b/tests/teal/fac_by_ref_expected.teal index d51621c2c..98f016936 100644 --- a/tests/teal/fac_by_ref_expected.teal +++ b/tests/teal/fac_by_ref_expected.teal @@ -1,41 +1,41 @@ -#pragma version 6 -int 4 -store 0 0: 4 -int 0 >0 -callsub factorial_0 -load 0 -return +#pragma version 6 Case n = 1 Case n = 2 +int 42 >1 >2 +store 0 0:1 0:2 +int 0 >0 >0 +callsub factorial_0 CALL CALL +load 0 >1 >2 +return RET(1!==1) RET(2!==1*2) // factorial -factorial_0: >0 >0,3,2 >0,3,2,2,2 >0,3,2,2,2,1,2 -store 1 1: 0 1: 2 1: 2 1: 2 -load 1 >0 >0,3,2 >0,3,2,2,2 >0,3,2,2,2,1,2,1 -loads >4 >0,3,3 >0,3,2,2,2 >0,3,2,2,2,1,2,2 -int 1 >4,1 >0,3,3,1 >0,3,2,2,2,1 >0,3,2,2,2,1,2,1 -<= >0 >0,3,0 >0,3,2,2,0 >0,3,2,2,2,1,2,1,1 -bnz factorial_0_l2 >0,3 >0,3,2,2 >0,3,2,2,2,1,2,1 -load 1 >0 >0,3,2 >0,3,2,2,2 -loads >4 >0,3,3 >0,3,2,2,2 -int 1 >0,3,3,1 >0,3,2,2,2,1 -- >0,3,2 >0,3,2,2,1 -store 2 2: 3 2: 2 2: 1 -int 2 >2 >0,3,2 >0,3,2,2,2 -load 1 >2,0 >0,3,2,2 >0,3,2,2,2,2 -load 2 >2,0,3 >0,3,2,2,2 >0,3,2,2,2,2,1 -uncover 2 >0,3,2 >0,3,2,2,2 >0,3,2,2,2,1,2 -callsub factorial_0 >0,3,2,2,2,1,2,1,2,1 >0,3,2,2,2,1,2,1 -store 2 2: 1 2: 1 -store 1 1: 2 1: 2 -load 1 >0,3,2,2,2,1,2,1,2 >0,3,2,2,2,1,2 -load 1 >0,3,2,2,2,1,2,1,2,2 >0,3,2,2,2,1,2,2 -loads >0,3,2,2,2,1,2,1,2,1 >0,3,2,2,2,1,2,1 -load 2 >0,3,2,2,2,1,2,1,2,1,1 -* >0,3,2,2,2,1,2,1 -stores 2:1 -b factorial_0_l3: -factorial_0_l2: >0,3,2,2,2,1,2,1 -load 1 >0,3,2,2,2,1,2,1,2 -int 1 >0,3,2,2,2,1,2,1,2,1 -stores 2: 1 -factorial_0_l3: -retsub \ No newline at end of file +factorial_0: *>0 *>0 *>2,0,1,2 +store 1 1: 0 1: 0 1: 2 +load 1 >0 >0 *>2,0,1,2 +loads >1 >2 *>2,0,1,1 +int 1 >1,1 >2,1 *>2,0,1,1,1 +<= *>1 *>2,0 *>2,0,1,1 +bnz factorial_0_l2 BRANCH *>2 BRANCH +load 1 *>2,0 +loads *>2,2 +int 1 *>2,2,1 +- *>2,1 +store 2 2: 1 +int 2 *>2,2 +load 1 *>2,2,0 +load 2 *>2,2,0,1 +uncover 2 *>2,0,1,2 +callsub factorial_0 CALL SRET >2,0,1 +store 2 2: 1 +store 1 1: 0 +load 1 >2,0 +load 1 >2,0,0 +loads >2,0,2 +load 2 >2,0,2,1 +* >2,0,2 +stores 0: 2 +b factorial_0_l3 BRANCH +factorial_0_l2: | *>2,0,1 +load 1 *>0 | *>2,0,1,2 +int 1 *>0,1 | *>2,0,1,2,1 +stores 0: 1 | 2: 1 +factorial_0_l3: >2 +retsub SRET SRET>2 SRET*>2,0,1 \ No newline at end of file diff --git a/tests/teal/sub_logcat_expected.teal b/tests/teal/sub_logcat_expected.teal index 0a0dcd842..a618fccdd 100644 --- a/tests/teal/sub_logcat_expected.teal +++ b/tests/teal/sub_logcat_expected.teal @@ -2,7 +2,9 @@ byte "hello" >"hello" int 42 >"hello",42 callsub logcat_0 >"hello42" -pop +byte "hello42" >"hello42","hello42" +== >1 +assert <> int 1 >1 return <> diff --git a/tests/teal/sub_slowfib_expected.teal b/tests/teal/sub_slowfib_expected.teal new file mode 100644 index 000000000..53bf3cd0e --- /dev/null +++ b/tests/teal/sub_slowfib_expected.teal @@ -0,0 +1,34 @@ +#pragma version 6 +int 3 +callsub slowfibonacci_0 +return + +// slow_fibonacci +slowfibonacci_0: +store 0 +load 0 +int 1 +<= +bnz slowfibonacci_0_l2 +load 0 +int 2 +- +load 0 +swap +callsub slowfibonacci_0 +swap +store 0 +load 0 +int 1 +- +load 0 +swap +callsub slowfibonacci_0 +swap +store 0 ++ +b slowfibonacci_0_l3 +slowfibonacci_0_l2: +load 0 +slowfibonacci_0_l3: +retsub \ No newline at end of file From 8f162c5fca8854ece3ffab03e7a576070de1d2d5 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 16:43:58 -0600 Subject: [PATCH 25/85] cleanup --- pyteal/__init__.pyi | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index 48f2dca67..b1642b13a 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -64,7 +64,7 @@ __all__ = [ "Continue", "DEFAULT_TEAL_VERSION", "Div", - "DynamicSlot", + "DynamicScratchVar", "Ed25519Verify", "EnumInt", "Eq", @@ -122,7 +122,6 @@ __all__ = [ "OnComplete", "Op", "Or", - "DynamicScratchVar", "Pop", "Reject", "Return", From 6fa17cbe311d000bdaf26073dffd577d938425e9 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 16:48:37 -0600 Subject: [PATCH 26/85] cleanup --- pyteal/ast/scratch.py | 102 +++--------------------------------------- 1 file changed, 6 insertions(+), 96 deletions(-) diff --git a/pyteal/ast/scratch.py b/pyteal/ast/scratch.py index 84e62d681..e1063f66f 100644 --- a/pyteal/ast/scratch.py +++ b/pyteal/ast/scratch.py @@ -9,52 +9,6 @@ from ..compiler import CompileOptions -# class Slot: -# """Abstract base closs for ScratchSlot and DynamicSlot""" - -# def __init__(self): -# self.id: Union[int, Expr] = None -# self.isReservedSlot: bool = None - -# def store(self, value: Expr = None) -> Expr: -# """Get an expression to store a value in this slot. - -# Args: -# value (optional): The value to store in this slot. If not included, the last value on -# the stack will be stored. NOTE: storing the last value on the stack breaks the typical -# semantics of PyTeal, only use if you know what you're doing. -# """ -# if value is not None: -# return ScratchStore(self, value) - -# if not isinstance(self, ScratchSlot): -# raise TealInternalError( -# "ScratchStackStore is only setup to handle ScratchSlot's but was about to supply type {}".format( -# type(self) -# ) -# ) - -# return ScratchStackStore(cast(ScratchSlot, self)) - -# def load(self, type: TealType = TealType.anytype) -> "ScratchLoad": -# """Get an expression to load a value from this slot. - -# Args: -# type (optional): The type being loaded from this slot, if known. Defaults to -# TealType.anytype. -# """ -# return ScratchLoad(self, type) - -# def index(self) -> "ScratchIndex": -# return ScratchIndex(self) - -# def __hash__(self): -# return hash(self.id) - - -# Slot.__module__ = "pyteal" - - class ScratchSlot: """Represents the allocation of a scratch space slot.""" @@ -63,9 +17,6 @@ class ScratchSlot: # Slot ids under 256 are manually reserved slots nextSlotId = NUM_SLOTS - # def __init__(self): - # self.id: Union[int, Expr] = None - # self.isReservedSlot: bool = None def __init__(self, requestedSlotId: int = None): """Initializes a scratch slot with a particular id @@ -73,8 +24,6 @@ def __init__(self, requestedSlotId: int = None): requestedSlotId (optional): A scratch slot id that the compiler must store the value. This id may be a Python int in the range [0-256). """ - # super().__init__() - if requestedSlotId is None: self.id = ScratchSlot.nextSlotId ScratchSlot.nextSlotId += 1 @@ -134,37 +83,6 @@ def __str__(self): ScratchSlot.__module__ = "pyteal" -# class DynamicSlot(Slot): -# """Represents the allocation of a scratch space slot.""" - -# # Unique identifier for the compiler to automatically assign slots -# # The id field is used by the compiler to map to an actual slot in the source code -# # Slot ids under 256 are manually reserved slots -# nextSlotId = NUM_SLOTS - -# def __init__(self, slotExpr: Expr): -# """Initializes a scratch slot with a particular id - -# Args: -# requestedSlotId (optional): A scratch slot id that the compiler must store the value. -# This id may be a Python int in the range [0-256). -# """ -# super().__init__() - -# # TODO: Zeph to add assertions -# self.id = slotExpr -# self.isReservedSlot = False - -# def __repr__(self): -# return "DynamicSlot({})".format(self.id) - -# def __str__(self): -# return "dslot#{}".format(self.id) - - -# DynamicSlot.__module__ = "pyteal" - - class ScratchIndex(Expr): def __init__(self, slot: ScratchSlot): super().__init__() @@ -204,9 +122,11 @@ def __init__( """Create a new ScratchLoad expression. Args: - slot: The slot to load the value from. + slot (optional): The slot to load the value from. type (optional): The type being loaded from this slot, if known. Defaults to TealType.anytype. + index_expression (optional): As an alternative to slot, + an expression can be supplied for the slot index. """ super().__init__() @@ -236,12 +156,6 @@ def __teal__(self, options: "CompileOptions"): op = TealOp(self, Op.load, self.slot) return TealBlock.FromOp(options, op) - # if not isinstance(self.slot, DynamicSlot): - # raise TealInternalError("unrecognized slot type {}".format(type(self.slot))) - - # op = TealOp(self, Op.loads) - # return TealBlock.FromOp(options, op, cast(Expr, self.slot.id)) - def type_of(self): return self.type @@ -259,8 +173,10 @@ def __init__(self, slot: ScratchSlot, value: Expr, index_expression: Expr = None """Create a new ScratchStore expression. Args: - slot: The slot to store the value in. + slot (optional): The slot to store the value in. value: The value to store. + index_expression (optional): As an alternative to slot, + an expression can be supplied for the slot index. """ super().__init__() @@ -290,12 +206,6 @@ def __teal__(self, options: "CompileOptions"): op = TealOp(self, Op.store, self.slot) return TealBlock.FromOp(options, op, self.value) - # if not isinstance(self.slot, DynamicSlot): - # raise TealInternalError("unrecognized slot type {}".format(type(self.slot))) - - # op = TealOp(self, Op.stores) - # return TealBlock.FromOp(options, op, cast(Expr, self.slot.id), self.value) - def type_of(self): return TealType.none From 004b5f536bb1fca2b6bc0e9be7e5f2c81738d58f Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 16:56:14 -0600 Subject: [PATCH 27/85] cleanup --- pyteal/ast/scratch.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/pyteal/ast/scratch.py b/pyteal/ast/scratch.py index e1063f66f..9acd9055a 100644 --- a/pyteal/ast/scratch.py +++ b/pyteal/ast/scratch.py @@ -48,15 +48,7 @@ def store(self, value: Expr = None) -> Expr: """ if value is not None: return ScratchStore(self, value) - - if not isinstance(self, ScratchSlot): - raise TealInternalError( - "ScratchStackStore is only setup to handle ScratchSlot's but was about to supply type {}".format( - type(self) - ) - ) - - return ScratchStackStore(cast(ScratchSlot, self)) + return ScratchStackStore(self) def load(self, type: TealType = TealType.anytype) -> "ScratchLoad": """Get an expression to load a value from this slot. @@ -70,9 +62,6 @@ def load(self, type: TealType = TealType.anytype) -> "ScratchLoad": def index(self) -> "ScratchIndex": return ScratchIndex(self) - def __hash__(self): - return hash(self.id) - def __repr__(self): return "ScratchSlot({})".format(self.id) @@ -230,7 +219,6 @@ def __init__(self, slot: ScratchSlot): slot: The slot to store the value in. """ super().__init__() - self.slot = slot def __str__(self): @@ -239,7 +227,7 @@ def __str__(self): def __teal__(self, options: "CompileOptions"): from ..ir import TealOp, Op, TealBlock - op = TealOp(self, Op.store, cast(ScratchSlot, self.slot)) + op = TealOp(self, Op.store, self.slot) return TealBlock.FromOp(options, op) def type_of(self): From 4c924257c5dba257bb965b9a8f5986c8248c0afe Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 17:02:07 -0600 Subject: [PATCH 28/85] cleaunp --- pyteal/ast/scratchvar.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/pyteal/ast/scratchvar.py b/pyteal/ast/scratchvar.py index dae05add1..a52f1d1b6 100644 --- a/pyteal/ast/scratchvar.py +++ b/pyteal/ast/scratchvar.py @@ -1,12 +1,12 @@ from operator import index from typing import cast -from ..errors import TealInputError +from ..errors import TealInputError, TealInternalError from ..types import TealType, require_type from .expr import Expr from .int import Int -from .scratch import ScratchSlot, ScratchLoad, ScratchStore # DynamicSlot +from .scratch import ScratchSlot, ScratchLoad, ScratchStore class ScratchVar: @@ -34,14 +34,7 @@ def __init__(self, type: TealType = TealType.anytype, slotId: int = None): This id may be a Python int in the range [0-256). """ - # TODO: Zeph to add assertions - self.slot = ScratchSlot(requestedSlotId=slotId) - # self.slot = ( - # ScratchSlot(requestedSlotId=slotId) - # if not isinstance(slotId, Expr) - # else DynamicSlot(cast(Expr, slotId)) - # ) self.type = type def storage_type(self) -> TealType: @@ -111,14 +104,14 @@ def __init__( def _set_indexer(self, indexer: ScratchVar) -> None: if not isinstance(indexer, ScratchVar): - raise TealInputError( + raise TealInternalError( "indexer must be a ScratchVar but had python type {}".format( type(indexer) ) ) if indexer.type != TealType.uint64: - raise TealInputError( + raise TealInternalError( "indexer must have teal type uint64 but was {} instead".format( indexer.type ) @@ -133,14 +126,10 @@ def set_index(self, indexVar: ScratchVar) -> Expr: def store(self, value: Expr) -> Expr: index = ScratchLoad(self.indexer.slot, TealType.uint64) return ScratchStore(slot=None, value=value, index_expression=index) - # dynsv = ScratchVar(self.type, self.indexer.load()) - # return dynsv.store(value) def load(self) -> ScratchLoad: index = ScratchLoad(self.indexer.slot, TealType.uint64) return ScratchLoad(slot=None, type=self.type, index_expression=index) - # dynsv = ScratchVar(self.type, self.indexer.load()) - # return dynsv.load() def index(self) -> Expr: return self.indexer.load() From 40f818e4278dd5636613e2b24edbb7336594f03f Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 17:19:50 -0600 Subject: [PATCH 29/85] merge head add in missing and re-alphabetize imports --- pyteal/__init__.pyi | 98 +++++++++++++++++++-------------------- pyteal/ast/__init__.py | 103 +++++++++++++++++++++-------------------- 2 files changed, 101 insertions(+), 100 deletions(-) diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index e2f2a380f..789e51d17 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -17,74 +17,29 @@ from .errors import TealInternalError, TealTypeError, TealInputError, TealCompil from .config import MAX_GROUP_SIZE, NUM_SLOTS __all__ = [ - "DynamicScratchVar", "AccountParam", "Add", + "Add", "Addr", "And", + "And", "App", "AppField", "AppParam", "Approve", + "Approve", "Arg", "Array", "Assert", + "Assert", "AssetHolding", "AssetParam", "Balance", "BinaryExpr", - "Add", - "Minus", - "Mul", - "Div", - "Mod", - "Exp", - "Divw", "BitwiseAnd", "BitwiseNot", "BitwiseOr", "BitwiseXor", - "ShiftLeft", - "ShiftRight", - "Eq", - "Neq", - "Lt", - "Le", - "Gt", - "Ge", - "GetBit", - "GetByte", - "Ed25519Verify", - "Substring", - "Extract", - "Suffix", - "SetBit", - "SetByte", - "NaryExpr", - "And", - "Or", - "Concat", - "WideRatio", - "If", - "Cond", - "Seq", - "Assert", - "Err", - "Return", - "Approve", - "Reject", - "Subroutine", - "SubroutineDefinition", - "SubroutineDeclaration", - "SubroutineCall", - "SubroutineFnWrapper", - "ScratchSlot", - "ScratchLoad", - "ScratchStore", - "ScratchStackStore", - "ScratchVar", - "MaybeValue", - "MultiValue", "BytesAdd", "BytesAnd", "BytesDiv", @@ -105,25 +60,38 @@ __all__ = [ "CompileOptions", "compileTeal", "Concat", + "Concat", + "Cond", "Cond", "Continue", "DEFAULT_TEAL_VERSION", "Div", + "Div", + "Divw", + "DynamicScratchVar", "DynamicScratchVar", "Ed25519Verify", + "Ed25519Verify", "EnumInt", "Eq", + "Eq", + "Err", "Err", "Exp", + "Exp", "Expr", "Extract", + "Extract", "ExtractUint16", "ExtractUint32", "ExtractUint64", "For", "Ge", + "Ge", "GeneratedID", "GetBit", + "GetBit", + "GetByte", "GetByte", "Gitxn", "GitxnaExpr", @@ -131,10 +99,12 @@ __all__ = [ "Global", "GlobalField", "Gt", + "Gt", "Gtxn", "GtxnaExpr", "GtxnExpr", "If", + "If", "ImportScratchValue", "InnerTxn", "InnerTxnAction", @@ -145,50 +115,79 @@ __all__ = [ "Keccak256", "LabelReference", "Le", + "Le", "LeafExpr", "Len", "Log", "Lt", + "Lt", "MAX_GROUP_SIZE", "MAX_TEAL_VERSION", "MaybeValue", + "MaybeValue", "MethodSignature", "MIN_TEAL_VERSION", "MinBalance", "Minus", + "Minus", + "Mod", "Mod", "Mode", "Mul", + "Mul", + "MultiValue", + "NaryExpr", "NaryExpr", "Neq", + "Neq", "Nonce", "Not", "NUM_SLOTS", "OnComplete", "Op", "Or", + "Or", "Pop", "Reject", + "Reject", + "Return", "Return", "ScratchLoad", + "ScratchLoad", + "ScratchSlot", "ScratchSlot", "ScratchStackStore", + "ScratchStackStore", "ScratchStore", + "ScratchStore", + "ScratchVar", "ScratchVar", "Seq", + "Seq", "SetBit", + "SetBit", + "SetByte", "SetByte", "Sha256", "Sha512_256", "ShiftLeft", + "ShiftLeft", + "ShiftRight", "ShiftRight", "Sqrt", "Subroutine", + "Subroutine", + "SubroutineCall", "SubroutineCall", "SubroutineDeclaration", + "SubroutineDeclaration", + "SubroutineDefinition", "SubroutineDefinition", "SubroutineFnWrapper", + "SubroutineFnWrapper", "Substring", + "Substring", + "Suffix", "Suffix", "TealBlock", "TealCompileError", @@ -213,4 +212,5 @@ __all__ = [ "UnaryExpr", "While", "WideRatio", + "WideRatio", ] diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index 1c4188c92..46a16fe43 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -121,7 +121,6 @@ # misc from .scratch import ( - # DynamicSlot, ScratchSlot, ScratchLoad, ScratchStore, @@ -134,71 +133,30 @@ __all__ = [ "AccountParam", "Add", + "Add", "Addr", "And", + "And", "App", "AppField", "AppParam", "Approve", + "Approve", "Arg", "Array", "Assert", + "Assert", "AssetHolding", "AssetParam", "Balance", "BinaryExpr", - "Add", - "Minus", - "Mul", - "Div", - "Mod", - "Exp", - "Divw", + "BitLen", "BitwiseAnd", "BitwiseNot", "BitwiseOr", "BitwiseXor", - "ShiftLeft", - "ShiftRight", - "Eq", - "Neq", - "Lt", - "Le", - "Gt", - "Ge", - "GetBit", - "GetByte", - "Ed25519Verify", - "Substring", - "Extract", - "Suffix", - "SetBit", - "SetByte", - "NaryExpr", - "And", - "Or", - "Concat", - "WideRatio", - "If", - "Cond", - "Seq", - "Assert", - "Err", - "Return", - "Approve", - "Reject", - "Subroutine", - "SubroutineDefinition", - "SubroutineDeclaration", - "SubroutineCall", - "SubroutineFnWrapper", - "ScratchSlot", - "ScratchLoad", - "ScratchStore", - "ScratchStackStore", - "ScratchVar", - "MaybeValue", - "MultiValue", + "Btoi", + "Bytes", "BytesAdd", "BytesAnd", "BytesDiv", @@ -217,24 +175,36 @@ "BytesXor", "BytesZero", "Concat", + "Concat", + "Cond", "Cond", "Continue", "Div", - # "DynamicSlot", + "Div", + "Divw", + "DynamicScratchVar", + "Ed25519Verify", "Ed25519Verify", "EnumInt", "Eq", + "Eq", "Err", + "Err", + "Exp", "Exp", "Expr", "Extract", + "Extract", "ExtractUint16", "ExtractUint32", "ExtractUint64", "For", "Ge", + "Ge", "GeneratedID", "GetBit", + "GetBit", + "GetByte", "GetByte", "Gitxn", "GitxnaExpr", @@ -242,10 +212,12 @@ "Global", "GlobalField", "Gt", + "Gt", "Gtxn", "GtxnaExpr", "GtxnExpr", "If", + "If", "ImportScratchValue", "InnerTxn", "InnerTxnAction", @@ -255,45 +227,73 @@ "Itob", "Keccak256", "Le", + "Le", "LeafExpr", "Len", "Log", "Lt", + "Lt", + "MaybeValue", "MaybeValue", "MethodSignature", "MinBalance", "Minus", + "Minus", "Mod", + "Mod", + "Mul", "Mul", + "MultiValue", "NaryExpr", + "NaryExpr", + "Neq", "Neq", "Nonce", "Not", "OnComplete", "Or", - "DynamicScratchVar", + "Or", "Pop", "Reject", + "Reject", + "Return", "Return", "ScratchLoad", + "ScratchLoad", + "ScratchSlot", "ScratchSlot", "ScratchStackStore", + "ScratchStackStore", "ScratchStore", + "ScratchStore", + "ScratchVar", "ScratchVar", "Seq", + "Seq", "SetBit", + "SetBit", + "SetByte", "SetByte", "Sha256", "Sha512_256", "ShiftLeft", + "ShiftLeft", + "ShiftRight", "ShiftRight", "Sqrt", "Subroutine", + "Subroutine", + "SubroutineCall", "SubroutineCall", "SubroutineDeclaration", + "SubroutineDeclaration", + "SubroutineDefinition", "SubroutineDefinition", "SubroutineFnWrapper", + "SubroutineFnWrapper", "Substring", + "Substring", + "Suffix", "Suffix", "Tmpl", "Txn", @@ -307,4 +307,5 @@ "UnaryExpr", "While", "WideRatio", + "WideRatio", ] From 285980d0b2cce507b7c537a2d45fa9a13276cc06 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 17:24:24 -0600 Subject: [PATCH 30/85] revert --- pyteal/compiler/scratchslots.py | 14 -------------- tests/pass_by_ref_test.py | 2 +- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/pyteal/compiler/scratchslots.py b/pyteal/compiler/scratchslots.py index 7f519e702..04fd781dd 100644 --- a/pyteal/compiler/scratchslots.py +++ b/pyteal/compiler/scratchslots.py @@ -106,13 +106,6 @@ def assignScratchSlotsToSubroutines( raise TealInternalError( "Slot ID {} has been assigned multiple times".format(slot.id) ) - - if not isinstance(slot.id, int): - raise TealInternalError( - "slot id {} was expected to be an int but was instead of type {}".format( - slot.id, type(slot.id) - ) - ) slotIds.add(slot.id) nextSlotIndex = 0 @@ -122,13 +115,6 @@ def assignScratchSlotsToSubroutines( nextSlotIndex += 1 if slot.isReservedSlot: - if not isinstance(slot.id, int): - raise TealInternalError( - "slot id {} was expected to be an int but was instead of type {}".format( - slot.id, type(slot.id) - ) - ) - # Slot ids under 256 are manually reserved slots slotAssignments[slot] = slot.id else: diff --git a/tests/pass_by_ref_test.py b/tests/pass_by_ref_test.py index 07502a371..91eded571 100644 --- a/tests/pass_by_ref_test.py +++ b/tests/pass_by_ref_test.py @@ -1,6 +1,6 @@ from pyteal import * -from compile_asserts import assert_new_v_old, compile_and_save +from .compile_asserts import assert_new_v_old, compile_and_save #### TESTS FOR PyTEAL THAT PREDATES PASS-BY-REF From 8e52b6032d8aac346e56606c27fe00ae7d73221b Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 17:25:47 -0600 Subject: [PATCH 31/85] revert --- pyteal/ir/tealblock.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/pyteal/ir/tealblock.py b/pyteal/ir/tealblock.py index 836b3ebe1..27b8d778a 100644 --- a/pyteal/ir/tealblock.py +++ b/pyteal/ir/tealblock.py @@ -1,8 +1,8 @@ from abc import ABC, abstractmethod -from typing import Union, List, Tuple, Set, Iterator, cast, TYPE_CHECKING +from typing import Optional, List, Tuple, Set, Iterator, cast, TYPE_CHECKING from .tealop import TealOp, Op -from ..errors import TealCompileError, TealInputError, TealInternalError +from ..errors import TealCompileError if TYPE_CHECKING: from ..ast import Expr, ScratchSlot @@ -82,14 +82,6 @@ def addIncoming( for b in self.getOutgoing(): b.addIncoming(self, visited) - @classmethod - def force_cast_int(cls, si: object, msg: str) -> int: - if not isinstance(si, int): - raise TealInternalError( - "expected {} of type {} to be an int: {}".format(si, type(si), msg) - ) - return cast(int, si) - def validateSlots( self, slotsInUse: Set["ScratchSlot"] = None, @@ -118,10 +110,7 @@ def validateSlots( errors.append(e) if not self.isTerminal(): - sortedSlots = sorted( - self.force_cast_int(slot.id, "compiled slots should have int id's") - for slot in currentSlotsInUse - ) + sortedSlots = sorted(slot.id for slot in currentSlotsInUse) for block in self.getOutgoing(): visitedKey = (id(block), *sortedSlots) if visitedKey in visited: From 6eca0226db47f855d8bb374bf4a0beec775cb7a4 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 17:41:42 -0600 Subject: [PATCH 32/85] pass unit tests locally --- pyteal/ast/__init__.py | 1 + pyteal/ast/subroutine.py | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index 46a16fe43..08cc152a1 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -155,6 +155,7 @@ "BitwiseNot", "BitwiseOr", "BitwiseXor", + "Break", "Btoi", "Bytes", "BytesAdd", diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 787e7f40f..16929a6ad 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -65,7 +65,7 @@ def __init__( if var != "return" and var_type not in self.PARAM_TYPES: raise TealInputError( "Function has parameter {} of disallowed type {}. Only the types {} are allowed".format( - var, var_type, self.param_type_names() + var, var_type, self.PARAM_TYPES ) ) if var_type is ScratchVar: @@ -334,7 +334,8 @@ def var_n_loaded(param): return argVar, loaded - argumentVars, loadedArgs = zip(*map(var_n_loaded, subroutine.arguments())) + args = subroutine.arguments() + argumentVars, loadedArgs = zip(*map(var_n_loaded, args)) if args else ([], []) # Arg usage "B" supplied to build an AST from the user-defined PyTEAL function: subroutineBody = subroutine.implementation(*loadedArgs) From f67947a0916357766c424db1dbe6c64d4b3983e9 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 17:59:28 -0600 Subject: [PATCH 33/85] mypy --- pyteal/ast/gtxn_test.py | 2 ++ pyteal/ast/multi_test.py | 6 ++---- pyteal/ast/scratch.py | 8 +++++--- pyteal/ast/subroutine.py | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pyteal/ast/gtxn_test.py b/pyteal/ast/gtxn_test.py index 9b895d100..1c5f9d103 100644 --- a/pyteal/ast/gtxn_test.py +++ b/pyteal/ast/gtxn_test.py @@ -2,6 +2,8 @@ from .. import * +from .bytes import Bytes + teal6Options = CompileOptions(version=6) diff --git a/pyteal/ast/multi_test.py b/pyteal/ast/multi_test.py index 665d7517b..a41cdbc44 100644 --- a/pyteal/ast/multi_test.py +++ b/pyteal/ast/multi_test.py @@ -1,10 +1,8 @@ -import pytest +from typing import List from .. import * -from typing import List -# this is not necessary but mypy complains if it's not included -from .. import CompileOptions +from .bytes import Bytes options = CompileOptions() diff --git a/pyteal/ast/scratch.py b/pyteal/ast/scratch.py index 9acd9055a..8d4d38ec2 100644 --- a/pyteal/ast/scratch.py +++ b/pyteal/ast/scratch.py @@ -1,4 +1,4 @@ -from typing import cast, TYPE_CHECKING, Union +from typing import cast, TYPE_CHECKING, Optional from ..types import TealType from ..config import NUM_SLOTS @@ -104,7 +104,7 @@ class ScratchLoad(Expr): def __init__( self, - slot: ScratchSlot, + slot: Optional[ScratchSlot], type: TealType = TealType.anytype, index_expression: Expr = None, ): @@ -158,7 +158,9 @@ def has_return(self): class ScratchStore(Expr): """Expression to store a value in scratch space.""" - def __init__(self, slot: ScratchSlot, value: Expr, index_expression: Expr = None): + def __init__( + self, slot: Optional[ScratchSlot], value: Expr, index_expression: Expr = None + ): """Create a new ScratchStore expression. Args: diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 16929a6ad..4718de918 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -93,7 +93,7 @@ def argumentCount(self) -> int: def arguments(self) -> List[str]: return list(self.implementationParams.keys()) - def invoke(self, args: List[Expr]) -> "SubroutineCall": + def invoke(self, args: List[Union[Expr, ScratchVar]]) -> "SubroutineCall": if len(args) != self.argumentCount(): raise TealInputError( "Incorrect number of arguments for subroutine call. Expected {} arguments, got {}".format( From 992d7dd1c1e041682f9a71497dc6889c69e5486b Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 19:10:27 -0600 Subject: [PATCH 34/85] comments --- pyteal/ast/scratchvar.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pyteal/ast/scratchvar.py b/pyteal/ast/scratchvar.py index a52f1d1b6..6d2545a7a 100644 --- a/pyteal/ast/scratchvar.py +++ b/pyteal/ast/scratchvar.py @@ -93,7 +93,16 @@ def __init__( ttype: TealType = TealType.anytype, indexer: ScratchVar = None, ): + """Create a new DynamicScratchVar which references other ScratchVar's + Args: + ttype (optional): The type that this variable can hold. Defaults to TealType.anytype. + indexer (optional): A ScratchVar object that holds the _index_ that this DynamicScratchVar is + using to look up other slots. It is _NOT RECOMMENDED_ that a developer provide this optional + argument, but instead, to let the compiler construct one dynamically. In rare situations + it could be useful to provide the ability to explicitly provide the indexer. For example, + this is useful in handling pass-by-reference subroutine call semantics. + """ self.type = ttype if indexer is None: indexer = ScratchVar(TealType.uint64) @@ -121,20 +130,28 @@ def _set_indexer(self, indexer: ScratchVar) -> None: self.slot = self.indexer.slot def set_index(self, indexVar: ScratchVar) -> Expr: + """Set the this DynamicScratchVar to reference the provided `indexVar`. + Followup `store`, `load` and `index` operations will use the proved `indexVar` until + `set_index()` is called again on different ScratchVar. + """ return self.indexer.store(indexVar.index()) def store(self, value: Expr) -> Expr: + """Store the value in the referenced ScratchVar.""" index = ScratchLoad(self.indexer.slot, TealType.uint64) return ScratchStore(slot=None, value=value, index_expression=index) def load(self) -> ScratchLoad: + """Load the current value from the referenced ScratchVar.""" index = ScratchLoad(self.indexer.slot, TealType.uint64) return ScratchLoad(slot=None, type=self.type, index_expression=index) def index(self) -> Expr: + """Get the index of the referenced ScratchVar.""" return self.indexer.load() def internal_index(self) -> Expr: + """Get the index of _this_ DynamicScratchVar, as opposed to that of the referenced ScratchVar.""" return self.indexer.index() From 3c14bc44c32cb50ce04b57cff7162c7fc1ad7116 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 19:14:31 -0600 Subject: [PATCH 35/85] better comment --- pyteal/ast/scratchvar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/scratchvar.py b/pyteal/ast/scratchvar.py index 6d2545a7a..3d50cfeda 100644 --- a/pyteal/ast/scratchvar.py +++ b/pyteal/ast/scratchvar.py @@ -101,7 +101,7 @@ def __init__( using to look up other slots. It is _NOT RECOMMENDED_ that a developer provide this optional argument, but instead, to let the compiler construct one dynamically. In rare situations it could be useful to provide the ability to explicitly provide the indexer. For example, - this is useful in handling pass-by-reference subroutine call semantics. + this is needed in the internal PyTEAL compiler code for handling pass-by-reference semantics. """ self.type = ttype if indexer is None: From 1c6916ddf6a9f04090aa93262ab5b64cda186c67 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 19:21:37 -0600 Subject: [PATCH 36/85] better wording --- pyteal/ast/scratchvar.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyteal/ast/scratchvar.py b/pyteal/ast/scratchvar.py index 3d50cfeda..832c5c12a 100644 --- a/pyteal/ast/scratchvar.py +++ b/pyteal/ast/scratchvar.py @@ -131,8 +131,8 @@ def _set_indexer(self, indexer: ScratchVar) -> None: def set_index(self, indexVar: ScratchVar) -> Expr: """Set the this DynamicScratchVar to reference the provided `indexVar`. - Followup `store`, `load` and `index` operations will use the proved `indexVar` until - `set_index()` is called again on different ScratchVar. + Followup `store`, `load` and `index` operations will use the provided `indexVar` until + `set_index()` is called again to reset the referenced ScratchVar. """ return self.indexer.store(indexVar.index()) From eb33cfe2cb4672ca09eba7d2a68f5007f94a3bd5 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 19:23:04 -0600 Subject: [PATCH 37/85] grammar --- pyteal/compiler/zompiler_test.NOTpy | 1677 +++++++++++++++++++++++++++ 1 file changed, 1677 insertions(+) create mode 100644 pyteal/compiler/zompiler_test.NOTpy diff --git a/pyteal/compiler/zompiler_test.NOTpy b/pyteal/compiler/zompiler_test.NOTpy new file mode 100644 index 000000000..c5972f611 --- /dev/null +++ b/pyteal/compiler/zompiler_test.NOTpy @@ -0,0 +1,1677 @@ +import pytest + +from .. import * + +# this is not necessary but mypy complains if it's not included +from ..ast import * + + +def test_compile_single(): + expr = Int(1) + + expected = """ +#pragma version 2 +int 1 +return +""".strip() + actual_application = compileTeal(expr, Mode.Application) + actual_signature = compileTeal(expr, Mode.Signature) + + assert actual_application == actual_signature + assert actual_application == expected + + +def test_compile_sequence(): + expr = Seq([Pop(Int(1)), Pop(Int(2)), Int(3) + Int(4)]) + + expected = """ +#pragma version 2 +int 1 +pop +int 2 +pop +int 3 +int 4 ++ +return +""".strip() + actual_application = compileTeal(expr, Mode.Application) + actual_signature = compileTeal(expr, Mode.Signature) + + assert actual_application == actual_signature + assert actual_application == expected + + +def test_compile_branch(): + expr = If(Int(1)).Then(Int(2)).Else(Int(3)) + + expected = """ +#pragma version 2 +int 1 +bnz main_l2 +int 3 +b main_l3 +main_l2: +int 2 +main_l3: +return +""".strip() + actual_application = compileTeal(expr, Mode.Application) + actual_signature = compileTeal(expr, Mode.Signature) + + assert actual_application == actual_signature + assert actual_application == expected + + +def test_compile_branch_multiple(): + expr = If(Int(1)).Then(Int(2)).ElseIf(Int(3)).Then(Int(4)).Else(Int(5)) + + expected = """ +#pragma version 2 +int 1 +bnz main_l4 +int 3 +bnz main_l3 +int 5 +b main_l5 +main_l3: +int 4 +b main_l5 +main_l4: +int 2 +main_l5: +return +""".strip() + actual_application = compileTeal(expr, Mode.Application) + actual_signature = compileTeal(expr, Mode.Signature) + + assert actual_application == actual_signature + assert actual_application == expected + + +def test_empty_branch(): + program = Seq( + [ + If(Txn.application_id() == Int(0)).Then(Seq()), + Approve(), + ] + ) + + expected = """#pragma version 5 +txn ApplicationID +int 0 +== +bnz main_l1 +main_l1: +int 1 +return + """.strip() + actual = compileTeal(program, Mode.Application, version=5, assembleConstants=False) + assert actual == expected + + +def test_compile_mode(): + expr = App.globalGet(Bytes("key")) + + expected = """ +#pragma version 2 +byte "key" +app_global_get +return +""".strip() + actual_application = compileTeal(expr, Mode.Application) + + assert actual_application == expected + + with pytest.raises(TealInputError): + compileTeal(expr, Mode.Signature) + + +def test_compile_version_invalid(): + expr = Int(1) + + with pytest.raises(TealInputError): + compileTeal(expr, Mode.Signature, version=1) # too small + + with pytest.raises(TealInputError): + compileTeal(expr, Mode.Signature, version=7) # too large + + with pytest.raises(TealInputError): + compileTeal(expr, Mode.Signature, version=2.0) # decimal + + +def test_compile_version_2(): + expr = Int(1) + + expected = """ +#pragma version 2 +int 1 +return +""".strip() + actual = compileTeal(expr, Mode.Signature, version=2) + assert actual == expected + + +def test_compile_version_default(): + expr = Int(1) + + actual_default = compileTeal(expr, Mode.Signature) + actual_version_2 = compileTeal(expr, Mode.Signature, version=2) + assert actual_default == actual_version_2 + + +def test_compile_version_3(): + expr = Int(1) + + expected = """ +#pragma version 3 +int 1 +return +""".strip() + actual = compileTeal(expr, Mode.Signature, version=3) + assert actual == expected + + +def test_compile_version_4(): + expr = Int(1) + + expected = """ +#pragma version 4 +int 1 +return +""".strip() + actual = compileTeal(expr, Mode.Signature, version=4) + assert actual == expected + + +def test_compile_version_5(): + expr = Int(1) + expected = """ +#pragma version 5 +int 1 +return +""".strip() + actual = compileTeal(expr, Mode.Signature, version=5) + assert actual == expected + + +def test_compile_version_6(): + expr = Int(1) + expected = """ +#pragma version 6 +int 1 +return +""".strip() + actual = compileTeal(expr, Mode.Signature, version=6) + assert actual == expected + + +def test_slot_load_before_store(): + + program = AssetHolding.balance(Int(0), Int(0)).value() + with pytest.raises(TealInternalError): + compileTeal(program, Mode.Application, version=2) + + program = AssetHolding.balance(Int(0), Int(0)).hasValue() + with pytest.raises(TealInternalError): + compileTeal(program, Mode.Application, version=2) + + program = App.globalGetEx(Int(0), Bytes("key")).value() + with pytest.raises(TealInternalError): + compileTeal(program, Mode.Application, version=2) + + program = App.globalGetEx(Int(0), Bytes("key")).hasValue() + with pytest.raises(TealInternalError): + compileTeal(program, Mode.Application, version=2) + + program = ScratchVar().load() + with pytest.raises(TealInternalError): + compileTeal(program, Mode.Application, version=2) + + +def test_assign_scratch_slots(): + myScratch = ScratchVar(TealType.uint64) + otherScratch = ScratchVar(TealType.uint64, 1) + anotherScratch = ScratchVar(TealType.uint64, 0) + lastScratch = ScratchVar(TealType.uint64) + prog = Seq( + [ + myScratch.store(Int(5)), # Slot 2 + otherScratch.store(Int(0)), # Slot 1 + anotherScratch.store(Int(7)), # Slot 0 + lastScratch.store(Int(9)), # Slot 3 + Approve(), + ] + ) + + expected = """ +#pragma version 4 +int 5 +store 2 +int 0 +store 1 +int 7 +store 0 +int 9 +store 3 +int 1 +return +""".strip() + actual = compileTeal(prog, mode=Mode.Signature, version=4) + assert actual == expected + + +def test_scratchvar_double_assign_invalid(): + myvar = ScratchVar(TealType.uint64, 10) + otherVar = ScratchVar(TealType.uint64, 10) + prog = Seq([myvar.store(Int(5)), otherVar.store(Int(0)), Approve()]) + with pytest.raises(TealInternalError): + compileTeal(prog, mode=Mode.Signature, version=4) + + +def test_assembleConstants(): + program = Itob(Int(1) + Int(1) + Tmpl.Int("TMPL_VAR")) == Concat( + Bytes("test"), Bytes("test"), Bytes("test2") + ) + + expectedNoAssemble = """ +#pragma version 3 +int 1 +int 1 ++ +int TMPL_VAR ++ +itob +byte "test" +byte "test" +concat +byte "test2" +concat +== +return +""".strip() + actualNoAssemble = compileTeal( + program, Mode.Application, version=3, assembleConstants=False + ) + assert expectedNoAssemble == actualNoAssemble + + expectedAssemble = """ +#pragma version 3 +intcblock 1 +bytecblock 0x74657374 +intc_0 // 1 +intc_0 // 1 ++ +pushint TMPL_VAR // TMPL_VAR ++ +itob +bytec_0 // "test" +bytec_0 // "test" +concat +pushbytes 0x7465737432 // "test2" +concat +== +return +""".strip() + actualAssemble = compileTeal( + program, Mode.Application, version=3, assembleConstants=True + ) + assert expectedAssemble == actualAssemble + + with pytest.raises(TealInternalError): + compileTeal(program, Mode.Application, version=2, assembleConstants=True) + + +def test_compile_while(): + i = ScratchVar() + program = Seq( + [ + i.store(Int(0)), + While(i.load() < Int(2)).Do(Seq([i.store(i.load() + Int(1))])), + Approve(), + ] + ) + + expected = """ + #pragma version 4 +int 0 +store 0 +main_l1: +load 0 +int 2 +< +bz main_l3 +load 0 +int 1 ++ +store 0 +b main_l1 +main_l3: +int 1 +return + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert expected == actual + + # nested + i = ScratchVar() + j = ScratchVar() + + program = Seq( + [ + i.store(Int(0)), + While(i.load() < Int(2)).Do( + Seq( + [ + j.store(Int(0)), + While(j.load() < Int(5)).Do(Seq([j.store(j.load() + Int(1))])), + i.store(i.load() + Int(1)), + ] + ) + ), + Approve(), + ] + ) + + expected = """#pragma version 4 +int 0 +store 0 +main_l1: +load 0 +int 2 +< +bz main_l6 +int 0 +store 1 +main_l3: +load 1 +int 5 +< +bnz main_l5 +load 0 +int 1 ++ +store 0 +b main_l1 +main_l5: +load 1 +int 1 ++ +store 1 +b main_l3 +main_l6: +int 1 +return + """.strip() + + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert expected == actual + + +def test_compile_for(): + i = ScratchVar() + program = Seq( + [ + For(i.store(Int(0)), i.load() < Int(10), i.store(i.load() + Int(1))).Do( + Seq([App.globalPut(Itob(i.load()), i.load() * Int(2))]) + ), + Approve(), + ] + ) + + expected = """#pragma version 4 +int 0 +store 0 +main_l1: +load 0 +int 10 +< +bz main_l3 +load 0 +itob +load 0 +int 2 +* +app_global_put +load 0 +int 1 ++ +store 0 +b main_l1 +main_l3: +int 1 +return + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert expected == actual + + # nested + i = ScratchVar() + j = ScratchVar() + program = Seq( + [ + For(i.store(Int(0)), i.load() < Int(10), i.store(i.load() + Int(1))).Do( + Seq( + [ + For( + j.store(Int(0)), + j.load() < Int(4), + j.store(j.load() + Int(2)), + ).Do(Seq([App.globalPut(Itob(j.load()), j.load() * Int(2))])) + ] + ) + ), + Approve(), + ] + ) + + expected = """#pragma version 4 +int 0 +store 0 +main_l1: +load 0 +int 10 +< +bz main_l6 +int 0 +store 1 +main_l3: +load 1 +int 4 +< +bnz main_l5 +load 0 +int 1 ++ +store 0 +b main_l1 +main_l5: +load 1 +itob +load 1 +int 2 +* +app_global_put +load 1 +int 2 ++ +store 1 +b main_l3 +main_l6: +int 1 +return + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert expected == actual + + +def test_compile_break(): + + # While + i = ScratchVar() + program = Seq( + [ + i.store(Int(0)), + While(i.load() < Int(3)).Do( + Seq([If(i.load() == Int(2), Break()), i.store(i.load() + Int(1))]) + ), + Approve(), + ] + ) + + expected = """#pragma version 4 +int 0 +store 0 +main_l1: +load 0 +int 3 +< +bz main_l4 +load 0 +int 2 +== +bnz main_l4 +load 0 +int 1 ++ +store 0 +b main_l1 +main_l4: +int 1 +return + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert expected == actual + + # For + i = ScratchVar() + program = Seq( + [ + For(i.store(Int(0)), i.load() < Int(10), i.store(i.load() + Int(1))).Do( + Seq( + [ + If(i.load() == Int(4), Break()), + App.globalPut(Itob(i.load()), i.load() * Int(2)), + ] + ) + ), + Approve(), + ] + ) + + expected = """#pragma version 4 +int 0 +store 0 +main_l1: +load 0 +int 10 +< +bz main_l4 +load 0 +int 4 +== +bnz main_l4 +load 0 +itob +load 0 +int 2 +* +app_global_put +load 0 +int 1 ++ +store 0 +b main_l1 +main_l4: +int 1 +return + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert expected == actual + + +def test_compile_continue(): + # While + i = ScratchVar() + program = Seq( + [ + i.store(Int(0)), + While(i.load() < Int(3)).Do( + Seq([If(i.load() == Int(2), Continue()), i.store(i.load() + Int(1))]) + ), + Approve(), + ] + ) + + expected = """#pragma version 4 +int 0 +store 0 +main_l1: +load 0 +int 3 +< +bz main_l4 +main_l2: +load 0 +int 2 +== +bnz main_l2 +load 0 +int 1 ++ +store 0 +b main_l1 +main_l4: +int 1 +return + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert expected == actual + + # For + i = ScratchVar() + program = Seq( + [ + For(i.store(Int(0)), i.load() < Int(10), i.store(i.load() + Int(1))).Do( + Seq( + [ + If(i.load() == Int(4), Continue()), + App.globalPut(Itob(i.load()), i.load() * Int(2)), + ] + ) + ), + Approve(), + ] + ) + + expected = """#pragma version 4 +int 0 +store 0 +main_l1: +load 0 +int 10 +< +bz main_l5 +load 0 +int 4 +== +bnz main_l4 +load 0 +itob +load 0 +int 2 +* +app_global_put +main_l4: +load 0 +int 1 ++ +store 0 +b main_l1 +main_l5: +int 1 +return + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert expected == actual + + +def test_compile_continue_break_nested(): + + i = ScratchVar() + program = Seq( + [ + i.store(Int(0)), + While(i.load() < Int(10)).Do( + Seq( + [ + i.store(i.load() + Int(1)), + If(i.load() < Int(4), Continue(), Break()), + ] + ) + ), + Approve(), + ] + ) + + expected = """#pragma version 4 +int 0 +store 0 +load 0 +int 10 +< +bz main_l2 +main_l1: +load 0 +int 1 ++ +store 0 +load 0 +int 4 +< +bnz main_l1 +main_l2: +int 1 +return + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert expected == actual + + i = ScratchVar() + program = Seq( + [ + i.store(Int(0)), + While(i.load() < Int(10)).Do( + Seq( + [ + If(i.load() == Int(8), Break()), + While(i.load() < Int(6)).Do( + Seq( + [ + If(i.load() == Int(3), Break()), + i.store(i.load() + Int(1)), + ] + ) + ), + If(i.load() < Int(5), Continue()), + i.store(i.load() + Int(1)), + ] + ) + ), + Approve(), + ] + ) + + expected = """#pragma version 4 +int 0 +store 0 +main_l1: +load 0 +int 10 +< +bz main_l8 +main_l2: +load 0 +int 8 +== +bnz main_l8 +main_l3: +load 0 +int 6 +< +bnz main_l6 +main_l4: +load 0 +int 5 +< +bnz main_l2 +load 0 +int 1 ++ +store 0 +b main_l1 +main_l6: +load 0 +int 3 +== +bnz main_l4 +load 0 +int 1 ++ +store 0 +b main_l3 +main_l8: +int 1 +return +""".strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert expected == actual + + +def test_compile_subroutine_unsupported(): + @Subroutine(TealType.none) + def storeValue(value: Expr) -> Expr: + return App.globalPut(Bytes("key"), value) + + program = Seq( + [ + If(Txn.sender() == Global.creator_address()).Then( + storeValue(Txn.application_args[0]) + ), + Approve(), + ] + ) + + with pytest.raises(TealInputError): + compileTeal(program, Mode.Application, version=3) + + +def test_compile_subroutine_no_return(): + @Subroutine(TealType.none) + def storeValue(value: Expr) -> Expr: + return App.globalPut(Bytes("key"), value) + + program = Seq( + [ + If(Txn.sender() == Global.creator_address()).Then( + storeValue(Txn.application_args[0]) + ), + Approve(), + ] + ) + + expected = """#pragma version 4 +txn Sender +global CreatorAddress +== +bz main_l2 +txna ApplicationArgs 0 +callsub storeValue_0 +main_l2: +int 1 +return + +// storeValue +storeValue_0: +store 0 +byte "key" +load 0 +app_global_put +retsub + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert actual == expected + + +def test_compile_subroutine_with_return(): + @Subroutine(TealType.none) + def storeValue(value: Expr) -> Expr: + return App.globalPut(Bytes("key"), value) + + @Subroutine(TealType.bytes) + def getValue() -> Expr: + return App.globalGet(Bytes("key")) + + program = Seq( + [ + If(Txn.sender() == Global.creator_address()).Then( + storeValue(Txn.application_args[0]) + ), + If(getValue() == Bytes("fail")).Then(Reject()), + Approve(), + ] + ) + + expected = """#pragma version 4 +txn Sender +global CreatorAddress +== +bnz main_l3 +main_l1: +callsub getValue_1 +byte "fail" +== +bz main_l4 +int 0 +return +main_l3: +txna ApplicationArgs 0 +callsub storeValue_0 +b main_l1 +main_l4: +int 1 +return + +// storeValue +storeValue_0: +store 0 +byte "key" +load 0 +app_global_put +retsub + +// getValue +getValue_1: +byte "key" +app_global_get +retsub + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert actual == expected + + +def test_compile_subroutine_many_args(): + @Subroutine(TealType.uint64) + def calculateSum( + a1: Expr, a2: Expr, a3: Expr, a4: Expr, a5: Expr, a6: Expr + ) -> Expr: + return a1 + a2 + a3 + a4 + a5 + a6 + + program = Return( + calculateSum(Int(1), Int(2), Int(3), Int(4), Int(5), Int(6)) + == Int(1 + 2 + 3 + 4 + 5 + 6) + ) + + expected = """#pragma version 4 +int 1 +int 2 +int 3 +int 4 +int 5 +int 6 +callsub calculateSum_0 +int 21 +== +return + +// calculateSum +calculateSum_0: +store 5 +store 4 +store 3 +store 2 +store 1 +store 0 +load 0 +load 1 ++ +load 2 ++ +load 3 ++ +load 4 ++ +load 5 ++ +retsub + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert actual == expected + + +def test_compile_subroutine_recursive(): + @Subroutine(TealType.uint64) + def isEven(i: Expr) -> Expr: + return ( + If(i == Int(0)) + .Then(Int(1)) + .ElseIf(i == Int(1)) + .Then(Int(0)) + .Else(isEven(i - Int(2))) + ) + + program = Return(isEven(Int(6))) + + expected = """#pragma version 4 +int 6 +callsub isEven_0 +return + +// isEven +isEven_0: +store 0 +load 0 +int 0 +== +bnz isEven_0_l4 +load 0 +int 1 +== +bnz isEven_0_l3 +load 0 +int 2 +- +load 0 +dig 1 +callsub isEven_0 +swap +store 0 +swap +pop +b isEven_0_l5 +isEven_0_l3: +int 0 +b isEven_0_l5 +isEven_0_l4: +int 1 +isEven_0_l5: +retsub + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert actual == expected + + +def test_compile_subroutine_recursive_5(): + @Subroutine(TealType.uint64) + def isEven(i: Expr) -> Expr: + return ( + If(i == Int(0)) + .Then(Int(1)) + .ElseIf(i == Int(1)) + .Then(Int(0)) + .Else(isEven(i - Int(2))) + ) + + program = Return(isEven(Int(6))) + + expected = """#pragma version 5 +int 6 +callsub isEven_0 +return + +// isEven +isEven_0: +store 0 +load 0 +int 0 +== +bnz isEven_0_l4 +load 0 +int 1 +== +bnz isEven_0_l3 +load 0 +int 2 +- +load 0 +swap +callsub isEven_0 +swap +store 0 +b isEven_0_l5 +isEven_0_l3: +int 0 +b isEven_0_l5 +isEven_0_l4: +int 1 +isEven_0_l5: +retsub + """.strip() + actual = compileTeal(program, Mode.Application, version=5, assembleConstants=False) + assert actual == expected + + +def test_compile_subroutine_recursive_multiple_args(): + @Subroutine(TealType.uint64) + def multiplyByAdding(a, b): + return ( + If(a == Int(0)) + .Then(Return(Int(0))) + .Else(Return(b + multiplyByAdding(a - Int(1), b))) + ) + + program = Return(multiplyByAdding(Int(3), Int(5))) + + expected = """#pragma version 4 +int 3 +int 5 +callsub multiplyByAdding_0 +return + +// multiplyByAdding +multiplyByAdding_0: +store 1 +store 0 +load 0 +int 0 +== +bnz multiplyByAdding_0_l2 +load 1 +load 0 +int 1 +- +load 1 +load 0 +load 1 +dig 3 +dig 3 +callsub multiplyByAdding_0 +store 0 +store 1 +load 0 +swap +store 0 +swap +pop +swap +pop ++ +retsub +multiplyByAdding_0_l2: +int 0 +retsub + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert actual == expected + + +def test_compile_subroutine_recursive_multiple_args_5(): + @Subroutine(TealType.uint64) + def multiplyByAdding(a, b): + return ( + If(a == Int(0)) + .Then(Return(Int(0))) + .Else(Return(b + multiplyByAdding(a - Int(1), b))) + ) + + program = Return(multiplyByAdding(Int(3), Int(5))) + + expected = """#pragma version 5 +int 3 +int 5 +callsub multiplyByAdding_0 +return + +// multiplyByAdding +multiplyByAdding_0: +store 1 +store 0 +load 0 +int 0 +== +bnz multiplyByAdding_0_l2 +load 1 +load 0 +int 1 +- +load 1 +load 0 +load 1 +uncover 3 +uncover 3 +callsub multiplyByAdding_0 +cover 2 +store 1 +store 0 ++ +retsub +multiplyByAdding_0_l2: +int 0 +retsub + """.strip() + actual = compileTeal(program, Mode.Application, version=5, assembleConstants=False) + assert actual == expected + + +def test_compile_subroutine_mutually_recursive(): + @Subroutine(TealType.uint64) + def isEven(i: Expr) -> Expr: + return If(i == Int(0), Int(1), Not(isOdd(i - Int(1)))) + + @Subroutine(TealType.uint64) + def isOdd(i: Expr) -> Expr: + return If(i == Int(0), Int(0), Not(isEven(i - Int(1)))) + + program = Return(isEven(Int(6))) + + expected = """#pragma version 4 +int 6 +callsub isEven_0 +return + +// isEven +isEven_0: +store 0 +load 0 +int 0 +== +bnz isEven_0_l2 +load 0 +int 1 +- +load 0 +dig 1 +callsub isOdd_1 +swap +store 0 +swap +pop +! +b isEven_0_l3 +isEven_0_l2: +int 1 +isEven_0_l3: +retsub + +// isOdd +isOdd_1: +store 1 +load 1 +int 0 +== +bnz isOdd_1_l2 +load 1 +int 1 +- +load 1 +dig 1 +callsub isEven_0 +swap +store 1 +swap +pop +! +b isOdd_1_l3 +isOdd_1_l2: +int 0 +isOdd_1_l3: +retsub + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert actual == expected + + +def test_compile_subroutine_mutually_recursive_5(): + @Subroutine(TealType.uint64) + def isEven(i: Expr) -> Expr: + return If(i == Int(0), Int(1), Not(isOdd(i - Int(1)))) + + @Subroutine(TealType.uint64) + def isOdd(i: Expr) -> Expr: + return If(i == Int(0), Int(0), Not(isEven(i - Int(1)))) + + program = Return(isEven(Int(6))) + + expected = """#pragma version 5 +int 6 +callsub isEven_0 +return + +// isEven +isEven_0: +store 0 +load 0 +int 0 +== +bnz isEven_0_l2 +load 0 +int 1 +- +load 0 +swap +callsub isOdd_1 +swap +store 0 +! +b isEven_0_l3 +isEven_0_l2: +int 1 +isEven_0_l3: +retsub + +// isOdd +isOdd_1: +store 1 +load 1 +int 0 +== +bnz isOdd_1_l2 +load 1 +int 1 +- +load 1 +swap +callsub isEven_0 +swap +store 1 +! +b isOdd_1_l3 +isOdd_1_l2: +int 0 +isOdd_1_l3: +retsub + """.strip() + actual = compileTeal(program, Mode.Application, version=5, assembleConstants=False) + assert actual == expected + + +def test_compile_loop_in_subroutine(): + @Subroutine(TealType.none) + def setState(value: Expr) -> Expr: + i = ScratchVar() + return For(i.store(Int(0)), i.load() < Int(10), i.store(i.load() + Int(1))).Do( + App.globalPut(Itob(i.load()), value) + ) + + program = Seq([setState(Bytes("value")), Approve()]) + + expected = """#pragma version 4 +byte "value" +callsub setState_0 +int 1 +return + +// setState +setState_0: +store 0 +int 0 +store 1 +setState_0_l1: +load 1 +int 10 +< +bz setState_0_l3 +load 1 +itob +load 0 +app_global_put +load 1 +int 1 ++ +store 1 +b setState_0_l1 +setState_0_l3: +retsub + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert actual == expected + + +def test_compile_subroutine_invalid_name(): + def tmp() -> Expr: + return Int(1) + + tmp.__name__ = "invalid-;)" + + program = Subroutine(TealType.uint64)(tmp)() + expected = """#pragma version 4 +callsub invalid_0 +return + +// invalid-;) +invalid_0: +int 1 +retsub + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert actual == expected + + +def test_compile_subroutine_assemble_constants(): + @Subroutine(TealType.none) + def storeValue(key: Expr, t1: Expr, t2: Expr, t3: Expr) -> Expr: + return App.globalPut(key, t1 + t2 + t3 + Int(10)) + + program = Seq( + [ + If(Txn.application_id() == Int(0)).Then( + storeValue( + Concat(Bytes("test"), Bytes("test"), Bytes("a")), + Int(1), + Int(1), + Int(3), + ) + ), + Approve(), + ] + ) + + expected = """#pragma version 4 +intcblock 1 +bytecblock 0x74657374 +txn ApplicationID +pushint 0 // 0 +== +bz main_l2 +bytec_0 // "test" +bytec_0 // "test" +concat +pushbytes 0x61 // "a" +concat +intc_0 // 1 +intc_0 // 1 +pushint 3 // 3 +callsub storeValue_0 +main_l2: +intc_0 // 1 +return + +// storeValue +storeValue_0: +store 3 +store 2 +store 1 +store 0 +load 0 +load 1 +load 2 ++ +load 3 ++ +pushint 10 // 10 ++ +app_global_put +retsub + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=True) + assert actual == expected + + +def test_compile_wide_ratio(): + cases = ( + ( + WideRatio([Int(2), Int(100)], [Int(5)]), + """#pragma version 5 +int 2 +int 100 +mulw +int 0 +int 5 +divmodw +pop +pop +swap +! +assert +return +""", + ), + ( + WideRatio([Int(2), Int(100)], [Int(10), Int(5)]), + """#pragma version 5 +int 2 +int 100 +mulw +int 10 +int 5 +mulw +divmodw +pop +pop +swap +! +assert +return +""", + ), + ( + WideRatio([Int(2), Int(100), Int(3)], [Int(10), Int(5)]), + """#pragma version 5 +int 2 +int 100 +mulw +int 3 +uncover 2 +dig 1 +* +cover 2 +mulw +cover 2 ++ +swap +int 10 +int 5 +mulw +divmodw +pop +pop +swap +! +assert +return +""", + ), + ( + WideRatio([Int(2), Int(100), Int(3)], [Int(10), Int(5), Int(6)]), + """#pragma version 5 +int 2 +int 100 +mulw +int 3 +uncover 2 +dig 1 +* +cover 2 +mulw +cover 2 ++ +swap +int 10 +int 5 +mulw +int 6 +uncover 2 +dig 1 +* +cover 2 +mulw +cover 2 ++ +swap +divmodw +pop +pop +swap +! +assert +return +""", + ), + ( + WideRatio([Int(2), Int(100), Int(3), Int(4)], [Int(10), Int(5), Int(6)]), + """#pragma version 5 +int 2 +int 100 +mulw +int 3 +uncover 2 +dig 1 +* +cover 2 +mulw +cover 2 ++ +swap +int 4 +uncover 2 +dig 1 +* +cover 2 +mulw +cover 2 ++ +swap +int 10 +int 5 +mulw +int 6 +uncover 2 +dig 1 +* +cover 2 +mulw +cover 2 ++ +swap +divmodw +pop +pop +swap +! +assert +return +""", + ), + ) + + for program, expected in cases: + actual = compileTeal( + program, Mode.Application, version=5, assembleConstants=False + ) + assert actual == expected.strip() + + +import inspect +import dis + + +def z1(foo): + x = { + "bytecode": dis.Bytecode(foo), + "code": inspect.getsource(foo), + "file": inspect.getsourcefile(foo), + "lines": inspect.getsourcelines(foo), + "mod": inspect.getmodule(foo), + "members": inspect.getmembers(foo), + "ismod": inspect.ismodule(foo), + "iscls": inspect.isclass(foo), + "ismeth": inspect.ismethod(foo), + "isfunc": inspect.isfunction(foo), + "isgenfunc": inspect.isgeneratorfunction(foo), + "isgen": inspect.isgenerator(foo), + "iscofunc": inspect.iscoroutinefunction(foo), + "isawait": inspect.isawaitable(foo), + "isasyncgenfunc": inspect.isasyncgenfunction(foo), + "isasyncgen": inspect.isasyncgen(foo), + "iscode": inspect.iscode(foo), + "isbuiltin": inspect.isbuiltin(foo), + "isroutine": inspect.isroutine(foo), + "isabstract": inspect.isabstract(foo), + } + lines = x["lines"] + x["line-map"] = list(zip(range(lines[1], lines[1] + len(lines[0])), lines[0])) + x["instructions"] = [ + instr.opname for instr in reversed([instr for instr in x["bytecode"]]) + ] + + return x + + +def test_z1(): + def f(x): + return x + 1339 + + def foo(): + y = f(42) + + # mom and dad + + # are very important + + return y + + foo_info = z1(foo) + + x = 42 + + +def test_zeph(): + @Subroutine(TealType.none) + def storeValue(key: Expr, t1: Expr, t2: Expr, t3: Expr) -> Expr: + return App.globalPut(key, t1 + t2 + t3 + Int(10)) + + program = Seq( + [ + If(Txn.application_id() == Int(0)).Then( + storeValue( + Concat(Bytes("test"), Bytes("test"), Bytes("a")), + Int(1), + Int(1), + Int(3), + ) + ), + Approve(), + ] + ) + + expected = """#pragma version 4 +intcblock 1 +bytecblock 0x74657374 +txn ApplicationID +pushint 0 // 0 +== +bz main_l2 +bytec_0 // "test" +bytec_0 // "test" +concat +pushbytes 0x61 // "a" +concat +intc_0 // 1 +intc_0 // 1 +pushint 3 // 3 +callsub storeValue_0 +main_l2: +intc_0 // 1 +return + +// storeValue +storeValue_0: +store 3 +store 2 +store 1 +store 0 +load 0 +load 1 +load 2 ++ +load 3 ++ +pushint 10 // 10 ++ +app_global_put +retsub + """.strip() + actual = compileTeal( + program, Mode.Application, version=4, assembleConstants=True, sourceMap=True + ) + assert actual == expected From f3bcc6b79aaf8ee5af3277b22962552805ac7bb3 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 19:24:14 -0600 Subject: [PATCH 38/85] remove mistakenly included file --- pyteal/ast/scratchvar.py | 2 +- pyteal/compiler/zompiler_test.NOTpy | 1677 --------------------------- 2 files changed, 1 insertion(+), 1678 deletions(-) delete mode 100644 pyteal/compiler/zompiler_test.NOTpy diff --git a/pyteal/ast/scratchvar.py b/pyteal/ast/scratchvar.py index 832c5c12a..2d23aeba9 100644 --- a/pyteal/ast/scratchvar.py +++ b/pyteal/ast/scratchvar.py @@ -130,7 +130,7 @@ def _set_indexer(self, indexer: ScratchVar) -> None: self.slot = self.indexer.slot def set_index(self, indexVar: ScratchVar) -> Expr: - """Set the this DynamicScratchVar to reference the provided `indexVar`. + """Set this DynamicScratchVar to reference the provided `indexVar`. Followup `store`, `load` and `index` operations will use the provided `indexVar` until `set_index()` is called again to reset the referenced ScratchVar. """ diff --git a/pyteal/compiler/zompiler_test.NOTpy b/pyteal/compiler/zompiler_test.NOTpy deleted file mode 100644 index c5972f611..000000000 --- a/pyteal/compiler/zompiler_test.NOTpy +++ /dev/null @@ -1,1677 +0,0 @@ -import pytest - -from .. import * - -# this is not necessary but mypy complains if it's not included -from ..ast import * - - -def test_compile_single(): - expr = Int(1) - - expected = """ -#pragma version 2 -int 1 -return -""".strip() - actual_application = compileTeal(expr, Mode.Application) - actual_signature = compileTeal(expr, Mode.Signature) - - assert actual_application == actual_signature - assert actual_application == expected - - -def test_compile_sequence(): - expr = Seq([Pop(Int(1)), Pop(Int(2)), Int(3) + Int(4)]) - - expected = """ -#pragma version 2 -int 1 -pop -int 2 -pop -int 3 -int 4 -+ -return -""".strip() - actual_application = compileTeal(expr, Mode.Application) - actual_signature = compileTeal(expr, Mode.Signature) - - assert actual_application == actual_signature - assert actual_application == expected - - -def test_compile_branch(): - expr = If(Int(1)).Then(Int(2)).Else(Int(3)) - - expected = """ -#pragma version 2 -int 1 -bnz main_l2 -int 3 -b main_l3 -main_l2: -int 2 -main_l3: -return -""".strip() - actual_application = compileTeal(expr, Mode.Application) - actual_signature = compileTeal(expr, Mode.Signature) - - assert actual_application == actual_signature - assert actual_application == expected - - -def test_compile_branch_multiple(): - expr = If(Int(1)).Then(Int(2)).ElseIf(Int(3)).Then(Int(4)).Else(Int(5)) - - expected = """ -#pragma version 2 -int 1 -bnz main_l4 -int 3 -bnz main_l3 -int 5 -b main_l5 -main_l3: -int 4 -b main_l5 -main_l4: -int 2 -main_l5: -return -""".strip() - actual_application = compileTeal(expr, Mode.Application) - actual_signature = compileTeal(expr, Mode.Signature) - - assert actual_application == actual_signature - assert actual_application == expected - - -def test_empty_branch(): - program = Seq( - [ - If(Txn.application_id() == Int(0)).Then(Seq()), - Approve(), - ] - ) - - expected = """#pragma version 5 -txn ApplicationID -int 0 -== -bnz main_l1 -main_l1: -int 1 -return - """.strip() - actual = compileTeal(program, Mode.Application, version=5, assembleConstants=False) - assert actual == expected - - -def test_compile_mode(): - expr = App.globalGet(Bytes("key")) - - expected = """ -#pragma version 2 -byte "key" -app_global_get -return -""".strip() - actual_application = compileTeal(expr, Mode.Application) - - assert actual_application == expected - - with pytest.raises(TealInputError): - compileTeal(expr, Mode.Signature) - - -def test_compile_version_invalid(): - expr = Int(1) - - with pytest.raises(TealInputError): - compileTeal(expr, Mode.Signature, version=1) # too small - - with pytest.raises(TealInputError): - compileTeal(expr, Mode.Signature, version=7) # too large - - with pytest.raises(TealInputError): - compileTeal(expr, Mode.Signature, version=2.0) # decimal - - -def test_compile_version_2(): - expr = Int(1) - - expected = """ -#pragma version 2 -int 1 -return -""".strip() - actual = compileTeal(expr, Mode.Signature, version=2) - assert actual == expected - - -def test_compile_version_default(): - expr = Int(1) - - actual_default = compileTeal(expr, Mode.Signature) - actual_version_2 = compileTeal(expr, Mode.Signature, version=2) - assert actual_default == actual_version_2 - - -def test_compile_version_3(): - expr = Int(1) - - expected = """ -#pragma version 3 -int 1 -return -""".strip() - actual = compileTeal(expr, Mode.Signature, version=3) - assert actual == expected - - -def test_compile_version_4(): - expr = Int(1) - - expected = """ -#pragma version 4 -int 1 -return -""".strip() - actual = compileTeal(expr, Mode.Signature, version=4) - assert actual == expected - - -def test_compile_version_5(): - expr = Int(1) - expected = """ -#pragma version 5 -int 1 -return -""".strip() - actual = compileTeal(expr, Mode.Signature, version=5) - assert actual == expected - - -def test_compile_version_6(): - expr = Int(1) - expected = """ -#pragma version 6 -int 1 -return -""".strip() - actual = compileTeal(expr, Mode.Signature, version=6) - assert actual == expected - - -def test_slot_load_before_store(): - - program = AssetHolding.balance(Int(0), Int(0)).value() - with pytest.raises(TealInternalError): - compileTeal(program, Mode.Application, version=2) - - program = AssetHolding.balance(Int(0), Int(0)).hasValue() - with pytest.raises(TealInternalError): - compileTeal(program, Mode.Application, version=2) - - program = App.globalGetEx(Int(0), Bytes("key")).value() - with pytest.raises(TealInternalError): - compileTeal(program, Mode.Application, version=2) - - program = App.globalGetEx(Int(0), Bytes("key")).hasValue() - with pytest.raises(TealInternalError): - compileTeal(program, Mode.Application, version=2) - - program = ScratchVar().load() - with pytest.raises(TealInternalError): - compileTeal(program, Mode.Application, version=2) - - -def test_assign_scratch_slots(): - myScratch = ScratchVar(TealType.uint64) - otherScratch = ScratchVar(TealType.uint64, 1) - anotherScratch = ScratchVar(TealType.uint64, 0) - lastScratch = ScratchVar(TealType.uint64) - prog = Seq( - [ - myScratch.store(Int(5)), # Slot 2 - otherScratch.store(Int(0)), # Slot 1 - anotherScratch.store(Int(7)), # Slot 0 - lastScratch.store(Int(9)), # Slot 3 - Approve(), - ] - ) - - expected = """ -#pragma version 4 -int 5 -store 2 -int 0 -store 1 -int 7 -store 0 -int 9 -store 3 -int 1 -return -""".strip() - actual = compileTeal(prog, mode=Mode.Signature, version=4) - assert actual == expected - - -def test_scratchvar_double_assign_invalid(): - myvar = ScratchVar(TealType.uint64, 10) - otherVar = ScratchVar(TealType.uint64, 10) - prog = Seq([myvar.store(Int(5)), otherVar.store(Int(0)), Approve()]) - with pytest.raises(TealInternalError): - compileTeal(prog, mode=Mode.Signature, version=4) - - -def test_assembleConstants(): - program = Itob(Int(1) + Int(1) + Tmpl.Int("TMPL_VAR")) == Concat( - Bytes("test"), Bytes("test"), Bytes("test2") - ) - - expectedNoAssemble = """ -#pragma version 3 -int 1 -int 1 -+ -int TMPL_VAR -+ -itob -byte "test" -byte "test" -concat -byte "test2" -concat -== -return -""".strip() - actualNoAssemble = compileTeal( - program, Mode.Application, version=3, assembleConstants=False - ) - assert expectedNoAssemble == actualNoAssemble - - expectedAssemble = """ -#pragma version 3 -intcblock 1 -bytecblock 0x74657374 -intc_0 // 1 -intc_0 // 1 -+ -pushint TMPL_VAR // TMPL_VAR -+ -itob -bytec_0 // "test" -bytec_0 // "test" -concat -pushbytes 0x7465737432 // "test2" -concat -== -return -""".strip() - actualAssemble = compileTeal( - program, Mode.Application, version=3, assembleConstants=True - ) - assert expectedAssemble == actualAssemble - - with pytest.raises(TealInternalError): - compileTeal(program, Mode.Application, version=2, assembleConstants=True) - - -def test_compile_while(): - i = ScratchVar() - program = Seq( - [ - i.store(Int(0)), - While(i.load() < Int(2)).Do(Seq([i.store(i.load() + Int(1))])), - Approve(), - ] - ) - - expected = """ - #pragma version 4 -int 0 -store 0 -main_l1: -load 0 -int 2 -< -bz main_l3 -load 0 -int 1 -+ -store 0 -b main_l1 -main_l3: -int 1 -return - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert expected == actual - - # nested - i = ScratchVar() - j = ScratchVar() - - program = Seq( - [ - i.store(Int(0)), - While(i.load() < Int(2)).Do( - Seq( - [ - j.store(Int(0)), - While(j.load() < Int(5)).Do(Seq([j.store(j.load() + Int(1))])), - i.store(i.load() + Int(1)), - ] - ) - ), - Approve(), - ] - ) - - expected = """#pragma version 4 -int 0 -store 0 -main_l1: -load 0 -int 2 -< -bz main_l6 -int 0 -store 1 -main_l3: -load 1 -int 5 -< -bnz main_l5 -load 0 -int 1 -+ -store 0 -b main_l1 -main_l5: -load 1 -int 1 -+ -store 1 -b main_l3 -main_l6: -int 1 -return - """.strip() - - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert expected == actual - - -def test_compile_for(): - i = ScratchVar() - program = Seq( - [ - For(i.store(Int(0)), i.load() < Int(10), i.store(i.load() + Int(1))).Do( - Seq([App.globalPut(Itob(i.load()), i.load() * Int(2))]) - ), - Approve(), - ] - ) - - expected = """#pragma version 4 -int 0 -store 0 -main_l1: -load 0 -int 10 -< -bz main_l3 -load 0 -itob -load 0 -int 2 -* -app_global_put -load 0 -int 1 -+ -store 0 -b main_l1 -main_l3: -int 1 -return - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert expected == actual - - # nested - i = ScratchVar() - j = ScratchVar() - program = Seq( - [ - For(i.store(Int(0)), i.load() < Int(10), i.store(i.load() + Int(1))).Do( - Seq( - [ - For( - j.store(Int(0)), - j.load() < Int(4), - j.store(j.load() + Int(2)), - ).Do(Seq([App.globalPut(Itob(j.load()), j.load() * Int(2))])) - ] - ) - ), - Approve(), - ] - ) - - expected = """#pragma version 4 -int 0 -store 0 -main_l1: -load 0 -int 10 -< -bz main_l6 -int 0 -store 1 -main_l3: -load 1 -int 4 -< -bnz main_l5 -load 0 -int 1 -+ -store 0 -b main_l1 -main_l5: -load 1 -itob -load 1 -int 2 -* -app_global_put -load 1 -int 2 -+ -store 1 -b main_l3 -main_l6: -int 1 -return - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert expected == actual - - -def test_compile_break(): - - # While - i = ScratchVar() - program = Seq( - [ - i.store(Int(0)), - While(i.load() < Int(3)).Do( - Seq([If(i.load() == Int(2), Break()), i.store(i.load() + Int(1))]) - ), - Approve(), - ] - ) - - expected = """#pragma version 4 -int 0 -store 0 -main_l1: -load 0 -int 3 -< -bz main_l4 -load 0 -int 2 -== -bnz main_l4 -load 0 -int 1 -+ -store 0 -b main_l1 -main_l4: -int 1 -return - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert expected == actual - - # For - i = ScratchVar() - program = Seq( - [ - For(i.store(Int(0)), i.load() < Int(10), i.store(i.load() + Int(1))).Do( - Seq( - [ - If(i.load() == Int(4), Break()), - App.globalPut(Itob(i.load()), i.load() * Int(2)), - ] - ) - ), - Approve(), - ] - ) - - expected = """#pragma version 4 -int 0 -store 0 -main_l1: -load 0 -int 10 -< -bz main_l4 -load 0 -int 4 -== -bnz main_l4 -load 0 -itob -load 0 -int 2 -* -app_global_put -load 0 -int 1 -+ -store 0 -b main_l1 -main_l4: -int 1 -return - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert expected == actual - - -def test_compile_continue(): - # While - i = ScratchVar() - program = Seq( - [ - i.store(Int(0)), - While(i.load() < Int(3)).Do( - Seq([If(i.load() == Int(2), Continue()), i.store(i.load() + Int(1))]) - ), - Approve(), - ] - ) - - expected = """#pragma version 4 -int 0 -store 0 -main_l1: -load 0 -int 3 -< -bz main_l4 -main_l2: -load 0 -int 2 -== -bnz main_l2 -load 0 -int 1 -+ -store 0 -b main_l1 -main_l4: -int 1 -return - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert expected == actual - - # For - i = ScratchVar() - program = Seq( - [ - For(i.store(Int(0)), i.load() < Int(10), i.store(i.load() + Int(1))).Do( - Seq( - [ - If(i.load() == Int(4), Continue()), - App.globalPut(Itob(i.load()), i.load() * Int(2)), - ] - ) - ), - Approve(), - ] - ) - - expected = """#pragma version 4 -int 0 -store 0 -main_l1: -load 0 -int 10 -< -bz main_l5 -load 0 -int 4 -== -bnz main_l4 -load 0 -itob -load 0 -int 2 -* -app_global_put -main_l4: -load 0 -int 1 -+ -store 0 -b main_l1 -main_l5: -int 1 -return - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert expected == actual - - -def test_compile_continue_break_nested(): - - i = ScratchVar() - program = Seq( - [ - i.store(Int(0)), - While(i.load() < Int(10)).Do( - Seq( - [ - i.store(i.load() + Int(1)), - If(i.load() < Int(4), Continue(), Break()), - ] - ) - ), - Approve(), - ] - ) - - expected = """#pragma version 4 -int 0 -store 0 -load 0 -int 10 -< -bz main_l2 -main_l1: -load 0 -int 1 -+ -store 0 -load 0 -int 4 -< -bnz main_l1 -main_l2: -int 1 -return - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert expected == actual - - i = ScratchVar() - program = Seq( - [ - i.store(Int(0)), - While(i.load() < Int(10)).Do( - Seq( - [ - If(i.load() == Int(8), Break()), - While(i.load() < Int(6)).Do( - Seq( - [ - If(i.load() == Int(3), Break()), - i.store(i.load() + Int(1)), - ] - ) - ), - If(i.load() < Int(5), Continue()), - i.store(i.load() + Int(1)), - ] - ) - ), - Approve(), - ] - ) - - expected = """#pragma version 4 -int 0 -store 0 -main_l1: -load 0 -int 10 -< -bz main_l8 -main_l2: -load 0 -int 8 -== -bnz main_l8 -main_l3: -load 0 -int 6 -< -bnz main_l6 -main_l4: -load 0 -int 5 -< -bnz main_l2 -load 0 -int 1 -+ -store 0 -b main_l1 -main_l6: -load 0 -int 3 -== -bnz main_l4 -load 0 -int 1 -+ -store 0 -b main_l3 -main_l8: -int 1 -return -""".strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert expected == actual - - -def test_compile_subroutine_unsupported(): - @Subroutine(TealType.none) - def storeValue(value: Expr) -> Expr: - return App.globalPut(Bytes("key"), value) - - program = Seq( - [ - If(Txn.sender() == Global.creator_address()).Then( - storeValue(Txn.application_args[0]) - ), - Approve(), - ] - ) - - with pytest.raises(TealInputError): - compileTeal(program, Mode.Application, version=3) - - -def test_compile_subroutine_no_return(): - @Subroutine(TealType.none) - def storeValue(value: Expr) -> Expr: - return App.globalPut(Bytes("key"), value) - - program = Seq( - [ - If(Txn.sender() == Global.creator_address()).Then( - storeValue(Txn.application_args[0]) - ), - Approve(), - ] - ) - - expected = """#pragma version 4 -txn Sender -global CreatorAddress -== -bz main_l2 -txna ApplicationArgs 0 -callsub storeValue_0 -main_l2: -int 1 -return - -// storeValue -storeValue_0: -store 0 -byte "key" -load 0 -app_global_put -retsub - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert actual == expected - - -def test_compile_subroutine_with_return(): - @Subroutine(TealType.none) - def storeValue(value: Expr) -> Expr: - return App.globalPut(Bytes("key"), value) - - @Subroutine(TealType.bytes) - def getValue() -> Expr: - return App.globalGet(Bytes("key")) - - program = Seq( - [ - If(Txn.sender() == Global.creator_address()).Then( - storeValue(Txn.application_args[0]) - ), - If(getValue() == Bytes("fail")).Then(Reject()), - Approve(), - ] - ) - - expected = """#pragma version 4 -txn Sender -global CreatorAddress -== -bnz main_l3 -main_l1: -callsub getValue_1 -byte "fail" -== -bz main_l4 -int 0 -return -main_l3: -txna ApplicationArgs 0 -callsub storeValue_0 -b main_l1 -main_l4: -int 1 -return - -// storeValue -storeValue_0: -store 0 -byte "key" -load 0 -app_global_put -retsub - -// getValue -getValue_1: -byte "key" -app_global_get -retsub - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert actual == expected - - -def test_compile_subroutine_many_args(): - @Subroutine(TealType.uint64) - def calculateSum( - a1: Expr, a2: Expr, a3: Expr, a4: Expr, a5: Expr, a6: Expr - ) -> Expr: - return a1 + a2 + a3 + a4 + a5 + a6 - - program = Return( - calculateSum(Int(1), Int(2), Int(3), Int(4), Int(5), Int(6)) - == Int(1 + 2 + 3 + 4 + 5 + 6) - ) - - expected = """#pragma version 4 -int 1 -int 2 -int 3 -int 4 -int 5 -int 6 -callsub calculateSum_0 -int 21 -== -return - -// calculateSum -calculateSum_0: -store 5 -store 4 -store 3 -store 2 -store 1 -store 0 -load 0 -load 1 -+ -load 2 -+ -load 3 -+ -load 4 -+ -load 5 -+ -retsub - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert actual == expected - - -def test_compile_subroutine_recursive(): - @Subroutine(TealType.uint64) - def isEven(i: Expr) -> Expr: - return ( - If(i == Int(0)) - .Then(Int(1)) - .ElseIf(i == Int(1)) - .Then(Int(0)) - .Else(isEven(i - Int(2))) - ) - - program = Return(isEven(Int(6))) - - expected = """#pragma version 4 -int 6 -callsub isEven_0 -return - -// isEven -isEven_0: -store 0 -load 0 -int 0 -== -bnz isEven_0_l4 -load 0 -int 1 -== -bnz isEven_0_l3 -load 0 -int 2 -- -load 0 -dig 1 -callsub isEven_0 -swap -store 0 -swap -pop -b isEven_0_l5 -isEven_0_l3: -int 0 -b isEven_0_l5 -isEven_0_l4: -int 1 -isEven_0_l5: -retsub - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert actual == expected - - -def test_compile_subroutine_recursive_5(): - @Subroutine(TealType.uint64) - def isEven(i: Expr) -> Expr: - return ( - If(i == Int(0)) - .Then(Int(1)) - .ElseIf(i == Int(1)) - .Then(Int(0)) - .Else(isEven(i - Int(2))) - ) - - program = Return(isEven(Int(6))) - - expected = """#pragma version 5 -int 6 -callsub isEven_0 -return - -// isEven -isEven_0: -store 0 -load 0 -int 0 -== -bnz isEven_0_l4 -load 0 -int 1 -== -bnz isEven_0_l3 -load 0 -int 2 -- -load 0 -swap -callsub isEven_0 -swap -store 0 -b isEven_0_l5 -isEven_0_l3: -int 0 -b isEven_0_l5 -isEven_0_l4: -int 1 -isEven_0_l5: -retsub - """.strip() - actual = compileTeal(program, Mode.Application, version=5, assembleConstants=False) - assert actual == expected - - -def test_compile_subroutine_recursive_multiple_args(): - @Subroutine(TealType.uint64) - def multiplyByAdding(a, b): - return ( - If(a == Int(0)) - .Then(Return(Int(0))) - .Else(Return(b + multiplyByAdding(a - Int(1), b))) - ) - - program = Return(multiplyByAdding(Int(3), Int(5))) - - expected = """#pragma version 4 -int 3 -int 5 -callsub multiplyByAdding_0 -return - -// multiplyByAdding -multiplyByAdding_0: -store 1 -store 0 -load 0 -int 0 -== -bnz multiplyByAdding_0_l2 -load 1 -load 0 -int 1 -- -load 1 -load 0 -load 1 -dig 3 -dig 3 -callsub multiplyByAdding_0 -store 0 -store 1 -load 0 -swap -store 0 -swap -pop -swap -pop -+ -retsub -multiplyByAdding_0_l2: -int 0 -retsub - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert actual == expected - - -def test_compile_subroutine_recursive_multiple_args_5(): - @Subroutine(TealType.uint64) - def multiplyByAdding(a, b): - return ( - If(a == Int(0)) - .Then(Return(Int(0))) - .Else(Return(b + multiplyByAdding(a - Int(1), b))) - ) - - program = Return(multiplyByAdding(Int(3), Int(5))) - - expected = """#pragma version 5 -int 3 -int 5 -callsub multiplyByAdding_0 -return - -// multiplyByAdding -multiplyByAdding_0: -store 1 -store 0 -load 0 -int 0 -== -bnz multiplyByAdding_0_l2 -load 1 -load 0 -int 1 -- -load 1 -load 0 -load 1 -uncover 3 -uncover 3 -callsub multiplyByAdding_0 -cover 2 -store 1 -store 0 -+ -retsub -multiplyByAdding_0_l2: -int 0 -retsub - """.strip() - actual = compileTeal(program, Mode.Application, version=5, assembleConstants=False) - assert actual == expected - - -def test_compile_subroutine_mutually_recursive(): - @Subroutine(TealType.uint64) - def isEven(i: Expr) -> Expr: - return If(i == Int(0), Int(1), Not(isOdd(i - Int(1)))) - - @Subroutine(TealType.uint64) - def isOdd(i: Expr) -> Expr: - return If(i == Int(0), Int(0), Not(isEven(i - Int(1)))) - - program = Return(isEven(Int(6))) - - expected = """#pragma version 4 -int 6 -callsub isEven_0 -return - -// isEven -isEven_0: -store 0 -load 0 -int 0 -== -bnz isEven_0_l2 -load 0 -int 1 -- -load 0 -dig 1 -callsub isOdd_1 -swap -store 0 -swap -pop -! -b isEven_0_l3 -isEven_0_l2: -int 1 -isEven_0_l3: -retsub - -// isOdd -isOdd_1: -store 1 -load 1 -int 0 -== -bnz isOdd_1_l2 -load 1 -int 1 -- -load 1 -dig 1 -callsub isEven_0 -swap -store 1 -swap -pop -! -b isOdd_1_l3 -isOdd_1_l2: -int 0 -isOdd_1_l3: -retsub - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert actual == expected - - -def test_compile_subroutine_mutually_recursive_5(): - @Subroutine(TealType.uint64) - def isEven(i: Expr) -> Expr: - return If(i == Int(0), Int(1), Not(isOdd(i - Int(1)))) - - @Subroutine(TealType.uint64) - def isOdd(i: Expr) -> Expr: - return If(i == Int(0), Int(0), Not(isEven(i - Int(1)))) - - program = Return(isEven(Int(6))) - - expected = """#pragma version 5 -int 6 -callsub isEven_0 -return - -// isEven -isEven_0: -store 0 -load 0 -int 0 -== -bnz isEven_0_l2 -load 0 -int 1 -- -load 0 -swap -callsub isOdd_1 -swap -store 0 -! -b isEven_0_l3 -isEven_0_l2: -int 1 -isEven_0_l3: -retsub - -// isOdd -isOdd_1: -store 1 -load 1 -int 0 -== -bnz isOdd_1_l2 -load 1 -int 1 -- -load 1 -swap -callsub isEven_0 -swap -store 1 -! -b isOdd_1_l3 -isOdd_1_l2: -int 0 -isOdd_1_l3: -retsub - """.strip() - actual = compileTeal(program, Mode.Application, version=5, assembleConstants=False) - assert actual == expected - - -def test_compile_loop_in_subroutine(): - @Subroutine(TealType.none) - def setState(value: Expr) -> Expr: - i = ScratchVar() - return For(i.store(Int(0)), i.load() < Int(10), i.store(i.load() + Int(1))).Do( - App.globalPut(Itob(i.load()), value) - ) - - program = Seq([setState(Bytes("value")), Approve()]) - - expected = """#pragma version 4 -byte "value" -callsub setState_0 -int 1 -return - -// setState -setState_0: -store 0 -int 0 -store 1 -setState_0_l1: -load 1 -int 10 -< -bz setState_0_l3 -load 1 -itob -load 0 -app_global_put -load 1 -int 1 -+ -store 1 -b setState_0_l1 -setState_0_l3: -retsub - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert actual == expected - - -def test_compile_subroutine_invalid_name(): - def tmp() -> Expr: - return Int(1) - - tmp.__name__ = "invalid-;)" - - program = Subroutine(TealType.uint64)(tmp)() - expected = """#pragma version 4 -callsub invalid_0 -return - -// invalid-;) -invalid_0: -int 1 -retsub - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert actual == expected - - -def test_compile_subroutine_assemble_constants(): - @Subroutine(TealType.none) - def storeValue(key: Expr, t1: Expr, t2: Expr, t3: Expr) -> Expr: - return App.globalPut(key, t1 + t2 + t3 + Int(10)) - - program = Seq( - [ - If(Txn.application_id() == Int(0)).Then( - storeValue( - Concat(Bytes("test"), Bytes("test"), Bytes("a")), - Int(1), - Int(1), - Int(3), - ) - ), - Approve(), - ] - ) - - expected = """#pragma version 4 -intcblock 1 -bytecblock 0x74657374 -txn ApplicationID -pushint 0 // 0 -== -bz main_l2 -bytec_0 // "test" -bytec_0 // "test" -concat -pushbytes 0x61 // "a" -concat -intc_0 // 1 -intc_0 // 1 -pushint 3 // 3 -callsub storeValue_0 -main_l2: -intc_0 // 1 -return - -// storeValue -storeValue_0: -store 3 -store 2 -store 1 -store 0 -load 0 -load 1 -load 2 -+ -load 3 -+ -pushint 10 // 10 -+ -app_global_put -retsub - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=True) - assert actual == expected - - -def test_compile_wide_ratio(): - cases = ( - ( - WideRatio([Int(2), Int(100)], [Int(5)]), - """#pragma version 5 -int 2 -int 100 -mulw -int 0 -int 5 -divmodw -pop -pop -swap -! -assert -return -""", - ), - ( - WideRatio([Int(2), Int(100)], [Int(10), Int(5)]), - """#pragma version 5 -int 2 -int 100 -mulw -int 10 -int 5 -mulw -divmodw -pop -pop -swap -! -assert -return -""", - ), - ( - WideRatio([Int(2), Int(100), Int(3)], [Int(10), Int(5)]), - """#pragma version 5 -int 2 -int 100 -mulw -int 3 -uncover 2 -dig 1 -* -cover 2 -mulw -cover 2 -+ -swap -int 10 -int 5 -mulw -divmodw -pop -pop -swap -! -assert -return -""", - ), - ( - WideRatio([Int(2), Int(100), Int(3)], [Int(10), Int(5), Int(6)]), - """#pragma version 5 -int 2 -int 100 -mulw -int 3 -uncover 2 -dig 1 -* -cover 2 -mulw -cover 2 -+ -swap -int 10 -int 5 -mulw -int 6 -uncover 2 -dig 1 -* -cover 2 -mulw -cover 2 -+ -swap -divmodw -pop -pop -swap -! -assert -return -""", - ), - ( - WideRatio([Int(2), Int(100), Int(3), Int(4)], [Int(10), Int(5), Int(6)]), - """#pragma version 5 -int 2 -int 100 -mulw -int 3 -uncover 2 -dig 1 -* -cover 2 -mulw -cover 2 -+ -swap -int 4 -uncover 2 -dig 1 -* -cover 2 -mulw -cover 2 -+ -swap -int 10 -int 5 -mulw -int 6 -uncover 2 -dig 1 -* -cover 2 -mulw -cover 2 -+ -swap -divmodw -pop -pop -swap -! -assert -return -""", - ), - ) - - for program, expected in cases: - actual = compileTeal( - program, Mode.Application, version=5, assembleConstants=False - ) - assert actual == expected.strip() - - -import inspect -import dis - - -def z1(foo): - x = { - "bytecode": dis.Bytecode(foo), - "code": inspect.getsource(foo), - "file": inspect.getsourcefile(foo), - "lines": inspect.getsourcelines(foo), - "mod": inspect.getmodule(foo), - "members": inspect.getmembers(foo), - "ismod": inspect.ismodule(foo), - "iscls": inspect.isclass(foo), - "ismeth": inspect.ismethod(foo), - "isfunc": inspect.isfunction(foo), - "isgenfunc": inspect.isgeneratorfunction(foo), - "isgen": inspect.isgenerator(foo), - "iscofunc": inspect.iscoroutinefunction(foo), - "isawait": inspect.isawaitable(foo), - "isasyncgenfunc": inspect.isasyncgenfunction(foo), - "isasyncgen": inspect.isasyncgen(foo), - "iscode": inspect.iscode(foo), - "isbuiltin": inspect.isbuiltin(foo), - "isroutine": inspect.isroutine(foo), - "isabstract": inspect.isabstract(foo), - } - lines = x["lines"] - x["line-map"] = list(zip(range(lines[1], lines[1] + len(lines[0])), lines[0])) - x["instructions"] = [ - instr.opname for instr in reversed([instr for instr in x["bytecode"]]) - ] - - return x - - -def test_z1(): - def f(x): - return x + 1339 - - def foo(): - y = f(42) - - # mom and dad - - # are very important - - return y - - foo_info = z1(foo) - - x = 42 - - -def test_zeph(): - @Subroutine(TealType.none) - def storeValue(key: Expr, t1: Expr, t2: Expr, t3: Expr) -> Expr: - return App.globalPut(key, t1 + t2 + t3 + Int(10)) - - program = Seq( - [ - If(Txn.application_id() == Int(0)).Then( - storeValue( - Concat(Bytes("test"), Bytes("test"), Bytes("a")), - Int(1), - Int(1), - Int(3), - ) - ), - Approve(), - ] - ) - - expected = """#pragma version 4 -intcblock 1 -bytecblock 0x74657374 -txn ApplicationID -pushint 0 // 0 -== -bz main_l2 -bytec_0 // "test" -bytec_0 // "test" -concat -pushbytes 0x61 // "a" -concat -intc_0 // 1 -intc_0 // 1 -pushint 3 // 3 -callsub storeValue_0 -main_l2: -intc_0 // 1 -return - -// storeValue -storeValue_0: -store 3 -store 2 -store 1 -store 0 -load 0 -load 1 -load 2 -+ -load 3 -+ -pushint 10 // 10 -+ -app_global_put -retsub - """.strip() - actual = compileTeal( - program, Mode.Application, version=4, assembleConstants=True, sourceMap=True - ) - assert actual == expected From 48adcae7350a966e148f152e050a18ffc57d58d3 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 19:27:23 -0600 Subject: [PATCH 39/85] better workding --- pyteal/ast/subroutine.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 4718de918..be5da21bc 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -180,14 +180,14 @@ def __init__( def __teal__(self, options: "CompileOptions"): """ Generate the subroutine's start and end teal blocks. - The subroutine's arguments put on the stack to be picked up into local scratch variables. - There are 2 cases for the arg expression put on the stack: + The subroutine's arguments are pushed on the stack to be picked up into local scratch variables. + There are 2 cases to consider for the pushed arg expression: 1. (by-value) In the case of typical arguments of type Expr, the expression ITSELF is evaluated for the stack - and will be stored in a local ScratchVar when beginning the subroutine execution + and will be stored in a local ScratchVar for subroutine evaluation 2. (by-reference) In the case of a by-reference argument of type ScratchVar, its SLOT INDEX is put on the stack - and will be stored in a local PassByRefScratchVar when beginning the subroutine execution + and will be stored in a local DynamicScratchVar for subroutine evaluation """ verifyTealVersion( Op.callsub.min_version, From 92d0b933f194762411ea2eaa70159f2e3055436f Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 19:33:27 -0600 Subject: [PATCH 40/85] wording --- pyteal/ast/subroutine.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index be5da21bc..eaeed0405 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -294,17 +294,18 @@ def evaluateSubroutine(subroutine: SubroutineDefinition) -> SubroutineDeclaratio Puts together the data necessary to define the code for a subroutine. "evaluate" is used here to connote evaluating the PyTEAL AST into a SubroutineDeclaration, but not actually placing it at call locations. The trickiest part here is managing the subroutine's arguments. - The arguments are needed in two different ways, and there are 2 different argument types to consider: + The arguments are needed for two different code-paths, and there are 2 different argument types to consider + for each of the code-paths: - 2 Argument Usages - - -------- ------ + 2 Argument Usages / Code-Paths + - -------- ------ ---------- Usage (A) for run-time: "argumentVars" --reverse--> "bodyOps" These are "store" expressions that pick up parameters that have been pre-placed on the stack prior to subroutine invocation. - These are stored into local scratch space to be used by the TEAL subroutine. + The argumentVars are stored into local scratch space to be used by the TEAL subroutine. Usage (B) for compile-time: "loadedArgs" These are expressions supplied to the user-defined PyTEAL function. - These are invoked to by the subroutine create a self-contained AST which will then define the TEAL subroutine. + The loadedArgs are invoked to by the subroutine to create a self-contained AST which will translate into a TEAL subroutine. In both usage cases, we need to handle @@ -313,12 +314,12 @@ def evaluateSubroutine(subroutine: SubroutineDefinition) -> SubroutineDeclaratio Type 1 (by-value): these have python type Expr Type 2 (by-reference): these hae python type ScratchVar - Usage (A) "argumentVars" --reverse--> "bodyOps" for storing pre-placed stack variables into local scratch space: + Usage (A) "argumentVars" - Storing pre-placed stack variables into local scratch space: Type 1. (by-value) use ScratchVar.store() to pick the actual value into a local scratch space Type 2. (by-reference) ALSO use ScratchVar.store() to pick up from the stack NOTE: SubroutineCall.__teal__() has placed the _SLOT INDEX_ on the stack so this is stored into the local scratch space - Usage (B) "loadedArgs" - Storing pre-placed stack variables into local scratch-variables: + Usage (B) "loadedArgs" - Passing through to an invoked PyTEAL subroutine AST: Type 1. (by-value) use ScratchVar.load() to have an Expr that can be compiled in python by the PyTEAL subroutine Type 2. (by-reference) use a PassByRefScratchVar as the user will have written the PyTEAL in a way that satifies the ScratchVar API. I.e., the user will write `x.load()` instead of `x` as they would have for by-value variables. From 34cf121106aa23091b08ead72a60b2f61ff0cd9c Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 19:37:47 -0600 Subject: [PATCH 41/85] wording --- pyteal/ast/subroutine.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index eaeed0405..0199015b2 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -321,8 +321,8 @@ def evaluateSubroutine(subroutine: SubroutineDefinition) -> SubroutineDeclaratio Usage (B) "loadedArgs" - Passing through to an invoked PyTEAL subroutine AST: Type 1. (by-value) use ScratchVar.load() to have an Expr that can be compiled in python by the PyTEAL subroutine - Type 2. (by-reference) use a PassByRefScratchVar as the user will have written the PyTEAL in a way that satifies - the ScratchVar API. I.e., the user will write `x.load()` instead of `x` as they would have for by-value variables. + Type 2. (by-reference) use a DynamicScratchVar as the user will have written the PyTEAL in a way that satifies + the ScratchVar API. I.e., the user will write `x.load()` and `x.store(val)` as opposed to just `x`. """ def var_n_loaded(param): From ecb2cc803f59b43ebf4ab8bca92c303a5cf5b80f Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 19:40:57 -0600 Subject: [PATCH 42/85] remove empty line --- tests/compile_asserts.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/compile_asserts.py b/tests/compile_asserts.py index e178697e1..240db3821 100644 --- a/tests/compile_asserts.py +++ b/tests/compile_asserts.py @@ -32,7 +32,6 @@ def mismatch_ligature(expected, actual): def assert_teal_as_expected(path2actual, path2expected): - with open(path2actual, "r") as fa, open(path2expected, "r") as fe: alines = fa.read().split("\n") elines = fe.read().split("\n") From ba92401c07848e1df022adf82b1e409acbe9cb8b Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 20:02:49 -0600 Subject: [PATCH 43/85] dedupe bad merge in init's --- pyteal/__init__.pyi | 47 +---------------------------------- pyteal/ast/__init__.py | 51 +++----------------------------------- pyteal/ast/scratch_test.py | 6 +++++ 3 files changed, 11 insertions(+), 93 deletions(-) diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index 789e51d17..0b422a832 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -19,19 +19,15 @@ from .config import MAX_GROUP_SIZE, NUM_SLOTS __all__ = [ "AccountParam", "Add", - "Add", "Addr", "And", - "And", "App", "AppField", "AppParam", "Approve", - "Approve", "Arg", "Array", "Assert", - "Assert", "AssetHolding", "AssetParam", "Balance", @@ -60,38 +56,26 @@ __all__ = [ "CompileOptions", "compileTeal", "Concat", - "Concat", - "Cond", "Cond", "Continue", "DEFAULT_TEAL_VERSION", "Div", - "Div", "Divw", "DynamicScratchVar", - "DynamicScratchVar", - "Ed25519Verify", "Ed25519Verify", "EnumInt", "Eq", - "Eq", - "Err", "Err", "Exp", - "Exp", "Expr", "Extract", - "Extract", "ExtractUint16", "ExtractUint32", "ExtractUint64", "For", "Ge", - "Ge", "GeneratedID", "GetBit", - "GetBit", - "GetByte", "GetByte", "Gitxn", "GitxnaExpr", @@ -99,12 +83,10 @@ __all__ = [ "Global", "GlobalField", "Gt", - "Gt", "Gtxn", "GtxnaExpr", "GtxnExpr", "If", - "If", "ImportScratchValue", "InnerTxn", "InnerTxnAction", @@ -115,79 +97,53 @@ __all__ = [ "Keccak256", "LabelReference", "Le", - "Le", "LeafExpr", "Len", "Log", "Lt", - "Lt", "MAX_GROUP_SIZE", "MAX_TEAL_VERSION", "MaybeValue", - "MaybeValue", "MethodSignature", "MIN_TEAL_VERSION", "MinBalance", "Minus", - "Minus", - "Mod", "Mod", "Mode", "Mul", - "Mul", "MultiValue", "NaryExpr", "NaryExpr", "Neq", - "Neq", "Nonce", "Not", "NUM_SLOTS", "OnComplete", "Op", "Or", - "Or", "Pop", "Reject", - "Reject", - "Return", "Return", - "ScratchLoad", + "ScratchIndex", "ScratchLoad", "ScratchSlot", - "ScratchSlot", - "ScratchStackStore", "ScratchStackStore", "ScratchStore", - "ScratchStore", "ScratchVar", - "ScratchVar", - "Seq", "Seq", "SetBit", - "SetBit", - "SetByte", "SetByte", "Sha256", "Sha512_256", "ShiftLeft", - "ShiftLeft", - "ShiftRight", "ShiftRight", "Sqrt", "Subroutine", - "Subroutine", - "SubroutineCall", "SubroutineCall", "SubroutineDeclaration", - "SubroutineDeclaration", "SubroutineDefinition", - "SubroutineDefinition", - "SubroutineFnWrapper", "SubroutineFnWrapper", "Substring", - "Substring", - "Suffix", "Suffix", "TealBlock", "TealCompileError", @@ -212,5 +168,4 @@ __all__ = [ "UnaryExpr", "While", "WideRatio", - "WideRatio", ] diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index 08cc152a1..87485a7c8 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -121,10 +121,11 @@ # misc from .scratch import ( - ScratchSlot, + ScratchIndex, ScratchLoad, - ScratchStore, + ScratchSlot, ScratchStackStore, + ScratchStore, ) from .scratchvar import DynamicScratchVar, ScratchVar from .maybe import MaybeValue @@ -133,19 +134,15 @@ __all__ = [ "AccountParam", "Add", - "Add", "Addr", "And", - "And", "App", "AppField", "AppParam", "Approve", - "Approve", "Arg", "Array", "Assert", - "Assert", "AssetHolding", "AssetParam", "Balance", @@ -176,36 +173,25 @@ "BytesXor", "BytesZero", "Concat", - "Concat", - "Cond", "Cond", "Continue", "Div", - "Div", "Divw", "DynamicScratchVar", "Ed25519Verify", - "Ed25519Verify", "EnumInt", "Eq", - "Eq", "Err", - "Err", - "Exp", "Exp", "Expr", "Extract", - "Extract", "ExtractUint16", "ExtractUint32", "ExtractUint64", "For", "Ge", - "Ge", "GeneratedID", "GetBit", - "GetBit", - "GetByte", "GetByte", "Gitxn", "GitxnaExpr", @@ -213,12 +199,10 @@ "Global", "GlobalField", "Gt", - "Gt", "Gtxn", "GtxnaExpr", "GtxnExpr", "If", - "If", "ImportScratchValue", "InnerTxn", "InnerTxnAction", @@ -228,73 +212,47 @@ "Itob", "Keccak256", "Le", - "Le", "LeafExpr", "Len", "Log", "Lt", - "Lt", - "MaybeValue", "MaybeValue", "MethodSignature", "MinBalance", "Minus", - "Minus", "Mod", - "Mod", - "Mul", "Mul", "MultiValue", "NaryExpr", - "NaryExpr", - "Neq", "Neq", "Nonce", "Not", "OnComplete", "Or", - "Or", "Pop", "Reject", "Reject", "Return", - "Return", + "ScratchIndex", "ScratchLoad", - "ScratchLoad", - "ScratchSlot", "ScratchSlot", "ScratchStackStore", - "ScratchStackStore", "ScratchStore", - "ScratchStore", - "ScratchVar", "ScratchVar", "Seq", - "Seq", "SetBit", - "SetBit", - "SetByte", "SetByte", "Sha256", "Sha512_256", "ShiftLeft", - "ShiftLeft", - "ShiftRight", "ShiftRight", "Sqrt", "Subroutine", - "Subroutine", - "SubroutineCall", "SubroutineCall", "SubroutineDeclaration", - "SubroutineDeclaration", - "SubroutineDefinition", "SubroutineDefinition", "SubroutineFnWrapper", - "SubroutineFnWrapper", "Substring", - "Substring", - "Suffix", "Suffix", "Tmpl", "Txn", @@ -308,5 +266,4 @@ "UnaryExpr", "While", "WideRatio", - "WideRatio", ] diff --git a/pyteal/ast/scratch_test.py b/pyteal/ast/scratch_test.py index 9661ef1b5..be39d7e29 100644 --- a/pyteal/ast/scratch_test.py +++ b/pyteal/ast/scratch_test.py @@ -106,3 +106,9 @@ def test_scratch_assign_id_invalid(): with pytest.raises(TealInputError): slot = ScratchSlot(NUM_SLOTS) + + +def test_scratch_index(): + slot = ScratchSlot() + + index = ScratchIndex(slot) From 1efbae8b19b772643db4b34cce2622d077c6d4c1 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 21:40:32 -0600 Subject: [PATCH 44/85] per CR comments --- pyteal/ast/scratch.py | 14 ++++++++------ pyteal/ast/subroutine.py | 16 ++++++++++++---- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/pyteal/ast/scratch.py b/pyteal/ast/scratch.py index 8d4d38ec2..7bc73d123 100644 --- a/pyteal/ast/scratch.py +++ b/pyteal/ast/scratch.py @@ -120,10 +120,15 @@ def __init__( super().__init__() if (slot is None) == (index_expression is None): - raise TealInternalError( + raise TealInputError( "Exactly one of slot or index_expressions must be provided" ) + if slot and not isinstance(slot, ScratchSlot): + raise TealInputError( + "cannot handle slot of type {}".format(type(self.slot)) + ) + self.slot = slot self.type = type self.index_expression = index_expression @@ -138,11 +143,8 @@ def __teal__(self, options: "CompileOptions"): op = TealOp(self, Op.loads) return TealBlock.FromOp(options, op, self.index_expression) - if not isinstance(self.slot, ScratchSlot): - raise TealInternalError( - "cannot handle slot of type {}".format(type(self.slot)) - ) - op = TealOp(self, Op.load, self.slot) + s = cast(ScratchSlot, self.slot) + op = TealOp(self, Op.load, s) return TealBlock.FromOp(options, op) def type_of(self): diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 0199015b2..009d3b37e 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -1,4 +1,4 @@ -from typing import Callable, List, Optional, Set, Union, TYPE_CHECKING +from typing import Callable, Dict, List, Optional, Set, Union, TYPE_CHECKING from inspect import isclass, Parameter, signature from ..types import TealType @@ -29,12 +29,15 @@ def __init__( self.by_ref_args: Set[str] = set() + self.expected_arg_types: List[type] = [] + if not callable(implementation): raise TealInputError("Input to SubroutineDefinition is not callable") sig = signature(implementation) for name, param in sig.parameters.items(): + param = sig.parameters[name] if param.kind not in ( Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD, @@ -51,6 +54,10 @@ def __init__( name ) ) + if name in implementation.__annotations__: + self.expected_arg_types.append(ScratchVar) + else: + self.expected_arg_types.append(Expr) for var, var_type in implementation.__annotations__.items(): if var == "return" and not ( @@ -102,10 +109,11 @@ def invoke(self, args: List[Union[Expr, ScratchVar]]) -> "SubroutineCall": ) for i, arg in enumerate(args): - if not isinstance(arg, self.PARAM_TYPES): + atype = self.expected_arg_types[i] + if not isinstance(arg, atype): raise TealInputError( - "Argument {} at index {} of subroutine call has incorrect type {}".format( - i, arg, type(arg) + "supplied argument {} at index {} had type {} but was expecting type {}".format( + arg, i, type(arg), atype ) ) From 9bc35391dc89f452a695c7e6042b63281a225d22 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 22:35:51 -0600 Subject: [PATCH 45/85] pass the compiler options thru so can be backwards compatible with new type checking --- pyteal/ast/subroutine.py | 28 ++++++++++++++++++---------- pyteal/compiler/compiler.py | 2 +- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 009d3b37e..6a5c9e04d 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -27,6 +27,7 @@ def __init__( self.id = SubroutineDefinition.nextSubroutineId SubroutineDefinition.nextSubroutineId += 1 + self.compiler_version: int = None self.by_ref_args: Set[str] = set() self.expected_arg_types: List[type] = [] @@ -85,10 +86,12 @@ def __init__( self.declaration: Optional["SubroutineDeclaration"] = None self.__name = self.implementation.__name__ if nameStr is None else nameStr - def getDeclaration(self) -> "SubroutineDeclaration": + def getDeclaration( + self, options: "CompileOptions" = None + ) -> "SubroutineDeclaration": if self.declaration is None: # lazy evaluate subroutine - self.declaration = evaluateSubroutine(self) + self.declaration = evaluateSubroutine(self, options=options) return self.declaration def name(self) -> str: @@ -108,14 +111,15 @@ def invoke(self, args: List[Union[Expr, ScratchVar]]) -> "SubroutineCall": ) ) - for i, arg in enumerate(args): - atype = self.expected_arg_types[i] - if not isinstance(arg, atype): - raise TealInputError( - "supplied argument {} at index {} had type {} but was expecting type {}".format( - arg, i, type(arg), atype + if self.compiler_version is not None and self.compiler_version >= 6: + for i, arg in enumerate(args): + atype = self.expected_arg_types[i] + if not isinstance(arg, atype): + raise TealInputError( + "supplied argument {} at index {} had type {} but was expecting type {}".format( + arg, i, type(arg), atype + ) ) - ) return SubroutineCall(self, args) @@ -297,7 +301,9 @@ def __call__(self, fnImplementation: Callable[..., Expr]) -> SubroutineFnWrapper Subroutine.__module__ = "pyteal" -def evaluateSubroutine(subroutine: SubroutineDefinition) -> SubroutineDeclaration: +def evaluateSubroutine( + subroutine: SubroutineDefinition, options: "CompileOptions" = None +) -> SubroutineDeclaration: """ Puts together the data necessary to define the code for a subroutine. "evaluate" is used here to connote evaluating the PyTEAL AST into a SubroutineDeclaration, @@ -332,6 +338,8 @@ def evaluateSubroutine(subroutine: SubroutineDefinition) -> SubroutineDeclaratio Type 2. (by-reference) use a DynamicScratchVar as the user will have written the PyTEAL in a way that satifies the ScratchVar API. I.e., the user will write `x.load()` and `x.store(val)` as opposed to just `x`. """ + if options: + subroutine.compiler_version = options.version def var_n_loaded(param): if param in subroutine.by_ref_args: diff --git a/pyteal/compiler/compiler.py b/pyteal/compiler/compiler.py index 8e0a6b9c0..5cf61f9b5 100644 --- a/pyteal/compiler/compiler.py +++ b/pyteal/compiler/compiler.py @@ -153,7 +153,7 @@ def compileSubroutine( newSubroutines = referencedSubroutines - subroutineMapping.keys() for subroutine in sorted(newSubroutines, key=lambda subroutine: subroutine.id): compileSubroutine( - subroutine.getDeclaration(), + subroutine.getDeclaration(options=options), options, subroutineMapping, subroutineGraph, From 988ef0f4944fcb926b3a6a3a7882c2687f056b9d Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 22:37:55 -0600 Subject: [PATCH 46/85] mypy --- pyteal/ast/subroutine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 6a5c9e04d..af308e05c 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -27,7 +27,7 @@ def __init__( self.id = SubroutineDefinition.nextSubroutineId SubroutineDefinition.nextSubroutineId += 1 - self.compiler_version: int = None + self.compiler_version: Optional[int] = None self.by_ref_args: Set[str] = set() self.expected_arg_types: List[type] = [] From c0eb0fdc638d8bede972eb69095a1c85a0207bf2 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 22:50:34 -0600 Subject: [PATCH 47/85] unit test_scratch_index() --- pyteal/ast/scratch_test.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pyteal/ast/scratch_test.py b/pyteal/ast/scratch_test.py index be39d7e29..6eedc1b8a 100644 --- a/pyteal/ast/scratch_test.py +++ b/pyteal/ast/scratch_test.py @@ -112,3 +112,17 @@ def test_scratch_index(): slot = ScratchSlot() index = ScratchIndex(slot) + assert index.slot is slot + + assert str(index) == "(ScratchIndex " + str(slot) + ")" + + assert hash(index) == hash(slot.id) + + assert index.type_of() == TealType.uint64 + + assert not index.has_return() + + expected = TealSimpleBlock([TealOp(index, Op.int, slot)]) + actual, _ = index.__teal__(options) + + assert actual == expected From 80ee8d609d16f3f806f60c306ddbe013fd8b6322 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 23:07:51 -0600 Subject: [PATCH 48/85] dynamicScratchvar unit tests with closer adherance to ScratchVar's API --- pyteal/ast/scratchvar.py | 5 +++ pyteal/ast/scratchvar_test.py | 58 +++++++++++++++++++++++++++++++++++ pyteal/ast/subroutine_test.py | 4 +++ 3 files changed, 67 insertions(+) diff --git a/pyteal/ast/scratchvar.py b/pyteal/ast/scratchvar.py index 2d23aeba9..ed309983d 100644 --- a/pyteal/ast/scratchvar.py +++ b/pyteal/ast/scratchvar.py @@ -136,8 +136,13 @@ def set_index(self, indexVar: ScratchVar) -> Expr: """ return self.indexer.store(indexVar.index()) + def storage_type(self) -> TealType: + """Get the type of expressions that can be stored in this ScratchVar.""" + return self.type + def store(self, value: Expr) -> Expr: """Store the value in the referenced ScratchVar.""" + require_type(value, self.type) index = ScratchLoad(self.indexer.slot, TealType.uint64) return ScratchStore(slot=None, value=value, index_expression=index) diff --git a/pyteal/ast/scratchvar_test.py b/pyteal/ast/scratchvar_test.py index a8ddbc9e3..f0a0d27ea 100644 --- a/pyteal/ast/scratchvar_test.py +++ b/pyteal/ast/scratchvar_test.py @@ -72,6 +72,19 @@ def test_scratchvar_load(): assert actual == expected +def test_scratchvar_index(): + myvar = ScratchVar() + expr = myvar.index() + + expected = TealSimpleBlock([TealOp(expr, Op.int, myvar.slot)]) + + actual, _ = expr.__teal__(options) + actual.addIncoming() + actual = TealBlock.NormalizeBlocks(actual) + + assert actual == expected + + def test_scratchvar_assign_store(): slotId = 2 myvar = ScratchVar(TealType.uint64, slotId) @@ -104,3 +117,48 @@ def test_scratchvar_assign_load(): actual = TealBlock.NormalizeBlocks(actual) assert actual == expected + + +def test_dynamic_scratchvar_type(): + myvar_default = DynamicScratchVar() + assert myvar_default.storage_type() == TealType.anytype + assert myvar_default.store(Bytes("value")).type_of() == TealType.none + assert myvar_default.load().type_of() == TealType.anytype + + with pytest.raises(TealTypeError): + myvar_default.store(Pop(Int(1))) + + myvar_int = ScratchVar(TealType.uint64) + assert myvar_int.storage_type() == TealType.uint64 + assert myvar_int.store(Int(1)).type_of() == TealType.none + assert myvar_int.load().type_of() == TealType.uint64 + + with pytest.raises(TealTypeError): + myvar_int.store(Bytes("value")) + + with pytest.raises(TealTypeError): + myvar_int.store(Pop(Int(1))) + + myvar_bytes = ScratchVar(TealType.bytes) + assert myvar_bytes.storage_type() == TealType.bytes + assert myvar_bytes.store(Bytes("value")).type_of() == TealType.none + assert myvar_bytes.load().type_of() == TealType.bytes + + with pytest.raises(TealTypeError): + myvar_bytes.store(Int(0)) + + with pytest.raises(TealTypeError): + myvar_bytes.store(Pop(Int(1))) + + +def test_dynamic_scratchvar_index(): + myvar = DynamicScratchVar() + expr = myvar.index() + + expected = TealSimpleBlock([TealOp(expr, Op.load, myvar.indexer.slot)]) + + actual, _ = expr.__teal__(options) + actual.addIncoming() + actual = TealBlock.NormalizeBlocks(actual) + + assert actual == expected diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index c60da0907..51a9db8ac 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -40,6 +40,9 @@ def fnWithOnlyArgExprAnnotations(a: Expr, b: Expr): def fnWithPartialExprAnnotations(a, b: Expr) -> Expr: return Return() + def fnWithMixedAnnotations(a: ScratchVar, b: Expr) -> Int: + return Return() + cases = ( (fn0Args, 0, "fn0Args"), (fn1Args, 1, "fn1Args"), @@ -53,6 +56,7 @@ def fnWithPartialExprAnnotations(a, b: Expr) -> Expr: (fnWithOnlyReturnExprAnnotations, 2, "fnWithOnlyReturnExprAnnotations"), (fnWithOnlyArgExprAnnotations, 2, "fnWithOnlyArgExprAnnotations"), (fnWithPartialExprAnnotations, 2, "fnWithPartialExprAnnotations"), + (fnWithMixedAnnotations, 2, "fnWithMixedAnnotations"), ) for (fn, numArgs, name) in cases: From 6e6973527932f1ee98f970ff0788fd3ac9be9016 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 17 Feb 2022 23:16:43 -0600 Subject: [PATCH 49/85] mypy --- pyteal/ast/subroutine_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index 51a9db8ac..dbb6bbf20 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -40,7 +40,7 @@ def fnWithOnlyArgExprAnnotations(a: Expr, b: Expr): def fnWithPartialExprAnnotations(a, b: Expr) -> Expr: return Return() - def fnWithMixedAnnotations(a: ScratchVar, b: Expr) -> Int: + def fnWithMixedAnnotations(a: ScratchVar, b: Expr) -> Return: return Return() cases = ( From f4e16e7b8fdb7c084caded20ea3c24611b4e6c09 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 18 Feb 2022 08:44:47 -0600 Subject: [PATCH 50/85] maybe pylance has a bug. Remove Bytes anyway --- pyteal/ast/gtxn_test.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyteal/ast/gtxn_test.py b/pyteal/ast/gtxn_test.py index 1c5f9d103..9b895d100 100644 --- a/pyteal/ast/gtxn_test.py +++ b/pyteal/ast/gtxn_test.py @@ -2,8 +2,6 @@ from .. import * -from .bytes import Bytes - teal6Options = CompileOptions(version=6) From cceca7a390eb732c45fd83227be066238b584be3 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 18 Feb 2022 08:48:11 -0600 Subject: [PATCH 51/85] bump up mypy to 0.931 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a6f36fa28..e3d37e35a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ black==21.7b0 -mypy==0.910 +mypy==0.931 pytest pytest-timeout py-algorand-sdk From 5584158fd577b2af091c6b58bf307b8a62c07a8c Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 18 Feb 2022 08:56:26 -0600 Subject: [PATCH 52/85] ahh Bytes missing from .pyi --- pyteal/__init__.pyi | 1 + pyteal/ast/multi_test.py | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index 0b422a832..c18a896ef 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -36,6 +36,7 @@ __all__ = [ "BitwiseNot", "BitwiseOr", "BitwiseXor", + "Bytes", "BytesAdd", "BytesAnd", "BytesDiv", diff --git a/pyteal/ast/multi_test.py b/pyteal/ast/multi_test.py index a41cdbc44..b710a027a 100644 --- a/pyteal/ast/multi_test.py +++ b/pyteal/ast/multi_test.py @@ -2,8 +2,6 @@ from .. import * -from .bytes import Bytes - options = CompileOptions() From c829fa25348faa30babae3d59e26d9f25fab40b5 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 18 Feb 2022 09:41:49 -0600 Subject: [PATCH 53/85] unit tests for storing/loading via an index_expression --- pyteal/ast/scratch_test.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/pyteal/ast/scratch_test.py b/pyteal/ast/scratch_test.py index 6eedc1b8a..15b518ae8 100644 --- a/pyteal/ast/scratch_test.py +++ b/pyteal/ast/scratch_test.py @@ -43,6 +43,19 @@ def test_scratch_load_default(): assert actual == expected +def test_scratch_load_index_expression(): + expr = ScratchLoad(slot=None, index_expression=Int(1337)) + assert expr.type_of() == TealType.anytype + + expected = TealSimpleBlock([TealOp(Int(1337), Op.int, 1337)]) + expected.nextBlock = TealSimpleBlock([TealOp(expr, Op.loads)]) + + actual, _ = expr.__teal__(options) + + with TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + def test_scratch_load_type(): for type in (TealType.uint64, TealType.bytes, TealType.anytype): slot = ScratchSlot() @@ -76,6 +89,28 @@ def test_scratch_store(): assert actual == expected +def test_scratch_store_index_expression(): + for value in ( + Int(1), + Bytes("test"), + App.globalGet(Bytes("key")), + If(Int(1), Int(2), Int(3)), + ): + expr = ScratchStore(slot=None, value=value, index_expression=Int(1337)) + assert expr.type_of() == TealType.none + + expected = TealSimpleBlock([TealOp(Int(1337), Op.int, 1337)]) + valueStart, valueEnd = value.__teal__(options) + expected.nextBlock = valueStart + storeBlock = TealSimpleBlock([TealOp(expr, Op.stores)]) + valueEnd.setNextBlock(storeBlock) + + actual, _ = expr.__teal__(options) + + with TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + def test_scratch_stack_store(): slot = ScratchSlot() expr = ScratchStackStore(slot) From d2ec80c9a8aac98688b294b08f70e96d10c5f49b Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 18 Feb 2022 10:25:13 -0600 Subject: [PATCH 54/85] unit tests for dynamic scratchvar load/store --- pyteal/ast/scratchvar_test.py | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/pyteal/ast/scratchvar_test.py b/pyteal/ast/scratchvar_test.py index f0a0d27ea..7fe353e40 100644 --- a/pyteal/ast/scratchvar_test.py +++ b/pyteal/ast/scratchvar_test.py @@ -151,6 +151,46 @@ def test_dynamic_scratchvar_type(): myvar_bytes.store(Pop(Int(1))) +def test_dynamic_scratchvar_load(): + myvar = DynamicScratchVar() + expr = myvar.load() + + expected = TealSimpleBlock( + [ + TealOp(ScratchLoad(myvar.indexer.slot), Op.load, myvar.indexer.slot), + TealOp(expr, Op.loads), + ] + ) + + actual, _ = expr.__teal__(options) + actual.addIncoming() + actual = TealBlock.NormalizeBlocks(actual) + + with TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + +def test_dynamic_scratchvar_store(): + myvar = DynamicScratchVar(TealType.bytes) + arg = Bytes("value") + expr = myvar.store(arg) + + expected = TealSimpleBlock( + [ + TealOp(ScratchLoad(myvar.indexer.slot), Op.load, myvar.indexer.slot), + TealOp(arg, Op.byte, '"value"'), + TealOp(expr, Op.stores), + ] + ) + + actual, _ = expr.__teal__(options) + actual.addIncoming() + actual = TealBlock.NormalizeBlocks(actual) + + with TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + def test_dynamic_scratchvar_index(): myvar = DynamicScratchVar() expr = myvar.index() From 8444dbddc44c0ce2416d60d85a7f47a4b545726c Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 18 Feb 2022 11:28:16 -0600 Subject: [PATCH 55/85] typo --- tests/pass_by_ref_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pass_by_ref_test.py b/tests/pass_by_ref_test.py index 91eded571..533c43d28 100644 --- a/tests/pass_by_ref_test.py +++ b/tests/pass_by_ref_test.py @@ -2,7 +2,7 @@ from .compile_asserts import assert_new_v_old, compile_and_save -#### TESTS FOR PyTEAL THAT PREDATES PASS-BY-REF +#### TESTS FOR PyTEAL THAT PREDATE PASS-BY-REF @Subroutine(TealType.bytes) From 42200abe86159c6cefb2ae8ba6aa61e977b233e8 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 18 Feb 2022 12:59:20 -0600 Subject: [PATCH 56/85] allow skipping new version in tests --- tests/pass_by_ref_test.py | 200 +++++++++++++++++++------------------- 1 file changed, 98 insertions(+), 102 deletions(-) diff --git a/tests/pass_by_ref_test.py b/tests/pass_by_ref_test.py index 533c43d28..25c8aabe4 100644 --- a/tests/pass_by_ref_test.py +++ b/tests/pass_by_ref_test.py @@ -2,9 +2,10 @@ from .compile_asserts import assert_new_v_old, compile_and_save -#### TESTS FOR PyTEAL THAT PREDATE PASS-BY-REF - +# Set the following True, if don't want to run pass-by-ref dependent tests +OLD_CODE_ONLY = False +#### TESTS FOR PyTEAL THAT PREDATE PASS-BY-REF @Subroutine(TealType.bytes) def logcat(some_bytes, an_int): catted = ScratchVar(TealType.bytes) @@ -75,110 +76,97 @@ def sub_even(): ) -#### TESTS FOR NEW PyTEAL THAT USES PASS-BY-REF / DYNAMIC - - -@Subroutine(TealType.none) -def logcat_dynamic(first: ScratchVar, an_int): - return Seq( - first.store(Concat(first.load(), Itob(an_int))), - Log(first.load()), - ) - - -def sub_logcat_dynamic(): - first = ScratchVar(TealType.bytes) - return Seq( - first.store(Bytes("hello")), - logcat_dynamic(first, Int(42)), - Assert(Bytes("hello42") == first.load()), - Int(1), - ) - - -def wilt_the_stilt(): - player_score = DynamicScratchVar(TealType.uint64) - - wilt = ScratchVar(TealType.uint64, 129) - kobe = ScratchVar(TealType.uint64) - dt = ScratchVar(TealType.uint64, 131) - - return Seq( - player_score.set_index(wilt), - player_score.store(Int(100)), - player_score.set_index(kobe), - player_score.store(Int(81)), - player_score.set_index(dt), - player_score.store(Int(73)), - Assert(player_score.load() == Int(73)), - Assert(player_score.index() == Int(131)), - player_score.set_index(wilt), - Assert(player_score.load() == Int(100)), - Assert(player_score.index() == Int(129)), - Int(100), - ) - - -@Subroutine(TealType.none) -def swap(x: ScratchVar, y: ScratchVar): - z = ScratchVar(TealType.anytype) - return Seq( - z.store(x.load()), - x.store(y.load()), - y.store(z.load()), - ) - +if not OLD_CODE_ONLY: + #### TESTS FOR NEW PyTEAL THAT USES PASS-BY-REF / DYNAMIC + @Subroutine(TealType.none) + def logcat_dynamic(first: ScratchVar, an_int): + return Seq( + first.store(Concat(first.load(), Itob(an_int))), + Log(first.load()), + ) -@Subroutine(TealType.none) -def cat(x, y): - return Pop(Concat(x, y)) + def sub_logcat_dynamic(): + first = ScratchVar(TealType.bytes) + return Seq( + first.store(Bytes("hello")), + logcat_dynamic(first, Int(42)), + Assert(Bytes("hello42") == first.load()), + Int(1), + ) + def wilt_the_stilt(): + player_score = DynamicScratchVar(TealType.uint64) + + wilt = ScratchVar(TealType.uint64, 129) + kobe = ScratchVar(TealType.uint64) + dt = ScratchVar(TealType.uint64, 131) + + return Seq( + player_score.set_index(wilt), + player_score.store(Int(100)), + player_score.set_index(kobe), + player_score.store(Int(81)), + player_score.set_index(dt), + player_score.store(Int(73)), + Assert(player_score.load() == Int(73)), + Assert(player_score.index() == Int(131)), + player_score.set_index(wilt), + Assert(player_score.load() == Int(100)), + Assert(player_score.index() == Int(129)), + Int(100), + ) -def swapper(): - a = ScratchVar(TealType.bytes) - b = ScratchVar(TealType.bytes) - return Seq( - a.store(Bytes("hello")), - b.store(Bytes("goodbye")), - cat(a.load(), b.load()), - swap(a, b), - Assert(a.load() == Bytes("goodbye")), - Assert(b.load() == Bytes("hello")), - Int(1000), - ) + @Subroutine(TealType.none) + def swap(x: ScratchVar, y: ScratchVar): + z = ScratchVar(TealType.anytype) + return Seq( + z.store(x.load()), + x.store(y.load()), + y.store(z.load()), + ) + @Subroutine(TealType.none) + def cat(x, y): + return Pop(Concat(x, y)) + + def swapper(): + a = ScratchVar(TealType.bytes) + b = ScratchVar(TealType.bytes) + return Seq( + a.store(Bytes("hello")), + b.store(Bytes("goodbye")), + cat(a.load(), b.load()), + swap(a, b), + Assert(a.load() == Bytes("goodbye")), + Assert(b.load() == Bytes("hello")), + Int(1000), + ) -@Subroutine(TealType.none) -def factorial(n: ScratchVar): - tmp = ScratchVar(TealType.uint64) - return ( - If(n.load() <= Int(1)) - .Then(n.store(Int(1))) - .Else( - Seq( - tmp.store(n.load() - Int(1)), - factorial(tmp), - n.store(n.load() * tmp.load()), + @Subroutine(TealType.none) + def factorial(n: ScratchVar): + tmp = ScratchVar(TealType.uint64) + return ( + If(n.load() <= Int(1)) + .Then(n.store(Int(1))) + .Else( + Seq( + tmp.store(n.load() - Int(1)), + factorial(tmp), + n.store(n.load() * tmp.load()), + ) ) ) - ) - -def fac_by_ref(): - n = ScratchVar(TealType.uint64) - return Seq( - n.store(Int(42)), - factorial(n), - n.load(), - ) + def fac_by_ref(): + n = ScratchVar(TealType.uint64) + return Seq( + n.store(Int(42)), + factorial(n), + n.load(), + ) OLD_CASES = (sub_logcat, sub_slowfib, sub_fastfib, sub_even) -NEW_CASES = (sub_logcat_dynamic, swapper, wilt_the_stilt, fac_by_ref) - - -def test_swapper(): - compile_and_save(swapper) def test_old(): @@ -186,12 +174,20 @@ def test_old(): assert_new_v_old(pt) -def test_new(): - for pt in NEW_CASES: - assert_new_v_old(pt) - - if __name__ == "__main__": - test_swapper() test_old() - test_new() + + +if not OLD_CODE_ONLY: + NEW_CASES = (sub_logcat_dynamic, swapper, wilt_the_stilt, fac_by_ref) + + def test_swapper(): + compile_and_save(swapper) + + def test_new(): + for pt in NEW_CASES: + assert_new_v_old(pt) + + if __name__ == "__main__": + test_swapper() + test_new() From daa4cf625872408adef561ababaf7e89b5a79bc7 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 18 Feb 2022 13:49:57 -0600 Subject: [PATCH 57/85] mid-refactor, lets run against Circle --- pyteal/ast/scratchvar.py | 50 ++++++++++++++++++++++------------------ pyteal/ast/subroutine.py | 7 ++++-- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/pyteal/ast/scratchvar.py b/pyteal/ast/scratchvar.py index ed309983d..e44f8110e 100644 --- a/pyteal/ast/scratchvar.py +++ b/pyteal/ast/scratchvar.py @@ -91,7 +91,7 @@ class DynamicScratchVar: def __init__( self, ttype: TealType = TealType.anytype, - indexer: ScratchVar = None, + # indexer: ScratchVar = None, ): """Create a new DynamicScratchVar which references other ScratchVar's @@ -104,31 +104,35 @@ def __init__( this is needed in the internal PyTEAL compiler code for handling pass-by-reference semantics. """ self.type = ttype - if indexer is None: - indexer = ScratchVar(TealType.uint64) - - self.indexer: ScratchVar - self.slot: ScratchSlot - self._set_indexer(indexer) - - def _set_indexer(self, indexer: ScratchVar) -> None: - if not isinstance(indexer, ScratchVar): - raise TealInternalError( - "indexer must be a ScratchVar but had python type {}".format( - type(indexer) - ) - ) - - if indexer.type != TealType.uint64: - raise TealInternalError( - "indexer must have teal type uint64 but was {} instead".format( - indexer.type - ) - ) + # if indexer is None: + # indexer = ScratchVar(TealType.uint64) - self.indexer = indexer + self.indexer = ScratchVar(TealType.uint64) self.slot = self.indexer.slot + # indexer = ScratchVar(TealType.uint64) + # self.indexer: ScratchVar + # self.slot: ScratchSlot + # self._set_indexer(indexer) + + # def _set_indexer(self, indexer: ScratchVar) -> None: + # if not isinstance(indexer, ScratchVar): + # raise TealInternalError( + # "indexer must be a ScratchVar but had python type {}".format( + # type(indexer) + # ) + # ) + + # if indexer.type != TealType.uint64: + # raise TealInternalError( + # "indexer must have teal type uint64 but was {} instead".format( + # indexer.type + # ) + # ) + + # self.indexer = indexer + # self.slot = self.indexer.slot + def set_index(self, indexVar: ScratchVar) -> Expr: """Set this DynamicScratchVar to reference the provided `indexVar`. Followup `store`, `load` and `index` operations will use the provided `indexVar` until diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index af308e05c..d2dc81267 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -343,8 +343,11 @@ def evaluateSubroutine( def var_n_loaded(param): if param in subroutine.by_ref_args: - argVar = ScratchVar(TealType.uint64) - loaded = DynamicScratchVar(TealType.anytype, indexer=argVar) + # Jason's idea: + argVar = DynamicScratchVar(TealType.anytype) + loaded = argVar + # argVar = ScratchVar(TealType.uint64) + # loaded = DynamicScratchVar(TealType.anytype, indexer=argVar) else: argVar = ScratchVar(TealType.anytype) loaded = argVar.load() From 8fa2d4ad45632a7e8c660681f041f507be1a3a71 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 18 Feb 2022 14:14:05 -0600 Subject: [PATCH 58/85] per cr catch: better assumption about argument types with a new e2e case to validate correctness --- pyteal/ast/subroutine.py | 4 +++- tests/pass_by_ref_test.py | 16 +++++++++++++++- tests/teal/sub_mixed_expected.teal | 24 ++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 tests/teal/sub_mixed_expected.teal diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index d2dc81267..1abd98eaf 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -55,7 +55,9 @@ def __init__( name ) ) - if name in implementation.__annotations__: + if name in implementation.__annotations__ and issubclass( + implementation.__annotations__[name], ScratchVar + ): self.expected_arg_types.append(ScratchVar) else: self.expected_arg_types.append(Expr) diff --git a/tests/pass_by_ref_test.py b/tests/pass_by_ref_test.py index 25c8aabe4..0dc30c769 100644 --- a/tests/pass_by_ref_test.py +++ b/tests/pass_by_ref_test.py @@ -165,6 +165,20 @@ def fac_by_ref(): n.load(), ) + @Subroutine(TealType.uint64) + def mixed_annotations(x: Expr, y: Expr, z: ScratchVar) -> Int: + return Seq( + z.store(x), + Log(Concat(y, Bytes("="), Itob(x))), + x, + ) + + def sub_mixed(): + x = Int(42) + y = Bytes("x") + z = ScratchVar(TealType.uint64) + return mixed_annotations(x, y, z) + OLD_CASES = (sub_logcat, sub_slowfib, sub_fastfib, sub_even) @@ -179,7 +193,7 @@ def test_old(): if not OLD_CODE_ONLY: - NEW_CASES = (sub_logcat_dynamic, swapper, wilt_the_stilt, fac_by_ref) + NEW_CASES = (sub_logcat_dynamic, swapper, wilt_the_stilt, fac_by_ref, sub_mixed) def test_swapper(): compile_and_save(swapper) diff --git a/tests/teal/sub_mixed_expected.teal b/tests/teal/sub_mixed_expected.teal new file mode 100644 index 000000000..f779975ad --- /dev/null +++ b/tests/teal/sub_mixed_expected.teal @@ -0,0 +1,24 @@ +#pragma version 6 +int 42 >42 +byte "x" >42,"x" +int 0 >42,"x",0 +callsub mixedannotations_0 >42 +return <> + +// mixed_annotations +mixedannotations_0: >42,"x",0 +store 3 3:0 +store 2 2:"x" +store 1 1:42 +load 3 >0 +load 1 >0,42 +stores 0:42 +load 2 >"x" +byte "=" >"x","=" +concat >"x=" +load 1 >"x=",42 +itob >"x=","42" +concat >"x=42" +log LOG +load 1 >42 +retsub \ No newline at end of file From 07c9431a832ae91a81ff834b3f1df0cc73e4d6fe Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 18 Feb 2022 14:28:46 -0600 Subject: [PATCH 59/85] more leniency on annotations --- pyteal/ast/subroutine.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 1abd98eaf..a2c8b0ac2 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -55,12 +55,14 @@ def __init__( name ) ) - if name in implementation.__annotations__ and issubclass( - implementation.__annotations__[name], ScratchVar - ): - self.expected_arg_types.append(ScratchVar) - else: - self.expected_arg_types.append(Expr) + + expected_arg_type = Expr + if name in implementation.__annotations__: + ptype = implementation.__annotations__[name] + if isclass(ptype) and issubclass(ptype, ScratchVar): + expected_arg_type = ScratchVar + + self.expected_arg_types.append(expected_arg_type) for var, var_type in implementation.__annotations__.items(): if var == "return" and not ( From 5ecf72ed9f4c35c0f854dd499ca6779d512523a5 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 18 Feb 2022 15:30:18 -0600 Subject: [PATCH 60/85] mypy --- pyteal/ast/subroutine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index a2c8b0ac2..ea3ad6399 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -56,7 +56,7 @@ def __init__( ) ) - expected_arg_type = Expr + expected_arg_type: type = Expr if name in implementation.__annotations__: ptype = implementation.__annotations__[name] if isclass(ptype) and issubclass(ptype, ScratchVar): From 49b1284993e56983b431475af25f22bfd2be7400 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 18 Feb 2022 16:58:16 -0600 Subject: [PATCH 61/85] try on circle --- pyteal/ast/subroutine.py | 19 +++---- pyteal/ast/subroutine_test.py | 97 ++++++++++++++++++++++++++++++++++- 2 files changed, 104 insertions(+), 12 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index ea3ad6399..494741e29 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -115,15 +115,15 @@ def invoke(self, args: List[Union[Expr, ScratchVar]]) -> "SubroutineCall": ) ) - if self.compiler_version is not None and self.compiler_version >= 6: - for i, arg in enumerate(args): - atype = self.expected_arg_types[i] - if not isinstance(arg, atype): - raise TealInputError( - "supplied argument {} at index {} had type {} but was expecting type {}".format( - arg, i, type(arg), atype - ) + # if self.compiler_version is not None and self.compiler_version >= 6: + for i, arg in enumerate(args): + atype = self.expected_arg_types[i] + if not isinstance(arg, atype): + raise TealInputError( + "supplied argument {} at index {} had type {} but was expecting type {}".format( + arg, i, type(arg), atype ) + ) return SubroutineCall(self, args) @@ -347,11 +347,8 @@ def evaluateSubroutine( def var_n_loaded(param): if param in subroutine.by_ref_args: - # Jason's idea: argVar = DynamicScratchVar(TealType.anytype) loaded = argVar - # argVar = ScratchVar(TealType.uint64) - # loaded = DynamicScratchVar(TealType.anytype, indexer=argVar) else: argVar = ScratchVar(TealType.anytype) loaded = argVar.load() diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index dbb6bbf20..8982162af 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -1,4 +1,6 @@ +import re from typing import List +from xml.dom import InvalidModificationErr import pytest from .. import * @@ -56,7 +58,6 @@ def fnWithMixedAnnotations(a: ScratchVar, b: Expr) -> Return: (fnWithOnlyReturnExprAnnotations, 2, "fnWithOnlyReturnExprAnnotations"), (fnWithOnlyArgExprAnnotations, 2, "fnWithOnlyArgExprAnnotations"), (fnWithPartialExprAnnotations, 2, "fnWithPartialExprAnnotations"), - (fnWithMixedAnnotations, 2, "fnWithMixedAnnotations"), ) for (fn, numArgs, name) in cases: @@ -82,6 +83,100 @@ def fnWithMixedAnnotations(a: ScratchVar, b: Expr) -> Return: assert invocation.args == args +def test_subroutine_definition_param_types(): + def fnWithNoAnnotations(a, b): + return Return() + + def fnWithExprAnnotations(a: Expr, b: Expr) -> Expr: + return Return() + + def fnWithSVAnnotations(a: ScratchVar, b: ScratchVar): + return Return() + + def fnWithMixedAnns1(a: ScratchVar, b: Expr) -> Expr: + return Return() + + def fnWithMixedAnns2(a: ScratchVar, b) -> Expr: + return Return() + + def fnWithMixedAnns3(a: Expr, b: ScratchVar): + return Return() + + def fnWithMixedAnns4AndIntReturn(a: Expr, b: ScratchVar) -> Bytes: + return Return() + + sv = ScratchVar() + x = Int(42) + s = Bytes("hello") + cases = [ + ("vannila 1", fnWithNoAnnotations, [x, s], None), + ("vannila 2", fnWithNoAnnotations, [x, x], None), + ("vanilla no sv's allowed 1", fnWithNoAnnotations, [x, sv], TealInputError), + ("exprs 1", fnWithExprAnnotations, [x, s], None), + ("exprs 2", fnWithExprAnnotations, [x, x], None), + ("exprs no sv's asllowed 1", fnWithExprAnnotations, [x, sv], TealInputError), + ("all sv's 1", fnWithSVAnnotations, [sv, sv], None), + ("all sv's but strings", fnWithSVAnnotations, [s, s], TealInputError), + ("all sv's but ints", fnWithSVAnnotations, [x, x], TealInputError), + ("mixed1 copacetic", fnWithMixedAnns1, [sv, x], None), + ("mixed1 flipped", fnWithMixedAnns1, [x, sv], TealInputError), + ("mixed1 missing the sv", fnWithMixedAnns1, [x, s], TealInputError), + ("mixed1 missing the non-sv", fnWithMixedAnns1, [sv, sv], TealInputError), + ("mixed2 copacetic", fnWithMixedAnns2, [sv, x], None), + ("mixed2 flipped", fnWithMixedAnns2, [x, sv], TealInputError), + ("mixed2 missing the sv", fnWithMixedAnns2, [x, s], TealInputError), + ("mixed2 missing the non-sv", fnWithMixedAnns2, [sv, sv], TealInputError), + ("mixed3 copacetic", fnWithMixedAnns3, [s, sv], None), + ("mixed3 flipped", fnWithMixedAnns3, [sv, x], TealInputError), + ("mixed3 missing the sv", fnWithMixedAnns3, [x, s], TealInputError), + ( + "mixed3 missing the non-sv", + fnWithMixedAnns4AndIntReturn, + [sv, sv], + TealInputError, + ), + ("mixed4 flipped", fnWithMixedAnns4AndIntReturn, [sv, x], TealInputError), + ("mixed4 missing the sv", fnWithMixedAnns4AndIntReturn, [x, s], TealInputError), + ( + "mixed4 missing the non-sv", + fnWithMixedAnns4AndIntReturn, + [sv, sv], + TealInputError, + ), + ] + for case_name, fn, args, err in cases: + definition = SubroutineDefinition(fn, TealType.none) + assert definition.argumentCount() == len(args), case_name + assert definition.name() == fn.__name__, case_name + + if err is None: + assert len(definition.by_ref_args) == len( + [x for x in args if isinstance(x, ScratchVar)] + ), case_name + + try: + invocation = definition.invoke(args) + assert isinstance(invocation, SubroutineCall), case_name + assert invocation.subroutine is definition, case_name + assert invocation.args == args, case_name + assert invocation.has_return() is False, case_name + + except Exception as e: + if isinstance(e, AssertionError): + raise + assert ( + not e + ), f"EXPECTED SUCCESS. encountered unexpected error during invocation case <{case_name}>: {e}" + else: + try: + with pytest.raises(err): + definition.invoke(args) + except Exception as e: + assert ( + not e + ), f"EXPECTED ERROR of type {err}. encountered unexpected error during invocation case <{case_name}>: {e}" + + def test_subroutine_definition_invalid(): def fnWithDefaults(a, b=None): return Return() From 9068277892191d6dcb7b48e6d62314ce7a4336e9 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 18 Feb 2022 17:02:24 -0600 Subject: [PATCH 62/85] mypy --- pyteal/ast/subroutine_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index 8982162af..39cace480 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -103,7 +103,7 @@ def fnWithMixedAnns3(a: Expr, b: ScratchVar): return Return() def fnWithMixedAnns4AndIntReturn(a: Expr, b: ScratchVar) -> Bytes: - return Return() + return Bytes("helo") sv = ScratchVar() x = Int(42) From f5221dbdd278ed27a1ba55aeb9e60b292fc6ac55 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 18 Feb 2022 17:08:31 -0600 Subject: [PATCH 63/85] get rid of options that I had added --- pyteal/ast/subroutine.py | 12 ++++++------ pyteal/compiler/compiler.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 494741e29..4a03fc85f 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -27,7 +27,7 @@ def __init__( self.id = SubroutineDefinition.nextSubroutineId SubroutineDefinition.nextSubroutineId += 1 - self.compiler_version: Optional[int] = None + # self.compiler_version: Optional[int] = None self.by_ref_args: Set[str] = set() self.expected_arg_types: List[type] = [] @@ -91,11 +91,11 @@ def __init__( self.__name = self.implementation.__name__ if nameStr is None else nameStr def getDeclaration( - self, options: "CompileOptions" = None + self, # , options: "CompileOptions" = None ) -> "SubroutineDeclaration": if self.declaration is None: # lazy evaluate subroutine - self.declaration = evaluateSubroutine(self, options=options) + self.declaration = evaluateSubroutine(self) # , options=options) return self.declaration def name(self) -> str: @@ -306,7 +306,7 @@ def __call__(self, fnImplementation: Callable[..., Expr]) -> SubroutineFnWrapper def evaluateSubroutine( - subroutine: SubroutineDefinition, options: "CompileOptions" = None + subroutine: SubroutineDefinition, # , options: "CompileOptions" = None ) -> SubroutineDeclaration: """ Puts together the data necessary to define the code for a subroutine. @@ -342,8 +342,8 @@ def evaluateSubroutine( Type 2. (by-reference) use a DynamicScratchVar as the user will have written the PyTEAL in a way that satifies the ScratchVar API. I.e., the user will write `x.load()` and `x.store(val)` as opposed to just `x`. """ - if options: - subroutine.compiler_version = options.version + # if options: + # subroutine.compiler_version = options.version def var_n_loaded(param): if param in subroutine.by_ref_args: diff --git a/pyteal/compiler/compiler.py b/pyteal/compiler/compiler.py index 5cf61f9b5..dafa0fd06 100644 --- a/pyteal/compiler/compiler.py +++ b/pyteal/compiler/compiler.py @@ -153,7 +153,7 @@ def compileSubroutine( newSubroutines = referencedSubroutines - subroutineMapping.keys() for subroutine in sorted(newSubroutines, key=lambda subroutine: subroutine.id): compileSubroutine( - subroutine.getDeclaration(options=options), + subroutine.getDeclaration(), # options), # =options), options, subroutineMapping, subroutineGraph, From 35bb603bbeb845d9ac1041562f9fde7d2d0b5439 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 18 Feb 2022 17:11:10 -0600 Subject: [PATCH 64/85] remove comments --- pyteal/ast/scratchvar.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/pyteal/ast/scratchvar.py b/pyteal/ast/scratchvar.py index e44f8110e..964e8b98c 100644 --- a/pyteal/ast/scratchvar.py +++ b/pyteal/ast/scratchvar.py @@ -104,35 +104,9 @@ def __init__( this is needed in the internal PyTEAL compiler code for handling pass-by-reference semantics. """ self.type = ttype - # if indexer is None: - # indexer = ScratchVar(TealType.uint64) - self.indexer = ScratchVar(TealType.uint64) self.slot = self.indexer.slot - # indexer = ScratchVar(TealType.uint64) - # self.indexer: ScratchVar - # self.slot: ScratchSlot - # self._set_indexer(indexer) - - # def _set_indexer(self, indexer: ScratchVar) -> None: - # if not isinstance(indexer, ScratchVar): - # raise TealInternalError( - # "indexer must be a ScratchVar but had python type {}".format( - # type(indexer) - # ) - # ) - - # if indexer.type != TealType.uint64: - # raise TealInternalError( - # "indexer must have teal type uint64 but was {} instead".format( - # indexer.type - # ) - # ) - - # self.indexer = indexer - # self.slot = self.indexer.slot - def set_index(self, indexVar: ScratchVar) -> Expr: """Set this DynamicScratchVar to reference the provided `indexVar`. Followup `store`, `load` and `index` operations will use the provided `indexVar` until From 61055b718958060ac2061b30d2bd783c1103199b Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 18 Feb 2022 17:14:37 -0600 Subject: [PATCH 65/85] remove comments --- pyteal/ast/subroutine.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 4a03fc85f..c97ee0e82 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -27,7 +27,6 @@ def __init__( self.id = SubroutineDefinition.nextSubroutineId SubroutineDefinition.nextSubroutineId += 1 - # self.compiler_version: Optional[int] = None self.by_ref_args: Set[str] = set() self.expected_arg_types: List[type] = [] @@ -90,12 +89,10 @@ def __init__( self.declaration: Optional["SubroutineDeclaration"] = None self.__name = self.implementation.__name__ if nameStr is None else nameStr - def getDeclaration( - self, # , options: "CompileOptions" = None - ) -> "SubroutineDeclaration": + def getDeclaration(self) -> "SubroutineDeclaration": if self.declaration is None: # lazy evaluate subroutine - self.declaration = evaluateSubroutine(self) # , options=options) + self.declaration = evaluateSubroutine(self) return self.declaration def name(self) -> str: @@ -115,7 +112,6 @@ def invoke(self, args: List[Union[Expr, ScratchVar]]) -> "SubroutineCall": ) ) - # if self.compiler_version is not None and self.compiler_version >= 6: for i, arg in enumerate(args): atype = self.expected_arg_types[i] if not isinstance(arg, atype): @@ -305,9 +301,7 @@ def __call__(self, fnImplementation: Callable[..., Expr]) -> SubroutineFnWrapper Subroutine.__module__ = "pyteal" -def evaluateSubroutine( - subroutine: SubroutineDefinition, # , options: "CompileOptions" = None -) -> SubroutineDeclaration: +def evaluateSubroutine(subroutine: SubroutineDefinition) -> SubroutineDeclaration: """ Puts together the data necessary to define the code for a subroutine. "evaluate" is used here to connote evaluating the PyTEAL AST into a SubroutineDeclaration, @@ -342,8 +336,6 @@ def evaluateSubroutine( Type 2. (by-reference) use a DynamicScratchVar as the user will have written the PyTEAL in a way that satifies the ScratchVar API. I.e., the user will write `x.load()` and `x.store(val)` as opposed to just `x`. """ - # if options: - # subroutine.compiler_version = options.version def var_n_loaded(param): if param in subroutine.by_ref_args: From 0f0ab39962f05748b2dac6949cfd7fe2b2de8102 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 18 Feb 2022 17:16:27 -0600 Subject: [PATCH 66/85] remove comments --- pyteal/ast/scratchvar.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pyteal/ast/scratchvar.py b/pyteal/ast/scratchvar.py index 964e8b98c..aaa7d356a 100644 --- a/pyteal/ast/scratchvar.py +++ b/pyteal/ast/scratchvar.py @@ -88,11 +88,7 @@ class DynamicScratchVar: ) """ - def __init__( - self, - ttype: TealType = TealType.anytype, - # indexer: ScratchVar = None, - ): + def __init__(self, ttype: TealType = TealType.anytype): """Create a new DynamicScratchVar which references other ScratchVar's Args: From 51cd869d32bbe43cd614d05347076fe12899b929 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 18 Feb 2022 17:18:21 -0600 Subject: [PATCH 67/85] remove comments --- pyteal/compiler/compiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/compiler/compiler.py b/pyteal/compiler/compiler.py index dafa0fd06..8e0a6b9c0 100644 --- a/pyteal/compiler/compiler.py +++ b/pyteal/compiler/compiler.py @@ -153,7 +153,7 @@ def compileSubroutine( newSubroutines = referencedSubroutines - subroutineMapping.keys() for subroutine in sorted(newSubroutines, key=lambda subroutine: subroutine.id): compileSubroutine( - subroutine.getDeclaration(), # options), # =options), + subroutine.getDeclaration(), options, subroutineMapping, subroutineGraph, From 05e774b9f328f2831408973f6f9371df395e8c90 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 18 Feb 2022 17:36:04 -0600 Subject: [PATCH 68/85] Make DynamicScratchVar a subclass of ScratchVar and ensure with a test that still cannot pass to a subroutine manually --- pyteal/ast/scratchvar.py | 21 ++++++++------------- pyteal/ast/scratchvar_test.py | 6 +++--- pyteal/ast/subroutine_test.py | 19 +++++++++++++------ 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/pyteal/ast/scratchvar.py b/pyteal/ast/scratchvar.py index aaa7d356a..074f238a7 100644 --- a/pyteal/ast/scratchvar.py +++ b/pyteal/ast/scratchvar.py @@ -61,7 +61,7 @@ def index(self) -> Expr: ScratchVar.__module__ = "pyteal" -class DynamicScratchVar: +class DynamicScratchVar(ScratchVar): """ Example of Dynamic Scratch space whereby the slot index is picked up from the stack: .. code-block:: python1 @@ -93,22 +93,17 @@ def __init__(self, ttype: TealType = TealType.anytype): Args: ttype (optional): The type that this variable can hold. Defaults to TealType.anytype. - indexer (optional): A ScratchVar object that holds the _index_ that this DynamicScratchVar is - using to look up other slots. It is _NOT RECOMMENDED_ that a developer provide this optional - argument, but instead, to let the compiler construct one dynamically. In rare situations - it could be useful to provide the ability to explicitly provide the indexer. For example, - this is needed in the internal PyTEAL compiler code for handling pass-by-reference semantics. """ self.type = ttype - self.indexer = ScratchVar(TealType.uint64) - self.slot = self.indexer.slot + self._indexer = ScratchVar(TealType.uint64) + self.slot = self._indexer.slot def set_index(self, indexVar: ScratchVar) -> Expr: """Set this DynamicScratchVar to reference the provided `indexVar`. Followup `store`, `load` and `index` operations will use the provided `indexVar` until `set_index()` is called again to reset the referenced ScratchVar. """ - return self.indexer.store(indexVar.index()) + return self._indexer.store(indexVar.index()) def storage_type(self) -> TealType: """Get the type of expressions that can be stored in this ScratchVar.""" @@ -117,21 +112,21 @@ def storage_type(self) -> TealType: def store(self, value: Expr) -> Expr: """Store the value in the referenced ScratchVar.""" require_type(value, self.type) - index = ScratchLoad(self.indexer.slot, TealType.uint64) + index = ScratchLoad(self._indexer.slot, TealType.uint64) return ScratchStore(slot=None, value=value, index_expression=index) def load(self) -> ScratchLoad: """Load the current value from the referenced ScratchVar.""" - index = ScratchLoad(self.indexer.slot, TealType.uint64) + index = ScratchLoad(self._indexer.slot, TealType.uint64) return ScratchLoad(slot=None, type=self.type, index_expression=index) def index(self) -> Expr: """Get the index of the referenced ScratchVar.""" - return self.indexer.load() + return self._indexer.load() def internal_index(self) -> Expr: """Get the index of _this_ DynamicScratchVar, as opposed to that of the referenced ScratchVar.""" - return self.indexer.index() + return self._indexer.index() DynamicScratchVar.__module__ = "pyteal" diff --git a/pyteal/ast/scratchvar_test.py b/pyteal/ast/scratchvar_test.py index 7fe353e40..da8fb055b 100644 --- a/pyteal/ast/scratchvar_test.py +++ b/pyteal/ast/scratchvar_test.py @@ -157,7 +157,7 @@ def test_dynamic_scratchvar_load(): expected = TealSimpleBlock( [ - TealOp(ScratchLoad(myvar.indexer.slot), Op.load, myvar.indexer.slot), + TealOp(ScratchLoad(myvar._indexer.slot), Op.load, myvar._indexer.slot), TealOp(expr, Op.loads), ] ) @@ -177,7 +177,7 @@ def test_dynamic_scratchvar_store(): expected = TealSimpleBlock( [ - TealOp(ScratchLoad(myvar.indexer.slot), Op.load, myvar.indexer.slot), + TealOp(ScratchLoad(myvar._indexer.slot), Op.load, myvar._indexer.slot), TealOp(arg, Op.byte, '"value"'), TealOp(expr, Op.stores), ] @@ -195,7 +195,7 @@ def test_dynamic_scratchvar_index(): myvar = DynamicScratchVar() expr = myvar.index() - expected = TealSimpleBlock([TealOp(expr, Op.load, myvar.indexer.slot)]) + expected = TealSimpleBlock([TealOp(expr, Op.load, myvar._indexer.slot)]) actual, _ = expr.__teal__(options) actual.addIncoming() diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index 39cace480..1da106048 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -83,7 +83,7 @@ def fnWithMixedAnnotations(a: ScratchVar, b: Expr) -> Return: assert invocation.args == args -def test_subroutine_definition_param_types(): +def test_subroutine_invocation_param_types(): def fnWithNoAnnotations(a, b): return Return() @@ -193,6 +193,9 @@ def fnWithNonExprReturnAnnotation(a, b) -> TealType.uint64: def fnWithNonExprParamAnnotation(a, b: TealType.uint64): return Return() + def fnWithScratchVarSubclass(a, b: DynamicScratchVar): + return Return() + cases = ( (1, "TealInputError('Input to SubroutineDefinition is not callable'"), (None, "TealInputError('Input to SubroutineDefinition is not callable'"), @@ -210,19 +213,23 @@ def fnWithNonExprParamAnnotation(a, b: TealType.uint64): ), ( fnWithNonExprReturnAnnotation, - "TealInputError('Function has Return of disallowed type TealType.uint64. Only type Expr is allowed'", + "Function has return of disallowed type TealType.uint64. Only subtype of Expr is allowed", ), ( fnWithNonExprParamAnnotation, - "TealInputError('Function has parameter b of disallowed type TealType.uint64. Only type Expr is allowed'", + "Function has parameter b of disallowed type TealType.uint64. Only the types", + ), + ( + fnWithScratchVarSubclass, + "Function has parameter b of disallowed type ", ), ) - for case, msg in cases: + for fn, msg in cases: with pytest.raises(TealInputError) as e: - SubroutineDefinition(case, TealType.none) + SubroutineDefinition(fn, TealType.none) - assert msg in str(e), "failed for case [{}]".format(case) + assert msg in str(e), "failed for case [{}]".format(fn.__name__) def test_subroutine_declaration(): From 9555455129fc5510a3a7ef2c595413f83e8972fd Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 18 Feb 2022 17:49:53 -0600 Subject: [PATCH 69/85] make the invalid subroutine definition tests pass in python 3.6 also --- pyteal/ast/subroutine_test.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index 1da106048..b19ad0825 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -1,4 +1,3 @@ -import re from typing import List from xml.dom import InvalidModificationErr import pytest @@ -205,11 +204,11 @@ def fnWithScratchVarSubclass(a, b: DynamicScratchVar): ), ( fnWithKeywordArgs, - "TealInputError('Function has a parameter type that is not allowed in a subroutine: parameter b with type KEYWORD_ONLY'", + "TealInputError('Function has a parameter type that is not allowed in a subroutine: parameter b with type", ), ( fnWithVariableArgs, - "TealInputError('Function has a parameter type that is not allowed in a subroutine: parameter b with type VAR_POSITIONAL'", + "TealInputError('Function has a parameter type that is not allowed in a subroutine: parameter b with type", ), ( fnWithNonExprReturnAnnotation, From 57013d950f6feb1d88d4a1d21c3ef77b0b2b04dd Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 18 Feb 2022 18:18:00 -0600 Subject: [PATCH 70/85] assert no setting index to a dynamic scratchvar --- pyteal/ast/scratchvar.py | 11 +++++++++-- pyteal/ast/scratchvar_test.py | 13 +++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/pyteal/ast/scratchvar.py b/pyteal/ast/scratchvar.py index 074f238a7..924a5baed 100644 --- a/pyteal/ast/scratchvar.py +++ b/pyteal/ast/scratchvar.py @@ -98,12 +98,19 @@ def __init__(self, ttype: TealType = TealType.anytype): self._indexer = ScratchVar(TealType.uint64) self.slot = self._indexer.slot - def set_index(self, indexVar: ScratchVar) -> Expr: + def set_index(self, index_var: ScratchVar) -> Expr: """Set this DynamicScratchVar to reference the provided `indexVar`. Followup `store`, `load` and `index` operations will use the provided `indexVar` until `set_index()` is called again to reset the referenced ScratchVar. """ - return self._indexer.store(indexVar.index()) + if type(index_var) is not ScratchVar: + raise TealInputError( + "Only allowed to use ScratchVar objects for setting indices, but was given a {}".format( + type(index_var) + ) + ) + + return self._indexer.store(index_var.index()) def storage_type(self) -> TealType: """Get the type of expressions that can be stored in this ScratchVar.""" diff --git a/pyteal/ast/scratchvar_test.py b/pyteal/ast/scratchvar_test.py index da8fb055b..a78f281da 100644 --- a/pyteal/ast/scratchvar_test.py +++ b/pyteal/ast/scratchvar_test.py @@ -202,3 +202,16 @@ def test_dynamic_scratchvar_index(): actual = TealBlock.NormalizeBlocks(actual) assert actual == expected + + +def test_dynamic_scratchvar_cannot_set_index_to_another_dynamic(): + myvar = DynamicScratchVar() + sl1 = myvar.load() + + regvar = ScratchVar() + myvar.set_index(regvar) + + dynvar = DynamicScratchVar() + + with pytest.raises(TealInputError): + myvar.set_index(dynvar) From 6335d6d9e34b00fb68ad2a2d0be26e5140aa0098 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 28 Feb 2022 12:10:52 -0600 Subject: [PATCH 71/85] Update pyteal/ast/scratchvar.py Co-authored-by: Michael Diamant --- pyteal/ast/scratchvar.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyteal/ast/scratchvar.py b/pyteal/ast/scratchvar.py index 924a5baed..1e5c6d8fa 100644 --- a/pyteal/ast/scratchvar.py +++ b/pyteal/ast/scratchvar.py @@ -99,8 +99,8 @@ def __init__(self, ttype: TealType = TealType.anytype): self.slot = self._indexer.slot def set_index(self, index_var: ScratchVar) -> Expr: - """Set this DynamicScratchVar to reference the provided `indexVar`. - Followup `store`, `load` and `index` operations will use the provided `indexVar` until + """Set this DynamicScratchVar to reference the provided `index_var`. + Followup `store`, `load` and `index` operations will use the provided `index_var` until `set_index()` is called again to reset the referenced ScratchVar. """ if type(index_var) is not ScratchVar: From 87d8644e7cbd4d10fbd53e5dc71f097964e2f181 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 28 Feb 2022 17:52:19 -0600 Subject: [PATCH 72/85] Per CR comments - make the expected teal's in e2e tests valid TEAL --- tests/teal/fac_by_ref_expected.teal | 78 ++++++++++----------- tests/teal/sub_logcat_dynamic_expected.teal | 44 ++++++------ tests/teal/sub_logcat_expected.teal | 36 +++++----- tests/teal/sub_mixed_expected.teal | 40 +++++------ tests/teal/swapper_expected.teal | 56 +++++++-------- tests/teal/wilt_the_stilt_expected.teal | 38 +++++----- 6 files changed, 146 insertions(+), 146 deletions(-) diff --git a/tests/teal/fac_by_ref_expected.teal b/tests/teal/fac_by_ref_expected.teal index 98f016936..c08bbef0f 100644 --- a/tests/teal/fac_by_ref_expected.teal +++ b/tests/teal/fac_by_ref_expected.teal @@ -1,41 +1,41 @@ -#pragma version 6 Case n = 1 Case n = 2 -int 42 >1 >2 -store 0 0:1 0:2 -int 0 >0 >0 -callsub factorial_0 CALL CALL -load 0 >1 >2 -return RET(1!==1) RET(2!==1*2) +#pragma version 6 // Case n = 1 Case n = 2 +int 42 // >1 >2 +store 0 // 0:1 0:2 +int 0 // >0 >0 +callsub factorial_0 // CALL CALL +load 0 // >1 >2 +return // RET(1!==1) RET(2!==1*2) // factorial -factorial_0: *>0 *>0 *>2,0,1,2 -store 1 1: 0 1: 0 1: 2 -load 1 >0 >0 *>2,0,1,2 -loads >1 >2 *>2,0,1,1 -int 1 >1,1 >2,1 *>2,0,1,1,1 -<= *>1 *>2,0 *>2,0,1,1 -bnz factorial_0_l2 BRANCH *>2 BRANCH -load 1 *>2,0 -loads *>2,2 -int 1 *>2,2,1 -- *>2,1 -store 2 2: 1 -int 2 *>2,2 -load 1 *>2,2,0 -load 2 *>2,2,0,1 -uncover 2 *>2,0,1,2 -callsub factorial_0 CALL SRET >2,0,1 -store 2 2: 1 -store 1 1: 0 -load 1 >2,0 -load 1 >2,0,0 -loads >2,0,2 -load 2 >2,0,2,1 -* >2,0,2 -stores 0: 2 -b factorial_0_l3 BRANCH -factorial_0_l2: | *>2,0,1 -load 1 *>0 | *>2,0,1,2 -int 1 *>0,1 | *>2,0,1,2,1 -stores 0: 1 | 2: 1 -factorial_0_l3: >2 -retsub SRET SRET>2 SRET*>2,0,1 \ No newline at end of file +factorial_0: // *>0 *>0 *>2,0,1,2 +store 1 // 1: 0 1: 0 1: 2 +load 1 // >0 >0 *>2,0,1,2 +loads // >1 >2 *>2,0,1,1 +int 1 // >1,1 >2,1 *>2,0,1,1,1 +<= // *>1 *>2,0 *>2,0,1,1 +bnz factorial_0_l2 // BRANCH *>2 BRANCH +load 1 // *>2,0 +loads // *>2,2 +int 1 // *>2,2,1 +- // *>2,1 +store 2 // 2: 1 +int 2 // *>2,2 +load 1 // *>2,2,0 +load 2 // *>2,2,0,1 +uncover 2 // *>2,0,1,2 +callsub factorial_0 // CALL SRET >2,0,1 +store 2 // 2: 1 +store 1 // 1: 0 +load 1 // >2,0 +load 1 // >2,0,0 +loads // >2,0,2 +load 2 // >2,0,2,1 +* // >2,0,2 +stores // 0: 2 +b factorial_0_l3 // BRANCH +factorial_0_l2: // | *>2,0,1 +load 1 // *>0 | *>2,0,1,2 +int 1 // *>0,1 | *>2,0,1,2,1 +stores // 0: 1 | 2: 1 +factorial_0_l3: // >2 +retsub // SRET SRET>2 SRET*>2,0,1 \ No newline at end of file diff --git a/tests/teal/sub_logcat_dynamic_expected.teal b/tests/teal/sub_logcat_dynamic_expected.teal index 9cc5660f1..87a430bc3 100644 --- a/tests/teal/sub_logcat_dynamic_expected.teal +++ b/tests/teal/sub_logcat_dynamic_expected.teal @@ -1,28 +1,28 @@ #pragma version 6 byte "hello" -store 0 0: "hello" -int 0 -int 42 >@0,42 -callsub logcatdynamic_0 <> -byte "hello42" >"hello42" -load 0 >"hello42","hello42" -== >1 -assert <> -int 1 >1 -return < +store 0 // 0: "hello" +int 0 +int 42 // >@0,42 +callsub logcatdynamic_0 // <> +byte "hello42" // >"hello42" +load 0 // >"hello42","hello42" +== // >1 +assert // <> +int 1 // >1 +return // < // logcat_dynamic -logcatdynamic_0: >@0,42 -store 2 2: 42 -store 1 1: @0 -load 1 -load 1 >@0,@0 -loads >@0,"hello" -load 2 >@0,"hello",42 -itob >@0,"hello","42" -concat >@0,"hello42" -stores 0: "hello42" -load 1 >@0 -loads >"hello42" +logcatdynamic_0: // >@0,42 +store 2 // 2: 42 +store 1 // 1: @0 +load 1 +load 1 // >@0,@0 +loads // >@0,"hello" +load 2 // >@0,"hello",42 +itob // >@0,"hello","42" +concat // >@0,"hello42" +stores // 0: "hello42" +load 1 // >@0 +loads // >"hello42" log retsub \ No newline at end of file diff --git a/tests/teal/sub_logcat_expected.teal b/tests/teal/sub_logcat_expected.teal index a618fccdd..8acb5c4df 100644 --- a/tests/teal/sub_logcat_expected.teal +++ b/tests/teal/sub_logcat_expected.teal @@ -1,23 +1,23 @@ #pragma version 6 -byte "hello" >"hello" -int 42 >"hello",42 -callsub logcat_0 >"hello42" -byte "hello42" >"hello42","hello42" -== >1 -assert <> -int 1 >1 -return <> +byte "hello" // >"hello" +int 42 // >"hello",42 +callsub logcat_0 // >"hello42" +byte "hello42" // >"hello42","hello42" +== // >1 +assert // <> +int 1 // >1 +return // <> // logcat -logcat_0: >"hello",42 -store 1 1: 42 -store 0 0: "hello" -load 0 >"hello" -load 1 >"hello",42 -itob >"hello","42" -concat >"hello42" -store 2 2: "hello42" -load 2 >"hello42" +logcat_0: // >"hello",42 +store 1 // 1: 42 +store 0 // 0: "hello" +load 0 // >"hello" +load 1 // >"hello",42 +itob // >"hello","42" +concat // >"hello42" +store 2 // 2: "hello42" +load 2 // >"hello42" log -load 2 >"hello42" +load 2 // >"hello42" retsub \ No newline at end of file diff --git a/tests/teal/sub_mixed_expected.teal b/tests/teal/sub_mixed_expected.teal index f779975ad..98934bb83 100644 --- a/tests/teal/sub_mixed_expected.teal +++ b/tests/teal/sub_mixed_expected.teal @@ -1,24 +1,24 @@ #pragma version 6 -int 42 >42 -byte "x" >42,"x" -int 0 >42,"x",0 -callsub mixedannotations_0 >42 -return <> +int 42 // >42 +byte "x" // >42,"x" +int 0 // >42,"x",0 +callsub mixedannotations_0 // >42 +return // <> // mixed_annotations -mixedannotations_0: >42,"x",0 -store 3 3:0 -store 2 2:"x" -store 1 1:42 -load 3 >0 -load 1 >0,42 -stores 0:42 -load 2 >"x" -byte "=" >"x","=" -concat >"x=" -load 1 >"x=",42 -itob >"x=","42" -concat >"x=42" -log LOG -load 1 >42 +mixedannotations_0: // >42,"x",0 +store 3 // 3:0 +store 2 // 2:"x" +store 1 // 1:42 +load 3 // >0 +load 1 // >0,42 +stores // 0:42 +load 2 // >"x" +byte "=" // >"x","=" +concat // >"x=" +load 1 // >"x=",42 +itob // >"x=","42" +concat // >"x=42" +log // LOG +load 1 // >42 retsub \ No newline at end of file diff --git a/tests/teal/swapper_expected.teal b/tests/teal/swapper_expected.teal index cd690b1ff..00a996f15 100644 --- a/tests/teal/swapper_expected.teal +++ b/tests/teal/swapper_expected.teal @@ -1,47 +1,47 @@ #pragma version 6 byte "hello" -store 5 5: hello // x +store 5 // 5: hello // x byte "goodbye" -store 6 6: goodbye // y +store 6 // 6: goodbye // y load 5 load 6 -callsub cat_1 <> +callsub cat_1 // <> int 5 int 6 -callsub swap_0 >5,6 -load 5 >goodbye +callsub swap_0 // >5,6 +load 5 // >goodbye byte "goodbye" == -assert <> -load 6 >hello +assert // <> +load 6 // >hello byte "hello" == -assert <> -int 1000 >1000 -return <> +assert // <> +int 1000 // >1000 +return // <> // swap swap_0: -store 1 1: 6 // @y -store 0 0: 5 // @x -load 0 >5 -loads >hello -store 2 2: hello // z -load 0 >5 -load 1 >5,6 -loads >5,goodbye -stores 5: goodbye -load 1 >6 -load 2 >6,hello -stores 6: hello +store 1 // 1: 6 // @y +store 0 // 0: 5 // @x +load 0 // >5 +loads // >hello +store 2 // 2: hello // z +load 0 // >5 +load 1 // >5,6 +loads // >5,goodbye +stores // 5: goodbye +load 1 // >6 +load 2 // >6,hello +stores // 6: hello retsub // cat cat_1: -store 4 4: goodbye -store 3 3: hello -load 3 >hello -load 4 >hello,goodbye -concat >hellogoodbye -pop > +store 4 // 4: goodbye +store 3 // 3: hello +load 3 // >hello +load 4 // >hello,goodbye +concat // >hellogoodbye +pop // > retsub \ No newline at end of file diff --git a/tests/teal/wilt_the_stilt_expected.teal b/tests/teal/wilt_the_stilt_expected.teal index 053289e7f..28d339350 100644 --- a/tests/teal/wilt_the_stilt_expected.teal +++ b/tests/teal/wilt_the_stilt_expected.teal @@ -1,38 +1,38 @@ #pragma version 6 int 129 -store 0 0: @129 // pointer to wilt's address +store 0 // 0: @129 // pointer to wilt's address load 0 int 100 -stores 129: 100 // set wilt's value +stores // 129: 100 // set wilt's value int 1 -store 0 0: @1 // pointer to kobe's address (compiler assigned) +store 0 // 0: @1 // pointer to kobe's address (compiler assigned) load 0 int 81 -stores 1: 81 // set kobe's value +stores // 1: 81 // set kobe's value int 131 -store 0 0: @131 // pointer to dt's address +store 0 // 0: @131 // pointer to dt's address load 0 int 73 -stores 131: 73 // set dt's value +stores // 131: 73 // set dt's value load 0 loads -int 73 >73,73 -== >1 -assert <> +int 73 // >73,73 +== // >1 +assert // <> load 0 -int 131 >131,131 -== >1 -assert <> +int 131 // >131,131 +== // >1 +assert // <> int 129 -store 0 0: @129 +store 0 // 0: @129 load 0 loads -int 100 >100,100 -== >1 -assert <> +int 100 // >100,100 +== // >1 +assert // <> load 0 -int 129 >129,129 -== >1 -assert <> +int 129 // >129,129 +== // >1 +assert // <> int 100 return \ No newline at end of file From 4b707b5668ef957db80552e2a24682afee473542 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 28 Feb 2022 18:11:30 -0600 Subject: [PATCH 73/85] per CR comments --- pyteal/ast/scratchvar_test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyteal/ast/scratchvar_test.py b/pyteal/ast/scratchvar_test.py index a78f281da..d7d262c98 100644 --- a/pyteal/ast/scratchvar_test.py +++ b/pyteal/ast/scratchvar_test.py @@ -128,7 +128,7 @@ def test_dynamic_scratchvar_type(): with pytest.raises(TealTypeError): myvar_default.store(Pop(Int(1))) - myvar_int = ScratchVar(TealType.uint64) + myvar_int = DynamicScratchVar(TealType.uint64) assert myvar_int.storage_type() == TealType.uint64 assert myvar_int.store(Int(1)).type_of() == TealType.none assert myvar_int.load().type_of() == TealType.uint64 @@ -139,7 +139,7 @@ def test_dynamic_scratchvar_type(): with pytest.raises(TealTypeError): myvar_int.store(Pop(Int(1))) - myvar_bytes = ScratchVar(TealType.bytes) + myvar_bytes = DynamicScratchVar(TealType.bytes) assert myvar_bytes.storage_type() == TealType.bytes assert myvar_bytes.store(Bytes("value")).type_of() == TealType.none assert myvar_bytes.load().type_of() == TealType.bytes @@ -206,7 +206,7 @@ def test_dynamic_scratchvar_index(): def test_dynamic_scratchvar_cannot_set_index_to_another_dynamic(): myvar = DynamicScratchVar() - sl1 = myvar.load() + myvar.load() regvar = ScratchVar() myvar.set_index(regvar) From 70e08c8307cf0afa5aa319fb76af20742555d064 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 28 Feb 2022 20:35:23 -0600 Subject: [PATCH 74/85] per CR suggestions: disallow Expr subtypes as subroutine return annotations, and improve tests --- pyteal/ast/subroutine.py | 12 +- pyteal/ast/subroutine_test.py | 56 +- pyteal/compiler/zompiler_test.NOTpy | 1677 +++++++++++++++++++++++++++ 3 files changed, 1703 insertions(+), 42 deletions(-) create mode 100644 pyteal/compiler/zompiler_test.NOTpy diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index c97ee0e82..ca10087f1 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -63,24 +63,22 @@ def __init__( self.expected_arg_types.append(expected_arg_type) - for var, var_type in implementation.__annotations__.items(): - if var == "return" and not ( - isclass(var_type) and issubclass(var_type, Expr) - ): + for var_name, var_type in implementation.__annotations__.items(): + if var_name == "return" and not var_type is Expr: raise TealInputError( "Function has return of disallowed type {}. Only subtype of Expr is allowed".format( var_type ) ) - if var != "return" and var_type not in self.PARAM_TYPES: + if var_name != "return" and var_type not in self.PARAM_TYPES: raise TealInputError( "Function has parameter {} of disallowed type {}. Only the types {} are allowed".format( - var, var_type, self.PARAM_TYPES + var_name, var_type, self.PARAM_TYPES ) ) if var_type is ScratchVar: - self.by_ref_args.add(var) + self.by_ref_args.add(var_name) self.implementation = implementation self.implementationParams = sig.parameters diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index b19ad0825..dc8db3d88 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -1,5 +1,4 @@ from typing import List -from xml.dom import InvalidModificationErr import pytest from .. import * @@ -41,9 +40,6 @@ def fnWithOnlyArgExprAnnotations(a: Expr, b: Expr): def fnWithPartialExprAnnotations(a, b: Expr) -> Expr: return Return() - def fnWithMixedAnnotations(a: ScratchVar, b: Expr) -> Return: - return Return() - cases = ( (fn0Args, 0, "fn0Args"), (fn1Args, 1, "fn1Args"), @@ -101,15 +97,12 @@ def fnWithMixedAnns2(a: ScratchVar, b) -> Expr: def fnWithMixedAnns3(a: Expr, b: ScratchVar): return Return() - def fnWithMixedAnns4AndIntReturn(a: Expr, b: ScratchVar) -> Bytes: - return Bytes("helo") - sv = ScratchVar() x = Int(42) s = Bytes("hello") cases = [ - ("vannila 1", fnWithNoAnnotations, [x, s], None), - ("vannila 2", fnWithNoAnnotations, [x, x], None), + ("vanilla 1", fnWithNoAnnotations, [x, s], None), + ("vanilla 2", fnWithNoAnnotations, [x, x], None), ("vanilla no sv's allowed 1", fnWithNoAnnotations, [x, sv], TealInputError), ("exprs 1", fnWithExprAnnotations, [x, s], None), ("exprs 2", fnWithExprAnnotations, [x, x], None), @@ -128,20 +121,6 @@ def fnWithMixedAnns4AndIntReturn(a: Expr, b: ScratchVar) -> Bytes: ("mixed3 copacetic", fnWithMixedAnns3, [s, sv], None), ("mixed3 flipped", fnWithMixedAnns3, [sv, x], TealInputError), ("mixed3 missing the sv", fnWithMixedAnns3, [x, s], TealInputError), - ( - "mixed3 missing the non-sv", - fnWithMixedAnns4AndIntReturn, - [sv, sv], - TealInputError, - ), - ("mixed4 flipped", fnWithMixedAnns4AndIntReturn, [sv, x], TealInputError), - ("mixed4 missing the sv", fnWithMixedAnns4AndIntReturn, [x, s], TealInputError), - ( - "mixed4 missing the non-sv", - fnWithMixedAnns4AndIntReturn, - [sv, sv], - TealInputError, - ), ] for case_name, fn, args, err in cases: definition = SubroutineDefinition(fn, TealType.none) @@ -153,19 +132,12 @@ def fnWithMixedAnns4AndIntReturn(a: Expr, b: ScratchVar) -> Bytes: [x for x in args if isinstance(x, ScratchVar)] ), case_name - try: - invocation = definition.invoke(args) - assert isinstance(invocation, SubroutineCall), case_name - assert invocation.subroutine is definition, case_name - assert invocation.args == args, case_name - assert invocation.has_return() is False, case_name + invocation = definition.invoke(args) + assert isinstance(invocation, SubroutineCall), case_name + assert invocation.subroutine is definition, case_name + assert invocation.args == args, case_name + assert invocation.has_return() is False, case_name - except Exception as e: - if isinstance(e, AssertionError): - raise - assert ( - not e - ), f"EXPECTED SUCCESS. encountered unexpected error during invocation case <{case_name}>: {e}" else: try: with pytest.raises(err): @@ -195,6 +167,12 @@ def fnWithNonExprParamAnnotation(a, b: TealType.uint64): def fnWithScratchVarSubclass(a, b: DynamicScratchVar): return Return() + def fnReturningExprSubclass(a: ScratchVar, b: Expr) -> Return: + return Return() + + def fnWithMixedAnns4AndBytesReturn(a: Expr, b: ScratchVar) -> Bytes: + return Bytes("helo") + cases = ( (1, "TealInputError('Input to SubroutineDefinition is not callable'"), (None, "TealInputError('Input to SubroutineDefinition is not callable'"), @@ -222,6 +200,14 @@ def fnWithScratchVarSubclass(a, b: DynamicScratchVar): fnWithScratchVarSubclass, "Function has parameter b of disallowed type ", ), + ( + fnReturningExprSubclass, + "Function has return of disallowed type ", + ), + ( + fnWithMixedAnns4AndBytesReturn, + "Function has return of disallowed type ", + ), ) for fn, msg in cases: diff --git a/pyteal/compiler/zompiler_test.NOTpy b/pyteal/compiler/zompiler_test.NOTpy new file mode 100644 index 000000000..c5972f611 --- /dev/null +++ b/pyteal/compiler/zompiler_test.NOTpy @@ -0,0 +1,1677 @@ +import pytest + +from .. import * + +# this is not necessary but mypy complains if it's not included +from ..ast import * + + +def test_compile_single(): + expr = Int(1) + + expected = """ +#pragma version 2 +int 1 +return +""".strip() + actual_application = compileTeal(expr, Mode.Application) + actual_signature = compileTeal(expr, Mode.Signature) + + assert actual_application == actual_signature + assert actual_application == expected + + +def test_compile_sequence(): + expr = Seq([Pop(Int(1)), Pop(Int(2)), Int(3) + Int(4)]) + + expected = """ +#pragma version 2 +int 1 +pop +int 2 +pop +int 3 +int 4 ++ +return +""".strip() + actual_application = compileTeal(expr, Mode.Application) + actual_signature = compileTeal(expr, Mode.Signature) + + assert actual_application == actual_signature + assert actual_application == expected + + +def test_compile_branch(): + expr = If(Int(1)).Then(Int(2)).Else(Int(3)) + + expected = """ +#pragma version 2 +int 1 +bnz main_l2 +int 3 +b main_l3 +main_l2: +int 2 +main_l3: +return +""".strip() + actual_application = compileTeal(expr, Mode.Application) + actual_signature = compileTeal(expr, Mode.Signature) + + assert actual_application == actual_signature + assert actual_application == expected + + +def test_compile_branch_multiple(): + expr = If(Int(1)).Then(Int(2)).ElseIf(Int(3)).Then(Int(4)).Else(Int(5)) + + expected = """ +#pragma version 2 +int 1 +bnz main_l4 +int 3 +bnz main_l3 +int 5 +b main_l5 +main_l3: +int 4 +b main_l5 +main_l4: +int 2 +main_l5: +return +""".strip() + actual_application = compileTeal(expr, Mode.Application) + actual_signature = compileTeal(expr, Mode.Signature) + + assert actual_application == actual_signature + assert actual_application == expected + + +def test_empty_branch(): + program = Seq( + [ + If(Txn.application_id() == Int(0)).Then(Seq()), + Approve(), + ] + ) + + expected = """#pragma version 5 +txn ApplicationID +int 0 +== +bnz main_l1 +main_l1: +int 1 +return + """.strip() + actual = compileTeal(program, Mode.Application, version=5, assembleConstants=False) + assert actual == expected + + +def test_compile_mode(): + expr = App.globalGet(Bytes("key")) + + expected = """ +#pragma version 2 +byte "key" +app_global_get +return +""".strip() + actual_application = compileTeal(expr, Mode.Application) + + assert actual_application == expected + + with pytest.raises(TealInputError): + compileTeal(expr, Mode.Signature) + + +def test_compile_version_invalid(): + expr = Int(1) + + with pytest.raises(TealInputError): + compileTeal(expr, Mode.Signature, version=1) # too small + + with pytest.raises(TealInputError): + compileTeal(expr, Mode.Signature, version=7) # too large + + with pytest.raises(TealInputError): + compileTeal(expr, Mode.Signature, version=2.0) # decimal + + +def test_compile_version_2(): + expr = Int(1) + + expected = """ +#pragma version 2 +int 1 +return +""".strip() + actual = compileTeal(expr, Mode.Signature, version=2) + assert actual == expected + + +def test_compile_version_default(): + expr = Int(1) + + actual_default = compileTeal(expr, Mode.Signature) + actual_version_2 = compileTeal(expr, Mode.Signature, version=2) + assert actual_default == actual_version_2 + + +def test_compile_version_3(): + expr = Int(1) + + expected = """ +#pragma version 3 +int 1 +return +""".strip() + actual = compileTeal(expr, Mode.Signature, version=3) + assert actual == expected + + +def test_compile_version_4(): + expr = Int(1) + + expected = """ +#pragma version 4 +int 1 +return +""".strip() + actual = compileTeal(expr, Mode.Signature, version=4) + assert actual == expected + + +def test_compile_version_5(): + expr = Int(1) + expected = """ +#pragma version 5 +int 1 +return +""".strip() + actual = compileTeal(expr, Mode.Signature, version=5) + assert actual == expected + + +def test_compile_version_6(): + expr = Int(1) + expected = """ +#pragma version 6 +int 1 +return +""".strip() + actual = compileTeal(expr, Mode.Signature, version=6) + assert actual == expected + + +def test_slot_load_before_store(): + + program = AssetHolding.balance(Int(0), Int(0)).value() + with pytest.raises(TealInternalError): + compileTeal(program, Mode.Application, version=2) + + program = AssetHolding.balance(Int(0), Int(0)).hasValue() + with pytest.raises(TealInternalError): + compileTeal(program, Mode.Application, version=2) + + program = App.globalGetEx(Int(0), Bytes("key")).value() + with pytest.raises(TealInternalError): + compileTeal(program, Mode.Application, version=2) + + program = App.globalGetEx(Int(0), Bytes("key")).hasValue() + with pytest.raises(TealInternalError): + compileTeal(program, Mode.Application, version=2) + + program = ScratchVar().load() + with pytest.raises(TealInternalError): + compileTeal(program, Mode.Application, version=2) + + +def test_assign_scratch_slots(): + myScratch = ScratchVar(TealType.uint64) + otherScratch = ScratchVar(TealType.uint64, 1) + anotherScratch = ScratchVar(TealType.uint64, 0) + lastScratch = ScratchVar(TealType.uint64) + prog = Seq( + [ + myScratch.store(Int(5)), # Slot 2 + otherScratch.store(Int(0)), # Slot 1 + anotherScratch.store(Int(7)), # Slot 0 + lastScratch.store(Int(9)), # Slot 3 + Approve(), + ] + ) + + expected = """ +#pragma version 4 +int 5 +store 2 +int 0 +store 1 +int 7 +store 0 +int 9 +store 3 +int 1 +return +""".strip() + actual = compileTeal(prog, mode=Mode.Signature, version=4) + assert actual == expected + + +def test_scratchvar_double_assign_invalid(): + myvar = ScratchVar(TealType.uint64, 10) + otherVar = ScratchVar(TealType.uint64, 10) + prog = Seq([myvar.store(Int(5)), otherVar.store(Int(0)), Approve()]) + with pytest.raises(TealInternalError): + compileTeal(prog, mode=Mode.Signature, version=4) + + +def test_assembleConstants(): + program = Itob(Int(1) + Int(1) + Tmpl.Int("TMPL_VAR")) == Concat( + Bytes("test"), Bytes("test"), Bytes("test2") + ) + + expectedNoAssemble = """ +#pragma version 3 +int 1 +int 1 ++ +int TMPL_VAR ++ +itob +byte "test" +byte "test" +concat +byte "test2" +concat +== +return +""".strip() + actualNoAssemble = compileTeal( + program, Mode.Application, version=3, assembleConstants=False + ) + assert expectedNoAssemble == actualNoAssemble + + expectedAssemble = """ +#pragma version 3 +intcblock 1 +bytecblock 0x74657374 +intc_0 // 1 +intc_0 // 1 ++ +pushint TMPL_VAR // TMPL_VAR ++ +itob +bytec_0 // "test" +bytec_0 // "test" +concat +pushbytes 0x7465737432 // "test2" +concat +== +return +""".strip() + actualAssemble = compileTeal( + program, Mode.Application, version=3, assembleConstants=True + ) + assert expectedAssemble == actualAssemble + + with pytest.raises(TealInternalError): + compileTeal(program, Mode.Application, version=2, assembleConstants=True) + + +def test_compile_while(): + i = ScratchVar() + program = Seq( + [ + i.store(Int(0)), + While(i.load() < Int(2)).Do(Seq([i.store(i.load() + Int(1))])), + Approve(), + ] + ) + + expected = """ + #pragma version 4 +int 0 +store 0 +main_l1: +load 0 +int 2 +< +bz main_l3 +load 0 +int 1 ++ +store 0 +b main_l1 +main_l3: +int 1 +return + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert expected == actual + + # nested + i = ScratchVar() + j = ScratchVar() + + program = Seq( + [ + i.store(Int(0)), + While(i.load() < Int(2)).Do( + Seq( + [ + j.store(Int(0)), + While(j.load() < Int(5)).Do(Seq([j.store(j.load() + Int(1))])), + i.store(i.load() + Int(1)), + ] + ) + ), + Approve(), + ] + ) + + expected = """#pragma version 4 +int 0 +store 0 +main_l1: +load 0 +int 2 +< +bz main_l6 +int 0 +store 1 +main_l3: +load 1 +int 5 +< +bnz main_l5 +load 0 +int 1 ++ +store 0 +b main_l1 +main_l5: +load 1 +int 1 ++ +store 1 +b main_l3 +main_l6: +int 1 +return + """.strip() + + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert expected == actual + + +def test_compile_for(): + i = ScratchVar() + program = Seq( + [ + For(i.store(Int(0)), i.load() < Int(10), i.store(i.load() + Int(1))).Do( + Seq([App.globalPut(Itob(i.load()), i.load() * Int(2))]) + ), + Approve(), + ] + ) + + expected = """#pragma version 4 +int 0 +store 0 +main_l1: +load 0 +int 10 +< +bz main_l3 +load 0 +itob +load 0 +int 2 +* +app_global_put +load 0 +int 1 ++ +store 0 +b main_l1 +main_l3: +int 1 +return + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert expected == actual + + # nested + i = ScratchVar() + j = ScratchVar() + program = Seq( + [ + For(i.store(Int(0)), i.load() < Int(10), i.store(i.load() + Int(1))).Do( + Seq( + [ + For( + j.store(Int(0)), + j.load() < Int(4), + j.store(j.load() + Int(2)), + ).Do(Seq([App.globalPut(Itob(j.load()), j.load() * Int(2))])) + ] + ) + ), + Approve(), + ] + ) + + expected = """#pragma version 4 +int 0 +store 0 +main_l1: +load 0 +int 10 +< +bz main_l6 +int 0 +store 1 +main_l3: +load 1 +int 4 +< +bnz main_l5 +load 0 +int 1 ++ +store 0 +b main_l1 +main_l5: +load 1 +itob +load 1 +int 2 +* +app_global_put +load 1 +int 2 ++ +store 1 +b main_l3 +main_l6: +int 1 +return + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert expected == actual + + +def test_compile_break(): + + # While + i = ScratchVar() + program = Seq( + [ + i.store(Int(0)), + While(i.load() < Int(3)).Do( + Seq([If(i.load() == Int(2), Break()), i.store(i.load() + Int(1))]) + ), + Approve(), + ] + ) + + expected = """#pragma version 4 +int 0 +store 0 +main_l1: +load 0 +int 3 +< +bz main_l4 +load 0 +int 2 +== +bnz main_l4 +load 0 +int 1 ++ +store 0 +b main_l1 +main_l4: +int 1 +return + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert expected == actual + + # For + i = ScratchVar() + program = Seq( + [ + For(i.store(Int(0)), i.load() < Int(10), i.store(i.load() + Int(1))).Do( + Seq( + [ + If(i.load() == Int(4), Break()), + App.globalPut(Itob(i.load()), i.load() * Int(2)), + ] + ) + ), + Approve(), + ] + ) + + expected = """#pragma version 4 +int 0 +store 0 +main_l1: +load 0 +int 10 +< +bz main_l4 +load 0 +int 4 +== +bnz main_l4 +load 0 +itob +load 0 +int 2 +* +app_global_put +load 0 +int 1 ++ +store 0 +b main_l1 +main_l4: +int 1 +return + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert expected == actual + + +def test_compile_continue(): + # While + i = ScratchVar() + program = Seq( + [ + i.store(Int(0)), + While(i.load() < Int(3)).Do( + Seq([If(i.load() == Int(2), Continue()), i.store(i.load() + Int(1))]) + ), + Approve(), + ] + ) + + expected = """#pragma version 4 +int 0 +store 0 +main_l1: +load 0 +int 3 +< +bz main_l4 +main_l2: +load 0 +int 2 +== +bnz main_l2 +load 0 +int 1 ++ +store 0 +b main_l1 +main_l4: +int 1 +return + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert expected == actual + + # For + i = ScratchVar() + program = Seq( + [ + For(i.store(Int(0)), i.load() < Int(10), i.store(i.load() + Int(1))).Do( + Seq( + [ + If(i.load() == Int(4), Continue()), + App.globalPut(Itob(i.load()), i.load() * Int(2)), + ] + ) + ), + Approve(), + ] + ) + + expected = """#pragma version 4 +int 0 +store 0 +main_l1: +load 0 +int 10 +< +bz main_l5 +load 0 +int 4 +== +bnz main_l4 +load 0 +itob +load 0 +int 2 +* +app_global_put +main_l4: +load 0 +int 1 ++ +store 0 +b main_l1 +main_l5: +int 1 +return + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert expected == actual + + +def test_compile_continue_break_nested(): + + i = ScratchVar() + program = Seq( + [ + i.store(Int(0)), + While(i.load() < Int(10)).Do( + Seq( + [ + i.store(i.load() + Int(1)), + If(i.load() < Int(4), Continue(), Break()), + ] + ) + ), + Approve(), + ] + ) + + expected = """#pragma version 4 +int 0 +store 0 +load 0 +int 10 +< +bz main_l2 +main_l1: +load 0 +int 1 ++ +store 0 +load 0 +int 4 +< +bnz main_l1 +main_l2: +int 1 +return + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert expected == actual + + i = ScratchVar() + program = Seq( + [ + i.store(Int(0)), + While(i.load() < Int(10)).Do( + Seq( + [ + If(i.load() == Int(8), Break()), + While(i.load() < Int(6)).Do( + Seq( + [ + If(i.load() == Int(3), Break()), + i.store(i.load() + Int(1)), + ] + ) + ), + If(i.load() < Int(5), Continue()), + i.store(i.load() + Int(1)), + ] + ) + ), + Approve(), + ] + ) + + expected = """#pragma version 4 +int 0 +store 0 +main_l1: +load 0 +int 10 +< +bz main_l8 +main_l2: +load 0 +int 8 +== +bnz main_l8 +main_l3: +load 0 +int 6 +< +bnz main_l6 +main_l4: +load 0 +int 5 +< +bnz main_l2 +load 0 +int 1 ++ +store 0 +b main_l1 +main_l6: +load 0 +int 3 +== +bnz main_l4 +load 0 +int 1 ++ +store 0 +b main_l3 +main_l8: +int 1 +return +""".strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert expected == actual + + +def test_compile_subroutine_unsupported(): + @Subroutine(TealType.none) + def storeValue(value: Expr) -> Expr: + return App.globalPut(Bytes("key"), value) + + program = Seq( + [ + If(Txn.sender() == Global.creator_address()).Then( + storeValue(Txn.application_args[0]) + ), + Approve(), + ] + ) + + with pytest.raises(TealInputError): + compileTeal(program, Mode.Application, version=3) + + +def test_compile_subroutine_no_return(): + @Subroutine(TealType.none) + def storeValue(value: Expr) -> Expr: + return App.globalPut(Bytes("key"), value) + + program = Seq( + [ + If(Txn.sender() == Global.creator_address()).Then( + storeValue(Txn.application_args[0]) + ), + Approve(), + ] + ) + + expected = """#pragma version 4 +txn Sender +global CreatorAddress +== +bz main_l2 +txna ApplicationArgs 0 +callsub storeValue_0 +main_l2: +int 1 +return + +// storeValue +storeValue_0: +store 0 +byte "key" +load 0 +app_global_put +retsub + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert actual == expected + + +def test_compile_subroutine_with_return(): + @Subroutine(TealType.none) + def storeValue(value: Expr) -> Expr: + return App.globalPut(Bytes("key"), value) + + @Subroutine(TealType.bytes) + def getValue() -> Expr: + return App.globalGet(Bytes("key")) + + program = Seq( + [ + If(Txn.sender() == Global.creator_address()).Then( + storeValue(Txn.application_args[0]) + ), + If(getValue() == Bytes("fail")).Then(Reject()), + Approve(), + ] + ) + + expected = """#pragma version 4 +txn Sender +global CreatorAddress +== +bnz main_l3 +main_l1: +callsub getValue_1 +byte "fail" +== +bz main_l4 +int 0 +return +main_l3: +txna ApplicationArgs 0 +callsub storeValue_0 +b main_l1 +main_l4: +int 1 +return + +// storeValue +storeValue_0: +store 0 +byte "key" +load 0 +app_global_put +retsub + +// getValue +getValue_1: +byte "key" +app_global_get +retsub + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert actual == expected + + +def test_compile_subroutine_many_args(): + @Subroutine(TealType.uint64) + def calculateSum( + a1: Expr, a2: Expr, a3: Expr, a4: Expr, a5: Expr, a6: Expr + ) -> Expr: + return a1 + a2 + a3 + a4 + a5 + a6 + + program = Return( + calculateSum(Int(1), Int(2), Int(3), Int(4), Int(5), Int(6)) + == Int(1 + 2 + 3 + 4 + 5 + 6) + ) + + expected = """#pragma version 4 +int 1 +int 2 +int 3 +int 4 +int 5 +int 6 +callsub calculateSum_0 +int 21 +== +return + +// calculateSum +calculateSum_0: +store 5 +store 4 +store 3 +store 2 +store 1 +store 0 +load 0 +load 1 ++ +load 2 ++ +load 3 ++ +load 4 ++ +load 5 ++ +retsub + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert actual == expected + + +def test_compile_subroutine_recursive(): + @Subroutine(TealType.uint64) + def isEven(i: Expr) -> Expr: + return ( + If(i == Int(0)) + .Then(Int(1)) + .ElseIf(i == Int(1)) + .Then(Int(0)) + .Else(isEven(i - Int(2))) + ) + + program = Return(isEven(Int(6))) + + expected = """#pragma version 4 +int 6 +callsub isEven_0 +return + +// isEven +isEven_0: +store 0 +load 0 +int 0 +== +bnz isEven_0_l4 +load 0 +int 1 +== +bnz isEven_0_l3 +load 0 +int 2 +- +load 0 +dig 1 +callsub isEven_0 +swap +store 0 +swap +pop +b isEven_0_l5 +isEven_0_l3: +int 0 +b isEven_0_l5 +isEven_0_l4: +int 1 +isEven_0_l5: +retsub + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert actual == expected + + +def test_compile_subroutine_recursive_5(): + @Subroutine(TealType.uint64) + def isEven(i: Expr) -> Expr: + return ( + If(i == Int(0)) + .Then(Int(1)) + .ElseIf(i == Int(1)) + .Then(Int(0)) + .Else(isEven(i - Int(2))) + ) + + program = Return(isEven(Int(6))) + + expected = """#pragma version 5 +int 6 +callsub isEven_0 +return + +// isEven +isEven_0: +store 0 +load 0 +int 0 +== +bnz isEven_0_l4 +load 0 +int 1 +== +bnz isEven_0_l3 +load 0 +int 2 +- +load 0 +swap +callsub isEven_0 +swap +store 0 +b isEven_0_l5 +isEven_0_l3: +int 0 +b isEven_0_l5 +isEven_0_l4: +int 1 +isEven_0_l5: +retsub + """.strip() + actual = compileTeal(program, Mode.Application, version=5, assembleConstants=False) + assert actual == expected + + +def test_compile_subroutine_recursive_multiple_args(): + @Subroutine(TealType.uint64) + def multiplyByAdding(a, b): + return ( + If(a == Int(0)) + .Then(Return(Int(0))) + .Else(Return(b + multiplyByAdding(a - Int(1), b))) + ) + + program = Return(multiplyByAdding(Int(3), Int(5))) + + expected = """#pragma version 4 +int 3 +int 5 +callsub multiplyByAdding_0 +return + +// multiplyByAdding +multiplyByAdding_0: +store 1 +store 0 +load 0 +int 0 +== +bnz multiplyByAdding_0_l2 +load 1 +load 0 +int 1 +- +load 1 +load 0 +load 1 +dig 3 +dig 3 +callsub multiplyByAdding_0 +store 0 +store 1 +load 0 +swap +store 0 +swap +pop +swap +pop ++ +retsub +multiplyByAdding_0_l2: +int 0 +retsub + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert actual == expected + + +def test_compile_subroutine_recursive_multiple_args_5(): + @Subroutine(TealType.uint64) + def multiplyByAdding(a, b): + return ( + If(a == Int(0)) + .Then(Return(Int(0))) + .Else(Return(b + multiplyByAdding(a - Int(1), b))) + ) + + program = Return(multiplyByAdding(Int(3), Int(5))) + + expected = """#pragma version 5 +int 3 +int 5 +callsub multiplyByAdding_0 +return + +// multiplyByAdding +multiplyByAdding_0: +store 1 +store 0 +load 0 +int 0 +== +bnz multiplyByAdding_0_l2 +load 1 +load 0 +int 1 +- +load 1 +load 0 +load 1 +uncover 3 +uncover 3 +callsub multiplyByAdding_0 +cover 2 +store 1 +store 0 ++ +retsub +multiplyByAdding_0_l2: +int 0 +retsub + """.strip() + actual = compileTeal(program, Mode.Application, version=5, assembleConstants=False) + assert actual == expected + + +def test_compile_subroutine_mutually_recursive(): + @Subroutine(TealType.uint64) + def isEven(i: Expr) -> Expr: + return If(i == Int(0), Int(1), Not(isOdd(i - Int(1)))) + + @Subroutine(TealType.uint64) + def isOdd(i: Expr) -> Expr: + return If(i == Int(0), Int(0), Not(isEven(i - Int(1)))) + + program = Return(isEven(Int(6))) + + expected = """#pragma version 4 +int 6 +callsub isEven_0 +return + +// isEven +isEven_0: +store 0 +load 0 +int 0 +== +bnz isEven_0_l2 +load 0 +int 1 +- +load 0 +dig 1 +callsub isOdd_1 +swap +store 0 +swap +pop +! +b isEven_0_l3 +isEven_0_l2: +int 1 +isEven_0_l3: +retsub + +// isOdd +isOdd_1: +store 1 +load 1 +int 0 +== +bnz isOdd_1_l2 +load 1 +int 1 +- +load 1 +dig 1 +callsub isEven_0 +swap +store 1 +swap +pop +! +b isOdd_1_l3 +isOdd_1_l2: +int 0 +isOdd_1_l3: +retsub + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert actual == expected + + +def test_compile_subroutine_mutually_recursive_5(): + @Subroutine(TealType.uint64) + def isEven(i: Expr) -> Expr: + return If(i == Int(0), Int(1), Not(isOdd(i - Int(1)))) + + @Subroutine(TealType.uint64) + def isOdd(i: Expr) -> Expr: + return If(i == Int(0), Int(0), Not(isEven(i - Int(1)))) + + program = Return(isEven(Int(6))) + + expected = """#pragma version 5 +int 6 +callsub isEven_0 +return + +// isEven +isEven_0: +store 0 +load 0 +int 0 +== +bnz isEven_0_l2 +load 0 +int 1 +- +load 0 +swap +callsub isOdd_1 +swap +store 0 +! +b isEven_0_l3 +isEven_0_l2: +int 1 +isEven_0_l3: +retsub + +// isOdd +isOdd_1: +store 1 +load 1 +int 0 +== +bnz isOdd_1_l2 +load 1 +int 1 +- +load 1 +swap +callsub isEven_0 +swap +store 1 +! +b isOdd_1_l3 +isOdd_1_l2: +int 0 +isOdd_1_l3: +retsub + """.strip() + actual = compileTeal(program, Mode.Application, version=5, assembleConstants=False) + assert actual == expected + + +def test_compile_loop_in_subroutine(): + @Subroutine(TealType.none) + def setState(value: Expr) -> Expr: + i = ScratchVar() + return For(i.store(Int(0)), i.load() < Int(10), i.store(i.load() + Int(1))).Do( + App.globalPut(Itob(i.load()), value) + ) + + program = Seq([setState(Bytes("value")), Approve()]) + + expected = """#pragma version 4 +byte "value" +callsub setState_0 +int 1 +return + +// setState +setState_0: +store 0 +int 0 +store 1 +setState_0_l1: +load 1 +int 10 +< +bz setState_0_l3 +load 1 +itob +load 0 +app_global_put +load 1 +int 1 ++ +store 1 +b setState_0_l1 +setState_0_l3: +retsub + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert actual == expected + + +def test_compile_subroutine_invalid_name(): + def tmp() -> Expr: + return Int(1) + + tmp.__name__ = "invalid-;)" + + program = Subroutine(TealType.uint64)(tmp)() + expected = """#pragma version 4 +callsub invalid_0 +return + +// invalid-;) +invalid_0: +int 1 +retsub + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) + assert actual == expected + + +def test_compile_subroutine_assemble_constants(): + @Subroutine(TealType.none) + def storeValue(key: Expr, t1: Expr, t2: Expr, t3: Expr) -> Expr: + return App.globalPut(key, t1 + t2 + t3 + Int(10)) + + program = Seq( + [ + If(Txn.application_id() == Int(0)).Then( + storeValue( + Concat(Bytes("test"), Bytes("test"), Bytes("a")), + Int(1), + Int(1), + Int(3), + ) + ), + Approve(), + ] + ) + + expected = """#pragma version 4 +intcblock 1 +bytecblock 0x74657374 +txn ApplicationID +pushint 0 // 0 +== +bz main_l2 +bytec_0 // "test" +bytec_0 // "test" +concat +pushbytes 0x61 // "a" +concat +intc_0 // 1 +intc_0 // 1 +pushint 3 // 3 +callsub storeValue_0 +main_l2: +intc_0 // 1 +return + +// storeValue +storeValue_0: +store 3 +store 2 +store 1 +store 0 +load 0 +load 1 +load 2 ++ +load 3 ++ +pushint 10 // 10 ++ +app_global_put +retsub + """.strip() + actual = compileTeal(program, Mode.Application, version=4, assembleConstants=True) + assert actual == expected + + +def test_compile_wide_ratio(): + cases = ( + ( + WideRatio([Int(2), Int(100)], [Int(5)]), + """#pragma version 5 +int 2 +int 100 +mulw +int 0 +int 5 +divmodw +pop +pop +swap +! +assert +return +""", + ), + ( + WideRatio([Int(2), Int(100)], [Int(10), Int(5)]), + """#pragma version 5 +int 2 +int 100 +mulw +int 10 +int 5 +mulw +divmodw +pop +pop +swap +! +assert +return +""", + ), + ( + WideRatio([Int(2), Int(100), Int(3)], [Int(10), Int(5)]), + """#pragma version 5 +int 2 +int 100 +mulw +int 3 +uncover 2 +dig 1 +* +cover 2 +mulw +cover 2 ++ +swap +int 10 +int 5 +mulw +divmodw +pop +pop +swap +! +assert +return +""", + ), + ( + WideRatio([Int(2), Int(100), Int(3)], [Int(10), Int(5), Int(6)]), + """#pragma version 5 +int 2 +int 100 +mulw +int 3 +uncover 2 +dig 1 +* +cover 2 +mulw +cover 2 ++ +swap +int 10 +int 5 +mulw +int 6 +uncover 2 +dig 1 +* +cover 2 +mulw +cover 2 ++ +swap +divmodw +pop +pop +swap +! +assert +return +""", + ), + ( + WideRatio([Int(2), Int(100), Int(3), Int(4)], [Int(10), Int(5), Int(6)]), + """#pragma version 5 +int 2 +int 100 +mulw +int 3 +uncover 2 +dig 1 +* +cover 2 +mulw +cover 2 ++ +swap +int 4 +uncover 2 +dig 1 +* +cover 2 +mulw +cover 2 ++ +swap +int 10 +int 5 +mulw +int 6 +uncover 2 +dig 1 +* +cover 2 +mulw +cover 2 ++ +swap +divmodw +pop +pop +swap +! +assert +return +""", + ), + ) + + for program, expected in cases: + actual = compileTeal( + program, Mode.Application, version=5, assembleConstants=False + ) + assert actual == expected.strip() + + +import inspect +import dis + + +def z1(foo): + x = { + "bytecode": dis.Bytecode(foo), + "code": inspect.getsource(foo), + "file": inspect.getsourcefile(foo), + "lines": inspect.getsourcelines(foo), + "mod": inspect.getmodule(foo), + "members": inspect.getmembers(foo), + "ismod": inspect.ismodule(foo), + "iscls": inspect.isclass(foo), + "ismeth": inspect.ismethod(foo), + "isfunc": inspect.isfunction(foo), + "isgenfunc": inspect.isgeneratorfunction(foo), + "isgen": inspect.isgenerator(foo), + "iscofunc": inspect.iscoroutinefunction(foo), + "isawait": inspect.isawaitable(foo), + "isasyncgenfunc": inspect.isasyncgenfunction(foo), + "isasyncgen": inspect.isasyncgen(foo), + "iscode": inspect.iscode(foo), + "isbuiltin": inspect.isbuiltin(foo), + "isroutine": inspect.isroutine(foo), + "isabstract": inspect.isabstract(foo), + } + lines = x["lines"] + x["line-map"] = list(zip(range(lines[1], lines[1] + len(lines[0])), lines[0])) + x["instructions"] = [ + instr.opname for instr in reversed([instr for instr in x["bytecode"]]) + ] + + return x + + +def test_z1(): + def f(x): + return x + 1339 + + def foo(): + y = f(42) + + # mom and dad + + # are very important + + return y + + foo_info = z1(foo) + + x = 42 + + +def test_zeph(): + @Subroutine(TealType.none) + def storeValue(key: Expr, t1: Expr, t2: Expr, t3: Expr) -> Expr: + return App.globalPut(key, t1 + t2 + t3 + Int(10)) + + program = Seq( + [ + If(Txn.application_id() == Int(0)).Then( + storeValue( + Concat(Bytes("test"), Bytes("test"), Bytes("a")), + Int(1), + Int(1), + Int(3), + ) + ), + Approve(), + ] + ) + + expected = """#pragma version 4 +intcblock 1 +bytecblock 0x74657374 +txn ApplicationID +pushint 0 // 0 +== +bz main_l2 +bytec_0 // "test" +bytec_0 // "test" +concat +pushbytes 0x61 // "a" +concat +intc_0 // 1 +intc_0 // 1 +pushint 3 // 3 +callsub storeValue_0 +main_l2: +intc_0 // 1 +return + +// storeValue +storeValue_0: +store 3 +store 2 +store 1 +store 0 +load 0 +load 1 +load 2 ++ +load 3 ++ +pushint 10 // 10 ++ +app_global_put +retsub + """.strip() + actual = compileTeal( + program, Mode.Application, version=4, assembleConstants=True, sourceMap=True + ) + assert actual == expected From 99626518e74a3bc019a5c0050c6297c7379a106d Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 28 Feb 2022 22:38:38 -0600 Subject: [PATCH 75/85] Per CR suggestions: tightening up SubroutineDefinition annotation type validation --- pyteal/ast/subroutine.py | 66 ++++++++++++++++++++--------------- pyteal/ast/subroutine_test.py | 5 +-- tests/pass_by_ref_test.py | 2 +- 3 files changed, 41 insertions(+), 32 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index ca10087f1..99b747b1c 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -1,9 +1,11 @@ -from typing import Callable, Dict, List, Optional, Set, Union, TYPE_CHECKING +from collections import OrderedDict from inspect import isclass, Parameter, signature +from typing import Callable, List, Optional, Set, Type, Union, TYPE_CHECKING -from ..types import TealType -from ..ir import TealOp, Op, TealBlock from ..errors import TealInputError, verifyTealVersion +from ..ir import TealOp, Op, TealBlock +from ..types import TealType + from .expr import Expr from .seq import Seq from .scratchvar import DynamicScratchVar, ScratchVar @@ -13,8 +15,6 @@ class SubroutineDefinition: - PARAM_TYPES = (Expr, ScratchVar) - nextSubroutineId = 0 def __init__( @@ -29,15 +29,23 @@ def __init__( self.by_ref_args: Set[str] = set() - self.expected_arg_types: List[type] = [] + self.expected_arg_types: List[Type[Union[Expr, ScratchVar]]] = [] if not callable(implementation): raise TealInputError("Input to SubroutineDefinition is not callable") sig = signature(implementation) + annotations = getattr(implementation, "__annotations__", OrderedDict()) + + if "return" in annotations and annotations["return"] is not Expr: + raise TealInputError( + "Function has return of disallowed type {}. Only Expr is allowed".format( + annotations["return"] + ) + ) + for name, param in sig.parameters.items(): - param = sig.parameters[name] if param.kind not in ( Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD, @@ -56,29 +64,29 @@ def __init__( ) expected_arg_type: type = Expr - if name in implementation.__annotations__: - ptype = implementation.__annotations__[name] - if isclass(ptype) and issubclass(ptype, ScratchVar): - expected_arg_type = ScratchVar - - self.expected_arg_types.append(expected_arg_type) - - for var_name, var_type in implementation.__annotations__.items(): - if var_name == "return" and not var_type is Expr: - raise TealInputError( - "Function has return of disallowed type {}. Only subtype of Expr is allowed".format( - var_type + if name in annotations: + ptype = annotations[name] + if not isclass(ptype): + raise TealInputError( + "Function has parameter {} of declared type {} which is not a class".format( + name, ptype + ) ) - ) - if var_name != "return" and var_type not in self.PARAM_TYPES: - raise TealInputError( - "Function has parameter {} of disallowed type {}. Only the types {} are allowed".format( - var_name, var_type, self.PARAM_TYPES + if ptype not in (Expr, ScratchVar): + raise TealInputError( + "Function has parameter {} of disallowed type {}. Only the types {} are allowed".format( + name, ptype, (Expr, ScratchVar) + ) ) - ) - if var_type is ScratchVar: - self.by_ref_args.add(var_name) + + expected_arg_type = ptype + else: + expected_arg_type = Expr + + self.expected_arg_types.append(expected_arg_type) + if expected_arg_type is ScratchVar: + self.by_ref_args.add(name) self.implementation = implementation self.implementationParams = sig.parameters @@ -322,7 +330,7 @@ def evaluateSubroutine(subroutine: SubroutineDefinition) -> SubroutineDeclaratio 2 Argument Types - -------- ----- Type 1 (by-value): these have python type Expr - Type 2 (by-reference): these hae python type ScratchVar + Type 2 (by-reference): these have python type ScratchVar Usage (A) "argumentVars" - Storing pre-placed stack variables into local scratch space: Type 1. (by-value) use ScratchVar.store() to pick the actual value into a local scratch space @@ -331,7 +339,7 @@ def evaluateSubroutine(subroutine: SubroutineDefinition) -> SubroutineDeclaratio Usage (B) "loadedArgs" - Passing through to an invoked PyTEAL subroutine AST: Type 1. (by-value) use ScratchVar.load() to have an Expr that can be compiled in python by the PyTEAL subroutine - Type 2. (by-reference) use a DynamicScratchVar as the user will have written the PyTEAL in a way that satifies + Type 2. (by-reference) use a DynamicScratchVar as the user will have written the PyTEAL in a way that satisfies the ScratchVar API. I.e., the user will write `x.load()` and `x.store(val)` as opposed to just `x`. """ diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index dc8db3d88..66459b3b3 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -190,11 +190,11 @@ def fnWithMixedAnns4AndBytesReturn(a: Expr, b: ScratchVar) -> Bytes: ), ( fnWithNonExprReturnAnnotation, - "Function has return of disallowed type TealType.uint64. Only subtype of Expr is allowed", + "Function has return of disallowed type TealType.uint64. Only Expr is allowed", ), ( fnWithNonExprParamAnnotation, - "Function has parameter b of disallowed type TealType.uint64. Only the types", + "Function has parameter b of declared type TealType.uint64 which is not a class", ), ( fnWithScratchVarSubclass, @@ -212,6 +212,7 @@ def fnWithMixedAnns4AndBytesReturn(a: Expr, b: ScratchVar) -> Bytes: for fn, msg in cases: with pytest.raises(TealInputError) as e: + print(f"case=[{msg}]") SubroutineDefinition(fn, TealType.none) assert msg in str(e), "failed for case [{}]".format(fn.__name__) diff --git a/tests/pass_by_ref_test.py b/tests/pass_by_ref_test.py index 0dc30c769..fb9a9a079 100644 --- a/tests/pass_by_ref_test.py +++ b/tests/pass_by_ref_test.py @@ -166,7 +166,7 @@ def fac_by_ref(): ) @Subroutine(TealType.uint64) - def mixed_annotations(x: Expr, y: Expr, z: ScratchVar) -> Int: + def mixed_annotations(x: Expr, y: Expr, z: ScratchVar) -> Expr: return Seq( z.store(x), Log(Concat(y, Bytes("="), Itob(x))), From 92917925a26ba38aa63a4b9f2200cb9c169defe4 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 28 Feb 2022 22:59:36 -0600 Subject: [PATCH 76/85] sort and uniquify output of generate_init --- pyteal/__init__.pyi | 16 +++++++++------- pyteal/ast/scratchvar.py | 6 +----- scripts/generate_init.py | 2 +- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index c18a896ef..1591faeea 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -32,10 +32,13 @@ __all__ = [ "AssetParam", "Balance", "BinaryExpr", + "BitLen", "BitwiseAnd", "BitwiseNot", "BitwiseOr", "BitwiseXor", + "Break", + "Btoi", "Bytes", "BytesAdd", "BytesAnd", @@ -55,7 +58,6 @@ __all__ = [ "BytesXor", "BytesZero", "CompileOptions", - "compileTeal", "Concat", "Cond", "Continue", @@ -79,14 +81,14 @@ __all__ = [ "GetBit", "GetByte", "Gitxn", - "GitxnaExpr", "GitxnExpr", + "GitxnaExpr", "Global", "GlobalField", "Gt", "Gtxn", - "GtxnaExpr", "GtxnExpr", + "GtxnaExpr", "If", "ImportScratchValue", "InnerTxn", @@ -104,21 +106,20 @@ __all__ = [ "Lt", "MAX_GROUP_SIZE", "MAX_TEAL_VERSION", + "MIN_TEAL_VERSION", "MaybeValue", "MethodSignature", - "MIN_TEAL_VERSION", "MinBalance", "Minus", "Mod", "Mode", "Mul", "MultiValue", - "NaryExpr", + "NUM_SLOTS", "NaryExpr", "Neq", "Nonce", "Not", - "NUM_SLOTS", "OnComplete", "Op", "Or", @@ -159,14 +160,15 @@ __all__ = [ "TealTypeError", "Tmpl", "Txn", - "TxnaExpr", "TxnArray", "TxnExpr", "TxnField", "TxnGroup", "TxnObject", "TxnType", + "TxnaExpr", "UnaryExpr", "While", "WideRatio", + "compileTeal", ] diff --git a/pyteal/ast/scratchvar.py b/pyteal/ast/scratchvar.py index 1e5c6d8fa..2afb64dde 100644 --- a/pyteal/ast/scratchvar.py +++ b/pyteal/ast/scratchvar.py @@ -1,11 +1,7 @@ -from operator import index -from typing import cast - -from ..errors import TealInputError, TealInternalError +from ..errors import TealInputError from ..types import TealType, require_type from .expr import Expr -from .int import Int from .scratch import ScratchSlot, ScratchLoad, ScratchStore diff --git a/scripts/generate_init.py b/scripts/generate_init.py index 09dd37efe..a84426710 100644 --- a/scripts/generate_init.py +++ b/scripts/generate_init.py @@ -37,7 +37,7 @@ def generate_init_pyi() -> str: start_idx = init_contents.index(begin_flag) end_idx = init_contents.index(end_flag) - all_imports = ",\n ".join(['"{}"'.format(s) for s in static_all]) + all_imports = ",\n ".join(['"{}"'.format(s) for s in sorted(set(static_all))]) return ( pyi_template From 74e05b316c74fee3a81b57ce75768f45a44948ff Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 28 Feb 2022 23:05:16 -0600 Subject: [PATCH 77/85] revert ast/__init__.py's __all__ --- pyteal/ast/__init__.py | 217 ++++++++++++++++++++--------------------- 1 file changed, 108 insertions(+), 109 deletions(-) diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index 87485a7c8..f554daad6 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -132,138 +132,137 @@ from .multi import MultiValue __all__ = [ - "AccountParam", - "Add", + "Expr", + "LeafExpr", "Addr", - "And", + "Bytes", + "Int", + "EnumInt", + "MethodSignature", + "Arg", + "TxnType", + "TxnField", + "TxnExpr", + "TxnaExpr", + "TxnArray", + "TxnObject", + "Txn", + "GtxnExpr", + "GtxnaExpr", + "TxnGroup", + "Gtxn", + "GeneratedID", + "ImportScratchValue", + "Global", + "GlobalField", "App", "AppField", + "OnComplete", "AppParam", - "Approve", - "Arg", - "Array", - "Assert", "AssetHolding", "AssetParam", + "AccountParam", + "InnerTxnBuilder", + "InnerTxn", + "InnerTxnAction", + "Gitxn", + "GitxnExpr", + "GitxnaExpr", + "InnerTxnGroup", + "Array", + "Tmpl", + "Nonce", + "UnaryExpr", + "Btoi", + "Itob", + "Len", + "BitLen", + "Sha256", + "Sha512_256", + "Keccak256", + "Not", + "BitwiseNot", + "Sqrt", + "Pop", "Balance", + "MinBalance", "BinaryExpr", - "BitLen", + "Add", + "Minus", + "Mul", + "Div", + "Mod", + "Exp", + "Divw", "BitwiseAnd", - "BitwiseNot", "BitwiseOr", "BitwiseXor", - "Break", - "Btoi", - "Bytes", - "BytesAdd", - "BytesAnd", - "BytesDiv", - "BytesEq", - "BytesGe", - "BytesGt", - "BytesLe", - "BytesLt", - "BytesMinus", - "BytesMod", - "BytesMul", - "BytesNeq", - "BytesNot", - "BytesOr", - "BytesSqrt", - "BytesXor", - "BytesZero", - "Concat", - "Cond", - "Continue", - "Div", - "Divw", - "DynamicScratchVar", - "Ed25519Verify", - "EnumInt", + "ShiftLeft", + "ShiftRight", "Eq", - "Err", - "Exp", - "Expr", - "Extract", - "ExtractUint16", - "ExtractUint32", - "ExtractUint64", - "For", + "Neq", + "Lt", + "Le", + "Gt", "Ge", - "GeneratedID", "GetBit", "GetByte", - "Gitxn", - "GitxnaExpr", - "GitxnExpr", - "Global", - "GlobalField", - "Gt", - "Gtxn", - "GtxnaExpr", - "GtxnExpr", - "If", - "ImportScratchValue", - "InnerTxn", - "InnerTxnAction", - "InnerTxnBuilder", - "InnerTxnGroup", - "Int", - "Itob", - "Keccak256", - "Le", - "LeafExpr", - "Len", - "Log", - "Lt", - "MaybeValue", - "MethodSignature", - "MinBalance", - "Minus", - "Mod", - "Mul", - "MultiValue", + "Ed25519Verify", + "Substring", + "Extract", + "Suffix", + "SetBit", + "SetByte", "NaryExpr", - "Neq", - "Nonce", - "Not", - "OnComplete", + "And", "Or", - "Pop", - "Reject", - "Reject", + "Concat", + "WideRatio", + "If", + "Cond", + "Seq", + "Assert", + "Err", "Return", + "Approve", + "Reject", + "Subroutine", + "SubroutineDefinition", + "SubroutineDeclaration", + "SubroutineCall", + "SubroutineFnWrapper", "ScratchIndex", "ScratchLoad", "ScratchSlot", "ScratchStackStore", "ScratchStore", + "DynamicScratchVar", "ScratchVar", - "Seq", - "SetBit", - "SetByte", - "Sha256", - "Sha512_256", - "ShiftLeft", - "ShiftRight", - "Sqrt", - "Subroutine", - "SubroutineCall", - "SubroutineDeclaration", - "SubroutineDefinition", - "SubroutineFnWrapper", - "Substring", - "Suffix", - "Tmpl", - "Txn", - "TxnaExpr", - "TxnArray", - "TxnExpr", - "TxnField", - "TxnGroup", - "TxnObject", - "TxnType", - "UnaryExpr", + "MaybeValue", + "MultiValue", + "BytesAdd", + "BytesMinus", + "BytesDiv", + "BytesMul", + "BytesMod", + "BytesAnd", + "BytesOr", + "BytesXor", + "BytesEq", + "BytesNeq", + "BytesLt", + "BytesLe", + "BytesGt", + "BytesGe", + "BytesNot", + "BytesSqrt", + "BytesZero", + "ExtractUint16", + "ExtractUint32", + "ExtractUint64", + "Log", "While", - "WideRatio", + "For", + "Break", + "Continue", ] From cfd5ce5efe7a7c73285ba85c1019faf020d6ddd0 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 28 Feb 2022 23:39:47 -0600 Subject: [PATCH 78/85] per CR suggestions: better validation and __str__() for ScratchLoad/Store --- pyteal/ast/scratch.py | 33 +++++++++++++++++++++++++-------- pyteal/ast/scratch_test.py | 2 -- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/pyteal/ast/scratch.py b/pyteal/ast/scratch.py index b538fb0f2..ba738f64c 100644 --- a/pyteal/ast/scratch.py +++ b/pyteal/ast/scratch.py @@ -1,6 +1,6 @@ from typing import cast, TYPE_CHECKING, Optional -from ..types import TealType +from ..types import TealType, require_type from ..config import NUM_SLOTS from ..errors import TealInputError, TealInternalError from .expr import Expr @@ -80,9 +80,6 @@ def __init__(self, slot: ScratchSlot): def __str__(self): return "(ScratchIndex {})".format(self.slot) - def __hash__(self): - return hash(self.slot.id) - def type_of(self): return TealType.uint64 @@ -104,7 +101,7 @@ class ScratchLoad(Expr): def __init__( self, - slot: Optional[ScratchSlot], + slot: ScratchSlot = None, type: TealType = TealType.anytype, index_expression: Expr = None, ): @@ -124,6 +121,15 @@ def __init__( "Exactly one of slot or index_expressions must be provided" ) + if index_expression: + if not isinstance(index_expression, Expr): + raise TealInputError( + "index_expression must be an Expr but was of type {}".format( + type(index_expression) + ) + ) + require_type(index_expression, TealType.uint64) + if slot and not isinstance(slot, ScratchSlot): raise TealInputError( "cannot handle slot of type {}".format(type(self.slot)) @@ -134,7 +140,7 @@ def __init__( self.index_expression = index_expression def __str__(self): - return "(Load {})".format(self.slot) + return "(Load {})".format(self.slot if self.slot else self.index_expression) def __teal__(self, options: "CompileOptions"): from ..ir import TealOp, Op, TealBlock @@ -178,12 +184,23 @@ def __init__( "Exactly one of slot or index_expressions must be provided" ) + if index_expression: + if not isinstance(index_expression, Expr): + raise TealInputError( + "index_expression must be an Expr but was of type {}".format( + type(index_expression) + ) + ) + require_type(index_expression, TealType.uint64) + self.slot = slot self.value = value self.index_expression = index_expression def __str__(self): - return "(Store {} {})".format(self.slot, self.value) + return "(Store {} {})".format( + self.slot if self.slot else self.index_expression, self.value + ) def __teal__(self, options: "CompileOptions"): from ..ir import TealOp, Op, TealBlock @@ -226,7 +243,7 @@ def __init__(self, slot: ScratchSlot): self.slot = slot def __str__(self): - return "(ScratchStackStore {})".format(self.slot) + return "(StackStore {})".format(self.slot) def __teal__(self, options: "CompileOptions"): from ..ir import TealOp, Op, TealBlock diff --git a/pyteal/ast/scratch_test.py b/pyteal/ast/scratch_test.py index 15b518ae8..6f47e9c5b 100644 --- a/pyteal/ast/scratch_test.py +++ b/pyteal/ast/scratch_test.py @@ -151,8 +151,6 @@ def test_scratch_index(): assert str(index) == "(ScratchIndex " + str(slot) + ")" - assert hash(index) == hash(slot.id) - assert index.type_of() == TealType.uint64 assert not index.has_return() From 0d40db147d44613ed180812f6fb01d7333257a8b Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 28 Feb 2022 23:49:43 -0600 Subject: [PATCH 79/85] per CR comments: clearer tests --- pyteal/ast/scratch_test.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pyteal/ast/scratch_test.py b/pyteal/ast/scratch_test.py index 6f47e9c5b..6379efb1c 100644 --- a/pyteal/ast/scratch_test.py +++ b/pyteal/ast/scratch_test.py @@ -48,7 +48,7 @@ def test_scratch_load_index_expression(): assert expr.type_of() == TealType.anytype expected = TealSimpleBlock([TealOp(Int(1337), Op.int, 1337)]) - expected.nextBlock = TealSimpleBlock([TealOp(expr, Op.loads)]) + expected.setNextBlock(TealSimpleBlock([TealOp(None, Op.loads)])) actual, _ = expr.__teal__(options) @@ -99,9 +99,10 @@ def test_scratch_store_index_expression(): expr = ScratchStore(slot=None, value=value, index_expression=Int(1337)) assert expr.type_of() == TealType.none - expected = TealSimpleBlock([TealOp(Int(1337), Op.int, 1337)]) + expected = TealSimpleBlock([TealOp(None, Op.int, 1337)]) valueStart, valueEnd = value.__teal__(options) - expected.nextBlock = valueStart + expected.setNextBlock(valueStart) + storeBlock = TealSimpleBlock([TealOp(expr, Op.stores)]) valueEnd.setNextBlock(storeBlock) From 4f39f5b7f0fa71c198c12180d96b318bc5d48878 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 1 Mar 2022 00:14:27 -0600 Subject: [PATCH 80/85] per CR suggestion: tighter Dynamic/ScratchVar class hierarchy --- pyteal/ast/scratchvar.py | 23 +++++++++++------------ pyteal/ast/scratchvar_test.py | 6 +++--- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/pyteal/ast/scratchvar.py b/pyteal/ast/scratchvar.py index e8da50918..b71c0048f 100644 --- a/pyteal/ast/scratchvar.py +++ b/pyteal/ast/scratchvar.py @@ -90,9 +90,8 @@ def __init__(self, ttype: TealType = TealType.anytype): Args: ttype (optional): The type that this variable can hold. Defaults to TealType.anytype. """ - self.type = ttype - self._indexer = ScratchVar(TealType.uint64) - self.slot = self._indexer.slot + super().__init__(TealType.uint64) + self.dynamic_type = ttype # differentiates from ScratchVar.type def set_index(self, index_var: ScratchVar) -> Expr: """Set this DynamicScratchVar to reference the provided `index_var`. @@ -106,30 +105,30 @@ def set_index(self, index_var: ScratchVar) -> Expr: ) ) - return self._indexer.store(index_var.index()) + return super().store(index_var.index()) def storage_type(self) -> TealType: """Get the type of expressions that can be stored in this ScratchVar.""" - return self.type + return self.dynamic_type def store(self, value: Expr) -> Expr: """Store the value in the referenced ScratchVar.""" - require_type(value, self.type) - index = ScratchLoad(self._indexer.slot, TealType.uint64) - return ScratchStore(slot=None, value=value, index_expression=index) + require_type(value, self.dynamic_type) + return ScratchStore(slot=None, value=value, index_expression=self.index()) def load(self) -> ScratchLoad: """Load the current value from the referenced ScratchVar.""" - index = ScratchLoad(self._indexer.slot, TealType.uint64) - return ScratchLoad(slot=None, type=self.type, index_expression=index) + return ScratchLoad( + slot=None, type=self.dynamic_type, index_expression=self.index() + ) def index(self) -> Expr: """Get the index of the referenced ScratchVar.""" - return self._indexer.load() + return super().load() def internal_index(self) -> Expr: """Get the index of _this_ DynamicScratchVar, as opposed to that of the referenced ScratchVar.""" - return self._indexer.index() + return super().index() DynamicScratchVar.__module__ = "pyteal" diff --git a/pyteal/ast/scratchvar_test.py b/pyteal/ast/scratchvar_test.py index d7d262c98..b757db862 100644 --- a/pyteal/ast/scratchvar_test.py +++ b/pyteal/ast/scratchvar_test.py @@ -157,7 +157,7 @@ def test_dynamic_scratchvar_load(): expected = TealSimpleBlock( [ - TealOp(ScratchLoad(myvar._indexer.slot), Op.load, myvar._indexer.slot), + TealOp(ScratchLoad(myvar.slot), Op.load, myvar.slot), TealOp(expr, Op.loads), ] ) @@ -177,7 +177,7 @@ def test_dynamic_scratchvar_store(): expected = TealSimpleBlock( [ - TealOp(ScratchLoad(myvar._indexer.slot), Op.load, myvar._indexer.slot), + TealOp(ScratchLoad(myvar.slot), Op.load, myvar.slot), TealOp(arg, Op.byte, '"value"'), TealOp(expr, Op.stores), ] @@ -195,7 +195,7 @@ def test_dynamic_scratchvar_index(): myvar = DynamicScratchVar() expr = myvar.index() - expected = TealSimpleBlock([TealOp(expr, Op.load, myvar._indexer.slot)]) + expected = TealSimpleBlock([TealOp(expr, Op.load, myvar.slot)]) actual, _ = expr.__teal__(options) actual.addIncoming() From 69a50e6bd286173397ff5dda80d9561fd8539a15 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 1 Mar 2022 00:36:41 -0600 Subject: [PATCH 81/85] revert --- pyteal/compiler/zompiler_test.NOTpy | 1677 --------------------------- 1 file changed, 1677 deletions(-) delete mode 100644 pyteal/compiler/zompiler_test.NOTpy diff --git a/pyteal/compiler/zompiler_test.NOTpy b/pyteal/compiler/zompiler_test.NOTpy deleted file mode 100644 index c5972f611..000000000 --- a/pyteal/compiler/zompiler_test.NOTpy +++ /dev/null @@ -1,1677 +0,0 @@ -import pytest - -from .. import * - -# this is not necessary but mypy complains if it's not included -from ..ast import * - - -def test_compile_single(): - expr = Int(1) - - expected = """ -#pragma version 2 -int 1 -return -""".strip() - actual_application = compileTeal(expr, Mode.Application) - actual_signature = compileTeal(expr, Mode.Signature) - - assert actual_application == actual_signature - assert actual_application == expected - - -def test_compile_sequence(): - expr = Seq([Pop(Int(1)), Pop(Int(2)), Int(3) + Int(4)]) - - expected = """ -#pragma version 2 -int 1 -pop -int 2 -pop -int 3 -int 4 -+ -return -""".strip() - actual_application = compileTeal(expr, Mode.Application) - actual_signature = compileTeal(expr, Mode.Signature) - - assert actual_application == actual_signature - assert actual_application == expected - - -def test_compile_branch(): - expr = If(Int(1)).Then(Int(2)).Else(Int(3)) - - expected = """ -#pragma version 2 -int 1 -bnz main_l2 -int 3 -b main_l3 -main_l2: -int 2 -main_l3: -return -""".strip() - actual_application = compileTeal(expr, Mode.Application) - actual_signature = compileTeal(expr, Mode.Signature) - - assert actual_application == actual_signature - assert actual_application == expected - - -def test_compile_branch_multiple(): - expr = If(Int(1)).Then(Int(2)).ElseIf(Int(3)).Then(Int(4)).Else(Int(5)) - - expected = """ -#pragma version 2 -int 1 -bnz main_l4 -int 3 -bnz main_l3 -int 5 -b main_l5 -main_l3: -int 4 -b main_l5 -main_l4: -int 2 -main_l5: -return -""".strip() - actual_application = compileTeal(expr, Mode.Application) - actual_signature = compileTeal(expr, Mode.Signature) - - assert actual_application == actual_signature - assert actual_application == expected - - -def test_empty_branch(): - program = Seq( - [ - If(Txn.application_id() == Int(0)).Then(Seq()), - Approve(), - ] - ) - - expected = """#pragma version 5 -txn ApplicationID -int 0 -== -bnz main_l1 -main_l1: -int 1 -return - """.strip() - actual = compileTeal(program, Mode.Application, version=5, assembleConstants=False) - assert actual == expected - - -def test_compile_mode(): - expr = App.globalGet(Bytes("key")) - - expected = """ -#pragma version 2 -byte "key" -app_global_get -return -""".strip() - actual_application = compileTeal(expr, Mode.Application) - - assert actual_application == expected - - with pytest.raises(TealInputError): - compileTeal(expr, Mode.Signature) - - -def test_compile_version_invalid(): - expr = Int(1) - - with pytest.raises(TealInputError): - compileTeal(expr, Mode.Signature, version=1) # too small - - with pytest.raises(TealInputError): - compileTeal(expr, Mode.Signature, version=7) # too large - - with pytest.raises(TealInputError): - compileTeal(expr, Mode.Signature, version=2.0) # decimal - - -def test_compile_version_2(): - expr = Int(1) - - expected = """ -#pragma version 2 -int 1 -return -""".strip() - actual = compileTeal(expr, Mode.Signature, version=2) - assert actual == expected - - -def test_compile_version_default(): - expr = Int(1) - - actual_default = compileTeal(expr, Mode.Signature) - actual_version_2 = compileTeal(expr, Mode.Signature, version=2) - assert actual_default == actual_version_2 - - -def test_compile_version_3(): - expr = Int(1) - - expected = """ -#pragma version 3 -int 1 -return -""".strip() - actual = compileTeal(expr, Mode.Signature, version=3) - assert actual == expected - - -def test_compile_version_4(): - expr = Int(1) - - expected = """ -#pragma version 4 -int 1 -return -""".strip() - actual = compileTeal(expr, Mode.Signature, version=4) - assert actual == expected - - -def test_compile_version_5(): - expr = Int(1) - expected = """ -#pragma version 5 -int 1 -return -""".strip() - actual = compileTeal(expr, Mode.Signature, version=5) - assert actual == expected - - -def test_compile_version_6(): - expr = Int(1) - expected = """ -#pragma version 6 -int 1 -return -""".strip() - actual = compileTeal(expr, Mode.Signature, version=6) - assert actual == expected - - -def test_slot_load_before_store(): - - program = AssetHolding.balance(Int(0), Int(0)).value() - with pytest.raises(TealInternalError): - compileTeal(program, Mode.Application, version=2) - - program = AssetHolding.balance(Int(0), Int(0)).hasValue() - with pytest.raises(TealInternalError): - compileTeal(program, Mode.Application, version=2) - - program = App.globalGetEx(Int(0), Bytes("key")).value() - with pytest.raises(TealInternalError): - compileTeal(program, Mode.Application, version=2) - - program = App.globalGetEx(Int(0), Bytes("key")).hasValue() - with pytest.raises(TealInternalError): - compileTeal(program, Mode.Application, version=2) - - program = ScratchVar().load() - with pytest.raises(TealInternalError): - compileTeal(program, Mode.Application, version=2) - - -def test_assign_scratch_slots(): - myScratch = ScratchVar(TealType.uint64) - otherScratch = ScratchVar(TealType.uint64, 1) - anotherScratch = ScratchVar(TealType.uint64, 0) - lastScratch = ScratchVar(TealType.uint64) - prog = Seq( - [ - myScratch.store(Int(5)), # Slot 2 - otherScratch.store(Int(0)), # Slot 1 - anotherScratch.store(Int(7)), # Slot 0 - lastScratch.store(Int(9)), # Slot 3 - Approve(), - ] - ) - - expected = """ -#pragma version 4 -int 5 -store 2 -int 0 -store 1 -int 7 -store 0 -int 9 -store 3 -int 1 -return -""".strip() - actual = compileTeal(prog, mode=Mode.Signature, version=4) - assert actual == expected - - -def test_scratchvar_double_assign_invalid(): - myvar = ScratchVar(TealType.uint64, 10) - otherVar = ScratchVar(TealType.uint64, 10) - prog = Seq([myvar.store(Int(5)), otherVar.store(Int(0)), Approve()]) - with pytest.raises(TealInternalError): - compileTeal(prog, mode=Mode.Signature, version=4) - - -def test_assembleConstants(): - program = Itob(Int(1) + Int(1) + Tmpl.Int("TMPL_VAR")) == Concat( - Bytes("test"), Bytes("test"), Bytes("test2") - ) - - expectedNoAssemble = """ -#pragma version 3 -int 1 -int 1 -+ -int TMPL_VAR -+ -itob -byte "test" -byte "test" -concat -byte "test2" -concat -== -return -""".strip() - actualNoAssemble = compileTeal( - program, Mode.Application, version=3, assembleConstants=False - ) - assert expectedNoAssemble == actualNoAssemble - - expectedAssemble = """ -#pragma version 3 -intcblock 1 -bytecblock 0x74657374 -intc_0 // 1 -intc_0 // 1 -+ -pushint TMPL_VAR // TMPL_VAR -+ -itob -bytec_0 // "test" -bytec_0 // "test" -concat -pushbytes 0x7465737432 // "test2" -concat -== -return -""".strip() - actualAssemble = compileTeal( - program, Mode.Application, version=3, assembleConstants=True - ) - assert expectedAssemble == actualAssemble - - with pytest.raises(TealInternalError): - compileTeal(program, Mode.Application, version=2, assembleConstants=True) - - -def test_compile_while(): - i = ScratchVar() - program = Seq( - [ - i.store(Int(0)), - While(i.load() < Int(2)).Do(Seq([i.store(i.load() + Int(1))])), - Approve(), - ] - ) - - expected = """ - #pragma version 4 -int 0 -store 0 -main_l1: -load 0 -int 2 -< -bz main_l3 -load 0 -int 1 -+ -store 0 -b main_l1 -main_l3: -int 1 -return - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert expected == actual - - # nested - i = ScratchVar() - j = ScratchVar() - - program = Seq( - [ - i.store(Int(0)), - While(i.load() < Int(2)).Do( - Seq( - [ - j.store(Int(0)), - While(j.load() < Int(5)).Do(Seq([j.store(j.load() + Int(1))])), - i.store(i.load() + Int(1)), - ] - ) - ), - Approve(), - ] - ) - - expected = """#pragma version 4 -int 0 -store 0 -main_l1: -load 0 -int 2 -< -bz main_l6 -int 0 -store 1 -main_l3: -load 1 -int 5 -< -bnz main_l5 -load 0 -int 1 -+ -store 0 -b main_l1 -main_l5: -load 1 -int 1 -+ -store 1 -b main_l3 -main_l6: -int 1 -return - """.strip() - - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert expected == actual - - -def test_compile_for(): - i = ScratchVar() - program = Seq( - [ - For(i.store(Int(0)), i.load() < Int(10), i.store(i.load() + Int(1))).Do( - Seq([App.globalPut(Itob(i.load()), i.load() * Int(2))]) - ), - Approve(), - ] - ) - - expected = """#pragma version 4 -int 0 -store 0 -main_l1: -load 0 -int 10 -< -bz main_l3 -load 0 -itob -load 0 -int 2 -* -app_global_put -load 0 -int 1 -+ -store 0 -b main_l1 -main_l3: -int 1 -return - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert expected == actual - - # nested - i = ScratchVar() - j = ScratchVar() - program = Seq( - [ - For(i.store(Int(0)), i.load() < Int(10), i.store(i.load() + Int(1))).Do( - Seq( - [ - For( - j.store(Int(0)), - j.load() < Int(4), - j.store(j.load() + Int(2)), - ).Do(Seq([App.globalPut(Itob(j.load()), j.load() * Int(2))])) - ] - ) - ), - Approve(), - ] - ) - - expected = """#pragma version 4 -int 0 -store 0 -main_l1: -load 0 -int 10 -< -bz main_l6 -int 0 -store 1 -main_l3: -load 1 -int 4 -< -bnz main_l5 -load 0 -int 1 -+ -store 0 -b main_l1 -main_l5: -load 1 -itob -load 1 -int 2 -* -app_global_put -load 1 -int 2 -+ -store 1 -b main_l3 -main_l6: -int 1 -return - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert expected == actual - - -def test_compile_break(): - - # While - i = ScratchVar() - program = Seq( - [ - i.store(Int(0)), - While(i.load() < Int(3)).Do( - Seq([If(i.load() == Int(2), Break()), i.store(i.load() + Int(1))]) - ), - Approve(), - ] - ) - - expected = """#pragma version 4 -int 0 -store 0 -main_l1: -load 0 -int 3 -< -bz main_l4 -load 0 -int 2 -== -bnz main_l4 -load 0 -int 1 -+ -store 0 -b main_l1 -main_l4: -int 1 -return - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert expected == actual - - # For - i = ScratchVar() - program = Seq( - [ - For(i.store(Int(0)), i.load() < Int(10), i.store(i.load() + Int(1))).Do( - Seq( - [ - If(i.load() == Int(4), Break()), - App.globalPut(Itob(i.load()), i.load() * Int(2)), - ] - ) - ), - Approve(), - ] - ) - - expected = """#pragma version 4 -int 0 -store 0 -main_l1: -load 0 -int 10 -< -bz main_l4 -load 0 -int 4 -== -bnz main_l4 -load 0 -itob -load 0 -int 2 -* -app_global_put -load 0 -int 1 -+ -store 0 -b main_l1 -main_l4: -int 1 -return - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert expected == actual - - -def test_compile_continue(): - # While - i = ScratchVar() - program = Seq( - [ - i.store(Int(0)), - While(i.load() < Int(3)).Do( - Seq([If(i.load() == Int(2), Continue()), i.store(i.load() + Int(1))]) - ), - Approve(), - ] - ) - - expected = """#pragma version 4 -int 0 -store 0 -main_l1: -load 0 -int 3 -< -bz main_l4 -main_l2: -load 0 -int 2 -== -bnz main_l2 -load 0 -int 1 -+ -store 0 -b main_l1 -main_l4: -int 1 -return - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert expected == actual - - # For - i = ScratchVar() - program = Seq( - [ - For(i.store(Int(0)), i.load() < Int(10), i.store(i.load() + Int(1))).Do( - Seq( - [ - If(i.load() == Int(4), Continue()), - App.globalPut(Itob(i.load()), i.load() * Int(2)), - ] - ) - ), - Approve(), - ] - ) - - expected = """#pragma version 4 -int 0 -store 0 -main_l1: -load 0 -int 10 -< -bz main_l5 -load 0 -int 4 -== -bnz main_l4 -load 0 -itob -load 0 -int 2 -* -app_global_put -main_l4: -load 0 -int 1 -+ -store 0 -b main_l1 -main_l5: -int 1 -return - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert expected == actual - - -def test_compile_continue_break_nested(): - - i = ScratchVar() - program = Seq( - [ - i.store(Int(0)), - While(i.load() < Int(10)).Do( - Seq( - [ - i.store(i.load() + Int(1)), - If(i.load() < Int(4), Continue(), Break()), - ] - ) - ), - Approve(), - ] - ) - - expected = """#pragma version 4 -int 0 -store 0 -load 0 -int 10 -< -bz main_l2 -main_l1: -load 0 -int 1 -+ -store 0 -load 0 -int 4 -< -bnz main_l1 -main_l2: -int 1 -return - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert expected == actual - - i = ScratchVar() - program = Seq( - [ - i.store(Int(0)), - While(i.load() < Int(10)).Do( - Seq( - [ - If(i.load() == Int(8), Break()), - While(i.load() < Int(6)).Do( - Seq( - [ - If(i.load() == Int(3), Break()), - i.store(i.load() + Int(1)), - ] - ) - ), - If(i.load() < Int(5), Continue()), - i.store(i.load() + Int(1)), - ] - ) - ), - Approve(), - ] - ) - - expected = """#pragma version 4 -int 0 -store 0 -main_l1: -load 0 -int 10 -< -bz main_l8 -main_l2: -load 0 -int 8 -== -bnz main_l8 -main_l3: -load 0 -int 6 -< -bnz main_l6 -main_l4: -load 0 -int 5 -< -bnz main_l2 -load 0 -int 1 -+ -store 0 -b main_l1 -main_l6: -load 0 -int 3 -== -bnz main_l4 -load 0 -int 1 -+ -store 0 -b main_l3 -main_l8: -int 1 -return -""".strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert expected == actual - - -def test_compile_subroutine_unsupported(): - @Subroutine(TealType.none) - def storeValue(value: Expr) -> Expr: - return App.globalPut(Bytes("key"), value) - - program = Seq( - [ - If(Txn.sender() == Global.creator_address()).Then( - storeValue(Txn.application_args[0]) - ), - Approve(), - ] - ) - - with pytest.raises(TealInputError): - compileTeal(program, Mode.Application, version=3) - - -def test_compile_subroutine_no_return(): - @Subroutine(TealType.none) - def storeValue(value: Expr) -> Expr: - return App.globalPut(Bytes("key"), value) - - program = Seq( - [ - If(Txn.sender() == Global.creator_address()).Then( - storeValue(Txn.application_args[0]) - ), - Approve(), - ] - ) - - expected = """#pragma version 4 -txn Sender -global CreatorAddress -== -bz main_l2 -txna ApplicationArgs 0 -callsub storeValue_0 -main_l2: -int 1 -return - -// storeValue -storeValue_0: -store 0 -byte "key" -load 0 -app_global_put -retsub - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert actual == expected - - -def test_compile_subroutine_with_return(): - @Subroutine(TealType.none) - def storeValue(value: Expr) -> Expr: - return App.globalPut(Bytes("key"), value) - - @Subroutine(TealType.bytes) - def getValue() -> Expr: - return App.globalGet(Bytes("key")) - - program = Seq( - [ - If(Txn.sender() == Global.creator_address()).Then( - storeValue(Txn.application_args[0]) - ), - If(getValue() == Bytes("fail")).Then(Reject()), - Approve(), - ] - ) - - expected = """#pragma version 4 -txn Sender -global CreatorAddress -== -bnz main_l3 -main_l1: -callsub getValue_1 -byte "fail" -== -bz main_l4 -int 0 -return -main_l3: -txna ApplicationArgs 0 -callsub storeValue_0 -b main_l1 -main_l4: -int 1 -return - -// storeValue -storeValue_0: -store 0 -byte "key" -load 0 -app_global_put -retsub - -// getValue -getValue_1: -byte "key" -app_global_get -retsub - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert actual == expected - - -def test_compile_subroutine_many_args(): - @Subroutine(TealType.uint64) - def calculateSum( - a1: Expr, a2: Expr, a3: Expr, a4: Expr, a5: Expr, a6: Expr - ) -> Expr: - return a1 + a2 + a3 + a4 + a5 + a6 - - program = Return( - calculateSum(Int(1), Int(2), Int(3), Int(4), Int(5), Int(6)) - == Int(1 + 2 + 3 + 4 + 5 + 6) - ) - - expected = """#pragma version 4 -int 1 -int 2 -int 3 -int 4 -int 5 -int 6 -callsub calculateSum_0 -int 21 -== -return - -// calculateSum -calculateSum_0: -store 5 -store 4 -store 3 -store 2 -store 1 -store 0 -load 0 -load 1 -+ -load 2 -+ -load 3 -+ -load 4 -+ -load 5 -+ -retsub - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert actual == expected - - -def test_compile_subroutine_recursive(): - @Subroutine(TealType.uint64) - def isEven(i: Expr) -> Expr: - return ( - If(i == Int(0)) - .Then(Int(1)) - .ElseIf(i == Int(1)) - .Then(Int(0)) - .Else(isEven(i - Int(2))) - ) - - program = Return(isEven(Int(6))) - - expected = """#pragma version 4 -int 6 -callsub isEven_0 -return - -// isEven -isEven_0: -store 0 -load 0 -int 0 -== -bnz isEven_0_l4 -load 0 -int 1 -== -bnz isEven_0_l3 -load 0 -int 2 -- -load 0 -dig 1 -callsub isEven_0 -swap -store 0 -swap -pop -b isEven_0_l5 -isEven_0_l3: -int 0 -b isEven_0_l5 -isEven_0_l4: -int 1 -isEven_0_l5: -retsub - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert actual == expected - - -def test_compile_subroutine_recursive_5(): - @Subroutine(TealType.uint64) - def isEven(i: Expr) -> Expr: - return ( - If(i == Int(0)) - .Then(Int(1)) - .ElseIf(i == Int(1)) - .Then(Int(0)) - .Else(isEven(i - Int(2))) - ) - - program = Return(isEven(Int(6))) - - expected = """#pragma version 5 -int 6 -callsub isEven_0 -return - -// isEven -isEven_0: -store 0 -load 0 -int 0 -== -bnz isEven_0_l4 -load 0 -int 1 -== -bnz isEven_0_l3 -load 0 -int 2 -- -load 0 -swap -callsub isEven_0 -swap -store 0 -b isEven_0_l5 -isEven_0_l3: -int 0 -b isEven_0_l5 -isEven_0_l4: -int 1 -isEven_0_l5: -retsub - """.strip() - actual = compileTeal(program, Mode.Application, version=5, assembleConstants=False) - assert actual == expected - - -def test_compile_subroutine_recursive_multiple_args(): - @Subroutine(TealType.uint64) - def multiplyByAdding(a, b): - return ( - If(a == Int(0)) - .Then(Return(Int(0))) - .Else(Return(b + multiplyByAdding(a - Int(1), b))) - ) - - program = Return(multiplyByAdding(Int(3), Int(5))) - - expected = """#pragma version 4 -int 3 -int 5 -callsub multiplyByAdding_0 -return - -// multiplyByAdding -multiplyByAdding_0: -store 1 -store 0 -load 0 -int 0 -== -bnz multiplyByAdding_0_l2 -load 1 -load 0 -int 1 -- -load 1 -load 0 -load 1 -dig 3 -dig 3 -callsub multiplyByAdding_0 -store 0 -store 1 -load 0 -swap -store 0 -swap -pop -swap -pop -+ -retsub -multiplyByAdding_0_l2: -int 0 -retsub - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert actual == expected - - -def test_compile_subroutine_recursive_multiple_args_5(): - @Subroutine(TealType.uint64) - def multiplyByAdding(a, b): - return ( - If(a == Int(0)) - .Then(Return(Int(0))) - .Else(Return(b + multiplyByAdding(a - Int(1), b))) - ) - - program = Return(multiplyByAdding(Int(3), Int(5))) - - expected = """#pragma version 5 -int 3 -int 5 -callsub multiplyByAdding_0 -return - -// multiplyByAdding -multiplyByAdding_0: -store 1 -store 0 -load 0 -int 0 -== -bnz multiplyByAdding_0_l2 -load 1 -load 0 -int 1 -- -load 1 -load 0 -load 1 -uncover 3 -uncover 3 -callsub multiplyByAdding_0 -cover 2 -store 1 -store 0 -+ -retsub -multiplyByAdding_0_l2: -int 0 -retsub - """.strip() - actual = compileTeal(program, Mode.Application, version=5, assembleConstants=False) - assert actual == expected - - -def test_compile_subroutine_mutually_recursive(): - @Subroutine(TealType.uint64) - def isEven(i: Expr) -> Expr: - return If(i == Int(0), Int(1), Not(isOdd(i - Int(1)))) - - @Subroutine(TealType.uint64) - def isOdd(i: Expr) -> Expr: - return If(i == Int(0), Int(0), Not(isEven(i - Int(1)))) - - program = Return(isEven(Int(6))) - - expected = """#pragma version 4 -int 6 -callsub isEven_0 -return - -// isEven -isEven_0: -store 0 -load 0 -int 0 -== -bnz isEven_0_l2 -load 0 -int 1 -- -load 0 -dig 1 -callsub isOdd_1 -swap -store 0 -swap -pop -! -b isEven_0_l3 -isEven_0_l2: -int 1 -isEven_0_l3: -retsub - -// isOdd -isOdd_1: -store 1 -load 1 -int 0 -== -bnz isOdd_1_l2 -load 1 -int 1 -- -load 1 -dig 1 -callsub isEven_0 -swap -store 1 -swap -pop -! -b isOdd_1_l3 -isOdd_1_l2: -int 0 -isOdd_1_l3: -retsub - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert actual == expected - - -def test_compile_subroutine_mutually_recursive_5(): - @Subroutine(TealType.uint64) - def isEven(i: Expr) -> Expr: - return If(i == Int(0), Int(1), Not(isOdd(i - Int(1)))) - - @Subroutine(TealType.uint64) - def isOdd(i: Expr) -> Expr: - return If(i == Int(0), Int(0), Not(isEven(i - Int(1)))) - - program = Return(isEven(Int(6))) - - expected = """#pragma version 5 -int 6 -callsub isEven_0 -return - -// isEven -isEven_0: -store 0 -load 0 -int 0 -== -bnz isEven_0_l2 -load 0 -int 1 -- -load 0 -swap -callsub isOdd_1 -swap -store 0 -! -b isEven_0_l3 -isEven_0_l2: -int 1 -isEven_0_l3: -retsub - -// isOdd -isOdd_1: -store 1 -load 1 -int 0 -== -bnz isOdd_1_l2 -load 1 -int 1 -- -load 1 -swap -callsub isEven_0 -swap -store 1 -! -b isOdd_1_l3 -isOdd_1_l2: -int 0 -isOdd_1_l3: -retsub - """.strip() - actual = compileTeal(program, Mode.Application, version=5, assembleConstants=False) - assert actual == expected - - -def test_compile_loop_in_subroutine(): - @Subroutine(TealType.none) - def setState(value: Expr) -> Expr: - i = ScratchVar() - return For(i.store(Int(0)), i.load() < Int(10), i.store(i.load() + Int(1))).Do( - App.globalPut(Itob(i.load()), value) - ) - - program = Seq([setState(Bytes("value")), Approve()]) - - expected = """#pragma version 4 -byte "value" -callsub setState_0 -int 1 -return - -// setState -setState_0: -store 0 -int 0 -store 1 -setState_0_l1: -load 1 -int 10 -< -bz setState_0_l3 -load 1 -itob -load 0 -app_global_put -load 1 -int 1 -+ -store 1 -b setState_0_l1 -setState_0_l3: -retsub - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert actual == expected - - -def test_compile_subroutine_invalid_name(): - def tmp() -> Expr: - return Int(1) - - tmp.__name__ = "invalid-;)" - - program = Subroutine(TealType.uint64)(tmp)() - expected = """#pragma version 4 -callsub invalid_0 -return - -// invalid-;) -invalid_0: -int 1 -retsub - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=False) - assert actual == expected - - -def test_compile_subroutine_assemble_constants(): - @Subroutine(TealType.none) - def storeValue(key: Expr, t1: Expr, t2: Expr, t3: Expr) -> Expr: - return App.globalPut(key, t1 + t2 + t3 + Int(10)) - - program = Seq( - [ - If(Txn.application_id() == Int(0)).Then( - storeValue( - Concat(Bytes("test"), Bytes("test"), Bytes("a")), - Int(1), - Int(1), - Int(3), - ) - ), - Approve(), - ] - ) - - expected = """#pragma version 4 -intcblock 1 -bytecblock 0x74657374 -txn ApplicationID -pushint 0 // 0 -== -bz main_l2 -bytec_0 // "test" -bytec_0 // "test" -concat -pushbytes 0x61 // "a" -concat -intc_0 // 1 -intc_0 // 1 -pushint 3 // 3 -callsub storeValue_0 -main_l2: -intc_0 // 1 -return - -// storeValue -storeValue_0: -store 3 -store 2 -store 1 -store 0 -load 0 -load 1 -load 2 -+ -load 3 -+ -pushint 10 // 10 -+ -app_global_put -retsub - """.strip() - actual = compileTeal(program, Mode.Application, version=4, assembleConstants=True) - assert actual == expected - - -def test_compile_wide_ratio(): - cases = ( - ( - WideRatio([Int(2), Int(100)], [Int(5)]), - """#pragma version 5 -int 2 -int 100 -mulw -int 0 -int 5 -divmodw -pop -pop -swap -! -assert -return -""", - ), - ( - WideRatio([Int(2), Int(100)], [Int(10), Int(5)]), - """#pragma version 5 -int 2 -int 100 -mulw -int 10 -int 5 -mulw -divmodw -pop -pop -swap -! -assert -return -""", - ), - ( - WideRatio([Int(2), Int(100), Int(3)], [Int(10), Int(5)]), - """#pragma version 5 -int 2 -int 100 -mulw -int 3 -uncover 2 -dig 1 -* -cover 2 -mulw -cover 2 -+ -swap -int 10 -int 5 -mulw -divmodw -pop -pop -swap -! -assert -return -""", - ), - ( - WideRatio([Int(2), Int(100), Int(3)], [Int(10), Int(5), Int(6)]), - """#pragma version 5 -int 2 -int 100 -mulw -int 3 -uncover 2 -dig 1 -* -cover 2 -mulw -cover 2 -+ -swap -int 10 -int 5 -mulw -int 6 -uncover 2 -dig 1 -* -cover 2 -mulw -cover 2 -+ -swap -divmodw -pop -pop -swap -! -assert -return -""", - ), - ( - WideRatio([Int(2), Int(100), Int(3), Int(4)], [Int(10), Int(5), Int(6)]), - """#pragma version 5 -int 2 -int 100 -mulw -int 3 -uncover 2 -dig 1 -* -cover 2 -mulw -cover 2 -+ -swap -int 4 -uncover 2 -dig 1 -* -cover 2 -mulw -cover 2 -+ -swap -int 10 -int 5 -mulw -int 6 -uncover 2 -dig 1 -* -cover 2 -mulw -cover 2 -+ -swap -divmodw -pop -pop -swap -! -assert -return -""", - ), - ) - - for program, expected in cases: - actual = compileTeal( - program, Mode.Application, version=5, assembleConstants=False - ) - assert actual == expected.strip() - - -import inspect -import dis - - -def z1(foo): - x = { - "bytecode": dis.Bytecode(foo), - "code": inspect.getsource(foo), - "file": inspect.getsourcefile(foo), - "lines": inspect.getsourcelines(foo), - "mod": inspect.getmodule(foo), - "members": inspect.getmembers(foo), - "ismod": inspect.ismodule(foo), - "iscls": inspect.isclass(foo), - "ismeth": inspect.ismethod(foo), - "isfunc": inspect.isfunction(foo), - "isgenfunc": inspect.isgeneratorfunction(foo), - "isgen": inspect.isgenerator(foo), - "iscofunc": inspect.iscoroutinefunction(foo), - "isawait": inspect.isawaitable(foo), - "isasyncgenfunc": inspect.isasyncgenfunction(foo), - "isasyncgen": inspect.isasyncgen(foo), - "iscode": inspect.iscode(foo), - "isbuiltin": inspect.isbuiltin(foo), - "isroutine": inspect.isroutine(foo), - "isabstract": inspect.isabstract(foo), - } - lines = x["lines"] - x["line-map"] = list(zip(range(lines[1], lines[1] + len(lines[0])), lines[0])) - x["instructions"] = [ - instr.opname for instr in reversed([instr for instr in x["bytecode"]]) - ] - - return x - - -def test_z1(): - def f(x): - return x + 1339 - - def foo(): - y = f(42) - - # mom and dad - - # are very important - - return y - - foo_info = z1(foo) - - x = 42 - - -def test_zeph(): - @Subroutine(TealType.none) - def storeValue(key: Expr, t1: Expr, t2: Expr, t3: Expr) -> Expr: - return App.globalPut(key, t1 + t2 + t3 + Int(10)) - - program = Seq( - [ - If(Txn.application_id() == Int(0)).Then( - storeValue( - Concat(Bytes("test"), Bytes("test"), Bytes("a")), - Int(1), - Int(1), - Int(3), - ) - ), - Approve(), - ] - ) - - expected = """#pragma version 4 -intcblock 1 -bytecblock 0x74657374 -txn ApplicationID -pushint 0 // 0 -== -bz main_l2 -bytec_0 // "test" -bytec_0 // "test" -concat -pushbytes 0x61 // "a" -concat -intc_0 // 1 -intc_0 // 1 -pushint 3 // 3 -callsub storeValue_0 -main_l2: -intc_0 // 1 -return - -// storeValue -storeValue_0: -store 3 -store 2 -store 1 -store 0 -load 0 -load 1 -load 2 -+ -load 3 -+ -pushint 10 // 10 -+ -app_global_put -retsub - """.strip() - actual = compileTeal( - program, Mode.Application, version=4, assembleConstants=True, sourceMap=True - ) - assert actual == expected From d982a3f4e37975df483c6838762a945dedb54571 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 1 Mar 2022 00:44:28 -0600 Subject: [PATCH 82/85] space --- pyteal/ast/scratchvar.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyteal/ast/scratchvar.py b/pyteal/ast/scratchvar.py index b71c0048f..f194e462b 100644 --- a/pyteal/ast/scratchvar.py +++ b/pyteal/ast/scratchvar.py @@ -29,7 +29,6 @@ def __init__(self, type: TealType = TealType.anytype, slotId: int = None): slotId (optional): A scratch slot id that the compiler must store the value. This id may be a Python int in the range [0-256). """ - self.slot = ScratchSlot(requestedSlotId=slotId) self.type = type From 8680b489e50a8ef625847818de4f80260506083e Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 1 Mar 2022 09:23:04 -0600 Subject: [PATCH 83/85] Update pyteal/ast/subroutine_test.py Co-authored-by: Michael Diamant --- pyteal/ast/subroutine_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index 66459b3b3..e7f93d901 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -106,7 +106,7 @@ def fnWithMixedAnns3(a: Expr, b: ScratchVar): ("vanilla no sv's allowed 1", fnWithNoAnnotations, [x, sv], TealInputError), ("exprs 1", fnWithExprAnnotations, [x, s], None), ("exprs 2", fnWithExprAnnotations, [x, x], None), - ("exprs no sv's asllowed 1", fnWithExprAnnotations, [x, sv], TealInputError), + ("exprs no sv's allowed 1", fnWithExprAnnotations, [x, sv], TealInputError), ("all sv's 1", fnWithSVAnnotations, [sv, sv], None), ("all sv's but strings", fnWithSVAnnotations, [s, s], TealInputError), ("all sv's but ints", fnWithSVAnnotations, [x, x], TealInputError), From cb343551c855521bff4b05b35e22a797c590ee41 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 1 Mar 2022 12:27:53 -0600 Subject: [PATCH 84/85] per CR comment: abort when dupe imports dectected --- scripts/generate_init.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/scripts/generate_init.py b/scripts/generate_init.py index a84426710..e9a1c85e6 100644 --- a/scripts/generate_init.py +++ b/scripts/generate_init.py @@ -1,4 +1,6 @@ import argparse, os, sys, difflib +from collections import Counter + from pyteal import __all__ as static_all @@ -37,7 +39,14 @@ def generate_init_pyi() -> str: start_idx = init_contents.index(begin_flag) end_idx = init_contents.index(end_flag) - all_imports = ",\n ".join(['"{}"'.format(s) for s in sorted(set(static_all))]) + counts = Counter(static_all) + dupes = [x for x, n in counts.items() if n > 1] + BR = "\n" + assert ( + not dupes + ), f"Aborting pyi file generation. The following duplicate imports were detected:{BR}{BR.join(dupes)}" + + all_imports = ",\n ".join(['"{}"'.format(s) for s in sorted(static_all)]) return ( pyi_template From 174973811cff5db5f9b7e5c1d412dbb0d68d0216 Mon Sep 17 00:00:00 2001 From: Michael Diamant Date: Tue, 1 Mar 2022 16:42:11 -0500 Subject: [PATCH 85/85] Refactor SubroutineDefinition parameter validation into method with explanatory comment (#217) --- pyteal/ast/subroutine.py | 53 +++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 99b747b1c..35210e54b 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -63,26 +63,7 @@ def __init__( ) ) - expected_arg_type: type = Expr - if name in annotations: - ptype = annotations[name] - if not isclass(ptype): - raise TealInputError( - "Function has parameter {} of declared type {} which is not a class".format( - name, ptype - ) - ) - - if ptype not in (Expr, ScratchVar): - raise TealInputError( - "Function has parameter {} of disallowed type {}. Only the types {} are allowed".format( - name, ptype, (Expr, ScratchVar) - ) - ) - - expected_arg_type = ptype - else: - expected_arg_type = Expr + expected_arg_type = self._validate_parameter_type(annotations, name) self.expected_arg_types.append(expected_arg_type) if expected_arg_type is ScratchVar: @@ -95,6 +76,38 @@ def __init__( self.declaration: Optional["SubroutineDeclaration"] = None self.__name = self.implementation.__name__ if nameStr is None else nameStr + @staticmethod + def _validate_parameter_type( + user_defined_annotations: dict, parameter_name: str + ) -> Type[Union[Expr, ScratchVar]]: + ptype = user_defined_annotations.get(parameter_name, None) + if ptype is None: + # Without a type annotation, `SubroutineDefinition` presumes an implicit `Expr` declaration rather than these alternatives: + # * Throw error requiring type annotation. + # * Defer parameter type checks until arguments provided during invocation. + # + # * Rationale: + # * Provide an upfront, best-effort type check before invocation. + # * Preserve backwards compatibility with TEAL programs written when `Expr` is the only supported annotation type. + # * `invoke` type checks provided arguments against parameter types to catch mismatches. + return Expr + else: + if not isclass(ptype): + raise TealInputError( + "Function has parameter {} of declared type {} which is not a class".format( + parameter_name, ptype + ) + ) + + if ptype not in (Expr, ScratchVar): + raise TealInputError( + "Function has parameter {} of disallowed type {}. Only the types {} are allowed".format( + parameter_name, ptype, (Expr, ScratchVar) + ) + ) + + return ptype + def getDeclaration(self) -> "SubroutineDeclaration": if self.declaration is None: # lazy evaluate subroutine