From d0f9d8d68a32ae28d6d09d91a92ca61013b6ab11 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Fri, 24 Jun 2022 10:05:29 -0400 Subject: [PATCH 01/19] upping max teal version --- pyteal/compiler/compiler.py | 2 +- pyteal/compiler/compiler_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyteal/compiler/compiler.py b/pyteal/compiler/compiler.py index 5a6709efe..2a68be57a 100644 --- a/pyteal/compiler/compiler.py +++ b/pyteal/compiler/compiler.py @@ -25,7 +25,7 @@ ) from pyteal.compiler.constants import createConstantBlocks -MAX_TEAL_VERSION = 6 +MAX_TEAL_VERSION = 7 MIN_TEAL_VERSION = 2 DEFAULT_TEAL_VERSION = MIN_TEAL_VERSION diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index 0d6fdbe09..b3e528506 100644 --- a/pyteal/compiler/compiler_test.py +++ b/pyteal/compiler/compiler_test.py @@ -139,7 +139,7 @@ def test_compile_version_invalid(): pt.compileTeal(expr, pt.Mode.Signature, version=1) # too small with pytest.raises(pt.TealInputError): - pt.compileTeal(expr, pt.Mode.Signature, version=7) # too large + pt.compileTeal(expr, pt.Mode.Signature, version=8) # too large with pytest.raises(pt.TealInputError): pt.compileTeal(expr, pt.Mode.Signature, version=2.0) # decimal From 749fb8c8e4cd956d5cd855f2288f5ba1c09eb160 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Thu, 30 Jun 2022 11:02:28 -0400 Subject: [PATCH 02/19] adding program page related ops (#412) * adding program page related ops --- docs/accessing_transaction_field.rst | 44 ++++++++++++---------- pyteal/ast/txn.py | 56 ++++++++++++++++++++++++++++ pyteal/ast/txn_test.py | 6 +++ 3 files changed, 86 insertions(+), 20 deletions(-) diff --git a/docs/accessing_transaction_field.rst b/docs/accessing_transaction_field.rst index adb8125e8..ea37663e5 100644 --- a/docs/accessing_transaction_field.rst +++ b/docs/accessing_transaction_field.rst @@ -37,26 +37,30 @@ Operator Application Call ....................................................... -================================================================================ ========================= ================ ============================================================================ -Operator Type Min TEAL Version Notes -================================================================================ ========================= ================ ============================================================================ -:any:`Txn.application_id() ` :code:`TealType.uint64` 2 -:any:`Txn.on_completion() ` :code:`TealType.uint64` 2 -:any:`Txn.approval_program() ` :code:`TealType.bytes` 2 -:any:`Txn.global_num_uints() ` :code:`TealType.uint64` 3 Maximum global integers in app schema -:any:`Txn.global_num_byte_slices() ` :code:`TealType.uint64` 3 Maximum global byte strings in app schema -:any:`Txn.local_num_uints() ` :code:`TealType.uint64` 3 Maximum local integers in app schema -:any:`Txn.local_num_byte_slices() ` :code:`TealType.uint64` 3 Maximum local byte strings in app schema -:any:`Txn.accounts ` :code:`TealType.bytes[]` 2 Array of accounts available to the application -:any:`Txn.assets ` :code:`TealType.uint64[]` 3 Array of assets available to the application -:any:`Txn.applications ` :code:`TealType.uint64[]` 3 Array of applications -:any:`Txn.clear_state_program() ` :code:`TealType.bytes` 2 -:any:`Txn.extra_program_pages() ` :code:`TealType.uint64` 4 Number of extra program pages for app -:any:`Txn.application_args ` :code:`TealType.bytes[]` 2 Array of application arguments -:any:`Txn.created_application_id() ` :code:`TealType.uint64` 5 The ID of the newly created application in this transaction. In v5, only valid on inner transactions. >= v6 works with top-level and inner transactions. -:any:`Txn.logs ` :code:`TealType.bytes[]` 5 Array of application logged items. In v5, only valid on inner transactions. >= v6 works with top-level and inner transactions. -:any:`Txn.last_log() ` :code:`TealType.bytes[]` 6 The last message emitted. Empty bytes if none were emitted. Application mode only. -================================================================================ ========================= ================ ============================================================================ +==================================================================================== ========================= ================ ============================================================================ +Operator Type Min TEAL Version Notes +==================================================================================== ========================= ================ ============================================================================ +:any:`Txn.application_id() ` :code:`TealType.uint64` 2 +:any:`Txn.on_completion() ` :code:`TealType.uint64` 2 +:any:`Txn.approval_program() ` :code:`TealType.bytes` 2 +:any:`Txn.global_num_uints() ` :code:`TealType.uint64` 3 Maximum global integers in app schema +:any:`Txn.global_num_byte_slices() ` :code:`TealType.uint64` 3 Maximum global byte strings in app schema +:any:`Txn.local_num_uints() ` :code:`TealType.uint64` 3 Maximum local integers in app schema +:any:`Txn.local_num_byte_slices() ` :code:`TealType.uint64` 3 Maximum local byte strings in app schema +:any:`Txn.accounts ` :code:`TealType.bytes[]` 2 Array of accounts available to the application +:any:`Txn.assets ` :code:`TealType.uint64[]` 3 Array of assets available to the application +:any:`Txn.applications ` :code:`TealType.uint64[]` 3 Array of applications +:any:`Txn.clear_state_program() ` :code:`TealType.bytes` 2 +:any:`Txn.extra_program_pages() ` :code:`TealType.uint64` 4 Number of extra program pages for app +:any:`Txn.application_args ` :code:`TealType.bytes[]` 2 Array of application arguments +:any:`Txn.created_application_id() ` :code:`TealType.uint64` 5 The ID of the newly created application in this transaction. In v5, only valid on inner transactions. >= v6 works with top-level and inner transactions. +:any:`Txn.logs ` :code:`TealType.bytes[]` 5 Array of application logged items. In v5, only valid on inner transactions. >= v6 works with top-level and inner transactions. +:any:`Txn.last_log() ` :code:`TealType.bytes[]` 6 The last message emitted. Empty bytes if none were emitted. Application mode only. +:any:`Txn.approval_program_pages() ` :code:`TealType.bytes[]` 7 The pages of the approval program as an array +:any:`Txn.num_approval_program_pages() ` :code:`TealType.uint64` 7 The number of approval program pages +:any:`Txn.clear_state_program_pages() ` :code:`TealType.bytes[]` 7 The pages of a clear state program as an array +:any:`Txn.num_clear_state_program_pages() ` :code:`TealType.uint64` 7 The number of clear state program pages +==================================================================================== ========================= ================ ============================================================================ Asset Config ....................................................... diff --git a/pyteal/ast/txn.py b/pyteal/ast/txn.py index e9182b74f..3c0fc0f4e 100644 --- a/pyteal/ast/txn.py +++ b/pyteal/ast/txn.py @@ -110,6 +110,22 @@ class TxnField(Enum): created_application_id = (61, "CreatedApplicationID", TealType.uint64, False, 5) last_log = (62, "LastLog", TealType.bytes, False, 6) state_proof_pk = (63, "StateProofPK", TealType.bytes, False, 6) + approval_program_pages = (64, "ApprovalProgramPages", TealType.bytes, True, 7) + num_approval_program_pages = ( + 65, + "NumApprovalProgramPages", + TealType.uint64, + False, + 7, + ) + clear_state_program_pages = (66, "ClearStateProgramPages", TealType.bytes, True, 7) + num_clear_state_program_pages = ( + 67, + "NumClearStateProgramPages", + TealType.uint64, + False, + 7, + ) def __init__( self, id: int, name: str, type: TealType, is_array: bool, min_version: int @@ -728,6 +744,20 @@ def state_proof_pk(self) -> TxnExpr: """ return self.makeTxnExpr(TxnField.state_proof_pk) + def num_approval_program_pages(self) -> TxnExpr: + """Get the number of pages in the approval program. + + Requires TEAL version 7 or higher. + """ + return self.makeTxnExpr(TxnField.num_approval_program_pages) + + def num_clear_state_program_pages(self) -> TxnExpr: + """Get the number of pages in the clear state program. + + Requires TEAL version 7 or higher. + """ + return self.makeTxnExpr(TxnField.num_clear_state_program_pages) + @property def application_args(self) -> TxnArray: """Application call arguments array. @@ -777,6 +807,32 @@ def logs(self) -> TxnArray: """ return TxnArray(self, TxnField.logs, TxnField.num_logs) + @property + def approval_program_pages(self) -> TxnArray: + """The approval program pages. + + :type: TxnArray + + Requires TEAL version 7 or higher. + """ + return TxnArray( + self, TxnField.approval_program_pages, TxnField.num_approval_program_pages + ) + + @property + def clear_state_program_pages(self) -> TxnArray: + """The clear state program pages. + + :type: TxnArray + + Requires TEAL version 7 or higher. + """ + return TxnArray( + self, + TxnField.clear_state_program_pages, + TxnField.num_clear_state_program_pages, + ) + TxnObject.__module__ = "pyteal" diff --git a/pyteal/ast/txn_test.py b/pyteal/ast/txn_test.py index eaaf01464..c0477fd23 100644 --- a/pyteal/ast/txn_test.py +++ b/pyteal/ast/txn_test.py @@ -58,6 +58,8 @@ pt.TxnField.created_application_id: lambda txn: txn.created_application_id(), pt.TxnField.last_log: lambda txn: txn.last_log(), pt.TxnField.state_proof_pk: lambda txn: txn.state_proof_pk(), + pt.TxnField.num_approval_program_pages: lambda txn: txn.num_approval_program_pages(), + pt.TxnField.num_clear_state_program_pages: lambda txn: txn.num_clear_state_program_pages(), } arrayFieldToProperty: Dict[pt.TxnField, Callable[[pt.TxnObject], pt.TxnArray]] = { @@ -66,6 +68,8 @@ pt.TxnField.assets: lambda txn: txn.assets, pt.TxnField.applications: lambda txn: txn.applications, pt.TxnField.logs: lambda txn: txn.logs, + pt.TxnField.approval_program_pages: lambda txn: txn.approval_program_pages, + pt.TxnField.clear_state_program_pages: lambda txn: txn.clear_state_program_pages, } arrayFieldToLengthField: Dict[pt.TxnField, pt.TxnField] = { @@ -74,6 +78,8 @@ pt.TxnField.assets: pt.TxnField.num_assets, pt.TxnField.applications: pt.TxnField.num_applications, pt.TxnField.logs: pt.TxnField.num_logs, + pt.TxnField.approval_program_pages: pt.TxnField.num_approval_program_pages, + pt.TxnField.clear_state_program_pages: pt.TxnField.num_clear_state_program_pages, } From 39a11c283eaa101d8188811ce22bc1e583baf151 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 30 Jun 2022 11:50:44 -0400 Subject: [PATCH 03/19] Add Replace (#413) * Add Replace * Remove replace auto-import * Use scripts/generate_init.py * Add more tests to replace, substring, and extract (#1) Co-authored-by: Michael Diamant --- pyteal/__init__.pyi | 1 + pyteal/ast/__init__.py | 2 + pyteal/ast/replace.py | 86 ++++++++++++++++++++++++++++++++ pyteal/ast/replace_test.py | 97 ++++++++++++++++++++++++++++++++++++ pyteal/ast/substring.py | 12 ++--- pyteal/ast/substring_test.py | 82 ++++++++++++++++++++++++++++++ pyteal/ir/ops.py | 2 + 7 files changed, 276 insertions(+), 6 deletions(-) create mode 100644 pyteal/ast/replace.py create mode 100644 pyteal/ast/replace_test.py diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index 4fa32d071..20bc15dd4 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -138,6 +138,7 @@ __all__ = [ "Or", "Pop", "Reject", + "Replace", "Return", "ScratchIndex", "ScratchLoad", diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index 613a39480..a72c064fc 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -102,6 +102,7 @@ # ternary ops from pyteal.ast.ternaryexpr import Divw, Ed25519Verify, SetBit, SetByte from pyteal.ast.substring import Substring, Extract, Suffix +from pyteal.ast.replace import Replace # more ops from pyteal.ast.naryexpr import NaryExpr, And, Or, Concat @@ -272,6 +273,7 @@ "ExtractUint16", "ExtractUint32", "ExtractUint64", + "Replace", "Log", "While", "For", diff --git a/pyteal/ast/replace.py b/pyteal/ast/replace.py new file mode 100644 index 000000000..2b7968978 --- /dev/null +++ b/pyteal/ast/replace.py @@ -0,0 +1,86 @@ +from typing import cast, TYPE_CHECKING + +from pyteal.types import TealType, require_type +from pyteal.errors import verifyTealVersion +from pyteal.ir import TealOp, Op, TealBlock +from pyteal.ast.expr import Expr +from pyteal.ast.int import Int +from pyteal.ast.ternaryexpr import TernaryExpr + +if TYPE_CHECKING: + from pyteal.compiler import CompileOptions + + +class ReplaceExpr(Expr): + """An expression for replacing a section of a byte string at a given start index""" + + def __init__(self, original: Expr, start: Expr, replacement: Expr) -> None: + super().__init__() + + require_type(original, TealType.bytes) + require_type(start, TealType.uint64) + require_type(replacement, TealType.bytes) + + self.original = original + self.start = start + self.replacement = replacement + + # helper method for correctly populating op + def __get_op(self, options: "CompileOptions"): + s = cast(Int, self.start).value + if s < 2**8: + return Op.replace2 + else: + return Op.replace3 + + def __teal__(self, options: "CompileOptions"): + if not isinstance(self.start, Int): + return TernaryExpr( + Op.replace3, + (TealType.bytes, TealType.uint64, TealType.bytes), + TealType.bytes, + self.original, + self.start, + self.replacement, + ).__teal__(options) + + op = self.__get_op(options) + + verifyTealVersion( + op.min_version, + options.version, + "TEAL version too low to use op {}".format(op), + ) + + s = cast(Int, self.start).value + if op == Op.replace2: + return TealBlock.FromOp( + options, TealOp(self, op, s), self.original, self.replacement + ) + elif op == Op.replace3: + return TealBlock.FromOp( + options, TealOp(self, op), self.original, self.start, self.replacement + ) + + def __str__(self): + return "(Replace {} {} {})".format(self.original, self.start, self.replacement) + + def type_of(self): + return TealType.bytes + + def has_return(self): + return False + + +def Replace(original: Expr, start: Expr, replacement: Expr) -> Expr: + """ + Replace a portion of original bytes with new bytes at a given starting point. + + Requires TEAL version 7 or higher. + + Args: + original: The value containing the original bytes. Must evaluate to bytes. + start: The index of the byte where replacement starts. Must evaluate to an integer less than Len(original). + replacement: The value containing the replacement bytes. Must evaluate to bytes with length at most Len(original) - start. + """ + return ReplaceExpr(original, start, replacement) diff --git a/pyteal/ast/replace_test.py b/pyteal/ast/replace_test.py new file mode 100644 index 000000000..b72a5d879 --- /dev/null +++ b/pyteal/ast/replace_test.py @@ -0,0 +1,97 @@ +import pytest + +import pyteal as pt + +teal6Options = pt.CompileOptions(version=6) +teal7Options = pt.CompileOptions(version=7) + + +def test_replace_immediate(): + args = [pt.Bytes("my string"), pt.Int(0), pt.Bytes("abcdefghi")] + expr = pt.Replace(args[0], args[1], args[2]) + assert expr.type_of() == pt.TealType.bytes + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.byte, '"my string"'), + pt.TealOp(args[2], pt.Op.byte, '"abcdefghi"'), + pt.TealOp(expr, pt.Op.replace2, 0), + ] + ) + + actual, _ = expr.__teal__(teal7Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + assert actual == expected + + with pytest.raises(pt.TealInputError): + expr.__teal__(teal6Options) + + +def test_replace_stack_int(): + my_string = "*" * 257 + args = [pt.Bytes(my_string), pt.Int(256), pt.Bytes("ab")] + expr = pt.Replace(args[0], args[1], args[2]) + assert expr.type_of() == pt.TealType.bytes + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.byte, '"{my_string}"'.format(my_string=my_string)), + pt.TealOp(args[1], pt.Op.int, 256), + pt.TealOp(args[2], pt.Op.byte, '"ab"'), + pt.TealOp(expr, pt.Op.replace3), + ] + ) + + actual, _ = expr.__teal__(teal7Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + assert actual == expected + + with pytest.raises(pt.TealInputError): + expr.__teal__(teal6Options) + + +# Mirrors `test_replace_stack_int`, but attempts replacement with start != pt.Int. +def test_replace_stack_not_int(): + my_string = "*" * 257 + add = pt.Add(pt.Int(254), pt.Int(2)) + args = [pt.Bytes(my_string), add, pt.Bytes("ab")] + expr = pt.Replace(args[0], args[1], args[2]) + assert expr.type_of() == pt.TealType.bytes + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.byte, '"{my_string}"'.format(my_string=my_string)), + pt.TealOp(pt.Int(254), pt.Op.int, 254), + pt.TealOp(pt.Int(2), pt.Op.int, 2), + pt.TealOp(add, pt.Op.add), + pt.TealOp(args[2], pt.Op.byte, '"ab"'), + pt.TealOp(expr, pt.Op.replace3), + ] + ) + + actual, _ = expr.__teal__(teal7Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + with pytest.raises(pt.TealInputError): + expr.__teal__(teal6Options) + + +def test_replace_invalid(): + with pytest.raises(pt.TealTypeError): + pt.Replace(pt.Bytes("my string"), pt.Int(0), pt.Int(1)) + + with pytest.raises(pt.TealTypeError): + pt.Replace( + pt.Bytes("my string"), pt.Bytes("should be int"), pt.Bytes("abcdefghi") + ) + + with pytest.raises(pt.TealTypeError): + pt.Replace(pt.Bytes("my string"), pt.Txn.sender(), pt.Bytes("abcdefghi")) diff --git a/pyteal/ast/substring.py b/pyteal/ast/substring.py index 438b40933..6febf69dc 100644 --- a/pyteal/ast/substring.py +++ b/pyteal/ast/substring.py @@ -26,7 +26,7 @@ def __init__(self, stringArg: Expr, startArg: Expr, endArg: Expr) -> None: self.endArg = endArg # helper method for correctly populating op - def __getOp(self, options: "CompileOptions"): + def __get_op(self, options: "CompileOptions"): s, e = cast(Int, self.startArg).value, cast(Int, self.endArg).value l = e - s @@ -58,7 +58,7 @@ def __teal__(self, options: "CompileOptions"): self.endArg, ).__teal__(options) - op = self.__getOp(options) + op = self.__get_op(options) verifyTealVersion( op.min_version, @@ -121,7 +121,7 @@ def __init__(self, stringArg: Expr, startArg: Expr, lenArg: Expr) -> None: self.lenArg = lenArg # helper method for correctly populating op - def __getOp(self, options: "CompileOptions"): + def __get_op(self, options: "CompileOptions"): s, l = cast(Int, self.startArg).value, cast(Int, self.lenArg).value if s < 2**8 and l > 0 and l < 2**8: return Op.extract @@ -139,7 +139,7 @@ def __teal__(self, options: "CompileOptions"): self.lenArg, ).__teal__(options) - op = self.__getOp(options) + op = self.__get_op(options) verifyTealVersion( op.min_version, @@ -186,7 +186,7 @@ def __init__( self.startArg = startArg # helper method for correctly populating op - def __getOp(self, options: "CompileOptions"): + def __get_op(self, options: "CompileOptions"): if not isinstance(self.startArg, Int): return Op.substring3 @@ -197,7 +197,7 @@ def __getOp(self, options: "CompileOptions"): return Op.substring3 def __teal__(self, options: "CompileOptions"): - op = self.__getOp(options) + op = self.__get_op(options) verifyTealVersion( op.min_version, diff --git a/pyteal/ast/substring_test.py b/pyteal/ast/substring_test.py index 2aafeabfd..ae5bcace2 100644 --- a/pyteal/ast/substring_test.py +++ b/pyteal/ast/substring_test.py @@ -272,6 +272,88 @@ def test_suffix_stack(): assert actual == expected +@pytest.mark.parametrize("op", [pt.Op.extract3, pt.Op.substring3]) +def test_startArg_not_int(op: pt.Op): + my_string = "*" * 257 + add = pt.Add(pt.Int(254), pt.Int(2)) + args = [pt.Bytes(my_string), add, pt.Int(257)] + + def generate_expr() -> pt.Expr: + match op: + case pt.Op.extract3: + return pt.Extract(args[0], args[1], args[2]) + case pt.Op.substring3: + return pt.Substring(args[0], args[1], args[2]) + case _: + raise Exception(f"Unsupported {op=}") + + expr = generate_expr() + assert expr.type_of() == pt.TealType.bytes + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.byte, '"{my_string}"'.format(my_string=my_string)), + pt.TealOp(pt.Int(254), pt.Op.int, 254), + pt.TealOp(pt.Int(2), pt.Op.int, 2), + pt.TealOp(add, pt.Op.add), + pt.TealOp(args[2], pt.Op.int, 257), + pt.TealOp(None, op), + ] + ) + + actual, _ = expr.__teal__(teal5Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + if op == pt.Op.extract3: + with pytest.raises(pt.TealInputError): + expr.__teal__(teal4Options) + + +@pytest.mark.parametrize("op", [pt.Op.extract3, pt.Op.substring3]) +def test_endArg_not_int(op: pt.Op): + my_string = "*" * 257 + add = pt.Add(pt.Int(254), pt.Int(3)) + args = [pt.Bytes(my_string), pt.Int(256), add] + + def generate_expr() -> pt.Expr: + match op: + case pt.Op.extract3: + return pt.Extract(args[0], args[1], args[2]) + case pt.Op.substring3: + return pt.Substring(args[0], args[1], args[2]) + case _: + raise Exception(f"Unsupported {op=}") + + expr = generate_expr() + assert expr.type_of() == pt.TealType.bytes + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.byte, '"{my_string}"'.format(my_string=my_string)), + pt.TealOp(args[1], pt.Op.int, 256), + pt.TealOp(pt.Int(254), pt.Op.int, 254), + pt.TealOp(pt.Int(3), pt.Op.int, 3), + pt.TealOp(add, pt.Op.add), + pt.TealOp(None, op), + ] + ) + + actual, _ = expr.__teal__(teal5Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + if op == pt.Op.extract3: + with pytest.raises(pt.TealInputError): + expr.__teal__(teal4Options) + + def test_suffix_invalid(): with pytest.raises(pt.TealTypeError): pt.Suffix(pt.Int(0), pt.Int(0)) diff --git a/pyteal/ir/ops.py b/pyteal/ir/ops.py index 7d91dab42..f9a8f731e 100644 --- a/pyteal/ir/ops.py +++ b/pyteal/ir/ops.py @@ -179,6 +179,8 @@ def min_version(self) -> int: gitxnas = OpType("gitxnas", Mode.Application, 6) gloadss = OpType("gloadss", Mode.Application, 6) acct_params_get = OpType("acct_params_get", Mode.Application, 6) + replace2 = OpType("replace2", Mode.Signature | Mode.Application, 7) + replace3 = OpType("replace3", Mode.Signature | Mode.Application, 7) # fmt: on From dd2c7ecc0617137411951e87452a58fde2887a8e Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 30 Jun 2022 12:09:30 -0400 Subject: [PATCH 04/19] Add Block (#415) * Add Block # Conflicts: # pyteal/ir/ops.py * Disable flake8 errors on formatted lines * Add past version failure check * Remove unnecessary ignore Expr equality context --- pyteal/__init__.pyi | 1 + pyteal/ast/__init__.py | 2 + pyteal/ast/block.py | 80 ++++++++++++++++++++++++++++++++++++++++ pyteal/ast/block_test.py | 60 ++++++++++++++++++++++++++++++ pyteal/ir/ops.py | 1 + 5 files changed, 144 insertions(+) create mode 100644 pyteal/ast/block.py create mode 100644 pyteal/ast/block_test.py diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index 20bc15dd4..85f89147a 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -43,6 +43,7 @@ __all__ = [ "BitwiseNot", "BitwiseOr", "BitwiseXor", + "Block", "Break", "Btoi", "Bytes", diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index a72c064fc..de22eb659 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -20,6 +20,7 @@ Txn, ) from pyteal.ast.gtxn import GtxnExpr, GtxnaExpr, TxnGroup, Gtxn +from pyteal.ast.block import Block from pyteal.ast.gaid import GeneratedID from pyteal.ast.gitxn import Gitxn, GitxnExpr, GitxnaExpr, InnerTxnGroup from pyteal.ast.gload import ImportScratchValue @@ -162,6 +163,7 @@ "GtxnaExpr", "TxnGroup", "Gtxn", + "Block", "GeneratedID", "ImportScratchValue", "Global", diff --git a/pyteal/ast/block.py b/pyteal/ast/block.py new file mode 100644 index 000000000..041483aae --- /dev/null +++ b/pyteal/ast/block.py @@ -0,0 +1,80 @@ +from typing import TYPE_CHECKING +from enum import Enum + +from pyteal.types import TealType, require_type +from pyteal.errors import verifyFieldVersion +from pyteal.ir import TealOp, Op, TealBlock +from pyteal.ast.expr import Expr +from pyteal.ast.leafexpr import LeafExpr + +if TYPE_CHECKING: + from pyteal.compiler import CompileOptions + + +class BlockField(Enum): + # fmt: off + # id | name | type | min version + block_seed = (0, "BlkSeed", TealType.bytes, 7) # noqa: E222 + block_timestamp = (1, "BlkTimestamp", TealType.uint64, 7) # noqa: E222 + + # fmt: on + + def __init__(self, id: int, name: str, type: TealType, min_version: int) -> None: + self.id = id + self.arg_name = name + self.ret_type = type + self.min_version = min_version + + def type_of(self) -> TealType: + return self.ret_type + + +BlockField.__module__ = "pyteal" + + +class Block(LeafExpr): + """An expression that accesses a block property.""" + + def __init__(self, field: BlockField, block: Expr) -> None: + super().__init__() + self.field = field + + require_type(block, TealType.uint64) + self.block = block + + def __teal__(self, options: "CompileOptions"): + verifyFieldVersion(self.field.arg_name, self.field.min_version, options.version) + + op = TealOp(self, Op.block, self.field.arg_name) + return TealBlock.FromOp(options, op, self.block) + + def __str__(self): + return "(Block {})".format(self.field.arg_name) + + def type_of(self): + return self.field.type_of() + + @classmethod + def seed(cls, block: Expr) -> "Block": + """Get the seed of a block. + + Args: + block: A block index that corresponds to the block to check, + must be evaluated to uint64. Fails if the block index is not less than the + current round or more than 1001 rounds before txn.LastValid. + """ + return cls(BlockField.block_seed, block) + + @classmethod + def timestamp(cls, block: Expr) -> "Block": + """Get the timestamp of a block. + + Args: + block: A block index that corresponds to the block to check, + must be evaluated to uint64. Fails if the block index is not less than the + current round or more than 1001 rounds before txn.LastValid. + """ + return cls(BlockField.block_timestamp, block) + + +Block.__module__ = "pyteal" diff --git a/pyteal/ast/block_test.py b/pyteal/ast/block_test.py new file mode 100644 index 000000000..0cd571063 --- /dev/null +++ b/pyteal/ast/block_test.py @@ -0,0 +1,60 @@ +import pytest + +import pyteal as pt + +teal6Options = pt.CompileOptions(version=6) +teal7Options = pt.CompileOptions(version=7) + + +def test_block_seed(): + arg = pt.Int(0) + expr = pt.Block.seed(arg) + assert expr.type_of() == pt.TealType.bytes + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(arg, pt.Op.int, 0), + pt.TealOp(expr, pt.Op.block, "BlkSeed"), + ] + ) + + actual, _ = expr.__teal__(teal7Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + assert actual == expected + + with pytest.raises(pt.TealInputError): + expr.__teal__(teal6Options) + + +def test_block_seed_invalid(): + with pytest.raises(pt.TealTypeError): + pt.Block.seed(pt.Bytes("")) + + +def test_block_timestamp(): + arg = pt.Int(0) + expr = pt.Block.timestamp(arg) + assert expr.type_of() == pt.TealType.uint64 + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(arg, pt.Op.int, 0), + pt.TealOp(expr, pt.Op.block, "BlkTimestamp"), + ] + ) + + actual, _ = expr.__teal__(teal7Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + assert actual == expected + + with pytest.raises(pt.TealInputError): + expr.__teal__(teal6Options) + + +def test_block_timestamp_invalid(): + with pytest.raises(pt.TealTypeError): + pt.Block.timestamp(pt.Txn.sender()) diff --git a/pyteal/ir/ops.py b/pyteal/ir/ops.py index f9a8f731e..3a2c49f37 100644 --- a/pyteal/ir/ops.py +++ b/pyteal/ir/ops.py @@ -181,6 +181,7 @@ def min_version(self) -> int: acct_params_get = OpType("acct_params_get", Mode.Application, 6) replace2 = OpType("replace2", Mode.Signature | Mode.Application, 7) replace3 = OpType("replace3", Mode.Signature | Mode.Application, 7) + block = OpType("block", Mode.Signature | Mode.Application, 7) # fmt: on From 8c3d2a03200e29694329e6102993b0460e6ea587 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 30 Jun 2022 12:25:30 -0400 Subject: [PATCH 05/19] Add JsonRef (#417) * Add JsonRef * Use named class methods to specify value type * Remove unnecessary ignore Expr equality context * Fix docstring link --- pyteal/__init__.pyi | 1 + pyteal/ast/__init__.py | 2 + pyteal/ast/jsonref.py | 107 +++++++++++++++++++++++++++++++++++++ pyteal/ast/jsonref_test.py | 83 ++++++++++++++++++++++++++++ pyteal/ir/ops.py | 1 + 5 files changed, 194 insertions(+) create mode 100644 pyteal/ast/jsonref.py create mode 100644 pyteal/ast/jsonref_test.py diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index 85f89147a..ec4af54a0 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -108,6 +108,7 @@ __all__ = [ "InnerTxnGroup", "Int", "Itob", + "JsonRef", "Keccak256", "LabelReference", "Le", diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index de22eb659..919e8b6d6 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -104,6 +104,7 @@ from pyteal.ast.ternaryexpr import Divw, Ed25519Verify, SetBit, SetByte from pyteal.ast.substring import Substring, Extract, Suffix from pyteal.ast.replace import Replace +from pyteal.ast.jsonref import JsonRef # more ops from pyteal.ast.naryexpr import NaryExpr, And, Or, Concat @@ -285,4 +286,5 @@ "EcdsaVerify", "EcdsaDecompress", "EcdsaRecover", + "JsonRef", ] diff --git a/pyteal/ast/jsonref.py b/pyteal/ast/jsonref.py new file mode 100644 index 000000000..91018d714 --- /dev/null +++ b/pyteal/ast/jsonref.py @@ -0,0 +1,107 @@ +from typing import TYPE_CHECKING +from enum import Enum + +from pyteal.types import TealType, require_type +from pyteal.errors import verifyFieldVersion, verifyTealVersion +from pyteal.ir import TealOp, Op, TealBlock +from pyteal.ast.expr import Expr +from pyteal.ast.leafexpr import LeafExpr + +if TYPE_CHECKING: + from pyteal.compiler import CompileOptions + + +class JsonRefType(Enum): + # fmt: off + # id | name | type | min version + string = (0, "JSONString", TealType.bytes, 7) + uint64 = (1, "JSONUint64", TealType.uint64, 7) + object = (2, "JSONObject", TealType.bytes, 7) + # fmt: on + + def __init__(self, id: int, name: str, type: TealType, min_version: int) -> None: + self.id = id + self.arg_name = name + self.ret_type = type + self.min_version = min_version + + def type_of(self) -> TealType: + return self.ret_type + + +JsonRefType.__module__ = "pyteal" + + +class JsonRef(LeafExpr): + """An expression that accesses the value associated with a given key from a supported utf-8 encoded json object. + + The json object must satisfy a `particular specification `_. + """ + + def __init__(self, type: JsonRefType, json_obj: Expr, key: Expr) -> None: + super().__init__() + + self.type = type + + require_type(json_obj, TealType.bytes) + self.json_obj = json_obj + + require_type(key, TealType.bytes) + self.key = key + + def __teal__(self, options: "CompileOptions"): + verifyTealVersion( + Op.json_ref.min_version, + options.version, + "TEAL version too low to use op json_ref", + ) + + verifyFieldVersion(self.type.arg_name, self.type.min_version, options.version) + + op = TealOp(self, Op.json_ref, self.type.arg_name) + return TealBlock.FromOp(options, op, self.json_obj, self.key) + + def __str__(self): + return "(JsonRef {})".format(self.type.arg_name) + + def type_of(self): + return self.type.type_of() + + @classmethod + def as_string(cls, json_obj: Expr, key: Expr) -> "JsonRef": + """Access the value of a given key as a string. + + Refer to the `JsonRef` class documentation for valid json specification. + + Args: + json_obj: The utf-8 encoded json object. + key: The key to access in the json object. + """ + return cls(JsonRefType.string, json_obj, key) + + @classmethod + def as_uint64(cls, json_obj: Expr, key: Expr) -> "JsonRef": + """Access the value of a given key as a uint64. + + Refer to the `JsonRef` class documentation for valid json specification. + + Args: + json_obj: The utf-8 encoded json object. + key: The key to access in the json object. + """ + return cls(JsonRefType.uint64, json_obj, key) + + @classmethod + def as_object(cls, json_obj: Expr, key: Expr) -> "JsonRef": + """Access the value of a given key as a json object. + + Refer to the `JsonRef` class documentation for valid json specification. + + Args: + json_obj: The utf-8 encoded json object. + key: The key to access in the json object. + """ + return cls(JsonRefType.object, json_obj, key) + + +JsonRef.__module__ = "pyteal" diff --git a/pyteal/ast/jsonref_test.py b/pyteal/ast/jsonref_test.py new file mode 100644 index 000000000..356c74591 --- /dev/null +++ b/pyteal/ast/jsonref_test.py @@ -0,0 +1,83 @@ +import pytest + +import pyteal as pt + +teal6Options = pt.CompileOptions(version=6) +teal7Options = pt.CompileOptions(version=7) + + +def test_json_string(): + args = [pt.Bytes('{"foo":"bar"}'), pt.Bytes("foo")] + expr = pt.JsonRef.as_string(*args) + assert expr.type_of() == pt.TealType.bytes + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.byte, '"{\\"foo\\":\\"bar\\"}"'), + pt.TealOp(args[1], pt.Op.byte, '"foo"'), + pt.TealOp(expr, pt.Op.json_ref, "JSONString"), + ] + ) + + actual, _ = expr.__teal__(teal7Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + assert actual == expected + + with pytest.raises(pt.TealInputError): + expr.__teal__(teal6Options) + + +def test_json_uint64(): + args = [pt.Bytes('{"foo":123456789}'), pt.Bytes("foo")] + expr = pt.JsonRef.as_uint64(*args) + assert expr.type_of() == pt.TealType.uint64 + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.byte, '"{\\"foo\\":123456789}"'), + pt.TealOp(args[1], pt.Op.byte, '"foo"'), + pt.TealOp(expr, pt.Op.json_ref, "JSONUint64"), + ] + ) + + actual, _ = expr.__teal__(teal7Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + assert actual == expected + + with pytest.raises(pt.TealInputError): + expr.__teal__(teal6Options) + + +def test_json_object(): + args = [pt.Bytes('{"foo":{"key": "value"}}'), pt.Bytes("foo")] + expr = pt.JsonRef.as_object(*args) + assert expr.type_of() == pt.TealType.bytes + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.byte, '"{\\"foo\\":{\\"key\\": \\"value\\"}}"'), + pt.TealOp(args[1], pt.Op.byte, '"foo"'), + pt.TealOp(expr, pt.Op.json_ref, "JSONObject"), + ] + ) + + actual, _ = expr.__teal__(teal7Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + assert actual == expected + + with pytest.raises(pt.TealInputError): + expr.__teal__(teal6Options) + + +def test_json_ref_invalid(): + with pytest.raises(pt.TealTypeError): + pt.JsonRef.as_object(pt.Int(0), pt.Bytes("a")) + + with pytest.raises(pt.TealTypeError): + pt.JsonRef.as_string(pt.Bytes("a"), pt.Int(0)) diff --git a/pyteal/ir/ops.py b/pyteal/ir/ops.py index 3a2c49f37..f3ffc1a44 100644 --- a/pyteal/ir/ops.py +++ b/pyteal/ir/ops.py @@ -181,6 +181,7 @@ def min_version(self) -> int: acct_params_get = OpType("acct_params_get", Mode.Application, 6) replace2 = OpType("replace2", Mode.Signature | Mode.Application, 7) replace3 = OpType("replace3", Mode.Signature | Mode.Application, 7) + json_ref = OpType("json_ref", Mode.Signature | Mode.Application, 7) block = OpType("block", Mode.Signature | Mode.Application, 7) # fmt: on From 839b9852521479a7a8be3e12de832c1ba4980258 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 30 Jun 2022 13:47:58 -0400 Subject: [PATCH 06/19] Add Base64Decode (#418) * Add Base64Decode * Remove unnecessary ignore Expr equality context --- pyteal/__init__.pyi | 1 + pyteal/ast/__init__.py | 2 + pyteal/ast/base64decode.py | 85 +++++++++++++++++++++++++++++++++ pyteal/ast/base64decode_test.py | 57 ++++++++++++++++++++++ pyteal/ir/ops.py | 1 + 5 files changed, 146 insertions(+) create mode 100644 pyteal/ast/base64decode.py create mode 100644 pyteal/ast/base64decode_test.py diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index ec4af54a0..c4f1dd670 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -37,6 +37,7 @@ __all__ = [ "AssetHolding", "AssetParam", "Balance", + "Base64Decode", "BinaryExpr", "BitLen", "BitwiseAnd", diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index 919e8b6d6..f42ee4ec0 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -99,6 +99,7 @@ ExtractUint32, ExtractUint64, ) +from pyteal.ast.base64decode import Base64Decode # ternary ops from pyteal.ast.ternaryexpr import Divw, Ed25519Verify, SetBit, SetByte @@ -277,6 +278,7 @@ "ExtractUint32", "ExtractUint64", "Replace", + "Base64Decode", "Log", "While", "For", diff --git a/pyteal/ast/base64decode.py b/pyteal/ast/base64decode.py new file mode 100644 index 000000000..7dcf47c18 --- /dev/null +++ b/pyteal/ast/base64decode.py @@ -0,0 +1,85 @@ +from typing import TYPE_CHECKING +from enum import Enum + +from pyteal.types import TealType, require_type +from pyteal.errors import verifyFieldVersion +from pyteal.ir import TealOp, Op, TealBlock +from pyteal.ast.expr import Expr +from pyteal.ast.leafexpr import LeafExpr + +if TYPE_CHECKING: + from pyteal.compiler import CompileOptions + + +class Base64Encoding(Enum): + # fmt: off + # id | name | min version + url = (0, "URLEncoding", 7) + std = (1, "StdEncoding", 7) + # fmt: on + + def __init__(self, id: int, name: str, min_version: int) -> None: + self.id = id + self.arg_name = name + self.min_version = min_version + + +Base64Encoding.__module__ = "pyteal" + + +class Base64Decode(LeafExpr): + """An expression that decodes a base64-encoded byte string according to a specific encoding. + + See [RFC 4648](https://rfc-editor.org/rfc/rfc4648.html#section-4) (sections 4 and 5) for information on specifications. + + It is assumed that the encoding ends with the exact number of = padding characters as required by the RFC. + When padding occurs, any unused pad bits in the encoding must be set to zero or the decoding will fail. + The special cases of \\n and \\r are allowed but completely ignored. An error will result when attempting + to decode a string with a character that is not in the encoding alphabet or not one of =, \\r, or \\n. + """ + + def __init__(self, encoding: Base64Encoding, base64: Expr) -> None: + super().__init__() + self.encoding = encoding + + require_type(base64, TealType.bytes) + self.base64 = base64 + + def __teal__(self, options: "CompileOptions"): + verifyFieldVersion( + self.encoding.arg_name, self.encoding.min_version, options.version + ) + + op = TealOp(self, Op.base64_decode, self.encoding.arg_name) + return TealBlock.FromOp(options, op, self.base64) + + def __str__(self): + return "(Base64Decode {})".format(self.encoding.arg_name) + + def type_of(self): + return TealType.bytes + + @classmethod + def url(cls, base64: Expr) -> "Base64Decode": + """Decode a base64-encoded byte string according to the URL encoding. + + Refer to the `Base64Decode` class documentation for more information. + + Args: + base64: A base64-encoded byte string. + """ + return cls(Base64Encoding.url, base64) + + @classmethod + def std(cls, base64: Expr) -> "Base64Decode": + """Decode a base64-encoded byte string according to the Standard encoding. + + Refer to the `Base64Decode` class documentation for more information. + + Args: + base64: A base64-encoded byte string. + """ + return cls(Base64Encoding.std, base64) + + +Base64Decode.__module__ = "pyteal" diff --git a/pyteal/ast/base64decode_test.py b/pyteal/ast/base64decode_test.py new file mode 100644 index 000000000..14eb48799 --- /dev/null +++ b/pyteal/ast/base64decode_test.py @@ -0,0 +1,57 @@ +import pytest + +import pyteal as pt + +teal6Options = pt.CompileOptions(version=6) +teal7Options = pt.CompileOptions(version=7) + + +def test_base64decode_std(): + arg = pt.Bytes("aGVsbG8gd29ybGQ=") + expr = pt.Base64Decode.std(arg) + assert expr.type_of() == pt.TealType.bytes + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(arg, pt.Op.byte, '"aGVsbG8gd29ybGQ="'), + pt.TealOp(expr, pt.Op.base64_decode, "StdEncoding"), + ] + ) + + actual, _ = expr.__teal__(teal7Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + assert actual == expected + + with pytest.raises(pt.TealInputError): + expr.__teal__(teal6Options) + + +def test_base64decode_url(): + arg = pt.Bytes("aGVsbG8gd29ybGQ") + expr = pt.Base64Decode.url(arg) + assert expr.type_of() == pt.TealType.bytes + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(arg, pt.Op.byte, '"aGVsbG8gd29ybGQ"'), + pt.TealOp(expr, pt.Op.base64_decode, "URLEncoding"), + ] + ) + + actual, _ = expr.__teal__(teal7Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + assert actual == expected + + with pytest.raises(pt.TealInputError): + expr.__teal__(teal6Options) + + +def test_base64decode_invalid(): + with pytest.raises(pt.TealTypeError): + pt.Base64Decode.std(pt.Int(0)) + with pytest.raises(pt.TealTypeError): + pt.Base64Decode.url(pt.Int(0)) diff --git a/pyteal/ir/ops.py b/pyteal/ir/ops.py index f3ffc1a44..a21e34bc4 100644 --- a/pyteal/ir/ops.py +++ b/pyteal/ir/ops.py @@ -181,6 +181,7 @@ def min_version(self) -> int: acct_params_get = OpType("acct_params_get", Mode.Application, 6) replace2 = OpType("replace2", Mode.Signature | Mode.Application, 7) replace3 = OpType("replace3", Mode.Signature | Mode.Application, 7) + base64_decode = OpType("base64_decode", Mode.Application | Mode.Signature, 7) json_ref = OpType("json_ref", Mode.Signature | Mode.Application, 7) block = OpType("block", Mode.Signature | Mode.Application, 7) # fmt: on From 7cccdcb9b9ab14b7539e349e1fa3b2ba9ea18ab0 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 30 Jun 2022 14:00:06 -0400 Subject: [PATCH 07/19] Support Secp256r1 curve (#423) * Support Secp256r1 curve * Fix type errors in ecdsa tests * Fix typo * Test Secp256k1 curve against TEAL 5 instead * Add compile check to `MultiValue` class * Use `MultiValue` compile checks instead of inheritance --- pyteal/ast/ecdsa.py | 18 +++-- pyteal/ast/ecdsa_test.py | 162 +++++++++++++++++++++++++++------------ pyteal/ast/multi.py | 6 +- pyteal/ast/multi_test.py | 102 ++++++++++++++---------- 4 files changed, 191 insertions(+), 97 deletions(-) diff --git a/pyteal/ast/ecdsa.py b/pyteal/ast/ecdsa.py index b85acf2c1..06e4e49db 100644 --- a/pyteal/ast/ecdsa.py +++ b/pyteal/ast/ecdsa.py @@ -2,7 +2,7 @@ from typing import Tuple, TYPE_CHECKING from pyteal.ast import Expr, MultiValue -from pyteal.errors import TealTypeError, verifyTealVersion +from pyteal.errors import TealTypeError, verifyFieldVersion, verifyTealVersion from pyteal.ir import Op, TealBlock, TealOp from pyteal.types import TealType, require_type @@ -17,6 +17,7 @@ class EcdsaCurve(Enum): """Enum representing an elliptic curve specification used in ECDSA.""" Secp256k1 = (0, "Secp256k1", 5) + Secp256r1 = (1, "Secp256r1", 7) def __init__(self, id: int, name: str, min_version: int) -> None: self.id = id @@ -52,11 +53,13 @@ def __init__( def __teal__(self, options: "CompileOptions"): verifyTealVersion( - max(self.op.min_version, self.curve.min_version), + self.op.min_version, options.version, "TEAL version too low to use op {}".format(self.op), ) + verifyFieldVersion(self.curve.arg_name, self.curve.min_version, options.version) + return TealBlock.FromOp( options, TealOp(self, self.op, self.curve.arg_name), *self.args ) @@ -113,11 +116,9 @@ def EcdsaVerify( def EcdsaDecompress(curve: EcdsaCurve, compressed_pk: Expr) -> MultiValue: """Decompress an ECDSA public key. - Args: curve: Enum representing the ECDSA curve used for the public key compressed_pk: The compressed public key. Must be 33 bytes long and big endian encoded. - Returns: A MultiValue expression representing the two components of the public key, big endian encoded. @@ -132,6 +133,9 @@ def EcdsaDecompress(curve: EcdsaCurve, compressed_pk: Expr) -> MultiValue: EcdsaPubkey, immediate_args=[curve.arg_name], args=[compressed_pk], + compile_check=lambda options: verifyFieldVersion( + curve.arg_name, curve.min_version, options.version + ), ) @@ -139,16 +143,13 @@ def EcdsaRecover( curve: EcdsaCurve, data: Expr, recovery_id: Expr, sigA: Expr, sigB: Expr ) -> MultiValue: """Reover an ECDSA public key from a signature. - All byte arguments must be big endian encoded. - Args: curve: Enum representing the ECDSA curve used for the public key data: Hash value of the signed data. Must be 32 bytes long. recovery_id: value used to extract public key from signature. Must evaluate to uint. sigA: First component of the signature. Must evaluate to bytes. sigB: Second component of the signature. Must evaluate to bytes. - Returns: A MultiValue expression representing the two components of the public key, big endian encoded. @@ -166,4 +167,7 @@ def EcdsaRecover( EcdsaPubkey, immediate_args=[curve.arg_name], args=[data, recovery_id, sigA, sigB], + compile_check=lambda options: verifyFieldVersion( + curve.arg_name, curve.min_version, options.version + ), ) diff --git a/pyteal/ast/ecdsa_test.py b/pyteal/ast/ecdsa_test.py index 681755a65..03cec2207 100644 --- a/pyteal/ast/ecdsa_test.py +++ b/pyteal/ast/ecdsa_test.py @@ -1,22 +1,28 @@ import pytest +from typing import Union, List, cast import pyteal as pt teal4Options = pt.CompileOptions(version=4) teal5Options = pt.CompileOptions(version=5) +teal7Options = pt.CompileOptions(version=7) +curve_options_map = { + pt.EcdsaCurve.Secp256k1: teal5Options, + pt.EcdsaCurve.Secp256r1: teal7Options, +} -def test_ecdsa_decompress(): + +@pytest.mark.parametrize("curve", [pt.EcdsaCurve.Secp256k1, pt.EcdsaCurve.Secp256r1]) +def test_ecdsa_decompress(curve: pt.EcdsaCurve): compressed_pubkey = pt.Bytes("XY") - pubkey = pt.EcdsaDecompress(pt.EcdsaCurve.Secp256k1, compressed_pubkey) + pubkey = pt.EcdsaDecompress(curve, compressed_pubkey) assert pubkey.type_of() == pt.TealType.none expected = pt.TealSimpleBlock( [ pt.TealOp(compressed_pubkey, pt.Op.byte, '"XY"'), - pt.TealOp( - pubkey, pt.Op.ecdsa_pk_decompress, pt.EcdsaCurve.Secp256k1.arg_name - ), + pt.TealOp(pubkey, pt.Op.ecdsa_pk_decompress, curve.arg_name), pt.TealOp( pubkey.output_slots[1].store(), pt.Op.store, pubkey.output_slots[1] ), @@ -26,7 +32,7 @@ def test_ecdsa_decompress(): ] ) - actual, _ = pubkey.__teal__(teal5Options) + actual, _ = pubkey.__teal__(curve_options_map[curve]) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -34,14 +40,22 @@ def test_ecdsa_decompress(): assert actual == expected # compile without errors this is necessary so assembly is also tested - pt.compileTeal(pt.Seq(pubkey, pt.Approve()), pt.Mode.Application, version=5) + pt.compileTeal( + pt.Seq(pubkey, pt.Approve()), pt.Mode.Application, version=curve.min_version + ) + with pytest.raises(pt.TealInputError): + pt.compileTeal( + pt.Seq(pubkey, pt.Approve()), + pt.Mode.Application, + version=curve.min_version - 1, + ) -def test_ecdsa_recover(): + +@pytest.mark.parametrize("curve", [pt.EcdsaCurve.Secp256k1, pt.EcdsaCurve.Secp256r1]) +def test_ecdsa_recover(curve: pt.EcdsaCurve): args = [pt.Bytes("data"), pt.Int(1), pt.Bytes("sigA"), pt.Bytes("sigB")] - pubkey = pt.EcdsaRecover( - pt.EcdsaCurve.Secp256k1, args[0], args[1], args[2], args[3] - ) + pubkey = pt.EcdsaRecover(curve, args[0], args[1], args[2], args[3]) assert pubkey.type_of() == pt.TealType.none expected = pt.TealSimpleBlock( @@ -50,7 +64,7 @@ def test_ecdsa_recover(): pt.TealOp(args[1], pt.Op.int, 1), pt.TealOp(args[2], pt.Op.byte, '"sigA"'), pt.TealOp(args[3], pt.Op.byte, '"sigB"'), - pt.TealOp(pubkey, pt.Op.ecdsa_pk_recover, pt.EcdsaCurve.Secp256k1.arg_name), + pt.TealOp(pubkey, pt.Op.ecdsa_pk_recover, curve.arg_name), pt.TealOp( pubkey.output_slots[1].store(), pt.Op.store, pubkey.output_slots[1] ), @@ -60,7 +74,7 @@ def test_ecdsa_recover(): ] ) - actual, _ = pubkey.__teal__(teal5Options) + actual, _ = pubkey.__teal__(curve_options_map[curve]) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -68,13 +82,23 @@ def test_ecdsa_recover(): assert actual == expected # compile without errors this is necessary so assembly is also tested - pt.compileTeal(pt.Seq(pubkey, pt.Approve()), pt.Mode.Application, version=5) + pt.compileTeal( + pt.Seq(pubkey, pt.Approve()), pt.Mode.Application, version=curve.min_version + ) + + with pytest.raises(pt.TealInputError): + pt.compileTeal( + pt.Seq(pubkey, pt.Approve()), + pt.Mode.Application, + version=curve.min_version - 1, + ) -def test_ecdsa_verify_basic(): +@pytest.mark.parametrize("curve", [pt.EcdsaCurve.Secp256k1, pt.EcdsaCurve.Secp256r1]) +def test_ecdsa_verify_basic(curve: pt.EcdsaCurve): args = [pt.Bytes("data"), pt.Bytes("sigA"), pt.Bytes("sigB")] pubkey = (pt.Bytes("X"), pt.Bytes("Y")) - expr = pt.EcdsaVerify(pt.EcdsaCurve.Secp256k1, args[0], args[1], args[2], pubkey) + expr = pt.EcdsaVerify(curve, args[0], args[1], args[2], pubkey) assert expr.type_of() == pt.TealType.uint64 expected = pt.TealSimpleBlock( @@ -84,33 +108,43 @@ def test_ecdsa_verify_basic(): pt.TealOp(args[2], pt.Op.byte, '"sigB"'), pt.TealOp(pubkey[0], pt.Op.byte, '"X"'), pt.TealOp(pubkey[1], pt.Op.byte, '"Y"'), - pt.TealOp(expr, pt.Op.ecdsa_verify, pt.EcdsaCurve.Secp256k1.arg_name), + pt.TealOp(expr, pt.Op.ecdsa_verify, curve.arg_name), ] ) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(curve_options_map[curve]) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected # compile without errors this is necessary so assembly is also tested - pt.compileTeal(pt.Seq(pt.Pop(expr), pt.Approve()), pt.Mode.Application, version=5) + pt.compileTeal( + pt.Seq(pt.Pop(expr), pt.Approve()), + pt.Mode.Application, + version=curve.min_version, + ) + + with pytest.raises(pt.TealInputError): + pt.compileTeal( + pt.Seq(pt.Pop(expr), pt.Approve()), + pt.Mode.Application, + version=curve.min_version - 1, + ) -def test_ecdsa_verify_compressed_pk(): +@pytest.mark.parametrize("curve", [pt.EcdsaCurve.Secp256k1, pt.EcdsaCurve.Secp256r1]) +def test_ecdsa_verify_compressed_pk(curve: pt.EcdsaCurve): args = [pt.Bytes("data"), pt.Bytes("sigA"), pt.Bytes("sigB")] compressed_pubkey = pt.Bytes("XY") - pubkey = pt.EcdsaDecompress(pt.EcdsaCurve.Secp256k1, compressed_pubkey) - expr = pt.EcdsaVerify(pt.EcdsaCurve.Secp256k1, args[0], args[1], args[2], pubkey) + pubkey = pt.EcdsaDecompress(curve, compressed_pubkey) + expr = pt.EcdsaVerify(curve, args[0], args[1], args[2], pubkey) assert expr.type_of() == pt.TealType.uint64 expected = pt.TealSimpleBlock( [ pt.TealOp(compressed_pubkey, pt.Op.byte, '"XY"'), - pt.TealOp( - pubkey, pt.Op.ecdsa_pk_decompress, pt.EcdsaCurve.Secp256k1.arg_name - ), + pt.TealOp(pubkey, pt.Op.ecdsa_pk_decompress, curve.arg_name), pt.TealOp( pubkey.output_slots[1].store(), pt.Op.store, pubkey.output_slots[1] ), @@ -126,11 +160,11 @@ def test_ecdsa_verify_compressed_pk(): pt.TealOp( pubkey.output_slots[1].load(), pt.Op.load, pubkey.output_slots[1] ), - pt.TealOp(expr, pt.Op.ecdsa_verify, pt.EcdsaCurve.Secp256k1.arg_name), + pt.TealOp(expr, pt.Op.ecdsa_verify, curve.arg_name), ] ) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(curve_options_map[curve]) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -138,15 +172,25 @@ def test_ecdsa_verify_compressed_pk(): assert actual == expected # compile without errors this is necessary so assembly is also tested - pt.compileTeal(pt.Seq(pt.Pop(expr), pt.Approve()), pt.Mode.Application, version=5) + pt.compileTeal( + pt.Seq(pt.Pop(expr), pt.Approve()), + pt.Mode.Application, + version=curve.min_version, + ) + with pytest.raises(pt.TealInputError): + pt.compileTeal( + pt.Seq(pt.Pop(expr), pt.Approve()), + pt.Mode.Application, + version=curve.min_version - 1, + ) -def test_ecdsa_verify_recovered_pk(): + +@pytest.mark.parametrize("curve", [pt.EcdsaCurve.Secp256k1, pt.EcdsaCurve.Secp256r1]) +def test_ecdsa_verify_recovered_pk(curve: pt.EcdsaCurve): args = [pt.Bytes("data"), pt.Int(1), pt.Bytes("sigA"), pt.Bytes("sigB")] - pubkey = pt.EcdsaRecover( - pt.EcdsaCurve.Secp256k1, args[0], args[1], args[2], args[3] - ) - expr = pt.EcdsaVerify(pt.EcdsaCurve.Secp256k1, args[0], args[2], args[3], pubkey) + pubkey = pt.EcdsaRecover(curve, args[0], args[1], args[2], args[3]) + expr = pt.EcdsaVerify(curve, args[0], args[2], args[3], pubkey) assert expr.type_of() == pt.TealType.uint64 expected = pt.TealSimpleBlock( @@ -155,7 +199,7 @@ def test_ecdsa_verify_recovered_pk(): pt.TealOp(args[1], pt.Op.int, 1), pt.TealOp(args[2], pt.Op.byte, '"sigA"'), pt.TealOp(args[3], pt.Op.byte, '"sigB"'), - pt.TealOp(pubkey, pt.Op.ecdsa_pk_recover, pt.EcdsaCurve.Secp256k1.arg_name), + pt.TealOp(pubkey, pt.Op.ecdsa_pk_recover, curve.arg_name), pt.TealOp( pubkey.output_slots[1].store(), pt.Op.store, pubkey.output_slots[1] ), @@ -171,11 +215,11 @@ def test_ecdsa_verify_recovered_pk(): pt.TealOp( pubkey.output_slots[1].load(), pt.Op.load, pubkey.output_slots[1] ), - pt.TealOp(expr, pt.Op.ecdsa_verify, pt.EcdsaCurve.Secp256k1.arg_name), + pt.TealOp(expr, pt.Op.ecdsa_verify, curve.arg_name), ] ) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(curve_options_map[curve]) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -183,26 +227,46 @@ def test_ecdsa_verify_recovered_pk(): assert actual == expected # compile without errors this is necessary so assembly is also tested - pt.compileTeal(pt.Seq(pt.Pop(expr), pt.Approve()), pt.Mode.Application, version=5) + pt.compileTeal( + pt.Seq(pt.Pop(expr), pt.Approve()), + pt.Mode.Application, + version=curve.min_version, + ) + + with pytest.raises(pt.TealInputError): + pt.compileTeal( + pt.Seq(pt.Pop(expr), pt.Approve()), + pt.Mode.Application, + version=curve.min_version - 1, + ) -def test_ecdsa_invalid(): +@pytest.mark.parametrize("curve", [pt.EcdsaCurve.Secp256k1, pt.EcdsaCurve.Secp256r1]) +def test_ecdsa_invalid(curve: pt.EcdsaCurve): with pytest.raises(pt.TealTypeError): - args = [pt.Bytes("data"), pt.Bytes("1"), pt.Bytes("sigA"), pt.Bytes("sigB")] - pt.EcdsaRecover(pt.EcdsaCurve.Secp256k1, args[0], args[1], args[2], args[3]) + args: List[Union[pt.Bytes, pt.Int]] = [ + pt.Bytes("data"), + pt.Bytes("1"), + pt.Bytes("sigA"), + pt.Bytes("sigB"), + ] + pt.EcdsaRecover(curve, args[0], args[1], args[2], args[3]) with pytest.raises(pt.TealTypeError): - pt.EcdsaDecompress(pt.EcdsaCurve.Secp256k1, pt.Int(1)) + pt.EcdsaDecompress(curve, pt.Int(1)) with pytest.raises(pt.TealTypeError): args = [pt.Bytes("data"), pt.Bytes("sigA"), pt.Bytes("sigB")] - pubkey = (pt.Bytes("X"), pt.Int(1)) - pt.EcdsaVerify(pt.EcdsaCurve.Secp256k1, args[0], args[1], args[2], pubkey) + pubkey: Union[tuple[pt.Bytes, Union[pt.Int, pt.Bytes]], pt.MultiValue] = ( + pt.Bytes("X"), + pt.Int(1), + ) + pt.EcdsaVerify(curve, args[0], args[1], args[2], pubkey) with pytest.raises(pt.TealTypeError): args = [pt.Bytes("data"), pt.Int(1), pt.Bytes("sigB")] pubkey = (pt.Bytes("X"), pt.Bytes("Y")) - pt.EcdsaVerify(pt.EcdsaCurve.Secp256k1, args[0], args[1], args[2], pubkey) + pt.EcdsaVerify(curve, args[0], args[1], args[2], pubkey) with pytest.raises(pt.TealTypeError): args = [pt.Bytes("data"), pt.Bytes("sigA"), pt.Bytes("sigB")] @@ -210,21 +274,19 @@ def test_ecdsa_invalid(): pubkey = pt.MultiValue( pt.Op.ecdsa_pk_decompress, [pt.TealType.uint64, pt.TealType.bytes], - immediate_args=[pt.EcdsaCurve.Secp256k1], + immediate_args=[curve.__str__()], args=[compressed_pk], ) - pt.EcdsaVerify(pt.EcdsaCurve.Secp256k1, args[0], args[1], args[2], pubkey) + pt.EcdsaVerify(curve, args[0], args[1], args[2], pubkey) with pytest.raises(pt.TealInputError): args = [pt.Bytes("data"), pt.Bytes("sigA"), pt.Bytes("sigB")] pubkey = (pt.Bytes("X"), pt.Bytes("Y")) - expr = pt.EcdsaVerify( - pt.EcdsaCurve.Secp256k1, args[0], args[1], args[2], pubkey - ) + expr = pt.EcdsaVerify(curve, args[0], args[1], args[2], pubkey) expr.__teal__(teal4Options) with pytest.raises(pt.TealTypeError): args = [pt.Bytes("data"), pt.Bytes("sigA"), pt.Bytes("sigB")] pubkey = (pt.Bytes("X"), pt.Bytes("Y")) - expr = pt.EcdsaVerify(5, args[0], args[1], args[2], pubkey) + expr = pt.EcdsaVerify(cast(pt.EcdsaCurve, 5), args[0], args[1], args[2], pubkey) diff --git a/pyteal/ast/multi.py b/pyteal/ast/multi.py index 63a4dec11..e61af4b4a 100644 --- a/pyteal/ast/multi.py +++ b/pyteal/ast/multi.py @@ -20,7 +20,8 @@ def __init__( types: List[TealType], *, immediate_args: List[Union[int, str]] = None, - args: List[Expr] = None + args: List[Expr] = None, + compile_check: Callable[["CompileOptions"], None] = lambda _: None, ): """Create a new MultiValue. @@ -35,6 +36,7 @@ def __init__( self.types = types self.immediate_args = immediate_args if immediate_args is not None else [] self.args = args if args is not None else [] + self.compile_check = compile_check self.output_slots = [ScratchSlot() for _ in self.types] @@ -57,6 +59,8 @@ def __str__(self): return ret_str def __teal__(self, options: "CompileOptions"): + self.compile_check(options) + tealOp = TealOp(self, self.op, *self.immediate_args) callStart, callEnd = TealBlock.FromOp(options, tealOp, *self.args) diff --git a/pyteal/ast/multi_test.py b/pyteal/ast/multi_test.py index e0fe3d58c..a85cebed7 100644 --- a/pyteal/ast/multi_test.py +++ b/pyteal/ast/multi_test.py @@ -1,3 +1,4 @@ +import pytest from typing import List import pyteal as pt @@ -147,47 +148,70 @@ def __test_single_with_vars( assert actual == expected -def test_multi_value(): - ops = ( +@pytest.mark.parametrize( + "op", + [ pt.Op.app_global_get_ex, pt.Op.app_local_get_ex, pt.Op.asset_holding_get, pt.Op.asset_params_get, + ], +) +@pytest.mark.parametrize( + "type", [pt.TealType.uint64, pt.TealType.bytes, pt.TealType.anytype] +) +@pytest.mark.parametrize("iargs", [[], ["AssetFrozen"]]) +@pytest.mark.parametrize("args", [[], [pt.Int(0)], [pt.Int(1), pt.Int(2)]]) +def test_multi_value(op, type, iargs, args): + reducer = ( + lambda value, hasValue: pt.If(hasValue) + .Then(value) + .Else(pt.App.globalGet(pt.Bytes("None"))) + ) + expr = pt.MultiValue( + op, [type, pt.TealType.uint64], immediate_args=iargs, args=args + ) + __test_single_conditional(expr, op, args, iargs, reducer) + + reducer = lambda value, hasValue: pt.Seq(pt.Assert(hasValue), value) # noqa: E731 + expr = pt.MultiValue( + op, [type, pt.TealType.uint64], immediate_args=iargs, args=args + ) + __test_single_assert(expr, op, args, iargs, reducer) + + hasValueVar = pt.ScratchVar(pt.TealType.uint64) + valueVar = pt.ScratchVar(type) + reducer = lambda value, hasValue: pt.Seq( # noqa: E731 + hasValueVar.store(hasValue), valueVar.store(value) + ) + expr = pt.MultiValue( + op, [type, pt.TealType.uint64], immediate_args=iargs, args=args + ) + __test_single_with_vars(expr, op, args, iargs, hasValueVar, valueVar, reducer) + + +def test_multi_compile_check(): + def never_fails(options): + return + + program_never_fails = pt.MultiValue( + pt.Op.app_global_get_ex, + [pt.TealType.uint64, pt.TealType.uint64], + compile_check=never_fails, ) - types = (pt.TealType.uint64, pt.TealType.bytes, pt.TealType.anytype) - immedate_argv = ([], ["AssetFrozen"]) - argv = ([], [pt.Int(0)], [pt.Int(1), pt.Int(2)]) - - for op in ops: - for type in types: - for iargs in immedate_argv: - for args in argv: - reducer = ( - lambda value, hasValue: pt.If(hasValue) - .Then(value) - .Else(pt.App.globalGet(pt.Bytes("None"))) - ) - expr = pt.MultiValue( - op, [type, pt.TealType.uint64], immediate_args=iargs, args=args - ) - __test_single_conditional(expr, op, args, iargs, reducer) - - reducer = lambda value, hasValue: pt.Seq( # noqa: E731 - pt.Assert(hasValue), value - ) - expr = pt.MultiValue( - op, [type, pt.TealType.uint64], immediate_args=iargs, args=args - ) - __test_single_assert(expr, op, args, iargs, reducer) - - hasValueVar = pt.ScratchVar(pt.TealType.uint64) - valueVar = pt.ScratchVar(type) - reducer = lambda value, hasValue: pt.Seq( # noqa: E731 - hasValueVar.store(hasValue), valueVar.store(value) - ) - expr = pt.MultiValue( - op, [type, pt.TealType.uint64], immediate_args=iargs, args=args - ) - __test_single_with_vars( - expr, op, args, iargs, hasValueVar, valueVar, reducer - ) + program_never_fails.__teal__(options) + + class TestException(Exception): + pass + + def always_fails(options): + raise TestException() + + program_always_fails = pt.MultiValue( + pt.Op.app_global_get_ex, + [pt.TealType.uint64, pt.TealType.uint64], + compile_check=always_fails, + ) + + with pytest.raises(TestException): + program_always_fails.__teal__(options) From 9a26fec4602d8f7ac1c9309ac148e6d43aa363e8 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 30 Jun 2022 14:12:21 -0400 Subject: [PATCH 08/19] Add VrfVerify (#419) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add VrfVerify # Conflicts: # pyteal/ast/__init__.py # pyteal/ir/ops.py * Tidy with `MultiValue`’s compile check --- pyteal/__init__.pyi | 1 + pyteal/ast/__init__.py | 4 ++ pyteal/ast/vrfverify.py | 80 ++++++++++++++++++++++++++++++++++++ pyteal/ast/vrfverify_test.py | 71 ++++++++++++++++++++++++++++++++ pyteal/ir/ops.py | 1 + 5 files changed, 157 insertions(+) create mode 100644 pyteal/ast/vrfverify.py create mode 100644 pyteal/ast/vrfverify_test.py diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index c4f1dd670..4008461a8 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -185,6 +185,7 @@ __all__ = [ "TxnType", "TxnaExpr", "UnaryExpr", + "VrfVerify", "While", "WideRatio", "compileTeal", diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index f42ee4ec0..6cce690a2 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -107,6 +107,9 @@ from pyteal.ast.replace import Replace from pyteal.ast.jsonref import JsonRef +# quaternary ops +from pyteal.ast.vrfverify import VrfVerify + # more ops from pyteal.ast.naryexpr import NaryExpr, And, Or, Concat from pyteal.ast.widemath import WideRatio @@ -289,4 +292,5 @@ "EcdsaDecompress", "EcdsaRecover", "JsonRef", + "VrfVerify", ] diff --git a/pyteal/ast/vrfverify.py b/pyteal/ast/vrfverify.py new file mode 100644 index 000000000..110671c7f --- /dev/null +++ b/pyteal/ast/vrfverify.py @@ -0,0 +1,80 @@ +from enum import Enum + +from pyteal.types import TealType, require_type +from pyteal.errors import verifyFieldVersion +from pyteal.ir import Op +from pyteal.ast.multi import MultiValue +from pyteal.ast.expr import Expr + + +class VrfVerifyStandard(Enum): + # fmt: off + # id | name | min version + algorand = (0, "VrfAlgorand", 7) # noqa: E222 + chainlink = (1, "VrfChainlink", 7) + # fmt: on + + def __init__(self, id: int, name: str, min_version: int) -> None: + self.id = id + self.arg_name = name + self.min_version = min_version + + +VrfVerifyStandard.__module__ = "pyteal" + + +class VrfVerify(MultiValue): + """An expression that verifies the proof of a message against a public key.""" + + def __init__( + self, standard: VrfVerifyStandard, message: Expr, proof: Expr, public_key: Expr + ) -> None: + require_type(message, TealType.bytes) + require_type(proof, TealType.bytes) + require_type(public_key, TealType.bytes) + + self.standard = standard + + super().__init__( + Op.vrf_verify, + [TealType.bytes, TealType.uint64], + immediate_args=[standard.arg_name], + args=[message, proof, public_key], + compile_check=lambda options: verifyFieldVersion( + standard.arg_name, standard.min_version, options.version + ), + ) + + def __str__(self): + return "(VrfVerify {})".format(self.standard.arg_name) + + @classmethod + def algorand(cls, message: Expr, proof: Expr, public_key: Expr) -> "VrfVerify": + """Verifies the proof of a message against a public key using the Algorand VRF standard. + + Args: + message: The message to verify. + proof: The proof of the message. + public_key: The public key to use to verify the proof. + + Returns: + A MultiValue expression representing the VRF output and a verification flag. + """ + return cls(VrfVerifyStandard.algorand, message, proof, public_key) + + @classmethod + def chainlink(cls, message: Expr, proof: Expr, public_key: Expr) -> "VrfVerify": + """Verifies the proof of a message against a public key using the Chainlink VRF standard. + + Args: + message: The message to verify. + proof: The proof of the message. + public_key: The public key to use to verify the proof. + + Returns: + A MultiValue expression representing the VRF output and a verification flag. + """ + return cls(VrfVerifyStandard.chainlink, message, proof, public_key) + + +VrfVerify.__module__ = "pyteal" diff --git a/pyteal/ast/vrfverify_test.py b/pyteal/ast/vrfverify_test.py new file mode 100644 index 000000000..8380d053c --- /dev/null +++ b/pyteal/ast/vrfverify_test.py @@ -0,0 +1,71 @@ +import pytest + +import pyteal as pt + +teal6Options = pt.CompileOptions(version=6) +teal7Options = pt.CompileOptions(version=7) + + +def test_vrf_verify_algorand(): + args = [pt.Bytes("a"), pt.Bytes("b"), pt.Bytes("c")] + expr = pt.VrfVerify.algorand(*args) + assert expr.type_of() == pt.TealType.none + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.byte, '"a"'), + pt.TealOp(args[1], pt.Op.byte, '"b"'), + pt.TealOp(args[2], pt.Op.byte, '"c"'), + pt.TealOp(expr, pt.Op.vrf_verify, "VrfAlgorand"), + pt.TealOp(expr.output_slots[1], pt.Op.store, expr.output_slots[1]), + pt.TealOp(expr.output_slots[0], pt.Op.store, expr.output_slots[0]), + ] + ) + + actual, _ = expr.__teal__(teal7Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + with pytest.raises(pt.TealInputError): + expr.__teal__(teal6Options) + + +def test_vrf_verify_chainlink(): + args = [pt.Bytes("a"), pt.Bytes("b"), pt.Bytes("c")] + expr = pt.VrfVerify.chainlink(*args) + assert expr.type_of() == pt.TealType.none + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.byte, '"a"'), + pt.TealOp(args[1], pt.Op.byte, '"b"'), + pt.TealOp(args[2], pt.Op.byte, '"c"'), + pt.TealOp(expr, pt.Op.vrf_verify, "VrfChainlink"), + pt.TealOp(expr.output_slots[1], pt.Op.store, expr.output_slots[1]), + pt.TealOp(expr.output_slots[0], pt.Op.store, expr.output_slots[0]), + ] + ) + + actual, _ = expr.__teal__(teal7Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + with pytest.raises(pt.TealInputError): + expr.__teal__(teal6Options) + + +def test_vrf_verify_invalid(): + with pytest.raises(pt.TealTypeError): + pt.VrfVerify.algorand(pt.Int(0), pt.Bytes("b"), pt.Bytes("c")) + + with pytest.raises(pt.TealTypeError): + pt.VrfVerify.chainlink(pt.Bytes("a"), pt.Int(0), pt.Bytes("c")) + + with pytest.raises(pt.TealTypeError): + pt.VrfVerify.chainlink(pt.Bytes("a"), pt.Bytes("b"), pt.Int(0)) diff --git a/pyteal/ir/ops.py b/pyteal/ir/ops.py index a21e34bc4..50563da94 100644 --- a/pyteal/ir/ops.py +++ b/pyteal/ir/ops.py @@ -183,6 +183,7 @@ def min_version(self) -> int: replace3 = OpType("replace3", Mode.Signature | Mode.Application, 7) base64_decode = OpType("base64_decode", Mode.Application | Mode.Signature, 7) json_ref = OpType("json_ref", Mode.Signature | Mode.Application, 7) + vrf_verify = OpType("vrf_verify", Mode.Signature | Mode.Application, 7) block = OpType("block", Mode.Signature | Mode.Application, 7) # fmt: on From cc544aae783b466450dae5aed5456920bba2526b Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 30 Jun 2022 14:23:33 -0400 Subject: [PATCH 09/19] Add `Sha3_256` (#425) * Add sha3_256 * Add crypto docs --- docs/crypto.rst | 1 + pyteal/__init__.pyi | 1 + pyteal/ast/__init__.py | 2 ++ pyteal/ast/unaryexpr.py | 5 +++++ pyteal/ast/unaryexpr_test.py | 22 ++++++++++++++++++++++ pyteal/ir/ops.py | 1 + 6 files changed, 32 insertions(+) diff --git a/docs/crypto.rst b/docs/crypto.rst index d062eb671..2ae4055b1 100644 --- a/docs/crypto.rst +++ b/docs/crypto.rst @@ -14,6 +14,7 @@ Below is how you express cryptographic primitives in PyTeal: Operator Cost Description ==================================== ========= ================================================================================================================== :code:`Sha256(e)` `35` `SHA-256` hash function, produces 32 bytes +:code:`Sha3_256(e)` `130` `SHA3-256` hash function, produces 32 bytes :code:`Keccak256(e)` `130` `Keccak-256` hash funciton, produces 32 bytes :code:`Sha512_256(e)` `45` `SHA-512/256` hash function, produces 32 bytes :code:`Ed25519Verify(d, s, p)` `1900`\* `1` if :code:`s` is the signature of :code:`d` signed by the private key corresponding to the public key :code:`p`, else `0` diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index 4008461a8..79545ec99 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -153,6 +153,7 @@ __all__ = [ "SetBit", "SetByte", "Sha256", + "Sha3_256", "Sha512_256", "ShiftLeft", "ShiftRight", diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index 6cce690a2..61d3254d3 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -46,6 +46,7 @@ BitLen, Sha256, Sha512_256, + Sha3_256, Keccak256, Not, BitwiseNot, @@ -197,6 +198,7 @@ "BitLen", "Sha256", "Sha512_256", + "Sha3_256", "Keccak256", "Not", "BitwiseNot", diff --git a/pyteal/ast/unaryexpr.py b/pyteal/ast/unaryexpr.py index 619bfab46..1fad9903b 100644 --- a/pyteal/ast/unaryexpr.py +++ b/pyteal/ast/unaryexpr.py @@ -80,6 +80,11 @@ def Sha512_256(arg: Expr) -> UnaryExpr: return UnaryExpr(Op.sha512_256, TealType.bytes, TealType.bytes, arg) +def Sha3_256(arg: Expr) -> UnaryExpr: + """Get the SHA3-256 hash of a byte string.""" + return UnaryExpr(Op.sha3_256, TealType.bytes, TealType.bytes, arg) + + def Keccak256(arg: Expr) -> UnaryExpr: """Get the KECCAK-256 hash of a byte string.""" return UnaryExpr(Op.keccak256, TealType.bytes, TealType.bytes, arg) diff --git a/pyteal/ast/unaryexpr_test.py b/pyteal/ast/unaryexpr_test.py index 50a6e2f15..5a096fcbd 100644 --- a/pyteal/ast/unaryexpr_test.py +++ b/pyteal/ast/unaryexpr_test.py @@ -7,6 +7,7 @@ teal4Options = pt.CompileOptions(version=4) teal5Options = pt.CompileOptions(version=5) teal6Options = pt.CompileOptions(version=6) +teal7Options = pt.CompileOptions(version=7) def test_btoi(): @@ -146,6 +147,27 @@ def test_sha512_256_invalid(): pt.Sha512_256(pt.Int(1)) +def test_sha3_256(): + arg = pt.Arg(0) + expr = pt.Sha3_256(arg) + assert expr.type_of() == pt.TealType.bytes + + expected = pt.TealSimpleBlock( + [pt.TealOp(arg, pt.Op.arg, 0), pt.TealOp(expr, pt.Op.sha3_256)] + ) + + actual, _ = expr.__teal__(teal7Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + assert actual == expected + + +def test_sha3_256_invalid(): + with pytest.raises(pt.TealTypeError): + pt.Sha3_256(pt.Int(1)) + + def test_keccak256(): arg = pt.Arg(0) expr = pt.Keccak256(arg) diff --git a/pyteal/ir/ops.py b/pyteal/ir/ops.py index 50563da94..4a040cac0 100644 --- a/pyteal/ir/ops.py +++ b/pyteal/ir/ops.py @@ -183,6 +183,7 @@ def min_version(self) -> int: replace3 = OpType("replace3", Mode.Signature | Mode.Application, 7) base64_decode = OpType("base64_decode", Mode.Application | Mode.Signature, 7) json_ref = OpType("json_ref", Mode.Signature | Mode.Application, 7) + sha3_256 = OpType("sha3_256", Mode.Signature | Mode.Application, 7) vrf_verify = OpType("vrf_verify", Mode.Signature | Mode.Application, 7) block = OpType("block", Mode.Signature | Mode.Application, 7) # fmt: on From 6e83ae65261dd3d33ea0d11a1c3d6876aaf58a1e Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 30 Jun 2022 14:24:11 -0400 Subject: [PATCH 10/19] Support `FirstValidTime` transaction field (#424) * Add first valid time factory and update min version * Include FirstValidTime in txn tests * Add transaction field docs --- docs/accessing_transaction_field.rst | 1 + pyteal/ast/txn.py | 9 ++++++++- pyteal/ast/txn_test.py | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/accessing_transaction_field.rst b/docs/accessing_transaction_field.rst index ea37663e5..64c5f0b65 100644 --- a/docs/accessing_transaction_field.rst +++ b/docs/accessing_transaction_field.rst @@ -27,6 +27,7 @@ Operator :any:`Txn.sender() ` :code:`TealType.bytes` 2 32 byte address :any:`Txn.fee() ` :code:`TealType.uint64` 2 in microAlgos :any:`Txn.first_valid() ` :code:`TealType.uint64` 2 round number +:any:`Txn.first_valid_time() ` :code:`TealType.uint64` 7 UNIX timestamp of block before :code:`Txn.first_valid()`. Fails if negative :any:`Txn.last_valid() ` :code:`TealType.uint64` 2 round number :any:`Txn.note() ` :code:`TealType.bytes` 2 transaction note in bytes :any:`Txn.lease() ` :code:`TealType.bytes` 2 transaction lease in bytes diff --git a/pyteal/ast/txn.py b/pyteal/ast/txn.py index 3c0fc0f4e..a4d011ddf 100644 --- a/pyteal/ast/txn.py +++ b/pyteal/ast/txn.py @@ -37,7 +37,7 @@ class TxnField(Enum): sender = (0, "Sender", TealType.bytes, False, 2) fee = (1, "Fee", TealType.uint64, False, 2) first_valid = (2, "FirstValid", TealType.uint64, False, 2) - first_valid_time = (3, "FirstValidTime", TealType.uint64, False, 2) + first_valid_time = (3, "FirstValidTime", TealType.uint64, False, 7) last_valid = (4, "LastValid", TealType.uint64, False, 2) note = (5, "Note", TealType.bytes, False, 2) lease = (6, "Lease", TealType.bytes, False, 2) @@ -319,6 +319,13 @@ def first_valid(self) -> TxnExpr: """ return self.makeTxnExpr(TxnField.first_valid) + def first_valid_time(self) -> TxnExpr: + """Get the UNIX timestamp of block before txn.FirstValid. Fails if negative. + + For more information, see https://developer.algorand.org/docs/reference/transactions/#firstvalidtime + """ + return self.makeTxnExpr(TxnField.first_valid_time) + def last_valid(self) -> TxnExpr: """Get the last valid round number. diff --git a/pyteal/ast/txn_test.py b/pyteal/ast/txn_test.py index c0477fd23..d169b459b 100644 --- a/pyteal/ast/txn_test.py +++ b/pyteal/ast/txn_test.py @@ -8,6 +8,7 @@ pt.TxnField.sender: lambda txn: txn.sender(), pt.TxnField.fee: lambda txn: txn.fee(), pt.TxnField.first_valid: lambda txn: txn.first_valid(), + pt.TxnField.first_valid_time: lambda txn: txn.first_valid_time(), pt.TxnField.last_valid: lambda txn: txn.last_valid(), pt.TxnField.note: lambda txn: txn.note(), pt.TxnField.lease: lambda txn: txn.lease(), From 594e8e42349219f70d6804bbed7f31fbf267802c Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 30 Jun 2022 14:41:49 -0400 Subject: [PATCH 11/19] Add `Ed25519Verify_Bare` (#426) * Add ed25519verify_bare * Fix typos in Ed25519 docstrings (#2) * Add crypto doc for Ed25519Verify_Bare Co-authored-by: Michael Diamant --- docs/crypto.rst | 3 ++- pyteal/__init__.pyi | 1 + pyteal/ast/__init__.py | 9 ++++++++- pyteal/ast/ternaryexpr.py | 22 +++++++++++++++++++-- pyteal/ast/ternaryexpr_test.py | 36 ++++++++++++++++++++++++++++++++++ pyteal/ir/ops.py | 1 + 6 files changed, 68 insertions(+), 4 deletions(-) diff --git a/docs/crypto.rst b/docs/crypto.rst index 2ae4055b1..6a80b2567 100644 --- a/docs/crypto.rst +++ b/docs/crypto.rst @@ -17,7 +17,8 @@ Operator Cost Description :code:`Sha3_256(e)` `130` `SHA3-256` hash function, produces 32 bytes :code:`Keccak256(e)` `130` `Keccak-256` hash funciton, produces 32 bytes :code:`Sha512_256(e)` `45` `SHA-512/256` hash function, produces 32 bytes -:code:`Ed25519Verify(d, s, p)` `1900`\* `1` if :code:`s` is the signature of :code:`d` signed by the private key corresponding to the public key :code:`p`, else `0` +:code:`Ed25519Verify(d, s, p)` `1900`\* `1` if :code:`s` is the signature of the concatenation :code:`("ProgData" + hash_of_current_program + d)` signed by the private key corresponding to the public key :code:`p`, else `0` +:code:`Ed25519Verify_Bare(d, s, p)` `1900` `1` if :code:`s` is the signature of :code:`d` signed by the private key corresponding to the public key :code:`p`, else `0` :code:`EcdsaVerify(c, d, r, s, pk)` `1700` `1` if :code:`(r, s)` is the signature of :code:`d` by private key corresponding to public key :code:`pk`, else 0 :code:`EcdsaDecompress(c, short_pk)` `650` produces the decompressed public key associated with the compressed public key :code:`short_pk` :code:`EcdsaRecover(c, d, id, r, s)` `2000` produces the public key associated with the signature :code:`(r, s)` and recovery id :code:`id` diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index 79545ec99..4e2e3b65b 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -78,6 +78,7 @@ __all__ = [ "EcdsaRecover", "EcdsaVerify", "Ed25519Verify", + "Ed25519Verify_Bare", "EnumInt", "Eq", "Err", diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index 61d3254d3..145e7f2f3 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -103,7 +103,13 @@ from pyteal.ast.base64decode import Base64Decode # ternary ops -from pyteal.ast.ternaryexpr import Divw, Ed25519Verify, SetBit, SetByte +from pyteal.ast.ternaryexpr import ( + Divw, + Ed25519Verify, + Ed25519Verify_Bare, + SetBit, + SetByte, +) from pyteal.ast.substring import Substring, Extract, Suffix from pyteal.ast.replace import Replace from pyteal.ast.jsonref import JsonRef @@ -228,6 +234,7 @@ "GetBit", "GetByte", "Ed25519Verify", + "Ed25519Verify_Bare", "Substring", "Extract", "Suffix", diff --git a/pyteal/ast/ternaryexpr.py b/pyteal/ast/ternaryexpr.py index b329165de..ad2c36bd6 100644 --- a/pyteal/ast/ternaryexpr.py +++ b/pyteal/ast/ternaryexpr.py @@ -62,9 +62,9 @@ def Ed25519Verify(data: Expr, sig: Expr, key: Expr) -> TernaryExpr: """Verify the ed25519 signature of the concatenation ("ProgData" + hash_of_current_program + data). Args: - data: The data signed by the public key. Must evalutes to bytes. + data: The data signed by the public key. Must evaluate to bytes. sig: The proposed 64-byte signature of the concatenation ("ProgData" + hash_of_current_program + data). - Must evalute to bytes. + Must evaluate to bytes. key: The 32 byte public key that produced the signature. Must evaluate to bytes. """ return TernaryExpr( @@ -77,6 +77,24 @@ def Ed25519Verify(data: Expr, sig: Expr, key: Expr) -> TernaryExpr: ) +def Ed25519Verify_Bare(data: Expr, sig: Expr, key: Expr) -> TernaryExpr: + """Verify the ed25519 signature of the data against the public key. + + Args: + data: The data signed by the public key. Must evaluate to bytes. + sig: The proposed 64-byte signature of the data. Must evaluate to bytes. + key: The 32 byte public key that produced the signature. Must evaluate to bytes. + """ + return TernaryExpr( + Op.ed25519verify_bare, + (TealType.bytes, TealType.bytes, TealType.bytes), + TealType.uint64, + data, + sig, + key, + ) + + def SetBit(value: Expr, index: Expr, newBitValue: Expr) -> TernaryExpr: """Set the bit value of an expression at a specific index. diff --git a/pyteal/ast/ternaryexpr_test.py b/pyteal/ast/ternaryexpr_test.py index 996c58635..51a01958a 100644 --- a/pyteal/ast/ternaryexpr_test.py +++ b/pyteal/ast/ternaryexpr_test.py @@ -7,6 +7,7 @@ teal4Options = pt.CompileOptions(version=4) teal5Options = pt.CompileOptions(version=5) teal6Options = pt.CompileOptions(version=6) +teal7Options = pt.CompileOptions(version=7) def test_ed25519verify(): @@ -41,6 +42,41 @@ def test_ed25519verify_invalid(): pt.Ed25519Verify(pt.Bytes("data"), pt.Bytes("sig"), pt.Int(0)) +def test_ed25519verify_bare(): + args = [pt.Bytes("data"), pt.Bytes("sig"), pt.Bytes("key")] + expr = pt.Ed25519Verify_Bare(args[0], args[1], args[2]) + assert expr.type_of() == pt.TealType.uint64 + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.byte, '"data"'), + pt.TealOp(args[1], pt.Op.byte, '"sig"'), + pt.TealOp(args[2], pt.Op.byte, '"key"'), + pt.TealOp(expr, pt.Op.ed25519verify_bare), + ] + ) + + actual, _ = expr.__teal__(teal7Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + assert actual == expected + + with pytest.raises(pt.TealInputError): + expr.__teal__(teal6Options) + + +def test_ed25519verify_bare_invalid(): + with pytest.raises(pt.TealTypeError): + pt.Ed25519Verify_Bare(pt.Int(0), pt.Bytes("sig"), pt.Bytes("key")) + + with pytest.raises(pt.TealTypeError): + pt.Ed25519Verify_Bare(pt.Bytes("data"), pt.Int(0), pt.Bytes("key")) + + with pytest.raises(pt.TealTypeError): + pt.Ed25519Verify_Bare(pt.Bytes("data"), pt.Bytes("sig"), pt.Int(0)) + + def test_set_bit_int(): args = [pt.Int(0), pt.Int(2), pt.Int(1)] expr = pt.SetBit(args[0], args[1], args[2]) diff --git a/pyteal/ir/ops.py b/pyteal/ir/ops.py index 4a040cac0..f0e217a63 100644 --- a/pyteal/ir/ops.py +++ b/pyteal/ir/ops.py @@ -183,6 +183,7 @@ def min_version(self) -> int: replace3 = OpType("replace3", Mode.Signature | Mode.Application, 7) base64_decode = OpType("base64_decode", Mode.Application | Mode.Signature, 7) json_ref = OpType("json_ref", Mode.Signature | Mode.Application, 7) + ed25519verify_bare = OpType("ed25519verify_bare", Mode.Signature | Mode.Application, 7) sha3_256 = OpType("sha3_256", Mode.Signature | Mode.Application, 7) vrf_verify = OpType("vrf_verify", Mode.Signature | Mode.Application, 7) block = OpType("block", Mode.Signature | Mode.Application, 7) From 7c24013f60150851994af29e89a0f79f054edeb5 Mon Sep 17 00:00:00 2001 From: Hang Su <87964331+ahangsu@users.noreply.github.com> Date: Wed, 20 Jul 2022 13:25:28 -0400 Subject: [PATCH 12/19] AVM Boxes Ops in Pyteal (#438) * add box ops * full support on ops * first set of test, add versioning in multi * remove some seemingly not necessary code? * update testcase * check invalid arguments * finish testcase * move stuffs to app * version check trick * verifyTealVersion apply * error message * update docs structures * period * update doc * update doc * update doc * per pr review on implementation * Update docs/state.rst Co-authored-by: Zeph Grunschlag * Update docs/state.rst Co-authored-by: Zeph Grunschlag * Update docs/state.rst Co-authored-by: Zeph Grunschlag * Update docs/state.rst Co-authored-by: Zeph Grunschlag * Update docs/state.rst Co-authored-by: Zeph Grunschlag * Update docs/state.rst Co-authored-by: Zeph Grunschlag * Update docs/state.rst Co-authored-by: Zeph Grunschlag * Update docs/state.rst Co-authored-by: Zeph Grunschlag * Update docs/state.rst Co-authored-by: Zeph Grunschlag * hex box size goes wild * Update docs/state.rst Co-authored-by: Zeph Grunschlag * warning about MBR * wording * Update docs/state.rst Co-authored-by: Zeph Grunschlag * emphasize * Update docs/state.rst Co-authored-by: Zeph Grunschlag * Update docs/state.rst Co-authored-by: Zeph Grunschlag * polishing * remove redundant box_put doc segment * per zeph pr review * use note and warning * per zeph's pr review * Update docs/state.rst Co-authored-by: Michael Diamant * creating boxes * Update docs/state.rst Co-authored-by: Michael Diamant * per pr review * table for state types Co-authored-by: Ben Guidarelli Co-authored-by: Zeph Grunschlag Co-authored-by: Michael Diamant --- docs/state.rst | 165 ++++++++++++++++++++-- pyteal/__init__.pyi | 7 + pyteal/ast/__init__.py | 17 +++ pyteal/ast/app.py | 87 +++++++++++- pyteal/ast/box.py | 226 +++++++++++++++++++++++++++++++ pyteal/ast/box_test.py | 184 +++++++++++++++++++++++++ pyteal/ast/maybe.py | 28 +++- pyteal/compiler/compiler.py | 2 +- pyteal/compiler/compiler_test.py | 2 +- pyteal/ir/ops.py | 7 + 10 files changed, 707 insertions(+), 18 deletions(-) create mode 100644 pyteal/ast/box.py create mode 100644 pyteal/ast/box_test.py diff --git a/docs/state.rst b/docs/state.rst index 71f1c638f..adb585b26 100644 --- a/docs/state.rst +++ b/docs/state.rst @@ -7,20 +7,30 @@ PyTeal can be used to write `Stateful Algorand Smart Contracts "App": Args: key: The key to write in the global application state. Must evaluate to bytes. - value: THe value to write in the global application state. Can evaluate to any type. + value: The value to write in the global application state. Can evaluate to any type. """ require_type(key, TealType.bytes) require_type(value, TealType.anytype) @@ -211,6 +220,82 @@ def globalDel(cls, key: Expr) -> "App": require_type(key, TealType.bytes) return cls(AppField.globalDel, [key]) + @classmethod + def box_create(cls, name: Expr, size: Expr) -> BoxCreate: + """ + Create a box with a given name and size. + + Args: + name: The key used to reference this box. Must evaluate to a bytes. + size: The number of bytes to reserve for this box. Must evaluate to a uint64. + """ + return BoxCreate(name, size) + + @classmethod + def box_delete(cls, name: Expr) -> BoxDelete: + """ + Deletes a box given it's name. + + Args: + name: The key the box was created with. Must evaluate to bytes. + """ + return BoxDelete(name) + + @classmethod + def box_extract(cls, name: Expr, start: Expr, length: Expr) -> BoxExtract: + """ + Extracts bytes in a box given its name, start index and stop index. + + Args: + name: The key the box was created with. Must evaluate to bytes. + start: The byte index into the box to start reading. Must evaluate to uint64. + length: The byte length into the box from start to stop reading. Must evaluate to uint64. + """ + return BoxExtract(name, start, length) + + @classmethod + def box_replace(cls, name: Expr, start: Expr, value: Expr) -> BoxReplace: + """ + Replaces bytes in a box given its name, start index, and value. + + Args: + name: The key the box was created with. Must evaluate to bytes. + start: The byte index into the box to start writing. Must evaluate to uint64. + value: The value to start writing at start index. Must evaluate to bytes. + """ + return BoxReplace(name, start, value) + + @classmethod + def box_length(cls, name: Expr) -> MaybeValue: + """ + Get the byte length of the box specified by its name. + + Args: + name: The key the box was created with. Must evaluate to bytes. + """ + return BoxLen(name) + + @classmethod + def box_get(cls, name: Expr) -> MaybeValue: + """ + Get the full contents of a box given its name. + + Args: + name: The key the box was created with. Must evaluate to bytes. + """ + return BoxGet(name) + + @classmethod + def box_put(cls, name: Expr, value: Expr) -> BoxPut: + """ + Write all contents to a box given its name. + + Args: + name: The key the box was created with. Must evaluate to bytes. + value: The value to write to the box. Must evaluate to bytes. + """ + return BoxPut(name, value) + App.__module__ = "pyteal" diff --git a/pyteal/ast/box.py b/pyteal/ast/box.py new file mode 100644 index 000000000..33b9712e3 --- /dev/null +++ b/pyteal/ast/box.py @@ -0,0 +1,226 @@ +from typing import TYPE_CHECKING +from pyteal.ast.maybe import MaybeValue +from pyteal.errors import verifyTealVersion + +from pyteal.types import TealType, require_type +from pyteal.ir import TealOp, Op, TealBlock +from pyteal.ast.expr import Expr + +if TYPE_CHECKING: + from pyteal.compiler import CompileOptions + + +class BoxCreate(Expr): + """Create a box with a given name and size.""" + + def __init__(self, name: Expr, size: Expr) -> None: + """ + Args: + name: The key used to reference this box. Must evaluate to a bytes. + size: The number of bytes to reserve for this box. Must evaluate to a uint64. + """ + + super().__init__() + require_type(name, TealType.bytes) + require_type(size, TealType.uint64) + self.name = name + self.size = size + + def __teal__(self, options: "CompileOptions"): + verifyTealVersion( + minVersion=Op.box_create.min_version, + version=options.version, + msg=f"{Op.box_create} unavailable", + ) + return TealBlock.FromOp( + options, TealOp(self, Op.box_create), self.size, self.name + ) + + def __str__(self): + return f"(box_create {self.name} {self.size})" + + def type_of(self): + return TealType.none + + def has_return(self): + return False + + +BoxCreate.__module__ = "pyteal" + + +class BoxDelete(Expr): + """Deletes a box given its name.""" + + def __init__(self, name: Expr) -> None: + """ + Args: + name: The key the box was created with. Must evaluate to bytes. + """ + super().__init__() + require_type(name, TealType.bytes) + self.name = name + + def __teal__(self, options: "CompileOptions"): + verifyTealVersion( + minVersion=Op.box_del.min_version, + version=options.version, + msg=f"{Op.box_del} unavailable", + ) + return TealBlock.FromOp(options, TealOp(self, Op.box_del), self.name) + + def __str__(self): + return f"(box_del {self.name})" + + def type_of(self): + return TealType.none + + def has_return(self): + return False + + +BoxDelete.__module__ = "pyteal" + + +class BoxReplace(Expr): + """Replaces bytes in a box given its name, start index, and value.""" + + def __init__(self, name: Expr, start: Expr, value: Expr) -> None: + """ + Args: + name: The key the box was created with. Must evaluate to bytes. + start: The byte index into the box to start writing. Must evaluate to uint64. + value: The value to start writing at start index. Must evaluate to bytes. + """ + super().__init__() + require_type(name, TealType.bytes) + require_type(start, TealType.uint64) + require_type(value, TealType.bytes) + self.name = name + self.start = start + self.value = value + + def __teal__(self, options: "CompileOptions"): + verifyTealVersion( + minVersion=Op.box_replace.min_version, + version=options.version, + msg=f"{Op.box_replace} unavailable", + ) + return TealBlock.FromOp( + options, TealOp(self, Op.box_replace), self.name, self.start, self.value + ) + + def __str__(self): + return f"(box_replace {self.name} {self.start} {self.value})" + + def type_of(self): + return TealType.none + + def has_return(self): + return False + + +BoxReplace.__module__ = "pyteal" + + +class BoxExtract(Expr): + """Extracts bytes in a box given its name, start index and stop index.""" + + def __init__(self, name: Expr, start: Expr, length: Expr) -> None: + """ + Args: + name: The key the box was created with. Must evaluate to bytes. + start: The byte index into the box to start reading. Must evaluate to uint64. + length: The byte length into the box from start to stop reading. Must evaluate to uint64. + """ + + super().__init__() + require_type(name, TealType.bytes) + require_type(start, TealType.uint64) + require_type(length, TealType.uint64) + self.name = name + self.start = start + self.length = length + + def __teal__(self, options: "CompileOptions"): + verifyTealVersion( + minVersion=Op.box_extract.min_version, + version=options.version, + msg=f"{Op.box_extract} unavailable", + ) + return TealBlock.FromOp( + options, TealOp(self, Op.box_extract), self.name, self.start, self.length + ) + + def __str__(self): + return f"(box_extract {self.name} {self.start} {self.length})" + + def type_of(self): + return TealType.bytes + + def has_return(self): + return False + + +BoxExtract.__module__ = "pyteal" + + +def BoxLen(name: Expr) -> MaybeValue: + """ + Get the byte length of the box specified by its name. + + Args: + name: The key the box was created with. Must evaluate to bytes. + """ + require_type(name, TealType.bytes) + return MaybeValue(Op.box_len, TealType.uint64, args=[name]) + + +def BoxGet(name: Expr) -> MaybeValue: + """ + Get the full contents of a box given its name. + + Args: + name: The key the box was created with. Must evaluate to bytes. + """ + require_type(name, TealType.bytes) + return MaybeValue(Op.box_get, TealType.bytes, args=[name]) + + +class BoxPut(Expr): + """Write all contents to a box given its name.""" + + def __init__(self, name: Expr, value: Expr) -> None: + """ + Args: + name: The key the box was created with. Must evaluate to bytes. + value: The value to write to the box. Must evaluate to bytes. + """ + + super().__init__() + require_type(name, TealType.bytes) + require_type(value, TealType.bytes) + self.name = name + self.value = value + + def __teal__(self, options: "CompileOptions"): + verifyTealVersion( + minVersion=Op.box_put.min_version, + version=options.version, + msg=f"{Op.box_put} unavailable", + ) + return TealBlock.FromOp( + options, TealOp(self, Op.box_put), self.name, self.value + ) + + def __str__(self): + return f"(box_put {self.name})" + + def type_of(self): + return TealType.none + + def has_return(self): + return False + + +BoxPut.__module__ = "pyteal" diff --git a/pyteal/ast/box_test.py b/pyteal/ast/box_test.py new file mode 100644 index 000000000..87968ce2f --- /dev/null +++ b/pyteal/ast/box_test.py @@ -0,0 +1,184 @@ +from typing import Callable, Tuple + +import pytest +import pyteal as pt + +teal7Options = pt.CompileOptions(version=7) +teal8Options = pt.CompileOptions(version=8) + +POSITIVE_TEST_CASES: list[Tuple[pt.Expr, pt.TealType]] = [ + (pt.BoxCreate(pt.Bytes("box"), pt.Int(10)), pt.TealType.none), + (pt.BoxDelete(pt.Bytes("box")), pt.TealType.none), + (pt.BoxExtract(pt.Bytes("box"), pt.Int(2), pt.Int(4)), pt.TealType.bytes), + ( + pt.BoxReplace(pt.Bytes("box"), pt.Int(3), pt.Bytes("replace")), + pt.TealType.none, + ), + (pt.BoxLen(pt.Bytes("box")), pt.TealType.none), + (pt.BoxGet(pt.Bytes("box")), pt.TealType.none), + (pt.BoxPut(pt.Bytes("box"), pt.Bytes("goonery")), pt.TealType.none), +] + + +@pytest.mark.parametrize("test_case, test_case_type", POSITIVE_TEST_CASES) +def test_compile_version_and_type(test_case, test_case_type): + with pytest.raises(pt.TealInputError): + test_case.__teal__(teal7Options) + + test_case.__teal__(teal8Options) + assert test_case.type_of() == test_case_type + assert not test_case.has_return() + + return + + +INVALID_TEST_CASES: list[Tuple[list[pt.Expr], type | Callable[..., pt.MaybeValue]]] = [ + ([pt.Bytes("box"), pt.Bytes("ten")], pt.BoxCreate), + ([pt.Int(0xB0B), pt.Int(10)], pt.BoxCreate), + ([pt.Int(0xA11CE)], pt.BoxDelete), + ([pt.Bytes("box"), pt.Int(2), pt.Bytes("three")], pt.BoxExtract), + ([pt.Bytes("box"), pt.Int(2), pt.Int(0x570FF)], pt.BoxReplace), + ([pt.Int(12)], pt.BoxLen), + ([pt.Int(45)], pt.BoxGet), + ([pt.Bytes("box"), pt.Int(123)], pt.BoxPut), +] + + +@pytest.mark.parametrize("test_args, test_expr", INVALID_TEST_CASES) +def test_box_invalid_args(test_args, test_expr): + with pytest.raises(pt.TealTypeError): + test_expr(*test_args) + + +def test_box_create_compile(): + name_arg: pt.Expr = pt.Bytes("eineName") + size_arg: pt.Expr = pt.Int(10) + expr: pt.Expr = pt.BoxCreate(name_arg, size_arg) + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(size_arg, pt.Op.int, 10), + pt.TealOp(name_arg, pt.Op.byte, '"eineName"'), + pt.TealOp(expr, pt.Op.box_create), + ] + ) + actual, _ = expr.__teal__(teal8Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + assert expected == actual + + +def test_box_delete_compile(): + name_arg: pt.Expr = pt.Bytes("eineName") + expr: pt.Expr = pt.BoxDelete(name_arg) + + expected = pt.TealSimpleBlock( + [pt.TealOp(name_arg, pt.Op.byte, '"eineName"'), pt.TealOp(expr, pt.Op.box_del)] + ) + actual, _ = expr.__teal__(teal8Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + assert expected == actual + + +def test_box_extract(): + name_arg: pt.Expr = pt.Bytes("eineName") + srt_arg: pt.Expr = pt.Int(10) + end_arg: pt.Expr = pt.Int(15) + expr: pt.Expr = pt.BoxExtract(name_arg, srt_arg, end_arg) + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(name_arg, pt.Op.byte, '"eineName"'), + pt.TealOp(srt_arg, pt.Op.int, 10), + pt.TealOp(end_arg, pt.Op.int, 15), + pt.TealOp(expr, pt.Op.box_extract), + ] + ) + actual, _ = expr.__teal__(teal8Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + assert expected == actual + + +def test_box_replace(): + name_arg: pt.Expr = pt.Bytes("eineName") + srt_arg: pt.Expr = pt.Int(10) + replace_arg: pt.Expr = pt.Bytes("replace-str") + expr: pt.Expr = pt.BoxReplace(name_arg, srt_arg, replace_arg) + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(name_arg, pt.Op.byte, '"eineName"'), + pt.TealOp(srt_arg, pt.Op.int, 10), + pt.TealOp(replace_arg, pt.Op.byte, '"replace-str"'), + pt.TealOp(expr, pt.Op.box_replace), + ] + ) + actual, _ = expr.__teal__(teal8Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + assert expected == actual + + +def test_box_length(): + name_arg: pt.Expr = pt.Bytes("eineName") + expr: pt.MaybeValue = pt.BoxLen(name_arg) + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(name_arg, pt.Op.byte, '"eineName"'), + pt.TealOp(expr, pt.Op.box_len), + pt.TealOp(expr.output_slots[1].store(), pt.Op.store, expr.output_slots[1]), + pt.TealOp(expr.output_slots[0].store(), pt.Op.store, expr.output_slots[0]), + ] + ) + actual, _ = expr.__teal__(teal8Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert expected == actual + + +def test_box_get(): + name_arg: pt.Expr = pt.Bytes("eineName") + expr: pt.MaybeValue = pt.BoxGet(name_arg) + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(name_arg, pt.Op.byte, '"eineName"'), + pt.TealOp(expr, pt.Op.box_get), + pt.TealOp(expr.output_slots[1].store(), pt.Op.store, expr.output_slots[1]), + pt.TealOp(expr.output_slots[0].store(), pt.Op.store, expr.output_slots[0]), + ] + ) + actual, _ = expr.__teal__(teal8Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert expected == actual + + +def test_box_put(): + name_arg: pt.Expr = pt.Bytes("eineName") + put_arg: pt.Expr = pt.Bytes("put-str") + expr: pt.Expr = pt.BoxPut(name_arg, put_arg) + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(name_arg, pt.Op.byte, '"eineName"'), + pt.TealOp(put_arg, pt.Op.byte, '"put-str"'), + pt.TealOp(expr, pt.Op.box_put), + ] + ) + actual, _ = expr.__teal__(teal8Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + assert expected == actual diff --git a/pyteal/ast/maybe.py b/pyteal/ast/maybe.py index 43846384d..ebe3d03e7 100644 --- a/pyteal/ast/maybe.py +++ b/pyteal/ast/maybe.py @@ -1,11 +1,15 @@ -from typing import List, Union - -from pyteal.ast.multi import MultiValue +from typing import List, Union, TYPE_CHECKING +from pyteal.errors import verifyTealVersion from pyteal.types import TealType from pyteal.ir import Op + from pyteal.ast.expr import Expr from pyteal.ast.scratch import ScratchLoad, ScratchSlot +from pyteal.ast.multi import MultiValue + +if TYPE_CHECKING: + from pyteal.compiler import CompileOptions class MaybeValue(MultiValue): @@ -17,7 +21,7 @@ def __init__( type: TealType, *, immediate_args: List[Union[int, str]] = None, - args: List[Expr] = None + args: List[Expr] = None, ): """Create a new MaybeValue. @@ -27,8 +31,22 @@ def __init__( immediate_args (optional): Immediate arguments for the op. Defaults to None. args (optional): Stack arguments for the op. Defaults to None. """ + + def local_version_check(option: "CompileOptions"): + verifyTealVersion( + minVersion=op.min_version, + version=option.version, + msg=f"{op.value} unavailable", + ) + types = [type, TealType.uint64] - super().__init__(op, types, immediate_args=immediate_args, args=args) + super().__init__( + op, + types, + immediate_args=immediate_args, + args=args, + compile_check=local_version_check, + ) def hasValue(self) -> ScratchLoad: """Check if the value exists. diff --git a/pyteal/compiler/compiler.py b/pyteal/compiler/compiler.py index 2a68be57a..faf8717bd 100644 --- a/pyteal/compiler/compiler.py +++ b/pyteal/compiler/compiler.py @@ -25,7 +25,7 @@ ) from pyteal.compiler.constants import createConstantBlocks -MAX_TEAL_VERSION = 7 +MAX_TEAL_VERSION = 8 MIN_TEAL_VERSION = 2 DEFAULT_TEAL_VERSION = MIN_TEAL_VERSION diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index b3e528506..014607465 100644 --- a/pyteal/compiler/compiler_test.py +++ b/pyteal/compiler/compiler_test.py @@ -139,7 +139,7 @@ def test_compile_version_invalid(): pt.compileTeal(expr, pt.Mode.Signature, version=1) # too small with pytest.raises(pt.TealInputError): - pt.compileTeal(expr, pt.Mode.Signature, version=8) # too large + pt.compileTeal(expr, pt.Mode.Signature, version=9) # too large with pytest.raises(pt.TealInputError): pt.compileTeal(expr, pt.Mode.Signature, version=2.0) # decimal diff --git a/pyteal/ir/ops.py b/pyteal/ir/ops.py index f0e217a63..d87c76250 100644 --- a/pyteal/ir/ops.py +++ b/pyteal/ir/ops.py @@ -187,6 +187,13 @@ def min_version(self) -> int: sha3_256 = OpType("sha3_256", Mode.Signature | Mode.Application, 7) vrf_verify = OpType("vrf_verify", Mode.Signature | Mode.Application, 7) block = OpType("block", Mode.Signature | Mode.Application, 7) + box_create = OpType("box_create", Mode.Application, 8) + box_extract = OpType("box_extract", Mode.Application, 8) + box_replace = OpType("box_replace", Mode.Application, 8) + box_del = OpType("box_del", Mode.Application, 8) + box_len = OpType("box_len", Mode.Application, 8) + box_get = OpType("box_get", Mode.Application, 8) + box_put = OpType("box_put", Mode.Application, 8) # fmt: on From ba260e6c337933b017a708b952eaf2f7842b1e03 Mon Sep 17 00:00:00 2001 From: Hang Su <87964331+ahangsu@users.noreply.github.com> Date: Fri, 22 Jul 2022 13:33:24 -0400 Subject: [PATCH 13/19] Merge Teal7 to AVM8, and consolidate Teal to AVM versioning (#470) * swapping base64 modes to match the rest (#446) * Merge master into teal7 (#450) * AVM 7: Address integration branch feedback (#452) * Add Execute Method (#444) * adding execute method to allow omission of begin/submit for common use case * exec docstring * update testcase Co-authored-by: Hang Su * Merge branch 'master' into teal7 (#463) * fix misspelling of uint (#431) * fix misspelling of uint * Clarify minimum Python version management docs (#435) * Foreign prefix on App and Asset arrays (#440) * replacing foreignapps with applications * fix assets as well * Add Execute Method (#444) * adding execute method to allow omission of begin/submit for common use case * exec docstring * update testcase Co-authored-by: Hang Su Co-authored-by: Ben Guidarelli Co-authored-by: Hang Su * Consolidate TEAL and AVM versioning (#441) * fix misspelling of uint (#431) * fix misspelling of uint * Clarify minimum Python version management docs (#435) * Convert TEAL version references to program version by hand * Replace `teal#Options` with `avm#Options` * Deprecate `*_TEAL_VERSION` in favor of `*_PROGRAM_VERSION` * Fix docs typo Co-authored-by: Michael Diamant * Minor `versions.rst` changes * Fix `verifyTealVersion` in new opcode files * Fix linter errors * Fix language discrepencies introduced by the merge * Remove incorrect avm replacement * Fix inconsistent language introduced by merge Co-authored-by: Ben Guidarelli Co-authored-by: Michael Diamant * max program version Co-authored-by: Ben Guidarelli Co-authored-by: Michael Diamant Co-authored-by: Jacob Daitzman --- docs/accessing_transaction_field.rst | 184 +++++++++++++------------- docs/arithmetic_expression.rst | 2 +- docs/byte_expression.rst | 2 +- docs/control_structures.rst | 8 +- docs/crypto.rst | 4 +- docs/loading_group_transaction.rst | 2 +- docs/versions.rst | 19 +-- examples/signature/factorizer_game.py | 2 +- pyteal/__init__.py | 6 + pyteal/__init__.pyi | 6 + pyteal/ast/acct_test.py | 12 +- pyteal/ast/app_test.py | 24 ++-- pyteal/ast/arg.py | 8 +- pyteal/ast/arg_test.py | 12 +- pyteal/ast/assert_test.py | 12 +- pyteal/ast/asset_test.py | 60 ++++----- pyteal/ast/base64decode.py | 12 +- pyteal/ast/base64decode_test.py | 12 +- pyteal/ast/binaryexpr.py | 50 +++---- pyteal/ast/binaryexpr_test.py | 170 ++++++++++++------------ pyteal/ast/block.py | 4 +- pyteal/ast/block_test.py | 12 +- pyteal/ast/box.py | 12 +- pyteal/ast/box_test.py | 22 +-- pyteal/ast/ecdsa.py | 16 ++- pyteal/ast/ecdsa_test.py | 105 ++++++++------- pyteal/ast/gaid.py | 8 +- pyteal/ast/gaid_test.py | 12 +- pyteal/ast/gitxn.py | 10 +- pyteal/ast/gitxn_test.py | 12 +- pyteal/ast/gload.py | 8 +- pyteal/ast/gload_test.py | 20 +-- pyteal/ast/global_.py | 14 +- pyteal/ast/global_test.py | 50 +++---- pyteal/ast/gtxn.py | 16 ++- pyteal/ast/gtxn_test.py | 6 +- pyteal/ast/itxn.py | 43 ++++-- pyteal/ast/itxn_test.py | 94 ++++++++----- pyteal/ast/jsonref.py | 18 +-- pyteal/ast/jsonref_test.py | 16 +-- pyteal/ast/maybe.py | 4 +- pyteal/ast/opup.py | 4 +- pyteal/ast/replace.py | 8 +- pyteal/ast/replace_test.py | 16 +-- pyteal/ast/return_.py | 6 +- pyteal/ast/subroutine.py | 6 +- pyteal/ast/substring.py | 20 +-- pyteal/ast/substring_test.py | 46 +++---- pyteal/ast/ternaryexpr.py | 12 +- pyteal/ast/ternaryexpr_test.py | 32 ++--- pyteal/ast/txn.py | 54 +++----- pyteal/ast/txn_test.py | 2 - pyteal/ast/unaryexpr.py | 22 +-- pyteal/ast/unaryexpr_test.py | 60 ++++----- pyteal/ast/widemath.py | 4 +- pyteal/compiler/__init__.py | 6 + pyteal/compiler/compiler.py | 32 +++-- pyteal/compiler/subroutines.py | 4 +- pyteal/errors.py | 6 +- pyteal/ir/ops.py | 2 +- 60 files changed, 764 insertions(+), 687 deletions(-) diff --git a/docs/accessing_transaction_field.rst b/docs/accessing_transaction_field.rst index 64c5f0b65..cf13f99a6 100644 --- a/docs/accessing_transaction_field.rst +++ b/docs/accessing_transaction_field.rst @@ -19,56 +19,54 @@ Fields by Transaction Type Common Fields ....................................................... -================================================================================ ========================= ================ ============================================================================ -Operator Type Min TEAL Version Notes -================================================================================ ========================= ================ ============================================================================ +================================================================================ ========================= ==================== ============================================================================ +Operator Type Min Program Version Notes +================================================================================ ========================= ==================== ============================================================================ :any:`Txn.type() ` :code:`TealType.bytes` 2 -:any:`Txn.type_enum() ` :code:`TealType.uint64` 2 see table below -:any:`Txn.sender() ` :code:`TealType.bytes` 2 32 byte address -:any:`Txn.fee() ` :code:`TealType.uint64` 2 in microAlgos -:any:`Txn.first_valid() ` :code:`TealType.uint64` 2 round number -:any:`Txn.first_valid_time() ` :code:`TealType.uint64` 7 UNIX timestamp of block before :code:`Txn.first_valid()`. Fails if negative -:any:`Txn.last_valid() ` :code:`TealType.uint64` 2 round number -:any:`Txn.note() ` :code:`TealType.bytes` 2 transaction note in bytes -:any:`Txn.lease() ` :code:`TealType.bytes` 2 transaction lease in bytes -:any:`Txn.group_index() ` :code:`TealType.uint64` 2 position of this transaction within a transaction group, starting at 0 -:any:`Txn.tx_id() ` :code:`TealType.bytes` 2 the computed ID for this transaction, 32 bytes -:any:`Txn.rekey_to() ` :code:`TealType.bytes` 2 32 byte address -================================================================================ ========================= ================ ============================================================================ +:any:`Txn.type_enum() ` :code:`TealType.uint64` 2 see table below +:any:`Txn.sender() ` :code:`TealType.bytes` 2 32 byte address +:any:`Txn.fee() ` :code:`TealType.uint64` 2 in microAlgos +:any:`Txn.first_valid() ` :code:`TealType.uint64` 2 round number +:any:`Txn.first_valid_time() ` :code:`TealType.uint64` 7 UNIX timestamp of block before :code:`Txn.first_valid()`. Fails if negative +:any:`Txn.last_valid() ` :code:`TealType.uint64` 2 round number +:any:`Txn.note() ` :code:`TealType.bytes` 2 transaction note in bytes +:any:`Txn.lease() ` :code:`TealType.bytes` 2 transaction lease in bytes +:any:`Txn.group_index() ` :code:`TealType.uint64` 2 position of this transaction within a transaction group, starting at 0 +:any:`Txn.tx_id() ` :code:`TealType.bytes` 2 the computed ID for this transaction, 32 bytes +:any:`Txn.rekey_to() ` :code:`TealType.bytes` 2 32 byte address +================================================================================ ========================= ==================== ============================================================================ Application Call ....................................................... -==================================================================================== ========================= ================ ============================================================================ -Operator Type Min TEAL Version Notes -==================================================================================== ========================= ================ ============================================================================ +==================================================================================== ========================= ==================== ============================================================================ +Operator Type Min Program Version Notes +==================================================================================== ========================= ==================== ============================================================================ :any:`Txn.application_id() ` :code:`TealType.uint64` 2 :any:`Txn.on_completion() ` :code:`TealType.uint64` 2 :any:`Txn.approval_program() ` :code:`TealType.bytes` 2 -:any:`Txn.global_num_uints() ` :code:`TealType.uint64` 3 Maximum global integers in app schema -:any:`Txn.global_num_byte_slices() ` :code:`TealType.uint64` 3 Maximum global byte strings in app schema -:any:`Txn.local_num_uints() ` :code:`TealType.uint64` 3 Maximum local integers in app schema -:any:`Txn.local_num_byte_slices() ` :code:`TealType.uint64` 3 Maximum local byte strings in app schema -:any:`Txn.accounts ` :code:`TealType.bytes[]` 2 Array of accounts available to the application -:any:`Txn.assets ` :code:`TealType.uint64[]` 3 Array of assets available to the application -:any:`Txn.applications ` :code:`TealType.uint64[]` 3 Array of applications +:any:`Txn.global_num_uints() ` :code:`TealType.uint64` 3 Maximum global integers in app schema +:any:`Txn.global_num_byte_slices() ` :code:`TealType.uint64` 3 Maximum global byte strings in app schema +:any:`Txn.local_num_uints() ` :code:`TealType.uint64` 3 Maximum local integers in app schema +:any:`Txn.local_num_byte_slices() ` :code:`TealType.uint64` 3 Maximum local byte strings in app schema +:any:`Txn.accounts ` :code:`TealType.bytes[]` 2 Array of accounts available to the application +:any:`Txn.assets ` :code:`TealType.uint64[]` 3 Array of assets available to the application +:any:`Txn.applications ` :code:`TealType.uint64[]` 3 Array of applications :any:`Txn.clear_state_program() ` :code:`TealType.bytes` 2 -:any:`Txn.extra_program_pages() ` :code:`TealType.uint64` 4 Number of extra program pages for app -:any:`Txn.application_args ` :code:`TealType.bytes[]` 2 Array of application arguments -:any:`Txn.created_application_id() ` :code:`TealType.uint64` 5 The ID of the newly created application in this transaction. In v5, only valid on inner transactions. >= v6 works with top-level and inner transactions. -:any:`Txn.logs ` :code:`TealType.bytes[]` 5 Array of application logged items. In v5, only valid on inner transactions. >= v6 works with top-level and inner transactions. -:any:`Txn.last_log() ` :code:`TealType.bytes[]` 6 The last message emitted. Empty bytes if none were emitted. Application mode only. -:any:`Txn.approval_program_pages() ` :code:`TealType.bytes[]` 7 The pages of the approval program as an array -:any:`Txn.num_approval_program_pages() ` :code:`TealType.uint64` 7 The number of approval program pages -:any:`Txn.clear_state_program_pages() ` :code:`TealType.bytes[]` 7 The pages of a clear state program as an array -:any:`Txn.num_clear_state_program_pages() ` :code:`TealType.uint64` 7 The number of clear state program pages -==================================================================================== ========================= ================ ============================================================================ +:any:`Txn.extra_program_pages() ` :code:`TealType.uint64` 4 Number of extra program pages for app +:any:`Txn.application_args ` :code:`TealType.bytes[]` 2 Array of application arguments +:any:`Txn.created_application_id() ` :code:`TealType.uint64` 5 The ID of the newly created application in this transaction. In v5, only valid on inner transactions. >= v6 works with top-level and inner transactions. +:any:`Txn.logs ` :code:`TealType.bytes[]` 5 Array of application logged items. In v5, only valid on inner transactions. >= v6 works with top-level and inner transactions. +:any:`Txn.last_log() ` :code:`TealType.bytes` 6 The last message emitted. Empty bytes if none were emitted. Application mode only. +:any:`Txn.approval_program_pages() ` :code:`TealType.bytes[]` 7 The pages of the approval program as an array +:any:`Txn.clear_state_program_pages() ` :code:`TealType.bytes[]` 7 The pages of a clear state program as an array +==================================================================================== ========================= ==================== ============================================================================ Asset Config ....................................................... -================================================================================ ========================= ================ ============================================================================ -Operator Type Min TEAL Version Notes -================================================================================ ========================= ================ ============================================================================ -:any:`Txn.config_asset() ` :code:`TealType.uint64` 2 ID of asset being configured +================================================================================ ========================= ==================== ============================================================================ +Operator Type Min Program Version Notes +================================================================================ ========================= ==================== ============================================================================ +:any:`Txn.config_asset() ` :code:`TealType.uint64` 2 ID of asset being configured :any:`Txn.config_asset_total() ` :code:`TealType.uint64` 2 :any:`Txn.config_asset_decimals() ` :code:`TealType.uint64` 2 :any:`Txn.config_asset_default_frozen() ` :code:`TealType.uint64` 2 @@ -76,58 +74,58 @@ Operator :any:`Txn.config_asset_name() ` :code:`TealType.bytes` 2 :any:`Txn.config_asset_url() ` :code:`TealType.bytes` 2 :any:`Txn.config_asset_metadata_hash() ` :code:`TealType.bytes` 2 -:any:`Txn.config_asset_manager() ` :code:`TealType.bytes` 2 32 byte address -:any:`Txn.config_asset_reserve() ` :code:`TealType.bytes` 2 32 byte address -:any:`Txn.config_asset_freeze() ` :code:`TealType.bytes` 2 32 byte address -:any:`Txn.config_asset_clawback() ` :code:`TealType.bytes` 2 32 byte address -:any:`Txn.created_asset_id() ` :code:`TealType.uint64` 5 The ID of the newly created asset in this transaction. In v5, only valid on inner transactions. >= v6 works with top-level and inner transactions. -================================================================================ ========================= ================ ============================================================================ +:any:`Txn.config_asset_manager() ` :code:`TealType.bytes` 2 32 byte address +:any:`Txn.config_asset_reserve() ` :code:`TealType.bytes` 2 32 byte address +:any:`Txn.config_asset_freeze() ` :code:`TealType.bytes` 2 32 byte address +:any:`Txn.config_asset_clawback() ` :code:`TealType.bytes` 2 32 byte address +:any:`Txn.created_asset_id() ` :code:`TealType.uint64` 5 The ID of the newly created asset in this transaction. In v5, only valid on inner transactions. >= v6 works with top-level and inner transactions. +================================================================================ ========================= ==================== ============================================================================ Asset Freeze ....................................................... -================================================================================ ========================= ================ ============================================================================ -Operator Type Min TEAL Version Notes -================================================================================ ========================= ================ ============================================================================ +================================================================================ ========================= ==================== ============================================================================ +Operator Type Min Program Version Notes +================================================================================ ========================= ==================== ============================================================================ :any:`Txn.freeze_asset() ` :code:`TealType.uint64` 2 -:any:`Txn.freeze_asset_account() ` :code:`TealType.bytes` 2 32 byte address +:any:`Txn.freeze_asset_account() ` :code:`TealType.bytes` 2 32 byte address :any:`Txn.freeze_asset_frozen() ` :code:`TealType.uint64` 2 -================================================================================ ========================= ================ ============================================================================ +================================================================================ ========================= ==================== ============================================================================ Asset Transfer ....................................................... -================================================================================ ========================= ================ ============================================================================ -Operator Type Min TEAL Version Notes -================================================================================ ========================= ================ ============================================================================ -:any:`Txn.xfer_asset() ` :code:`TealType.uint64` 2 ID of asset being transferred -:any:`Txn.asset_amount() ` :code:`TealType.uint64` 2 value in Asset's units -:any:`Txn.asset_sender() ` :code:`TealType.bytes` 2 32 byte address, causes clawback of all value if sender is the clawback -:any:`Txn.asset_receiver() ` :code:`TealType.bytes` 2 32 byte address -:any:`Txn.asset_close_to() ` :code:`TealType.bytes` 2 32 byte address -================================================================================ ========================= ================ ============================================================================ +================================================================================ ========================= ==================== ============================================================================ +Operator Type Min Program Version Notes +================================================================================ ========================= ==================== ============================================================================ +:any:`Txn.xfer_asset() ` :code:`TealType.uint64` 2 ID of asset being transferred +:any:`Txn.asset_amount() ` :code:`TealType.uint64` 2 value in Asset's units +:any:`Txn.asset_sender() ` :code:`TealType.bytes` 2 32 byte address, causes clawback of all value if sender is the clawback +:any:`Txn.asset_receiver() ` :code:`TealType.bytes` 2 32 byte address +:any:`Txn.asset_close_to() ` :code:`TealType.bytes` 2 32 byte address +================================================================================ ========================= ==================== ============================================================================ Key Registration ....................................................... -================================================================================ ========================= ================ ============================================================================ -Operator Type Min TEAL Version Notes -================================================================================ ========================= ================ ============================================================================ -:any:`Txn.vote_pk() ` :code:`TealType.bytes` 2 32 byte address -:any:`Txn.selection_pk() ` :code:`TealType.bytes` 2 32 byte address -:any:`Txn.state_proof_pk ` :code:`TealType.bytes` 6 64 byte state proof public key commitment. +================================================================================ ========================= ==================== ============================================================================ +Operator Type Min Program Version Notes +================================================================================ ========================= ==================== ============================================================================ +:any:`Txn.vote_pk() ` :code:`TealType.bytes` 2 32 byte address +:any:`Txn.selection_pk() ` :code:`TealType.bytes` 2 32 byte address +:any:`Txn.state_proof_pk ` :code:`TealType.bytes` 6 64 byte state proof public key commitment. :any:`Txn.vote_first() ` :code:`TealType.uint64` 2 :any:`Txn.vote_last() ` :code:`TealType.uint64` 2 :any:`Txn.vote_key_dilution() ` :code:`TealType.uint64` 2 -:any:`Txn.nonparticipation() ` :code:`TealType.uint64` 5 Marks an account nonparticipating for rewards -================================================================================ ========================= ================ ============================================================================ +:any:`Txn.nonparticipation() ` :code:`TealType.uint64` 5 Marks an account nonparticipating for rewards +================================================================================ ========================= ==================== ============================================================================ Payment ....................................................... -================================================================================ ========================= ================ ============================================================================ -Operator Type Min TEAL Version Notes -================================================================================ ========================= ================ ============================================================================ -:any:`Txn.receiver() ` :code:`TealType.bytes` 2 32 byte address -:any:`Txn.amount() ` :code:`TealType.uint64` 2 in microAlgos -:any:`Txn.close_remainder_to() ` :code:`TealType.bytes` 2 32 byte address -================================================================================ ========================= ================ ============================================================================ +================================================================================ ========================= ==================== ============================================================================ +Operator Type Min Program Version Notes +================================================================================ ========================= ==================== ============================================================================ +:any:`Txn.receiver() ` :code:`TealType.bytes` 2 32 byte address +:any:`Txn.amount() ` :code:`TealType.uint64` 2 in microAlgos +:any:`Txn.close_remainder_to() ` :code:`TealType.bytes` 2 32 byte address +================================================================================ ========================= ==================== ============================================================================ Transaction Types ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -162,7 +160,7 @@ items can be accessed using bracket notation. For example: Txn.application_args[0] # get the first application argument Txn.application_args[1] # get the second application argument - # as of TEAL v5, PyTeal expressions can be used to dynamically index into array properties as well + # as of AVM v5, PyTeal expressions can be used to dynamically index into array properties as well Txn.application_args[Txn.application_args.length() - Int(1)] # get the last application argument .. _txn_special_case_arrays: @@ -197,7 +195,7 @@ available on the elements of :code:`Gtxn`. For example: Gtxn[0].sender() # get the sender of the first transaction in the atomic transfer group Gtxn[1].receiver() # get the receiver of the second transaction in the atomic transfer group - # as of TEAL v3, PyTeal expressions can be used to dynamically index into Gtxn as well + # as of AVM v3, PyTeal expressions can be used to dynamically index into Gtxn as well Gtxn[Txn.group_index() - Int(1)].sender() # get the sender of the previous transaction in the atomic transfer group :code:`Gtxn` is zero-indexed and the maximum size of an atomic transfer group is 16. The size of the @@ -211,7 +209,7 @@ Inner Transactions ------------------ .. note:: - Inner transactions are only available in TEAL version 5 or higher. + Inner transactions are only available in AVM version 5 or higher. Inner transactions can be created and submitted with expressions from the :any:`InnerTxnBuilder` class. The properties of the most recently submitted inner transaction can be accessed using the :any:`InnerTxn` @@ -224,19 +222,19 @@ Global Parameters Information about the current state of the blockchain can be obtained using the following :any:`Global` expressions: -=========================================== ======================= ================ ============================================================= -Operator Type Min TEAL Version Notes -=========================================== ======================= ================ ============================================================= -:any:`Global.min_txn_fee()` :code:`TealType.uint64` 2 in microAlgos -:any:`Global.min_balance()` :code:`TealType.uint64` 2 in microAlgos -:any:`Global.max_txn_life()` :code:`TealType.uint64` 2 number of rounds -:any:`Global.zero_address()` :code:`TealType.bytes` 2 32 byte address of all zero bytes -:any:`Global.group_size()` :code:`TealType.uint64` 2 number of txns in this atomic transaction group, at least 1 -:any:`Global.logic_sig_version()` :code:`TealType.uint64` 2 the maximum supported TEAL version -:any:`Global.round()` :code:`TealType.uint64` 2 the current round number -:any:`Global.latest_timestamp()` :code:`TealType.uint64` 2 the latest confirmed block UNIX timestamp -:any:`Global.current_application_id()` :code:`TealType.uint64` 2 the ID of the current application executing -:any:`Global.creator_address()` :code:`TealType.bytes` 3 32 byte address of the creator of the current application -:any:`Global.current_application_address()` :code:`TealType.bytes` 5 32 byte address of the current application controlled account -:any:`Global.group_id()` :code:`TealType.bytes` 5 32 byte ID of the current transaction group -=========================================== ======================= ================ ============================================================= +=========================================== ======================= ==================== ============================================================= +Operator Type Min Program Version Notes +=========================================== ======================= ==================== ============================================================= +:any:`Global.min_txn_fee()` :code:`TealType.uint64` 2 in microAlgos +:any:`Global.min_balance()` :code:`TealType.uint64` 2 in microAlgos +:any:`Global.max_txn_life()` :code:`TealType.uint64` 2 number of rounds +:any:`Global.zero_address()` :code:`TealType.bytes` 2 32 byte address of all zero bytes +:any:`Global.group_size()` :code:`TealType.uint64` 2 number of txns in this atomic transaction group, at least 1 +:any:`Global.logic_sig_version()` :code:`TealType.uint64` 2 the maximum supported program version +:any:`Global.round()` :code:`TealType.uint64` 2 the current round number +:any:`Global.latest_timestamp()` :code:`TealType.uint64` 2 the latest confirmed block UNIX timestamp +:any:`Global.current_application_id()` :code:`TealType.uint64` 2 the ID of the current application executing +:any:`Global.creator_address()` :code:`TealType.bytes` 3 32 byte address of the creator of the current application +:any:`Global.current_application_address()` :code:`TealType.bytes` 5 32 byte address of the current application controlled account +:any:`Global.group_id()` :code:`TealType.bytes` 5 32 byte ID of the current transaction group +=========================================== ======================= ==================== ============================================================= diff --git a/docs/arithmetic_expression.rst b/docs/arithmetic_expression.rst index daa4619c6..a9d0f75aa 100644 --- a/docs/arithmetic_expression.rst +++ b/docs/arithmetic_expression.rst @@ -49,7 +49,7 @@ The associativity and precedence of the overloaded Python arithmetic operators a Byteslice Arithmetic -------------------- -Byteslice arithemetic is available for Teal V4 and above. +Byteslice arithmetic is available for AVM V4 and above. Byteslice arithmetic operators allow up to 512-bit arithmetic. In PyTeal, byteslice arithmetic expressions include :code:`TealType.Bytes` values as arguments (with the exception of :code:`BytesZero`) diff --git a/docs/byte_expression.rst b/docs/byte_expression.rst index acc535576..b457e756b 100644 --- a/docs/byte_expression.rst +++ b/docs/byte_expression.rst @@ -48,7 +48,7 @@ Extract ~~~~~~~ .. note:: - :code:`Extract` is only available in TEAL version 5 or higher. + :code:`Extract` is only available in program version 5 or higher. The :any:`Extract` expression can extract part of a byte slice given the start index and length. For example: diff --git a/docs/control_structures.rst b/docs/control_structures.rst index a62bfec2c..b464b1535 100644 --- a/docs/control_structures.rst +++ b/docs/control_structures.rst @@ -11,7 +11,7 @@ Exiting the Program: :code:`Approve` and :code:`Reject` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. note:: - The :code:`Approve` and :code:`Reject` expressions are only available in TEAL version 4 or higher. + The :code:`Approve` and :code:`Reject` expressions are only available in program version 4 or higher. Prior to this, :code:`Return(Int(1))` is equivalent to :code:`Approve()` and :code:`Return(Int(0))` is equivalent to :code:`Reject()`. @@ -166,7 +166,7 @@ Looping: :code:`While` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. note:: - This expression is only available in TEAL version 4 or higher. + This expression is only available in program version 4 or higher. The :any:`While` expression can be used to create simple loops in PyTeal. The syntax of :code:`While` is: @@ -203,7 +203,7 @@ Looping: :code:`For` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. note:: - This expression is only available in TEAL version 4 or higher. + This expression is only available in program version 4 or higher. Similar to :code:`While`, the :any:`For` expression can also be used to create loops in PyTeal. The syntax of :code:`For` is: @@ -294,7 +294,7 @@ Subroutines ~~~~~~~~~~~ .. note:: - Subroutines are only available in TEAL version 4 or higher. + Subroutines are only available in program version 4 or higher. A subroutine is section of code that can be called multiple times from within a program. Subroutines are PyTeal's equivalent to functions. Subroutine constraints include: diff --git a/docs/crypto.rst b/docs/crypto.rst index 6a80b2567..7f9a0ef75 100644 --- a/docs/crypto.rst +++ b/docs/crypto.rst @@ -24,9 +24,9 @@ Operator Cost Description :code:`EcdsaRecover(c, d, id, r, s)` `2000` produces the public key associated with the signature :code:`(r, s)` and recovery id :code:`id` ==================================== ========= ================================================================================================================== -\* :code:`Ed25519Verify` is only available in signature mode up to version 4 of TEAL. From version 5 upwards, `Ed25519Verify` can be used in any mode. +\* :code:`Ed25519Verify` is only available in signature mode up to version 4 of AVM. From version 5 upwards, `Ed25519Verify` can be used in any mode. -Note the cost amount is accurate for version 2 of TEAL and higher. The parameter :code:`c` in the ECDSA expressions defined above represents the elliptic curve +Note the cost amount is accurate for version 2 of AVM and higher. The parameter :code:`c` in the ECDSA expressions defined above represents the elliptic curve specification to be used (for example, :code:`Secp256k1`). These cryptographic primitives cover the most used ones in blockchains and cryptocurrencies. For example, Bitcoin uses `SHA-256` for creating Bitcoin addresses; diff --git a/docs/loading_group_transaction.rst b/docs/loading_group_transaction.rst index 90ada05d5..be2753a7d 100644 --- a/docs/loading_group_transaction.rst +++ b/docs/loading_group_transaction.rst @@ -3,7 +3,7 @@ Loading Values from Group Transactions ====================================== -Since TEAL version 4 and above, programs can load values from transactions within an atomic +Since program version 4 and above, programs can load values from transactions within an atomic group transaction. For instance, you can import values from the scratch space of another application call, and you can access the generated ID of a new application or asset. These operations are only permitted in application mode. diff --git a/docs/versions.rst b/docs/versions.rst index b64910451..15a9bfad0 100644 --- a/docs/versions.rst +++ b/docs/versions.rst @@ -1,14 +1,14 @@ .. _versions: -TEAL Versions +AVM Versions ============= -Each version of PyTeal compiles contracts for a specific version of TEAL. Newer versions of TEAL +Each version of PyTeal compiles contracts for a specific AVM version. Newer versions of the AVM introduce new opcodes and transaction fields, so PyTeal must be updated to support these new -features. Below is a table which shows the relationship between TEAL and PyTeal versions. +features. Below is a table which shows the relationship between AVM and PyTeal versions. ============ ============== -TEAL Version PyTeal Version +AVM Version PyTeal Version ============ ============== 1 <= 0.5.4 2 >= 0.6.0 @@ -16,16 +16,17 @@ TEAL Version PyTeal Version 4 >= 0.8.0 5 >= 0.9.0 6 >= 0.10.0 +7 >= 0.14.0 ============ ============== -In order to support TEAL v2, PyTeal v0.6.0 breaks backward compatibility with v0.5.4. PyTeal +In order to support AVM v2, PyTeal v0.6.0 breaks backward compatibility with v0.5.4. PyTeal programs written for PyTeal version 0.5.4 and below will not compile properly and most likely will display an error of the form :code:`AttributeError: * object has no attribute 'teal'`. -**WARNING:** before updating PyTeal to a version with generates TEAL v2 contracts and fixing the +**WARNING:** before updating PyTeal to a version with generates AVM v2 contracts and fixing the programs to use the global function :any:`compileTeal` rather the class method :code:`.teal()`, make -sure your program abides by the TEAL safety guidelines ``_. -Changing a v1 TEAL program to a v2 TEAL program without any code changes is insecure because v2 -TEAL programs allow rekeying. Specifically, you must add a check that the :code:`RekeyTo` property +sure your program abides by the AVM safety guidelines ``_. +Changing a v1 AVM program to a v2 AVM program without any code changes is insecure because v2 +AVM programs allow rekeying. Specifically, you must add a check that the :code:`RekeyTo` property of any transaction is set to the zero address when updating an older PyTeal program from v0.5.4 and below. diff --git a/examples/signature/factorizer_game.py b/examples/signature/factorizer_game.py index ad8ffe3b7..6f4ba48c7 100644 --- a/examples/signature/factorizer_game.py +++ b/examples/signature/factorizer_game.py @@ -47,7 +47,7 @@ def logicsig(a: int, p: int, q: int) -> Expr: """ Choices * (a, p, q) = (1, 5, 7) - * compiling on TEAL version 5 and + * compiling on program version 5 and * with assembleConstants = True results in Logic-Sig Contract Account Address: WO3TQD3WBSDKB6WEHUMSEBFH53GZVVXYGPWYDWKUZCKEXTVCDNDHJGG6II diff --git a/pyteal/__init__.py b/pyteal/__init__.py index ae30dd312..1d8d92f3d 100644 --- a/pyteal/__init__.py +++ b/pyteal/__init__.py @@ -6,6 +6,9 @@ MAX_TEAL_VERSION, MIN_TEAL_VERSION, DEFAULT_TEAL_VERSION, + MAX_PROGRAM_VERSION, + MIN_PROGRAM_VERSION, + DEFAULT_PROGRAM_VERSION, CompileOptions, compileTeal, OptimizeOptions, @@ -27,6 +30,9 @@ "MAX_TEAL_VERSION", "MIN_TEAL_VERSION", "DEFAULT_TEAL_VERSION", + "MAX_PROGRAM_VERSION", + "MIN_PROGRAM_VERSION", + "DEFAULT_PROGRAM_VERSION", "CompileOptions", "compileTeal", "OptimizeOptions", diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index a94633317..1ea51a5a1 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -9,6 +9,9 @@ from pyteal.compiler import ( MAX_TEAL_VERSION, MIN_TEAL_VERSION, DEFAULT_TEAL_VERSION, + MAX_PROGRAM_VERSION, + MIN_PROGRAM_VERSION, + DEFAULT_PROGRAM_VERSION, CompileOptions, compileTeal, OptimizeOptions, @@ -76,6 +79,7 @@ __all__ = [ "Concat", "Cond", "Continue", + "DEFAULT_PROGRAM_VERSION", "DEFAULT_TEAL_VERSION", "Div", "Divw", @@ -126,7 +130,9 @@ __all__ = [ "Log", "Lt", "MAX_GROUP_SIZE", + "MAX_PROGRAM_VERSION", "MAX_TEAL_VERSION", + "MIN_PROGRAM_VERSION", "MIN_TEAL_VERSION", "MaybeValue", "MethodSignature", diff --git a/pyteal/ast/acct_test.py b/pyteal/ast/acct_test.py index 52fc89132..ee60690d8 100644 --- a/pyteal/ast/acct_test.py +++ b/pyteal/ast/acct_test.py @@ -1,9 +1,9 @@ import pyteal as pt options = pt.CompileOptions() -teal4Options = pt.CompileOptions(version=4) -teal5Options = pt.CompileOptions(version=5) -teal6Options = pt.CompileOptions(version=6) +avm4Options = pt.CompileOptions(version=4) +avm5Options = pt.CompileOptions(version=5) +avm6Options = pt.CompileOptions(version=6) def test_acct_param_balance_valid(): @@ -21,7 +21,7 @@ def test_acct_param_balance_valid(): ] ) - actual, _ = expr.__teal__(teal6Options) + actual, _ = expr.__teal__(avm6Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -44,7 +44,7 @@ def test_acct_param_min_balance_valid(): ] ) - actual, _ = expr.__teal__(teal6Options) + actual, _ = expr.__teal__(avm6Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -67,7 +67,7 @@ def test_acct_param_auth_addr_valid(): ] ) - actual, _ = expr.__teal__(teal6Options) + actual, _ = expr.__teal__(avm6Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) diff --git a/pyteal/ast/app_test.py b/pyteal/ast/app_test.py index 79a6c1491..a74e66332 100644 --- a/pyteal/ast/app_test.py +++ b/pyteal/ast/app_test.py @@ -3,8 +3,8 @@ import pyteal as pt options = pt.CompileOptions() -teal4Options = pt.CompileOptions(version=4) -teal5Options = pt.CompileOptions(version=5) +avm4Options = pt.CompileOptions(version=4) +avm5Options = pt.CompileOptions(version=5) def test_on_complete(): @@ -258,7 +258,7 @@ def test_global_get_ex_direct_ref(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -439,7 +439,7 @@ def test_app_param_approval_program_valid(): ] ) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -467,7 +467,7 @@ def test_app_param_clear_state_program_valid(): ] ) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -495,7 +495,7 @@ def test_app_param_global_num_uint_valid(): ] ) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -523,7 +523,7 @@ def test_app_param_global_num_byte_slice_valid(): ] ) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -551,7 +551,7 @@ def test_app_param_local_num_uint_valid(): ] ) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -579,7 +579,7 @@ def test_app_param_local_num_byte_slice_valid(): ] ) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -607,7 +607,7 @@ def test_app_param_extra_programs_page_valid(): ] ) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -635,7 +635,7 @@ def test_app_param_creator_valid(): ] ) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -663,7 +663,7 @@ def test_app_param_address_valid(): ] ) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) diff --git a/pyteal/ast/arg.py b/pyteal/ast/arg.py index 4c5697eda..25f7cb45c 100644 --- a/pyteal/ast/arg.py +++ b/pyteal/ast/arg.py @@ -2,7 +2,7 @@ from pyteal.types import TealType, require_type from pyteal.ir import TealOp, Op, TealBlock -from pyteal.errors import TealInputError, verifyTealVersion +from pyteal.errors import TealInputError, verifyProgramVersion from pyteal.ast.expr import Expr from pyteal.ast.leafexpr import LeafExpr @@ -21,7 +21,7 @@ def __init__(self, index: Union[int, Expr]) -> None: Args: index: The index of the argument to get. The index must be between 0 and 255 inclusive. - Starting in TEAL v5, the index may be a PyTeal expression that evaluates to uint64. + Starting in AVM v5, the index may be a PyTeal expression that evaluates to uint64. """ super().__init__() @@ -38,10 +38,10 @@ def __teal__(self, options: "CompileOptions"): op = TealOp(self, Op.arg, self.index) return TealBlock.FromOp(options, op) - verifyTealVersion( + verifyProgramVersion( Op.args.min_version, options.version, - "TEAL version too low to use dynamic indexes with Arg", + "Program version too low to use dynamic indexes with Arg", ) op = TealOp(self, Op.args) diff --git a/pyteal/ast/arg_test.py b/pyteal/ast/arg_test.py index 90cfc8929..b4feca2ad 100644 --- a/pyteal/ast/arg_test.py +++ b/pyteal/ast/arg_test.py @@ -2,9 +2,9 @@ import pyteal as pt -teal2Options = pt.CompileOptions(version=2) -teal4Options = pt.CompileOptions(version=4) -teal5Options = pt.CompileOptions(version=5) +avm2Options = pt.CompileOptions(version=2) +avm4Options = pt.CompileOptions(version=4) +avm5Options = pt.CompileOptions(version=5) def test_arg_static(): @@ -15,7 +15,7 @@ def test_arg_static(): expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.arg, i)]) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) assert actual == expected @@ -29,14 +29,14 @@ def test_arg_dynamic(): [pt.TealOp(i, pt.Op.int, 7), pt.TealOp(expr, pt.Op.args)] ) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal4Options) + expr.__teal__(avm4Options) def test_arg_invalid(): diff --git a/pyteal/ast/assert_test.py b/pyteal/ast/assert_test.py index fd6b756ab..1291a799c 100644 --- a/pyteal/ast/assert_test.py +++ b/pyteal/ast/assert_test.py @@ -2,8 +2,8 @@ import pyteal as pt -teal2Options = pt.CompileOptions(version=2) -teal3Options = pt.CompileOptions(version=3) +avm2Options = pt.CompileOptions(version=2) +avm3Options = pt.CompileOptions(version=3) def test_teal_2_assert(): @@ -11,13 +11,13 @@ def test_teal_2_assert(): expr = pt.Assert(arg) assert expr.type_of() == pt.TealType.none - expected, _ = arg.__teal__(teal2Options) + expected, _ = arg.__teal__(avm2Options) expectedBranch = pt.TealConditionalBlock([]) expectedBranch.setTrueBlock(pt.TealSimpleBlock([])) - expectedBranch.setFalseBlock(pt.Err().__teal__(teal2Options)[0]) + expectedBranch.setFalseBlock(pt.Err().__teal__(avm2Options)[0]) expected.setNextBlock(expectedBranch) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected @@ -32,7 +32,7 @@ def test_teal_3_assert(): [pt.TealOp(arg, pt.Op.int, 1), pt.TealOp(expr, pt.Op.assert_)] ) - actual, _ = expr.__teal__(teal3Options) + actual, _ = expr.__teal__(avm3Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) diff --git a/pyteal/ast/asset_test.py b/pyteal/ast/asset_test.py index 6ac9cf0e1..8bdaf57b1 100644 --- a/pyteal/ast/asset_test.py +++ b/pyteal/ast/asset_test.py @@ -2,9 +2,9 @@ import pyteal as pt -teal2Options = pt.CompileOptions() -teal4Options = pt.CompileOptions(version=4) -teal5Options = pt.CompileOptions(version=5) +avm2Options = pt.CompileOptions() +avm4Options = pt.CompileOptions(version=4) +avm5Options = pt.CompileOptions(version=5) def test_asset_holding_balance(): @@ -23,7 +23,7 @@ def test_asset_holding_balance(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -47,7 +47,7 @@ def test_asset_holding_balance_direct_ref(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -79,7 +79,7 @@ def test_asset_holding_frozen(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -103,7 +103,7 @@ def test_asset_holding_frozen_direct_ref(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -134,7 +134,7 @@ def test_asset_param_total(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -157,7 +157,7 @@ def test_asset_param_total_direct_ref(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -185,7 +185,7 @@ def test_asset_param_decimals(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -208,7 +208,7 @@ def test_asset_param_decimals_direct_ref(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -236,7 +236,7 @@ def test_asset_param_default_frozen(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -259,7 +259,7 @@ def test_asset_param_default_frozen_direct_ref(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -287,7 +287,7 @@ def test_asset_param_unit_name(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -310,7 +310,7 @@ def test_asset_param_unit_name_direct_ref(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -338,7 +338,7 @@ def test_asset_param_name(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -361,7 +361,7 @@ def test_asset_param_name_direct_ref(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -389,7 +389,7 @@ def test_asset_param_url(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -412,7 +412,7 @@ def test_asset_param_url_direct_ref(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -440,7 +440,7 @@ def test_asset_param_metadata_hash(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -463,7 +463,7 @@ def test_asset_param_metadata_hash_direct_ref(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -491,7 +491,7 @@ def test_asset_param_manager(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -514,7 +514,7 @@ def test_asset_param_manager_direct_ref(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -542,7 +542,7 @@ def test_asset_param_reserve(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -565,7 +565,7 @@ def test_asset_param_reserve_direct_ref(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -593,7 +593,7 @@ def test_asset_param_freeze(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -616,7 +616,7 @@ def test_asset_param_freeze_direct_ref(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -644,7 +644,7 @@ def test_asset_param_clawback(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -667,7 +667,7 @@ def test_asset_param_clawback_direct_ref(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -695,7 +695,7 @@ def test_asset_param_creator_valid(): ] ) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) diff --git a/pyteal/ast/base64decode.py b/pyteal/ast/base64decode.py index 7dcf47c18..be850a001 100644 --- a/pyteal/ast/base64decode.py +++ b/pyteal/ast/base64decode.py @@ -5,7 +5,6 @@ from pyteal.errors import verifyFieldVersion from pyteal.ir import TealOp, Op, TealBlock from pyteal.ast.expr import Expr -from pyteal.ast.leafexpr import LeafExpr if TYPE_CHECKING: from pyteal.compiler import CompileOptions @@ -27,7 +26,7 @@ def __init__(self, id: int, name: str, min_version: int) -> None: Base64Encoding.__module__ = "pyteal" -class Base64Decode(LeafExpr): +class Base64Decode(Expr): """An expression that decodes a base64-encoded byte string according to a specific encoding. See [RFC 4648](https://rfc-editor.org/rfc/rfc4648.html#section-4) (sections 4 and 5) for information on specifications. @@ -36,6 +35,8 @@ class Base64Decode(LeafExpr): When padding occurs, any unused pad bits in the encoding must be set to zero or the decoding will fail. The special cases of \\n and \\r are allowed but completely ignored. An error will result when attempting to decode a string with a character that is not in the encoding alphabet or not one of =, \\r, or \\n. + + NOTE: Base64Decode usage is not intended for introducing constants. Instead, use :any:`Bytes`. """ def __init__(self, encoding: Base64Encoding, base64: Expr) -> None: @@ -59,8 +60,11 @@ def __str__(self): def type_of(self): return TealType.bytes + def has_return(self): + return False + @classmethod - def url(cls, base64: Expr) -> "Base64Decode": + def url(cls, base64: Expr) -> Expr: """Decode a base64-encoded byte string according to the URL encoding. Refer to the `Base64Decode` class documentation for more information. @@ -71,7 +75,7 @@ def url(cls, base64: Expr) -> "Base64Decode": return cls(Base64Encoding.url, base64) @classmethod - def std(cls, base64: Expr) -> "Base64Decode": + def std(cls, base64: Expr) -> Expr: """Decode a base64-encoded byte string according to the Standard encoding. Refer to the `Base64Decode` class documentation for more information. diff --git a/pyteal/ast/base64decode_test.py b/pyteal/ast/base64decode_test.py index 14eb48799..724dbc4aa 100644 --- a/pyteal/ast/base64decode_test.py +++ b/pyteal/ast/base64decode_test.py @@ -2,8 +2,8 @@ import pyteal as pt -teal6Options = pt.CompileOptions(version=6) -teal7Options = pt.CompileOptions(version=7) +avm6Options = pt.CompileOptions(version=6) +avm7Options = pt.CompileOptions(version=7) def test_base64decode_std(): @@ -18,14 +18,14 @@ def test_base64decode_std(): ] ) - actual, _ = expr.__teal__(teal7Options) + actual, _ = expr.__teal__(avm7Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal6Options) + expr.__teal__(avm6Options) def test_base64decode_url(): @@ -40,14 +40,14 @@ def test_base64decode_url(): ] ) - actual, _ = expr.__teal__(teal7Options) + actual, _ = expr.__teal__(avm7Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal6Options) + expr.__teal__(avm6Options) def test_base64decode_invalid(): diff --git a/pyteal/ast/binaryexpr.py b/pyteal/ast/binaryexpr.py index c6f879f7c..7b2ebebe2 100644 --- a/pyteal/ast/binaryexpr.py +++ b/pyteal/ast/binaryexpr.py @@ -1,7 +1,7 @@ from typing import Union, Tuple, cast, TYPE_CHECKING from pyteal.types import TealType, require_type -from pyteal.errors import verifyTealVersion +from pyteal.errors import verifyProgramVersion from pyteal.ir import TealOp, Op, TealBlock from pyteal.ast.expr import Expr @@ -35,10 +35,10 @@ def __init__( self.argRight = argRight def __teal__(self, options: "CompileOptions"): - verifyTealVersion( + verifyProgramVersion( self.op.min_version, options.version, - "TEAL version too low to use op {}".format(self.op), + "Program version too low to use op {}".format(self.op), ) return TealBlock.FromOp( @@ -123,7 +123,7 @@ def Exp(a: Expr, b: Expr) -> BinaryExpr: Produces a ** b. - Requires TEAL version 4 or higher. + Requires program version 4 or higher. Args: a: Must evaluate to uint64. @@ -173,7 +173,7 @@ def ShiftLeft(a: Expr, b: Expr) -> BinaryExpr: Produces a << b. This is equivalent to a times 2^b, modulo 2^64. - Requires TEAL version 4 or higher. + Requires program version 4 or higher. Args: a: Must evaluate to uint64. @@ -187,7 +187,7 @@ def ShiftRight(a: Expr, b: Expr) -> BinaryExpr: Produces a >> b. This is equivalent to a divided by 2^b. - Requires TEAL version 4 or higher. + Requires program version 4 or higher. Args: a: Must evaluate to uint64. @@ -281,7 +281,7 @@ def GetBit(value: Expr, index: Expr) -> BinaryExpr: yields 1. Any index less than 4 would yield 1, and any valid index 4 or greater would yield 0. Any integer less than 8*Len(value) is a valid index. - Requires TEAL version 3 or higher. + Requires program version 3 or higher. Args: value: The value containing bits. Can evaluate to any type. @@ -298,7 +298,7 @@ def GetByte(value: Expr, index: Expr) -> BinaryExpr: Similar to GetBit, indexing begins at the first byte. For example, :code:`GetByte(Bytes("base16", "0xff0000"), Int(0))` yields 255. Any other valid index would yield 0. - Requires TEAL version 3 or higher. + Requires program version 3 or higher. Args: value: The value containing the bytes. Must evaluate to bytes. @@ -315,7 +315,7 @@ def BytesAdd(left: Expr, right: Expr) -> BinaryExpr: Produces left + right, where left and right are interpreted as big-endian unsigned integers. Arguments must not exceed 64 bytes. - Requires TEAL version 4 or higher. + Requires program version 4 or higher. Args: left: Must evaluate to bytes. @@ -330,7 +330,7 @@ def BytesMinus(left: Expr, right: Expr) -> BinaryExpr: Produces left - right, where left and right are interpreted as big-endian unsigned integers. Arguments must not exceed 64 bytes. - Requires TEAL version 4 or higher. + Requires program version 4 or higher. Args: left: Must evaluate to bytes. @@ -347,7 +347,7 @@ def BytesDiv(left: Expr, right: Expr) -> BinaryExpr: Panics if right is 0. - Requires TEAL version 4 or higher. + Requires program version 4 or higher. Args: left: Must evaluate to bytes. @@ -362,7 +362,7 @@ def BytesMul(left: Expr, right: Expr) -> BinaryExpr: Produces left * right, where left and right are interpreted as big-endian unsigned integers. Arguments must not exceed 64 bytes. - Requires TEAL version 4 or higher. + Requires program version 4 or higher. Args: left: Must evaluate to bytes. @@ -379,7 +379,7 @@ def BytesMod(left: Expr, right: Expr) -> BinaryExpr: Panics if right is 0. - Requires TEAL version 4 or higher. + Requires program version 4 or higher. Args: left: Must evaluate to bytes. @@ -395,7 +395,7 @@ def BytesAnd(left: Expr, right: Expr) -> BinaryExpr: Left and right are zero-left extended to the greater of their lengths. Arguments must not exceed 64 bytes. - Requires TEAL version 4 or higher. + Requires program version 4 or higher. Args: left: Must evaluate to bytes. @@ -411,7 +411,7 @@ def BytesOr(left: Expr, right: Expr) -> BinaryExpr: Left and right are zero-left extended to the greater of their lengths. Arguments must not exceed 64 bytes. - Requires TEAL version 4 or higher. + Requires program version 4 or higher. Args: left: Must evaluate to bytes. @@ -427,7 +427,7 @@ def BytesXor(left: Expr, right: Expr) -> BinaryExpr: Left and right are zero-left extended to the greater of their lengths. Arguments must not exceed 64 bytes. - Requires TEAL version 4 or higher. + Requires program version 4 or higher. Args: left: Must evaluate to bytes. @@ -442,7 +442,7 @@ def BytesEq(left: Expr, right: Expr) -> BinaryExpr: Checks if left == right, where left and right are interpreted as big-endian unsigned integers. Arguments must not exceed 64 bytes. - Requires TEAL version 4 or higher. + Requires program version 4 or higher. Args: left: Must evaluate to bytes. @@ -457,7 +457,7 @@ def BytesNeq(left: Expr, right: Expr) -> BinaryExpr: Checks if left != right, where left and right are interpreted as big-endian unsigned integers. Arguments must not exceed 64 bytes. - Requires TEAL version 4 or higher. + Requires program version 4 or higher. Args: left: Must evaluate to bytes. @@ -472,7 +472,7 @@ def BytesLt(left: Expr, right: Expr) -> BinaryExpr: Checks if left < right, where left and right are interpreted as big-endian unsigned integers. Arguments must not exceed 64 bytes. - Requires TEAL version 4 or higher. + Requires program version 4 or higher. Args: left: Must evaluate to bytes. @@ -487,7 +487,7 @@ def BytesLe(left: Expr, right: Expr) -> BinaryExpr: Checks if left <= right, where left and right are interpreted as big-endian unsigned integers. Arguments must not exceed 64 bytes. - Requires TEAL version 4 or higher. + Requires program version 4 or higher. Args: left: Must evaluate to bytes. @@ -502,7 +502,7 @@ def BytesGt(left: Expr, right: Expr) -> BinaryExpr: Checks if left > right, where left and right are interpreted as big-endian unsigned integers. Arguments must not exceed 64 bytes. - Requires TEAL version 4 or higher. + Requires program version 4 or higher. Args: left: Must evaluate to bytes. @@ -517,7 +517,7 @@ def BytesGe(left: Expr, right: Expr) -> BinaryExpr: Checks if left >= right, where left and right are interpreted as big-endian unsigned integers. Arguments must not exceed 64 bytes. - Requires TEAL version 4 or higher. + Requires program version 4 or higher. Args: left: Must evaluate to bytes. @@ -534,7 +534,7 @@ def ExtractUint16(string: Expr, offset: Expr) -> BinaryExpr: If :code:`offset + 2` exceeds :code:`Len(string)`, the program fails. - Requires TEAL version 5 or higher. + Requires program version 5 or higher. Args: string: The bytestring to extract from. Must evaluate to bytes. @@ -557,7 +557,7 @@ def ExtractUint32(string: Expr, offset: Expr) -> BinaryExpr: If :code:`offset + 4` exceeds :code:`Len(string)`, the program fails. - Requires TEAL version 5 or higher. + Requires program version 5 or higher. Args: string: The bytestring to extract from. Must evaluate to bytes. @@ -580,7 +580,7 @@ def ExtractUint64(string: Expr, offset: Expr) -> BinaryExpr: If :code:`offset + 8` exceeds :code:`Len(string)`, the program fails. - Requires TEAL version 5 or higher. + Requires program version 5 or higher. Args: string: The bytestring to extract from. Must evaluate to bytes. diff --git a/pyteal/ast/binaryexpr_test.py b/pyteal/ast/binaryexpr_test.py index a1b493365..1d050fd1e 100644 --- a/pyteal/ast/binaryexpr_test.py +++ b/pyteal/ast/binaryexpr_test.py @@ -2,10 +2,10 @@ import pyteal as pt -teal2Options = pt.CompileOptions(version=2) -teal3Options = pt.CompileOptions(version=3) -teal4Options = pt.CompileOptions(version=4) -teal5Options = pt.CompileOptions(version=5) +avm2Options = pt.CompileOptions(version=2) +avm3Options = pt.CompileOptions(version=3) +avm4Options = pt.CompileOptions(version=4) +avm5Options = pt.CompileOptions(version=5) def test_add(): @@ -21,7 +21,7 @@ def test_add(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -43,7 +43,7 @@ def test_add_overload(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -72,7 +72,7 @@ def test_minus(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -94,7 +94,7 @@ def test_minus_overload(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -123,7 +123,7 @@ def test_mul(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -145,7 +145,7 @@ def test_mul_overload(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -174,7 +174,7 @@ def test_div(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -196,7 +196,7 @@ def test_div_overload(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -225,7 +225,7 @@ def test_mod(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -247,7 +247,7 @@ def test_mod_overload(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -276,14 +276,14 @@ def test_exp(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal3Options) + expr.__teal__(avm3Options) def test_exp_overload(): @@ -302,7 +302,7 @@ def test_exp_overload(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -310,7 +310,7 @@ def test_exp_overload(): assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal3Options) + expr.__teal__(avm3Options) def test_exp_invalid(): @@ -342,7 +342,7 @@ def test_arithmetic(): ] ) - actual, _ = v.__teal__(teal2Options) + actual, _ = v.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -363,7 +363,7 @@ def test_bitwise_and(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -385,7 +385,7 @@ def test_bitwise_and_overload(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -414,7 +414,7 @@ def test_bitwise_or(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -436,7 +436,7 @@ def test_bitwise_or_overload(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -465,7 +465,7 @@ def test_bitwise_xor(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -487,7 +487,7 @@ def test_bitwise_xor_overload(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -516,14 +516,14 @@ def test_shift_left(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal3Options) + expr.__teal__(avm3Options) def test_shift_left_overload(): @@ -541,7 +541,7 @@ def test_shift_left_overload(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -549,7 +549,7 @@ def test_shift_left_overload(): assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal3Options) + expr.__teal__(avm3Options) def test_shift_left_invalid(): @@ -573,14 +573,14 @@ def test_shift_right(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal3Options) + expr.__teal__(avm3Options) def test_shift_right_overload(): @@ -598,7 +598,7 @@ def test_shift_right_overload(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -606,7 +606,7 @@ def test_shift_right_overload(): assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal3Options) + expr.__teal__(avm3Options) def test_shift_right_invalid(): @@ -630,7 +630,7 @@ def test_eq(): ] ) - actual_int, _ = expr_int.__teal__(teal2Options) + actual_int, _ = expr_int.__teal__(avm2Options) actual_int.addIncoming() actual_int = pt.TealBlock.NormalizeBlocks(actual_int) @@ -648,7 +648,7 @@ def test_eq(): ] ) - actual_bytes, _ = expr_bytes.__teal__(teal2Options) + actual_bytes, _ = expr_bytes.__teal__(avm2Options) actual_bytes.addIncoming() actual_bytes = pt.TealBlock.NormalizeBlocks(actual_bytes) @@ -668,7 +668,7 @@ def test_eq_overload(): ] ) - actual_int, _ = expr_int.__teal__(teal2Options) + actual_int, _ = expr_int.__teal__(avm2Options) actual_int.addIncoming() actual_int = pt.TealBlock.NormalizeBlocks(actual_int) @@ -686,7 +686,7 @@ def test_eq_overload(): ] ) - actual_bytes, _ = expr_bytes.__teal__(teal2Options) + actual_bytes, _ = expr_bytes.__teal__(avm2Options) actual_bytes.addIncoming() actual_bytes = pt.TealBlock.NormalizeBlocks(actual_bytes) @@ -714,7 +714,7 @@ def test_neq(): ] ) - actual_int, _ = expr_int.__teal__(teal2Options) + actual_int, _ = expr_int.__teal__(avm2Options) actual_int.addIncoming() actual_int = pt.TealBlock.NormalizeBlocks(actual_int) @@ -732,7 +732,7 @@ def test_neq(): ] ) - actual_bytes, _ = expr_bytes.__teal__(teal2Options) + actual_bytes, _ = expr_bytes.__teal__(avm2Options) actual_bytes.addIncoming() actual_bytes = pt.TealBlock.NormalizeBlocks(actual_bytes) @@ -752,7 +752,7 @@ def test_neq_overload(): ] ) - actual_int, _ = expr_int.__teal__(teal2Options) + actual_int, _ = expr_int.__teal__(avm2Options) actual_int.addIncoming() actual_int = pt.TealBlock.NormalizeBlocks(actual_int) @@ -770,7 +770,7 @@ def test_neq_overload(): ] ) - actual_bytes, _ = expr_bytes.__teal__(teal2Options) + actual_bytes, _ = expr_bytes.__teal__(avm2Options) actual_bytes.addIncoming() actual_bytes = pt.TealBlock.NormalizeBlocks(actual_bytes) @@ -798,7 +798,7 @@ def test_lt(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -818,7 +818,7 @@ def test_lt_overload(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -846,7 +846,7 @@ def test_le(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -866,7 +866,7 @@ def test_le_overload(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -894,7 +894,7 @@ def test_gt(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -914,7 +914,7 @@ def test_gt_overload(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -942,7 +942,7 @@ def test_ge(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -962,7 +962,7 @@ def test_ge_overload(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -990,14 +990,14 @@ def test_get_bit_int(): ] ) - actual, _ = expr.__teal__(teal3Options) + actual, _ = expr.__teal__(avm3Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal2Options) + expr.__teal__(avm2Options) def test_get_bit_bytes(): @@ -1013,14 +1013,14 @@ def test_get_bit_bytes(): ] ) - actual, _ = expr.__teal__(teal3Options) + actual, _ = expr.__teal__(avm3Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal2Options) + expr.__teal__(avm2Options) def test_get_bit_invalid(): @@ -1044,14 +1044,14 @@ def test_get_byte(): ] ) - actual, _ = expr.__teal__(teal3Options) + actual, _ = expr.__teal__(avm3Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal2Options) + expr.__teal__(avm2Options) def test_get_byte_invalid(): @@ -1078,14 +1078,14 @@ def test_b_add(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal3Options) + expr.__teal__(avm3Options) def test_b_add_invalid(): @@ -1112,14 +1112,14 @@ def test_b_minus(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal3Options) + expr.__teal__(avm3Options) def test_b_minus_invalid(): @@ -1143,14 +1143,14 @@ def test_b_div(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal3Options) + expr.__teal__(avm3Options) def test_b_div_invalid(): @@ -1174,14 +1174,14 @@ def test_b_mul(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal3Options) + expr.__teal__(avm3Options) def test_b_mul_invalid(): @@ -1205,14 +1205,14 @@ def test_b_mod(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal3Options) + expr.__teal__(avm3Options) def test_b_mod_invalid(): @@ -1236,14 +1236,14 @@ def test_b_and(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal3Options) + expr.__teal__(avm3Options) def test_b_and_invalid(): @@ -1267,14 +1267,14 @@ def test_b_or(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal3Options) + expr.__teal__(avm3Options) def test_b_or_invalid(): @@ -1298,14 +1298,14 @@ def test_b_xor(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal3Options) + expr.__teal__(avm3Options) def test_b_xor_invalid(): @@ -1332,14 +1332,14 @@ def test_b_eq(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal3Options) + expr.__teal__(avm3Options) def test_b_eq_invalid(): @@ -1366,14 +1366,14 @@ def test_b_neq(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal3Options) + expr.__teal__(avm3Options) def test_b_neq_invalid(): @@ -1400,14 +1400,14 @@ def test_b_lt(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal3Options) + expr.__teal__(avm3Options) def test_b_lt_invalid(): @@ -1434,14 +1434,14 @@ def test_b_le(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal3Options) + expr.__teal__(avm3Options) def test_b_le_invalid(): @@ -1468,14 +1468,14 @@ def test_b_gt(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal3Options) + expr.__teal__(avm3Options) def test_b_gt_invalid(): @@ -1502,14 +1502,14 @@ def test_b_ge(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal3Options) + expr.__teal__(avm3Options) def test_b_ge_invalid(): @@ -1541,14 +1541,14 @@ def test_extract_uint(): ] ) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal4Options) + expr.__teal__(avm4Options) def test_extract_uint_invalid(): diff --git a/pyteal/ast/block.py b/pyteal/ast/block.py index 041483aae..775f1593c 100644 --- a/pyteal/ast/block.py +++ b/pyteal/ast/block.py @@ -55,7 +55,7 @@ def type_of(self): return self.field.type_of() @classmethod - def seed(cls, block: Expr) -> "Block": + def seed(cls, block: Expr) -> Expr: """Get the seed of a block. Args: @@ -66,7 +66,7 @@ def seed(cls, block: Expr) -> "Block": return cls(BlockField.block_seed, block) @classmethod - def timestamp(cls, block: Expr) -> "Block": + def timestamp(cls, block: Expr) -> Expr: """Get the timestamp of a block. Args: diff --git a/pyteal/ast/block_test.py b/pyteal/ast/block_test.py index 0cd571063..e55ddf319 100644 --- a/pyteal/ast/block_test.py +++ b/pyteal/ast/block_test.py @@ -2,8 +2,8 @@ import pyteal as pt -teal6Options = pt.CompileOptions(version=6) -teal7Options = pt.CompileOptions(version=7) +avm6Options = pt.CompileOptions(version=6) +avm7Options = pt.CompileOptions(version=7) def test_block_seed(): @@ -18,14 +18,14 @@ def test_block_seed(): ] ) - actual, _ = expr.__teal__(teal7Options) + actual, _ = expr.__teal__(avm7Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal6Options) + expr.__teal__(avm6Options) def test_block_seed_invalid(): @@ -45,14 +45,14 @@ def test_block_timestamp(): ] ) - actual, _ = expr.__teal__(teal7Options) + actual, _ = expr.__teal__(avm7Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal6Options) + expr.__teal__(avm6Options) def test_block_timestamp_invalid(): diff --git a/pyteal/ast/box.py b/pyteal/ast/box.py index 33b9712e3..368c001e0 100644 --- a/pyteal/ast/box.py +++ b/pyteal/ast/box.py @@ -1,6 +1,6 @@ from typing import TYPE_CHECKING from pyteal.ast.maybe import MaybeValue -from pyteal.errors import verifyTealVersion +from pyteal.errors import verifyProgramVersion from pyteal.types import TealType, require_type from pyteal.ir import TealOp, Op, TealBlock @@ -27,7 +27,7 @@ def __init__(self, name: Expr, size: Expr) -> None: self.size = size def __teal__(self, options: "CompileOptions"): - verifyTealVersion( + verifyProgramVersion( minVersion=Op.box_create.min_version, version=options.version, msg=f"{Op.box_create} unavailable", @@ -62,7 +62,7 @@ def __init__(self, name: Expr) -> None: self.name = name def __teal__(self, options: "CompileOptions"): - verifyTealVersion( + verifyProgramVersion( minVersion=Op.box_del.min_version, version=options.version, msg=f"{Op.box_del} unavailable", @@ -101,7 +101,7 @@ def __init__(self, name: Expr, start: Expr, value: Expr) -> None: self.value = value def __teal__(self, options: "CompileOptions"): - verifyTealVersion( + verifyProgramVersion( minVersion=Op.box_replace.min_version, version=options.version, msg=f"{Op.box_replace} unavailable", @@ -143,7 +143,7 @@ def __init__(self, name: Expr, start: Expr, length: Expr) -> None: self.length = length def __teal__(self, options: "CompileOptions"): - verifyTealVersion( + verifyProgramVersion( minVersion=Op.box_extract.min_version, version=options.version, msg=f"{Op.box_extract} unavailable", @@ -204,7 +204,7 @@ def __init__(self, name: Expr, value: Expr) -> None: self.value = value def __teal__(self, options: "CompileOptions"): - verifyTealVersion( + verifyProgramVersion( minVersion=Op.box_put.min_version, version=options.version, msg=f"{Op.box_put} unavailable", diff --git a/pyteal/ast/box_test.py b/pyteal/ast/box_test.py index 87968ce2f..4000b3974 100644 --- a/pyteal/ast/box_test.py +++ b/pyteal/ast/box_test.py @@ -3,8 +3,8 @@ import pytest import pyteal as pt -teal7Options = pt.CompileOptions(version=7) -teal8Options = pt.CompileOptions(version=8) +avm7Options = pt.CompileOptions(version=7) +avm8Options = pt.CompileOptions(version=8) POSITIVE_TEST_CASES: list[Tuple[pt.Expr, pt.TealType]] = [ (pt.BoxCreate(pt.Bytes("box"), pt.Int(10)), pt.TealType.none), @@ -23,9 +23,9 @@ @pytest.mark.parametrize("test_case, test_case_type", POSITIVE_TEST_CASES) def test_compile_version_and_type(test_case, test_case_type): with pytest.raises(pt.TealInputError): - test_case.__teal__(teal7Options) + test_case.__teal__(avm7Options) - test_case.__teal__(teal8Options) + test_case.__teal__(avm8Options) assert test_case.type_of() == test_case_type assert not test_case.has_return() @@ -62,7 +62,7 @@ def test_box_create_compile(): pt.TealOp(expr, pt.Op.box_create), ] ) - actual, _ = expr.__teal__(teal8Options) + actual, _ = expr.__teal__(avm8Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -76,7 +76,7 @@ def test_box_delete_compile(): expected = pt.TealSimpleBlock( [pt.TealOp(name_arg, pt.Op.byte, '"eineName"'), pt.TealOp(expr, pt.Op.box_del)] ) - actual, _ = expr.__teal__(teal8Options) + actual, _ = expr.__teal__(avm8Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -97,7 +97,7 @@ def test_box_extract(): pt.TealOp(expr, pt.Op.box_extract), ] ) - actual, _ = expr.__teal__(teal8Options) + actual, _ = expr.__teal__(avm8Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -118,7 +118,7 @@ def test_box_replace(): pt.TealOp(expr, pt.Op.box_replace), ] ) - actual, _ = expr.__teal__(teal8Options) + actual, _ = expr.__teal__(avm8Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -137,7 +137,7 @@ def test_box_length(): pt.TealOp(expr.output_slots[0].store(), pt.Op.store, expr.output_slots[0]), ] ) - actual, _ = expr.__teal__(teal8Options) + actual, _ = expr.__teal__(avm8Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -157,7 +157,7 @@ def test_box_get(): pt.TealOp(expr.output_slots[0].store(), pt.Op.store, expr.output_slots[0]), ] ) - actual, _ = expr.__teal__(teal8Options) + actual, _ = expr.__teal__(avm8Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -177,7 +177,7 @@ def test_box_put(): pt.TealOp(expr, pt.Op.box_put), ] ) - actual, _ = expr.__teal__(teal8Options) + actual, _ = expr.__teal__(avm8Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) diff --git a/pyteal/ast/ecdsa.py b/pyteal/ast/ecdsa.py index 06e4e49db..b71e7ae46 100644 --- a/pyteal/ast/ecdsa.py +++ b/pyteal/ast/ecdsa.py @@ -2,7 +2,12 @@ from typing import Tuple, TYPE_CHECKING from pyteal.ast import Expr, MultiValue -from pyteal.errors import TealTypeError, verifyFieldVersion, verifyTealVersion +from pyteal.errors import ( + TealTypeError, + verifyFieldVersion, + verifyProgramVersion, + TealInputError, +) from pyteal.ir import Op, TealBlock, TealOp from pyteal.types import TealType, require_type @@ -52,10 +57,10 @@ def __init__( self.args = [data, sigA, sigB, pkX, pkY] def __teal__(self, options: "CompileOptions"): - verifyTealVersion( + verifyProgramVersion( self.op.min_version, options.version, - "TEAL version too low to use op {}".format(self.op), + "Program version too low to use op {}".format(self.op), ) verifyFieldVersion(self.curve.arg_name, self.curve.min_version, options.version) @@ -142,7 +147,7 @@ def EcdsaDecompress(curve: EcdsaCurve, compressed_pk: Expr) -> MultiValue: def EcdsaRecover( curve: EcdsaCurve, data: Expr, recovery_id: Expr, sigA: Expr, sigB: Expr ) -> MultiValue: - """Reover an ECDSA public key from a signature. + """Recover an ECDSA public key from a signature. All byte arguments must be big endian encoded. Args: curve: Enum representing the ECDSA curve used for the public key @@ -158,6 +163,9 @@ def EcdsaRecover( if not isinstance(curve, EcdsaCurve): raise TealTypeError(curve, EcdsaCurve) + if curve != EcdsaCurve.Secp256k1: + raise TealInputError("Recover only supports Secp256k1") + require_type(data, TealType.bytes) require_type(recovery_id, TealType.uint64) require_type(sigA, TealType.bytes) diff --git a/pyteal/ast/ecdsa_test.py b/pyteal/ast/ecdsa_test.py index 03cec2207..c6dc1bdb1 100644 --- a/pyteal/ast/ecdsa_test.py +++ b/pyteal/ast/ecdsa_test.py @@ -3,13 +3,13 @@ import pyteal as pt -teal4Options = pt.CompileOptions(version=4) -teal5Options = pt.CompileOptions(version=5) -teal7Options = pt.CompileOptions(version=7) +avm4Options = pt.CompileOptions(version=4) +avm5Options = pt.CompileOptions(version=5) +avm7Options = pt.CompileOptions(version=7) curve_options_map = { - pt.EcdsaCurve.Secp256k1: teal5Options, - pt.EcdsaCurve.Secp256r1: teal7Options, + pt.EcdsaCurve.Secp256k1: avm5Options, + pt.EcdsaCurve.Secp256r1: avm7Options, } @@ -54,45 +54,51 @@ def test_ecdsa_decompress(curve: pt.EcdsaCurve): @pytest.mark.parametrize("curve", [pt.EcdsaCurve.Secp256k1, pt.EcdsaCurve.Secp256r1]) def test_ecdsa_recover(curve: pt.EcdsaCurve): - args = [pt.Bytes("data"), pt.Int(1), pt.Bytes("sigA"), pt.Bytes("sigB")] - pubkey = pt.EcdsaRecover(curve, args[0], args[1], args[2], args[3]) - assert pubkey.type_of() == pt.TealType.none - - expected = pt.TealSimpleBlock( - [ - pt.TealOp(args[0], pt.Op.byte, '"data"'), - pt.TealOp(args[1], pt.Op.int, 1), - pt.TealOp(args[2], pt.Op.byte, '"sigA"'), - pt.TealOp(args[3], pt.Op.byte, '"sigB"'), - pt.TealOp(pubkey, pt.Op.ecdsa_pk_recover, curve.arg_name), - pt.TealOp( - pubkey.output_slots[1].store(), pt.Op.store, pubkey.output_slots[1] - ), - pt.TealOp( - pubkey.output_slots[0].store(), pt.Op.store, pubkey.output_slots[0] - ), - ] - ) + if curve != pt.EcdsaCurve.Secp256k1: + with pytest.raises(pt.TealInputError): + pt.EcdsaRecover( + curve, pt.Bytes("data"), pt.Int(1), pt.Bytes("sigA"), pt.Bytes("sigB") + ) + else: + args = [pt.Bytes("data"), pt.Int(1), pt.Bytes("sigA"), pt.Bytes("sigB")] + pubkey = pt.EcdsaRecover(curve, args[0], args[1], args[2], args[3]) + assert pubkey.type_of() == pt.TealType.none + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(args[0], pt.Op.byte, '"data"'), + pt.TealOp(args[1], pt.Op.int, 1), + pt.TealOp(args[2], pt.Op.byte, '"sigA"'), + pt.TealOp(args[3], pt.Op.byte, '"sigB"'), + pt.TealOp(pubkey, pt.Op.ecdsa_pk_recover, curve.arg_name), + pt.TealOp( + pubkey.output_slots[1].store(), pt.Op.store, pubkey.output_slots[1] + ), + pt.TealOp( + pubkey.output_slots[0].store(), pt.Op.store, pubkey.output_slots[0] + ), + ] + ) - actual, _ = pubkey.__teal__(curve_options_map[curve]) - actual.addIncoming() - actual = pt.TealBlock.NormalizeBlocks(actual) + actual, _ = pubkey.__teal__(curve_options_map[curve]) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) - with pt.TealComponent.Context.ignoreExprEquality(): - assert actual == expected + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected - # compile without errors this is necessary so assembly is also tested - pt.compileTeal( - pt.Seq(pubkey, pt.Approve()), pt.Mode.Application, version=curve.min_version - ) - - with pytest.raises(pt.TealInputError): + # compile without errors this is necessary so assembly is also tested pt.compileTeal( - pt.Seq(pubkey, pt.Approve()), - pt.Mode.Application, - version=curve.min_version - 1, + pt.Seq(pubkey, pt.Approve()), pt.Mode.Application, version=curve.min_version ) + with pytest.raises(pt.TealInputError): + pt.compileTeal( + pt.Seq(pubkey, pt.Approve()), + pt.Mode.Application, + version=curve.min_version - 1, + ) + @pytest.mark.parametrize("curve", [pt.EcdsaCurve.Secp256k1, pt.EcdsaCurve.Secp256r1]) def test_ecdsa_verify_basic(curve: pt.EcdsaCurve): @@ -186,8 +192,8 @@ def test_ecdsa_verify_compressed_pk(curve: pt.EcdsaCurve): ) -@pytest.mark.parametrize("curve", [pt.EcdsaCurve.Secp256k1, pt.EcdsaCurve.Secp256r1]) -def test_ecdsa_verify_recovered_pk(curve: pt.EcdsaCurve): +def test_ecdsa_verify_recovered_pk(): + curve = pt.EcdsaCurve.Secp256k1 args = [pt.Bytes("data"), pt.Int(1), pt.Bytes("sigA"), pt.Bytes("sigB")] pubkey = pt.EcdsaRecover(curve, args[0], args[1], args[2], args[3]) expr = pt.EcdsaVerify(curve, args[0], args[2], args[3], pubkey) @@ -243,14 +249,15 @@ def test_ecdsa_verify_recovered_pk(curve: pt.EcdsaCurve): @pytest.mark.parametrize("curve", [pt.EcdsaCurve.Secp256k1, pt.EcdsaCurve.Secp256r1]) def test_ecdsa_invalid(curve: pt.EcdsaCurve): - with pytest.raises(pt.TealTypeError): - args: List[Union[pt.Bytes, pt.Int]] = [ - pt.Bytes("data"), - pt.Bytes("1"), - pt.Bytes("sigA"), - pt.Bytes("sigB"), - ] - pt.EcdsaRecover(curve, args[0], args[1], args[2], args[3]) + if curve == pt.EcdsaCurve.Secp256k1: + with pytest.raises(pt.TealTypeError): + args: List[Union[pt.Bytes, pt.Int]] = [ + pt.Bytes("data"), + pt.Bytes("1"), + pt.Bytes("sigA"), + pt.Bytes("sigB"), + ] + pt.EcdsaRecover(curve, args[0], args[1], args[2], args[3]) with pytest.raises(pt.TealTypeError): pt.EcdsaDecompress(curve, pt.Int(1)) @@ -284,7 +291,7 @@ def test_ecdsa_invalid(curve: pt.EcdsaCurve): pubkey = (pt.Bytes("X"), pt.Bytes("Y")) expr = pt.EcdsaVerify(curve, args[0], args[1], args[2], pubkey) - expr.__teal__(teal4Options) + expr.__teal__(avm4Options) with pytest.raises(pt.TealTypeError): args = [pt.Bytes("data"), pt.Bytes("sigA"), pt.Bytes("sigB")] diff --git a/pyteal/ast/gaid.py b/pyteal/ast/gaid.py index d9faa6128..371aa86ac 100644 --- a/pyteal/ast/gaid.py +++ b/pyteal/ast/gaid.py @@ -2,7 +2,7 @@ from pyteal.types import TealType, require_type from pyteal.ir import TealOp, Op, TealBlock -from pyteal.errors import TealInputError, verifyTealVersion +from pyteal.errors import TealInputError, verifyProgramVersion from pyteal.config import MAX_GROUP_SIZE from pyteal.ast.expr import Expr from pyteal.ast.leafexpr import LeafExpr @@ -17,7 +17,7 @@ class GeneratedID(LeafExpr): def __init__(self, txnIndex: Union[int, Expr]) -> None: """Create an expression to extract the created ID from a transaction in the current group. - Requires TEAL version 4 or higher. This operation is only permitted in application mode. + Requires program version 4 or higher. This operation is only permitted in application mode. Args: txnIndex: The index of the transaction from which the created ID should be obtained. @@ -41,10 +41,10 @@ def __str__(self): return "(Gaid {})".format(self.txnIndex) def __teal__(self, options: "CompileOptions"): - verifyTealVersion( + verifyProgramVersion( Op.gaid.min_version, options.version, - "TEAL version too low to use Gaid expression", + "Program version too low to use Gaid expression", ) if type(self.txnIndex) is int: diff --git a/pyteal/ast/gaid_test.py b/pyteal/ast/gaid_test.py index 9da4fe254..51e896200 100644 --- a/pyteal/ast/gaid_test.py +++ b/pyteal/ast/gaid_test.py @@ -2,13 +2,13 @@ import pyteal as pt -teal3Options = pt.CompileOptions(version=3) -teal4Options = pt.CompileOptions(version=4) +avm3Options = pt.CompileOptions(version=3) +avm4Options = pt.CompileOptions(version=4) def test_gaid_teal_3(): with pytest.raises(pt.TealInputError): - pt.GeneratedID(0).__teal__(teal3Options) + pt.GeneratedID(0).__teal__(avm3Options) def test_gaid(): @@ -17,7 +17,7 @@ def test_gaid(): expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.gaid, 0)]) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) assert actual == expected @@ -32,7 +32,7 @@ def test_gaid_invalid(): def test_gaid_dynamic_teal_3(): with pytest.raises(pt.TealInputError): - pt.GeneratedID(pt.Int(0)).__teal__(teal3Options) + pt.GeneratedID(pt.Int(0)).__teal__(avm3Options) def test_gaid_dynamic(): @@ -44,7 +44,7 @@ def test_gaid_dynamic(): [pt.TealOp(arg, pt.Op.int, 0), pt.TealOp(expr, pt.Op.gaids)] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) diff --git a/pyteal/ast/gitxn.py b/pyteal/ast/gitxn.py index 486120e07..ad6c5aa06 100644 --- a/pyteal/ast/gitxn.py +++ b/pyteal/ast/gitxn.py @@ -2,7 +2,7 @@ from pyteal.config import MAX_GROUP_SIZE -from pyteal.errors import TealInputError, verifyFieldVersion, verifyTealVersion +from pyteal.errors import TealInputError, verifyFieldVersion, verifyProgramVersion from pyteal.ir import TealOp, Op, TealBlock from pyteal.ast.expr import Expr from pyteal.ast.txn import TxnExpr, TxnField, TxnObject, TxnaExpr @@ -33,10 +33,10 @@ def __str__(self): def __teal__(self, options: "CompileOptions"): verifyFieldVersion(self.field.arg_name, self.field.min_version, options.version) - verifyTealVersion( + verifyProgramVersion( Op.gitxn.min_version, options.version, - "TEAL version too low to use gitxn", + "Program version too low to use gitxn", ) op = TealOp(self, Op.gitxn, self.txnIndex, self.field.arg_name) return TealBlock.FromOp(options, op) @@ -71,10 +71,10 @@ def __teal__(self, options: "CompileOptions"): else: opToUse = Op.gitxnas - verifyTealVersion( + verifyProgramVersion( opToUse.min_version, options.version, - "TEAL version too low to use op {}".format(opToUse), + "Program version too low to use op {}".format(opToUse), ) if type(self.index) is int: diff --git a/pyteal/ast/gitxn_test.py b/pyteal/ast/gitxn_test.py index 44415df83..8501cc4e7 100644 --- a/pyteal/ast/gitxn_test.py +++ b/pyteal/ast/gitxn_test.py @@ -2,8 +2,8 @@ import pyteal as pt -teal5Options = pt.CompileOptions(version=5) -teal6Options = pt.CompileOptions(version=6) +avm5Options = pt.CompileOptions(version=5) +avm6Options = pt.CompileOptions(version=6) def test_gitxn_invalid(): @@ -33,7 +33,7 @@ def test_gitxn_expr_invalid(): pt.TealInputError, ), ( - lambda: pt.GitxnExpr(1, pt.TxnField.sender).__teal__(teal5Options), + lambda: pt.GitxnExpr(1, pt.TxnField.sender).__teal__(avm5Options), pt.TealInputError, ), ]: @@ -42,7 +42,7 @@ def test_gitxn_expr_invalid(): def test_gitxn_expr_valid(): - pt.GitxnExpr(1, pt.TxnField.sender).__teal__(teal6Options) + pt.GitxnExpr(1, pt.TxnField.sender).__teal__(avm6Options) def test_gitxna_expr_invalid(): @@ -63,7 +63,7 @@ def test_gitxna_expr_invalid(): ), ( lambda: pt.GitxnaExpr(0, pt.TxnField.application_args, 0).__teal__( - teal5Options + avm5Options ), pt.TealInputError, ), @@ -74,7 +74,7 @@ def test_gitxna_expr_invalid(): def test_gitxna_valid(): [ - e.__teal__(teal6Options) + e.__teal__(avm6Options) for e in [ pt.GitxnaExpr(0, pt.TxnField.application_args, 1), pt.GitxnaExpr(0, pt.TxnField.application_args, pt.Int(1)), diff --git a/pyteal/ast/gload.py b/pyteal/ast/gload.py index 0c4b86c55..6b85a4725 100644 --- a/pyteal/ast/gload.py +++ b/pyteal/ast/gload.py @@ -2,7 +2,7 @@ from pyteal.types import TealType, require_type from pyteal.ir import TealOp, Op, TealBlock -from pyteal.errors import TealInputError, verifyTealVersion +from pyteal.errors import TealInputError, verifyProgramVersion from pyteal.config import MAX_GROUP_SIZE, NUM_SLOTS from pyteal.ast.expr import Expr from pyteal.ast.leafexpr import LeafExpr @@ -17,7 +17,7 @@ class ImportScratchValue(LeafExpr): def __init__(self, txnIndex: Union[int, Expr], slotId: Union[int, Expr]) -> None: """Create an expression to load a scratch space slot from a transaction in the current group. - Requires TEAL version 4 or higher. This operation is only permitted in application mode. + Requires program version 4 or higher. This operation is only permitted in application mode. Args: txnIndex: The index of the transaction from which the created ID should be obtained. @@ -62,10 +62,10 @@ def __str__(self) -> str: def __teal__(self, options: "CompileOptions"): def local_version_check(opcode: TealOp): - verifyTealVersion( + verifyProgramVersion( opcode.op.min_version, options.version, - "TEAL version too low to use {} experssion".format(opcode.op.name), + "Program version too low to use {} experssion".format(opcode.op.name), ) # For txnIndex and slotId, there are only three scenario as following diff --git a/pyteal/ast/gload_test.py b/pyteal/ast/gload_test.py index fe182af6a..3be3390ea 100644 --- a/pyteal/ast/gload_test.py +++ b/pyteal/ast/gload_test.py @@ -2,25 +2,25 @@ import pyteal as pt -teal3Options = pt.CompileOptions(version=3) -teal4Options = pt.CompileOptions(version=4) -teal6Options = pt.CompileOptions(version=6) +avm3Options = pt.CompileOptions(version=3) +avm4Options = pt.CompileOptions(version=4) +avm6Options = pt.CompileOptions(version=6) def test_gload_teal_3(): with pytest.raises(pt.TealInputError): - pt.ImportScratchValue(0, 1).__teal__(teal3Options) + pt.ImportScratchValue(0, 1).__teal__(avm3Options) with pytest.raises(pt.TealInputError): - pt.ImportScratchValue(pt.Int(0), 1).__teal__(teal3Options) + pt.ImportScratchValue(pt.Int(0), 1).__teal__(avm3Options) with pytest.raises(pt.TealInputError): - pt.ImportScratchValue(pt.Int(0), pt.Int(1)).__teal__(teal3Options) + pt.ImportScratchValue(pt.Int(0), pt.Int(1)).__teal__(avm3Options) def test_gload_teal_4(): with pytest.raises(pt.TealInputError): - pt.ImportScratchValue(pt.Int(0), pt.Int(2)).__teal__(teal4Options) + pt.ImportScratchValue(pt.Int(0), pt.Int(2)).__teal__(avm4Options) def test_gload(): @@ -29,7 +29,7 @@ def test_gload(): expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.gload, 0, 1)]) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) assert actual == expected @@ -43,7 +43,7 @@ def test_gloads(): [pt.TealOp(arg, pt.Op.int, 1), pt.TealOp(expr, pt.Op.gloads, 0)] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -64,7 +64,7 @@ def test_gloadss(): ] ) - actual, _ = expr.__teal__(teal6Options) + actual, _ = expr.__teal__(avm6Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) diff --git a/pyteal/ast/global_.py b/pyteal/ast/global_.py index 9c311e84e..1ed4c313a 100644 --- a/pyteal/ast/global_.py +++ b/pyteal/ast/global_.py @@ -89,7 +89,7 @@ def group_size(cls) -> "Global": @classmethod def logic_sig_version(cls) -> "Global": - """Get the maximum supported TEAL version.""" + """Get the maximum supported program version.""" return cls(GlobalField.logic_sig_version) @classmethod @@ -115,14 +115,14 @@ def current_application_id(cls) -> "Global": def creator_address(cls) -> "Global": """Address of the creator of the current application. - Fails during Signature mode. Requires TEAL version 3 or higher.""" + Fails during Signature mode. Requires program version 3 or higher.""" return cls(GlobalField.creator_address) @classmethod def current_application_address(cls) -> "Global": """Get the address of that the current application controls. - Fails during Signature mode. Requires TEAL version 5 or higher.""" + Fails during Signature mode. Requires program version 5 or higher.""" return cls(GlobalField.current_app_address) @classmethod @@ -131,14 +131,14 @@ def group_id(cls) -> "Global": If the current transaction is not part of a group, this will return 32 zero bytes. - Requires TEAL version 5 or higher.""" + Requires program version 5 or higher.""" return cls(GlobalField.group_id) @classmethod def opcode_budget(cls) -> "Global": """Get the remaining opcode execution budget - Requires TEAL version 6 or higher.""" + Requires program version 6 or higher.""" return cls(GlobalField.opcode_budget) @classmethod @@ -147,7 +147,7 @@ def caller_app_id(cls) -> "Global": If not called from another app, this will return 0 - Requires TEAL version 6 or higher.""" + Requires program version 6 or higher.""" return cls(GlobalField.caller_app_id) @classmethod @@ -156,7 +156,7 @@ def caller_app_address(cls) -> "Global": If not called from another app, this will return the ZeroAddress - Requires TEAL version 6 or higher.""" + Requires program version 6 or higher.""" return cls(GlobalField.caller_app_address) diff --git a/pyteal/ast/global_test.py b/pyteal/ast/global_test.py index 4e72b3bd1..b06addefb 100644 --- a/pyteal/ast/global_test.py +++ b/pyteal/ast/global_test.py @@ -2,10 +2,10 @@ import pyteal as pt -teal2Options = pt.CompileOptions(version=2) -teal3Options = pt.CompileOptions(version=3) -teal5Options = pt.CompileOptions(version=5) -teal6Options = pt.CompileOptions(version=6) +avm2Options = pt.CompileOptions(version=2) +avm3Options = pt.CompileOptions(version=3) +avm5Options = pt.CompileOptions(version=5) +avm6Options = pt.CompileOptions(version=6) def test_global_min_txn_fee(): @@ -14,7 +14,7 @@ def test_global_min_txn_fee(): expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.global_, "MinTxnFee")]) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) assert actual == expected @@ -25,7 +25,7 @@ def test_global_min_balance(): expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.global_, "MinBalance")]) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) assert actual == expected @@ -36,7 +36,7 @@ def test_global_max_txn_life(): expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.global_, "MaxTxnLife")]) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) assert actual == expected @@ -47,7 +47,7 @@ def test_global_zero_address(): expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.global_, "ZeroAddress")]) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) assert actual == expected @@ -58,7 +58,7 @@ def test_global_group_size(): expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.global_, "GroupSize")]) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) assert actual == expected @@ -69,7 +69,7 @@ def test_global_logic_sig_version(): expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.global_, "LogicSigVersion")]) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) assert actual == expected @@ -80,7 +80,7 @@ def test_global_round(): expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.global_, "Round")]) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) assert actual == expected @@ -91,7 +91,7 @@ def test_global_latest_timestamp(): expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.global_, "LatestTimestamp")]) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) assert actual == expected @@ -104,7 +104,7 @@ def test_global_current_application_id(): [pt.TealOp(expr, pt.Op.global_, "CurrentApplicationID")] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) assert actual == expected @@ -115,12 +115,12 @@ def test_global_creator_address(): expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.global_, "CreatorAddress")]) - actual, _ = expr.__teal__(teal3Options) + actual, _ = expr.__teal__(avm3Options) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal2Options) + expr.__teal__(avm2Options) def test_global_current_application_address(): @@ -131,12 +131,12 @@ def test_global_current_application_address(): [pt.TealOp(expr, pt.Op.global_, "CurrentApplicationAddress")] ) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal3Options) + expr.__teal__(avm3Options) def test_global_group_id(): @@ -145,12 +145,12 @@ def test_global_group_id(): expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.global_, "GroupID")]) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal3Options) + expr.__teal__(avm3Options) def test_global_opcode_budget(): @@ -159,12 +159,12 @@ def test_global_opcode_budget(): expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.global_, "OpcodeBudget")]) - actual, _ = expr.__teal__(teal6Options) + actual, _ = expr.__teal__(avm6Options) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal5Options) + expr.__teal__(avm5Options) def test_global_caller_application_id(): @@ -175,12 +175,12 @@ def test_global_caller_application_id(): [pt.TealOp(expr, pt.Op.global_, "CallerApplicationID")] ) - actual, _ = expr.__teal__(teal6Options) + actual, _ = expr.__teal__(avm6Options) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal5Options) + expr.__teal__(avm5Options) def test_global_caller_app_address(): @@ -191,9 +191,9 @@ def test_global_caller_app_address(): [pt.TealOp(expr, pt.Op.global_, "CallerApplicationAddress")] ) - actual, _ = expr.__teal__(teal6Options) + actual, _ = expr.__teal__(avm6Options) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal5Options) + expr.__teal__(avm5Options) diff --git a/pyteal/ast/gtxn.py b/pyteal/ast/gtxn.py index 640f0605d..f1f59a95d 100644 --- a/pyteal/ast/gtxn.py +++ b/pyteal/ast/gtxn.py @@ -2,7 +2,7 @@ from pyteal.types import TealType, require_type from pyteal.ir import TealOp, Op, TealBlock -from pyteal.errors import TealInputError, verifyFieldVersion, verifyTealVersion +from pyteal.errors import TealInputError, verifyFieldVersion, verifyProgramVersion from pyteal.config import MAX_GROUP_SIZE from pyteal.ast.expr import Expr from pyteal.ast.txn import TxnField, TxnExpr, TxnaExpr, TxnObject @@ -35,16 +35,18 @@ def __teal__(self, options: "CompileOptions"): verifyFieldVersion(self.field.arg_name, self.field.min_version, options.version) if type(self.txnIndex) is int: - verifyTealVersion( - Op.gtxn.min_version, options.version, "TEAL version too low to use gtxn" + verifyProgramVersion( + Op.gtxn.min_version, + options.version, + "Program version too low to use gtxn", ) op = TealOp(self, Op.gtxn, self.txnIndex, self.field.arg_name) return TealBlock.FromOp(options, op) - verifyTealVersion( + verifyProgramVersion( Op.gtxns.min_version, options.version, - "TEAL version too low to index Gtxn with dynamic values", + "Program version too low to index Gtxn with dynamic values", ) op = TealOp(self, Op.gtxns, self.field.arg_name) @@ -83,10 +85,10 @@ def __teal__(self, options: "CompileOptions"): else: opToUse = Op.gtxnsas - verifyTealVersion( + verifyProgramVersion( opToUse.min_version, options.version, - "TEAL version too low to use op {}".format(opToUse), + "Program version too low to use op {}".format(opToUse), ) if type(self.txnIndex) is int: diff --git a/pyteal/ast/gtxn_test.py b/pyteal/ast/gtxn_test.py index 9f48e5317..a42135bec 100644 --- a/pyteal/ast/gtxn_test.py +++ b/pyteal/ast/gtxn_test.py @@ -2,7 +2,7 @@ import pyteal as pt -teal6Options = pt.CompileOptions(version=6) +avm6Options = pt.CompileOptions(version=6) def test_gtxn_invalid(): @@ -29,7 +29,7 @@ def test_gtxn_expr_invalid(): def test_gtxn_expr_valid(): [ - e.__teal__(teal6Options) + e.__teal__(avm6Options) for e in [ pt.GtxnExpr(1, pt.TxnField.sender), pt.GtxnExpr(pt.Int(1), pt.TxnField.sender), @@ -62,7 +62,7 @@ def test_gtxna_expr_invalid(): def test_gtxna_expr_valid(): [ - e.__teal__(teal6Options) + e.__teal__(avm6Options) for e in [ pt.GtxnaExpr(1, pt.TxnField.assets, 1), pt.GtxnaExpr(pt.Int(1), pt.TxnField.assets, pt.Int(1)), diff --git a/pyteal/ast/itxn.py b/pyteal/ast/itxn.py index af7704198..b2cfae2ad 100644 --- a/pyteal/ast/itxn.py +++ b/pyteal/ast/itxn.py @@ -2,7 +2,7 @@ from typing import Dict, TYPE_CHECKING, List, Union, cast from pyteal.types import TealType, require_type -from pyteal.errors import TealInputError, verifyTealVersion +from pyteal.errors import TealInputError, verifyProgramVersion from pyteal.ir import TealOp, Op, TealBlock from pyteal.ast.expr import Expr from pyteal.ast.txn import TxnField, TxnExprBuilder, TxnaExprBuilder, TxnObject @@ -32,10 +32,10 @@ def __str__(self): def __teal__(self, options: "CompileOptions"): op = self.action.value - verifyTealVersion( + verifyProgramVersion( op.min_version, options.version, - "TEAL version too low to create inner transactions", + "Program version too low to create inner transactions", ) return TealBlock.FromOp(options, TealOp(self, op)) @@ -58,10 +58,10 @@ def __str__(self): return "(InnerTxnSetField {} {})".format(self.field.arg_name, self.value) def __teal__(self, options: "CompileOptions"): - verifyTealVersion( + verifyProgramVersion( Op.itxn_field.min_version, options.version, - "TEAL version too low to create inner transactions", + "Program version too low to create inner transactions", ) return TealBlock.FromOp( @@ -81,7 +81,7 @@ class InnerTxnBuilder: Inner transactions are transactions which applications can dynamically create. Each inner transaction will appear as a transaction inside of the current transaction being executed. - As of TEAL version 5, only the transaction types :any:`TxnType.Payment`, :any:`TxnType.AssetTransfer`, + As of program version 5, only the transaction types :any:`TxnType.Payment`, :any:`TxnType.AssetTransfer`, :any:`TxnType.AssetConfig`, and :any:`TxnType.AssetFreeze` are allowed. Additionally, not all fields are allowed to be set. For example, it is not currently allowed to set the rekeyTo field of an inner transaction. @@ -96,7 +96,7 @@ def Begin(cls) -> Expr: overpaying in earlier transactions; :code:`FirstValid`/:code:`LastValid` to the values in the top-level transaction, and all other fields to zero values. - Requires TEAL version 5 or higher. This operation is only permitted in application mode. + Requires program version 5 or higher. This operation is only permitted in application mode. """ return InnerTxnActionExpr(InnerTxnAction.Begin) @@ -109,7 +109,7 @@ def Next(cls) -> Expr: overpaying in earlier transactions; :code:`FirstValid`/:code:`LastValid` to the values in the top-level transaction, and all other fields to zero values. - Requires TEAL version 6 or higher. This operation is only permitted in application mode. + Requires program version 6 or higher. This operation is only permitted in application mode. """ return InnerTxnActionExpr(InnerTxnAction.Next) @@ -130,7 +130,7 @@ def Submit(cls) -> Expr: If the inner transaction creates an asset, the new asset ID can be found by looking at :any:`InnerTxn.created_asset_id() `. - Requires TEAL version 5 or higher. This operation is only permitted in application mode. + Requires program version 5 or higher. This operation is only permitted in application mode. """ return InnerTxnActionExpr(InnerTxnAction.Submit) @@ -144,7 +144,7 @@ def SetField(cls, field: TxnField, value: Union[Expr, List[Expr]]) -> Expr: Note: For non-array field (e.g., note), setting it twice will overwrite the original value. While for array field (e.g., accounts), setting it multiple times will append the values. - Requires TEAL version 5 or higher. This operation is only permitted in application mode. + Requires program version 5 or higher. This operation is only permitted in application mode. Args: field: The field to set on the inner transaction. @@ -180,6 +180,27 @@ def SetField(cls, field: TxnField, value: Union[Expr, List[Expr]]) -> Expr: ] ) + @classmethod + def Execute(cls, fields: Dict[TxnField, Union[Expr, List[Expr]]]) -> Expr: + """Performs a single transaction given fields passed in. + + A convenience method that accepts fields to submit a single inner transaction, which is equivalent to: + + .. code-block:: python + + InnerTxnBuilder.Begin() + InnerTxnBuilder.SetFields(fields) + InnerTxnBuilder.End() + + Requires program version 5 or higher. This operation is only permitted in application mode. + + Args: + fields: A dictionary whose keys are fields to set and whose values are the value each + field should take. Each value must evaluate to a type that is compatible with the + field being set. + """ + return Seq(cls.Begin(), cls.SetFields(fields), cls.Submit()) + @classmethod def SetFields(cls, fields: Dict[TxnField, Union[Expr, List[Expr]]]) -> Expr: """Set multiple fields of the current inner transaction. @@ -190,7 +211,7 @@ def SetFields(cls, fields: Dict[TxnField, Union[Expr, List[Expr]]]) -> Expr: Note: For non-array field (e.g., note), setting it twice will overwrite the original value. While for array field (e.g., accounts), setting it multiple times will append the values. - Requires TEAL version 5 or higher. This operation is only permitted in application mode. + Requires program version 5 or higher. This operation is only permitted in application mode. Args: fields: A dictionary whose keys are fields to set and whose values are the value each diff --git a/pyteal/ast/itxn_test.py b/pyteal/ast/itxn_test.py index af9c3a79c..b4da1b7dc 100644 --- a/pyteal/ast/itxn_test.py +++ b/pyteal/ast/itxn_test.py @@ -3,9 +3,9 @@ import pyteal as pt from pyteal.types import types_match -teal4Options = pt.CompileOptions(version=4) -teal5Options = pt.CompileOptions(version=5) -teal6Options = pt.CompileOptions(version=6) +avm4Options = pt.CompileOptions(version=4) +avm5Options = pt.CompileOptions(version=5) +avm6Options = pt.CompileOptions(version=6) def test_InnerTxnBuilder_Begin(): @@ -15,12 +15,12 @@ def test_InnerTxnBuilder_Begin(): expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.itxn_begin)]) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal4Options) + expr.__teal__(avm4Options) def test_InnerTxnBuilder_Submit(): @@ -30,12 +30,12 @@ def test_InnerTxnBuilder_Submit(): expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.itxn_submit)]) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal4Options) + expr.__teal__(avm4Options) def test_InnerTxnBuilder_Next(): @@ -45,12 +45,12 @@ def test_InnerTxnBuilder_Next(): expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.itxn_next)]) - actual, _ = expr.__teal__(teal6Options) + actual, _ = expr.__teal__(avm6Options) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal5Options) + expr.__teal__(avm5Options) def test_InnerTxnBuilder_SetField(): @@ -82,47 +82,48 @@ def test_InnerTxnBuilder_SetField(): ] ) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal4Options) - - -def test_InnerTxnBuilder_SetFields(): - cases = ( - ({}, pt.Seq()), - ( - {pt.TxnField.amount: pt.Int(5)}, - pt.InnerTxnBuilder.SetField(pt.TxnField.amount, pt.Int(5)), - ), - ( - { - pt.TxnField.type_enum: pt.TxnType.Payment, - pt.TxnField.close_remainder_to: pt.Txn.sender(), - }, - pt.Seq( - pt.InnerTxnBuilder.SetField(pt.TxnField.type_enum, pt.TxnType.Payment), - pt.InnerTxnBuilder.SetField( - pt.TxnField.close_remainder_to, pt.Txn.sender() - ), + expr.__teal__(avm4Options) + + +ITXN_FIELDS_CASES = [ + ({}, pt.Seq()), + ( + {pt.TxnField.amount: pt.Int(5)}, + pt.InnerTxnBuilder.SetField(pt.TxnField.amount, pt.Int(5)), + ), + ( + { + pt.TxnField.type_enum: pt.TxnType.Payment, + pt.TxnField.close_remainder_to: pt.Txn.sender(), + }, + pt.Seq( + pt.InnerTxnBuilder.SetField(pt.TxnField.type_enum, pt.TxnType.Payment), + pt.InnerTxnBuilder.SetField( + pt.TxnField.close_remainder_to, pt.Txn.sender() ), ), - ) + ), +] + - for fields, expectedExpr in cases: +def test_InnerTxnBuilder_SetFields(): + for fields, expectedExpr in ITXN_FIELDS_CASES: expr = pt.InnerTxnBuilder.SetFields(fields) assert expr.type_of() == pt.TealType.none assert not expr.has_return() - expected, _ = expectedExpr.__teal__(teal5Options) + expected, _ = expectedExpr.__teal__(avm5Options) expected.addIncoming() expected = pt.TealBlock.NormalizeBlocks(expected) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -131,7 +132,30 @@ def test_InnerTxnBuilder_SetFields(): if len(fields) != 0: with pytest.raises(pt.TealInputError): - expr.__teal__(teal4Options) + expr.__teal__(avm4Options) + + +def test_InnerTxnBuilder_Execute(): + for fields, expectedExpr in ITXN_FIELDS_CASES: + expr = pt.InnerTxnBuilder.Execute(fields) + + expected, _ = pt.Seq( + pt.InnerTxnBuilder.Begin(), + expectedExpr, + pt.InnerTxnBuilder.Submit(), + ).__teal__(avm5Options) + expected.addIncoming() + expected = pt.TealBlock.NormalizeBlocks(expected) + + actual, _ = expr.__teal__(avm5Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + with pytest.raises(pt.TealInputError): + expr.__teal__(avm4Options) # txn_test.py performs additional testing diff --git a/pyteal/ast/jsonref.py b/pyteal/ast/jsonref.py index 91018d714..5173218c4 100644 --- a/pyteal/ast/jsonref.py +++ b/pyteal/ast/jsonref.py @@ -2,10 +2,9 @@ from enum import Enum from pyteal.types import TealType, require_type -from pyteal.errors import verifyFieldVersion, verifyTealVersion +from pyteal.errors import verifyFieldVersion, verifyProgramVersion from pyteal.ir import TealOp, Op, TealBlock from pyteal.ast.expr import Expr -from pyteal.ast.leafexpr import LeafExpr if TYPE_CHECKING: from pyteal.compiler import CompileOptions @@ -32,7 +31,7 @@ def type_of(self) -> TealType: JsonRefType.__module__ = "pyteal" -class JsonRef(LeafExpr): +class JsonRef(Expr): """An expression that accesses the value associated with a given key from a supported utf-8 encoded json object. The json object must satisfy a `particular specification `_. @@ -50,10 +49,10 @@ def __init__(self, type: JsonRefType, json_obj: Expr, key: Expr) -> None: self.key = key def __teal__(self, options: "CompileOptions"): - verifyTealVersion( + verifyProgramVersion( Op.json_ref.min_version, options.version, - "TEAL version too low to use op json_ref", + "Program version too low to use op json_ref", ) verifyFieldVersion(self.type.arg_name, self.type.min_version, options.version) @@ -62,13 +61,16 @@ def __teal__(self, options: "CompileOptions"): return TealBlock.FromOp(options, op, self.json_obj, self.key) def __str__(self): - return "(JsonRef {})".format(self.type.arg_name) + return "(JsonRef {} {} {})".format(self.type.arg_name, self.json_obj, self.key) def type_of(self): return self.type.type_of() + def has_return(self): + return False + @classmethod - def as_string(cls, json_obj: Expr, key: Expr) -> "JsonRef": + def as_string(cls, json_obj: Expr, key: Expr) -> Expr: """Access the value of a given key as a string. Refer to the `JsonRef` class documentation for valid json specification. @@ -80,7 +82,7 @@ def as_string(cls, json_obj: Expr, key: Expr) -> "JsonRef": return cls(JsonRefType.string, json_obj, key) @classmethod - def as_uint64(cls, json_obj: Expr, key: Expr) -> "JsonRef": + def as_uint64(cls, json_obj: Expr, key: Expr) -> Expr: """Access the value of a given key as a uint64. Refer to the `JsonRef` class documentation for valid json specification. diff --git a/pyteal/ast/jsonref_test.py b/pyteal/ast/jsonref_test.py index 356c74591..10733952f 100644 --- a/pyteal/ast/jsonref_test.py +++ b/pyteal/ast/jsonref_test.py @@ -2,8 +2,8 @@ import pyteal as pt -teal6Options = pt.CompileOptions(version=6) -teal7Options = pt.CompileOptions(version=7) +avm6Options = pt.CompileOptions(version=6) +avm7Options = pt.CompileOptions(version=7) def test_json_string(): @@ -19,14 +19,14 @@ def test_json_string(): ] ) - actual, _ = expr.__teal__(teal7Options) + actual, _ = expr.__teal__(avm7Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal6Options) + expr.__teal__(avm6Options) def test_json_uint64(): @@ -42,14 +42,14 @@ def test_json_uint64(): ] ) - actual, _ = expr.__teal__(teal7Options) + actual, _ = expr.__teal__(avm7Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal6Options) + expr.__teal__(avm6Options) def test_json_object(): @@ -65,14 +65,14 @@ def test_json_object(): ] ) - actual, _ = expr.__teal__(teal7Options) + actual, _ = expr.__teal__(avm7Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal6Options) + expr.__teal__(avm6Options) def test_json_ref_invalid(): diff --git a/pyteal/ast/maybe.py b/pyteal/ast/maybe.py index ebe3d03e7..4d863e6ff 100644 --- a/pyteal/ast/maybe.py +++ b/pyteal/ast/maybe.py @@ -1,6 +1,6 @@ from typing import List, Union, TYPE_CHECKING -from pyteal.errors import verifyTealVersion +from pyteal.errors import verifyProgramVersion from pyteal.types import TealType from pyteal.ir import Op @@ -33,7 +33,7 @@ def __init__( """ def local_version_check(option: "CompileOptions"): - verifyTealVersion( + verifyProgramVersion( minVersion=op.min_version, version=option.version, msg=f"{op.value} unavailable", diff --git a/pyteal/ast/opup.py b/pyteal/ast/opup.py index 45a84d37d..af862b10b 100644 --- a/pyteal/ast/opup.py +++ b/pyteal/ast/opup.py @@ -29,14 +29,14 @@ class OpUpMode(Enum): OnCall = 1 -ON_CALL_APP = Bytes("base16", "068101") # v6 teal program "int 1" +ON_CALL_APP = Bytes("base16", "068101") # v6 program "int 1" MIN_TXN_FEE = Int(1000) class OpUp: """Utility for increasing opcode budget during app execution. - Requires TEAL version 6 or higher. + Requires program version 6 or higher. Example: .. code-block:: python diff --git a/pyteal/ast/replace.py b/pyteal/ast/replace.py index 2b7968978..fa625df8a 100644 --- a/pyteal/ast/replace.py +++ b/pyteal/ast/replace.py @@ -1,7 +1,7 @@ from typing import cast, TYPE_CHECKING from pyteal.types import TealType, require_type -from pyteal.errors import verifyTealVersion +from pyteal.errors import verifyProgramVersion from pyteal.ir import TealOp, Op, TealBlock from pyteal.ast.expr import Expr from pyteal.ast.int import Int @@ -46,10 +46,10 @@ def __teal__(self, options: "CompileOptions"): op = self.__get_op(options) - verifyTealVersion( + verifyProgramVersion( op.min_version, options.version, - "TEAL version too low to use op {}".format(op), + "Program version too low to use op {}".format(op), ) s = cast(Int, self.start).value @@ -76,7 +76,7 @@ def Replace(original: Expr, start: Expr, replacement: Expr) -> Expr: """ Replace a portion of original bytes with new bytes at a given starting point. - Requires TEAL version 7 or higher. + Requires program version 7 or higher. Args: original: The value containing the original bytes. Must evaluate to bytes. diff --git a/pyteal/ast/replace_test.py b/pyteal/ast/replace_test.py index b72a5d879..5f7076ed0 100644 --- a/pyteal/ast/replace_test.py +++ b/pyteal/ast/replace_test.py @@ -2,8 +2,8 @@ import pyteal as pt -teal6Options = pt.CompileOptions(version=6) -teal7Options = pt.CompileOptions(version=7) +avm6Options = pt.CompileOptions(version=6) +avm7Options = pt.CompileOptions(version=7) def test_replace_immediate(): @@ -19,14 +19,14 @@ def test_replace_immediate(): ] ) - actual, _ = expr.__teal__(teal7Options) + actual, _ = expr.__teal__(avm7Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal6Options) + expr.__teal__(avm6Options) def test_replace_stack_int(): @@ -44,14 +44,14 @@ def test_replace_stack_int(): ] ) - actual, _ = expr.__teal__(teal7Options) + actual, _ = expr.__teal__(avm7Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal6Options) + expr.__teal__(avm6Options) # Mirrors `test_replace_stack_int`, but attempts replacement with start != pt.Int. @@ -73,7 +73,7 @@ def test_replace_stack_not_int(): ] ) - actual, _ = expr.__teal__(teal7Options) + actual, _ = expr.__teal__(avm7Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -81,7 +81,7 @@ def test_replace_stack_not_int(): assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal6Options) + expr.__teal__(avm6Options) def test_replace_invalid(): diff --git a/pyteal/ast/return_.py b/pyteal/ast/return_.py index be27155fe..0540a4ff2 100644 --- a/pyteal/ast/return_.py +++ b/pyteal/ast/return_.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING from pyteal.types import TealType, require_type, types_match -from pyteal.errors import verifyTealVersion, TealCompileError +from pyteal.errors import verifyProgramVersion, TealCompileError from pyteal.ir import TealOp, Op, TealBlock from pyteal.ast.expr import Expr from pyteal.ast.int import Int @@ -31,10 +31,10 @@ def __init__(self, value: Expr = None) -> None: def __teal__(self, options: "CompileOptions"): if options.currentSubroutine is not None: - verifyTealVersion( + verifyProgramVersion( Op.retsub.min_version, options.version, - "TEAL version too low to use subroutines", + "Program version too low to use subroutines", ) returnType = options.currentSubroutine.returnType if returnType == TealType.none: diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 657134bee..026f1c27d 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -2,7 +2,7 @@ from types import MappingProxyType from typing import Callable, List, Optional, Type, Union, TYPE_CHECKING -from pyteal.errors import TealInputError, verifyTealVersion +from pyteal.errors import TealInputError, verifyProgramVersion from pyteal.ir import TealOp, Op, TealBlock from pyteal.types import TealType @@ -263,10 +263,10 @@ def __teal__(self, options: "CompileOptions"): 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 DynamicScratchVar for subroutine evaluation """ - verifyTealVersion( + verifyProgramVersion( Op.callsub.min_version, options.version, - "TEAL version too low to use SubroutineCall expression", + "Program version too low to use SubroutineCall expression", ) def handle_arg(arg): diff --git a/pyteal/ast/substring.py b/pyteal/ast/substring.py index 6febf69dc..fe4ce4809 100644 --- a/pyteal/ast/substring.py +++ b/pyteal/ast/substring.py @@ -1,7 +1,7 @@ from typing import cast, TYPE_CHECKING from pyteal.types import TealType, require_type -from pyteal.errors import TealCompileError, verifyTealVersion +from pyteal.errors import TealCompileError, verifyProgramVersion from pyteal.ir import TealOp, Op, TealBlock, TealSimpleBlock from pyteal.ast.expr import Expr from pyteal.ast.int import Int @@ -60,10 +60,10 @@ def __teal__(self, options: "CompileOptions"): op = self.__get_op(options) - verifyTealVersion( + verifyProgramVersion( op.min_version, options.version, - "TEAL version too low to use op {}".format(op), + "Program version too low to use op {}".format(op), ) start, end = cast(Int, self.startArg).value, cast(Int, self.endArg).value @@ -141,10 +141,10 @@ def __teal__(self, options: "CompileOptions"): op = self.__get_op(options) - verifyTealVersion( + verifyProgramVersion( op.min_version, options.version, - "TEAL version too low to use op {}".format(op), + "Program version too low to use op {}".format(op), ) s, l = cast(Int, self.startArg).value, cast(Int, self.lenArg).value @@ -199,10 +199,10 @@ def __get_op(self, options: "CompileOptions"): def __teal__(self, options: "CompileOptions"): op = self.__get_op(options) - verifyTealVersion( + verifyProgramVersion( op.min_version, options.version, - "TEAL version too low to use op {}".format(op), + "Program version too low to use op {}".format(op), ) if op == Op.extract: @@ -248,7 +248,7 @@ def Substring(string: Expr, start: Expr, end: Expr) -> Expr: This expression is similar to :any:`Extract`, except this expression uses start and end indexes, while :code:`Extract` uses a start index and length. - Requires TEAL version 2 or higher. + Requires program version 2 or higher. Args: string: The byte string. @@ -273,7 +273,7 @@ def Extract(string: Expr, start: Expr, length: Expr) -> Expr: This expression is similar to :any:`Substring`, except this expression uses a start index and length, while :code:`Substring` uses start and end indexes. - Requires TEAL version 5 or higher. + Requires program version 5 or higher. Args: string: The byte string. @@ -296,7 +296,7 @@ def Suffix(string: Expr, start: Expr) -> Expr: This expression is similar to :any:`Substring` and :any:`Extract`, except this expression only uses a start index. - Requires TEAL version 5 or higher. + Requires program version 5 or higher. Args: string: The byte string. diff --git a/pyteal/ast/substring_test.py b/pyteal/ast/substring_test.py index ae5bcace2..3e10c5f51 100644 --- a/pyteal/ast/substring_test.py +++ b/pyteal/ast/substring_test.py @@ -2,10 +2,10 @@ import pyteal as pt -teal2Options = pt.CompileOptions(version=2) -teal3Options = pt.CompileOptions(version=3) -teal4Options = pt.CompileOptions(version=4) -teal5Options = pt.CompileOptions(version=5) +avm2Options = pt.CompileOptions(version=2) +avm3Options = pt.CompileOptions(version=3) +avm4Options = pt.CompileOptions(version=4) +avm5Options = pt.CompileOptions(version=5) def test_substring_immediate_v2(): @@ -20,7 +20,7 @@ def test_substring_immediate_v2(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -39,7 +39,7 @@ def test_substring_immediate_v5(): ] ) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -59,7 +59,7 @@ def test_substring_to_extract(): ] ) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -82,7 +82,7 @@ def test_substring_stack_v2(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -105,7 +105,7 @@ def test_substring_stack_v5(): ] ) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -126,7 +126,7 @@ def test_zero_length_substring_immediate(): ] ) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -145,7 +145,7 @@ def test_substring_invalid(): pt.Substring(pt.Bytes("my string"), pt.Int(0), pt.Txn.sender()) with pytest.raises(Exception): - pt.Substring(pt.Bytes("my string"), pt.Int(1), pt.Int(0)).__teal__(teal5Options) + pt.Substring(pt.Bytes("my string"), pt.Int(1), pt.Int(0)).__teal__(avm5Options) def test_extract_immediate(): @@ -160,14 +160,14 @@ def test_extract_immediate(): ] ) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal4Options) + expr.__teal__(avm4Options) def test_extract_zero(): @@ -184,14 +184,14 @@ def test_extract_zero(): ] ) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal4Options) + expr.__teal__(avm4Options) def test_extract_stack(): @@ -209,14 +209,14 @@ def test_extract_stack(): ] ) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal4Options) + expr.__teal__(avm4Options) def test_extract_invalid(): @@ -242,7 +242,7 @@ def test_suffix_immediate(): ] ) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -265,7 +265,7 @@ def test_suffix_stack(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -301,7 +301,7 @@ def generate_expr() -> pt.Expr: ] ) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -310,7 +310,7 @@ def generate_expr() -> pt.Expr: if op == pt.Op.extract3: with pytest.raises(pt.TealInputError): - expr.__teal__(teal4Options) + expr.__teal__(avm4Options) @pytest.mark.parametrize("op", [pt.Op.extract3, pt.Op.substring3]) @@ -342,7 +342,7 @@ def generate_expr() -> pt.Expr: ] ) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -351,7 +351,7 @@ def generate_expr() -> pt.Expr: if op == pt.Op.extract3: with pytest.raises(pt.TealInputError): - expr.__teal__(teal4Options) + expr.__teal__(avm4Options) def test_suffix_invalid(): diff --git a/pyteal/ast/ternaryexpr.py b/pyteal/ast/ternaryexpr.py index ad2c36bd6..a8a526cff 100644 --- a/pyteal/ast/ternaryexpr.py +++ b/pyteal/ast/ternaryexpr.py @@ -1,7 +1,7 @@ from typing import Tuple, TYPE_CHECKING from pyteal.types import TealType, require_type -from pyteal.errors import verifyTealVersion +from pyteal.errors import verifyProgramVersion from pyteal.ir import TealOp, Op, TealBlock from pyteal.ast.expr import Expr @@ -33,10 +33,10 @@ def __init__( self.thirdArg = thirdArg def __teal__(self, options: "CompileOptions"): - verifyTealVersion( + verifyProgramVersion( self.op.min_version, options.version, - "TEAL version too low to use op {}".format(self.op), + "Program version too low to use op {}".format(self.op), ) return TealBlock.FromOp( @@ -106,7 +106,7 @@ def SetBit(value: Expr, index: Expr, newBitValue: Expr) -> TernaryExpr: * For byte strings, bit indexing begins at the first byte. For example, :code:`SetBit(Bytes("base16", "0x00"), Int(7), Int(1))` yields the byte string 0x01. Any integer less than 8*Len(value) is a valid index. - Requires TEAL version 3 or higher. + Requires program version 3 or higher. Args: value: The value containing bits. Can evaluate to any type. @@ -129,7 +129,7 @@ def SetByte(value: Expr, index: Expr, newByteValue: Expr) -> TernaryExpr: Similar to SetBit, indexing begins at the first byte. For example, :code:`SetByte(Bytes("base16", "0x000000"), Int(0), Int(255))` yields the byte string 0xff0000. - Requires TEAL version 3 or higher. + Requires program version 3 or higher. Args: value: The value containing the bytes. Must evaluate to bytes. @@ -150,7 +150,7 @@ def Divw(hi: Expr, lo: Expr, y: Expr) -> TernaryExpr: """ Performs wide division by interpreting `hi` and `lo` as a uint128 value. - Requires TEAL version 6 or higher. + Requires program version 6 or higher. Args: hi: Quotient's high 64 bits. Must evaluate to uint64. diff --git a/pyteal/ast/ternaryexpr_test.py b/pyteal/ast/ternaryexpr_test.py index 51a01958a..0530ec6b6 100644 --- a/pyteal/ast/ternaryexpr_test.py +++ b/pyteal/ast/ternaryexpr_test.py @@ -2,12 +2,12 @@ import pyteal as pt -teal2Options = pt.CompileOptions(version=2) -teal3Options = pt.CompileOptions(version=3) -teal4Options = pt.CompileOptions(version=4) -teal5Options = pt.CompileOptions(version=5) -teal6Options = pt.CompileOptions(version=6) -teal7Options = pt.CompileOptions(version=7) +avm2Options = pt.CompileOptions(version=2) +avm3Options = pt.CompileOptions(version=3) +avm4Options = pt.CompileOptions(version=4) +avm5Options = pt.CompileOptions(version=5) +avm6Options = pt.CompileOptions(version=6) +avm7Options = pt.CompileOptions(version=7) def test_ed25519verify(): @@ -24,7 +24,7 @@ def test_ed25519verify(): ] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -56,14 +56,14 @@ def test_ed25519verify_bare(): ] ) - actual, _ = expr.__teal__(teal7Options) + actual, _ = expr.__teal__(avm7Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal6Options) + expr.__teal__(avm6Options) def test_ed25519verify_bare_invalid(): @@ -91,14 +91,14 @@ def test_set_bit_int(): ] ) - actual, _ = expr.__teal__(teal3Options) + actual, _ = expr.__teal__(avm3Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal2Options) + expr.__teal__(avm2Options) def test_set_bit_bytes(): @@ -115,14 +115,14 @@ def test_set_bit_bytes(): ] ) - actual, _ = expr.__teal__(teal3Options) + actual, _ = expr.__teal__(avm3Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal2Options) + expr.__teal__(avm2Options) def test_set_bit_invalid(): @@ -153,14 +153,14 @@ def test_set_byte(): ] ) - actual, _ = expr.__teal__(teal3Options) + actual, _ = expr.__teal__(avm3Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal2Options) + expr.__teal__(avm2Options) def test_set_byte_invalid(): @@ -188,7 +188,7 @@ def test_divw(): ] ) - actual, _ = expr.__teal__(teal6Options) + actual, _ = expr.__teal__(avm6Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) diff --git a/pyteal/ast/txn.py b/pyteal/ast/txn.py index a4d011ddf..d8de1bd84 100644 --- a/pyteal/ast/txn.py +++ b/pyteal/ast/txn.py @@ -6,7 +6,7 @@ TealInputError, TealCompileError, verifyFieldVersion, - verifyTealVersion, + verifyProgramVersion, ) from pyteal.ir import TealOp, Op, TealBlock from pyteal.ast.leafexpr import LeafExpr @@ -159,10 +159,10 @@ def __str__(self): def __teal__(self, options: "CompileOptions"): verifyFieldVersion(self.field.arg_name, self.field.min_version, options.version) - verifyTealVersion( + verifyProgramVersion( self.op.min_version, options.version, - "TEAL version too low to use op {}".format(self.op), + "Program version too low to use op {}".format(self.op), ) op = TealOp(self, self.op, self.field.arg_name) @@ -216,10 +216,10 @@ def __teal__(self, options: "CompileOptions"): if opToUse is None: raise TealCompileError("Dynamic array indexing not supported", self) - verifyTealVersion( + verifyProgramVersion( opToUse.min_version, options.version, - "TEAL version too low to use op {}".format(opToUse), + "Program version too low to use op {}".format(opToUse), ) if type(self.index) is int: @@ -426,7 +426,7 @@ def nonparticipation(self) -> TxnExpr: For more information, see https://developer.algorand.org/docs/reference/transactions/#nonparticipation - Requires TEAL version 5 or higher. + Requires program version 5 or higher. """ return self.makeTxnExpr(TxnField.nonparticipation) @@ -643,7 +643,7 @@ def created_asset_id(self) -> TxnExpr: Only set when :any:`type_enum()` is :any:`TxnType.AssetConfig` and this is an asset creation transaction. - Requires TEAL version 5 or higher. + Requires program version 5 or higher. * v5 - Only works on inner transactions. * >= v6 - Works on top-level and inner transactions. @@ -679,7 +679,7 @@ def global_num_uints(self) -> TxnExpr: Only set when :any:`type_enum()` is :any:`TxnType.ApplicationCall` and this is an app creation call. - Requires TEAL version 3 or higher. + Requires program version 3 or higher. """ return self.makeTxnExpr(TxnField.global_num_uints) @@ -688,7 +688,7 @@ def global_num_byte_slices(self) -> TxnExpr: Only set when :any:`type_enum()` is :any:`TxnType.ApplicationCall` and this is an app creation call. - Requires TEAL version 3 or higher. + Requires program version 3 or higher. """ return self.makeTxnExpr(TxnField.global_num_byte_slices) @@ -697,7 +697,7 @@ def local_num_uints(self) -> TxnExpr: Only set when :any:`type_enum()` is :any:`TxnType.ApplicationCall` and this is an app creation call. - Requires TEAL version 3 or higher. + Requires program version 3 or higher. """ return self.makeTxnExpr(TxnField.local_num_uints) @@ -706,7 +706,7 @@ def local_num_byte_slices(self) -> TxnExpr: Only set when :any:`type_enum()` is :any:`TxnType.ApplicationCall` and this is an app creation call. - Requires TEAL version 3 or higher. + Requires program version 3 or higher. """ return self.makeTxnExpr(TxnField.local_num_byte_slices) @@ -717,7 +717,7 @@ def extra_program_pages(self) -> TxnExpr: Only set when :any:`type_enum()` is :any:`TxnType.ApplicationCall` and this is an app creation call. - Requires TEAL version 4 or higher. + Requires program version 4 or higher. """ return self.makeTxnExpr(TxnField.extra_program_pages) @@ -726,7 +726,7 @@ def created_application_id(self) -> TxnExpr: Only set when :any:`type_enum()` is :any:`TxnType.ApplicationCall` and this is an app creation call. - Requires TEAL version 5 or higher. + Requires program version 5 or higher. * v5 - Only works on inner transactions. * >= v6 - Works on top-level and inner transactions. @@ -740,31 +740,17 @@ def last_log(self) -> TxnExpr: Only set when :any:`type_enum()` is :any:`TxnType.ApplicationCall`. - Requires TEAL version 6 or higher. + Requires program version 6 or higher. """ return self.makeTxnExpr(TxnField.last_log) def state_proof_pk(self) -> TxnExpr: """Get the state proof public key commitment from a transaction. - Requires TEAL version 6 or higher. + Requires program version 6 or higher. """ return self.makeTxnExpr(TxnField.state_proof_pk) - def num_approval_program_pages(self) -> TxnExpr: - """Get the number of pages in the approval program. - - Requires TEAL version 7 or higher. - """ - return self.makeTxnExpr(TxnField.num_approval_program_pages) - - def num_clear_state_program_pages(self) -> TxnExpr: - """Get the number of pages in the clear state program. - - Requires TEAL version 7 or higher. - """ - return self.makeTxnExpr(TxnField.num_clear_state_program_pages) - @property def application_args(self) -> TxnArray: """Application call arguments array. @@ -787,7 +773,7 @@ def assets(self) -> TxnArray: :type: TxnArray - Requires TEAL version 3 or higher. + Requires program version 3 or higher. """ return TxnArray(self, TxnField.assets, TxnField.num_assets) @@ -797,7 +783,7 @@ def applications(self) -> TxnArray: :type: TxnArray - Requires TEAL version 3 or higher. + Requires program version 3 or higher. """ return TxnArray(self, TxnField.applications, TxnField.num_applications) @@ -807,7 +793,7 @@ def logs(self) -> TxnArray: :type: TxnArray - Requires TEAL version 5 or higher. + Requires program version 5 or higher. * v5 - Only works on inner transactions. * >= v6 - Works on top-level and inner transactions. @@ -820,7 +806,7 @@ def approval_program_pages(self) -> TxnArray: :type: TxnArray - Requires TEAL version 7 or higher. + Requires program version 7 or higher. """ return TxnArray( self, TxnField.approval_program_pages, TxnField.num_approval_program_pages @@ -832,7 +818,7 @@ def clear_state_program_pages(self) -> TxnArray: :type: TxnArray - Requires TEAL version 7 or higher. + Requires program version 7 or higher. """ return TxnArray( self, diff --git a/pyteal/ast/txn_test.py b/pyteal/ast/txn_test.py index d169b459b..a53273376 100644 --- a/pyteal/ast/txn_test.py +++ b/pyteal/ast/txn_test.py @@ -59,8 +59,6 @@ pt.TxnField.created_application_id: lambda txn: txn.created_application_id(), pt.TxnField.last_log: lambda txn: txn.last_log(), pt.TxnField.state_proof_pk: lambda txn: txn.state_proof_pk(), - pt.TxnField.num_approval_program_pages: lambda txn: txn.num_approval_program_pages(), - pt.TxnField.num_clear_state_program_pages: lambda txn: txn.num_clear_state_program_pages(), } arrayFieldToProperty: Dict[pt.TxnField, Callable[[pt.TxnObject], pt.TxnArray]] = { diff --git a/pyteal/ast/unaryexpr.py b/pyteal/ast/unaryexpr.py index 1fad9903b..9d532a2b1 100644 --- a/pyteal/ast/unaryexpr.py +++ b/pyteal/ast/unaryexpr.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING from pyteal.types import TealType, require_type -from pyteal.errors import verifyTealVersion +from pyteal.errors import verifyProgramVersion from pyteal.ir import TealOp, Op, TealBlock from pyteal.ast.expr import Expr @@ -22,10 +22,10 @@ def __init__( self.arg = arg def __teal__(self, options: "CompileOptions"): - verifyTealVersion( + verifyProgramVersion( self.op.min_version, options.version, - "TEAL version too low to use op {}".format(self.op), + "Program version too low to use op {}".format(self.op), ) return TealBlock.FromOp(options, TealOp(self, self.op), self.arg) @@ -65,7 +65,7 @@ def BitLen(arg: Expr) -> UnaryExpr: If the argument is a byte array, it is interpreted as a big-endian unsigned integer. - Requires TEAL version 4 or higher. + Requires program version 4 or higher. """ return UnaryExpr(Op.bitlen, TealType.anytype, TealType.uint64, arg) @@ -111,7 +111,7 @@ def Sqrt(arg: Expr) -> UnaryExpr: This will return the largest integer X such that X^2 <= arg. - Requires TEAL version 4 or higher. + Requires program version 4 or higher. """ return UnaryExpr(Op.sqrt, TealType.uint64, TealType.uint64, arg) @@ -142,7 +142,7 @@ def MinBalance(account: Expr) -> UnaryExpr: must be evaluated to uint64 (or, since v4, an account address that appears in Txn.Accounts or is Txn.Sender). - Requires TEAL version 3 or higher. This operation is only permitted in application mode. + Requires program version 3 or higher. This operation is only permitted in application mode. """ return UnaryExpr(Op.min_balance, TealType.anytype, TealType.uint64, account) @@ -153,7 +153,7 @@ def BytesNot(arg: Expr) -> UnaryExpr: Produces ~arg. Argument must not exceed 64 bytes. - Requires TEAL version 4 or higher. + Requires program version 4 or higher. """ return UnaryExpr(Op.b_not, TealType.bytes, TealType.bytes, arg) @@ -163,7 +163,7 @@ def BytesSqrt(arg: Expr) -> UnaryExpr: This will return the largest integer X such that X^2 <= arg. - Requires TEAL version 6 or higher. + Requires program version 6 or higher. """ return UnaryExpr(Op.bsqrt, TealType.bytes, TealType.bytes, arg) @@ -173,7 +173,7 @@ def BytesZero(arg: Expr) -> UnaryExpr: Argument must evaluate to uint64. - Requires TEAL version 4 or higher. + Requires program version 4 or higher. """ return UnaryExpr(Op.bzero, TealType.uint64, TealType.bytes, arg) @@ -181,12 +181,12 @@ def BytesZero(arg: Expr) -> UnaryExpr: def Log(message: Expr) -> UnaryExpr: """Write a message to log state of the current application. - This will fail if called more than :code:`MaxLogCalls` times in a program (32 as of TEAL v5), or + This will fail if called more than :code:`MaxLogCalls` times in a program (32 as of AVM v5), or if the sum of the lengths of all logged messages in a program exceeds 1024 bytes. Args: message: The message to write. Must evaluate to bytes. - Requires TEAL version 5 or higher. + Requires program version 5 or higher. """ return UnaryExpr(Op.log, TealType.bytes, TealType.none, message) diff --git a/pyteal/ast/unaryexpr_test.py b/pyteal/ast/unaryexpr_test.py index 5a096fcbd..589286b75 100644 --- a/pyteal/ast/unaryexpr_test.py +++ b/pyteal/ast/unaryexpr_test.py @@ -2,12 +2,12 @@ import pyteal as pt -teal2Options = pt.CompileOptions(version=2) -teal3Options = pt.CompileOptions(version=3) -teal4Options = pt.CompileOptions(version=4) -teal5Options = pt.CompileOptions(version=5) -teal6Options = pt.CompileOptions(version=6) -teal7Options = pt.CompileOptions(version=7) +avm2Options = pt.CompileOptions(version=2) +avm3Options = pt.CompileOptions(version=3) +avm4Options = pt.CompileOptions(version=4) +avm5Options = pt.CompileOptions(version=5) +avm6Options = pt.CompileOptions(version=6) +avm7Options = pt.CompileOptions(version=7) def test_btoi(): @@ -19,7 +19,7 @@ def test_btoi(): [pt.TealOp(arg, pt.Op.arg, 1), pt.TealOp(expr, pt.Op.btoi)] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -40,7 +40,7 @@ def test_itob(): [pt.TealOp(arg, pt.Op.int, 1), pt.TealOp(expr, pt.Op.itob)] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -61,7 +61,7 @@ def test_len(): [pt.TealOp(arg, pt.Op.txn, "Receiver"), pt.TealOp(expr, pt.Op.len)] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -82,7 +82,7 @@ def test_bitlen_int(): [pt.TealOp(arg, pt.Op.int, 7), pt.TealOp(expr, pt.Op.bitlen)] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -98,7 +98,7 @@ def test_bitlen_bytes(): [pt.TealOp(arg, pt.Op.txn, "Receiver"), pt.TealOp(expr, pt.Op.bitlen)] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -114,7 +114,7 @@ def test_sha256(): [pt.TealOp(arg, pt.Op.arg, 0), pt.TealOp(expr, pt.Op.sha256)] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -135,7 +135,7 @@ def test_sha512_256(): [pt.TealOp(arg, pt.Op.arg, 0), pt.TealOp(expr, pt.Op.sha512_256)] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -156,7 +156,7 @@ def test_sha3_256(): [pt.TealOp(arg, pt.Op.arg, 0), pt.TealOp(expr, pt.Op.sha3_256)] ) - actual, _ = expr.__teal__(teal7Options) + actual, _ = expr.__teal__(avm7Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -177,7 +177,7 @@ def test_keccak256(): [pt.TealOp(arg, pt.Op.arg, 0), pt.TealOp(expr, pt.Op.keccak256)] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -198,7 +198,7 @@ def test_not(): [pt.TealOp(arg, pt.Op.int, 1), pt.TealOp(expr, pt.Op.logic_not)] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -219,7 +219,7 @@ def test_bitwise_not(): [pt.TealOp(arg, pt.Op.int, 2), pt.TealOp(expr, pt.Op.bitwise_not)] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -235,7 +235,7 @@ def test_bitwise_not_overload(): [pt.TealOp(arg, pt.Op.int, 10), pt.TealOp(expr, pt.Op.bitwise_not)] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -256,7 +256,7 @@ def test_sqrt(): [pt.TealOp(arg, pt.Op.int, 4), pt.TealOp(expr, pt.Op.sqrt)] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -277,7 +277,7 @@ def test_pop(): [pt.TealOp(arg_int, pt.Op.int, 3), pt.TealOp(expr_int, pt.Op.pop)] ) - actual_int, _ = expr_int.__teal__(teal2Options) + actual_int, _ = expr_int.__teal__(avm2Options) actual_int.addIncoming() actual_int = pt.TealBlock.NormalizeBlocks(actual_int) @@ -291,7 +291,7 @@ def test_pop(): [pt.TealOp(arg_bytes, pt.Op.txn, "Receiver"), pt.TealOp(expr_bytes, pt.Op.pop)] ) - actual_bytes, _ = expr_bytes.__teal__(teal2Options) + actual_bytes, _ = expr_bytes.__teal__(avm2Options) actual_bytes.addIncoming() actual_bytes = pt.TealBlock.NormalizeBlocks(actual_bytes) @@ -313,7 +313,7 @@ def test_balance(): [pt.TealOp(arg, pt.Op.int, 0), pt.TealOp(expr, pt.Op.balance)] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -329,7 +329,7 @@ def test_balance_direct_ref(): [pt.TealOp(arg, pt.Op.txn, "Sender"), pt.TealOp(expr, pt.Op.balance)] ) - actual, _ = expr.__teal__(teal2Options) + actual, _ = expr.__teal__(avm2Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -352,7 +352,7 @@ def test_min_balance(): [pt.TealOp(arg, pt.Op.int, 0), pt.TealOp(expr, pt.Op.min_balance)] ) - actual, _ = expr.__teal__(teal3Options) + actual, _ = expr.__teal__(avm3Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -368,7 +368,7 @@ def test_min_balance_direct_ref(): [pt.TealOp(arg, pt.Op.txn, "Sender"), pt.TealOp(expr, pt.Op.min_balance)] ) - actual, _ = expr.__teal__(teal3Options) + actual, _ = expr.__teal__(avm3Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -394,7 +394,7 @@ def test_b_not(): ] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -415,7 +415,7 @@ def test_bsqrt(): [pt.TealOp(arg, pt.Op.byte, "0xFEDCBA9876543210"), pt.TealOp(expr, pt.Op.bsqrt)] ) - actual, _ = expr.__teal__(teal6Options) + actual, _ = expr.__teal__(avm6Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -436,7 +436,7 @@ def test_b_zero(): [pt.TealOp(arg, pt.Op.int, 8), pt.TealOp(expr, pt.Op.bzero)] ) - actual, _ = expr.__teal__(teal4Options) + actual, _ = expr.__teal__(avm4Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) @@ -458,14 +458,14 @@ def test_log(): [pt.TealOp(arg, pt.Op.byte, '"message"'), pt.TealOp(expr, pt.Op.log)] ) - actual, _ = expr.__teal__(teal5Options) + actual, _ = expr.__teal__(avm5Options) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected with pytest.raises(pt.TealInputError): - expr.__teal__(teal4Options) + expr.__teal__(avm4Options) def test_log_invalid(): diff --git a/pyteal/ast/widemath.py b/pyteal/ast/widemath.py index 3098a7d3a..b70c4c2e8 100644 --- a/pyteal/ast/widemath.py +++ b/pyteal/ast/widemath.py @@ -90,7 +90,7 @@ def __init__( :code:`N_i` represents an element in :code:`numeratorFactors` and each :code:`D_i` represents an element in :code:`denominatorFactors`. - Requires TEAL version 5 or higher. + Requires program version 5 or higher. Args: numeratorFactors: The factors in the numerator of the ratio. This list must have at @@ -114,7 +114,7 @@ def __init__( def __teal__(self, options: "CompileOptions"): if options.version < Op.cover.min_version: raise TealCompileError( - "WideRatio requires TEAL version {} or higher".format( + "WideRatio requires program version {} or higher".format( Op.cover.min_version ), self, diff --git a/pyteal/compiler/__init__.py b/pyteal/compiler/__init__.py index 13a3e6e92..e0d29450d 100644 --- a/pyteal/compiler/__init__.py +++ b/pyteal/compiler/__init__.py @@ -2,6 +2,9 @@ MAX_TEAL_VERSION, MIN_TEAL_VERSION, DEFAULT_TEAL_VERSION, + MAX_PROGRAM_VERSION, + MIN_PROGRAM_VERSION, + DEFAULT_PROGRAM_VERSION, CompileOptions, compileTeal, ) @@ -12,6 +15,9 @@ "MAX_TEAL_VERSION", "MIN_TEAL_VERSION", "DEFAULT_TEAL_VERSION", + "MAX_PROGRAM_VERSION", + "MIN_PROGRAM_VERSION", + "DEFAULT_PROGRAM_VERSION", "CompileOptions", "compileTeal", "OptimizeOptions", diff --git a/pyteal/compiler/compiler.py b/pyteal/compiler/compiler.py index faf8717bd..f98396e36 100644 --- a/pyteal/compiler/compiler.py +++ b/pyteal/compiler/compiler.py @@ -25,9 +25,17 @@ ) from pyteal.compiler.constants import createConstantBlocks -MAX_TEAL_VERSION = 8 -MIN_TEAL_VERSION = 2 -DEFAULT_TEAL_VERSION = MIN_TEAL_VERSION +MAX_PROGRAM_VERSION = 8 +MIN_PROGRAM_VERSION = 2 +DEFAULT_PROGRAM_VERSION = MIN_PROGRAM_VERSION + + +"""Deprecated. Use MAX_PROGRAM_VERSION instead.""" +MAX_TEAL_VERSION = MAX_PROGRAM_VERSION +"""Deprecated. Use MIN_PROGRAM_VERSION instead.""" +MIN_TEAL_VERSION = MIN_PROGRAM_VERSION +"""Deprecated. Use DEFAULT_PROGRAM_VERSION instead.""" +DEFAULT_TEAL_VERSION = DEFAULT_PROGRAM_VERSION class CompileOptions: @@ -35,7 +43,7 @@ def __init__( self, *, mode: Mode = Mode.Signature, - version: int = DEFAULT_TEAL_VERSION, + version: int = DEFAULT_PROGRAM_VERSION, optimize: OptimizeOptions = None, ) -> None: self.mode = mode @@ -88,7 +96,7 @@ def verifyOpsForVersion(teal: List[TealComponent], version: int): op = stmt.getOp() if op.min_version > version: raise TealInputError( - "Op not supported in TEAL version {}: {}. Minimum required version is {}".format( + "Op not supported in program version {}: {}. Minimum required version is {}".format( version, op, op.min_version ) ) @@ -192,7 +200,7 @@ def compileTeal( ast: Expr, mode: Mode, *, - version: int = DEFAULT_TEAL_VERSION, + version: int = DEFAULT_PROGRAM_VERSION, assembleConstants: bool = False, optimize: OptimizeOptions = None, ) -> str: @@ -201,13 +209,13 @@ def compileTeal( Args: ast: The PyTeal expression to assemble. mode: The mode of the program to assemble. Must be Signature or Application. - version (optional): The TEAL version used to assemble the program. This will determine which + version (optional): The program version used to assemble the program. This will determine which expressions and fields are able to be used in the program and how expressions compile to TEAL opcodes. Defaults to 2 if not included. assembleConstants (optional): When true, the compiler will produce a program with fully assembled constants, rather than using the pseudo-ops `int`, `byte`, and `addr`. These constants will be assembled in the most space-efficient way, so enabling this may reduce - the compiled program's size. Enabling this option requires a minimum TEAL version of 3. + the compiled program's size. Enabling this option requires a minimum program version of 3. Defaults to false. optimize (optional): OptimizeOptions that determine which optimizations will be applied. @@ -219,12 +227,12 @@ def compileTeal( TealInternalError: if an internal error is encounter during compilation. """ if ( - not (MIN_TEAL_VERSION <= version <= MAX_TEAL_VERSION) + not (MIN_PROGRAM_VERSION <= version <= MAX_PROGRAM_VERSION) or type(version) is not int ): raise TealInputError( - "Unsupported TEAL version: {}. Excepted an integer in the range [{}, {}]".format( - version, MIN_TEAL_VERSION, MAX_TEAL_VERSION + "Unsupported program version: {}. Excepted an integer in the range [{}, {}]".format( + version, MIN_PROGRAM_VERSION, MAX_PROGRAM_VERSION ) ) @@ -265,7 +273,7 @@ def compileTeal( if assembleConstants: if version < 3: raise TealInternalError( - "The minimum TEAL version required to enable assembleConstants is 3. The current version is {}".format( + "The minimum program version required to enable assembleConstants is 3. The current version is {}".format( version ) ) diff --git a/pyteal/compiler/subroutines.py b/pyteal/compiler/subroutines.py index 6335b82b3..bcde38b1b 100644 --- a/pyteal/compiler/subroutines.py +++ b/pyteal/compiler/subroutines.py @@ -103,7 +103,7 @@ def spillLocalSlotsDuringRecursion( slots from being modifying by a new recursive invocation of the current subroutine. Args: - version: The current TEAL version being assembled. + version: The current program version being assembled. subroutineMapping: A dictionary containing a list of TealComponents for every subroutine in a program. The key None is taken to indicate the main program routine. This input may be modified by this function in order to spill subroutine slots. @@ -209,7 +209,7 @@ def spillLocalSlotsDuringRecursion( stackDistance, ) ) - # because we are stuck using dig instead of uncover in TEAL 4, we'll need to + # because we are stuck using dig instead of uncover in AVM 4, we'll need to # pop all of the dug up arguments after the function returns hideReturnValueInFirstSlot = False diff --git a/pyteal/errors.py b/pyteal/errors.py index 03e597e2f..f5b4d34ef 100644 --- a/pyteal/errors.py +++ b/pyteal/errors.py @@ -64,7 +64,7 @@ def __eq__(self, other) -> bool: TealCompileError.__module__ = "pyteal" -def verifyTealVersion(minVersion: int, version: int, msg: str): +def verifyProgramVersion(minVersion: int, version: int, msg: str): if minVersion > version: msg = "{}. Minimum version needed is {}, but current version being compiled is {}".format( msg, minVersion, version @@ -73,8 +73,8 @@ def verifyTealVersion(minVersion: int, version: int, msg: str): def verifyFieldVersion(fieldName: str, fieldMinVersion: int, version: int): - verifyTealVersion( + verifyProgramVersion( fieldMinVersion, version, - "TEAL version too low to use field {}".format(fieldName), + "Program version too low to use field {}".format(fieldName), ) diff --git a/pyteal/ir/ops.py b/pyteal/ir/ops.py index d87c76250..c0be2e59c 100644 --- a/pyteal/ir/ops.py +++ b/pyteal/ir/ops.py @@ -181,7 +181,7 @@ def min_version(self) -> int: acct_params_get = OpType("acct_params_get", Mode.Application, 6) replace2 = OpType("replace2", Mode.Signature | Mode.Application, 7) replace3 = OpType("replace3", Mode.Signature | Mode.Application, 7) - base64_decode = OpType("base64_decode", Mode.Application | Mode.Signature, 7) + base64_decode = OpType("base64_decode", Mode.Signature | Mode.Application, 7) json_ref = OpType("json_ref", Mode.Signature | Mode.Application, 7) ed25519verify_bare = OpType("ed25519verify_bare", Mode.Signature | Mode.Application, 7) sha3_256 = OpType("sha3_256", Mode.Signature | Mode.Application, 7) From 055c981619904f0a4cb979ff48dad05eccf1e432 Mon Sep 17 00:00:00 2001 From: Hang Su <87964331+ahangsu@users.noreply.github.com> Date: Wed, 3 Aug 2022 18:46:21 -0400 Subject: [PATCH 14/19] change according to https://github.com/algorand/go-algorand/pull/4323/files (#488) --- pyteal/ast/box.py | 6 +++--- pyteal/ast/box_test.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyteal/ast/box.py b/pyteal/ast/box.py index 368c001e0..119f7f24e 100644 --- a/pyteal/ast/box.py +++ b/pyteal/ast/box.py @@ -33,14 +33,14 @@ def __teal__(self, options: "CompileOptions"): msg=f"{Op.box_create} unavailable", ) return TealBlock.FromOp( - options, TealOp(self, Op.box_create), self.size, self.name + options, TealOp(self, Op.box_create), self.name, self.size ) def __str__(self): return f"(box_create {self.name} {self.size})" def type_of(self): - return TealType.none + return TealType.uint64 def has_return(self): return False @@ -73,7 +73,7 @@ def __str__(self): return f"(box_del {self.name})" def type_of(self): - return TealType.none + return TealType.uint64 def has_return(self): return False diff --git a/pyteal/ast/box_test.py b/pyteal/ast/box_test.py index 4000b3974..c3ec419f3 100644 --- a/pyteal/ast/box_test.py +++ b/pyteal/ast/box_test.py @@ -7,8 +7,8 @@ avm8Options = pt.CompileOptions(version=8) POSITIVE_TEST_CASES: list[Tuple[pt.Expr, pt.TealType]] = [ - (pt.BoxCreate(pt.Bytes("box"), pt.Int(10)), pt.TealType.none), - (pt.BoxDelete(pt.Bytes("box")), pt.TealType.none), + (pt.BoxCreate(pt.Bytes("box"), pt.Int(10)), pt.TealType.uint64), + (pt.BoxDelete(pt.Bytes("box")), pt.TealType.uint64), (pt.BoxExtract(pt.Bytes("box"), pt.Int(2), pt.Int(4)), pt.TealType.bytes), ( pt.BoxReplace(pt.Bytes("box"), pt.Int(3), pt.Bytes("replace")), @@ -57,8 +57,8 @@ def test_box_create_compile(): expected = pt.TealSimpleBlock( [ - pt.TealOp(size_arg, pt.Op.int, 10), pt.TealOp(name_arg, pt.Op.byte, '"eineName"'), + pt.TealOp(size_arg, pt.Op.int, 10), pt.TealOp(expr, pt.Op.box_create), ] ) From 76010b27c81e0b52fba33814f3124c7ee982e921 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Thu, 29 Sep 2022 12:31:21 -0700 Subject: [PATCH 15/19] Changes to avm8 docs (#546) --- docs/state.rst | 63 +++++++++++++++++++++++++----------------- pyteal/ast/app.py | 42 +++++++++++++++------------- pyteal/ast/box_test.py | 16 +++++------ pyteal/ast/maybe.py | 4 +-- 4 files changed, 70 insertions(+), 55 deletions(-) diff --git a/docs/state.rst b/docs/state.rst index adb585b26..b45a5a0c6 100644 --- a/docs/state.rst +++ b/docs/state.rst @@ -28,8 +28,8 @@ Current App Global :any:`App.globalPut` :any:`App.gl Current App Local :any:`App.localPut` :any:`App.localGet` :any:`App.localDel` :any:`App.localGetEx` Other App Global :any:`App.globalGetEx` :any:`App.globalGetEx` Other App Local :any:`App.localGetEx` :any:`App.localGetEx` -Current App Boxes | :any:`App.box_create` | :any:`App.box_put` | :any:`App.box_extract` :any:`App.box_delete` | :any:`App.box_length` - | :any:`App.box_put` | :any:`App.box_replace` | :any:`App.box_get` | :any:`App.box_get` +Current App Boxes :any:`App.box_create` :any:`App.box_put` :any:`App.box_extract` :any:`App.box_delete` :any:`App.box_length` + :any:`App.box_put` :any:`App.box_replace` :any:`App.box_get` :any:`App.box_get` ================== ======================= ======================== ======================== ===================== ======================= Global State @@ -279,30 +279,35 @@ Creating Boxes To create a box, use :any:`App.box_create`, or :any:`App.box_put` method. -:any:`App.box_create` makes a box with a specified name and byte length. -The first argument is the box name, and the second argument is the byte size to be allocated. +For :any:`App.box_create`, the first argument is the box name, and the second argument is the byte size to be allocated. + +:any:`App.box_create` creates a new box with the specified name and byte length. New boxes will contain a byte string of all zeros. Performing this operation on a box that already exists will not change its contents. + +If successful, :any:`App.box_create` will return :code:`0` if the box already existed, otherwise it will return :code:`1`. A failure will occur if you attempt to create a box that already exists with a different size. For example: .. code-block:: python - # Allocate a box called "BoxA" of byte size 100 - App.box_create(Bytes("BoxA"), Int(100)) - # Allocate a box called "BoxB" of byte size 90 - App.box_create(Bytes("BoxB"), Int(90) + # Allocate a box called "BoxA" of byte size 100 and ignore the return value + Pop(App.box_create(Bytes("BoxA"), Int(100))) + + # Allocate a box called "BoxB" of byte size 90, asserting that it didn't exist before. + Assert(App.box_create(Bytes("BoxB"), Int(90)) For :any:`App.box_put`, the first argument is the box name to create or to write to, and the second argument is the bytes to write. .. note:: If the box exists, then :any:`App.box_put` will write the contents to the box - (fails when the replacement length is **not identical** to the box's byte size); + (fails when the content length is **not identical** to the existing box's byte size); otherwise, it will create a box containing exactly the same input bytes. .. code-block:: python # create a 42 bytes length box called `poemLine` with content App.box_put(Bytes("poemLine"), Bytes("Of that colossal wreck, boundless and bare")) + # write to box `poemLine` with new value App.box_put(Bytes("poemLine"), Bytes("The lone and level sands stretch far away.")) @@ -335,7 +340,7 @@ and the third argument is the length of bytes to extract. For example: .. code-block:: python - # extract a segment of length 10 starting at the 5'th byte in a box named `NoteBook` + # extract a segment of length 10 starting at the 5th byte in a box named `NoteBook` App.box_extract(Bytes("NoteBook"), Int(5), Int(10)) :any:`App.box_get` gets the full contents of a box. @@ -348,27 +353,29 @@ For example: .. code-block:: python - # get the full contents from a box named `NoteBook` - App.box_get(Bytes("NoteBook")).value() - - -.. note:: - - :any:`App.box_get` can also be used to check the existence of a box. For example: - - .. code-block:: python - - # check existence of a box named `NoteBook` - App.box_get(Bytes("NoteBook")).hasValue() + # get the full contents from a box named `NoteBook`, asserting that it exists + Seq( + contents := App.box_get(Bytes("NoteBook")), + Assert(contents.hasValue()), + contents.value() + ) Deleting a Box ~~~~~~~~~~~~~~ -To delete a box, use :any:`App.box_delete` method. The only argument is the box name. For example: +To delete a box, use :any:`App.box_delete` method. The only argument is the box name. + +:any:`App.box_delete` will return :code:`1` if the box already existed, otherwise it will return :code:`0`. Deleting a nonexistent box is allowed, but has no effect. + +For example: .. code-block:: python - App.box_delete(Bytes("boxToRemove")) + # delete the box `boxToRemove`, asserting that it existed prior to this + Assert(App.box_delete(Bytes("boxToRemove"))) + + # delete the box `mightExist` and ignore the return value + Pop(App.box_delete(Bytes("mightExist"))) Checking if a Box Exists and Reads its Length ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -383,8 +390,12 @@ For example: .. code-block:: python - # search for the box length for box `someBox`, and get the bool value for box existence - App.box_length(Bytes("someBox")).hasValue() + # get the length of the box `someBox`, and assert that the box exists + Seq( + length := App.box_length(Bytes("someBox")), + Assert(length.hasValue()), + length.value() + ) .. note:: diff --git a/pyteal/ast/app.py b/pyteal/ast/app.py index cf9993d46..e6f9d0de9 100644 --- a/pyteal/ast/app.py +++ b/pyteal/ast/app.py @@ -221,9 +221,15 @@ def globalDel(cls, key: Expr) -> "App": return cls(AppField.globalDel, [key]) @classmethod - def box_create(cls, name: Expr, size: Expr) -> BoxCreate: - """ - Create a box with a given name and size. + def box_create(cls, name: Expr, size: Expr) -> Expr: + """Create a box with a given name and size. + + New boxes will contain a byte string of all zeros. Performing this operation on a box that + already exists will not change its contents. + + If successful, this expression returns 0 if the box already existed, otherwise it returns 1. + + A failure will occur if you attempt to create a box that already exists with a different size. Args: name: The key used to reference this box. Must evaluate to a bytes. @@ -232,9 +238,12 @@ def box_create(cls, name: Expr, size: Expr) -> BoxCreate: return BoxCreate(name, size) @classmethod - def box_delete(cls, name: Expr) -> BoxDelete: - """ - Deletes a box given it's name. + def box_delete(cls, name: Expr) -> Expr: + """Deletes a box given it's name. + + This expression returns 1 if the box existed, otherwise it returns 0. + + Deleting a nonexistent box is allowed, but has no effect. Args: name: The key the box was created with. Must evaluate to bytes. @@ -242,9 +251,8 @@ def box_delete(cls, name: Expr) -> BoxDelete: return BoxDelete(name) @classmethod - def box_extract(cls, name: Expr, start: Expr, length: Expr) -> BoxExtract: - """ - Extracts bytes in a box given its name, start index and stop index. + def box_extract(cls, name: Expr, start: Expr, length: Expr) -> Expr: + """Extracts bytes in a box given its name, start index and stop index. Args: name: The key the box was created with. Must evaluate to bytes. @@ -254,9 +262,8 @@ def box_extract(cls, name: Expr, start: Expr, length: Expr) -> BoxExtract: return BoxExtract(name, start, length) @classmethod - def box_replace(cls, name: Expr, start: Expr, value: Expr) -> BoxReplace: - """ - Replaces bytes in a box given its name, start index, and value. + def box_replace(cls, name: Expr, start: Expr, value: Expr) -> Expr: + """Replaces bytes in a box given its name, start index, and value. Args: name: The key the box was created with. Must evaluate to bytes. @@ -267,8 +274,7 @@ def box_replace(cls, name: Expr, start: Expr, value: Expr) -> BoxReplace: @classmethod def box_length(cls, name: Expr) -> MaybeValue: - """ - Get the byte length of the box specified by its name. + """Get the byte length of the box specified by its name. Args: name: The key the box was created with. Must evaluate to bytes. @@ -277,8 +283,7 @@ def box_length(cls, name: Expr) -> MaybeValue: @classmethod def box_get(cls, name: Expr) -> MaybeValue: - """ - Get the full contents of a box given its name. + """Get the full contents of a box given its name. Args: name: The key the box was created with. Must evaluate to bytes. @@ -286,9 +291,8 @@ def box_get(cls, name: Expr) -> MaybeValue: return BoxGet(name) @classmethod - def box_put(cls, name: Expr, value: Expr) -> BoxPut: - """ - Write all contents to a box given its name. + def box_put(cls, name: Expr, value: Expr) -> Expr: + """Write all contents to a box given its name. Args: name: The key the box was created with. Must evaluate to bytes. diff --git a/pyteal/ast/box_test.py b/pyteal/ast/box_test.py index c3ec419f3..008ffb580 100644 --- a/pyteal/ast/box_test.py +++ b/pyteal/ast/box_test.py @@ -131,10 +131,10 @@ def test_box_length(): expected = pt.TealSimpleBlock( [ - pt.TealOp(name_arg, pt.Op.byte, '"eineName"'), - pt.TealOp(expr, pt.Op.box_len), - pt.TealOp(expr.output_slots[1].store(), pt.Op.store, expr.output_slots[1]), - pt.TealOp(expr.output_slots[0].store(), pt.Op.store, expr.output_slots[0]), + pt.TealOp(None, pt.Op.byte, '"eineName"'), + pt.TealOp(None, pt.Op.box_len), + pt.TealOp(None, pt.Op.store, expr.output_slots[1]), + pt.TealOp(None, pt.Op.store, expr.output_slots[0]), ] ) actual, _ = expr.__teal__(avm8Options) @@ -151,10 +151,10 @@ def test_box_get(): expected = pt.TealSimpleBlock( [ - pt.TealOp(name_arg, pt.Op.byte, '"eineName"'), - pt.TealOp(expr, pt.Op.box_get), - pt.TealOp(expr.output_slots[1].store(), pt.Op.store, expr.output_slots[1]), - pt.TealOp(expr.output_slots[0].store(), pt.Op.store, expr.output_slots[0]), + pt.TealOp(None, pt.Op.byte, '"eineName"'), + pt.TealOp(None, pt.Op.box_get), + pt.TealOp(None, pt.Op.store, expr.output_slots[1]), + pt.TealOp(None, pt.Op.store, expr.output_slots[0]), ] ) actual, _ = expr.__teal__(avm8Options) diff --git a/pyteal/ast/maybe.py b/pyteal/ast/maybe.py index 4d863e6ff..4c6091953 100644 --- a/pyteal/ast/maybe.py +++ b/pyteal/ast/maybe.py @@ -67,7 +67,7 @@ def value(self) -> ScratchLoad: def slotOk(self) -> ScratchSlot: """Get the scratch slot that stores hasValue. - Note: This is mainly added for backwards compatability and normally shouldn't be used + Note: This is mainly added for backwards compatibility and normally shouldn't be used directly in pyteal code. """ return self.output_slots[1] @@ -77,7 +77,7 @@ def slotValue(self) -> ScratchSlot: """Get the scratch slot that stores the value or the zero value for the type if the value doesn't exist. - Note: This is mainly added for backwards compatability and normally shouldn't be used + Note: This is mainly added for backwards compatibility and normally shouldn't be used directly in pyteal code. """ return self.output_slots[0] From cc49a55b96356108068b1771a785451438b68e11 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Wed, 26 Oct 2022 07:50:23 -0400 Subject: [PATCH 16/19] Support new AVM 8 account parameters (#555) --- pyteal/ast/acct.py | 225 ++++++++++++++++++++++++++++++++++++---- pyteal/ast/acct_test.py | 190 ++++++++++++++++++++------------- pyteal/ast/maybe.py | 14 ++- 3 files changed, 332 insertions(+), 97 deletions(-) diff --git a/pyteal/ast/acct.py b/pyteal/ast/acct.py index 7f61e10ee..7bc19ec78 100644 --- a/pyteal/ast/acct.py +++ b/pyteal/ast/acct.py @@ -1,12 +1,67 @@ -from typing import Final +from enum import Enum +from typing import Final, TYPE_CHECKING +from pyteal.errors import verifyFieldVersion, verifyProgramVersion from pyteal.types import TealType, require_type from pyteal.ir import Op from pyteal.ast.expr import Expr from pyteal.ast.maybe import MaybeValue +if TYPE_CHECKING: + from pyteal.compiler import CompileOptions + + +class AccountParamField(Enum): + # fmt: off + # id | name | type | min version + balance = (0, "AcctBalance", TealType.uint64, 6) # noqa: E221 + min_balance = (1, "AcctMinBalance", TealType.uint64, 6) # noqa: E221 + auth_addr = (2, "AcctAuthAddr", TealType.bytes, 6) # noqa: E221 + total_num_uint = (3, "AcctTotalNumUint", TealType.uint64, 8) # noqa: E221 + total_num_byte_slice = (4, "AcctTotalNumByteSlice", TealType.uint64, 8) # noqa: E221 + total_extra_app_pages = (5, "AcctTotalExtraAppPages", TealType.uint64, 8) # noqa: E221 + total_apps_created = (6, "AcctTotalAppsCreated", TealType.uint64, 8) # noqa: E221 + total_apps_opted_in = (7, "AcctTotalAppsOptedIn", TealType.uint64, 8) # noqa: E221 + total_assets_created = (8, "AcctTotalAssetsCreated", TealType.uint64, 8) # noqa: E221 + total_assets = (9, "AcctTotalAssets", TealType.uint64, 8) # noqa: E221 + total_boxes = (10, "AcctTotalBoxes", TealType.uint64, 8) # noqa: E221 + total_box_bytes = (11, "AcctTotalBoxBytes", TealType.uint64, 8) # noqa: E221 + # fmt: on + + def __init__(self, id: int, name: str, type: TealType, min_version: int) -> None: + self.id = id + self.arg_name = name + self.type = type + self.min_version = min_version + + def type_of(self) -> TealType: + return self.type + + +AccountParamField.__module__ = "pyteal" + class AccountParam: + @staticmethod + def __makeAccountParamExpr(field: AccountParamField, acct: Expr) -> MaybeValue: + require_type(acct, TealType.anytype) + + def field_and_program_version_check(options: "CompileOptions"): + verifyProgramVersion( + minVersion=Op.acct_params_get.min_version, + version=options.version, + msg=f"{Op.acct_params_get.value} unavailable", + ) + verifyFieldVersion(field.arg_name, field.min_version, options.version) + + return MaybeValue( + Op.acct_params_get, + field.type_of(), + immediate_args=[field.arg_name], + args=[acct], + compile_check=field_and_program_version_check, + ) + @classmethod def balance(cls, acct: Expr) -> MaybeValue: """Get the current balance in microalgos an account. @@ -15,13 +70,7 @@ def balance(cls, acct: Expr) -> MaybeValue: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. """ - require_type(acct, TealType.anytype) - return MaybeValue( - Op.acct_params_get, - TealType.uint64, - immediate_args=["AcctBalance"], - args=[acct], - ) + return cls.__makeAccountParamExpr(AccountParamField.balance, acct) @classmethod def minBalance(cls, acct: Expr) -> MaybeValue: @@ -31,13 +80,7 @@ def minBalance(cls, acct: Expr) -> MaybeValue: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. """ - require_type(acct, TealType.anytype) - return MaybeValue( - Op.acct_params_get, - TealType.uint64, - immediate_args=["AcctMinBalance"], - args=[acct], - ) + return cls.__makeAccountParamExpr(AccountParamField.min_balance, acct) @classmethod def authAddr(cls, acct: Expr) -> MaybeValue: @@ -47,13 +90,115 @@ def authAddr(cls, acct: Expr) -> MaybeValue: acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. May evaluate to uint64 or an address. """ - require_type(acct, TealType.anytype) - return MaybeValue( - Op.acct_params_get, - TealType.bytes, - immediate_args=["AcctAuthAddr"], - args=[acct], - ) + return cls.__makeAccountParamExpr(AccountParamField.auth_addr, acct) + + @classmethod + def totalNumUint(cls, acct: Expr) -> MaybeValue: + """Get the total number of uint64 values allocated by the account in Global and Local States. + + Requires program version 8 or higher. + + Args: + acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. + May evaluate to uint64 or an address. + """ + return cls.__makeAccountParamExpr(AccountParamField.total_num_uint, acct) + + @classmethod + def totalNumByteSlice(cls, acct: Expr) -> MaybeValue: + """Get the total number of byte array values allocated by the account in Global and Local States. + + Requires program version 8 or higher. + + Args: + acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. + May evaluate to uint64 or an address. + """ + return cls.__makeAccountParamExpr(AccountParamField.total_num_byte_slice, acct) + + @classmethod + def totalExtraAppPages(cls, acct: Expr) -> MaybeValue: + """Get the number of extra app code pages used by the account. + + Requires program version 8 or higher. + + Args: + acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. + May evaluate to uint64 or an address. + """ + return cls.__makeAccountParamExpr(AccountParamField.total_extra_app_pages, acct) + + @classmethod + def totalAppsCreated(cls, acct: Expr) -> MaybeValue: + """Get the number of existing apps created by the account. + + Requires program version 8 or higher. + + Args: + acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. + May evaluate to uint64 or an address. + """ + return cls.__makeAccountParamExpr(AccountParamField.total_apps_created, acct) + + @classmethod + def totalAppsOptedIn(cls, acct: Expr) -> MaybeValue: + """Get the number of apps the account is opted into. + + Requires program version 8 or higher. + + Args: + acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. + May evaluate to uint64 or an address. + """ + return cls.__makeAccountParamExpr(AccountParamField.total_apps_opted_in, acct) + + @classmethod + def totalAssetsCreated(cls, acct: Expr) -> MaybeValue: + """Get the number of existing ASAs created by the account. + + Requires program version 8 or higher. + + Args: + acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. + May evaluate to uint64 or an address. + """ + return cls.__makeAccountParamExpr(AccountParamField.total_assets_created, acct) + + @classmethod + def totalAssets(cls, acct: Expr) -> MaybeValue: + """Get the number of ASAs held by the account (including ASAs the account created). + + Requires program version 8 or higher. + + Args: + acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. + May evaluate to uint64 or an address. + """ + return cls.__makeAccountParamExpr(AccountParamField.total_assets, acct) + + @classmethod + def totalBoxes(cls, acct: Expr) -> MaybeValue: + """Get the number of existing boxes created by the account's app. + + Requires program version 8 or higher. + + Args: + acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. + May evaluate to uint64 or an address. + """ + return cls.__makeAccountParamExpr(AccountParamField.total_boxes, acct) + + @classmethod + def totalBoxBytes(cls, acct: Expr) -> MaybeValue: + """Get the total number of bytes used by the account's app's box keys and values. + + Requires program version 8 or higher. + + Args: + acct: An index into Txn.accounts that corresponds to the application to check or an address available at runtime. + May evaluate to uint64 or an address. + """ + return cls.__makeAccountParamExpr(AccountParamField.total_box_bytes, acct) AccountParam.__module__ = "pyteal" @@ -85,5 +230,41 @@ def auth_address(self) -> MaybeValue: If the account is not rekeyed, the empty address is returned.""" return AccountParam.authAddr(self._account) + def total_num_uint(self) -> MaybeValue: + """Get the total number of uint64 values allocated by the account in Global and Local States.""" + return AccountParam.totalNumUint(self._account) + + def total_num_byte_slice(self) -> MaybeValue: + """Get the total number of byte array values allocated by the account in Global and Local States.""" + return AccountParam.totalNumByteSlice(self._account) + + def total_extra_app_pages(self) -> MaybeValue: + """Get the number of extra app code pages used by the account.""" + return AccountParam.totalExtraAppPages(self._account) + + def total_apps_created(self) -> MaybeValue: + """Get the number of existing apps created by the account.""" + return AccountParam.totalAppsCreated(self._account) + + def total_apps_opted_in(self) -> MaybeValue: + """Get the number of apps the account is opted into.""" + return AccountParam.totalAppsOptedIn(self._account) + + def total_assets_created(self) -> MaybeValue: + """Get the number of existing ASAs created by the account.""" + return AccountParam.totalAssetsCreated(self._account) + + def total_assets(self) -> MaybeValue: + """Get the number of ASAs held by the account (including ASAs the account created).""" + return AccountParam.totalAssets(self._account) + + def total_boxes(self) -> MaybeValue: + """Get the number of existing boxes created by the account's app.""" + return AccountParam.totalBoxes(self._account) + + def total_box_bytes(self) -> MaybeValue: + """Get the total number of bytes used by the account's app's box keys and values.""" + return AccountParam.totalBoxBytes(self._account) + AccountParamObject.__module__ = "pyteal" diff --git a/pyteal/ast/acct_test.py b/pyteal/ast/acct_test.py index e4f12f958..8259f8ac6 100644 --- a/pyteal/ast/acct_test.py +++ b/pyteal/ast/acct_test.py @@ -1,79 +1,89 @@ +import pytest + import pyteal as pt +from pyteal.ast.acct import AccountParamField from pyteal.ast.maybe_test import assert_MaybeValue_equality -options = pt.CompileOptions() -avm4Options = pt.CompileOptions(version=4) -avm5Options = pt.CompileOptions(version=5) avm6Options = pt.CompileOptions(version=6) +avm8Options = pt.CompileOptions(version=8) + + +@pytest.mark.parametrize( + "method_name,field_name", + [ + ("balance", "balance"), + ("minBalance", "min_balance"), + ("authAddr", "auth_addr"), + ("totalNumUint", "total_num_uint"), + ("totalNumByteSlice", "total_num_byte_slice"), + ("totalExtraAppPages", "total_extra_app_pages"), + ("totalAppsCreated", "total_apps_created"), + ("totalAppsOptedIn", "total_apps_opted_in"), + ("totalAssetsCreated", "total_assets_created"), + ("totalAssets", "total_assets"), + ("totalBoxes", "total_boxes"), + ("totalBoxBytes", "total_box_bytes"), + ], +) +class TestAcctParam: + @staticmethod + def test_acct_param_fields_valid(method_name, field_name): + arg = pt.Int(1) + account_param_method = getattr(pt.AccountParam, method_name) + expr = account_param_method(arg) + assert expr.type_of() == pt.TealType.none + + account_param_field = AccountParamField[field_name] + assert expr.value().type_of() == account_param_field.type_of() + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(arg, pt.Op.int, 1), + pt.TealOp(expr, pt.Op.acct_params_get, account_param_field.arg_name), + pt.TealOp(None, pt.Op.store, expr.slotOk), + pt.TealOp(None, pt.Op.store, expr.slotValue), + ] + ) - -def test_acct_param_balance_valid(): - arg = pt.Int(1) - expr = pt.AccountParam.balance(arg) - assert expr.type_of() == pt.TealType.none - assert expr.value().type_of() == pt.TealType.uint64 - - expected = pt.TealSimpleBlock( - [ - pt.TealOp(arg, pt.Op.int, 1), - pt.TealOp(expr, pt.Op.acct_params_get, "AcctBalance"), - pt.TealOp(None, pt.Op.store, expr.slotOk), - pt.TealOp(None, pt.Op.store, expr.slotValue), - ] - ) - - actual, _ = expr.__teal__(avm6Options) - actual.addIncoming() - actual = pt.TealBlock.NormalizeBlocks(actual) - - with pt.TealComponent.Context.ignoreExprEquality(): - assert actual == expected - - -def test_acct_param_min_balance_valid(): - arg = pt.Int(0) - expr = pt.AccountParam.minBalance(arg) - assert expr.type_of() == pt.TealType.none - assert expr.value().type_of() == pt.TealType.uint64 - - expected = pt.TealSimpleBlock( - [ - pt.TealOp(arg, pt.Op.int, 0), - pt.TealOp(expr, pt.Op.acct_params_get, "AcctMinBalance"), - pt.TealOp(None, pt.Op.store, expr.slotOk), - pt.TealOp(None, pt.Op.store, expr.slotValue), - ] - ) - - actual, _ = expr.__teal__(avm6Options) - actual.addIncoming() - actual = pt.TealBlock.NormalizeBlocks(actual) - - with pt.TealComponent.Context.ignoreExprEquality(): - assert actual == expected - - -def test_acct_param_auth_addr_valid(): - arg = pt.Int(1) - expr = pt.AccountParam.authAddr(arg) - assert expr.type_of() == pt.TealType.none - assert expr.value().type_of() == pt.TealType.bytes - - expected = pt.TealSimpleBlock( - [ - pt.TealOp(arg, pt.Op.int, 1), - pt.TealOp(expr, pt.Op.acct_params_get, "AcctAuthAddr"), - pt.TealOp(None, pt.Op.store, expr.slotOk), - pt.TealOp(None, pt.Op.store, expr.slotValue), - ] - ) - - actual, _ = expr.__teal__(avm6Options) - actual.addIncoming() - actual = pt.TealBlock.NormalizeBlocks(actual) - - with pt.TealComponent.Context.ignoreExprEquality(): - assert actual == expected + supported_options_version = pt.CompileOptions( + version=account_param_field.min_version + ) + actual, _ = expr.__teal__(supported_options_version) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + with pt.TealComponent.Context.ignoreExprEquality(): + assert actual == expected + + @staticmethod + def test_acct_param_version_checks(method_name, field_name): + arg = pt.Int(1) + account_param_method = getattr(pt.AccountParam, method_name) + expr = account_param_method(arg) + + account_param_field = AccountParamField[field_name] + + def test_unsupported_version(version: int, match: str = None): + with pytest.raises(pt.TealInputError, match=match): + unsupported_options_version = pt.CompileOptions(version=version) + expr.__teal__(unsupported_options_version) + + # Test program and field version checks + program_unsupported_version = pt.ir.Op.acct_params_get.min_version - 1 + program_error_match = "unavailable" + test_unsupported_version(program_unsupported_version, program_error_match) + + field_unsupported_version = account_param_field.min_version - 1 + + # Since program version dominates, we conditionally check field error message or program error message + # depending on whether the unsupported field version is less than or equal to the program unsupported + # version. + field_error_match = ( + "Program version too low to use field" + if field_unsupported_version > program_unsupported_version + else program_error_match + ) + test_unsupported_version(field_unsupported_version, field_error_match) def test_AccountParamObject(): @@ -94,3 +104,41 @@ def test_AccountParamObject(): assert_MaybeValue_equality( obj.auth_address(), pt.AccountParam.authAddr(account), avm6Options ) + + assert_MaybeValue_equality( + obj.total_num_uint(), pt.AccountParam.totalNumUint(account), avm8Options + ) + assert_MaybeValue_equality( + obj.total_num_byte_slice(), + pt.AccountParam.totalNumByteSlice(account), + avm8Options, + ) + assert_MaybeValue_equality( + obj.total_extra_app_pages(), + pt.AccountParam.totalExtraAppPages(account), + avm8Options, + ) + assert_MaybeValue_equality( + obj.total_apps_created(), + pt.AccountParam.totalAppsCreated(account), + avm8Options, + ) + assert_MaybeValue_equality( + obj.total_apps_opted_in(), + pt.AccountParam.totalAppsOptedIn(account), + avm8Options, + ) + assert_MaybeValue_equality( + obj.total_assets_created(), + pt.AccountParam.totalAssetsCreated(account), + avm8Options, + ) + assert_MaybeValue_equality( + obj.total_assets(), pt.AccountParam.totalAssets(account), avm8Options + ) + assert_MaybeValue_equality( + obj.total_boxes(), pt.AccountParam.totalBoxes(account), avm8Options + ) + assert_MaybeValue_equality( + obj.total_box_bytes(), pt.AccountParam.totalBoxBytes(account), avm8Options + ) diff --git a/pyteal/ast/maybe.py b/pyteal/ast/maybe.py index 4c6091953..9ae1f0245 100644 --- a/pyteal/ast/maybe.py +++ b/pyteal/ast/maybe.py @@ -1,4 +1,4 @@ -from typing import List, Union, TYPE_CHECKING +from typing import Callable, List, Union, TYPE_CHECKING from pyteal.errors import verifyProgramVersion from pyteal.types import TealType @@ -22,6 +22,7 @@ def __init__( *, immediate_args: List[Union[int, str]] = None, args: List[Expr] = None, + compile_check: Callable[["CompileOptions"], None] = None, ): """Create a new MaybeValue. @@ -30,12 +31,15 @@ def __init__( type: The type of the returned value. immediate_args (optional): Immediate arguments for the op. Defaults to None. args (optional): Stack arguments for the op. Defaults to None. + compile_check (optional): Callable compile check. Defaults to program version check. + This parameter overwrites the default program version check. """ - def local_version_check(option: "CompileOptions"): + # Default compile check if one is not given + def local_version_check(options: "CompileOptions"): verifyProgramVersion( minVersion=op.min_version, - version=option.version, + version=options.version, msg=f"{op.value} unavailable", ) @@ -45,7 +49,9 @@ def local_version_check(option: "CompileOptions"): types, immediate_args=immediate_args, args=args, - compile_check=local_version_check, + compile_check=( + local_version_check if compile_check is None else compile_check + ), ) def hasValue(self) -> ScratchLoad: From c241cfa19c8290072b4707f21aace38e0a720053 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 2 Nov 2022 16:29:57 -0400 Subject: [PATCH 17/19] CHANGELOG.md --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ff7c84a7..3b3f7c841 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Unreleased +## Added +* adding program page related ops ([#412](https://github.com/algorand/pyteal/pull/412)) +* Add Replace ([#413](https://github.com/algorand/pyteal/pull/413)) +* Add Block ([#415](https://github.com/algorand/pyteal/pull/415)) +* Add JsonRef ([#417](https://github.com/algorand/pyteal/pull/417)) +* Add Base64Decode ([#418](https://github.com/algorand/pyteal/pull/418)) +* Support Secp256r1 curve ([#423](https://github.com/algorand/pyteal/pull/423)) +* Add VrfVerify ([#419](https://github.com/algorand/pyteal/pull/419)) +* Add Sha3_256 ([#425](https://github.com/algorand/pyteal/pull/425)) +* Support FirstValidTime transaction field ([#424](https://github.com/algorand/pyteal/pull/424)) +* Add Ed25519Verify_Bare ([#426](https://github.com/algorand/pyteal/pull/426)) +* AVM Boxes Ops in Pyteal ([#438](https://github.com/algorand/pyteal/pull/438)) +* Support new AVM 8 account parameters ([#555](https://github.com/algorand/pyteal/pull/555)) + +## Changed +* Changes to avm8 docs ([#546](https://github.com/algorand/pyteal/pull/546)) + # 0.19.0 ## Added From dd687c7643b2e87f241f96250f43e689abb91abf Mon Sep 17 00:00:00 2001 From: Hang Su <87964331+ahangsu@users.noreply.github.com> Date: Wed, 2 Nov 2022 17:12:36 -0400 Subject: [PATCH 18/19] Frame Ops to `avm8` branch PR (#585) * add frame ops to avm8 branch * specify FRAME_POINTER_VERSION in frame-op branch * per review comments * per review comments * per review comments, depth -> frame_depth * take bury out * pop popn * unexport use of frame ops * hide FRAME_POINTER_VERISON --- pyteal/ast/frame.py | 132 ++++++++++++++++++++++++++++++++++++ pyteal/ast/frame_test.py | 107 +++++++++++++++++++++++++++++ pyteal/compiler/compiler.py | 1 + pyteal/ir/ops.py | 8 ++- 4 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 pyteal/ast/frame.py create mode 100644 pyteal/ast/frame_test.py diff --git a/pyteal/ast/frame.py b/pyteal/ast/frame.py new file mode 100644 index 000000000..8f5e24891 --- /dev/null +++ b/pyteal/ast/frame.py @@ -0,0 +1,132 @@ +from typing import TYPE_CHECKING + +from pyteal.ast.expr import Expr +from pyteal.types import TealType, require_type +from pyteal.errors import TealInputError, verifyProgramVersion +from pyteal.ir import TealBlock, TealSimpleBlock, TealOp, Op + +if TYPE_CHECKING: + from pyteal.compiler import CompileOptions + + +class Proto(Expr): + def __init__(self, num_args: int, num_returns: int): + super().__init__() + if num_args < 0: + raise TealInputError( + f"the number of arguments provided to Proto must be >= 0 but {num_args=}" + ) + if num_returns < 0: + raise TealInputError( + f"the number of return values provided to Proto must be >= 0 but {num_returns=}" + ) + self.num_args = num_args + self.num_returns = num_returns + + def __teal__(self, options: "CompileOptions") -> tuple[TealBlock, TealSimpleBlock]: + verifyProgramVersion( + Op.proto.min_version, + options.version, + "Program version too low to use op proto", + ) + op = TealOp(self, Op.proto, self.num_args, self.num_returns) + return TealBlock.FromOp(options, op) + + def __str__(self) -> str: + return f"(proto: num_args = {self.num_args}, num_returns = {self.num_returns})" + + def type_of(self) -> TealType: + return TealType.none + + def has_return(self) -> bool: + return False + + +Proto.__module__ = "pyteal" + + +class FrameDig(Expr): + def __init__(self, frame_index: int): + super().__init__() + self.frame_index = frame_index + + def __teal__(self, options: "CompileOptions") -> tuple[TealBlock, TealSimpleBlock]: + verifyProgramVersion( + Op.frame_dig.min_version, + options.version, + "Program version too low to use op frame_dig", + ) + op = TealOp(self, Op.frame_dig, self.frame_index) + return TealBlock.FromOp(options, op) + + def __str__(self) -> str: + return f"(frame_dig: dig_from = {self.frame_index})" + + def type_of(self) -> TealType: + return TealType.anytype + + def has_return(self) -> bool: + return False + + +FrameDig.__module__ = "pyteal" + + +class FrameBury(Expr): + def __init__(self, value: Expr, frame_index: int): + super().__init__() + require_type(value, TealType.anytype) + self.value = value + self.frame_index = frame_index + + def __teal__(self, options: "CompileOptions") -> tuple[TealBlock, TealSimpleBlock]: + verifyProgramVersion( + Op.frame_bury.min_version, + options.version, + "Program version too low to use op frame_bury", + ) + op = TealOp(self, Op.frame_bury, self.frame_index) + return TealBlock.FromOp(options, op, self.value) + + def __str__(self) -> str: + return f"(frame_bury (bury_to = {self.frame_index}) ({self.value}))" + + def type_of(self) -> TealType: + return TealType.none + + def has_return(self) -> bool: + return False + + +FrameBury.__module__ = "pyteal" + + +class DupN(Expr): + def __init__(self, value: Expr, repetition: int): + super().__init__() + require_type(value, TealType.anytype) + if repetition < 0: + raise TealInputError("dupn repetition should be non negative") + self.value = value + self.repetition = repetition + + def __teal__(self, options: "CompileOptions") -> tuple[TealBlock, TealSimpleBlock]: + verifyProgramVersion( + Op.dupn.min_version, + options.version, + "Program version too low to use op dupn", + ) + op = TealOp(self, Op.dupn, self.repetition) + return TealBlock.FromOp(options, op, self.value) + + def __str__(self) -> str: + return f"(dupn (repetition = {self.repetition}) ({self.value}))" + + def type_of(self) -> TealType: + return self.value.type_of() + + def has_return(self) -> bool: + return False + + +DupN.__module__ = "pyteal" diff --git a/pyteal/ast/frame_test.py b/pyteal/ast/frame_test.py new file mode 100644 index 000000000..e4cde2aa4 --- /dev/null +++ b/pyteal/ast/frame_test.py @@ -0,0 +1,107 @@ +import pytest +import pyteal as pt +from pyteal.ast.frame import FrameBury, FrameDig, Proto, DupN + +avm7Options = pt.CompileOptions(version=7) +avm8Options = pt.CompileOptions(version=8) + + +@pytest.mark.parametrize("input_num, output_num", [(1, 1), (1, 0), (5, 5)]) +def test_proto(input_num: int, output_num: int): + expr = Proto(input_num, output_num) + assert not expr.has_return() + assert expr.type_of() == pt.TealType.none + + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.proto, input_num, output_num)]) + actual, _ = expr.__teal__(avm8Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + assert actual == expected + + +def test_proto_invalid(): + with pytest.raises(pt.TealInputError): + Proto(-1, 1) + + with pytest.raises(pt.TealInputError): + Proto(1, -1) + + with pytest.raises(pt.TealInputError): + Proto(1, 1).__teal__(avm7Options) + + +@pytest.mark.parametrize("depth", [-1, 0, 1, 2]) +def test_frame_dig(depth: int): + expr = FrameDig(depth) + assert not expr.has_return() + assert expr.type_of() == pt.TealType.anytype + + expected = pt.TealSimpleBlock([pt.TealOp(expr, pt.Op.frame_dig, depth)]) + actual, _ = expr.__teal__(avm8Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + assert actual == expected + + +def test_frame_dig_invalid(): + with pytest.raises(pt.TealInputError): + FrameDig(1).__teal__(avm7Options) + + +def test_frame_bury(): + byte_expr = pt.Bytes("Astartes") + expr = FrameBury(byte_expr, 4) + assert not expr.has_return() + assert expr.type_of() == pt.TealType.none + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(byte_expr, pt.Op.byte, '"Astartes"'), + pt.TealOp(expr, pt.Op.frame_bury, 4), + ] + ) + actual, _ = expr.__teal__(avm8Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + assert actual == expected + + +def test_frame_bury_invalid(): + with pytest.raises(pt.TealTypeError): + FrameBury(pt.Seq(), 1) + + with pytest.raises(pt.TealInputError): + FrameBury(pt.Int(1), 1).__teal__(avm7Options) + + +def test_dupn(): + byte_expr = pt.Bytes("Astartes") + expr = DupN(byte_expr, 4) + assert not expr.has_return() + assert expr.type_of() == byte_expr.type_of() + + expected = pt.TealSimpleBlock( + [ + pt.TealOp(byte_expr, pt.Op.byte, '"Astartes"'), + pt.TealOp(expr, pt.Op.dupn, 4), + ] + ) + actual, _ = expr.__teal__(avm8Options) + actual.addIncoming() + actual = pt.TealBlock.NormalizeBlocks(actual) + + assert actual == expected + + +def test_dupn_invalid(): + with pytest.raises(pt.TealTypeError): + DupN(pt.Seq(), 1) + + with pytest.raises(pt.TealInputError): + DupN(pt.Int(1), -1) + + with pytest.raises(pt.TealInputError): + DupN(pt.Int(1), 1).__teal__(avm7Options) diff --git a/pyteal/compiler/compiler.py b/pyteal/compiler/compiler.py index 1955d0430..f2b25b489 100644 --- a/pyteal/compiler/compiler.py +++ b/pyteal/compiler/compiler.py @@ -26,6 +26,7 @@ from pyteal.compiler.constants import createConstantBlocks MAX_PROGRAM_VERSION = 8 +FRAME_POINTER_VERSION = 8 MIN_PROGRAM_VERSION = 2 DEFAULT_PROGRAM_VERSION = MIN_PROGRAM_VERSION diff --git a/pyteal/ir/ops.py b/pyteal/ir/ops.py index 43a62b5d4..9ec496288 100644 --- a/pyteal/ir/ops.py +++ b/pyteal/ir/ops.py @@ -33,7 +33,7 @@ def min_version(self) -> int: # fmt: off # meta comment = OpType("//", Mode.Signature | Mode.Application, 0) - # avm + # avm err = OpType("err", Mode.Signature | Mode.Application, 2) sha256 = OpType("sha256", Mode.Signature | Mode.Application, 2) keccak256 = OpType("keccak256", Mode.Signature | Mode.Application, 2) @@ -197,6 +197,12 @@ def min_version(self) -> int: box_len = OpType("box_len", Mode.Application, 8) box_get = OpType("box_get", Mode.Application, 8) box_put = OpType("box_put", Mode.Application, 8) + popn = OpType("popn", Mode.Signature | Mode.Application, 8) + dupn = OpType("dupn", Mode.Signature | Mode.Application, 8) + bury = OpType("bury", Mode.Signature | Mode.Application, 8) + frame_dig = OpType("frame_dig", Mode.Signature | Mode.Application, 8) + frame_bury = OpType("frame_bury", Mode.Signature | Mode.Application, 8) + proto = OpType("proto", Mode.Signature | Mode.Application, 8) # fmt: on From 9cdcb30e8e38aa5e81ac9110dd59c937c1b8de9c Mon Sep 17 00:00:00 2001 From: Hang Su <87964331+ahangsu@users.noreply.github.com> Date: Thu, 3 Nov 2022 11:56:31 -0400 Subject: [PATCH 19/19] CHANGELOG 0.20.0 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b3f7c841..3552e82e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +# 0.20.0 + ## Added * adding program page related ops ([#412](https://github.com/algorand/pyteal/pull/412)) * Add Replace ([#413](https://github.com/algorand/pyteal/pull/413))