From dd9abff546345e72a8ccd98cb439429904da47ba Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 30 Mar 2022 18:00:16 -0400 Subject: [PATCH 001/170] wtf --- pyteal/ast/subroutine_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index f93abf75e..c640f8c19 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -9,6 +9,7 @@ options = CompileOptions(version=6) +# something here def test_subroutine_definition(): def fn0Args(): From d33e26cfafeb0a213f258ae50dbbc6423c2d6ddd Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 30 Mar 2022 18:01:40 -0400 Subject: [PATCH 002/170] update --- pyteal/ast/subroutine_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index c640f8c19..8afd0809f 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -11,6 +11,7 @@ # something here + def test_subroutine_definition(): def fn0Args(): return Return() From cd7632ec1edca32dfc9f7ddfbfc7b69c782bab9b Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 30 Mar 2022 20:53:50 -0400 Subject: [PATCH 003/170] update to f-str --- pyteal/ast/subroutine.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index baa7d3db4..632d9bfd8 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -105,9 +105,7 @@ def _validate_parameter_type( else: if not isclass(ptype) and not SubroutineDefinition.is_abi_annotation(ptype): raise TealInputError( - "Function has parameter {} of declared type {} which is not a class".format( - parameter_name, ptype - ) + f"Function has parameter {parameter_name} of declared type {ptype} which is not a class" ) if ptype in (Expr, ScratchVar): @@ -116,9 +114,8 @@ def _validate_parameter_type( return abi.type_spec_from_annotation(ptype) else: raise TealInputError( - "Function has parameter {} of disallowed type {}. Only the types {} are allowed".format( - parameter_name, ptype, (Expr, ScratchVar, "ABI") - ) + f"Function has parameter {parameter_name} of disallowed type {ptype}. " + f"Only the types {(Expr, ScratchVar, 'ABI')} are allowed" ) @staticmethod From a21ce2c54ffd54ffeb0f92a760cada4fc2b6a818 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 30 Mar 2022 21:02:00 -0400 Subject: [PATCH 004/170] define void type --- pyteal/ast/abi/__init__.py | 3 ++- pyteal/ast/abi/type.py | 10 ++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/pyteal/ast/abi/__init__.py b/pyteal/ast/abi/__init__.py index b922d0b06..1745c41d5 100644 --- a/pyteal/ast/abi/__init__.py +++ b/pyteal/ast/abi/__init__.py @@ -1,4 +1,4 @@ -from .type import TypeSpec, BaseType, ComputedType +from .type import TypeSpec, BaseType, ComputedType, void_t from .bool import BoolTypeSpec, Bool from .uint import ( UintTypeSpec, @@ -34,6 +34,7 @@ "TypeSpec", "BaseType", "ComputedType", + "void_t", "BoolTypeSpec", "Bool", "UintTypeSpec", diff --git a/pyteal/ast/abi/type.py b/pyteal/ast/abi/type.py index b88a704c7..75ee1c46d 100644 --- a/pyteal/ast/abi/type.py +++ b/pyteal/ast/abi/type.py @@ -1,4 +1,4 @@ -from typing import TypeVar, Generic, Callable, Final, cast +from typing import TypeVar, Generic, Callable, Final, cast, Literal from abc import ABC, abstractmethod from ...types import TealType @@ -138,7 +138,7 @@ class ComputedType(ABC, Generic[T]): """Represents an ABI Type whose value must be computed by an expression.""" @abstractmethod - def produced_type_spec(cls) -> TypeSpec: + def produced_type_spec(self) -> TypeSpec: """Get the ABI TypeSpec that this object produces.""" pass @@ -173,3 +173,9 @@ def use(self, action: Callable[[T], Expr]) -> Expr: ComputedType.__module__ = "pyteal" + + +void_t = Literal["void"] + + +void_t.__module__ = "pyteal" From 480ece7d586e20804ceb6ba6a0c0778b9936cbb6 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 30 Mar 2022 21:10:39 -0400 Subject: [PATCH 005/170] update instantiated computed type returnedType for ABI return in subroutine --- pyteal/ast/abi/type.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pyteal/ast/abi/type.py b/pyteal/ast/abi/type.py index 75ee1c46d..48fac9d50 100644 --- a/pyteal/ast/abi/type.py +++ b/pyteal/ast/abi/type.py @@ -5,6 +5,7 @@ from ..expr import Expr from ..scratchvar import ScratchVar from ..seq import Seq +from ...errors import TealInputError class TypeSpec(ABC): @@ -179,3 +180,23 @@ def use(self, action: Callable[[T], Expr]) -> Expr: void_t.__module__ = "pyteal" + + +class ReturnedType(ComputedType): + def __init__(self, type_spec: TypeSpec, encodings: Expr): + self.type_spec = type_spec + self.encodings = encodings + + def produced_type_spec(self) -> TypeSpec: + return self.type_spec + + @abstractmethod + def store_into(self, output: BaseType) -> Expr: + if output.type_spec() != self.type_spec: + raise TealInputError( + f"expected type_spec {self.type_spec} but get {output.type_spec()}" + ) + return output.stored_value.store(self.encodings) + + +ReturnedType.__module__ = "pyteal" From ff0d275787fd2d24dc91d9d18728948514de9efe Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 30 Mar 2022 21:12:26 -0400 Subject: [PATCH 006/170] minor --- pyteal/ast/abi/type.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/abi/type.py b/pyteal/ast/abi/type.py index 48fac9d50..56718e324 100644 --- a/pyteal/ast/abi/type.py +++ b/pyteal/ast/abi/type.py @@ -176,7 +176,7 @@ def use(self, action: Callable[[T], Expr]) -> Expr: ComputedType.__module__ = "pyteal" -void_t = Literal["void"] +void_t = ComputedType[Literal["void"]] void_t.__module__ = "pyteal" From 6929922f5deef99f8b86b48b6f47b4898f22f39a Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 30 Mar 2022 21:28:49 -0400 Subject: [PATCH 007/170] update stuffs to help infer type annotation of return ABI --- pyteal/ast/abi/type.py | 2 +- pyteal/ast/abi/util.py | 22 +++++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/pyteal/ast/abi/type.py b/pyteal/ast/abi/type.py index 56718e324..48fac9d50 100644 --- a/pyteal/ast/abi/type.py +++ b/pyteal/ast/abi/type.py @@ -176,7 +176,7 @@ def use(self, action: Callable[[T], Expr]) -> Expr: ComputedType.__module__ = "pyteal" -void_t = ComputedType[Literal["void"]] +void_t = Literal["void"] void_t.__module__ = "pyteal" diff --git a/pyteal/ast/abi/util.py b/pyteal/ast/abi/util.py index e9077ded6..176d8a13a 100644 --- a/pyteal/ast/abi/util.py +++ b/pyteal/ast/abi/util.py @@ -1,10 +1,10 @@ -from typing import Any, Literal, get_origin, get_args +from typing import Any, Literal, get_origin, get_args, Union from ...errors import TealInputError from ..expr import Expr from ..int import Int from ..substring import Extract, Substring, Suffix -from .type import TypeSpec +from .type import TypeSpec, ComputedType, void_t def substringForDecoding( @@ -12,7 +12,7 @@ def substringForDecoding( *, startIndex: Expr = None, endIndex: Expr = None, - length: Expr = None + length: Expr = None, ) -> Expr: """A helper function for getting the substring to decode according to the rules of BaseType.decode.""" if length is not None and endIndex is not None: @@ -193,3 +193,19 @@ def type_spec_from_annotation(annotation: Any) -> TypeSpec: return TupleTypeSpec(*(type_spec_from_annotation(arg) for arg in args)) raise TypeError("Unknown annotation origin: {}".format(origin)) + + +def type_spec_from_computed_type_annotation(annotation: Any) -> Union[str, TypeSpec]: + if annotation is void_t: + return "void" + + if get_origin(annotation) is not ComputedType: + raise TealInputError( + f"expected type annotation ComputedType[...] but get {get_origin(annotation)}" + ) + args = get_args(annotation) + if len(args) != 1: + raise TealInputError( + f"expected ComputedType[...] has 1 argument annotation but get {len(args)}" + ) + return type_spec_from_annotation(args[0]) From c24e0bdd461a20417f84149d55a693dbfcada070 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 30 Mar 2022 21:34:12 -0400 Subject: [PATCH 008/170] minor --- pyteal/ast/abi/type.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyteal/ast/abi/type.py b/pyteal/ast/abi/type.py index 48fac9d50..e43c36c11 100644 --- a/pyteal/ast/abi/type.py +++ b/pyteal/ast/abi/type.py @@ -176,10 +176,10 @@ def use(self, action: Callable[[T], Expr]) -> Expr: ComputedType.__module__ = "pyteal" -void_t = Literal["void"] +Void = Literal["void"] -void_t.__module__ = "pyteal" +Void.__module__ = "pyteal" class ReturnedType(ComputedType): From 9e3989b8c78ae3633273a8e62bd057525af5f926 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 30 Mar 2022 21:36:12 -0400 Subject: [PATCH 009/170] minor --- pyteal/ast/abi/__init__.py | 4 ++-- pyteal/ast/abi/util.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyteal/ast/abi/__init__.py b/pyteal/ast/abi/__init__.py index 1745c41d5..8304b3c06 100644 --- a/pyteal/ast/abi/__init__.py +++ b/pyteal/ast/abi/__init__.py @@ -1,4 +1,4 @@ -from .type import TypeSpec, BaseType, ComputedType, void_t +from .type import TypeSpec, BaseType, ComputedType, Void from .bool import BoolTypeSpec, Bool from .uint import ( UintTypeSpec, @@ -34,7 +34,7 @@ "TypeSpec", "BaseType", "ComputedType", - "void_t", + "Void", "BoolTypeSpec", "Bool", "UintTypeSpec", diff --git a/pyteal/ast/abi/util.py b/pyteal/ast/abi/util.py index 176d8a13a..45e523f04 100644 --- a/pyteal/ast/abi/util.py +++ b/pyteal/ast/abi/util.py @@ -4,7 +4,7 @@ from ..expr import Expr from ..int import Int from ..substring import Extract, Substring, Suffix -from .type import TypeSpec, ComputedType, void_t +from .type import TypeSpec, ComputedType, Void def substringForDecoding( @@ -196,7 +196,7 @@ def type_spec_from_annotation(annotation: Any) -> TypeSpec: def type_spec_from_computed_type_annotation(annotation: Any) -> Union[str, TypeSpec]: - if annotation is void_t: + if annotation is Void: return "void" if get_origin(annotation) is not ComputedType: From a1e138ea4847ea73514d42196eff1a9f3c73fc17 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 30 Mar 2022 21:37:03 -0400 Subject: [PATCH 010/170] minor --- pyteal/ast/abi/type.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyteal/ast/abi/type.py b/pyteal/ast/abi/type.py index e43c36c11..119e31c44 100644 --- a/pyteal/ast/abi/type.py +++ b/pyteal/ast/abi/type.py @@ -190,7 +190,6 @@ def __init__(self, type_spec: TypeSpec, encodings: Expr): def produced_type_spec(self) -> TypeSpec: return self.type_spec - @abstractmethod def store_into(self, output: BaseType) -> Expr: if output.type_spec() != self.type_spec: raise TealInputError( From 0d617ffc704df77260b2f4b6004ea4a174604f98 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 30 Mar 2022 22:15:02 -0400 Subject: [PATCH 011/170] minor --- pyteal/ast/abi/__init__.py | 3 ++- pyteal/ast/subroutine.py | 40 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/pyteal/ast/abi/__init__.py b/pyteal/ast/abi/__init__.py index 8304b3c06..c89b3953f 100644 --- a/pyteal/ast/abi/__init__.py +++ b/pyteal/ast/abi/__init__.py @@ -1,4 +1,4 @@ -from .type import TypeSpec, BaseType, ComputedType, Void +from .type import TypeSpec, BaseType, ComputedType, ReturnedType, Void from .bool import BoolTypeSpec, Bool from .uint import ( UintTypeSpec, @@ -34,6 +34,7 @@ "TypeSpec", "BaseType", "ComputedType", + "ReturnedType", "Void", "BoolTypeSpec", "Bool", diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 632d9bfd8..e716b20bf 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -12,6 +12,7 @@ Tuple, cast, Any, + TypeVar, ) from ..errors import TealInputError, verifyTealVersion @@ -380,6 +381,45 @@ def has_return(self): SubroutineFnWrapper.__module__ = "pyteal" +T = TypeVar("T", bound=abi.BaseType) +ABI_Return_T = Union[abi.Void, abi.ComputedType[T]] + + +class ABIReturnSubroutineFnWrapper: + def __init__( + self, + fn_implementation: Callable[..., ABI_Return_T], + return_type: TealType, + name: Optional[str] = None, + ) -> None: + # self.subroutine = SubroutineDefinition( + # fn_implementation, + # return_type=return_type, + # name_str=name, + # ) + pass + + def __call__( + self, *args: Union[Expr, ScratchVar, abi.BaseType], **kwargs + ) -> Union[abi.ReturnedType, Expr]: + pass + + def name(self) -> str: + pass + + def is_void(self) -> bool: + pass + + def type_of(self) -> Union[str, abi.TypeSpec]: + pass + + def is_registrable(self) -> bool: + pass + + +ABIReturnSubroutineFnWrapper.__module__ = "pyteal" + + class Subroutine: """Used to create a PyTeal subroutine from a Python function. From 081bfb6c57dc7ff9cfa57eeb9c018da75c95ce7a Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 30 Mar 2022 22:20:13 -0400 Subject: [PATCH 012/170] minor --- pyteal/ast/subroutine.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index e716b20bf..2c8b7ebf0 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -389,7 +389,6 @@ class ABIReturnSubroutineFnWrapper: def __init__( self, fn_implementation: Callable[..., ABI_Return_T], - return_type: TealType, name: Optional[str] = None, ) -> None: # self.subroutine = SubroutineDefinition( @@ -458,6 +457,22 @@ def __call__(self, fn_implementation: Callable[..., Expr]) -> SubroutineFnWrappe Subroutine.__module__ = "pyteal" +class ABIReturnSubroutine: + def __init__(self, name: Optional[str] = None) -> None: + self.name = name + + def __call__( + self, fn_implementation: Callable[..., ABI_Return_T] + ) -> ABIReturnSubroutineFnWrapper: + return ABIReturnSubroutineFnWrapper( + fn_implementation=fn_implementation, + name=self.name, + ) + + +ABIReturnSubroutine.__module__ = "pyteal" + + def evaluateSubroutine(subroutine: SubroutineDefinition) -> SubroutineDeclaration: """ Puts together the data necessary to define the code for a subroutine. From e8e481e96034338cb064892d9b2337b57006d8b2 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 31 Mar 2022 11:25:41 -0400 Subject: [PATCH 013/170] try my best to save stuffs --- pyteal/ast/abi/__init__.py | 7 +++- pyteal/ast/abi/type.py | 6 ++- pyteal/ast/abi/util.py | 22 ++++++++-- pyteal/ast/subroutine.py | 85 ++++++++++++++++++++++++++++++++++---- 4 files changed, 107 insertions(+), 13 deletions(-) diff --git a/pyteal/ast/abi/__init__.py b/pyteal/ast/abi/__init__.py index b922d0b06..bdc24b255 100644 --- a/pyteal/ast/abi/__init__.py +++ b/pyteal/ast/abi/__init__.py @@ -1,4 +1,4 @@ -from .type import TypeSpec, BaseType, ComputedType +from .type import TypeSpec, BaseType, ComputedType, ReturnedType, Void from .bool import BoolTypeSpec, Bool from .uint import ( UintTypeSpec, @@ -28,12 +28,14 @@ from .array_base import ArrayTypeSpec, Array, ArrayElement from .array_static import StaticArrayTypeSpec, StaticArray from .array_dynamic import DynamicArrayTypeSpec, DynamicArray -from .util import type_spec_from_annotation +from .util import type_spec_from_annotation, type_spec_from_computed_type_annotation __all__ = [ "TypeSpec", "BaseType", "ComputedType", + "ReturnedType", + "Void", "BoolTypeSpec", "Bool", "UintTypeSpec", @@ -65,4 +67,5 @@ "DynamicArrayTypeSpec", "DynamicArray", "type_spec_from_annotation", + "type_spec_from_computed_type_annotation", ] diff --git a/pyteal/ast/abi/type.py b/pyteal/ast/abi/type.py index b88a704c7..41787eeed 100644 --- a/pyteal/ast/abi/type.py +++ b/pyteal/ast/abi/type.py @@ -1,4 +1,4 @@ -from typing import TypeVar, Generic, Callable, Final, cast +from typing import TypeVar, Generic, Callable, Final, cast, Literal from abc import ABC, abstractmethod from ...types import TealType @@ -173,3 +173,7 @@ def use(self, action: Callable[[T], Expr]) -> Expr: ComputedType.__module__ = "pyteal" + + +Void = Literal["void"] + diff --git a/pyteal/ast/abi/util.py b/pyteal/ast/abi/util.py index e9077ded6..45e523f04 100644 --- a/pyteal/ast/abi/util.py +++ b/pyteal/ast/abi/util.py @@ -1,10 +1,10 @@ -from typing import Any, Literal, get_origin, get_args +from typing import Any, Literal, get_origin, get_args, Union from ...errors import TealInputError from ..expr import Expr from ..int import Int from ..substring import Extract, Substring, Suffix -from .type import TypeSpec +from .type import TypeSpec, ComputedType, Void def substringForDecoding( @@ -12,7 +12,7 @@ def substringForDecoding( *, startIndex: Expr = None, endIndex: Expr = None, - length: Expr = None + length: Expr = None, ) -> Expr: """A helper function for getting the substring to decode according to the rules of BaseType.decode.""" if length is not None and endIndex is not None: @@ -193,3 +193,19 @@ def type_spec_from_annotation(annotation: Any) -> TypeSpec: return TupleTypeSpec(*(type_spec_from_annotation(arg) for arg in args)) raise TypeError("Unknown annotation origin: {}".format(origin)) + + +def type_spec_from_computed_type_annotation(annotation: Any) -> Union[str, TypeSpec]: + if annotation is Void: + return "void" + + if get_origin(annotation) is not ComputedType: + raise TealInputError( + f"expected type annotation ComputedType[...] but get {get_origin(annotation)}" + ) + args = get_args(annotation) + if len(args) != 1: + raise TealInputError( + f"expected ComputedType[...] has 1 argument annotation but get {len(args)}" + ) + return type_spec_from_annotation(args[0]) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index baa7d3db4..0f3f0f970 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -12,8 +12,11 @@ Tuple, cast, Any, + TypeVar, ) +from pyteal.ast.int import Int + from ..errors import TealInputError, verifyTealVersion from ..ir import TealOp, Op, TealBlock from ..types import TealType @@ -27,12 +30,17 @@ from ..compiler import CompileOptions +T_abi = TypeVar("T_abi", bound=abi.BaseType) +T_abi_ret = Union[abi.Void, abi.ComputedType[T_abi]] +T_sub_ret = Union[T_abi_ret, Expr] + + class SubroutineDefinition: nextSubroutineId = 0 def __init__( self, - implementation: Callable[..., Expr], + implementation: Callable[..., T_sub_ret], return_type: TealType, name_str: Optional[str] = None, ) -> None: @@ -105,9 +113,7 @@ def _validate_parameter_type( else: if not isclass(ptype) and not SubroutineDefinition.is_abi_annotation(ptype): raise TealInputError( - "Function has parameter {} of declared type {} which is not a class".format( - parameter_name, ptype - ) + f"Function has parameter {parameter_name} of declared type {ptype} which is not a class" ) if ptype in (Expr, ScratchVar): @@ -116,9 +122,8 @@ def _validate_parameter_type( return abi.type_spec_from_annotation(ptype) else: raise TealInputError( - "Function has parameter {} of disallowed type {}. Only the types {} are allowed".format( - parameter_name, ptype, (Expr, ScratchVar, "ABI") - ) + f"Function has parameter {parameter_name} of disallowed type {ptype}. " + f"Only the types {(Expr, ScratchVar, 'ABI')} are allowed" ) @staticmethod @@ -383,6 +388,56 @@ def has_return(self): SubroutineFnWrapper.__module__ = "pyteal" +class ABIReturnSubroutineFnWrapper: + def __init__( + self, + fn_implementation: Callable[..., T_abi_ret], + name: Optional[str] = None, + ) -> None: + annos = getattr(fn_implementation, "__annotations__") + type_spec_or_void = annos.get("return", abi.Void) + self.abi_type = abi.type_spec_from_computed_type_annotation(type_spec_or_void) + + stack_type: TealType = ( + TealType.none + if type_spec_or_void == abi.Void + else cast(abi.TypeSpec, type_spec_or_void).storage_type() + ) + self.subroutine = SubroutineDefinition( + fn_implementation, + return_type=stack_type, + name_str=name, + ) + + def __call__( + self, *args: Union[Expr, ScratchVar, abi.BaseType], **kwargs + ) -> Union[abi.ReturnedType, Expr]: + if len(kwargs) != 0: + raise TealInputError( + f"Subroutine cannot be called with keyword arguments. " + f"Received keyword arguments: {','.join(kwargs.keys())}" + ) + + invoked = self.subroutine.invoke(list(args)) + + if self.type_of() == "void": + return invoked + else: + return abi.ReturnedType(cast(abi.TypeSpec, self.abi_type), invoked) + + def name(self) -> str: + return self.subroutine.name() + + def type_of(self) -> Union[str, abi.TypeSpec]: + return self.abi_type + + def is_registrable(self) -> bool: + return len(self.subroutine.abi_args) == self.subroutine.argumentCount() + + +ABIReturnSubroutineFnWrapper.__module__ = "pyteal" + + class Subroutine: """Used to create a PyTeal subroutine from a Python function. @@ -421,6 +476,22 @@ def __call__(self, fn_implementation: Callable[..., Expr]) -> SubroutineFnWrappe Subroutine.__module__ = "pyteal" +class ABIReturnSubroutine: + def __init__(self, name: Optional[str] = None) -> None: + self.name = name + + def __call__( + self, fn_implementation: Callable[..., T_abi_ret] + ) -> ABIReturnSubroutineFnWrapper: + return ABIReturnSubroutineFnWrapper( + fn_implementation=fn_implementation, + name=self.name, + ) + + +ABIReturnSubroutine.__module__ = "pyteal" + + def evaluateSubroutine(subroutine: SubroutineDefinition) -> SubroutineDeclaration: """ Puts together the data necessary to define the code for a subroutine. From 616968ded713b3733fc3105dd0aa704a65b20ec6 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 31 Mar 2022 11:39:14 -0400 Subject: [PATCH 014/170] simplify decorator to single function --- pyteal/ast/subroutine.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 7c0cf9267..d53396507 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -474,20 +474,10 @@ def __call__(self, fn_implementation: Callable[..., Expr]) -> SubroutineFnWrappe Subroutine.__module__ = "pyteal" -class ABIReturnSubroutine: - def __init__(self, name: Optional[str] = None) -> None: - self.name = name - - def __call__( - self, fn_implementation: Callable[..., T_abi_ret] - ) -> ABIReturnSubroutineFnWrapper: - return ABIReturnSubroutineFnWrapper( - fn_implementation=fn_implementation, - name=self.name, - ) - - -ABIReturnSubroutine.__module__ = "pyteal" +def abi_return_subroutine( + fn_implementation: Callable[..., T_abi_ret] +) -> ABIReturnSubroutineFnWrapper: + return ABIReturnSubroutineFnWrapper(fn_implementation=fn_implementation) def evaluateSubroutine(subroutine: SubroutineDefinition) -> SubroutineDeclaration: From 12e3ee4795d85f21f61b0f4f84f8d0a628f1c84e Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 31 Mar 2022 12:21:47 -0400 Subject: [PATCH 015/170] tear it down --- .gitignore | 3 +++ pyteal/ast/abi/__init__.py | 3 +-- pyteal/ast/abi/type.py | 5 ----- pyteal/ast/abi/util.py | 6 +++--- pyteal/ast/subroutine.py | 16 +++++----------- 5 files changed, 12 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index a23af5544..e16583f29 100644 --- a/.gitignore +++ b/.gitignore @@ -132,3 +132,6 @@ dmypy.json # IDE .idea + +# emacs +*.~undo-tree~ diff --git a/pyteal/ast/abi/__init__.py b/pyteal/ast/abi/__init__.py index bdc24b255..4c7aef4f6 100644 --- a/pyteal/ast/abi/__init__.py +++ b/pyteal/ast/abi/__init__.py @@ -1,4 +1,4 @@ -from .type import TypeSpec, BaseType, ComputedType, ReturnedType, Void +from .type import TypeSpec, BaseType, ComputedType, ReturnedType from .bool import BoolTypeSpec, Bool from .uint import ( UintTypeSpec, @@ -35,7 +35,6 @@ "BaseType", "ComputedType", "ReturnedType", - "Void", "BoolTypeSpec", "Bool", "UintTypeSpec", diff --git a/pyteal/ast/abi/type.py b/pyteal/ast/abi/type.py index 95e5bed0a..1e9410960 100644 --- a/pyteal/ast/abi/type.py +++ b/pyteal/ast/abi/type.py @@ -176,11 +176,6 @@ def use(self, action: Callable[[T], Expr]) -> Expr: ComputedType.__module__ = "pyteal" -Void = Literal["void"] - -Void.__module__ = "pyteal" - - class ReturnedType(ComputedType): def __init__(self, type_spec: TypeSpec, encodings: Expr): self.type_spec = type_spec diff --git a/pyteal/ast/abi/util.py b/pyteal/ast/abi/util.py index 45e523f04..1a39c4482 100644 --- a/pyteal/ast/abi/util.py +++ b/pyteal/ast/abi/util.py @@ -4,7 +4,7 @@ from ..expr import Expr from ..int import Int from ..substring import Extract, Substring, Suffix -from .type import TypeSpec, ComputedType, Void +from .type import TypeSpec, ComputedType def substringForDecoding( @@ -195,8 +195,8 @@ def type_spec_from_annotation(annotation: Any) -> TypeSpec: raise TypeError("Unknown annotation origin: {}".format(origin)) -def type_spec_from_computed_type_annotation(annotation: Any) -> Union[str, TypeSpec]: - if annotation is Void: +def type_spec_from_computed_type_annotation(annotation: Any) -> Union[TypeSpec, str]: + if annotation is None: return "void" if get_origin(annotation) is not ComputedType: diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index d53396507..11524170b 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -12,7 +12,6 @@ Tuple, cast, Any, - TypeVar, ) from ..errors import TealInputError, verifyTealVersion @@ -28,17 +27,12 @@ from ..compiler import CompileOptions -T_abi = TypeVar("T_abi", bound=abi.BaseType) -T_abi_ret = Union[abi.Void, abi.ComputedType[T_abi]] -T_sub_ret = Union[T_abi_ret, Expr] - - class SubroutineDefinition: nextSubroutineId = 0 def __init__( self, - implementation: Callable[..., T_sub_ret], + implementation: Callable[..., Expr], return_type: TealType, name_str: Optional[str] = None, ) -> None: @@ -389,16 +383,16 @@ def has_return(self): class ABIReturnSubroutineFnWrapper: def __init__( self, - fn_implementation: Callable[..., T_abi_ret], + fn_implementation: Callable[..., Expr], name: Optional[str] = None, ) -> None: annos = getattr(fn_implementation, "__annotations__") - type_spec_or_void = annos.get("return", abi.Void) + type_spec_or_void = annos.get("return", None) self.abi_type = abi.type_spec_from_computed_type_annotation(type_spec_or_void) stack_type: TealType = ( TealType.none - if type_spec_or_void == abi.Void + if type_spec_or_void == "void" else cast(abi.TypeSpec, type_spec_or_void).storage_type() ) self.subroutine = SubroutineDefinition( @@ -475,7 +469,7 @@ def __call__(self, fn_implementation: Callable[..., Expr]) -> SubroutineFnWrappe def abi_return_subroutine( - fn_implementation: Callable[..., T_abi_ret] + fn_implementation: Callable[..., Expr] ) -> ABIReturnSubroutineFnWrapper: return ABIReturnSubroutineFnWrapper(fn_implementation=fn_implementation) From f6af8fae9b0f6bb0d2da738ef062cb39fcc3486d Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 31 Mar 2022 12:24:55 -0400 Subject: [PATCH 016/170] minor --- pyteal/ast/abi/type.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/abi/type.py b/pyteal/ast/abi/type.py index 1e9410960..c20fdecb7 100644 --- a/pyteal/ast/abi/type.py +++ b/pyteal/ast/abi/type.py @@ -1,4 +1,4 @@ -from typing import TypeVar, Generic, Callable, Final, cast, Literal +from typing import TypeVar, Generic, Callable, Final, cast from abc import ABC, abstractmethod from ...types import TealType From fee70a6a1b1febf5c92e6502856ade21fc680c7d Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 31 Mar 2022 12:25:55 -0400 Subject: [PATCH 017/170] sheeeesh emacs --- .gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index e16583f29..a23af5544 100644 --- a/.gitignore +++ b/.gitignore @@ -132,6 +132,3 @@ dmypy.json # IDE .idea - -# emacs -*.~undo-tree~ From f0101d032824eff7aee06224d5c1342fc29716a7 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 12 Apr 2022 17:18:20 -0400 Subject: [PATCH 018/170] updates --- pyteal/ast/abi/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyteal/ast/abi/util.py b/pyteal/ast/abi/util.py index 1a39c4482..5290520b7 100644 --- a/pyteal/ast/abi/util.py +++ b/pyteal/ast/abi/util.py @@ -4,7 +4,7 @@ from ..expr import Expr from ..int import Int from ..substring import Extract, Substring, Suffix -from .type import TypeSpec, ComputedType +from .type import TypeSpec, ComputedValue def substringForDecoding( @@ -199,7 +199,7 @@ def type_spec_from_computed_type_annotation(annotation: Any) -> Union[TypeSpec, if annotation is None: return "void" - if get_origin(annotation) is not ComputedType: + if get_origin(annotation) is not ComputedValue: raise TealInputError( f"expected type annotation ComputedType[...] but get {get_origin(annotation)}" ) From a2d9ea201f4276867ba552f085bd16866f43fa68 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 14 Apr 2022 15:22:26 -0400 Subject: [PATCH 019/170] minor, renaming something --- pyteal/ast/abi/__init__.py | 4 ++-- pyteal/ast/abi/type.py | 4 ++-- pyteal/ast/subroutine.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyteal/ast/abi/__init__.py b/pyteal/ast/abi/__init__.py index 3ef226d2f..b6563ca59 100644 --- a/pyteal/ast/abi/__init__.py +++ b/pyteal/ast/abi/__init__.py @@ -1,4 +1,4 @@ -from .type import TypeSpec, BaseType, ComputedValue, ReturnedType +from .type import TypeSpec, BaseType, ComputedValue, ReturnedValue from .bool import BoolTypeSpec, Bool from .uint import ( UintTypeSpec, @@ -35,7 +35,7 @@ "TypeSpec", "BaseType", "ComputedValue", - "ReturnedType", + "ReturnedValue", "BoolTypeSpec", "Bool", "UintTypeSpec", diff --git a/pyteal/ast/abi/type.py b/pyteal/ast/abi/type.py index fe4225ebd..e9d92c9cd 100644 --- a/pyteal/ast/abi/type.py +++ b/pyteal/ast/abi/type.py @@ -184,7 +184,7 @@ def use(self, action: Callable[[T], Expr]) -> Expr: ComputedValue.__module__ = "pyteal" -class ReturnedType(ComputedValue): +class ReturnedValue(ComputedValue): def __init__(self, type_spec: TypeSpec, encodings: Expr): self.type_spec = type_spec self.encodings = encodings @@ -200,4 +200,4 @@ def store_into(self, output: BaseType) -> Expr: return output.stored_value.store(self.encodings) -ReturnedType.__module__ = "pyteal" +ReturnedValue.__module__ = "pyteal" diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 11524170b..53659ec90 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -403,7 +403,7 @@ def __init__( def __call__( self, *args: Union[Expr, ScratchVar, abi.BaseType], **kwargs - ) -> Union[abi.ReturnedType, Expr]: + ) -> Union[abi.ReturnedValue, Expr]: if len(kwargs) != 0: raise TealInputError( f"Subroutine cannot be called with keyword arguments. " @@ -415,7 +415,7 @@ def __call__( if self.type_of() == "void": return invoked else: - return abi.ReturnedType(cast(abi.TypeSpec, self.abi_type), invoked) + return abi.ReturnedValue(cast(abi.TypeSpec, self.abi_type), invoked) def name(self) -> str: return self.subroutine.name() From 91c87b7584283c515597fafbf789e1b3672b3c0b Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 14 Apr 2022 15:52:13 -0400 Subject: [PATCH 020/170] new design, start over --- pyteal/ast/abi/__init__.py | 3 +-- pyteal/ast/abi/util.py | 20 ++------------------ pyteal/ast/subroutine.py | 15 +++++++++++++-- 3 files changed, 16 insertions(+), 22 deletions(-) diff --git a/pyteal/ast/abi/__init__.py b/pyteal/ast/abi/__init__.py index b6563ca59..c5865bb8b 100644 --- a/pyteal/ast/abi/__init__.py +++ b/pyteal/ast/abi/__init__.py @@ -28,7 +28,7 @@ from .array_base import ArrayTypeSpec, Array, ArrayElement from .array_static import StaticArrayTypeSpec, StaticArray from .array_dynamic import DynamicArrayTypeSpec, DynamicArray -from .util import type_spec_from_annotation, type_spec_from_computed_type_annotation +from .util import type_spec_from_annotation from .method_return import MethodReturn __all__ = [ @@ -67,6 +67,5 @@ "DynamicArrayTypeSpec", "DynamicArray", "type_spec_from_annotation", - "type_spec_from_computed_type_annotation", "MethodReturn", ] diff --git a/pyteal/ast/abi/util.py b/pyteal/ast/abi/util.py index 5290520b7..1032e37e1 100644 --- a/pyteal/ast/abi/util.py +++ b/pyteal/ast/abi/util.py @@ -1,10 +1,10 @@ -from typing import Any, Literal, get_origin, get_args, Union +from typing import Any, Literal, get_origin, get_args from ...errors import TealInputError from ..expr import Expr from ..int import Int from ..substring import Extract, Substring, Suffix -from .type import TypeSpec, ComputedValue +from .type import TypeSpec def substringForDecoding( @@ -193,19 +193,3 @@ def type_spec_from_annotation(annotation: Any) -> TypeSpec: return TupleTypeSpec(*(type_spec_from_annotation(arg) for arg in args)) raise TypeError("Unknown annotation origin: {}".format(origin)) - - -def type_spec_from_computed_type_annotation(annotation: Any) -> Union[TypeSpec, str]: - if annotation is None: - return "void" - - if get_origin(annotation) is not ComputedValue: - raise TealInputError( - f"expected type annotation ComputedType[...] but get {get_origin(annotation)}" - ) - args = get_args(annotation) - if len(args) != 1: - raise TealInputError( - f"expected ComputedType[...] has 1 argument annotation but get {len(args)}" - ) - return type_spec_from_annotation(args[0]) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 53659ec90..625c1c138 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -386,6 +386,10 @@ def __init__( fn_implementation: Callable[..., Expr], name: Optional[str] = None, ) -> None: + self.abi_type = "void" + pass + + """ annos = getattr(fn_implementation, "__annotations__") type_spec_or_void = annos.get("return", None) self.abi_type = abi.type_spec_from_computed_type_annotation(type_spec_or_void) @@ -400,10 +404,12 @@ def __init__( return_type=stack_type, name_str=name, ) + """ def __call__( self, *args: Union[Expr, ScratchVar, abi.BaseType], **kwargs ) -> Union[abi.ReturnedValue, Expr]: + """ if len(kwargs) != 0: raise TealInputError( f"Subroutine cannot be called with keyword arguments. " @@ -416,15 +422,20 @@ def __call__( return invoked else: return abi.ReturnedValue(cast(abi.TypeSpec, self.abi_type), invoked) + """ + return Seq() def name(self) -> str: - return self.subroutine.name() + # return self.subroutine.name() + return "TODO" def type_of(self) -> Union[str, abi.TypeSpec]: return self.abi_type def is_registrable(self) -> bool: - return len(self.subroutine.abi_args) == self.subroutine.argumentCount() + # return len(self.subroutine.abi_args) == self.subroutine.argumentCount() + # TODO + return False ABIReturnSubroutineFnWrapper.__module__ = "pyteal" From 3fb73746bf6d0a25aad63ddb162bf8c62c279e07 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 15 Apr 2022 14:43:35 -0400 Subject: [PATCH 021/170] updates --- pyteal/ast/subroutine.py | 49 ++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 625c1c138..0ddc627f4 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -384,16 +384,19 @@ class ABIReturnSubroutineFnWrapper: def __init__( self, fn_implementation: Callable[..., Expr], - name: Optional[str] = None, ) -> None: - self.abi_type = "void" - pass + self.output_name: Union[None, str] + self.abi_type: Union[str, abi.TypeSpec] - """ - annos = getattr(fn_implementation, "__annotations__") - type_spec_or_void = annos.get("return", None) - self.abi_type = abi.type_spec_from_computed_type_annotation(type_spec_or_void) + self.output_name, self.abi_type = self._output_type_from_fn(fn_implementation) + + self.output_arg: Union[None, abi.BaseType] = ( + None + if self.abi_type is str + else cast(abi.TypeSpec, self.abi_type).new_instance() + ) + """ stack_type: TealType = ( TealType.none if type_spec_or_void == "void" @@ -406,18 +409,46 @@ def __init__( ) """ + @staticmethod + def _output_type_from_fn( + fn_implementation: Callable[..., Expr] + ) -> Union[Tuple[None, str], Tuple[str, abi.TypeSpec]]: + sig = signature(fn_implementation) + fn_annotations = getattr(fn_implementation, "__annotations__", OrderedDict()) + + potential_abi_arg_names = list( + filter( + lambda key: sig.parameters[key].kind == Parameter.KEYWORD_ONLY, + sig.parameters.keys(), + ) + ) + if len(potential_abi_arg_names) == 0: + return None, "void" + elif len(potential_abi_arg_names) == 0: + name = potential_abi_arg_names[0] + annotation = fn_annotations.get(name, None) + if annotation is None: + raise TealInputError( + f"abi subroutine output {name} must specify ABI type" + ) + return name, abi.type_spec_from_annotation(annotation) + else: + raise TealInputError( + f"multiple output arguments with type annotations {potential_abi_arg_names}" + ) + def __call__( self, *args: Union[Expr, ScratchVar, abi.BaseType], **kwargs ) -> Union[abi.ReturnedValue, Expr]: - """ if len(kwargs) != 0: raise TealInputError( f"Subroutine cannot be called with keyword arguments. " f"Received keyword arguments: {','.join(kwargs.keys())}" ) - invoked = self.subroutine.invoke(list(args)) + # invoked = self.subroutine.invoke(list(args)) + """ if self.type_of() == "void": return invoked else: From cf3106d55cac7d66aa13b235c130f56ffc3ef3e2 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Sat, 23 Apr 2022 16:08:02 -0400 Subject: [PATCH 022/170] abi fn wrapper for now --- pyteal/ast/abi/type.py | 19 ++-- pyteal/ast/subroutine.py | 235 +++++++++++++++++++++++++-------------- 2 files changed, 164 insertions(+), 90 deletions(-) diff --git a/pyteal/ast/abi/type.py b/pyteal/ast/abi/type.py index e9d92c9cd..d403b8705 100644 --- a/pyteal/ast/abi/type.py +++ b/pyteal/ast/abi/type.py @@ -72,7 +72,7 @@ def __init__(self, spec: TypeSpec) -> None: """Create a new BaseType.""" super().__init__() self._type_spec: Final = spec - self.stored_value: Final = ScratchVar(spec.storage_type()) + self.stored_value = ScratchVar(spec.storage_type()) def type_spec(self) -> TypeSpec: """Get the TypeSpec for this ABI type instance.""" @@ -185,19 +185,22 @@ def use(self, action: Callable[[T], Expr]) -> Expr: class ReturnedValue(ComputedValue): - def __init__(self, type_spec: TypeSpec, encodings: Expr): - self.type_spec = type_spec - self.encodings = encodings + def __init__(self, abi_return: BaseType, computation_expr: Expr): + self.abi_return = abi_return + self.computation = computation_expr def produced_type_spec(self) -> TypeSpec: - return self.type_spec + return self.abi_return.type_spec() def store_into(self, output: BaseType) -> Expr: - if output.type_spec() != self.type_spec: + if output.type_spec() != self.abi_return: raise TealInputError( - f"expected type_spec {self.type_spec} but get {output.type_spec()}" + f"expected type_spec {self.produced_type_spec()} but get {output.type_spec()}" ) - return output.stored_value.store(self.encodings) + return Seq( + self.computation, + output.stored_value.store(self.abi_return.stored_value.load()), + ) ReturnedValue.__module__ = "pyteal" diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 0ddc627f4..9d3aa3413 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -1,19 +1,17 @@ from collections import OrderedDict +from dataclasses import dataclass from inspect import isclass, Parameter, signature, Signature from typing import ( Callable, - Dict, - List, Optional, - Set, Type, - Union, TYPE_CHECKING, - Tuple, cast, Any, ) +from pyteal.ast.abi.type import ReturnedValue + from ..errors import TealInputError, verifyTealVersion from ..ir import TealOp, Op, TealBlock from ..types import TealType @@ -35,6 +33,7 @@ def __init__( implementation: Callable[..., Expr], return_type: TealType, name_str: Optional[str] = None, + abi_output_arg_name: Optional[str] = None, ) -> None: super().__init__() self.id = SubroutineDefinition.nextSubroutineId @@ -57,17 +56,22 @@ def __init__( # NOTE: it contains all the arguments, we get type annotations from `annotations`. # - `annotations`, which contains all available argument type annotations and return type annotation. # NOTE: `annotations` does not contain all the arguments, - # an argument is not included in `annotations` if its type annotation is not available. + # an argument is not included in `annotations` if its type + # annotation is not available. ( expected_arg_types, by_ref_args, abi_args, - ) = SubroutineDefinition._arg_types_and_by_refs(sig, annotations) - self.expected_arg_types: List[ - Union[Type[Expr], Type[ScratchVar], abi.TypeSpec] + abi_output_kwarg, + ) = SubroutineDefinition._arg_types_and_by_refs( + sig, annotations, abi_output_arg_name + ) + self.expected_arg_types: list[ + Type[Expr] | Type[ScratchVar] | abi.TypeSpec ] = expected_arg_types - self.by_ref_args: Set[str] = by_ref_args - self.abi_args: Dict[str, abi.TypeSpec] = abi_args + self.by_ref_args: set[str] = by_ref_args + self.abi_args: dict[str, abi.TypeSpec] = abi_args + self.abi_output_kwarg: dict[str, abi.TypeSpec] = abi_output_kwarg self.implementation = implementation self.implementation_params = sig.parameters @@ -86,8 +90,8 @@ def is_abi_annotation(obj: Any) -> bool: @staticmethod def _validate_parameter_type( - user_defined_annotations: dict, parameter_name: str - ) -> Union[Type[Expr], Type[ScratchVar], abi.TypeSpec]: + user_defined_annotations: dict[str, Any], parameter_name: str + ) -> Type[Expr] | Type[ScratchVar] | abi.TypeSpec: ptype = user_defined_annotations.get(parameter_name, None) if ptype is None: @@ -121,11 +125,13 @@ def _validate_parameter_type( @staticmethod def _arg_types_and_by_refs( sig: Signature, - annotations: Dict[str, type], - ) -> Tuple[ - List[Union[Type[Expr], Type[ScratchVar], abi.TypeSpec]], - Set[str], - Dict[str, abi.TypeSpec], + annotations: dict[str, type], + abi_output_arg_name: Optional[str] = None, + ) -> tuple[ + list[Type[Expr] | Type[ScratchVar] | abi.TypeSpec], + set[str], + dict[str, abi.TypeSpec], + dict[str, abi.TypeSpec], ]: """Validate the full function signature and annotations for subroutine definition. @@ -155,17 +161,23 @@ def _arg_types_and_by_refs( an argument is not included in `annotations` if its type annotation is not available. """ expected_arg_types = [] - by_ref_args: Set[str] = set() - abi_args: Dict[str, abi.TypeSpec] = {} + by_ref_args: set[str] = set() + abi_args: dict[str, abi.TypeSpec] = {} + abi_output_kwarg: dict[str, abi.TypeSpec] = {} for name, param in sig.parameters.items(): if param.kind not in ( Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD, + ) and not ( + param.kind is Parameter.KEYWORD_ONLY + and abi_output_arg_name is not None + and name == abi_output_arg_name ): raise TealInputError( f"Function has a parameter type that is not allowed in a subroutine: " f"parameter {name} with type {param.kind}" ) + if param.default != Parameter.empty: raise TealInputError( f"Function has a parameter with a default value, which is not allowed in a subroutine: {name}" @@ -174,13 +186,24 @@ def _arg_types_and_by_refs( expected_arg_type = SubroutineDefinition._validate_parameter_type( annotations, name ) - expected_arg_types.append(expected_arg_type) + + if param.kind is Parameter.KEYWORD_ONLY: + if not isinstance(expected_arg_type, abi.TypeSpec): + raise TealInputError( + f"Function keyword parameter {name} has type {expected_arg_type}" + ) + # TODO not sure if I should put this in by_ref_args, since compiler will use this to disallow recursion + abi_output_kwarg[name] = expected_arg_type + continue + else: + expected_arg_types.append(expected_arg_type) + if expected_arg_type is ScratchVar: by_ref_args.add(name) if isinstance(expected_arg_type, abi.TypeSpec): abi_args[name] = expected_arg_type - return expected_arg_types, by_ref_args, abi_args + return expected_arg_types, by_ref_args, abi_args, abi_output_kwarg def getDeclaration(self) -> "SubroutineDeclaration": if self.declaration is None: @@ -194,11 +217,15 @@ def name(self) -> str: def argumentCount(self) -> int: return len(self.implementation_params) - def arguments(self) -> List[str]: + def arguments(self) -> list[str]: return list(self.implementation_params.keys()) + # TODO need to support keyword invoke def invoke( - self, args: List[Union[Expr, ScratchVar, abi.BaseType]] + self, + args: list[Expr | ScratchVar | abi.BaseType], + *, + output_kwargs: Optional[dict[str, abi.BaseType]] = None, ) -> "SubroutineCall": if len(args) != self.argumentCount(): raise TealInputError( @@ -266,11 +293,12 @@ def has_return(self): SubroutineDeclaration.__module__ = "pyteal" +# TODO support keyword argument class SubroutineCall(Expr): def __init__( self, subroutine: SubroutineDefinition, - args: List[Union[Expr, ScratchVar, abi.BaseType]], + args: list[Expr | ScratchVar | abi.BaseType], ) -> None: super().__init__() self.subroutine = subroutine @@ -314,7 +342,7 @@ def __teal__(self, options: "CompileOptions"): "TEAL version too low to use SubroutineCall expression", ) - def handle_arg(arg: Union[Expr, ScratchVar, abi.BaseType]) -> Expr: + def handle_arg(arg: Expr | ScratchVar | abi.BaseType) -> Expr: if isinstance(arg, ScratchVar): return arg.index() elif isinstance(arg, Expr): @@ -359,7 +387,7 @@ def __init__( name_str=name, ) - def __call__(self, *args: Union[Expr, ScratchVar, abi.BaseType], **kwargs) -> Expr: + def __call__(self, *args: Expr | ScratchVar | abi.BaseType, **kwargs) -> Expr: if len(kwargs) != 0: raise TealInputError( f"Subroutine cannot be called with keyword arguments. " @@ -380,39 +408,36 @@ def has_return(self): SubroutineFnWrapper.__module__ = "pyteal" +@dataclass +class _OutputKwArgInfo: + name: str + abi_type: abi.TypeSpec + abi_instance: abi.BaseType + + class ABIReturnSubroutineFnWrapper: def __init__( self, fn_implementation: Callable[..., Expr], ) -> None: - self.output_name: Union[None, str] - self.abi_type: Union[str, abi.TypeSpec] - - self.output_name, self.abi_type = self._output_type_from_fn(fn_implementation) - - self.output_arg: Union[None, abi.BaseType] = ( - None - if self.abi_type is str - else cast(abi.TypeSpec, self.abi_type).new_instance() + self.output_kwarg_info: None | _OutputKwArgInfo = ( + self._output_name_type_from_fn(fn_implementation) ) - - """ - stack_type: TealType = ( - TealType.none - if type_spec_or_void == "void" - else cast(abi.TypeSpec, type_spec_or_void).storage_type() + output_kwarg_name = ( + None if self.output_kwarg_info is None else self.output_kwarg_info.name ) + + # no matter what, output is void or abiType, stack type is TealType.none self.subroutine = SubroutineDefinition( fn_implementation, - return_type=stack_type, - name_str=name, + return_type=TealType.none, + abi_output_arg_name=output_kwarg_name, ) - """ @staticmethod - def _output_type_from_fn( + def _output_name_type_from_fn( fn_implementation: Callable[..., Expr] - ) -> Union[Tuple[None, str], Tuple[str, abi.TypeSpec]]: + ) -> None | _OutputKwArgInfo: sig = signature(fn_implementation) fn_annotations = getattr(fn_implementation, "__annotations__", OrderedDict()) @@ -423,45 +448,60 @@ def _output_type_from_fn( ) ) if len(potential_abi_arg_names) == 0: - return None, "void" - elif len(potential_abi_arg_names) == 0: + return None + elif len(potential_abi_arg_names) == 1: name = potential_abi_arg_names[0] annotation = fn_annotations.get(name, None) if annotation is None: raise TealInputError( - f"abi subroutine output {name} must specify ABI type" + f"ABI subroutine output-kwarg {name} must specify ABI type" ) - return name, abi.type_spec_from_annotation(annotation) + type_spec = abi.type_spec_from_annotation(annotation) + type_instance = type_spec.new_instance() + return _OutputKwArgInfo(name, type_spec, type_instance) else: raise TealInputError( f"multiple output arguments with type annotations {potential_abi_arg_names}" ) def __call__( - self, *args: Union[Expr, ScratchVar, abi.BaseType], **kwargs - ) -> Union[abi.ReturnedValue, Expr]: - if len(kwargs) != 0: + self, *args: Expr | ScratchVar | abi.BaseType, **kwargs: abi.BaseType + ) -> abi.ReturnedValue | Expr: + if self.output_kwarg_info is None: + if len(kwargs) != 0: + raise TealInputError( + f"Subroutine cannot be called with keyword arguments. " + f"Received keyword arguments: {','.join(kwargs.keys())}" + ) + return self.subroutine.invoke(list(args)) + + if len(kwargs) != 1: raise TealInputError( - f"Subroutine cannot be called with keyword arguments. " - f"Received keyword arguments: {','.join(kwargs.keys())}" + f"Subroutine should have provided output keyword argument with name {self.output_kwarg_info.name}, " + f"while the kwargs are {kwargs}." ) - - # invoked = self.subroutine.invoke(list(args)) - - """ - if self.type_of() == "void": - return invoked - else: - return abi.ReturnedValue(cast(abi.TypeSpec, self.abi_type), invoked) - """ - return Seq() + if self.output_kwarg_info.name not in kwargs: + raise TealInputError( + f"Subroutine should have provided output keyword argument with name {self.output_kwarg_info.name}, " + f"while provided kwarg is {list(kwargs.keys())}" + ) + invoked = self.subroutine.invoke( + list(args), + output_kwargs={ + self.output_kwarg_info.name: self.output_kwarg_info.abi_instance + }, + ) + return ReturnedValue(self.output_kwarg_info.abi_instance, invoked) def name(self) -> str: - # return self.subroutine.name() - return "TODO" + return self.subroutine.name() - def type_of(self) -> Union[str, abi.TypeSpec]: - return self.abi_type + def type_of(self) -> str | abi.TypeSpec: + return ( + "void" + if self.output_kwarg_info is None + else self.output_kwarg_info.abi_type + ) def is_registrable(self) -> bool: # return len(self.subroutine.abi_args) == self.subroutine.argumentCount() @@ -541,44 +581,75 @@ def evaluateSubroutine(subroutine: SubroutineDefinition) -> SubroutineDeclaratio Type 1 (by-value): these have python type Expr Type 2 (by-reference): these have python type ScratchVar Type 3 (ABI): these are ABI typed variables with scratch space storage, and still pass by value + Type 4 (ABI-output-arg): ABI typed variables with scractch space, but pass by ref to allow for changes Usage (A) "argumentVars" - Storing pre-placed stack variables into local scratch space: Type 1. (by-value) use ScratchVar.store() to pick the actual value into a local scratch space Type 2. (by-reference) ALSO use ScratchVar.store() to pick up from the stack NOTE: SubroutineCall.__teal__() has placed the _SLOT INDEX_ on the stack so this is stored into the local scratch space Type 3. (ABI) abi_value.stored_value.store() to pick from the stack + Type 4. (ABI-output-arg) use ScratchVar.store() to pick up from the stack + NOTE: SubroutineCall.__teal__() has placed the ABI value's _SLOT INDEX_ on the stack, pass-by-ref is similarly achieved Usage (B) "loadedArgs" - Passing through to an invoked PyTEAL subroutine AST: Type 1. (by-value) use ScratchVar.load() to have an Expr that can be compiled in python by the PyTEAL subroutine Type 2. (by-reference) use a DynamicScratchVar as the user will have written the PyTEAL in a way that satisfies the ScratchVar API. I.e., the user will write `x.load()` and `x.store(val)` as opposed to just `x`. Type 3. (ABI) use abi_value itself after storing stack value into scratch space. + Type 4. (ABI-output-arg) use a DynamicScratchVar to "point to" the output ABI value's scratch variable + all the ABI operations are passed through DynamicScratchVar to original ScratchVar, which is pass-by-ref behavior """ def var_n_loaded( param: str, - ) -> Tuple[ScratchVar, Union[ScratchVar, abi.BaseType, Expr]]: - loaded: Union[ScratchVar, abi.BaseType, Expr] - argVar: ScratchVar + ) -> tuple[ScratchVar, ScratchVar | abi.BaseType | Expr]: + loaded: ScratchVar | abi.BaseType | Expr + arg_var: ScratchVar if param in subroutine.by_ref_args: - argVar = DynamicScratchVar(TealType.anytype) - loaded = argVar + arg_var = DynamicScratchVar(TealType.anytype) + loaded = arg_var + elif param in subroutine.abi_output_kwarg: + arg_var = DynamicScratchVar(TealType.anytype) + internal_abi_var = subroutine.abi_output_kwarg[param].new_instance() + internal_abi_var.stored_value = arg_var + loaded = internal_abi_var elif param in subroutine.abi_args: internal_abi_var = subroutine.abi_args[param].new_instance() - argVar = internal_abi_var.stored_value + arg_var = internal_abi_var.stored_value loaded = internal_abi_var else: - argVar = ScratchVar(TealType.anytype) - loaded = argVar.load() + arg_var = ScratchVar(TealType.anytype) + loaded = arg_var.load() - return argVar, loaded + return arg_var, loaded args = subroutine.arguments() - argumentVars, loadedArgs = zip(*map(var_n_loaded, args)) if args else ([], []) + args = [arg for arg in args if arg not in subroutine.abi_output_kwarg] + + argument_vars, loaded_args = ( + cast( + tuple[list[ScratchVar], list[ScratchVar | Expr | abi.BaseType]], + zip(*map(var_n_loaded, args)), + ) + if args + else ([], []) + ) + + abi_output_kwargs = {} + assert len(subroutine.abi_output_kwarg) <= 1, "exceeding " + if len(subroutine.abi_output_kwarg) > 1: + raise TealInputError( + f"ABI keyword argument num: {len(subroutine.abi_output_kwarg)}. " + f"Exceeding abi output keyword argument max number 1." + ) + for name in subroutine.abi_output_kwarg: + arg_var, loaded = var_n_loaded(name) + abi_output_kwargs[name] = loaded + argument_vars.append(arg_var) # Arg usage "B" supplied to build an AST from the user-defined PyTEAL function: - subroutineBody = subroutine.implementation(*loadedArgs) + subroutineBody = subroutine.implementation(*loaded_args, **abi_output_kwargs) if not isinstance(subroutineBody, Expr): raise TealInputError( @@ -587,7 +658,7 @@ def var_n_loaded( # Arg usage "A" to be pick up and store in scratch parameters that have been placed on the stack # need to reverse order of argumentVars because the last argument will be on top of the stack - bodyOps = [var.slot.store() for var in argumentVars[::-1]] + bodyOps = [var.slot.store() for var in argument_vars[::-1]] bodyOps.append(subroutineBody) return SubroutineDeclaration(subroutine, Seq(bodyOps)) From dec018f2693da3957385d7308a6d6f0dab49e459 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Sat, 23 Apr 2022 16:11:00 -0400 Subject: [PATCH 023/170] minor --- pyteal/ast/abi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/abi/__init__.py b/pyteal/ast/abi/__init__.py index 84c057ded..a1dfb9fa4 100644 --- a/pyteal/ast/abi/__init__.py +++ b/pyteal/ast/abi/__init__.py @@ -1,6 +1,6 @@ from pyteal.ast.abi.string import String, StringTypeSpec from pyteal.ast.abi.address import AddressTypeSpec, Address, ADDRESS_LENGTH -from pyteal.ast.abi.type import TypeSpec, BaseType, ComputedValue +from pyteal.ast.abi.type import TypeSpec, BaseType, ComputedValue, ReturnedValue from pyteal.ast.abi.bool import BoolTypeSpec, Bool from pyteal.ast.abi.uint import ( UintTypeSpec, From 54fe3bc379dea7f5f830bc00b6b5828b32519a9e Mon Sep 17 00:00:00 2001 From: Hang Su Date: Sun, 24 Apr 2022 00:12:06 -0400 Subject: [PATCH 024/170] minor update on subroutine def --- pyteal/ast/subroutine.py | 159 ++++++++++++++++++++------------- pyteal/ast/subroutine_test.py | 14 +-- pyteal/compiler/compiler.py | 2 +- pyteal/compiler/subroutines.py | 2 +- 4 files changed, 105 insertions(+), 72 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 68e9e0353..2afcc0a99 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -70,7 +70,7 @@ def __init__( ] = expected_arg_types self.by_ref_args: set[str] = by_ref_args self.abi_args: dict[str, abi.TypeSpec] = abi_args - self.abi_output_kwarg: dict[str, abi.TypeSpec] = abi_output_kwarg + self.output_kwarg: dict[str, abi.TypeSpec] = abi_output_kwarg self.implementation = implementation self.implementation_params = sig.parameters @@ -191,7 +191,7 @@ def _arg_types_and_by_refs( raise TealInputError( f"Function keyword parameter {name} has type {expected_arg_type}" ) - # TODO not sure if I should put this in by_ref_args, since compiler will use this to disallow recursion + # NOTE not sure if I should put this in by_ref_args, since compiler will use this to disallow recursion abi_output_kwarg[name] = expected_arg_type continue else: @@ -204,32 +204,31 @@ def _arg_types_and_by_refs( return expected_arg_types, by_ref_args, abi_args, abi_output_kwarg - def getDeclaration(self) -> "SubroutineDeclaration": + def get_declaration(self) -> "SubroutineDeclaration": if self.declaration is None: # lazy evaluate subroutine - self.declaration = evaluateSubroutine(self) + self.declaration = evaluate_subroutine(self) return self.declaration def name(self) -> str: return self.__name - def argumentCount(self) -> int: + def argument_count(self) -> int: return len(self.implementation_params) def arguments(self) -> list[str]: return list(self.implementation_params.keys()) - # TODO need to support keyword invoke def invoke( self, args: list[Expr | ScratchVar | abi.BaseType], *, - output_kwargs: Optional[dict[str, abi.BaseType]] = None, + output_kwarg: Optional[dict[str, abi.BaseType]] = None, ) -> "SubroutineCall": - if len(args) != self.argumentCount(): + if len(args) != self.argument_count(): raise TealInputError( f"Incorrect number of arguments for subroutine call. " - f"Expected {self.argumentCount()} arguments, got {len(args)}" + f"Expected {self.argument_count()} arguments, got {len(args)}" ) for i, arg in enumerate(args): @@ -253,7 +252,25 @@ def invoke( f"should have ABI typespec {arg_type} but got {arg.type_spec()}" ) - return SubroutineCall(self, args) + if len(self.output_kwarg) == 1: + if output_kwarg is None: + raise TealInputError( + f"expected output keyword argument {self.output_kwarg} with no input" + ) + actual_kwarg_type_spec = { + key: output_kwarg[key].type_spec() for key in output_kwarg + } + if actual_kwarg_type_spec != self.output_kwarg: + raise TealInputError( + f"expected output keyword argument {self.output_kwarg} with input {actual_kwarg_type_spec}" + ) + return SubroutineCall(self, args, output_kwarg=output_kwarg) + else: + if output_kwarg is not None: + raise TealInputError( + f"expected no output keyword argument with input {output_kwarg}" + ) + return SubroutineCall(self, args) def __str__(self): return f"subroutine#{self.id}" @@ -292,16 +309,18 @@ def has_return(self): SubroutineDeclaration.__module__ = "pyteal" -# TODO support keyword argument class SubroutineCall(Expr): def __init__( self, subroutine: SubroutineDefinition, args: list[Expr | ScratchVar | abi.BaseType], + *, + output_kwarg: Optional[dict[str, abi.BaseType]] = None, ) -> None: super().__init__() self.subroutine = subroutine self.args = args + self.output_kwarg = output_kwarg for i, arg in enumerate(args): if isinstance(arg, Expr): @@ -324,7 +343,7 @@ def __teal__(self, options: "CompileOptions"): """ Generate the subroutine's start and end teal blocks. The subroutine's arguments are pushed on the stack to be picked up into local scratch variables. - There are 2 cases to consider for the pushed arg expression: + There are 4 cases to consider for the pushed arg expression: 1. (by-value) In the case of typical arguments of type Expr, the expression ITSELF is evaluated for the stack and will be stored in a local ScratchVar for subroutine evaluation @@ -334,6 +353,10 @@ def __teal__(self, options: "CompileOptions"): 3. (ABI, or a special case in by-value) In this case, the storage of an ABI value are loaded to the stack and will be stored in a local ABI value for subroutine evaluation + + 4. (ABI output keyword argument, or by-ref ABI value) In this case of returning ABI values, + its SLOT INDEX is put on the stack and will be stored in a local DynamicScratchVar underlying a local ABI + value for subroutine evaluation """ verifyTealVersion( Op.callsub.min_version, @@ -354,14 +377,20 @@ def handle_arg(arg: Expr | ScratchVar | abi.BaseType) -> Expr: ) op = TealOp(self, Op.callsub, self.subroutine) - return TealBlock.FromOp(options, op, *(handle_arg(x) for x in self.args)) + argument_list = [handle_arg(x) for x in self.args] + if self.output_kwarg is not None: + argument_list += [ + x.stored_value.index() for x in self.output_kwarg.values() + ] + return TealBlock.FromOp(options, op, *argument_list) def __str__(self): - ret_str = '(SubroutineCall "' + self.subroutine.name() + '" (' - for a in self.args: - ret_str += " " + a.__str__() - ret_str += "))" - return ret_str + arg_str_list = list(map(str, self.args)) + if self.output_kwarg: + arg_str_list += [ + f"{x}={str(self.output_kwarg[x])}" for x in self.output_kwarg + ] + return f'(SubroutineCall {self.subroutine.name()} ({" ".join(arg_str_list)}))' def type_of(self): return self.subroutine.return_type @@ -386,7 +415,7 @@ def __init__( name_str=name, ) - def __call__(self, *args: Expr | ScratchVar | abi.BaseType, **kwargs) -> Expr: + def __call__(self, *args: Expr | ScratchVar | abi.BaseType, **kwargs: Any) -> Expr: if len(kwargs) != 0: raise TealInputError( f"Subroutine cannot be called with keyword arguments. " @@ -398,10 +427,10 @@ def name(self) -> str: return self.subroutine.name() def type_of(self): - return self.subroutine.getDeclaration().type_of() + return self.subroutine.get_declaration().type_of() def has_return(self): - return self.subroutine.getDeclaration().has_return() + return self.subroutine.get_declaration().has_return() SubroutineFnWrapper.__module__ = "pyteal" @@ -411,10 +440,31 @@ def has_return(self): class _OutputKwArgInfo: name: str abi_type: abi.TypeSpec - abi_instance: abi.BaseType -class ABIReturnSubroutineFnWrapper: +_OutputKwArgInfo.__module__ = "pyteal" + + +class ABIReturnSubroutine: + """Used to create a PyTeal Subroutine (returning an ABI value) from a python function. + + This class is meant to be used as a function decorator. For example: + + .. code-block:: python + + @ABIReturnSubroutine + def an_abi_subroutine(a: abi.Uint64, b: abi.Uint64, *, output: abi.Uint64) -> Expr: + return output.set(a.get() * b.get()) + + program = Seq( + (a := abi.Uint64()).decode(Txn.application_args[1]), + (b := abi.Uint64()).decode(Txn.application_args[2]), + (c := abi.Uint64()).set(an_abi_subroutine(a, b)), + MethodReturn(c), + Approve(), + ) + """ + def __init__( self, fn_implementation: Callable[..., Expr], @@ -456,41 +506,30 @@ def _output_name_type_from_fn( f"ABI subroutine output-kwarg {name} must specify ABI type" ) type_spec = abi.type_spec_from_annotation(annotation) - type_instance = type_spec.new_instance() - return _OutputKwArgInfo(name, type_spec, type_instance) + return _OutputKwArgInfo(name, type_spec) else: raise TealInputError( f"multiple output arguments with type annotations {potential_abi_arg_names}" ) def __call__( - self, *args: Expr | ScratchVar | abi.BaseType, **kwargs: abi.BaseType + self, *args: Expr | ScratchVar | abi.BaseType, **kwargs ) -> abi.ReturnedValue | Expr: + if len(kwargs) != 0: + raise TealInputError( + f"Subroutine cannot be called with keyword arguments. " + f"Received keyword arguments: {', '.join(kwargs.keys())}" + ) + if self.output_kwarg_info is None: - if len(kwargs) != 0: - raise TealInputError( - f"Subroutine cannot be called with keyword arguments. " - f"Received keyword arguments: {','.join(kwargs.keys())}" - ) return self.subroutine.invoke(list(args)) - if len(kwargs) != 1: - raise TealInputError( - f"Subroutine should have provided output keyword argument with name {self.output_kwarg_info.name}, " - f"while the kwargs are {kwargs}." - ) - if self.output_kwarg_info.name not in kwargs: - raise TealInputError( - f"Subroutine should have provided output keyword argument with name {self.output_kwarg_info.name}, " - f"while provided kwarg is {list(kwargs.keys())}" - ) + output_instance = self.output_kwarg_info.abi_type.new_instance() invoked = self.subroutine.invoke( list(args), - output_kwargs={ - self.output_kwarg_info.name: self.output_kwarg_info.abi_instance - }, + output_kwarg={self.output_kwarg_info.name: output_instance}, ) - return ReturnedValue(self.output_kwarg_info.abi_instance, invoked) + return ReturnedValue(output_instance, invoked) def name(self) -> str: return self.subroutine.name() @@ -503,12 +542,13 @@ def type_of(self) -> str | abi.TypeSpec: ) def is_registrable(self) -> bool: - # return len(self.subroutine.abi_args) == self.subroutine.argumentCount() - # TODO - return False + if self.type_of() == "void": + return len(self.subroutine.abi_args) == self.subroutine.argument_count() + else: + return len(self.subroutine.abi_args) + 1 == self.subroutine.argument_count() -ABIReturnSubroutineFnWrapper.__module__ = "pyteal" +ABIReturnSubroutine.__module__ = "pyteal" class Subroutine: @@ -549,13 +589,7 @@ def __call__(self, fn_implementation: Callable[..., Expr]) -> SubroutineFnWrappe Subroutine.__module__ = "pyteal" -def abi_return_subroutine( - fn_implementation: Callable[..., Expr] -) -> ABIReturnSubroutineFnWrapper: - return ABIReturnSubroutineFnWrapper(fn_implementation=fn_implementation) - - -def evaluateSubroutine(subroutine: SubroutineDefinition) -> SubroutineDeclaration: +def evaluate_subroutine(subroutine: SubroutineDefinition) -> SubroutineDeclaration: """ Puts together the data necessary to define the code for a subroutine. "evaluate" is used here to connote evaluating the PyTEAL AST into a SubroutineDeclaration, @@ -608,9 +642,9 @@ def var_n_loaded( if param in subroutine.by_ref_args: arg_var = DynamicScratchVar(TealType.anytype) loaded = arg_var - elif param in subroutine.abi_output_kwarg: + elif param in subroutine.output_kwarg: arg_var = DynamicScratchVar(TealType.anytype) - internal_abi_var = subroutine.abi_output_kwarg[param].new_instance() + internal_abi_var = subroutine.output_kwarg[param].new_instance() internal_abi_var.stored_value = arg_var loaded = internal_abi_var elif param in subroutine.abi_args: @@ -624,7 +658,7 @@ def var_n_loaded( return arg_var, loaded args = subroutine.arguments() - args = [arg for arg in args if arg not in subroutine.abi_output_kwarg] + args = [arg for arg in args if arg not in subroutine.output_kwarg] argument_vars, loaded_args = ( cast( @@ -636,13 +670,12 @@ def var_n_loaded( ) abi_output_kwargs = {} - assert len(subroutine.abi_output_kwarg) <= 1, "exceeding " - if len(subroutine.abi_output_kwarg) > 1: + if len(subroutine.output_kwarg) > 1: raise TealInputError( - f"ABI keyword argument num: {len(subroutine.abi_output_kwarg)}. " + f"ABI keyword argument num: {len(subroutine.output_kwarg)}. " f"Exceeding abi output keyword argument max number 1." ) - for name in subroutine.abi_output_kwarg: + for name in subroutine.output_kwarg: arg_var, loaded = var_n_loaded(name) abi_output_kwargs[name] = loaded argument_vars.append(arg_var) diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index 189c152aa..299736f11 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -2,7 +2,7 @@ import pytest import pyteal as pt -from pyteal.ast.subroutine import evaluateSubroutine +from pyteal.ast.subroutine import evaluate_subroutine options = pt.CompileOptions(version=4) @@ -58,7 +58,7 @@ def fnWithPartialExprAnnotations(a, b: pt.Expr) -> pt.Expr: for (fn, numArgs, name) in cases: definition = pt.SubroutineDefinition(fn, pt.TealType.none) - assert definition.argumentCount() == numArgs + assert definition.argument_count() == numArgs assert definition.name() == name if numArgs > 0: @@ -215,7 +215,7 @@ def fnWithMixedAnns4(a: pt.ScratchVar, b, c: pt.abi.Uint16) -> pt.Expr: ] for case_name, fn, args, err in cases: definition = pt.SubroutineDefinition(fn, pt.TealType.none) - assert definition.argumentCount() == len(args), case_name + assert definition.argument_count() == len(args), case_name assert definition.name() == fn.__name__, case_name if err is None: @@ -433,7 +433,7 @@ def mySubroutine(): definition = pt.SubroutineDefinition(mySubroutine, returnType) - declaration = evaluateSubroutine(definition) + declaration = evaluate_subroutine(definition) assert isinstance(declaration, pt.SubroutineDeclaration) assert declaration.subroutine is definition @@ -467,7 +467,7 @@ def mySubroutine(a1): definition = pt.SubroutineDefinition(mySubroutine, returnType) - declaration = evaluateSubroutine(definition) + declaration = evaluate_subroutine(definition) assert isinstance(declaration, pt.SubroutineDeclaration) assert declaration.subroutine is definition @@ -510,7 +510,7 @@ def mySubroutine(a1, a2): definition = pt.SubroutineDefinition(mySubroutine, returnType) - declaration = evaluateSubroutine(definition) + declaration = evaluate_subroutine(definition) assert isinstance(declaration, pt.SubroutineDeclaration) assert declaration.subroutine is definition @@ -556,7 +556,7 @@ def mySubroutine(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10): definition = pt.SubroutineDefinition(mySubroutine, returnType) - declaration = evaluateSubroutine(definition) + declaration = evaluate_subroutine(definition) assert isinstance(declaration, pt.SubroutineDeclaration) assert declaration.subroutine is definition diff --git a/pyteal/compiler/compiler.py b/pyteal/compiler/compiler.py index d5a0174b2..dd33941a7 100644 --- a/pyteal/compiler/compiler.py +++ b/pyteal/compiler/compiler.py @@ -160,7 +160,7 @@ def compileSubroutine( newSubroutines = referencedSubroutines - subroutine_start_blocks.keys() for subroutine in sorted(newSubroutines, key=lambda subroutine: subroutine.id): compileSubroutine( - subroutine.getDeclaration(), + subroutine.get_declaration(), options, subroutineGraph, subroutine_start_blocks, diff --git a/pyteal/compiler/subroutines.py b/pyteal/compiler/subroutines.py index bc3ee74e7..8ea3f4ddb 100644 --- a/pyteal/compiler/subroutines.py +++ b/pyteal/compiler/subroutines.py @@ -167,7 +167,7 @@ def spillLocalSlotsDuringRecursion( # reentrySubroutineCalls should have a length of 1, since calledSubroutines has a # maximum length of 1 reentrySubroutineCall = reentrySubroutineCalls[0] - numArgs = reentrySubroutineCall.argumentCount() + numArgs = reentrySubroutineCall.argument_count() digArgs = True coverSpilledSlots = False From 4e5e1e5ef6b491028c1e6a1fa34d399c9d3ea6cb Mon Sep 17 00:00:00 2001 From: Hang Su Date: Sun, 24 Apr 2022 00:38:09 -0400 Subject: [PATCH 025/170] minor fixes --- pyteal/ast/subroutine.py | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 2afcc0a99..40b909253 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -55,8 +55,7 @@ def __init__( # NOTE: it contains all the arguments, we get type annotations from `annotations`. # - `annotations`, which contains all available argument type annotations and return type annotation. # NOTE: `annotations` does not contain all the arguments, - # an argument is not included in `annotations` if its type - # annotation is not available. + # an argument is not included in `annotations` if its type annotation is not available. ( expected_arg_types, by_ref_args, @@ -225,10 +224,13 @@ def invoke( *, output_kwarg: Optional[dict[str, abi.BaseType]] = None, ) -> "SubroutineCall": - if len(args) != self.argument_count(): + argument_only = ( + set(self.arguments()) - set(output_kwarg.keys()) if output_kwarg else set() + ) + if len(args) != len(argument_only): raise TealInputError( f"Incorrect number of arguments for subroutine call. " - f"Expected {self.argument_count()} arguments, got {len(args)}" + f"Expected {self.argument_count()} arguments, got {len(args)} arguments" ) for i, arg in enumerate(args): @@ -614,7 +616,7 @@ def evaluate_subroutine(subroutine: SubroutineDefinition) -> SubroutineDeclarati Type 1 (by-value): these have python type Expr Type 2 (by-reference): these have python type ScratchVar Type 3 (ABI): these are ABI typed variables with scratch space storage, and still pass by value - Type 4 (ABI-output-arg): ABI typed variables with scractch space, but pass by ref to allow for changes + Type 4 (ABI-output-arg): ABI typed variables with scratch space, but pass by ref to allow for changes Usage (A) "argumentVars" - Storing pre-placed stack variables into local scratch space: Type 1. (by-value) use ScratchVar.store() to pick the actual value into a local scratch space @@ -636,26 +638,26 @@ def evaluate_subroutine(subroutine: SubroutineDefinition) -> SubroutineDeclarati def var_n_loaded( param: str, ) -> tuple[ScratchVar, ScratchVar | abi.BaseType | Expr]: - loaded: ScratchVar | abi.BaseType | Expr - arg_var: ScratchVar + loaded_var: ScratchVar | abi.BaseType | Expr + argument_var: ScratchVar if param in subroutine.by_ref_args: - arg_var = DynamicScratchVar(TealType.anytype) - loaded = arg_var + argument_var = DynamicScratchVar(TealType.anytype) + loaded_var = argument_var elif param in subroutine.output_kwarg: - arg_var = DynamicScratchVar(TealType.anytype) + argument_var = DynamicScratchVar(TealType.anytype) internal_abi_var = subroutine.output_kwarg[param].new_instance() - internal_abi_var.stored_value = arg_var - loaded = internal_abi_var + internal_abi_var.stored_value = argument_var + loaded_var = internal_abi_var elif param in subroutine.abi_args: internal_abi_var = subroutine.abi_args[param].new_instance() - arg_var = internal_abi_var.stored_value - loaded = internal_abi_var + argument_var = internal_abi_var.stored_value + loaded_var = internal_abi_var else: - arg_var = ScratchVar(TealType.anytype) - loaded = arg_var.load() + argument_var = ScratchVar(TealType.anytype) + loaded_var = argument_var.load() - return arg_var, loaded + return argument_var, loaded_var args = subroutine.arguments() args = [arg for arg in args if arg not in subroutine.output_kwarg] From 287ba71142060d51a94a55150cbb56108c7d005d Mon Sep 17 00:00:00 2001 From: Hang Su Date: Sun, 24 Apr 2022 14:21:05 -0400 Subject: [PATCH 026/170] minor fixes --- pyteal/ast/subroutine.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 40b909253..1d517776e 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -224,13 +224,13 @@ def invoke( *, output_kwarg: Optional[dict[str, abi.BaseType]] = None, ) -> "SubroutineCall": - argument_only = ( - set(self.arguments()) - set(output_kwarg.keys()) if output_kwarg else set() + argument_only = set(self.arguments()) - ( + set(output_kwarg.keys()) if output_kwarg else set() ) if len(args) != len(argument_only): raise TealInputError( f"Incorrect number of arguments for subroutine call. " - f"Expected {self.argument_count()} arguments, got {len(args)} arguments" + f"Expected {len(argument_only)} arguments, got {len(args)} arguments" ) for i, arg in enumerate(args): From fa3309509693a6f8ee912e6c71348e9b218b63a4 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Sun, 24 Apr 2022 21:42:32 -0400 Subject: [PATCH 027/170] changes --- pyteal/__init__.pyi | 1 + pyteal/ast/__init__.py | 2 + pyteal/ast/abi/type.py | 15 ++- pyteal/ast/subroutine.py | 195 ++++++++++++++++------------------ pyteal/ast/subroutine_test.py | 2 - 5 files changed, 100 insertions(+), 115 deletions(-) diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index 851c0dd3b..94c5ad96a 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -23,6 +23,7 @@ from pyteal.errors import ( from pyteal.config import MAX_GROUP_SIZE, NUM_SLOTS __all__ = [ + "ABIReturnSubroutine", "AccountParam", "Add", "Addr", diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index 7d951442e..f1d004ff7 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -118,6 +118,7 @@ SubroutineDeclaration, SubroutineCall, SubroutineFnWrapper, + ABIReturnSubroutine, ) from pyteal.ast.while_ import While from pyteal.ast.for_ import For @@ -240,6 +241,7 @@ "SubroutineDeclaration", "SubroutineCall", "SubroutineFnWrapper", + "ABIReturnSubroutine", "ScratchIndex", "ScratchLoad", "ScratchSlot", diff --git a/pyteal/ast/abi/type.py b/pyteal/ast/abi/type.py index fa6225d1a..4832545e8 100644 --- a/pyteal/ast/abi/type.py +++ b/pyteal/ast/abi/type.py @@ -72,7 +72,7 @@ def __init__(self, spec: TypeSpec) -> None: """Create a new BaseType.""" super().__init__() self._type_spec: Final = spec - self.stored_value = ScratchVar(spec.storage_type()) + self.stored_value: Final = ScratchVar(spec.storage_type()) def type_spec(self) -> TypeSpec: """Get the TypeSpec for this ABI type instance.""" @@ -185,22 +185,19 @@ def use(self, action: Callable[[T], Expr]) -> Expr: class ReturnedValue(ComputedValue): - def __init__(self, abi_return: BaseType, computation_expr: Expr): - self.abi_return = abi_return + def __init__(self, type_spec: TypeSpec, computation_expr: Expr): + self.type_spec = type_spec self.computation = computation_expr def produced_type_spec(self) -> TypeSpec: - return self.abi_return.type_spec() + return self.type_spec def store_into(self, output: BaseType) -> Expr: - if output.type_spec() != self.abi_return: + if output.type_spec() != self.produced_type_spec(): raise TealInputError( f"expected type_spec {self.produced_type_spec()} but get {output.type_spec()}" ) - return Seq( - self.computation, - output.stored_value.store(self.abi_return.stored_value.load()), - ) + return output.stored_value.store(self.computation) ReturnedValue.__module__ = "pyteal" diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 1d517776e..ee796ae0a 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -10,6 +10,7 @@ Any, ) +from pyteal.ast import Return from pyteal.ast.abi.type import ReturnedValue from pyteal.errors import TealInputError, verifyTealVersion from pyteal.ir import TealOp, Op, TealBlock @@ -190,7 +191,6 @@ def _arg_types_and_by_refs( raise TealInputError( f"Function keyword parameter {name} has type {expected_arg_type}" ) - # NOTE not sure if I should put this in by_ref_args, since compiler will use this to disallow recursion abi_output_kwarg[name] = expected_arg_type continue else: @@ -213,24 +213,22 @@ def name(self) -> str: return self.__name def argument_count(self) -> int: - return len(self.implementation_params) + return len(self.arguments()) def arguments(self) -> list[str]: - return list(self.implementation_params.keys()) + syntax_args = list(self.implementation_params.keys()) + for key in self.output_kwarg: + syntax_args.remove(key) + return syntax_args def invoke( self, args: list[Expr | ScratchVar | abi.BaseType], - *, - output_kwarg: Optional[dict[str, abi.BaseType]] = None, ) -> "SubroutineCall": - argument_only = set(self.arguments()) - ( - set(output_kwarg.keys()) if output_kwarg else set() - ) - if len(args) != len(argument_only): + if len(args) != self.argument_count(): raise TealInputError( f"Incorrect number of arguments for subroutine call. " - f"Expected {len(argument_only)} arguments, got {len(args)} arguments" + f"Expected {self.arguments()} arguments, got {len(args)} arguments" ) for i, arg in enumerate(args): @@ -254,25 +252,9 @@ def invoke( f"should have ABI typespec {arg_type} but got {arg.type_spec()}" ) - if len(self.output_kwarg) == 1: - if output_kwarg is None: - raise TealInputError( - f"expected output keyword argument {self.output_kwarg} with no input" - ) - actual_kwarg_type_spec = { - key: output_kwarg[key].type_spec() for key in output_kwarg - } - if actual_kwarg_type_spec != self.output_kwarg: - raise TealInputError( - f"expected output keyword argument {self.output_kwarg} with input {actual_kwarg_type_spec}" - ) - return SubroutineCall(self, args, output_kwarg=output_kwarg) - else: - if output_kwarg is not None: - raise TealInputError( - f"expected no output keyword argument with input {output_kwarg}" - ) - return SubroutineCall(self, args) + return SubroutineCall( + self, args, output_kwarg=_OutputKwArgInfo.from_dict(self.output_kwarg) + ) def __str__(self): return f"subroutine#{self.id}" @@ -311,13 +293,34 @@ def has_return(self): SubroutineDeclaration.__module__ = "pyteal" +@dataclass +class _OutputKwArgInfo: + name: str + abi_type: abi.TypeSpec + + @staticmethod + def from_dict(kwarg_info: dict[str, abi.TypeSpec]) -> Optional["_OutputKwArgInfo"]: + if kwarg_info is None or len(kwarg_info) == 0: + return None + elif len(kwarg_info) == 1: + key = list(kwarg_info.keys())[0] + return _OutputKwArgInfo(key, kwarg_info[key]) + else: + raise TealInputError( + f"illegal conversion kwarg_info length {len(kwarg_info)}." + ) + + +_OutputKwArgInfo.__module__ = "pyteal" + + class SubroutineCall(Expr): def __init__( self, subroutine: SubroutineDefinition, args: list[Expr | ScratchVar | abi.BaseType], *, - output_kwarg: Optional[dict[str, abi.BaseType]] = None, + output_kwarg: Optional[_OutputKwArgInfo] = None, ) -> None: super().__init__() self.subroutine = subroutine @@ -356,9 +359,8 @@ def __teal__(self, options: "CompileOptions"): 3. (ABI, or a special case in by-value) In this case, the storage of an ABI value are loaded to the stack and will be stored in a local ABI value for subroutine evaluation - 4. (ABI output keyword argument, or by-ref ABI value) In this case of returning ABI values, - its SLOT INDEX is put on the stack and will be stored in a local DynamicScratchVar underlying a local ABI - value for subroutine evaluation + 4. (ABI output keyword argument, or by-ref ABI value) In this case of returning ABI values, we do not place + ABI values on the stack, while in `evaluate_subroutine` we use an ABI typed instance for subroutine evaluation """ verifyTealVersion( Op.callsub.min_version, @@ -379,19 +381,14 @@ def handle_arg(arg: Expr | ScratchVar | abi.BaseType) -> Expr: ) op = TealOp(self, Op.callsub, self.subroutine) - argument_list = [handle_arg(x) for x in self.args] - if self.output_kwarg is not None: - argument_list += [ - x.stored_value.index() for x in self.output_kwarg.values() - ] - return TealBlock.FromOp(options, op, *argument_list) + return TealBlock.FromOp(options, op, *[handle_arg(x) for x in self.args]) def __str__(self): arg_str_list = list(map(str, self.args)) if self.output_kwarg: - arg_str_list += [ - f"{x}={str(self.output_kwarg[x])}" for x in self.output_kwarg - ] + arg_str_list.append( + f"{self.output_kwarg.name}={str(self.output_kwarg.abi_type)}" + ) return f'(SubroutineCall {self.subroutine.name()} ({" ".join(arg_str_list)}))' def type_of(self): @@ -438,15 +435,6 @@ def has_return(self): SubroutineFnWrapper.__module__ = "pyteal" -@dataclass -class _OutputKwArgInfo: - name: str - abi_type: abi.TypeSpec - - -_OutputKwArgInfo.__module__ = "pyteal" - - class ABIReturnSubroutine: """Used to create a PyTeal Subroutine (returning an ABI value) from a python function. @@ -471,8 +459,14 @@ def __init__( self, fn_implementation: Callable[..., Expr], ) -> None: - self.output_kwarg_info: None | _OutputKwArgInfo = ( - self._output_name_type_from_fn(fn_implementation) + self.output_kwarg_info: Optional[ + _OutputKwArgInfo + ] = self._output_name_type_from_fn(fn_implementation) + + internal_subroutine_ret_type = ( + TealType.none + if self.output_kwarg_info is None + else self.output_kwarg_info.abi_type.storage_type() ) output_kwarg_name = ( None if self.output_kwarg_info is None else self.output_kwarg_info.name @@ -481,14 +475,14 @@ def __init__( # no matter what, output is void or abiType, stack type is TealType.none self.subroutine = SubroutineDefinition( fn_implementation, - return_type=TealType.none, + return_type=internal_subroutine_ret_type, abi_output_arg_name=output_kwarg_name, ) @staticmethod def _output_name_type_from_fn( fn_implementation: Callable[..., Expr] - ) -> None | _OutputKwArgInfo: + ) -> Optional[_OutputKwArgInfo]: sig = signature(fn_implementation) fn_annotations = getattr(fn_implementation, "__annotations__", OrderedDict()) @@ -523,15 +517,11 @@ def __call__( f"Received keyword arguments: {', '.join(kwargs.keys())}" ) + invoked = self.subroutine.invoke(list(args)) if self.output_kwarg_info is None: - return self.subroutine.invoke(list(args)) + return invoked - output_instance = self.output_kwarg_info.abi_type.new_instance() - invoked = self.subroutine.invoke( - list(args), - output_kwarg={self.output_kwarg_info.name: output_instance}, - ) - return ReturnedValue(output_instance, invoked) + return ReturnedValue(self.output_kwarg_info.abi_type, invoked) def name(self) -> str: return self.subroutine.name() @@ -544,10 +534,7 @@ def type_of(self) -> str | abi.TypeSpec: ) def is_registrable(self) -> bool: - if self.type_of() == "void": - return len(self.subroutine.abi_args) == self.subroutine.argument_count() - else: - return len(self.subroutine.abi_args) + 1 == self.subroutine.argument_count() + return len(self.subroutine.abi_args) == self.subroutine.argument_count() ABIReturnSubroutine.__module__ = "pyteal" @@ -601,7 +588,7 @@ def evaluate_subroutine(subroutine: SubroutineDefinition) -> SubroutineDeclarati 2 Argument Usages / Code-Paths - -------- ------ ---------- - Usage (A) for run-time: "argumentVars" --reverse--> "bodyOps" + Usage (A) for run-time: "argumentVars" --reverse--> "body_ops" These are "store" expressions that pick up parameters that have been pre-placed on the stack prior to subroutine invocation. The argumentVars are stored into local scratch space to be used by the TEAL subroutine. @@ -623,76 +610,76 @@ def evaluate_subroutine(subroutine: SubroutineDefinition) -> SubroutineDeclarati Type 2. (by-reference) ALSO use ScratchVar.store() to pick up from the stack NOTE: SubroutineCall.__teal__() has placed the _SLOT INDEX_ on the stack so this is stored into the local scratch space Type 3. (ABI) abi_value.stored_value.store() to pick from the stack - Type 4. (ABI-output-arg) use ScratchVar.store() to pick up from the stack - NOTE: SubroutineCall.__teal__() has placed the ABI value's _SLOT INDEX_ on the stack, pass-by-ref is similarly achieved + Type 4. (ABI-output-arg) it is not really used here, since it is only generated internal of the subroutine Usage (B) "loadedArgs" - Passing through to an invoked PyTEAL subroutine AST: Type 1. (by-value) use ScratchVar.load() to have an Expr that can be compiled in python by the PyTEAL subroutine Type 2. (by-reference) use a DynamicScratchVar as the user will have written the PyTEAL in a way that satisfies the ScratchVar API. I.e., the user will write `x.load()` and `x.store(val)` as opposed to just `x`. Type 3. (ABI) use abi_value itself after storing stack value into scratch space. - Type 4. (ABI-output-arg) use a DynamicScratchVar to "point to" the output ABI value's scratch variable - all the ABI operations are passed through DynamicScratchVar to original ScratchVar, which is pass-by-ref behavior + Type 4. (ABI-output-arg) generates a new instance of the ABI value, + and appends a return expression of stored value of the ABI keyword value. """ def var_n_loaded( param: str, ) -> tuple[ScratchVar, ScratchVar | abi.BaseType | Expr]: - loaded_var: ScratchVar | abi.BaseType | Expr - argument_var: ScratchVar + _loaded_var: ScratchVar | abi.BaseType | Expr + _argument_var: ScratchVar if param in subroutine.by_ref_args: - argument_var = DynamicScratchVar(TealType.anytype) - loaded_var = argument_var - elif param in subroutine.output_kwarg: - argument_var = DynamicScratchVar(TealType.anytype) - internal_abi_var = subroutine.output_kwarg[param].new_instance() - internal_abi_var.stored_value = argument_var - loaded_var = internal_abi_var + _argument_var = DynamicScratchVar(TealType.anytype) + _loaded_var = _argument_var elif param in subroutine.abi_args: internal_abi_var = subroutine.abi_args[param].new_instance() - argument_var = internal_abi_var.stored_value - loaded_var = internal_abi_var + _argument_var = internal_abi_var.stored_value + _loaded_var = internal_abi_var else: - argument_var = ScratchVar(TealType.anytype) - loaded_var = argument_var.load() + _argument_var = ScratchVar(TealType.anytype) + _loaded_var = _argument_var.load() - return argument_var, loaded_var + return _argument_var, _loaded_var args = subroutine.arguments() args = [arg for arg in args if arg not in subroutine.output_kwarg] - argument_vars, loaded_args = ( - cast( - tuple[list[ScratchVar], list[ScratchVar | Expr | abi.BaseType]], - zip(*map(var_n_loaded, args)), - ) - if args - else ([], []) - ) + argument_vars: list[ScratchVar] = [] + loaded_args: list[ScratchVar | Expr | abi.BaseType] = [] + for arg in args: + argument_var, loaded_arg = var_n_loaded(arg) + argument_vars.append(argument_var) + loaded_args.append(loaded_arg) - abi_output_kwargs = {} + abi_output_kwargs: dict[str, abi.BaseType] = {} if len(subroutine.output_kwarg) > 1: raise TealInputError( f"ABI keyword argument num: {len(subroutine.output_kwarg)}. " f"Exceeding abi output keyword argument max number 1." ) - for name in subroutine.output_kwarg: - arg_var, loaded = var_n_loaded(name) - abi_output_kwargs[name] = loaded - argument_vars.append(arg_var) + + output_kwarg_info = _OutputKwArgInfo.from_dict(subroutine.output_kwarg) + output_carrying_abi: Optional[abi.BaseType] = None + + if output_kwarg_info: + output_carrying_abi = output_kwarg_info.abi_type.new_instance() + abi_output_kwargs[output_kwarg_info.name] = output_carrying_abi # Arg usage "B" supplied to build an AST from the user-defined PyTEAL function: - subroutineBody = subroutine.implementation(*loaded_args, **abi_output_kwargs) + subroutine_body = subroutine.implementation(*loaded_args, **abi_output_kwargs) - if not isinstance(subroutineBody, Expr): + if not isinstance(subroutine_body, Expr): raise TealInputError( - f"Subroutine function does not return a PyTeal expression. Got type {type(subroutineBody)}" + f"Subroutine function does not return a PyTeal expression. Got type {type(subroutine_body)}." + ) + # if there is an output keyword argument for ABI, place the storing on the stack + if output_carrying_abi: + subroutine_body = Seq( + subroutine_body, Return(output_carrying_abi.stored_value.load()) ) # Arg usage "A" to be pick up and store in scratch parameters that have been placed on the stack # need to reverse order of argumentVars because the last argument will be on top of the stack - bodyOps = [var.slot.store() for var in argument_vars[::-1]] - bodyOps.append(subroutineBody) + body_ops = [var.slot.store() for var in argument_vars[::-1]] + body_ops.append(subroutine_body) - return SubroutineDeclaration(subroutine, Seq(bodyOps)) + return SubroutineDeclaration(subroutine, Seq(body_ops)) diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index 299736f11..9bb75ec0c 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -6,8 +6,6 @@ options = pt.CompileOptions(version=4) -# something here - def test_subroutine_definition(): def fn0Args(): From 43212b7615b8ad2e70db41fc462feedfdb0556c2 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Sun, 24 Apr 2022 22:07:20 -0400 Subject: [PATCH 028/170] more constraint on void ret --- pyteal/ast/subroutine.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index ee796ae0a..fe9cf9f94 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -519,6 +519,10 @@ def __call__( invoked = self.subroutine.invoke(list(args)) if self.output_kwarg_info is None: + if invoked.type_of() != TealType.none: + raise TealInputError( + "ABI subroutine with void type should be evaluated to TealType.none" + ) return invoked return ReturnedValue(self.output_kwarg_info.abi_type, invoked) From 31cf5277048a0d2885bc34cdb5521e2a0f7a8b36 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 25 Apr 2022 09:35:22 -0400 Subject: [PATCH 029/170] update comment examples --- pyteal/ast/subroutine.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index fe9cf9f94..7d2662057 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -443,15 +443,26 @@ class ABIReturnSubroutine: .. code-block:: python @ABIReturnSubroutine - def an_abi_subroutine(a: abi.Uint64, b: abi.Uint64, *, output: abi.Uint64) -> Expr: - return output.set(a.get() * b.get()) + def abi_sum(toSum: abi.DynamicArray[abi.Uint64], *, output: abi.Uint64) -> Expr: + i = ScratchVar(TealType.uint64) + valueAtIndex = abi.Uint64() + return Seq( + output.set(0), + For(i.store(Int(0)), i.load() < toSum.length(), i.store(i.load() + Int(1))).Do( + Seq( + toSum[i.load()].store_into(valueAtIndex), + output.set(output.get() + valueAtIndex.get()), + ) + ), + ) program = Seq( - (a := abi.Uint64()).decode(Txn.application_args[1]), - (b := abi.Uint64()).decode(Txn.application_args[2]), - (c := abi.Uint64()).set(an_abi_subroutine(a, b)), - MethodReturn(c), - Approve(), + (to_sum_arr := abi.DynamicArray(abi.Uint64TypeSpec())).decode( + Txn.application_args[1] + ), + (res := abi.Uint64()).set(abi_sum(to_sum_arr)), + abi.MethodReturn(res), + Int(1), ) """ From 87c34d863c1a99e6b3d4d408b11221c7d0b10420 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 25 Apr 2022 10:34:19 -0400 Subject: [PATCH 030/170] import from abi --- pyteal/ast/subroutine.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 7d2662057..8beb24da7 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -10,8 +10,7 @@ Any, ) -from pyteal.ast import Return -from pyteal.ast.abi.type import ReturnedValue +from pyteal.ast.return_ import Return from pyteal.errors import TealInputError, verifyTealVersion from pyteal.ir import TealOp, Op, TealBlock from pyteal.types import TealType @@ -536,7 +535,7 @@ def __call__( ) return invoked - return ReturnedValue(self.output_kwarg_info.abi_type, invoked) + return abi.ReturnedValue(self.output_kwarg_info.abi_type, invoked) def name(self) -> str: return self.subroutine.name() @@ -618,7 +617,8 @@ def evaluate_subroutine(subroutine: SubroutineDefinition) -> SubroutineDeclarati Type 1 (by-value): these have python type Expr Type 2 (by-reference): these have python type ScratchVar Type 3 (ABI): these are ABI typed variables with scratch space storage, and still pass by value - Type 4 (ABI-output-arg): ABI typed variables with scratch space, but pass by ref to allow for changes + Type 4 (ABI-output-arg): ABI typed variables with scratch space, a new ABI instance is generated inside function body, + not one of the cases in the previous three options Usage (A) "argumentVars" - Storing pre-placed stack variables into local scratch space: Type 1. (by-value) use ScratchVar.store() to pick the actual value into a local scratch space From b0baea56bb7deb7e8b53d0aaa19a991435d2c356 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 25 Apr 2022 10:42:53 -0400 Subject: [PATCH 031/170] update some error msg and comments --- pyteal/ast/subroutine.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 8beb24da7..2d93ef726 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -482,7 +482,8 @@ def __init__( None if self.output_kwarg_info is None else self.output_kwarg_info.name ) - # no matter what, output is void or abiType, stack type is TealType.none + # output ABI type is void, return_type = TealType.none + # otherwise, return_type = ABI value's storage_type() self.subroutine = SubroutineDefinition( fn_implementation, return_type=internal_subroutine_ret_type, @@ -688,6 +689,15 @@ def var_n_loaded( ) # if there is an output keyword argument for ABI, place the storing on the stack if output_carrying_abi: + if subroutine_body.has_return(): + raise TealInputError( + "ABI returning subroutine definition should have no return" + ) + if subroutine_body.type_of() != TealType.none: + raise TealInputError( + f"ABI returning subroutine definition should evaluate to TealType.none, " + f"while evaluate to {subroutine_body.type_of()}." + ) subroutine_body = Seq( subroutine_body, Return(output_carrying_abi.stored_value.load()) ) From 1c48d0cbe49fc23b2accd61029e5c34a0be012ca Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 26 Apr 2022 16:37:46 -0400 Subject: [PATCH 032/170] testcases partial --- pyteal/ast/subroutine_test.py | 288 +++++++++++++++++++++++++++++++++- 1 file changed, 285 insertions(+), 3 deletions(-) diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index 9bb75ec0c..4cd7aaa95 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -1,10 +1,11 @@ -from typing import List, Literal +from typing import List, Literal, Callable import pytest +from dataclasses import dataclass import pyteal as pt from pyteal.ast.subroutine import evaluate_subroutine -options = pt.CompileOptions(version=4) +options = pt.CompileOptions(version=5) def test_subroutine_definition(): @@ -77,6 +78,99 @@ def fnWithPartialExprAnnotations(a, b: pt.Expr) -> pt.Expr: assert invocation.args == args +@dataclass +class ABISubroutineTC: + impl: Callable[..., pt.Expr] + arg_instances: list[pt.Expr | pt.abi.BaseType] + name: str + ret_type: str | pt.abi.TypeSpec + + +def test_abi_subroutine_definition(): + def fn_0arg_0ret() -> pt.Expr: + return pt.Return() + + def fn_0arg_uint64_ret(*, res: pt.abi.Uint64) -> pt.Expr: + return res.set(1) + + def fn_1arg_0ret(a: pt.abi.Uint64) -> pt.Expr: + return pt.Return() + + def fn_1arg_1ret(a: pt.abi.Uint64, *, out: pt.abi.Uint64) -> pt.Expr: + return out.set(a) + + def fn_2arg_0ret( + a: pt.abi.Uint64, b: pt.abi.StaticArray[pt.abi.Byte, Literal[10]] + ) -> pt.Expr: + return pt.Return() + + def fn_2arg_1ret( + a: pt.abi.Uint64, + b: pt.abi.StaticArray[pt.abi.Byte, Literal[10]], + *, + out: pt.abi.Byte, + ) -> pt.Expr: + return out.set(b[a.get() % pt.Int(10)]) + + def fn_2arg_1ret_with_expr( + a: pt.Expr, + b: pt.abi.StaticArray[pt.abi.Byte, Literal[10]], + *, + out: pt.abi.Byte, + ) -> pt.Expr: + return out.set(b[a % pt.Int(10)]) + + cases = ( + ABISubroutineTC(fn_0arg_0ret, [], "fn_0arg_0ret", "void"), + ABISubroutineTC( + fn_0arg_uint64_ret, [], "fn_0arg_uint64_ret", pt.abi.Uint64TypeSpec() + ), + ABISubroutineTC(fn_1arg_0ret, [pt.abi.Uint64()], "fn_1arg_0ret", "void"), + ABISubroutineTC( + fn_1arg_1ret, [pt.abi.Uint64()], "fn_1arg_1ret", pt.abi.Uint64TypeSpec() + ), + ABISubroutineTC( + fn_2arg_0ret, + [pt.abi.Uint64(), pt.abi.StaticArray(pt.abi.ByteTypeSpec(), 10)], + "fn_2arg_0ret", + "void", + ), + ABISubroutineTC( + fn_2arg_1ret, + [pt.abi.Uint64(), pt.abi.StaticArray(pt.abi.ByteTypeSpec(), 10)], + "fn_2arg_1ret", + pt.abi.ByteTypeSpec(), + ), + ABISubroutineTC( + fn_2arg_1ret_with_expr, + [pt.Int(5), pt.abi.StaticArray(pt.abi.ByteTypeSpec(), 10)], + "fn_2arg_1ret_with_expr", + pt.abi.ByteTypeSpec(), + ), + ) + + for case in cases: + definition = pt.ABIReturnSubroutine(case.impl) + assert definition.subroutine.argument_count() == len(case.arg_instances) + assert definition.name() == case.name + + if len(case.arg_instances) > 0: + with pytest.raises(pt.TealInputError): + definition(*case.arg_instances[:-1]) + + with pytest.raises(pt.TealInputError): + definition(*(case.arg_instances + [pt.abi.Uint64()])) + + assert definition.type_of() == case.ret_type + invoked = definition(*case.arg_instances) + assert isinstance( + invoked, (pt.Expr if case.ret_type == "void" else pt.abi.ReturnedValue) + ) + assert definition.is_registrable() == all( + map(lambda x: isinstance(x, pt.abi.BaseType), case.arg_instances) + ) + + def test_subroutine_invocation_param_types(): def fnWithNoAnnotations(a, b): return pt.Return() @@ -237,6 +331,194 @@ def fnWithMixedAnns4(a: pt.ScratchVar, b, c: pt.abi.Uint16) -> pt.Expr: ), f"EXPECTED ERROR of type {err}. encountered unexpected error during invocation case <{case_name}>: {e}" +def test_abi_subroutine_calling_param_types(): + def fn_log_add(a: pt.abi.Uint64, b: pt.abi.Uint32) -> pt.Expr: + return pt.Seq(pt.Log(pt.Itob(a.get() + b.get())), pt.Return()) + + def fn_ret_add(a: pt.abi.Uint64, b: pt.abi.Uint32, *, c: pt.abi.Uint64) -> pt.Expr: + return c.set(a.get() + b.get() + pt.Int(0xA190)) + + def fn_abi_annotations_0( + a: pt.abi.Byte, + b: pt.abi.StaticArray[pt.abi.Uint32, Literal[10]], + c: pt.abi.DynamicArray[pt.abi.Bool], + ) -> pt.Expr: + return pt.Return() + + def fn_abi_annotations_0_with_ret( + a: pt.abi.Byte, + b: pt.abi.StaticArray[pt.abi.Uint32, Literal[10]], + c: pt.abi.DynamicArray[pt.abi.Bool], + *, + out: pt.abi.Byte, + ): + return out.set(a) + + def fn_mixed_annotations_0(a: pt.ScratchVar, b: pt.Expr, c: pt.abi.Byte) -> pt.Expr: + return pt.Seq( + a.store(c.get() * pt.Int(0x0FF1CE) * b), + pt.Return(), + ) + + def fn_mixed_annotations_0_with_ret( + a: pt.ScratchVar, b: pt.Expr, c: pt.abi.Byte, *, out: pt.abi.Uint64 + ) -> pt.Expr: + return pt.Seq( + a.store(c.get() * pt.Int(0x0FF1CE) * b), + out.set(a.load()), + ) + + def fn_mixed_annotation_1( + a: pt.ScratchVar, b: pt.abi.StaticArray[pt.abi.Uint32, Literal[10]] + ) -> pt.Expr: + return pt.Seq( + (intermediate := pt.abi.Uint32()).set(b[a.load() % pt.Int(10)]), + a.store(intermediate.get()), + pt.Return(), + ) + + def fn_mixed_annotation_1_with_ret( + a: pt.ScratchVar, b: pt.abi.Uint64, *, c: pt.abi.Bool + ) -> pt.Expr: + return c.set((a.load() + b.get()) % pt.Int(2)) + + abi_u64 = pt.abi.Uint64() + abi_u32 = pt.abi.Uint32() + abi_byte = pt.abi.Byte() + abi_static_u32_10 = pt.abi.StaticArray(pt.abi.Uint32TypeSpec(), 10) + abi_dynamic_bool = pt.abi.DynamicArray(pt.abi.BoolTypeSpec()) + sv = pt.ScratchVar() + expr_int = pt.Int(1) + + cases = [ + ("vanilla 1", fn_log_add, [abi_u64, abi_u32], "void", None), + ( + "vanilla 1 with wrong ABI type", + fn_log_add, + [abi_u64, abi_u64], + None, + pt.TealInputError, + ), + ( + "vanilla 1 with ABI return", + fn_ret_add, + [abi_u64, abi_u32], + pt.abi.Uint64TypeSpec(), + None, + ), + ( + "vanilla 1 with ABI return wrong typed", + fn_ret_add, + [abi_u32, abi_u64], + None, + pt.TealInputError, + ), + ( + "full ABI annotations no return", + fn_abi_annotations_0, + [abi_byte, abi_static_u32_10, abi_dynamic_bool], + "void", + None, + ), + ( + "full ABI annotations wrong input 0", + fn_abi_annotations_0, + [abi_u64, abi_static_u32_10, abi_dynamic_bool], + None, + pt.TealInputError, + ), + ( + "full ABI annotations with ABI return", + fn_abi_annotations_0_with_ret, + [abi_byte, abi_static_u32_10, abi_dynamic_bool], + pt.abi.ByteTypeSpec(), + None, + ), + ( + "full ABI annotations with ABI return wrong inputs", + fn_abi_annotations_0_with_ret, + [abi_byte, abi_dynamic_bool, abi_static_u32_10], + None, + pt.TealInputError, + ), + ( + "mixed with ABI annotations 0", + fn_mixed_annotations_0, + [sv, expr_int, abi_byte], + "void", + None, + ), + ( + "mixed with ABI annotations 0 wrong inputs", + fn_mixed_annotations_0, + [abi_u64, expr_int, abi_byte], + None, + pt.TealInputError, + ), + ( + "mixed with ABI annotations 0 with ABI return", + fn_mixed_annotations_0_with_ret, + [sv, expr_int, abi_byte], + pt.abi.Uint64TypeSpec(), + None, + ), + ( + "mixed with ABI annotations 0 with ABI return wrong inputs", + fn_mixed_annotations_0_with_ret, + [sv, expr_int, sv], + None, + pt.TealInputError, + ), + ( + "mixed with ABI annotations 1", + fn_mixed_annotation_1, + [sv, abi_static_u32_10], + "void", + None, + ), + ( + "mixed with ABI annotations 1 with ABI return", + fn_mixed_annotation_1_with_ret, + [sv, abi_u64], + pt.abi.BoolTypeSpec(), + None, + ), + ( + "mixed with ABI annotations 1 with ABI return wrong inputs", + fn_mixed_annotation_1_with_ret, + [expr_int, abi_static_u32_10], + None, + pt.TealInputError, + ), + ] + + for case_name, impl, args, ret_type, err in cases: + definition = pt.ABIReturnSubroutine(impl) + assert definition.subroutine.argument_count() == len(args), case_name + assert definition.name() == impl.__name__, case_name + + if err is None: + invocation = definition(*args) + if ret_type == "void": + assert isinstance(invocation, pt.SubroutineCall), case_name + assert not invocation.has_return(), case_name + assert invocation.args == args, case_name + else: + assert isinstance(invocation, pt.abi.ReturnedValue), case_name + assert invocation.type_spec == ret_type + assert isinstance(invocation.computation, pt.SubroutineCall), case_name + assert not invocation.computation.has_return(), case_name + assert invocation.computation.args == args, case_name + else: + try: + with pytest.raises(err): + definition(*args) + except Exception as e: + assert ( + not e + ), f"EXPECTED ERROR of type {err}. encountered unexpected error during invocation case <{case_name}>: {e}" + + def test_subroutine_definition_invalid(): def fnWithDefaults(a, b=None): return pt.Return() @@ -323,7 +605,7 @@ def fnWithMixedAnnsABIRet2( print(f"case=[{msg}]") pt.SubroutineDefinition(fn, pt.TealType.none) - assert msg in str(e), "failed for case [{}]".format(fn.__name__) + assert msg in str(e), f"failed for case [{fn.__name__}]" def test_subroutine_declaration(): From 319b953a7407225dc4f9e323f955e3ef562b4afa Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 27 Apr 2022 12:50:08 -0400 Subject: [PATCH 033/170] upgrade testscripts --- pyteal/ast/subroutine.py | 2 ++ pyteal/ast/subroutine_test.py | 41 ++++++++++++++++++++++++++++++----- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 2d93ef726..c72b931ce 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -494,6 +494,8 @@ def __init__( def _output_name_type_from_fn( fn_implementation: Callable[..., Expr] ) -> Optional[_OutputKwArgInfo]: + if not callable(fn_implementation): + raise TealInputError("Input to ABIReturnSubroutine is not callable") sig = signature(fn_implementation) fn_annotations = getattr(fn_implementation, "__annotations__", OrderedDict()) diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index 4cd7aaa95..199c2c322 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -526,6 +526,9 @@ def fnWithDefaults(a, b=None): def fnWithKeywordArgs(a, *, b): return pt.Return() + def fnWithMultipleABIKeywordArgs(a, *, b: pt.abi.Byte, c: pt.abi.Bool): + return pt.Return() + def fnWithVariableArgs(a, *b): return pt.Return() @@ -555,57 +558,85 @@ def fnWithMixedAnnsABIRet2( return pt.abi.Uint64() cases = ( - (1, "TealInputError('Input to SubroutineDefinition is not callable'"), - (None, "TealInputError('Input to SubroutineDefinition is not callable'"), + ( + 1, + "TealInputError('Input to SubroutineDefinition is not callable'", + "TealInputError('Input to ABIReturnSubroutine is not callable'", + ), + ( + None, + "TealInputError('Input to SubroutineDefinition is not callable'", + "TealInputError('Input to ABIReturnSubroutine is not callable'", + ), ( fnWithDefaults, "TealInputError('Function has a parameter with a default value, which is not allowed in a subroutine: b'", + "TealInputError('Function has a parameter with a default value, which is not allowed in a subroutine: b'", ), ( fnWithKeywordArgs, "TealInputError('Function has a parameter type that is not allowed in a subroutine: parameter b with type", + "TealInputError('ABI subroutine output-kwarg b must specify ABI type')", + ), + ( + fnWithMultipleABIKeywordArgs, + "TealInputError('Function has a parameter type that is not allowed in a subroutine: parameter b with type", + "TealInputError('multiple output arguments with type annotations", ), ( fnWithVariableArgs, "TealInputError('Function has a parameter type that is not allowed in a subroutine: parameter b with type", + "Function has a parameter type that is not allowed in a subroutine: parameter b with type VAR_POSITIONAL", ), ( fnWithNonExprReturnAnnotation, "Function has return of disallowed type TealType.uint64. Only Expr is allowed", + "Function has return of disallowed type TealType.uint64. Only Expr is allowed", ), ( fnWithNonExprParamAnnotation, "Function has parameter b of declared type TealType.uint64 which is not a class", + "Function has parameter b of declared type TealType.uint64 which is not a class", ), ( fnWithScratchVarSubclass, "Function has parameter b of disallowed type ", + "Function has parameter b of disallowed type ", ), ( fnReturningExprSubclass, "Function has return of disallowed type ", + "Function has return of disallowed type . Only Expr is allowed", ), ( fnWithMixedAnns4AndBytesReturn, "Function has return of disallowed type ", + "Function has return of disallowed type . Only Expr is allowed", ), ( fnWithMixedAnnsABIRet1, "Function has return of disallowed type pyteal.StaticArray[pyteal.Uint32, typing.Literal[10]]. " "Only Expr is allowed", + "Function has return of disallowed type pyteal.StaticArray[pyteal.Uint32, typing.Literal[10]]. " + "Only Expr is allowed", ), ( fnWithMixedAnnsABIRet2, "Function has return of disallowed type . Only Expr is allowed", + "Function has return of disallowed type . Only Expr is allowed", ), ) - for fn, msg in cases: + for fn, sub_def_msg, abi_sub_def_msg in cases: with pytest.raises(pt.TealInputError) as e: - print(f"case=[{msg}]") + print(f"case=[{sub_def_msg}]") pt.SubroutineDefinition(fn, pt.TealType.none) - assert msg in str(e), f"failed for case [{fn.__name__}]" + assert sub_def_msg in str(e), f"failed for case [{fn.__name__}]" + + with pytest.raises(pt.TealInputError) as e: + print(f"case=[{abi_sub_def_msg}]") + pt.ABIReturnSubroutine(fn) def test_subroutine_declaration(): From 96f7ede0211b9a032972be2b23af2751ef95921b Mon Sep 17 00:00:00 2001 From: Michael Diamant Date: Fri, 29 Apr 2022 12:35:57 -0400 Subject: [PATCH 034/170] Bundle optional refactorings to subroutine.py (#308) * Bundle optional refactorings to subroutine.py * Refactor to remove branching --- pyteal/ast/subroutine.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index c72b931ce..d04670a55 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -79,7 +79,7 @@ def __init__( self.__name = self.implementation.__name__ if name_str is None else name_str @staticmethod - def is_abi_annotation(obj: Any) -> bool: + def _is_abi_annotation(obj: Any) -> bool: try: abi.type_spec_from_annotation(obj) return True @@ -105,14 +105,16 @@ def _validate_parameter_type( # * `invoke` type checks provided arguments against parameter types to catch mismatches. return Expr else: - if not isclass(ptype) and not SubroutineDefinition.is_abi_annotation(ptype): + if not isclass(ptype) and not SubroutineDefinition._is_abi_annotation( + ptype + ): raise TealInputError( f"Function has parameter {parameter_name} of declared type {ptype} which is not a class" ) if ptype in (Expr, ScratchVar): return ptype - elif SubroutineDefinition.is_abi_annotation(ptype): + elif SubroutineDefinition._is_abi_annotation(ptype): return abi.type_spec_from_annotation(ptype) else: raise TealInputError( @@ -192,8 +194,8 @@ def _arg_types_and_by_refs( ) abi_output_kwarg[name] = expected_arg_type continue - else: - expected_arg_types.append(expected_arg_type) + + expected_arg_types.append(expected_arg_type) if expected_arg_type is ScratchVar: by_ref_args.add(name) @@ -299,15 +301,15 @@ class _OutputKwArgInfo: @staticmethod def from_dict(kwarg_info: dict[str, abi.TypeSpec]) -> Optional["_OutputKwArgInfo"]: - if kwarg_info is None or len(kwarg_info) == 0: - return None - elif len(kwarg_info) == 1: - key = list(kwarg_info.keys())[0] - return _OutputKwArgInfo(key, kwarg_info[key]) - else: - raise TealInputError( - f"illegal conversion kwarg_info length {len(kwarg_info)}." - ) + match list(kwarg_info.keys()): + case []: + return None + case [k]: + return _OutputKwArgInfo(k, kwarg_info[k]) + case _: + raise TealInputError( + f"illegal conversion kwarg_info length {len(kwarg_info)}." + ) _OutputKwArgInfo.__module__ = "pyteal" From 79d787b559251f2aa941e4aea148ff7b4853adca Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 2 May 2022 11:01:43 -0400 Subject: [PATCH 035/170] storing local changes --- pyteal/ast/subroutine.py | 105 +++++++++++++++++++-------------------- 1 file changed, 51 insertions(+), 54 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index c72b931ce..c6be95bec 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -4,7 +4,6 @@ from typing import ( Callable, Optional, - Type, TYPE_CHECKING, cast, Any, @@ -61,11 +60,9 @@ def __init__( by_ref_args, abi_args, abi_output_kwarg, - ) = SubroutineDefinition._arg_types_and_by_refs( - sig, annotations, abi_output_arg_name - ) + ) = self._arg_types_and_by_refs(sig, annotations, abi_output_arg_name) self.expected_arg_types: list[ - Type[Expr] | Type[ScratchVar] | abi.TypeSpec + type[Expr] | type[ScratchVar] | abi.TypeSpec ] = expected_arg_types self.by_ref_args: set[str] = by_ref_args self.abi_args: dict[str, abi.TypeSpec] = abi_args @@ -79,7 +76,7 @@ def __init__( self.__name = self.implementation.__name__ if name_str is None else name_str @staticmethod - def is_abi_annotation(obj: Any) -> bool: + def _is_abi_annotation(obj: Any) -> bool: try: abi.type_spec_from_annotation(obj) return True @@ -87,9 +84,9 @@ def is_abi_annotation(obj: Any) -> bool: return False @staticmethod - def _validate_parameter_type( + def _validate_annotation( user_defined_annotations: dict[str, Any], parameter_name: str - ) -> Type[Expr] | Type[ScratchVar] | abi.TypeSpec: + ) -> type[Expr] | type[ScratchVar] | abi.TypeSpec: ptype = user_defined_annotations.get(parameter_name, None) if ptype is None: @@ -104,29 +101,28 @@ def _validate_parameter_type( # when `Expr` is the only supported annotation type. # * `invoke` type checks provided arguments against parameter types to catch mismatches. return Expr + elif ptype in (Expr, ScratchVar): + return ptype + elif SubroutineDefinition._is_abi_annotation(ptype): + return abi.type_spec_from_annotation(ptype) else: - if not isclass(ptype) and not SubroutineDefinition.is_abi_annotation(ptype): + if not isclass(ptype): raise TealInputError( f"Function has parameter {parameter_name} of declared type {ptype} which is not a class" ) + raise TealInputError( + f"Function has parameter {parameter_name} of disallowed type {ptype}. " + f"Only the types {(Expr, ScratchVar, 'ABI')} are allowed" + ) - if ptype in (Expr, ScratchVar): - return ptype - elif SubroutineDefinition.is_abi_annotation(ptype): - return abi.type_spec_from_annotation(ptype) - else: - raise TealInputError( - f"Function has parameter {parameter_name} of disallowed type {ptype}. " - f"Only the types {(Expr, ScratchVar, 'ABI')} are allowed" - ) - - @staticmethod + @classmethod def _arg_types_and_by_refs( + cls, sig: Signature, annotations: dict[str, type], abi_output_arg_name: Optional[str] = None, ) -> tuple[ - list[Type[Expr] | Type[ScratchVar] | abi.TypeSpec], + list[type[Expr] | type[ScratchVar] | abi.TypeSpec], set[str], dict[str, abi.TypeSpec], dict[str, abi.TypeSpec], @@ -181,7 +177,7 @@ def _arg_types_and_by_refs( f"Function has a parameter with a default value, which is not allowed in a subroutine: {name}" ) - expected_arg_type = SubroutineDefinition._validate_parameter_type( + expected_arg_type = SubroutineDefinition._validate_annotation( annotations, name ) @@ -252,7 +248,7 @@ def invoke( ) return SubroutineCall( - self, args, output_kwarg=_OutputKwArgInfo.from_dict(self.output_kwarg) + self, args, output_kwarg=OutputKwArgInfo.from_dict(self.output_kwarg) ) def __str__(self): @@ -293,24 +289,24 @@ def has_return(self): @dataclass -class _OutputKwArgInfo: +class OutputKwArgInfo: name: str abi_type: abi.TypeSpec @staticmethod - def from_dict(kwarg_info: dict[str, abi.TypeSpec]) -> Optional["_OutputKwArgInfo"]: - if kwarg_info is None or len(kwarg_info) == 0: + def from_dict(kwarg_info: dict[str, abi.TypeSpec]) -> Optional["OutputKwArgInfo"]: + if not kwarg_info: return None elif len(kwarg_info) == 1: - key = list(kwarg_info.keys())[0] - return _OutputKwArgInfo(key, kwarg_info[key]) + (key,) = [*kwarg_info.keys()] + return OutputKwArgInfo(key, kwarg_info[key]) else: raise TealInputError( f"illegal conversion kwarg_info length {len(kwarg_info)}." ) -_OutputKwArgInfo.__module__ = "pyteal" +OutputKwArgInfo.__module__ = "pyteal" class SubroutineCall(Expr): @@ -319,7 +315,7 @@ def __init__( subroutine: SubroutineDefinition, args: list[Expr | ScratchVar | abi.BaseType], *, - output_kwarg: Optional[_OutputKwArgInfo] = None, + output_kwarg: OutputKwArgInfo = None, ) -> None: super().__init__() self.subroutine = subroutine @@ -470,17 +466,18 @@ def __init__( fn_implementation: Callable[..., Expr], ) -> None: self.output_kwarg_info: Optional[ - _OutputKwArgInfo + OutputKwArgInfo ] = self._output_name_type_from_fn(fn_implementation) - internal_subroutine_ret_type = ( - TealType.none - if self.output_kwarg_info is None - else self.output_kwarg_info.abi_type.storage_type() - ) - output_kwarg_name = ( - None if self.output_kwarg_info is None else self.output_kwarg_info.name - ) + internal_subroutine_ret_type = TealType.none + if self.output_kwarg_info: + internal_subroutine_ret_type = ( + self.output_kwarg_info.abi_type.storage_type() + ) + + output_kwarg_name = None + if self.output_kwarg_info: + output_kwarg_name = self.output_kwarg_info.name # output ABI type is void, return_type = TealType.none # otherwise, return_type = ABI value's storage_type() @@ -493,7 +490,7 @@ def __init__( @staticmethod def _output_name_type_from_fn( fn_implementation: Callable[..., Expr] - ) -> Optional[_OutputKwArgInfo]: + ) -> Optional[OutputKwArgInfo]: if not callable(fn_implementation): raise TealInputError("Input to ABIReturnSubroutine is not callable") sig = signature(fn_implementation) @@ -505,21 +502,21 @@ def _output_name_type_from_fn( sig.parameters.keys(), ) ) - if len(potential_abi_arg_names) == 0: - return None - elif len(potential_abi_arg_names) == 1: - name = potential_abi_arg_names[0] - annotation = fn_annotations.get(name, None) - if annotation is None: + match potential_abi_arg_names: + case []: + return None + case [name]: + annotation = fn_annotations.get(name, None) + if annotation is None: + raise TealInputError( + f"ABI subroutine output-kwarg {name} must specify ABI type" + ) + type_spec = abi.type_spec_from_annotation(annotation) + return OutputKwArgInfo(name, type_spec) + case _: raise TealInputError( - f"ABI subroutine output-kwarg {name} must specify ABI type" + f"multiple output arguments ({len(potential_abi_arg_names)}) with type annotations {potential_abi_arg_names}" ) - type_spec = abi.type_spec_from_annotation(annotation) - return _OutputKwArgInfo(name, type_spec) - else: - raise TealInputError( - f"multiple output arguments with type annotations {potential_abi_arg_names}" - ) def __call__( self, *args: Expr | ScratchVar | abi.BaseType, **kwargs @@ -675,7 +672,7 @@ def var_n_loaded( f"Exceeding abi output keyword argument max number 1." ) - output_kwarg_info = _OutputKwArgInfo.from_dict(subroutine.output_kwarg) + output_kwarg_info = OutputKwArgInfo.from_dict(subroutine.output_kwarg) output_carrying_abi: Optional[abi.BaseType] = None if output_kwarg_info: From 9422ea32536c9d287a9aa200f517372ac8b4061d Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 2 May 2022 13:47:38 -0400 Subject: [PATCH 036/170] pr review partly --- pyteal/ast/subroutine.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 20edb03f7..bb08d9ee8 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -1,6 +1,5 @@ -from collections import OrderedDict from dataclasses import dataclass -from inspect import isclass, Parameter, signature, Signature +from inspect import isclass, Parameter, signature, Signature, get_annotations from typing import ( Callable, Optional, @@ -42,7 +41,7 @@ def __init__( sig = signature(implementation) - annotations = getattr(implementation, "__annotations__", OrderedDict()) + annotations = get_annotations(implementation) if "return" in annotations and annotations["return"] is not Expr: raise TealInputError( @@ -494,14 +493,12 @@ def _output_name_type_from_fn( if not callable(fn_implementation): raise TealInputError("Input to ABIReturnSubroutine is not callable") sig = signature(fn_implementation) - fn_annotations = getattr(fn_implementation, "__annotations__", OrderedDict()) + fn_annotations = get_annotations(fn_implementation) + + potential_abi_arg_names = [ + k for k, v in sig.parameters.items() if v.kind == Parameter.KEYWORD_ONLY + ] - potential_abi_arg_names = list( - filter( - lambda key: sig.parameters[key].kind == Parameter.KEYWORD_ONLY, - sig.parameters.keys(), - ) - ) match potential_abi_arg_names: case []: return None From 02f442a0d6d5ebfaf8bd00e25e89060517802722 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 2 May 2022 14:01:02 -0400 Subject: [PATCH 037/170] pr review partly --- pyteal/ast/subroutine.py | 41 ++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index bb08d9ee8..0d526540e 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -512,7 +512,8 @@ def _output_name_type_from_fn( return OutputKwArgInfo(name, type_spec) case _: raise TealInputError( - f"multiple output arguments ({len(potential_abi_arg_names)}) with type annotations {potential_abi_arg_names}" + f"multiple output arguments ({len(potential_abi_arg_names)}) " + f"with type annotations {potential_abi_arg_names}" ) def __call__( @@ -636,39 +637,39 @@ def evaluate_subroutine(subroutine: SubroutineDefinition) -> SubroutineDeclarati def var_n_loaded( param: str, ) -> tuple[ScratchVar, ScratchVar | abi.BaseType | Expr]: - _loaded_var: ScratchVar | abi.BaseType | Expr - _argument_var: ScratchVar + loaded_var: ScratchVar | abi.BaseType | Expr + argument_var: ScratchVar if param in subroutine.by_ref_args: - _argument_var = DynamicScratchVar(TealType.anytype) - _loaded_var = _argument_var + argument_var = DynamicScratchVar(TealType.anytype) + loaded_var = argument_var elif param in subroutine.abi_args: internal_abi_var = subroutine.abi_args[param].new_instance() - _argument_var = internal_abi_var.stored_value - _loaded_var = internal_abi_var + argument_var = internal_abi_var.stored_value + loaded_var = internal_abi_var else: - _argument_var = ScratchVar(TealType.anytype) - _loaded_var = _argument_var.load() + argument_var = ScratchVar(TealType.anytype) + loaded_var = argument_var.load() - return _argument_var, _loaded_var + return argument_var, loaded_var + + if len(subroutine.output_kwarg) > 1: + raise TealInputError( + f"ABI keyword argument num: {len(subroutine.output_kwarg)}. " + f"Exceeding abi output keyword argument max number 1." + ) args = subroutine.arguments() args = [arg for arg in args if arg not in subroutine.output_kwarg] - argument_vars: list[ScratchVar] = [] + arg_vars: list[ScratchVar] = [] loaded_args: list[ScratchVar | Expr | abi.BaseType] = [] for arg in args: - argument_var, loaded_arg = var_n_loaded(arg) - argument_vars.append(argument_var) + arg_var, loaded_arg = var_n_loaded(arg) + arg_vars.append(arg_var) loaded_args.append(loaded_arg) abi_output_kwargs: dict[str, abi.BaseType] = {} - if len(subroutine.output_kwarg) > 1: - raise TealInputError( - f"ABI keyword argument num: {len(subroutine.output_kwarg)}. " - f"Exceeding abi output keyword argument max number 1." - ) - output_kwarg_info = OutputKwArgInfo.from_dict(subroutine.output_kwarg) output_carrying_abi: Optional[abi.BaseType] = None @@ -700,7 +701,7 @@ def var_n_loaded( # Arg usage "A" to be pick up and store in scratch parameters that have been placed on the stack # need to reverse order of argumentVars because the last argument will be on top of the stack - body_ops = [var.slot.store() for var in argument_vars[::-1]] + body_ops = [var.slot.store() for var in arg_vars[::-1]] body_ops.append(subroutine_body) return SubroutineDeclaration(subroutine, Seq(body_ops)) From c9ed2b20595218a4807fb127fe50e6c53c132f02 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 2 May 2022 14:23:08 -0400 Subject: [PATCH 038/170] update test script --- pyteal/ast/subroutine_test.py | 41 ++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index 199c2c322..3da4ec608 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -1,4 +1,4 @@ -from typing import List, Literal, Callable +from typing import List, Literal import pytest from dataclasses import dataclass @@ -80,30 +80,36 @@ def fnWithPartialExprAnnotations(a, b: pt.Expr) -> pt.Expr: @dataclass class ABISubroutineTC: - impl: Callable[..., pt.Expr] + definition: pt.ABIReturnSubroutine arg_instances: list[pt.Expr | pt.abi.BaseType] name: str ret_type: str | pt.abi.TypeSpec def test_abi_subroutine_definition(): + @pt.ABIReturnSubroutine def fn_0arg_0ret() -> pt.Expr: return pt.Return() + @pt.ABIReturnSubroutine def fn_0arg_uint64_ret(*, res: pt.abi.Uint64) -> pt.Expr: return res.set(1) + @pt.ABIReturnSubroutine def fn_1arg_0ret(a: pt.abi.Uint64) -> pt.Expr: return pt.Return() + @pt.ABIReturnSubroutine def fn_1arg_1ret(a: pt.abi.Uint64, *, out: pt.abi.Uint64) -> pt.Expr: return out.set(a) + @pt.ABIReturnSubroutine def fn_2arg_0ret( a: pt.abi.Uint64, b: pt.abi.StaticArray[pt.abi.Byte, Literal[10]] ) -> pt.Expr: return pt.Return() + @pt.ABIReturnSubroutine def fn_2arg_1ret( a: pt.abi.Uint64, b: pt.abi.StaticArray[pt.abi.Byte, Literal[10]], @@ -112,6 +118,7 @@ def fn_2arg_1ret( ) -> pt.Expr: return out.set(b[a.get() % pt.Int(10)]) + @pt.ABIReturnSubroutine def fn_2arg_1ret_with_expr( a: pt.Expr, b: pt.abi.StaticArray[pt.abi.Byte, Literal[10]], @@ -150,23 +157,22 @@ def fn_2arg_1ret_with_expr( ) for case in cases: - definition = pt.ABIReturnSubroutine(case.impl) - assert definition.subroutine.argument_count() == len(case.arg_instances) - assert definition.name() == case.name + assert case.definition.subroutine.argument_count() == len(case.arg_instances) + assert case.definition.name() == case.name if len(case.arg_instances) > 0: with pytest.raises(pt.TealInputError): - definition(*case.arg_instances[:-1]) + case.definition(*case.arg_instances[:-1]) with pytest.raises(pt.TealInputError): - definition(*(case.arg_instances + [pt.abi.Uint64()])) + case.definition(*(case.arg_instances + [pt.abi.Uint64()])) - assert definition.type_of() == case.ret_type - invoked = definition(*case.arg_instances) + assert case.definition.type_of() == case.ret_type + invoked = case.definition(*case.arg_instances) assert isinstance( invoked, (pt.Expr if case.ret_type == "void" else pt.abi.ReturnedValue) ) - assert definition.is_registrable() == all( + assert case.definition.is_registrable() == all( map(lambda x: isinstance(x, pt.abi.BaseType), case.arg_instances) ) @@ -332,12 +338,15 @@ def fnWithMixedAnns4(a: pt.ScratchVar, b, c: pt.abi.Uint16) -> pt.Expr: def test_abi_subroutine_calling_param_types(): + @pt.ABIReturnSubroutine def fn_log_add(a: pt.abi.Uint64, b: pt.abi.Uint32) -> pt.Expr: return pt.Seq(pt.Log(pt.Itob(a.get() + b.get())), pt.Return()) + @pt.ABIReturnSubroutine def fn_ret_add(a: pt.abi.Uint64, b: pt.abi.Uint32, *, c: pt.abi.Uint64) -> pt.Expr: return c.set(a.get() + b.get() + pt.Int(0xA190)) + @pt.ABIReturnSubroutine def fn_abi_annotations_0( a: pt.abi.Byte, b: pt.abi.StaticArray[pt.abi.Uint32, Literal[10]], @@ -345,6 +354,7 @@ def fn_abi_annotations_0( ) -> pt.Expr: return pt.Return() + @pt.ABIReturnSubroutine def fn_abi_annotations_0_with_ret( a: pt.abi.Byte, b: pt.abi.StaticArray[pt.abi.Uint32, Literal[10]], @@ -354,12 +364,14 @@ def fn_abi_annotations_0_with_ret( ): return out.set(a) + @pt.ABIReturnSubroutine def fn_mixed_annotations_0(a: pt.ScratchVar, b: pt.Expr, c: pt.abi.Byte) -> pt.Expr: return pt.Seq( a.store(c.get() * pt.Int(0x0FF1CE) * b), pt.Return(), ) + @pt.ABIReturnSubroutine def fn_mixed_annotations_0_with_ret( a: pt.ScratchVar, b: pt.Expr, c: pt.abi.Byte, *, out: pt.abi.Uint64 ) -> pt.Expr: @@ -368,6 +380,7 @@ def fn_mixed_annotations_0_with_ret( out.set(a.load()), ) + @pt.ABIReturnSubroutine def fn_mixed_annotation_1( a: pt.ScratchVar, b: pt.abi.StaticArray[pt.abi.Uint32, Literal[10]] ) -> pt.Expr: @@ -377,6 +390,7 @@ def fn_mixed_annotation_1( pt.Return(), ) + @pt.ABIReturnSubroutine def fn_mixed_annotation_1_with_ret( a: pt.ScratchVar, b: pt.abi.Uint64, *, c: pt.abi.Bool ) -> pt.Expr: @@ -492,10 +506,11 @@ def fn_mixed_annotation_1_with_ret( ), ] - for case_name, impl, args, ret_type, err in cases: - definition = pt.ABIReturnSubroutine(impl) + for case_name, definition, args, ret_type, err in cases: assert definition.subroutine.argument_count() == len(args), case_name - assert definition.name() == impl.__name__, case_name + assert ( + definition.name() == definition.subroutine.implementation.__name__ + ), case_name if err is None: invocation = definition(*args) From 21447bb4103c2dc633fbfe868a6f0e0cbb78a816 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 3 May 2022 17:37:29 -0500 Subject: [PATCH 039/170] Abi subroutine feature merge (#315) --- .flake8 | 5 +- .github/workflows/build.yml | 53 +- .gitignore | 9 +- CHANGELOG.md | 10 + CONTRIBUTING.md | 2 +- Makefile | 49 +- README.md | 26 +- docker-compose.yml | 9 + docs/accessing_transaction_field.rst | 6 +- docs/assets.rst | 1 + docs/index.rst | 1 + docs/opup.rst | 95 +++ examples/application/opup.py | 95 +++ examples/signature/factorizer_game.py | 86 ++ pyteal/__init__.pyi | 2 + pyteal/ast/__init__.py | 5 +- pyteal/ast/opup.py | 157 ++++ pyteal/ast/opup_test.py | 46 ++ pyteal/ast/subroutine.py | 230 +++--- pyteal/ast/subroutine_test.py | 223 ++++- pyteal/errors.py | 5 +- pyteal/ir/tealblock_test.py | 1 + setup.py | 6 +- tests/blackbox.py | 201 +++++ tests/compile_asserts.py | 25 +- tests/integration/__init__.py | 0 tests/integration/graviton_test.py | 778 ++++++++++++++++++ tests/integration/teal/stability/app_exp.teal | 15 + .../teal/stability/app_oldfac.teal | 34 + .../teal/stability/app_slow_fibonacci.teal | 41 + .../teal/stability/app_square.teal | 18 + .../teal/stability/app_square_byref.teal | 25 + .../teal/stability/app_string_mult.teal | 47 ++ .../integration/teal/stability/app_swap.teal | 31 + .../integration/teal/stability/lsig_exp.teal | 10 + .../teal/stability/lsig_oldfac.teal | 29 + .../teal/stability/lsig_slow_fibonacci.teal | 36 + .../teal/stability/lsig_square.teal | 13 + .../teal/stability/lsig_square_byref.teal | 20 + .../teal/stability/lsig_string_mult.teal | 43 + .../integration/teal/stability/lsig_swap.teal | 26 + tests/teal/fac_by_ref_expected.teal | 41 - ...er_guide_snippet_dynamic_scratch_var.teal} | 0 ...> user_guide_snippet_recursiveIsEven.teal} | 0 tests/unit/__init__.py | 0 tests/unit/blackbox_test.py | 97 +++ tests/{ => unit}/compile_test.py | 31 +- tests/{ => unit}/module_test.py | 0 tests/{ => unit}/pass_by_ref_test.py | 218 +++-- tests/unit/pre_v6_test.py | 85 ++ tests/unit/teal/blackbox/app_utest_any.teal | 16 + .../teal/blackbox/app_utest_any_args.teal | 23 + tests/unit/teal/blackbox/app_utest_bytes.teal | 13 + .../teal/blackbox/app_utest_bytes_args.teal | 20 + tests/unit/teal/blackbox/app_utest_int.teal | 13 + .../teal/blackbox/app_utest_int_args.teal | 20 + tests/unit/teal/blackbox/app_utest_noop.teal | 15 + .../teal/blackbox/app_utest_noop_args.teal | 22 + tests/unit/teal/blackbox/lsig_utest_any.teal | 12 + .../teal/blackbox/lsig_utest_any_args.teal | 19 + .../unit/teal/blackbox/lsig_utest_bytes.teal | 9 + .../teal/blackbox/lsig_utest_bytes_args.teal | 16 + tests/unit/teal/blackbox/lsig_utest_int.teal | 8 + .../teal/blackbox/lsig_utest_int_args.teal | 15 + tests/unit/teal/blackbox/lsig_utest_noop.teal | 10 + .../teal/blackbox/lsig_utest_noop_args.teal | 17 + .../teal/pre_v6/sub_even.teal} | 2 +- .../teal/pre_v6/sub_fastfib.teal} | 2 +- .../teal/pre_v6/sub_logcat.teal} | 2 +- .../teal/pre_v6/sub_slowfib.teal} | 2 +- .../teal/unchanged/empty_scratches.teal} | 0 .../teal/unchanged/lots_o_vars.teal} | 0 .../teal/unchanged/sub_logcat_dynamic.teal} | 0 .../teal/unchanged/sub_mixed.teal} | 0 .../teal/unchanged/swapper.teal} | 0 .../teal/unchanged/wilt_the_stilt.teal} | 0 ...ser_guide_snippet_dynamic_scratch_var.teal | 17 + .../user_guide_snippet_recursiveIsEven.teal | 32 + tests/unit/user_guide_test.py | 93 +++ tests/user_guide_test.py | 84 -- 80 files changed, 3100 insertions(+), 368 deletions(-) create mode 100644 docker-compose.yml create mode 100644 docs/opup.rst create mode 100644 examples/application/opup.py create mode 100644 examples/signature/factorizer_game.py create mode 100644 pyteal/ast/opup.py create mode 100644 pyteal/ast/opup_test.py create mode 100644 tests/blackbox.py create mode 100644 tests/integration/__init__.py create mode 100644 tests/integration/graviton_test.py create mode 100644 tests/integration/teal/stability/app_exp.teal create mode 100644 tests/integration/teal/stability/app_oldfac.teal create mode 100644 tests/integration/teal/stability/app_slow_fibonacci.teal create mode 100644 tests/integration/teal/stability/app_square.teal create mode 100644 tests/integration/teal/stability/app_square_byref.teal create mode 100644 tests/integration/teal/stability/app_string_mult.teal create mode 100644 tests/integration/teal/stability/app_swap.teal create mode 100644 tests/integration/teal/stability/lsig_exp.teal create mode 100644 tests/integration/teal/stability/lsig_oldfac.teal create mode 100644 tests/integration/teal/stability/lsig_slow_fibonacci.teal create mode 100644 tests/integration/teal/stability/lsig_square.teal create mode 100644 tests/integration/teal/stability/lsig_square_byref.teal create mode 100644 tests/integration/teal/stability/lsig_string_mult.teal create mode 100644 tests/integration/teal/stability/lsig_swap.teal delete mode 100644 tests/teal/fac_by_ref_expected.teal rename tests/teal/{user_guide_snippet_dynamic_scratch_var_expected.teal => user_guide_snippet_dynamic_scratch_var.teal} (100%) rename tests/teal/{user_guide_snippet_recursiveIsEven_expected.teal => user_guide_snippet_recursiveIsEven.teal} (100%) create mode 100644 tests/unit/__init__.py create mode 100644 tests/unit/blackbox_test.py rename tests/{ => unit}/compile_test.py (85%) rename tests/{ => unit}/module_test.py (100%) rename tests/{ => unit}/pass_by_ref_test.py (87%) create mode 100644 tests/unit/pre_v6_test.py create mode 100644 tests/unit/teal/blackbox/app_utest_any.teal create mode 100644 tests/unit/teal/blackbox/app_utest_any_args.teal create mode 100644 tests/unit/teal/blackbox/app_utest_bytes.teal create mode 100644 tests/unit/teal/blackbox/app_utest_bytes_args.teal create mode 100644 tests/unit/teal/blackbox/app_utest_int.teal create mode 100644 tests/unit/teal/blackbox/app_utest_int_args.teal create mode 100644 tests/unit/teal/blackbox/app_utest_noop.teal create mode 100644 tests/unit/teal/blackbox/app_utest_noop_args.teal create mode 100644 tests/unit/teal/blackbox/lsig_utest_any.teal create mode 100644 tests/unit/teal/blackbox/lsig_utest_any_args.teal create mode 100644 tests/unit/teal/blackbox/lsig_utest_bytes.teal create mode 100644 tests/unit/teal/blackbox/lsig_utest_bytes_args.teal create mode 100644 tests/unit/teal/blackbox/lsig_utest_int.teal create mode 100644 tests/unit/teal/blackbox/lsig_utest_int_args.teal create mode 100644 tests/unit/teal/blackbox/lsig_utest_noop.teal create mode 100644 tests/unit/teal/blackbox/lsig_utest_noop_args.teal rename tests/{teal/sub_even_expected.teal => unit/teal/pre_v6/sub_even.teal} (95%) rename tests/{teal/sub_fastfib_expected.teal => unit/teal/pre_v6/sub_fastfib.teal} (94%) rename tests/{teal/sub_logcat_expected.teal => unit/teal/pre_v6/sub_logcat.teal} (97%) rename tests/{teal/sub_slowfib_expected.teal => unit/teal/pre_v6/sub_slowfib.teal} (94%) rename tests/{teal/empty_scratches_expected.teal => unit/teal/unchanged/empty_scratches.teal} (100%) rename tests/{teal/lots_o_vars_expected.teal => unit/teal/unchanged/lots_o_vars.teal} (100%) rename tests/{teal/sub_logcat_dynamic_expected.teal => unit/teal/unchanged/sub_logcat_dynamic.teal} (100%) rename tests/{teal/sub_mixed_expected.teal => unit/teal/unchanged/sub_mixed.teal} (100%) rename tests/{teal/swapper_expected.teal => unit/teal/unchanged/swapper.teal} (100%) rename tests/{teal/wilt_the_stilt_expected.teal => unit/teal/unchanged/wilt_the_stilt.teal} (100%) create mode 100644 tests/unit/teal/user_guide/user_guide_snippet_dynamic_scratch_var.teal create mode 100644 tests/unit/teal/user_guide/user_guide_snippet_recursiveIsEven.teal create mode 100644 tests/unit/user_guide_test.py delete mode 100644 tests/user_guide_test.py diff --git a/.flake8 b/.flake8 index 227d3d690..99982dab1 100644 --- a/.flake8 +++ b/.flake8 @@ -11,6 +11,7 @@ ignore = per-file-ignores = pyteal/compiler/optimizer/__init__.py: F401 examples/application/asset.py: F403, F405 + examples/application/opup.py: F403, F405 examples/application/security_token.py: F403, F405 examples/application/vote.py: F403, F405 examples/signature/atomic_swap.py: F403, F405 @@ -23,9 +24,7 @@ per-file-ignores = examples/signature/recurring_swap_deploy.py: F403, F405 pyteal/__init__.py: F401, F403 pyteal/ir/ops.py: E221 - tests/module_test.py: F401, F403 - tests/*.py: I252 + tests/unit/module_test.py: F401, F403 # from flake8-tidy-imports ban-relative-imports = true - diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4eb3bbd35..b1ffbfbe1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,7 +13,7 @@ jobs: container: python:${{ matrix.python }} strategy: matrix: - python: ['3.10'] + python: ["3.10"] steps: - run: python3 --version - name: Check out code @@ -25,19 +25,68 @@ jobs: - name: Build and Test run: make build-and-test + run-integration-tests: + runs-on: ubuntu-20.04 + strategy: + matrix: + python: [ "3.10" ] + steps: + - name: Check out code + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - uses: actions/setup-python@v3 + with: + python-version: "${{ matrix.python }}" + - name: Test Python version + run: | + installed="$(python --version)" + expected="${{ matrix.python }}" + echo $installed + [[ $installed =~ "Python ${expected}" ]] && echo "Configured Python" || (echo "Failed to configure Python" && exit 1) + - name: Local ACT Only - Install required os level applications + if: ${{ env.ACT }} + run: | + sudo apt update -y + sudo apt install -y curl + sudo apt -y install ca-certificates curl gnupg lsb-release + sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg + sudo echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ + $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + sudo apt update + sudo apt -y install docker-ce docker-ce-cli containerd.io + sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose + sudo chmod +x /usr/local/bin/docker-compose + sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose + docker-compose --version + - name: Create sandbox + run: make sandbox-dev-up + - name: Install python dependencies + run: make setup-development + - name: Build, Unit Tests and Integration Tests + run: make all-tests + - name: Stop running images + run: make sandbox-dev-stop + build-docset: runs-on: ubuntu-20.04 - container: python:3.10 # Needs `make`, can't be slim + strategy: + matrix: + python: [ "3.10" ] steps: - name: Check out code uses: actions/checkout@v2 with: fetch-depth: 0 + - uses: actions/setup-python@v3 + with: + python-version: "${{ matrix.python }}" - name: Install python dependencies run: make setup-docs - name: Make docs run: make bundle-docs - name: Archive docset + if: ${{ !env.ACT }} uses: actions/upload-artifact@v2 with: name: pyteal.docset diff --git a/.gitignore b/.gitignore index b96db3ef2..f930cdc2f 100644 --- a/.gitignore +++ b/.gitignore @@ -51,9 +51,8 @@ coverage.xml .hypothesis/ .pytest_cache/ -# Tests generating TEAL output to compared against an expected example. -tests/**/*.teal -!tests/**/*_expected.teal +# Generated by unit tests - usually for diffs against expected: +**/generated # Translations *.mo @@ -133,5 +132,9 @@ dmypy.json .idea .vscode +# comma seperated vals report files +*.csv +!tests/**/*_example.csv + # mac OS .DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md index 3becbbf89..c77a05892 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +# 0.12.0 + +## Added +* Introduce a utility for increasing opcode budget referred to as OpUp ([#274](https://github.com/algorand/pyteal/pull/274)). +* Introduce dryrun testing facilities referred to as blackbox testing ([#249](https://github.com/algorand/pyteal/pull/249)). + +## Changed +* Make various user guide updates/corrections ([#291](https://github.com/algorand/pyteal/pull/291), [#295](https://github.com/algorand/pyteal/pull/295), [#301](https://github.com/algorand/pyteal/pull/301)). +* Install flake8 linter ([#273](https://github.com/algorand/pyteal/pull/273), [#283](https://github.com/algorand/pyteal/pull/283)). + # 0.11.1 ## Fixed diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eabd337fb..e9ed1daa2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,7 +43,7 @@ A sibling module is defined as a different child module of the parent module. Fo #### In Runtime Code -A file in this codebase that requires another module/file in this codebase, should import the absolute path of the module/file, not the relative one, using the `from X import Y` method. +When a runtime file in this codebase needs to import another module/file in this codebase, you should import the absolute path of the module/file, not the relative one, using the `from X import Y` method. With regard to modules, there are two ways to import an object from this codebase: diff --git a/Makefile b/Makefile index 8e77f54fd..64ab76e30 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,7 @@ +# ---- Setup ---- # + setup-development: - pip install -e.[development] + pip install -e .[development] setup-docs: setup-development pip install -r docs/requirements.txt @@ -8,6 +10,11 @@ setup-docs: setup-development setup-wheel: pip install wheel +generate-init: + python -m scripts.generate_init + +# ---- Docs and Distribution ---- # + bdist-wheel: python setup.py sdist bdist_wheel @@ -20,8 +27,7 @@ bundle-docs: bundle-docs-clean doc2dash --name pyteal --index-page index.html --online-redirect-url https://pyteal.readthedocs.io/en/ _build/html && \ tar -czvf pyteal.docset.tar.gz pyteal.docset -generate-init: - python -m scripts.generate_init +# ---- Code Quality ---- # check-generate-init: python -m scripts.generate_init --check @@ -33,17 +39,50 @@ black: flake8: flake8 $(ALLPY) +# TODO: add `tests` to $MYPY when graviton respects mypy (version 🐗) MYPY = pyteal scripts mypy: mypy $(MYPY) lint: black flake8 mypy +# ---- Unit Tests (no algod) ---- # + +# TODO: add blackbox_test.py to multithreaded tests when following issue has been fixed https://github.com/algorand/pyteal/issues/199 +NUM_PROCS = auto test-unit: - pytest + pytest -n $(NUM_PROCS) --durations=10 -sv pyteal tests/unit --ignore tests/unit/blackbox_test.py --ignore tests/unit/user_guide_test.py + pytest -n 1 -sv tests/unit/blackbox_test.py tests/unit/user_guide_test.py build-and-test: check-generate-init lint test-unit -# Extras: +# ---- Integration Test (algod required) ---- # + +sandbox-dev-up: + docker-compose up -d algod + +sandbox-dev-stop: + docker-compose stop algod + +integration-run: + pytest -n $(NUM_PROCS) --durations=10 -sv tests/integration + +test-integration: integration-run + +all-tests: build-and-test test-integration + +# ---- Local Github Actions Simulation via `act` ---- # +# assumes act is installed, e.g. via `brew install act` + +ACT_JOB = run-integration-tests +local-gh-job: + act -j $(ACT_JOB) + +local-gh-simulate: + act + + +# ---- Extras ---- # + coverage: pytest --cov-report html --cov=pyteal diff --git a/README.md b/README.md index 61db520c4..d96e4a031 100644 --- a/README.md +++ b/README.md @@ -60,18 +60,30 @@ Pip install PyTeal in editable state with dependencies: * `pip install -e.[development]` * Note, that if you're using `zsh` you'll need to escape the brackets: `pip install -e.\[development\]` +Format code: + +* `black .` + +Lint using flake8: + +* `flake8 docs examples pyteal scripts tests *.py` + Type checking using mypy: -* `mypy pyteal` +* `mypy pyteal scripts` -Run tests: +Run unit tests: -* `pytest` +* `pytest pyteal tests/unit` -Format code: +Run integration tests (assumes a developer-mode `algod` is available on port 4001): -* `black .` +* `pytest tests/integration` -Lint using flake8: +Stand up developer-mode algod on ports 4001, 4002 and `tealdbg` on port 9392 (assumes [Docker](https://www.docker.com/) is available on your system): -* `flake8 docs examples pyteal scripts tests *.py` +* `docker-compose up -d` + +Tear down and clean up resources for the developer-mode algod stood up above: + +* `docker-compose down` diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..f81beaf2f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,9 @@ +version: '3' + +services: + algod: + image: makerxau/algorand-sandbox-dev:latest + ports: + - "4001:4001" + - "4002:4002" + - "9392:9392" diff --git a/docs/accessing_transaction_field.rst b/docs/accessing_transaction_field.rst index 9ff9017e3..adb8125e8 100644 --- a/docs/accessing_transaction_field.rst +++ b/docs/accessing_transaction_field.rst @@ -47,8 +47,8 @@ Operator :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 application accounts -:any:`Txn.assets ` :code:`TealType.uint64[]` 3 Array of application assets +: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 @@ -223,7 +223,7 @@ Information about the current state of the blockchain can be obtained using the 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 mircoAlgos +: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 diff --git a/docs/assets.rst b/docs/assets.rst index 6f160c56f..313e5830f 100644 --- a/docs/assets.rst +++ b/docs/assets.rst @@ -126,6 +126,7 @@ Expression Type Description :any:`AssetParam.name()` :code:`TealType.bytes` The name of the asset. :any:`AssetParam.url()` :code:`TealType.bytes` A URL associated with the asset. :any:`AssetParam.metadataHash()` :code:`TealType.bytes` A 32-byte hash associated with the asset. +:any:`AssetParam.creator()` :code:`TealType.bytes` The address of the asset's creator account. :any:`AssetParam.manager()` :code:`TealType.bytes` The address of the asset's manager account. :any:`AssetParam.reserve()` :code:`TealType.bytes` The address of the asset's reserve account. :any:`AssetParam.freeze()` :code:`TealType.bytes` The address of the asset's freeze account. diff --git a/docs/index.rst b/docs/index.rst index bbf72f87f..9ff3f2fe8 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -44,6 +44,7 @@ PyTeal **hasn't been security audited**. Use it at your own risk. assets versions compiler_optimization + opup .. toctree:: :maxdepth: 3 diff --git a/docs/opup.rst b/docs/opup.rst new file mode 100644 index 000000000..dbb435301 --- /dev/null +++ b/docs/opup.rst @@ -0,0 +1,95 @@ +.. _opup: + +OpUp: Budget Increase Utility +================================= + +Some opcode budget is consumed during execution of every Algorand Smart Contract because every TEAL +instruction has a corresponding cost. In order for the evaluation to succeed, the budget consumed must not +exceed the budget provided. This constraint may introduce a problem for expensive contracts that quickly +consume the initial budget of 700. The OpUp budget increase utility provides a workaround using NoOp inner +transactions that increase the transaction group's pooled compute budget. The funding for issuing the inner +transactions is provided by the contract doing the issuing, not the user calling the contract, so the +contract must have enough funds for this purpose. Note that there is a context specific limit to the number +of inner transactions issued in a transaction group so budget cannot be increased arbitrarily. The available +budget when using the OpUp utility will need to be high enough to execute the TEAL code that issues the inner +transactions. A budget of ~20 is enough for most use cases. + +Usage +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The :any:`pyteal.OpUp` utility is available in two modes: :any:`Explicit` and :any:`OnCall`: + +================= =================================================================================== +OpUp Mode Description +================= =================================================================================== +:code:`Explicit` Calls a user provided external app when more budget is requested. +:code:`OnCall` Creates and immediately deletes the app to be called when more budget is requested. +================= =================================================================================== + + +:any:`Explicit` has the benefit of constructing more lightweight inner transactions, but requires the +target app ID to be provided in the foreign apps array field of the transaction and the :any:`pyteal.OpUp` +constructor in order for it to be accessible. :any:`OnCall` is easier to use, but has slightly more overhead +because the target app must be created and deleted during the evaluation of an app call. + +Ensure Budget +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:any:`pyteal.OpUp.ensure_budget` attempts to ensure that the available budget is at least the budget requested by +the caller. If there aren't enough funds for issuing the inner transactions or the inner transaction limit +is exceeded, the evaluation will fail. The typical usage pattern is to insert the :any:`pyteal.OpUp.ensure_budget` +call just before a particularly heavyweight subroutine or expression. Keep in mind that the required budget +expression will be evaluated before the inner transactions are issued so it may be prudent to avoid expensive +expressions, which may exhaust the budget before it can be increased. + +In the example below, the :py:meth:`pyteal.Ed25519Verify` expression is used, which costs 1,900. + +.. code-block:: python + + # The application id to be called when more budget is requested. This should be + # replaced with an id provided by the developer. + target_app_id = Int(1) + + # OnCall mode works the exact same way, just omit the target_app_id + opup = OpUp(OpUpMode.Explicit, target_app_id) + program = Seq( + If(Txn.application_id() != Int(0)).Then( + Seq( + opup.ensure_budget(Int(2000)), + Assert(Ed25519Verify(args[0], args[1], args[2])), + ) + ), + Approve(), + ) + +Maximize Budget +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:any:`pyteal.OpUp.maximize_budget` attempts to issue as many inner transactions as possible with the given fee. +This essentially maximizes the available budget while putting a ceiling on the amount of fee spent. Just +as with :any:`pyteal.OpUp.ensure_budget`, the evaluation will fail if there aren't enough funds for issuing the +inner transactions or the inner transaction limit is exceeded. This method may be preferred to +:any:`pyteal.OpUp.ensure_budget` when the fee spent on increasing budget needs to be capped or if the developer +would rather just maximize the available budget instead of doing in depth cost analysis on the program. + +In the example below, the fee is capped at 3,000 microAlgos for increasing the budget. This works out to 3 inner +transactions being issued, each increasing the available budget by ~700. + +.. code-block:: python + + target_app_id = Int(1) # the application id to be called when more budget is requested + + # OnCall mode works the exact same way, just omit the target_app_id + opup = OpUp(OpUpMode.Explicit, target_app_id) + program = Seq( + If(Txn.application_id() != Int(0)).Then( + Seq( + opup.maximize_budget(Int(3000)), + Assert(Ed25519Verify(args[0], args[1], args[2])), + ) + ), + Approve(), + ) + +If budget increase requests appear multiple times in the program, it may be a good idea to wrap the +invocation in a PyTeal Subroutine to improve code reuse and reduce the size of the compiled program. \ No newline at end of file diff --git a/examples/application/opup.py b/examples/application/opup.py new file mode 100644 index 000000000..82cc00118 --- /dev/null +++ b/examples/application/opup.py @@ -0,0 +1,95 @@ +# This example is provided for informational purposes only and has not been audited for security. + +from pyteal import * + + +def approval_program_explicit_ensure(): + args = [ + Bytes("base64", "iZWMx72KvU6Bw6sPAWQFL96YH+VMrBA0XKWD9XbZOZI="), + Bytes( + "base64", + "if8ooA+32YZc4SQBvIDDY8tgTatPoq4IZ8Kr+We1t38LR2RuURmaVu9D4shbi4VvND87PUqq5/0vsNFEGIIEDA==", + ), + Addr("7JOPVEP3ABJUW5YZ5WFIONLPWTZ5MYX5HFK4K7JLGSIAG7RRB42MNLQ224"), + ] + opup = OpUp(OpUpMode.Explicit, Int(1)) + return Seq( + If(Txn.application_id() != Int(0)).Then( + Seq( + opup.ensure_budget(Int(2000)), + Pop(Ed25519Verify(args[0], args[1], args[2])), + ) + ), + Approve(), + ) + + +def approval_program_oncall_ensure(): + args = [ + Bytes("base64", "iZWMx72KvU6Bw6sPAWQFL96YH+VMrBA0XKWD9XbZOZI="), + Bytes( + "base64", + "if8ooA+32YZc4SQBvIDDY8tgTatPoq4IZ8Kr+We1t38LR2RuURmaVu9D4shbi4VvND87PUqq5/0vsNFEGIIEDA==", + ), + Addr("7JOPVEP3ABJUW5YZ5WFIONLPWTZ5MYX5HFK4K7JLGSIAG7RRB42MNLQ224"), + ] + opup = OpUp(OpUpMode.OnCall) + return Seq( + If(Txn.application_id() != Int(0)).Then( + Seq( + opup.ensure_budget(Int(2000)), + Pop(Ed25519Verify(args[0], args[1], args[2])), + ) + ), + Approve(), + ) + + +def approval_program_explicit_maximize(): + args = [ + Bytes("base64", "iZWMx72KvU6Bw6sPAWQFL96YH+VMrBA0XKWD9XbZOZI="), + Bytes( + "base64", + "if8ooA+32YZc4SQBvIDDY8tgTatPoq4IZ8Kr+We1t38LR2RuURmaVu9D4shbi4VvND87PUqq5/0vsNFEGIIEDA==", + ), + Addr("7JOPVEP3ABJUW5YZ5WFIONLPWTZ5MYX5HFK4K7JLGSIAG7RRB42MNLQ224"), + ] + opup = OpUp(OpUpMode.Explicit, Int(1)) + return Seq( + If(Txn.application_id() != Int(0)).Then( + Seq( + opup.maximize_budget(Int(3000)), + Pop(Ed25519Verify(args[0], args[1], args[2])), + ) + ), + Approve(), + ) + + +def approval_program_oncall_maximize(): + args = [ + Bytes("base64", "iZWMx72KvU6Bw6sPAWQFL96YH+VMrBA0XKWD9XbZOZI="), + Bytes( + "base64", + "if8ooA+32YZc4SQBvIDDY8tgTatPoq4IZ8Kr+We1t38LR2RuURmaVu9D4shbi4VvND87PUqq5/0vsNFEGIIEDA==", + ), + Addr("7JOPVEP3ABJUW5YZ5WFIONLPWTZ5MYX5HFK4K7JLGSIAG7RRB42MNLQ224"), + ] + opup = OpUp(OpUpMode.OnCall) + return Seq( + If(Txn.application_id() != Int(0)).Then( + Seq( + opup.maximize_budget(Int(3000)), + Pop(Ed25519Verify(args[0], args[1], args[2])), + ) + ), + Approve(), + ) + + +if __name__ == "__main__": + with open("program.teal", "w") as f: + compiled = compileTeal( + approval_program_oncall_maximize(), mode=Mode.Application, version=6 + ) + f.write(compiled) diff --git a/examples/signature/factorizer_game.py b/examples/signature/factorizer_game.py new file mode 100644 index 000000000..ad8ffe3b7 --- /dev/null +++ b/examples/signature/factorizer_game.py @@ -0,0 +1,86 @@ +# WARNING: this logic sig is for demo purposes only + +from pyteal import ( + And, + Arg, + Btoi, + Bytes, + Expr, + Global, + If, + Int, + Pop, + ScratchVar, + Seq, + Subroutine, + TealType, + Txn, + TxnType, +) + +ONE_ALGO = Int(1_000_000) + + +@Subroutine(TealType.uint64) +def root_closeness(A, B, C, X): + left = ScratchVar(TealType.uint64) + right = ScratchVar(TealType.uint64) + return Seq( + left.store(A * X * X + C), + right.store(B * X), + If(left.load() < right.load()) + .Then(right.load() - left.load()) + .Else(left.load() - right.load()), + ) + + +@Subroutine(TealType.uint64) +def calculate_prize(closeness): + return ( + If(closeness + Int(1) < Int(20)) + .Then(ONE_ALGO * (Int(10) - (closeness + Int(1)) / Int(2))) + .Else(Int(0)) + ) + + +def logicsig(a: int, p: int, q: int) -> Expr: + """ + Choices + * (a, p, q) = (1, 5, 7) + * compiling on TEAL version 5 and + * with assembleConstants = True + results in Logic-Sig Contract Account Address: + WO3TQD3WBSDKB6WEHUMSEBFH53GZVVXYGPWYDWKUZCKEXTVCDNDHJGG6II + """ + assert all( + isinstance(x, int) and p < q and a > 0 and x >= 0 for x in (a, p, q) + ), f"require non-negative ints a, p, q with p < q but got {a, p, q}" + + b, c = a * (p + q), a * p * q + msg = Bytes(f"Can you factor {a} * x^2 - {b} * x + {c} ?") + + A, B, C = Int(a), Int(b), Int(c) + X1 = Btoi(Arg(0)) + X2 = Btoi(Arg(1)) + C1 = ScratchVar(TealType.uint64) + C2 = ScratchVar(TealType.uint64) + SUM = ScratchVar(TealType.uint64) + PRIZE = ScratchVar(TealType.uint64) + return Seq( + Pop(msg), + C1.store(root_closeness(A, B, C, X1)), + C2.store(root_closeness(A, B, C, X2)), + SUM.store(C1.load() + C2.load()), + PRIZE.store(calculate_prize(SUM.load())), + And( + Txn.type_enum() == TxnType.Payment, + Txn.close_remainder_to() == Global.zero_address(), + X1 != X2, + PRIZE.load(), + Txn.amount() == PRIZE.load(), + ), + ) + + +def create(a, b, c): + return logicsig(*map(lambda x: int(x), (a, b, c))) diff --git a/pyteal/__init__.pyi b/pyteal/__init__.pyi index 94c5ad96a..bf7c6ab75 100644 --- a/pyteal/__init__.pyi +++ b/pyteal/__init__.pyi @@ -129,6 +129,8 @@ __all__ = [ "Not", "OnComplete", "Op", + "OpUp", + "OpUpMode", "OptimizeOptions", "Or", "Pop", diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index f1d004ff7..e13584043 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -137,9 +137,10 @@ from pyteal.ast.scratchvar import DynamicScratchVar, ScratchVar from pyteal.ast.maybe import MaybeValue from pyteal.ast.multi import MultiValue +from pyteal.ast.opup import OpUp, OpUpMode # abi -from pyteal.ast import abi +import pyteal.ast.abi as abi # noqa: I250 __all__ = [ "Expr", @@ -251,6 +252,8 @@ "ScratchVar", "MaybeValue", "MultiValue", + "OpUp", + "OpUpMode", "BytesAdd", "BytesMinus", "BytesDiv", diff --git a/pyteal/ast/opup.py b/pyteal/ast/opup.py new file mode 100644 index 000000000..45a84d37d --- /dev/null +++ b/pyteal/ast/opup.py @@ -0,0 +1,157 @@ +from pyteal.ast.app import OnComplete +from pyteal.errors import TealInputError +from pyteal.ast.while_ import While +from pyteal.ast.expr import Expr +from pyteal.ast.global_ import Global +from pyteal.ast.seq import Seq +from pyteal.ast.int import Int +from pyteal.ast.bytes import Bytes +from pyteal.ast.itxn import InnerTxnBuilder +from pyteal.ast.scratchvar import ScratchVar +from pyteal.ast.txn import TxnField, TxnType +from pyteal.ast.for_ import For +from pyteal.types import TealType, require_type +from enum import Enum + + +class OpUpMode(Enum): + """An Enum object that defines the mode used for the OpUp utility. + + Note: the Explicit mode requires the app id to be provided + through the foreign apps array in order for it to be accessible + during evaluation. + """ + + # The app to call must be provided by the user. + Explicit = 0 + + # The app to call is created then deleted for each request to increase budget. + OnCall = 1 + + +ON_CALL_APP = Bytes("base16", "068101") # v6 teal program "int 1" +MIN_TXN_FEE = Int(1000) + + +class OpUp: + """Utility for increasing opcode budget during app execution. + + Requires TEAL version 6 or higher. + + Example: + .. code-block:: python + + # OnCall mode: doesn't accept target_app_id as an argument + opup = OpUp(OpUpMode.OnCall) + program_with_opup = Seq( + ..., + opup.ensure_budget(Int(1000)), + ..., + ) + + # Explicit mode: requires target_app_id as an argument + opup = OpUp(OpUpMode.Explicit, Int(1)) + program_with_opup = Seq( + ..., + opup.ensure_budget(Int(1000)), + ..., + ) + """ + + def __init__(self, mode: OpUpMode, target_app_id: Expr = None): + """Create a new OpUp object. + + Args: + mode: OpUpMode that determines the style of budget increase + to use. See the OpUpMode Enum for more information. + target_app_id (optional): In Explicit mode, the OpUp utility + requires the app_id to target for inner app calls. Defaults + to None. + """ + + # With only OnCall and Explicit modes supported, the mode argument + # isn't strictly necessary but it will most likely be required if + # we do decide to add more modes in the future. + if mode == OpUpMode.Explicit: + if target_app_id is None: + raise TealInputError( + "target_app_id must be specified in Explicit OpUp mode" + ) + require_type(target_app_id, TealType.uint64) + self.target_app_id = target_app_id + elif mode == OpUpMode.OnCall: + if target_app_id is not None: + raise TealInputError("target_app_id is not used in OnCall OpUp mode") + else: + raise TealInputError("Invalid OpUp mode provided") + + self.mode = mode + + def _construct_itxn(self) -> Expr: + if self.mode == OpUpMode.Explicit: + return Seq( + InnerTxnBuilder.Begin(), + InnerTxnBuilder.SetFields( + { + TxnField.type_enum: TxnType.ApplicationCall, + TxnField.application_id: self.target_app_id, + } + ), + InnerTxnBuilder.Submit(), + ) + else: + return Seq( + InnerTxnBuilder.Begin(), + InnerTxnBuilder.SetFields( + { + TxnField.type_enum: TxnType.ApplicationCall, + TxnField.on_completion: OnComplete.DeleteApplication, + TxnField.approval_program: ON_CALL_APP, + TxnField.clear_state_program: ON_CALL_APP, + } + ), + InnerTxnBuilder.Submit(), + ) + + def ensure_budget(self, required_budget: Expr) -> Expr: + """Ensure that the budget will be at least the required_budget. + + Note: the available budget just prior to calling ensure_budget() must be + high enough to execute the budget increase code. The exact budget required + depends on the provided required_budget expression, but a budget of ~20 + should be sufficient for most use cases. If lack of budget is an issue then + consider moving the call to ensure_budget() earlier in the pyteal program.""" + require_type(required_budget, TealType.uint64) + + # A budget buffer is necessary to deal with an edge case of ensure_budget(): + # if the current budget is equal to or only slightly higher than the + # required budget then it's possible for ensure_budget() to return with a + # current budget less than the required budget. The buffer prevents this + # from being the case. + buffer = Int(10) + buffered_budget = ScratchVar(TealType.uint64) + return Seq( + buffered_budget.store(required_budget + buffer), + While(buffered_budget.load() > Global.opcode_budget()).Do( + self._construct_itxn() + ), + ) + + def maximize_budget(self, fee: Expr) -> Expr: + """Maximize the available opcode budget without spending more than the given fee. + + Note: the available budget just prior to calling maximize_budget() must be + high enough to execute the budget increase code. The exact budget required + depends on the provided fee expression, but a budget of ~25 should be + sufficient for most use cases. If lack of budget is an issue then consider + moving the call to maximize_budget() earlier in the pyteal program.""" + require_type(fee, TealType.uint64) + + i = ScratchVar(TealType.uint64) + n = fee / Global.min_txn_fee() + return For(i.store(Int(0)), i.load() < n, i.store(i.load() + Int(1))).Do( + self._construct_itxn() + ) + + +OpUp.__module__ = "pyteal" diff --git a/pyteal/ast/opup_test.py b/pyteal/ast/opup_test.py new file mode 100644 index 000000000..ac81cd255 --- /dev/null +++ b/pyteal/ast/opup_test.py @@ -0,0 +1,46 @@ +import pytest + +from pyteal.ast.opup import OpUp, OpUpMode + +import pyteal as pt + + +def test_opup_explicit(): + mode = OpUpMode.Explicit + with pytest.raises(pt.TealInputError) as err: + opup = OpUp(mode) + assert "target_app_id must be specified in Explicit OpUp mode" in str(err.value) + + with pytest.raises(pt.TealTypeError): + opup = OpUp(mode, pt.Bytes("appid")) + + opup = OpUp(mode, pt.Int(1)) + + with pytest.raises(pt.TealTypeError): + opup.ensure_budget(pt.Bytes("budget")) + + with pytest.raises(pt.TealTypeError): + opup.maximize_budget(pt.Bytes("fee")) + + assert opup.target_app_id == pt.Int(1) + + # verify correct usage doesn't cause an error + _ = pt.Seq(opup.ensure_budget(pt.Int(500) + pt.Int(1000)), pt.Return(pt.Int(1))) + + _ = pt.Seq(opup.maximize_budget(pt.Txn.fee() - pt.Int(100)), pt.Return(pt.Int(1))) + + +def test_opup_oncall(): + mode = OpUpMode.OnCall + opup = OpUp(mode) + + with pytest.raises(pt.TealTypeError): + opup.ensure_budget(pt.Bytes("budget")) + + with pytest.raises(pt.TealTypeError): + opup.maximize_budget(pt.Bytes("fee")) + + # verify correct usage doesn't cause an error + _ = pt.Seq(opup.ensure_budget(pt.Int(500) + pt.Int(1000)), pt.Return(pt.Int(1))) + + _ = pt.Seq(opup.maximize_budget(pt.Txn.fee() - pt.Int(100)), pt.Return(pt.Int(1))) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 0d526540e..51b9a1ca6 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -1,5 +1,6 @@ from dataclasses import dataclass -from inspect import isclass, Parameter, signature, Signature, get_annotations +from inspect import isclass, Parameter, signature, get_annotations +from types import MappingProxyType from typing import ( Callable, Optional, @@ -8,13 +9,13 @@ Any, ) -from pyteal.ast.return_ import Return from pyteal.errors import TealInputError, verifyTealVersion from pyteal.ir import TealOp, Op, TealBlock from pyteal.types import TealType from pyteal.ast import abi from pyteal.ast.expr import Expr +from pyteal.ast.return_ import Return from pyteal.ast.seq import Seq from pyteal.ast.scratchvar import DynamicScratchVar, ScratchVar @@ -23,6 +24,10 @@ class SubroutineDefinition: + """ + Class that leverages TEAL's `callsub` and `retsub` opcode-pair for subroutines + """ + nextSubroutineId = 0 def __init__( @@ -32,95 +37,47 @@ def __init__( name_str: Optional[str] = None, abi_output_arg_name: Optional[str] = None, ) -> None: + """ + Args: + implementation: The python function defining the subroutine + return_type: the TealType to be returned by the subroutine + name_str (optional): the name that is used to identify the subroutine. + If omitted, the name defaults to the implementation's __name__ attribute + abi_output_arg_name (optional): the name that is used to identify ABI output kwarg for subroutine. + """ super().__init__() self.id = SubroutineDefinition.nextSubroutineId SubroutineDefinition.nextSubroutineId += 1 - if not callable(implementation): - raise TealInputError("Input to SubroutineDefinition is not callable") - - sig = signature(implementation) - - annotations = get_annotations(implementation) - - if "return" in annotations and annotations["return"] is not Expr: - raise TealInputError( - f"Function has return of disallowed type {annotations['return']}. Only Expr is allowed" - ) - - # validate full signature takes following two arguments: - # - `signature`, which contains the signature of the python function. - # NOTE: it contains all the arguments, we get type annotations from `annotations`. - # - `annotations`, which contains all available argument type annotations and return type annotation. - # NOTE: `annotations` does not contain all the arguments, - # an argument is not included in `annotations` if its type annotation is not available. - ( - expected_arg_types, - by_ref_args, - abi_args, - abi_output_kwarg, - ) = self._arg_types_and_by_refs(sig, annotations, abi_output_arg_name) - self.expected_arg_types: list[ - type[Expr] | type[ScratchVar] | abi.TypeSpec - ] = expected_arg_types - self.by_ref_args: set[str] = by_ref_args - self.abi_args: dict[str, abi.TypeSpec] = abi_args - self.output_kwarg: dict[str, abi.TypeSpec] = abi_output_kwarg - - self.implementation = implementation - self.implementation_params = sig.parameters self.return_type = return_type - self.declaration: Optional["SubroutineDeclaration"] = None - self.__name = self.implementation.__name__ if name_str is None else name_str - @staticmethod - def _is_abi_annotation(obj: Any) -> bool: - try: - abi.type_spec_from_annotation(obj) - return True - except TypeError: - return False + self.implementation: Callable = implementation + self.abi_output_arg_name: Optional[str] = abi_output_arg_name - @staticmethod - def _validate_annotation( - user_defined_annotations: dict[str, Any], parameter_name: str - ) -> type[Expr] | type[ScratchVar] | abi.TypeSpec: - ptype = user_defined_annotations.get(parameter_name, None) + self.implementation_params: MappingProxyType[str, Parameter] + self.annotations: dict[str, type] + self.expected_arg_types: list[type[Expr] | type[ScratchVar] | abi.TypeSpec] + self.by_ref_args: set[str] + self.abi_args: dict[str, abi.TypeSpec] + self.output_kwarg: dict[str, abi.TypeSpec] - if ptype is None: - # Without a type annotation, `SubroutineDefinition` presumes an implicit `Expr` declaration - # rather than these alternatives: - # * Throw error requiring type annotation. - # * Defer parameter type checks until arguments provided during invocation. - # - # * Rationale: - # * Provide an upfront, best-effort type check before invocation. - # * Preserve backwards compatibility with TEAL programs written - # when `Expr` is the only supported annotation type. - # * `invoke` type checks provided arguments against parameter types to catch mismatches. - return Expr - elif ptype in (Expr, ScratchVar): - return ptype - elif SubroutineDefinition._is_abi_annotation(ptype): - return abi.type_spec_from_annotation(ptype) - else: - if not isclass(ptype): - raise TealInputError( - f"Function has parameter {parameter_name} of declared type {ptype} which is not a class" - ) - raise TealInputError( - f"Function has parameter {parameter_name} of disallowed type {ptype}. " - f"Only the types {(Expr, ScratchVar, 'ABI')} are allowed" - ) - - @classmethod - def _arg_types_and_by_refs( - cls, - sig: Signature, - annotations: dict[str, type], - abi_output_arg_name: Optional[str] = None, + ( + self.implementation_params, + self.annotations, + self.expected_arg_types, + self.by_ref_args, + self.abi_args, + self.output_kwarg, + ) = self._validate() + + self.__name: str = name_str if name_str else self.implementation.__name__ + + def _validate( + self, input_types: list[TealType] = None ) -> tuple[ + MappingProxyType[str, Parameter], + dict[str, type], list[type[Expr] | type[ScratchVar] | abi.TypeSpec], set[str], dict[str, abi.TypeSpec], @@ -147,24 +104,58 @@ def _arg_types_and_by_refs( We load the ABI scratch space stored value to stack, and store them later in subroutine's local ABI values. Args: - sig: containing the signature of the python function for subroutine definition. - NOTE: it contains all the arguments, we obtain type annotations from `annotations`. - annotations: all available argument type annotations and return type annotation. - NOTE: `annotations` does not contain all the arguments, - an argument is not included in `annotations` if its type annotation is not available. + input_types (optional): for testing purposes - expected `TealType`s of each parameter + Returns: + impl_params: a map from python function implementation's argument name, to argument's parameter. + annotations: a dict whose keys are names of type-annotated arguments, + and values are appearing type-annotations. + arg_types: a list of argument type inferred from python function implementation, + containing [type[Expr]| type[ScratchVar] | abi.TypeSpec]. + by_ref_args: a list of argument names that are passed in Subroutine with by-reference mechanism. + abi_args: a dict whose keys are names of ABI arguments, and values are their ABI type-specs. + abi_output_kwarg (might be empty): a dict whose key is the name of ABI output keyword argument, + and the value is the corresponding ABI type-spec. + NOTE: this dict might be empty, when we are defining a normal subroutine, + but it has at most one element when we define an ABI-returning subroutine. """ - expected_arg_types = [] + + if not callable(self.implementation): + raise TealInputError("Input to SubroutineDefinition is not callable") + + impl_params: MappingProxyType[str, Parameter] = signature( + self.implementation + ).parameters + annotations: dict[str, type] = get_annotations(self.implementation) + arg_types: list[type[Expr] | type[ScratchVar] | abi.TypeSpec] = [] by_ref_args: set[str] = set() abi_args: dict[str, abi.TypeSpec] = {} abi_output_kwarg: dict[str, abi.TypeSpec] = {} - for name, param in sig.parameters.items(): + + if input_types: + if len(input_types) != len(impl_params): + raise TealInputError( + f"Provided number of input_types ({len(input_types)}) " + f"does not match detected number of parameters ({len(impl_params)})" + ) + for in_type, name in zip(input_types, impl_params): + if not isinstance(in_type, TealType): + raise TealInputError( + f"Function has input type {in_type} for parameter {name} which is not a TealType" + ) + + if "return" in annotations and annotations["return"] is not Expr: + raise TealInputError( + f"Function has return of disallowed type {annotations['return']}. Only Expr is allowed" + ) + + for name, param in impl_params.items(): if param.kind not in ( Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD, ) and not ( param.kind is Parameter.KEYWORD_ONLY - and abi_output_arg_name is not None - and name == abi_output_arg_name + and self.abi_output_arg_name is not None + and name == self.abi_output_arg_name ): raise TealInputError( f"Function has a parameter type that is not allowed in a subroutine: " @@ -176,9 +167,7 @@ def _arg_types_and_by_refs( f"Function has a parameter with a default value, which is not allowed in a subroutine: {name}" ) - expected_arg_type = SubroutineDefinition._validate_annotation( - annotations, name - ) + expected_arg_type = self._validate_annotation(annotations, name) if param.kind is Parameter.KEYWORD_ONLY: if not isinstance(expected_arg_type, abi.TypeSpec): @@ -188,14 +177,58 @@ def _arg_types_and_by_refs( abi_output_kwarg[name] = expected_arg_type continue - expected_arg_types.append(expected_arg_type) - + arg_types.append(expected_arg_type) if expected_arg_type is ScratchVar: by_ref_args.add(name) if isinstance(expected_arg_type, abi.TypeSpec): abi_args[name] = expected_arg_type - return expected_arg_types, by_ref_args, abi_args, abi_output_kwarg + return ( + impl_params, + annotations, + arg_types, + by_ref_args, + abi_args, + abi_output_kwarg, + ) + + @staticmethod + def _is_abi_annotation(obj: Any) -> bool: + try: + abi.type_spec_from_annotation(obj) + return True + except TypeError: + return False + + @staticmethod + def _validate_annotation( + user_defined_annotations: dict[str, Any], parameter_name: str + ) -> type[Expr] | type[ScratchVar] | abi.TypeSpec: + ptype = user_defined_annotations.get(parameter_name, None) + if ptype is None: + # Without a type annotation, `SubroutineDefinition` presumes an implicit `Expr` declaration + # rather than these alternatives: + # * Throw error requiring type annotation. + # * Defer parameter type checks until arguments provided during invocation. + # + # * Rationale: + # * Provide an upfront, best-effort type check before invocation. + # * Preserve backwards compatibility with TEAL programs written + # when `Expr` is the only supported annotation type. + # * `invoke` type checks provided arguments against parameter types to catch mismatches. + return Expr + if ptype in (Expr, ScratchVar): + return ptype + if SubroutineDefinition._is_abi_annotation(ptype): + return abi.type_spec_from_annotation(ptype) + if not isclass(ptype): + raise TealInputError( + f"Function has parameter {parameter_name} of declared type {ptype} which is not a class" + ) + raise TealInputError( + f"Function has parameter {parameter_name} of disallowed type {ptype}. " + f"Only the types {(Expr, ScratchVar, 'ABI')} are allowed" + ) def get_declaration(self) -> "SubroutineDeclaration": if self.declaration is None: @@ -211,8 +244,9 @@ def argument_count(self) -> int: def arguments(self) -> list[str]: syntax_args = list(self.implementation_params.keys()) - for key in self.output_kwarg: - syntax_args.remove(key) + syntax_args = [ + arg_name for arg_name in syntax_args if arg_name not in self.output_kwarg + ] return syntax_args def invoke( diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index 3da4ec608..c71f21d6c 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -1,3 +1,4 @@ +from itertools import product from typing import List, Literal import pytest from dataclasses import dataclass @@ -177,6 +178,221 @@ def fn_2arg_1ret_with_expr( ) +def test_subroutine_definition_validate(): + """ + DFS through SubroutineDefinition.validate()'s logic + """ + + def mock_subroutine_definition(implementation, abi_output_arg_name=None): + mock = pt.SubroutineDefinition(lambda: pt.Return(pt.Int(1)), pt.TealType.uint64) + mock._validate() # haven't failed with dummy implementation + mock.implementation = implementation + mock.abi_output_arg_name = abi_output_arg_name + return mock + + not_callable = mock_subroutine_definition("I'm not callable") + with pytest.raises(pt.TealInputError) as tie: + not_callable._validate() + + assert tie.value == pt.TealInputError( + "Input to SubroutineDefinition is not callable" + ) + + # input_types: + + three_params = mock_subroutine_definition(lambda x, y, z: pt.Return(pt.Int(1))) + two_inputs = [pt.TealType.uint64, pt.TealType.bytes] + with pytest.raises(pt.TealInputError) as tie: + three_params._validate(input_types=two_inputs) + + assert tie.value == pt.TealInputError( + "Provided number of input_types (2) does not match detected number of parameters (3)" + ) + + three_inputs_with_a_wrong_type = [pt.TealType.uint64, pt.Expr, pt.TealType.bytes] + + with pytest.raises(pt.TealInputError) as tie: + three_params._validate(input_types=three_inputs_with_a_wrong_type) + + assert tie.value == pt.TealInputError( + "Function has input type for parameter y which is not a TealType" + ) + + params, anns, arg_types, byrefs, abi_args, output_kwarg = three_params._validate() + assert len(params) == 3 + assert anns == {} + assert all(at is pt.Expr for at in arg_types) + assert byrefs == set() + assert abi_args == {} + assert output_kwarg == {} + + def bad_return_impl() -> str: + return pt.Return(pt.Int(1)) # type: ignore + + bad_return = mock_subroutine_definition(bad_return_impl) + with pytest.raises(pt.TealInputError) as tie: + bad_return._validate() + + assert tie.value == pt.TealInputError( + "Function has return of disallowed type . Only Expr is allowed" + ) + + # now we iterate through the implementation params validating each as we go + + def var_abi_output_impl(*, z: pt.abi.Uint16): + pt.Return(pt.Int(1)) # this is wrong but ignored + + # raises without abi_output_arg_name: + var_abi_output_noname = mock_subroutine_definition(var_abi_output_impl) + with pytest.raises(pt.TealInputError) as tie: + var_abi_output_noname._validate() + + assert tie.value == pt.TealInputError( + "Function has a parameter type that is not allowed in a subroutine: parameter z with type KEYWORD_ONLY" + ) + + # raises with wrong name + var_abi_output = mock_subroutine_definition( + var_abi_output_impl, abi_output_arg_name="foo" + ) + with pytest.raises(pt.TealInputError) as tie: + var_abi_output._validate() + + assert tie.value == pt.TealInputError( + "Function has a parameter type that is not allowed in a subroutine: parameter z with type KEYWORD_ONLY" + ) + + # copacetic abi output: + var_abi_output = mock_subroutine_definition( + var_abi_output_impl, abi_output_arg_name="z" + ) + params, anns, arg_types, byrefs, abi_args, output_kwarg = var_abi_output._validate() + assert len(params) == 1 + assert anns == {"z": pt.abi.Uint16} + assert all(at is pt.Expr for at in arg_types) + assert byrefs == set() + assert abi_args == {} + assert output_kwarg == {"z": pt.abi.Uint16TypeSpec()} + + var_positional = mock_subroutine_definition(lambda *args: pt.Return(pt.Int(1))) + with pytest.raises(pt.TealInputError) as tie: + var_positional._validate() + + assert tie.value == pt.TealInputError( + "Function has a parameter type that is not allowed in a subroutine: parameter args with type VAR_POSITIONAL" + ) + + kw_only = mock_subroutine_definition(lambda *, kw: pt.Return(pt.Int(1))) + with pytest.raises(pt.TealInputError) as tie: + kw_only._validate() + + assert tie.value == pt.TealInputError( + "Function has a parameter type that is not allowed in a subroutine: parameter kw with type KEYWORD_ONLY" + ) + + var_keyword = mock_subroutine_definition(lambda **kw: pt.Return(pt.Int(1))) + with pytest.raises(pt.TealInputError) as tie: + var_keyword._validate() + + assert tie.value == pt.TealInputError( + "Function has a parameter type that is not allowed in a subroutine: parameter kw with type VAR_KEYWORD" + ) + + param_default = mock_subroutine_definition(lambda x="niiiice": pt.Return(pt.Int(1))) + with pytest.raises(pt.TealInputError) as tie: + param_default._validate() + + assert tie.value == pt.TealInputError( + "Function has a parameter with a default value, which is not allowed in a subroutine: x" + ) + + with pytest.raises(pt.TealInputError) as tie: + three_params._validate( + input_types=[pt.TealType.uint64, pt.Expr, pt.TealType.anytype] + ) + + assert tie.value == pt.TealInputError( + "Function has input type for parameter y which is not a TealType" + ) + + # Now we get to _validate_annotation(): + one_vanilla = mock_subroutine_definition(lambda x: pt.Return(pt.Int(1))) + + params, anns, arg_types, byrefs, abi_args, output_kwarg = one_vanilla._validate() + assert len(params) == 1 + assert anns == {} + assert all(at is pt.Expr for at in arg_types) + assert byrefs == set() + assert abi_args == {} + assert output_kwarg == {} + + def one_expr_impl(x: pt.Expr): + return pt.Return(pt.Int(1)) + + one_expr = mock_subroutine_definition(one_expr_impl) + params, anns, arg_types, byrefs, abi_args, output_kwarg = one_expr._validate() + assert len(params) == 1 + assert anns == {"x": pt.Expr} + assert all(at is pt.Expr for at in arg_types) + assert byrefs == set() + assert abi_args == {} + assert output_kwarg == {} + + def one_scratchvar_impl(x: pt.ScratchVar): + return pt.Return(pt.Int(1)) + + one_scratchvar = mock_subroutine_definition(one_scratchvar_impl) + params, anns, arg_types, byrefs, abi_args, output_kwarg = one_scratchvar._validate() + assert len(params) == 1 + assert anns == {"x": pt.ScratchVar} + assert all(at is pt.ScratchVar for at in arg_types) + assert byrefs == {"x"} + assert abi_args == {} + assert output_kwarg == {} + + # for _is_abi_annotation() cf. copacetic x,y,z product below + + # not is_class() + def one_nontype_impl(x: "blahBlah"): # type: ignore # noqa: F821 + return pt.Return(pt.Int(1)) + + one_nontype = mock_subroutine_definition(one_nontype_impl) + with pytest.raises(pt.TealInputError) as tie: + one_nontype._validate() + + assert tie.value == pt.TealInputError( + "Function has parameter x of declared type blahBlah which is not a class" + ) + + def one_dynscratchvar_impl(x: pt.DynamicScratchVar): + return pt.Return(pt.Int(1)) + + one_dynscratchvar = mock_subroutine_definition(one_dynscratchvar_impl) + with pytest.raises(pt.TealInputError) as tie: + one_dynscratchvar._validate() + + assert tie.value == pt.TealInputError( + "Function has parameter x of disallowed type . Only the types (, , 'ABI') are allowed" + ) + + # Now we're back to validate() and everything should be copacetic + for x, y, z in product(pt.TealType, pt.TealType, pt.TealType): + ( + params, + anns, + arg_types, + byrefs, + abi_args, + output_kwarg, + ) = three_params._validate(input_types=[x, y, z]) + assert len(params) == 3 + assert anns == {} + assert all(at is pt.Expr for at in arg_types) + assert byrefs == set() + assert abi_args == {} + assert output_kwarg == {} + + def test_subroutine_invocation_param_types(): def fnWithNoAnnotations(a, b): return pt.Return() @@ -758,8 +974,8 @@ def mySubroutine(): return returnValue definition = pt.SubroutineDefinition(mySubroutine, returnType) - declaration = evaluate_subroutine(definition) + assert isinstance(declaration, pt.SubroutineDeclaration) assert declaration.subroutine is definition @@ -792,8 +1008,8 @@ def mySubroutine(a1): return returnValue definition = pt.SubroutineDefinition(mySubroutine, returnType) - declaration = evaluate_subroutine(definition) + assert isinstance(declaration, pt.SubroutineDeclaration) assert declaration.subroutine is definition @@ -837,6 +1053,7 @@ def mySubroutine(a1, a2): definition = pt.SubroutineDefinition(mySubroutine, returnType) declaration = evaluate_subroutine(definition) + assert isinstance(declaration, pt.SubroutineDeclaration) assert declaration.subroutine is definition @@ -881,8 +1098,8 @@ def mySubroutine(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10): return returnValue definition = pt.SubroutineDefinition(mySubroutine, returnType) - declaration = evaluate_subroutine(definition) + assert isinstance(declaration, pt.SubroutineDeclaration) assert declaration.subroutine is definition diff --git a/pyteal/errors.py b/pyteal/errors.py index bb238d98e..03e597e2f 100644 --- a/pyteal/errors.py +++ b/pyteal/errors.py @@ -1,4 +1,4 @@ -from typing import Optional, TYPE_CHECKING +from typing import Any, Optional, TYPE_CHECKING if TYPE_CHECKING: from pyteal.ast import Expr @@ -33,6 +33,9 @@ def __init__(self, msg: str) -> None: def __str__(self) -> str: return self.message + def __eq__(self, other: Any) -> bool: + return type(other) is TealInputError and self.message == other.message + TealInputError.__module__ = "pyteal" diff --git a/pyteal/ir/tealblock_test.py b/pyteal/ir/tealblock_test.py index 4d215ccac..cd6bb1e29 100644 --- a/pyteal/ir/tealblock_test.py +++ b/pyteal/ir/tealblock_test.py @@ -1,4 +1,5 @@ from typing import NamedTuple, List + import pyteal as pt options = pt.CompileOptions() diff --git a/setup.py b/setup.py index c09becbd9..82fcbf054 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setuptools.setup( name="pyteal", - version="0.11.1", + version="0.12.1b1", author="Algorand", author_email="pypiservice@algorand.com", description="Algorand Smart Contracts in Python", @@ -21,10 +21,12 @@ "black==22.3.0", "flake8==4.0.1", "flake8-tidy-imports==4.6.0", - "mypy==0.942", + "graviton@git+https://github.com/algorand/graviton@5549e6227a819b9f6d346f407aed32f4976ec0b2", + "mypy==0.950", "pytest==7.1.1", "pytest-cov==3.0.0", "pytest-timeout==2.1.0", + "pytest-xdist==2.5.0", ], }, classifiers=[ diff --git a/tests/blackbox.py b/tests/blackbox.py new file mode 100644 index 000000000..81bca9f69 --- /dev/null +++ b/tests/blackbox.py @@ -0,0 +1,201 @@ +from typing import Callable + +from algosdk.v2client import algod + +from graviton import blackbox + +from pyteal import ( + Arg, + Btoi, + Bytes, + Expr, + Int, + Itob, + Len, + Log, + Mode, + ScratchVar, + Seq, + SubroutineFnWrapper, + TealType, + Txn, +) + +# ---- Clients ---- # + + +def algod_with_assertion(): + algod = _algod_client() + assert algod.status(), "algod.status() did not produce any results" + return algod + + +def _algod_client( + algod_address="http://localhost:4001", algod_token="a" * 64 +) -> algod.AlgodClient: + """Instantiate and return Algod client object.""" + return algod.AlgodClient(algod_token, algod_address) + + +# ---- Decorator ---- # + + +class BlackboxWrapper: + def __init__(self, subr: SubroutineFnWrapper, input_types: list[TealType]): + subr.subroutine._validate(input_types=input_types) + self.subroutine = subr + self.input_types = input_types + + def __call__(self, *args: Expr | ScratchVar, **kwargs) -> Expr: + return self.subroutine(*args, **kwargs) + + def name(self) -> str: + return self.subroutine.name() + + +def Blackbox(input_types: list[TealType]): + def decorator_blackbox(func: SubroutineFnWrapper): + return BlackboxWrapper(func, input_types) + + return decorator_blackbox + + +# ---- API ---- # + + +def mode_to_execution_mode(mode: Mode) -> blackbox.ExecutionMode: + if mode == Mode.Application: + return blackbox.ExecutionMode.Application + if mode == Mode.Signature: + return blackbox.ExecutionMode.Signature + + raise Exception(f"Unknown mode {mode} of type {type(mode)}") + + +def blackbox_pyteal(subr: BlackboxWrapper, mode: Mode) -> Callable[..., Expr]: + """Functor producing ready-to-compile PyTeal programs from annotated subroutines + + Args: + subr: annotated subroutine to wrap inside program. + Note: the `input_types` parameters should be supplied to @Subroutine() annotation + mode: type of program to produce: logic sig (Mode.Signature) or app (Mode.Application) + + Returns: + a function that called with no parameters -e.g. result()- + returns a PyTeal expression compiling to a ready-to-test TEAL program. + + The return type is callable in order to adhere to the API of blackbox tests. + + Generated TEAL code depends on the mode, subroutine input types, and subroutine output types. + * logic sigs: + * input received via `arg i` + * args are converted (cf. "input conversion" below) and passed to the subroutine + * subroutine output is not logged (log is not available) + * subroutine output is converted (cf "output conversion" below) + * apps: + * input received via `txna ApplicationArgs i` + * args are converted (cf. "input conversion" below) and passed to the subroutine + * subroutine output is logged after possible conversion (cf. "logging conversion") + * subroutine output is converted (cf "output conversion" below) + * input conversion: + * Empty input array: + do not read any args and call subroutine immediately + * arg of TealType.bytes and TealType.anytype: + read arg and pass to subroutine as is + * arg of TealType.uint64: + convert arg to int using Btoi() when received + * pass-by-ref ScratchVar arguments: + in addition to the above - + o store the arg (or converted arg) in a ScratchVar + o invoke the subroutine using this ScratchVar instead of the arg (or converted arg) + * output conversion: + * TealType.uint64: + provide subroutine's result to the top of the stack when exiting program + * TealType.bytes: + convert subroutine's result to the top of the stack to its length and then exit + * TealType.none or TealType.anytype: + push Int(1337) to the stack as it is either impossible (TealType.none), + or unknown at compile time (TealType.anytype) to convert to an Int + * logging conversion: + * TealType.uint64: + convert subroutine's output using Itob() and log the result + * TealType.bytes: + log the subroutine's result + * TealType.none or TealType.anytype: + log Itob(Int(1337)) as it is either impossible (TealType.none), + or unknown at compile time (TealType.anytype) how to convert to Bytes + + For illustrative examples of how to use this function please refer to the integration test file `graviton_test.py` and especially: + + * `blackbox_pyteal_example1()`: Using blackbox_pyteal() for a simple test of both an app and logic sig + * `blackbox_pyteal_example2()`: Using blackbox_pyteal() to make 400 assertions and generate a CSV report with 400 dryrun rows + * `blackbox_pyteal_example3()`: declarative Test Driven Development approach through Invariant's + """ + input_types = subr.input_types + assert ( + input_types is not None + ), "please provide input_types in your @Subroutine annotation (crucial for generating proper end-to-end testable PyTeal)" + + subdef = subr.subroutine.subroutine + arg_names = subdef.arguments() + + def arg_prep_n_call(i, p): + name = arg_names[i] + by_ref = name in subdef.by_ref_args + arg_expr = Txn.application_args[i] if mode == Mode.Application else Arg(i) + if p == TealType.uint64: + arg_expr = Btoi(arg_expr) + prep = None + arg_var = arg_expr + if by_ref: + arg_var = ScratchVar(p) + prep = arg_var.store(arg_expr) + return prep, arg_var + + def subr_caller(): + preps_n_calls = [*(arg_prep_n_call(i, p) for i, p in enumerate(input_types))] + preps, calls = zip(*preps_n_calls) if preps_n_calls else ([], []) + preps = [p for p in preps if p] + invocation = subr(*calls) + if preps: + return Seq(*(preps + [invocation])) + return invocation + + def make_return(e): + if e.type_of() == TealType.uint64: + return e + if e.type_of() == TealType.bytes: + return Len(e) + if e.type_of() == TealType.anytype: + x = ScratchVar(TealType.anytype) + return Seq(x.store(e), Int(1337)) + # TealType.none: + return Seq(e, Int(1337)) + + def make_log(e): + if e.type_of() == TealType.uint64: + return Log(Itob(e)) + if e.type_of() == TealType.bytes: + return Log(e) + return Log(Bytes("nada")) + + if mode == Mode.Signature: + + def approval(): + return make_return(subr_caller()) + + else: + + def approval(): + if subdef.return_type == TealType.none: + result = ScratchVar(TealType.uint64) + part1 = [subr_caller(), result.store(Int(1337))] + else: + result = ScratchVar(subdef.return_type) + part1 = [result.store(subr_caller())] + + part2 = [make_log(result.load()), make_return(result.load())] + return Seq(*(part1 + part2)) + + setattr(approval, "__name__", f"sem_{mode}_{subr.name()}") + return approval diff --git a/tests/compile_asserts.py b/tests/compile_asserts.py index 2a84448d3..0f682415b 100644 --- a/tests/compile_asserts.py +++ b/tests/compile_asserts.py @@ -3,20 +3,25 @@ from pyteal.compiler import compileTeal from pyteal.ir import Mode +PATH = Path.cwd() / "tests" / "unit" +FIXTURES = PATH / "teal" +GENERATED = PATH / "generated" -def compile_and_save(approval, version): - teal = Path.cwd() / "tests" / "teal" + +def compile_and_save(approval, version, test_name): compiled = compileTeal(approval(), mode=Mode.Application, version=version) name = approval.__name__ - with open(teal / (name + ".teal"), "w") as f: + tealdir = GENERATED / test_name + tealdir.mkdir(parents=True, exist_ok=True) + with open(tealdir / (name + ".teal"), "w") as f: f.write(compiled) print( f"""Successfuly tested approval program <<{name}>> having compiled it into {len(compiled)} characters. See the results in: -{teal} +{tealdir} """ ) - return teal, name, compiled + return tealdir, name, compiled def mismatch_ligature(expected, actual): @@ -55,15 +60,15 @@ def assert_teal_as_expected(path2actual, path2expected): """ -def assert_new_v_old(approve_func, version): - teal_dir, name, compiled = compile_and_save(approve_func, version) +def assert_new_v_old(approve_func, version, test_name): + tealdir, name, compiled = compile_and_save(approve_func, version, test_name) print( f"""Compilation resulted in TEAL program of length {len(compiled)}. -To view output SEE <{name}.teal> in ({teal_dir}) +To view output SEE <{name}.teal> in ({tealdir}) --------------""" ) - path2actual = teal_dir / (name + ".teal") - path2expected = teal_dir / (name + "_expected.teal") + path2actual = tealdir / (name + ".teal") + path2expected = FIXTURES / test_name / (name + ".teal") assert_teal_as_expected(path2actual, path2expected) diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/integration/graviton_test.py b/tests/integration/graviton_test.py new file mode 100644 index 000000000..b620962ff --- /dev/null +++ b/tests/integration/graviton_test.py @@ -0,0 +1,778 @@ +from itertools import product +from pathlib import Path +from typing import Any, Dict + +import pytest + +from pyteal import ( + Bytes, + Concat, + For, + If, + Int, + Mode, + ScratchVar, + Seq, + Subroutine, + SubroutineFnWrapper, + TealType, + compileTeal, +) + +from tests.compile_asserts import assert_teal_as_expected +from tests.blackbox import ( + Blackbox, + BlackboxWrapper, + algod_with_assertion, + blackbox_pyteal, + mode_to_execution_mode, +) + +from graviton.blackbox import ( + DryRunProperty as DRProp, + DryRunEncoder as Encoder, + DryRunExecutor, + DryRunInspector, + mode_has_property, +) + +from graviton.invariant import Invariant + +PATH = Path.cwd() / "tests" / "integration" +FIXTURES = PATH / "teal" +GENERATED = PATH / "generated" + +# TODO: remove these skips after the following issue has been fixed https://github.com/algorand/pyteal/issues/199 +STABLE_SLOT_GENERATION = False +SKIP_SCRATCH_ASSERTIONS = not STABLE_SLOT_GENERATION + +# ---- Helper ---- # + + +def wrap_compile_and_save( + subr, mode, version, assemble_constants, test_name, case_name +): + is_app = mode == Mode.Application + + # 1. PyTeal program Expr generation + approval = blackbox_pyteal(subr, mode) + + # 2. TEAL generation + teal = compileTeal( + approval(), mode, version=version, assembleConstants=assemble_constants + ) + tealfile = f'{"app" if is_app else "lsig"}_{case_name}.teal' + + tealdir = GENERATED / test_name + tealdir.mkdir(parents=True, exist_ok=True) + tealpath = tealdir / tealfile + with open(tealpath, "w") as f: + f.write(teal) + + print( + f"""subroutine {case_name}@{mode} generated TEAL. +saved to {tealpath}: +------- +{teal} +-------""" + ) + + return teal, is_app, tealfile + + +# ---- Subroutines for Blackbox Testing ---- # + + +@Blackbox(input_types=[]) +@Subroutine(TealType.uint64) +def exp(): + return Int(2) ** Int(10) + + +@Blackbox(input_types=[TealType.uint64]) +@Subroutine(TealType.none) +def square_byref(x: ScratchVar): + return x.store(x.load() * x.load()) + + +@Blackbox(input_types=[TealType.uint64]) +@Subroutine(TealType.uint64) +def square(x): + return x ** Int(2) + + +@Blackbox(input_types=[TealType.anytype, TealType.anytype]) +@Subroutine(TealType.none) +def swap(x: ScratchVar, y: ScratchVar): + z = ScratchVar(TealType.anytype) + return Seq( + z.store(x.load()), + x.store(y.load()), + y.store(z.load()), + ) + + +@Blackbox(input_types=[TealType.bytes, TealType.uint64]) +@Subroutine(TealType.bytes) +def string_mult(s: ScratchVar, n): + i = ScratchVar(TealType.uint64) + tmp = ScratchVar(TealType.bytes) + start = Seq(i.store(Int(1)), tmp.store(s.load()), s.store(Bytes(""))) + step = i.store(i.load() + Int(1)) + return Seq( + For(start, i.load() <= n, step).Do(s.store(Concat(s.load(), tmp.load()))), + s.load(), + ) + + +@Blackbox(input_types=[TealType.uint64]) +@Subroutine(TealType.uint64) +def oldfac(n): + return If(n < Int(2)).Then(Int(1)).Else(n * oldfac(n - Int(1))) + + +@Blackbox(input_types=[TealType.uint64]) +@Subroutine(TealType.uint64) +def slow_fibonacci(n): + return ( + If(n <= Int(1)) + .Then(n) + .Else(slow_fibonacci(n - Int(2)) + slow_fibonacci(n - Int(1))) + ) + + +def fac_with_overflow(n): + if n < 2: + return 1 + if n > 20: + return 2432902008176640000 + return n * fac_with_overflow(n - 1) + + +def fib(n): + a, b = 0, 1 + for _ in range(n): + a, b = b, a + b + return a + + +def fib_cost(args): + cost = 17 + for n in range(1, args[0] + 1): + cost += 31 * fib(n - 1) + return cost + + +# ---- Blackbox pure unit tests (Skipping for now due to flakiness) ---- # + + +@pytest.mark.skipif(not STABLE_SLOT_GENERATION, reason="cf. #199") +@pytest.mark.parametrize( + "subr, mode", + product( + [exp, square_byref, square, swap, string_mult, oldfac, slow_fibonacci], + [Mode.Application, Mode.Signature], + ), +) +def test_stable_teal_generation(subr, mode): + """ + TODO: here's an example of issue #199 at play - need to run a dynamic version of `git bisect` + to figure out what is driving this + """ + case_name = subr.name() + print(f"stable TEAL generation test for {case_name} in mode {mode}") + + _, _, tealfile = wrap_compile_and_save(subr, mode, 6, True, "stability", case_name) + path2actual = GENERATED / "stability" / tealfile + path2expected = FIXTURES / "stability" / tealfile + assert_teal_as_expected(path2actual, path2expected) + + +APP_SCENARIOS = { + exp: { + "inputs": [()], + # since only a single input, just assert a constant in each case + "assertions": { + DRProp.cost: 11, + # int assertions on log outputs need encoding to varuint-hex: + DRProp.lastLog: Encoder.hex(2**10), + # dicts have a special meaning as assertions. So in the case of "finalScratch" + # which is supposed to _ALSO_ output a dict, we need to use a lambda as a work-around + DRProp.finalScratch: lambda _: {0: 1024}, + DRProp.stackTop: 1024, + DRProp.maxStackHeight: 2, + DRProp.status: "PASS", + DRProp.passed: True, + DRProp.rejected: False, + DRProp.errorMessage: None, + }, + }, + square_byref: { + "inputs": [(i,) for i in range(100)], + "assertions": { + DRProp.cost: lambda _, actual: 20 < actual < 22, + DRProp.lastLog: Encoder.hex(1337), + # due to dry-run artifact of not reporting 0-valued scratchvars, + # we have a special case for n=0: + DRProp.finalScratch: lambda args, actual: ( + {1, 1337, (args[0] ** 2 if args[0] else 1)} + ).issubset(set(actual.values())), + DRProp.stackTop: 1337, + DRProp.maxStackHeight: 3, + DRProp.status: "PASS", + DRProp.passed: True, + DRProp.rejected: False, + DRProp.errorMessage: None, + }, + }, + square: { + "inputs": [(i,) for i in range(100)], + "assertions": { + DRProp.cost: 14, + DRProp.lastLog: { + # since execution REJECTS for 0, expect last log for this case to be None + (i,): Encoder.hex(i * i) if i else None + for i in range(100) + }, + DRProp.finalScratch: lambda args: ( + {0: args[0] ** 2, 1: args[0]} if args[0] else {} + ), + DRProp.stackTop: lambda args: args[0] ** 2, + DRProp.maxStackHeight: 2, + DRProp.status: lambda i: "PASS" if i[0] > 0 else "REJECT", + DRProp.passed: lambda i: i[0] > 0, + DRProp.rejected: lambda i: i[0] == 0, + DRProp.errorMessage: None, + }, + }, + swap: { + "inputs": [(1, 2), (1, "two"), ("one", 2), ("one", "two")], + "assertions": { + DRProp.cost: 27, + DRProp.lastLog: Encoder.hex(1337), + DRProp.finalScratch: lambda args: { + 0: 1337, + 1: Encoder.hex0x(args[1]), + 2: Encoder.hex0x(args[0]), + 3: 1, + 4: 2, + 5: Encoder.hex0x(args[0]), + }, + DRProp.stackTop: 1337, + DRProp.maxStackHeight: 2, + DRProp.status: "PASS", + DRProp.passed: True, + DRProp.rejected: False, + DRProp.errorMessage: None, + }, + }, + string_mult: { + "inputs": [("xyzw", i) for i in range(100)], + "assertions": { + DRProp.cost: lambda args: 30 + 15 * args[1], + DRProp.lastLog: ( + lambda args: Encoder.hex(args[0] * args[1]) if args[1] else None + ), + # due to dryrun 0-scratchvar artifact, special case for i == 0: + DRProp.finalScratch: lambda args: ( + { + 0: Encoder.hex0x(args[0] * args[1]), + 1: Encoder.hex0x(args[0] * args[1]), + 2: 1, + 3: args[1], + 4: args[1] + 1, + 5: Encoder.hex0x(args[0]), + } + if args[1] + else { + 2: 1, + 4: args[1] + 1, + 5: Encoder.hex0x(args[0]), + } + ), + DRProp.stackTop: lambda args: len(args[0] * args[1]), + DRProp.maxStackHeight: lambda args: 3 if args[1] else 2, + DRProp.status: lambda args: ("PASS" if 0 < args[1] < 45 else "REJECT"), + DRProp.passed: lambda args: 0 < args[1] < 45, + DRProp.rejected: lambda args: 0 >= args[1] or args[1] >= 45, + DRProp.errorMessage: None, + }, + }, + oldfac: { + "inputs": [(i,) for i in range(25)], + "assertions": { + DRProp.cost: lambda args, actual: ( + actual - 40 <= 17 * args[0] <= actual + 40 + ), + DRProp.lastLog: lambda args: ( + Encoder.hex(fac_with_overflow(args[0])) if args[0] < 21 else None + ), + DRProp.finalScratch: lambda args: ( + {1: args[0], 0: fac_with_overflow(args[0])} + if 0 < args[0] < 21 + else ( + {1: min(21, args[0])} + if args[0] + else {0: fac_with_overflow(args[0])} + ) + ), + DRProp.stackTop: lambda args: fac_with_overflow(args[0]), + DRProp.maxStackHeight: lambda args: max(2, 2 * args[0]), + DRProp.status: lambda args: "PASS" if args[0] < 21 else "REJECT", + DRProp.passed: lambda args: args[0] < 21, + DRProp.rejected: lambda args: args[0] >= 21, + DRProp.errorMessage: lambda args, actual: ( + actual is None if args[0] < 21 else "overflowed" in actual + ), + }, + }, + slow_fibonacci: { + "inputs": [(i,) for i in range(18)], + "assertions": { + DRProp.cost: lambda args: (fib_cost(args) if args[0] < 17 else 70_000), + DRProp.lastLog: lambda args: ( + Encoder.hex(fib(args[0])) if 0 < args[0] < 17 else None + ), + DRProp.finalScratch: lambda args, actual: ( + actual == {1: args[0], 0: fib(args[0])} + if 0 < args[0] < 17 + else (True if args[0] >= 17 else actual == {}) + ), + # we declare to "not care" about the top of the stack for n >= 17 + DRProp.stackTop: lambda args, actual: ( + actual == fib(args[0]) if args[0] < 17 else True + ), + # similarly, we don't care about max stack height for n >= 17 + DRProp.maxStackHeight: lambda args, actual: ( + actual == max(2, 2 * args[0]) if args[0] < 17 else True + ), + DRProp.status: lambda args: "PASS" if 0 < args[0] < 8 else "REJECT", + DRProp.passed: lambda args: 0 < args[0] < 8, + DRProp.rejected: lambda args: 0 >= args[0] or args[0] >= 8, + DRProp.errorMessage: lambda args, actual: ( + actual is None + if args[0] < 17 + else "dynamic cost budget exceeded" in actual + ), + }, + }, +} + +# NOTE: logic sig dry runs are missing some information when compared with app dry runs. +# Therefore, certain assertions don't make sense for logic sigs explaining why some of the below are commented out: +LOGICSIG_SCENARIOS = { + exp: { + "inputs": [()], + "assertions": { + # DRProp.cost: 11, + # DRProp.lastLog: Encoder.hex(2 ** 10), + DRProp.finalScratch: lambda _: {}, + DRProp.stackTop: 1024, + DRProp.maxStackHeight: 2, + DRProp.status: "PASS", + DRProp.passed: True, + DRProp.rejected: False, + DRProp.errorMessage: None, + }, + }, + square_byref: { + "inputs": [(i,) for i in range(100)], + "assertions": { + # DRProp.cost: lambda _, actual: 20 < actual < 22, + # DRProp.lastLog: Encoder.hex(1337), + # due to dry-run artifact of not reporting 0-valued scratchvars, + # we have a special case for n=0: + DRProp.finalScratch: lambda args: ( + {0: 1, 1: args[0] ** 2} if args[0] else {0: 1} + ), + DRProp.stackTop: 1337, + DRProp.maxStackHeight: 3, + DRProp.status: "PASS", + DRProp.passed: True, + DRProp.rejected: False, + DRProp.errorMessage: None, + }, + }, + square: { + "inputs": [(i,) for i in range(100)], + "assertions": { + # DRProp.cost: 14, + # DRProp.lastLog: {(i,): Encoder.hex(i * i) if i else None for i in range(100)}, + DRProp.finalScratch: lambda args: ({0: args[0]} if args[0] else {}), + DRProp.stackTop: lambda args: args[0] ** 2, + DRProp.maxStackHeight: 2, + DRProp.status: lambda i: "PASS" if i[0] > 0 else "REJECT", + DRProp.passed: lambda i: i[0] > 0, + DRProp.rejected: lambda i: i[0] == 0, + DRProp.errorMessage: None, + }, + }, + swap: { + "inputs": [(1, 2), (1, "two"), ("one", 2), ("one", "two")], + "assertions": { + # DRProp.cost: 27, + # DRProp.lastLog: Encoder.hex(1337), + DRProp.finalScratch: lambda args: { + 0: 3, + 1: 4, + 2: Encoder.hex0x(args[0]), + 3: Encoder.hex0x(args[1]), + 4: Encoder.hex0x(args[0]), + }, + DRProp.stackTop: 1337, + DRProp.maxStackHeight: 2, + DRProp.status: "PASS", + DRProp.passed: True, + DRProp.rejected: False, + DRProp.errorMessage: None, + }, + }, + string_mult: { + "inputs": [("xyzw", i) for i in range(100)], + "assertions": { + # DRProp.cost: lambda args: 30 + 15 * args[1], + # DRProp.lastLog: lambda args: Encoder.hex(args[0] * args[1]) if args[1] else None, + DRProp.finalScratch: lambda args: ( + { + 0: len(args[0]), + 1: args[1], + 2: args[1] + 1, + 3: Encoder.hex0x(args[0]), + 4: Encoder.hex0x(args[0] * args[1]), + } + if args[1] + else { + 0: len(args[0]), + 2: args[1] + 1, + 3: Encoder.hex0x(args[0]), + } + ), + DRProp.stackTop: lambda args: len(args[0] * args[1]), + DRProp.maxStackHeight: lambda args: 3 if args[1] else 2, + DRProp.status: lambda args: "PASS" if args[1] else "REJECT", + DRProp.passed: lambda args: bool(args[1]), + DRProp.rejected: lambda args: not bool(args[1]), + DRProp.errorMessage: None, + }, + }, + oldfac: { + "inputs": [(i,) for i in range(25)], + "assertions": { + # DRProp.cost: lambda args, actual: actual - 40 <= 17 * args[0] <= actual + 40, + # DRProp.lastLog: lambda args, actual: (actual is None) or (int(actual, base=16) == fac_with_overflow(args[0])), + DRProp.finalScratch: lambda args: ( + {0: min(args[0], 21)} if args[0] else {} + ), + DRProp.stackTop: lambda args: fac_with_overflow(args[0]), + DRProp.maxStackHeight: lambda args: max(2, 2 * args[0]), + DRProp.status: lambda args: "PASS" if args[0] < 21 else "REJECT", + DRProp.passed: lambda args: args[0] < 21, + DRProp.rejected: lambda args: args[0] >= 21, + DRProp.errorMessage: lambda args, actual: ( + actual is None + if args[0] < 21 + else "logic 0 failed at line 21: * overflowed" in actual + ), + }, + }, + slow_fibonacci: { + "inputs": [(i,) for i in range(18)], + "assertions": { + # DRProp.cost: fib_cost, + # DRProp.lastLog: fib_last_log, + # by returning True for n >= 15, we're declaring that we don't care about the scratchvar's for such cases: + DRProp.finalScratch: lambda args, actual: ( + actual == {0: args[0]} + if 0 < args[0] < 15 + else (True if args[0] else actual == {}) + ), + DRProp.stackTop: lambda args, actual: ( + actual == fib(args[0]) if args[0] < 15 else True + ), + DRProp.maxStackHeight: lambda args, actual: ( + actual == max(2, 2 * args[0]) if args[0] < 15 else True + ), + DRProp.status: lambda args: "PASS" if 0 < args[0] < 15 else "REJECT", + DRProp.passed: lambda args: 0 < args[0] < 15, + DRProp.rejected: lambda args: not (0 < args[0] < 15), + DRProp.errorMessage: lambda args, actual: ( + actual is None + if args[0] < 15 + else "dynamic cost budget exceeded" in actual + ), + }, + }, +} + + +def blackbox_test_runner( + subr: SubroutineFnWrapper, + mode: Mode, + scenario: Dict[str, Any], + version: int, + assemble_constants: bool = True, +): + case_name = subr.name() + print(f"blackbox test of {case_name} with mode {mode}") + exec_mode = mode_to_execution_mode(mode) + + # 0. Validations + assert isinstance(subr, BlackboxWrapper), f"unexpected subr type {type(subr)}" + assert isinstance(mode, Mode) + + # 1. Compile to TEAL + teal, _, tealfile = wrap_compile_and_save( + subr, mode, version, assemble_constants, "blackbox", case_name + ) + + # Fail fast in case algod is not configured: + algod = algod_with_assertion() + + # 2. validate dry run scenarios: + inputs, predicates = Invariant.inputs_and_invariants( + scenario, exec_mode, raw_predicates=True + ) + + # 3. execute dry run sequence: + execute = DryRunExecutor.execute_one_dryrun + inspectors = list(map(lambda a: execute(algod, teal, a, exec_mode), inputs)) + + # 4. Statistical report: + csvpath = GENERATED / "blackbox" / f"{tealfile}.csv" + with open(csvpath, "w") as f: + f.write(DryRunInspector.csv_report(inputs, inspectors)) + + print(f"Saved Dry Run CSV report to {csvpath}") + + # 5. Sequential assertions (if provided any) + for i, type_n_assertion in enumerate(predicates.items()): + dr_prop, predicate = type_n_assertion + + if SKIP_SCRATCH_ASSERTIONS and dr_prop == DRProp.finalScratch: + print("skipping scratch assertions because unstable slots produced") + continue + + assert mode_has_property(exec_mode, dr_prop) + + invariant = Invariant(predicate, name=f"{case_name}[{i}]@{mode}-{dr_prop}") + print(f"{i+1}. Assertion for {case_name}-{mode}: {dr_prop} <<{predicate}>>") + invariant.validates(dr_prop, inputs, inspectors) + + +# ---- Graviton / Blackbox tests ---- # + + +@pytest.mark.parametrize("subr, scenario", APP_SCENARIOS.items()) +def test_blackbox_subroutines_as_apps( + subr: SubroutineFnWrapper, + scenario: Dict[str, Any], +): + blackbox_test_runner(subr, Mode.Application, scenario, 6) + + +@pytest.mark.parametrize("subr, scenario", LOGICSIG_SCENARIOS.items()) +def test_blackbox_subroutines_as_logic_sigs( + subr: SubroutineFnWrapper, + scenario: Dict[str, Any], +): + blackbox_test_runner(subr, Mode.Signature, scenario, 6) + + +def blackbox_pyteal_example1(): + # Example 1: Using blackbox_pyteal for a simple test of both an app and logic sig: + from graviton.blackbox import DryRunEncoder, DryRunExecutor + + from pyteal import compileTeal, Int, Mode, Subroutine, TealType + from tests.blackbox import Blackbox, algod_with_assertion, blackbox_pyteal + + @Blackbox(input_types=[TealType.uint64]) + @Subroutine(TealType.uint64) + def square(x): + return x ** Int(2) + + # create pyteal app and logic sig approvals: + approval_app = blackbox_pyteal(square, Mode.Application) + approval_lsig = blackbox_pyteal(square, Mode.Signature) + + # compile the evaluated approvals to generate TEAL: + app_teal = compileTeal(approval_app(), Mode.Application, version=6) + lsig_teal = compileTeal(approval_lsig(), Mode.Signature, version=6) + + # provide args for evaluation (will compute x^2) + x = 9 + args = [x] + + # evaluate the programs + algod = algod_with_assertion() + app_result = DryRunExecutor.dryrun_app(algod, app_teal, args) + lsig_result = DryRunExecutor.dryrun_logicsig(algod, lsig_teal, args) + + # check to see that x^2 is at the top of the stack as expected + assert app_result.stack_top() == x**2, app_result.report( + args, "stack_top() gave unexpected results for app" + ) + assert lsig_result.stack_top() == x**2, lsig_result.report( + args, "stack_top() gave unexpected results for lsig" + ) + + # check to see that itob of x^2 has been logged (only for the app case) + assert app_result.last_log() == DryRunEncoder.hex(x**2), app_result.report( + args, "last_log() gave unexpected results from app" + ) + + +def blackbox_pyteal_example2(): + # Example 2: Using blackbox_pyteal to make 400 assertions and generate a CSV report with 400 dryrun rows + from itertools import product + import math + from pathlib import Path + import random + + from graviton.blackbox import DryRunExecutor, DryRunInspector + + from pyteal import ( + compileTeal, + For, + If, + Int, + Mod, + Mode, + ScratchVar, + Seq, + Subroutine, + TealType, + ) + + from tests.blackbox import Blackbox, algod_with_assertion, blackbox_pyteal + + # GCD via the Euclidean Algorithm (iterative version): + @Blackbox(input_types=[TealType.uint64, TealType.uint64]) + @Subroutine(TealType.uint64) + def euclid(x, y): + a = ScratchVar(TealType.uint64) + b = ScratchVar(TealType.uint64) + tmp = ScratchVar(TealType.uint64) + start = If(x < y, Seq(a.store(y), b.store(x)), Seq(a.store(x), b.store(y))) + cond = b.load() > Int(0) + step = Seq( + tmp.store(b.load()), b.store(Mod(a.load(), b.load())), a.store(tmp.load()) + ) + return Seq(For(start, cond, step).Do(Seq()), a.load()) + + # create approval PyTeal and compile it to TEAL: + euclid_app = blackbox_pyteal(euclid, Mode.Application) + euclid_app_teal = compileTeal(euclid_app(), Mode.Application, version=6) + + # generate a report with 400 = 20*20 dry run rows: + N = 20 + inputs = list( + product( + tuple(random.randint(0, 1000) for _ in range(N)), + tuple(random.randint(0, 1000) for _ in range(N)), + ) + ) + + # execute the dry-run sequence: + algod = algod_with_assertion() + + # assert that each result is that same as what Python's math.gcd() computes + inspectors = DryRunExecutor.dryrun_app_on_sequence(algod, euclid_app_teal, inputs) + for i, result in enumerate(inspectors): + args = inputs[i] + assert result.stack_top() == math.gcd(*args), result.report( + args, f"failed for {args}" + ) + + # save the CSV to ...current working directory.../euclid.csv + euclid_csv = DryRunInspector.csv_report(inputs, inspectors) + with open(Path.cwd() / "euclid.csv", "w") as f: + f.write(euclid_csv) + + +def blackbox_pyteal_example3(): + # Example 3: declarative Test Driven Development approach through Invariant's + from itertools import product + import math + import random + + from graviton.blackbox import ( + DryRunEncoder, + DryRunExecutor, + DryRunProperty as DRProp, + ) + from graviton.invariant import Invariant + + from pyteal import compileTeal, If, Int, Mod, Mode, Subroutine, TealType + + from tests.blackbox import Blackbox, algod_with_assertion, blackbox_pyteal + + # avoid flaky tests just in case I was wrong about the stack height invariant... + random.seed(42) + + # helper that will be used for scratch-slots invariant: + def is_subdict(x, y): + return all(k in y and x[k] == y[k] for k in x) + + predicates = { + # the program's log should be the hex encoding of Python's math.gcd: + DRProp.lastLog: lambda args: ( + DryRunEncoder.hex(math.gcd(*args)) if math.gcd(*args) else None + ), + # the program's scratch should contain math.gcd() at slot 0: + DRProp.finalScratch: lambda args, actual: is_subdict( + {0: math.gcd(*args)}, actual + ), + # the top of the stack should be math.gcd(): + DRProp.stackTop: lambda args: math.gcd(*args), + # Making the rather weak assertion that the max stack height is between 2 and 3*log2(max(args)): + DRProp.maxStackHeight: ( + lambda args, actual: 2 + <= actual + <= 3 * math.ceil(math.log2(max(args + (1,)))) + ), + # the program PASS'es exactly for non-0 math.gcd (3 variants): + DRProp.status: lambda args: "PASS" if math.gcd(*args) else "REJECT", + DRProp.passed: lambda args: bool(math.gcd(*args)), + DRProp.rejected: lambda args: not bool(math.gcd(*args)), + # the program never errors: + DRProp.errorMessage: None, + } + + # Define a scenario 400 random pairs (x,y) as inputs: + N = 20 + inputs = list( + product( + tuple(random.randint(0, 1000) for _ in range(N)), + tuple(random.randint(0, 1000) for _ in range(N)), + ) + ) + + # GCD via the Euclidean Algorithm (recursive version): + @Blackbox(input_types=[TealType.uint64, TealType.uint64]) + @Subroutine(TealType.uint64) + def euclid(x, y): + return ( + If(x < y) + .Then(euclid(y, x)) + .Else(If(y == Int(0)).Then(x).Else(euclid(y, Mod(x, y)))) + ) + + # Generate PyTeal and TEAL for the recursive Euclidean algorithm: + euclid_app = blackbox_pyteal(euclid, Mode.Application) + euclid_app_teal = compileTeal(euclid_app(), Mode.Application, version=6) + + # Execute on the input sequence to get a dry-run inspectors: + algod = algod_with_assertion() + inspectors = DryRunExecutor.dryrun_app_on_sequence(algod, euclid_app_teal, inputs) + + # Assert that each invariant holds on the sequences of inputs and dry-runs: + for property, predicate in predicates.items(): + Invariant(predicate).validates(property, inputs, inspectors) + + +@pytest.mark.parametrize( + "example", + [blackbox_pyteal_example1, blackbox_pyteal_example2, blackbox_pyteal_example3], +) +def test_blackbox_pyteal_examples(example): + example() diff --git a/tests/integration/teal/stability/app_exp.teal b/tests/integration/teal/stability/app_exp.teal new file mode 100644 index 000000000..6685b3a18 --- /dev/null +++ b/tests/integration/teal/stability/app_exp.teal @@ -0,0 +1,15 @@ +#pragma version 6 +callsub exp_0 +store 0 +load 0 +itob +log +load 0 +return + +// exp +exp_0: +pushint 2 // 2 +pushint 10 // 10 +exp +retsub \ No newline at end of file diff --git a/tests/integration/teal/stability/app_oldfac.teal b/tests/integration/teal/stability/app_oldfac.teal new file mode 100644 index 000000000..b6d0cd54f --- /dev/null +++ b/tests/integration/teal/stability/app_oldfac.teal @@ -0,0 +1,34 @@ +#pragma version 6 +intcblock 1 +txna ApplicationArgs 0 +btoi +callsub oldfac_0 +store 0 +load 0 +itob +log +load 0 +return + +// oldfac +oldfac_0: +store 1 +load 1 +pushint 2 // 2 +< +bnz oldfac_0_l2 +load 1 +load 1 +intc_0 // 1 +- +load 1 +swap +callsub oldfac_0 +swap +store 1 +* +b oldfac_0_l3 +oldfac_0_l2: +intc_0 // 1 +oldfac_0_l3: +retsub \ No newline at end of file diff --git a/tests/integration/teal/stability/app_slow_fibonacci.teal b/tests/integration/teal/stability/app_slow_fibonacci.teal new file mode 100644 index 000000000..bc54d2d88 --- /dev/null +++ b/tests/integration/teal/stability/app_slow_fibonacci.teal @@ -0,0 +1,41 @@ +#pragma version 6 +intcblock 1 +txna ApplicationArgs 0 +btoi +callsub slowfibonacci_0 +store 0 +load 0 +itob +log +load 0 +return + +// slow_fibonacci +slowfibonacci_0: +store 1 +load 1 +intc_0 // 1 +<= +bnz slowfibonacci_0_l2 +load 1 +pushint 2 // 2 +- +load 1 +swap +callsub slowfibonacci_0 +swap +store 1 +load 1 +intc_0 // 1 +- +load 1 +swap +callsub slowfibonacci_0 +swap +store 1 ++ +b slowfibonacci_0_l3 +slowfibonacci_0_l2: +load 1 +slowfibonacci_0_l3: +retsub \ No newline at end of file diff --git a/tests/integration/teal/stability/app_square.teal b/tests/integration/teal/stability/app_square.teal new file mode 100644 index 000000000..11e703ceb --- /dev/null +++ b/tests/integration/teal/stability/app_square.teal @@ -0,0 +1,18 @@ +#pragma version 6 +txna ApplicationArgs 0 +btoi +callsub square_0 +store 0 +load 0 +itob +log +load 0 +return + +// square +square_0: +store 1 +load 1 +pushint 2 // 2 +exp +retsub \ No newline at end of file diff --git a/tests/integration/teal/stability/app_square_byref.teal b/tests/integration/teal/stability/app_square_byref.teal new file mode 100644 index 000000000..b6c39b9df --- /dev/null +++ b/tests/integration/teal/stability/app_square_byref.teal @@ -0,0 +1,25 @@ +#pragma version 6 +txna ApplicationArgs 0 +btoi +store 1 +pushint 1 // 1 +callsub squarebyref_0 +pushint 1337 // 1337 +store 0 +load 0 +itob +log +load 0 +return + +// square_byref +squarebyref_0: +store 2 +load 2 +load 2 +loads +load 2 +loads +* +stores +retsub \ No newline at end of file diff --git a/tests/integration/teal/stability/app_string_mult.teal b/tests/integration/teal/stability/app_string_mult.teal new file mode 100644 index 000000000..cf135fc8e --- /dev/null +++ b/tests/integration/teal/stability/app_string_mult.teal @@ -0,0 +1,47 @@ +#pragma version 6 +intcblock 1 +txna ApplicationArgs 0 +store 1 +intc_0 // 1 +txna ApplicationArgs 1 +btoi +callsub stringmult_0 +store 0 +load 0 +log +load 0 +len +return + +// string_mult +stringmult_0: +store 3 +store 2 +intc_0 // 1 +store 4 +load 2 +loads +store 5 +load 2 +pushbytes 0x // "" +stores +stringmult_0_l1: +load 4 +load 3 +<= +bz stringmult_0_l3 +load 2 +load 2 +loads +load 5 +concat +stores +load 4 +intc_0 // 1 ++ +store 4 +b stringmult_0_l1 +stringmult_0_l3: +load 2 +loads +retsub \ No newline at end of file diff --git a/tests/integration/teal/stability/app_swap.teal b/tests/integration/teal/stability/app_swap.teal new file mode 100644 index 000000000..bbcef87ff --- /dev/null +++ b/tests/integration/teal/stability/app_swap.teal @@ -0,0 +1,31 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 1 +txna ApplicationArgs 1 +store 2 +pushint 1 // 1 +pushint 2 // 2 +callsub swap_0 +pushint 1337 // 1337 +store 0 +load 0 +itob +log +load 0 +return + +// swap +swap_0: +store 4 +store 3 +load 3 +loads +store 5 +load 3 +load 4 +loads +stores +load 4 +load 5 +stores +retsub \ No newline at end of file diff --git a/tests/integration/teal/stability/lsig_exp.teal b/tests/integration/teal/stability/lsig_exp.teal new file mode 100644 index 000000000..e14669bf4 --- /dev/null +++ b/tests/integration/teal/stability/lsig_exp.teal @@ -0,0 +1,10 @@ +#pragma version 6 +callsub exp_0 +return + +// exp +exp_0: +pushint 2 // 2 +pushint 10 // 10 +exp +retsub \ No newline at end of file diff --git a/tests/integration/teal/stability/lsig_oldfac.teal b/tests/integration/teal/stability/lsig_oldfac.teal new file mode 100644 index 000000000..ed8ec0bf7 --- /dev/null +++ b/tests/integration/teal/stability/lsig_oldfac.teal @@ -0,0 +1,29 @@ +#pragma version 6 +intcblock 1 +arg 0 +btoi +callsub oldfac_0 +return + +// oldfac +oldfac_0: +store 0 +load 0 +pushint 2 // 2 +< +bnz oldfac_0_l2 +load 0 +load 0 +intc_0 // 1 +- +load 0 +swap +callsub oldfac_0 +swap +store 0 +* +b oldfac_0_l3 +oldfac_0_l2: +intc_0 // 1 +oldfac_0_l3: +retsub \ No newline at end of file diff --git a/tests/integration/teal/stability/lsig_slow_fibonacci.teal b/tests/integration/teal/stability/lsig_slow_fibonacci.teal new file mode 100644 index 000000000..a1d8df8e9 --- /dev/null +++ b/tests/integration/teal/stability/lsig_slow_fibonacci.teal @@ -0,0 +1,36 @@ +#pragma version 6 +intcblock 1 +arg 0 +btoi +callsub slowfibonacci_0 +return + +// slow_fibonacci +slowfibonacci_0: +store 0 +load 0 +intc_0 // 1 +<= +bnz slowfibonacci_0_l2 +load 0 +pushint 2 // 2 +- +load 0 +swap +callsub slowfibonacci_0 +swap +store 0 +load 0 +intc_0 // 1 +- +load 0 +swap +callsub slowfibonacci_0 +swap +store 0 ++ +b slowfibonacci_0_l3 +slowfibonacci_0_l2: +load 0 +slowfibonacci_0_l3: +retsub \ No newline at end of file diff --git a/tests/integration/teal/stability/lsig_square.teal b/tests/integration/teal/stability/lsig_square.teal new file mode 100644 index 000000000..a87bea8ae --- /dev/null +++ b/tests/integration/teal/stability/lsig_square.teal @@ -0,0 +1,13 @@ +#pragma version 6 +arg 0 +btoi +callsub square_0 +return + +// square +square_0: +store 0 +load 0 +pushint 2 // 2 +exp +retsub \ No newline at end of file diff --git a/tests/integration/teal/stability/lsig_square_byref.teal b/tests/integration/teal/stability/lsig_square_byref.teal new file mode 100644 index 000000000..bb2445ae6 --- /dev/null +++ b/tests/integration/teal/stability/lsig_square_byref.teal @@ -0,0 +1,20 @@ +#pragma version 6 +arg 0 +btoi +store 1 +pushint 1 // 1 +callsub squarebyref_0 +pushint 1337 // 1337 +return + +// square_byref +squarebyref_0: +store 0 +load 0 +load 0 +loads +load 0 +loads +* +stores +retsub \ No newline at end of file diff --git a/tests/integration/teal/stability/lsig_string_mult.teal b/tests/integration/teal/stability/lsig_string_mult.teal new file mode 100644 index 000000000..6f9cfcb71 --- /dev/null +++ b/tests/integration/teal/stability/lsig_string_mult.teal @@ -0,0 +1,43 @@ +#pragma version 6 +intcblock 1 +arg 0 +store 4 +pushint 4 // 4 +arg 1 +btoi +callsub stringmult_0 +len +return + +// string_mult +stringmult_0: +store 1 +store 0 +intc_0 // 1 +store 2 +load 0 +loads +store 3 +load 0 +pushbytes 0x // "" +stores +stringmult_0_l1: +load 2 +load 1 +<= +bz stringmult_0_l3 +load 0 +load 0 +loads +load 3 +concat +stores +load 2 +intc_0 // 1 ++ +store 2 +b stringmult_0_l1 +stringmult_0_l3: +load 0 +loads +retsub \ No newline at end of file diff --git a/tests/integration/teal/stability/lsig_swap.teal b/tests/integration/teal/stability/lsig_swap.teal new file mode 100644 index 000000000..c8d8ccd6d --- /dev/null +++ b/tests/integration/teal/stability/lsig_swap.teal @@ -0,0 +1,26 @@ +#pragma version 6 +arg 0 +store 3 +arg 1 +store 4 +pushint 3 // 3 +pushint 4 // 4 +callsub swap_0 +pushint 1337 // 1337 +return + +// swap +swap_0: +store 1 +store 0 +load 0 +loads +store 2 +load 0 +load 1 +loads +stores +load 1 +load 2 +stores +retsub \ No newline at end of file diff --git a/tests/teal/fac_by_ref_expected.teal b/tests/teal/fac_by_ref_expected.teal deleted file mode 100644 index c08bbef0f..000000000 --- a/tests/teal/fac_by_ref_expected.teal +++ /dev/null @@ -1,41 +0,0 @@ -#pragma version 6 // Case n = 1 Case n = 2 -int 42 // >1 >2 -store 0 // 0:1 0:2 -int 0 // >0 >0 -callsub factorial_0 // CALL CALL -load 0 // >1 >2 -return // RET(1!==1) RET(2!==1*2) - -// factorial -factorial_0: // *>0 *>0 *>2,0,1,2 -store 1 // 1: 0 1: 0 1: 2 -load 1 // >0 >0 *>2,0,1,2 -loads // >1 >2 *>2,0,1,1 -int 1 // >1,1 >2,1 *>2,0,1,1,1 -<= // *>1 *>2,0 *>2,0,1,1 -bnz factorial_0_l2 // BRANCH *>2 BRANCH -load 1 // *>2,0 -loads // *>2,2 -int 1 // *>2,2,1 -- // *>2,1 -store 2 // 2: 1 -int 2 // *>2,2 -load 1 // *>2,2,0 -load 2 // *>2,2,0,1 -uncover 2 // *>2,0,1,2 -callsub factorial_0 // CALL SRET >2,0,1 -store 2 // 2: 1 -store 1 // 1: 0 -load 1 // >2,0 -load 1 // >2,0,0 -loads // >2,0,2 -load 2 // >2,0,2,1 -* // >2,0,2 -stores // 0: 2 -b factorial_0_l3 // BRANCH -factorial_0_l2: // | *>2,0,1 -load 1 // *>0 | *>2,0,1,2 -int 1 // *>0,1 | *>2,0,1,2,1 -stores // 0: 1 | 2: 1 -factorial_0_l3: // >2 -retsub // SRET SRET>2 SRET*>2,0,1 \ No newline at end of file diff --git a/tests/teal/user_guide_snippet_dynamic_scratch_var_expected.teal b/tests/teal/user_guide_snippet_dynamic_scratch_var.teal similarity index 100% rename from tests/teal/user_guide_snippet_dynamic_scratch_var_expected.teal rename to tests/teal/user_guide_snippet_dynamic_scratch_var.teal diff --git a/tests/teal/user_guide_snippet_recursiveIsEven_expected.teal b/tests/teal/user_guide_snippet_recursiveIsEven.teal similarity index 100% rename from tests/teal/user_guide_snippet_recursiveIsEven_expected.teal rename to tests/teal/user_guide_snippet_recursiveIsEven.teal diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/unit/blackbox_test.py b/tests/unit/blackbox_test.py new file mode 100644 index 000000000..0cb71c80b --- /dev/null +++ b/tests/unit/blackbox_test.py @@ -0,0 +1,97 @@ +from itertools import product +from pathlib import Path +import pytest + +import pyteal as pt + +from tests.blackbox import Blackbox, blackbox_pyteal + +from tests.compile_asserts import assert_teal_as_expected + +PATH = Path.cwd() / "tests" / "unit" +FIXTURES = PATH / "teal" +GENERATED = PATH / "generated" + + +@Blackbox(input_types=[]) +@pt.Subroutine(pt.TealType.none) +def utest_noop(): + return pt.Pop(pt.Int(0)) + + +@Blackbox(input_types=[pt.TealType.uint64, pt.TealType.bytes, pt.TealType.anytype]) +@pt.Subroutine(pt.TealType.none) +def utest_noop_args(x, y, z): + return pt.Pop(pt.Int(0)) + + +@Blackbox(input_types=[]) +@pt.Subroutine(pt.TealType.uint64) +def utest_int(): + return pt.Int(0) + + +@Blackbox(input_types=[pt.TealType.uint64, pt.TealType.bytes, pt.TealType.anytype]) +@pt.Subroutine(pt.TealType.uint64) +def utest_int_args(x, y, z): + return pt.Int(0) + + +@Blackbox(input_types=[]) +@pt.Subroutine(pt.TealType.bytes) +def utest_bytes(): + return pt.Bytes("") + + +@Blackbox(input_types=[pt.TealType.uint64, pt.TealType.bytes, pt.TealType.anytype]) +@pt.Subroutine(pt.TealType.bytes) +def utest_bytes_args(x, y, z): + return pt.Bytes("") + + +@Blackbox(input_types=[]) +@pt.Subroutine(pt.TealType.anytype) +def utest_any(): + x = pt.ScratchVar(pt.TealType.anytype) + return pt.Seq(x.store(pt.Int(0)), x.load()) + + +@Blackbox(input_types=[pt.TealType.uint64, pt.TealType.bytes, pt.TealType.anytype]) +@pt.Subroutine(pt.TealType.anytype) +def utest_any_args(x, y, z): + x = pt.ScratchVar(pt.TealType.anytype) + return pt.Seq(x.store(pt.Int(0)), x.load()) + + +UNITS = [ + utest_noop, + utest_noop_args, + utest_int, + utest_int_args, + utest_bytes, + utest_bytes_args, + utest_any, + utest_any_args, +] + + +@pytest.mark.parametrize("subr, mode", product(UNITS, pt.Mode)) +def test_blackbox_pyteal(subr, mode): + """ + TODO: here's an example of issue #199 at play - (the thread-safety aspect): + compare the following! + % pytest -n 2 tests/unit/blackbox_test.py::test_blackbox_pyteal + vs + % pytest -n 1 tests/unit/blackbox_test.py::test_blackbox_pyteal + """ + is_app = mode == pt.Mode.Application + name = f"{'app' if is_app else 'lsig'}_{subr.name()}" + + compiled = pt.compileTeal(blackbox_pyteal(subr, mode)(), mode, version=6) + tealdir = GENERATED / "blackbox" + tealdir.mkdir(parents=True, exist_ok=True) + save_to = tealdir / (name + ".teal") + with open(save_to, "w") as f: + f.write(compiled) + + assert_teal_as_expected(save_to, FIXTURES / "blackbox" / (name + ".teal")) diff --git a/tests/compile_test.py b/tests/unit/compile_test.py similarity index 85% rename from tests/compile_test.py rename to tests/unit/compile_test.py index f6ba6d030..9f901da26 100644 --- a/tests/compile_test.py +++ b/tests/unit/compile_test.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path import pytest import pyteal as pt @@ -11,9 +11,8 @@ def test_basic_bank(): "ZZAF5ARA4MEC5PVDOP64JM5O5MQST63Q2KOY2FLYFLXXD3PFSNJJBYAFZM" ) - target_path = os.path.join( - os.path.dirname(__file__), "../examples/signature/basic.teal" - ) + target_path = Path.cwd() / "examples" / "signature" / "basic.teal" + with open(target_path, "r") as target_file: target = "".join(target_file.readlines()).strip() assert pt.compileTeal(program, mode=pt.Mode.Signature, version=3) == target @@ -24,9 +23,8 @@ def test_atomic_swap(): program = htlc() - target_path = os.path.join( - os.path.dirname(__file__), "../examples/signature/atomic_swap.teal" - ) + target_path = Path.cwd() / "examples" / "signature" / "atomic_swap.teal" + with open(target_path, "r") as target_file: target = "".join(target_file.readlines()).strip() assert pt.compileTeal(program, mode=pt.Mode.Signature, version=2) == target @@ -37,9 +35,7 @@ def test_periodic_payment(): program = periodic_payment() - target_path = os.path.join( - os.path.dirname(__file__), "../examples/signature/periodic_payment.teal" - ) + target_path = Path.cwd() / "examples" / "signature" / "periodic_payment.teal" with open(target_path, "r") as target_file: target = "".join(target_file.readlines()).strip() assert pt.compileTeal(program, mode=pt.Mode.Signature, version=2) == target @@ -50,9 +46,8 @@ def test_split(): program = split() - target_path = os.path.join( - os.path.dirname(__file__), "../examples/signature/split.teal" - ) + target_path = Path.cwd() / "examples" / "signature" / "split.teal" + with open(target_path, "r") as target_file: target = "".join(target_file.readlines()).strip() assert pt.compileTeal(program, mode=pt.Mode.Signature, version=2) == target @@ -63,9 +58,8 @@ def test_dutch_auction(): program = dutch_auction() - target_path = os.path.join( - os.path.dirname(__file__), "../examples/signature/dutch_auction.teal" - ) + target_path = Path.cwd() / "examples" / "signature" / "dutch_auction.teal" + with open(target_path, "r") as target_file: target = "".join(target_file.readlines()).strip() assert pt.compileTeal(program, mode=pt.Mode.Signature, version=2) == target @@ -76,9 +70,8 @@ def test_recurring_swap(): program = recurring_swap() - target_path = os.path.join( - os.path.dirname(__file__), "../examples/signature/recurring_swap.teal" - ) + target_path = Path.cwd() / "examples" / "signature" / "recurring_swap.teal" + with open(target_path, "r") as target_file: target = "".join(target_file.readlines()).strip() assert pt.compileTeal(program, mode=pt.Mode.Signature, version=2) == target diff --git a/tests/module_test.py b/tests/unit/module_test.py similarity index 100% rename from tests/module_test.py rename to tests/unit/module_test.py diff --git a/tests/pass_by_ref_test.py b/tests/unit/pass_by_ref_test.py similarity index 87% rename from tests/pass_by_ref_test.py rename to tests/unit/pass_by_ref_test.py index f92300caa..9c37dbafc 100644 --- a/tests/pass_by_ref_test.py +++ b/tests/unit/pass_by_ref_test.py @@ -2,7 +2,7 @@ import pyteal as pt -from .compile_asserts import assert_new_v_old +from tests.compile_asserts import assert_new_v_old # TODO: remove these skips when the following is fixed: https://github.com/algorand/pyteal/issues/199 STABLE_SLOT_GENERATION = False @@ -209,7 +209,7 @@ def oldfac(n): return pt.If(n < pt.Int(2)).Then(pt.Int(1)).Else(n * oldfac(n - pt.Int(1))) -CASES = ( +ISSUE_199_CASES = ( sub_logcat_dynamic, swapper, wilt_the_stilt, @@ -220,9 +220,9 @@ def oldfac(n): @pytest.mark.skipif(not STABLE_SLOT_GENERATION, reason="cf. #199") -@pytest.mark.parametrize("pt", CASES) +@pytest.mark.parametrize("pt", ISSUE_199_CASES) def test_teal_output_is_unchanged(pt): - assert_new_v_old(pt, 6) + assert_new_v_old(pt, 6, "unchanged") # #### pt.Subroutine Definitions for pass-by-ref guardrails testing ##### @@ -267,10 +267,10 @@ def not_ok_indirect2(x: pt.ScratchVar): Complex subroutine graph example: a --> b --> a (loop) - --> e + --> e --> f --> *c--> g --> a (loop) - --> h + --> h --> d --> d (loop) --> i --> j @@ -345,22 +345,6 @@ def tally(n, result: pt.ScratchVar): ) -@pt.Subroutine(pt.TealType.none) -def subr_string_mult(s: pt.ScratchVar, n): - tmp = pt.ScratchVar(pt.TealType.bytes) - return ( - pt.If(n == pt.Int(0)) - .Then(s.store(pt.Bytes(""))) - .Else( - pt.Seq( - tmp.store(s.load()), - subr_string_mult(s, n - pt.Int(1)), - s.store(pt.Concat(s.load(), tmp.load())), - ) - ) - ) - - @pt.Subroutine(pt.TealType.none) def factorial_BAD(n: pt.ScratchVar): tmp = pt.ScratchVar(pt.TealType.uint64) @@ -410,29 +394,59 @@ def plus_one(n: pt.ScratchVar): ) -# #### Approval PyTEAL Expressions (COPACETIC) ##### - -approval_ok = ok(pt.Int(42)) - -x_scratchvar = pt.ScratchVar(pt.TealType.uint64) - -approval_ok_byref = pt.Seq(x_scratchvar.store(pt.Int(42)), ok_byref(x_scratchvar)) +def make_creatable_factory(approval): + """ + Wrap a pyteal program with code that: + * approves immediately in the case of app creation (appId == 0) + * runs the original code otherwise + """ -approval_ok_indirect = ok_indirect1(pt.Int(42)) + def func(): + return ( + pt.If(pt.Txn.application_id() == pt.Int(0)).Then(pt.Int(1)).Else(approval()) + ) -# #### BANNED Approval PyTEAL Expressions (wrapped in a function) ##### + func.__name__ = approval.__name__ + return func -def approval_not_ok(): - return pt.Seq(x_scratchvar.store(pt.Int(42)), not_ok(x_scratchvar)) +def fac_by_ref(): + n = pt.ScratchVar(pt.TealType.uint64) + return pt.Seq( + n.store(pt.Int(10)), + factorial(n), + n.load(), + ) -def approval_not_ok_indirect(): - return pt.Seq(x_scratchvar.store(pt.Int(42)), not_ok_indirect1(x_scratchvar)) +def fac_by_ref_BAD(): + n = pt.ScratchVar(pt.TealType.uint64) + return pt.Seq( + n.store(pt.Int(10)), + factorial_BAD(n), + n.load(), + ) -def approval_its_complicated(): - return a(pt.Int(42)) +# Proved correct via blackbox testing, but BANNING for now +def fac_by_ref_args(): + n = pt.ScratchVar(pt.TealType.uint64) + return pt.Seq( + pt.If( + pt.Or( + pt.App.id() == pt.Int(0), + pt.Txn.application_args.length() == pt.Int(0), + ) + ) + .Then(pt.Int(1)) + .Else( + pt.Seq( + n.store(pt.Btoi(pt.Txn.application_args[0])), + factorial(n), + n.load(), + ) + ) + ) def tallygo(): @@ -455,6 +469,38 @@ def tallygo(): ) +TESTABLE_CASES = [(oldfac, [pt.TealType.uint64])] + + +# ---- Approval PyTEAL Expressions (COPACETIC) ---- # + +approval_ok = ok(pt.Int(42)) + +x_scratchvar = pt.ScratchVar(pt.TealType.uint64) + +approval_ok_byref = pt.Seq(x_scratchvar.store(pt.Int(42)), ok_byref(x_scratchvar)) + +approval_ok_indirect = ok_indirect1(pt.Int(42)) + +# ---- BANNED Approval PyTEAL Expressions (wrapped in a function) ---- # + + +@pt.Subroutine(pt.TealType.none) +def subr_string_mult(s: pt.ScratchVar, n): + tmp = pt.ScratchVar(pt.TealType.bytes) + return ( + pt.If(n == pt.Int(0)) + .Then(s.store(pt.Bytes(""))) + .Else( + pt.Seq( + tmp.store(s.load()), + subr_string_mult(s, n - pt.Int(1)), + s.store(pt.Concat(s.load(), tmp.load())), + ) + ) + ) + + def string_mult(): s = pt.ScratchVar(pt.TealType.bytes) return pt.Seq( @@ -465,42 +511,16 @@ def string_mult(): ) -def fac_by_ref_BAD(): - n = pt.ScratchVar(pt.TealType.uint64) - return pt.Seq( - n.store(pt.Int(10)), - factorial_BAD(n), - n.load(), - ) +def approval_not_ok(): + return pt.Seq(x_scratchvar.store(pt.Int(42)), not_ok(x_scratchvar)) -def fac_by_ref(): - n = pt.ScratchVar(pt.TealType.uint64) - return pt.Seq( - n.store(pt.Int(10)), - factorial(n), - n.load(), - ) +def approval_not_ok_indirect(): + return pt.Seq(x_scratchvar.store(pt.Int(42)), not_ok_indirect1(x_scratchvar)) -# Proved correct via blackbox testing, but BANNING for now -def fac_by_ref_args(): - n = pt.ScratchVar(pt.TealType.uint64) - return pt.Seq( - pt.If( - pt.Or( - pt.App.id() == pt.Int(0), pt.Txn.application_args.length() == pt.Int(0) - ) - ) - .Then(pt.Int(1)) - .Else( - pt.Seq( - n.store(pt.Btoi(pt.Txn.application_args[0])), - factorial(n), - n.load(), - ) - ) - ) +def approval_its_complicated(): + return a(pt.Int(42)) def increment(): @@ -510,29 +530,65 @@ def increment(): COPACETIC_APPROVALS = [approval_ok, approval_ok_byref, approval_ok_indirect] + +@pytest.mark.parametrize("approval", COPACETIC_APPROVALS) +def test_pass_by_ref_guardrails_COPACETIC(approval): + assert pt.compileTeal(approval, pt.Mode.Application, version=6) + + ILLEGAL_APPROVALS = { approval_not_ok: "not_ok()-->not_ok()", approval_not_ok_indirect: "not_ok_indirect1()-->not_ok_indirect2()-->not_ok_indirect1()", approval_its_complicated: "c()-->g()-->a()-->c()", - tallygo: "tally()-->tally()", - string_mult: "subr_string_mult()-->subr_string_mult()", fac_by_ref: "factorial()-->factorial()", - fac_by_ref_BAD: "factorial_BAD()-->factorial_BAD()", fac_by_ref_args: "factorial()-->factorial()", + fac_by_ref_BAD: "factorial_BAD()-->factorial_BAD()", increment: "plus_one()-->plus_one()", + string_mult: "subr_string_mult()-->subr_string_mult()", + tallygo: "tally()-->tally()", } -@pytest.mark.parametrize("approval", COPACETIC_APPROVALS) -def test_pass_by_ref_guardrails_COPACETIC(approval): - assert pt.compileTeal(approval, pt.Mode.Application, version=6) - - -@pytest.mark.parametrize("approval_func_n_suffix", ILLEGAL_APPROVALS.items()) -def test_pass_by_ref_guardrails_BANNED(approval_func_n_suffix): - approval, suffix = approval_func_n_suffix +@pytest.mark.parametrize("approval_func, suffix", ILLEGAL_APPROVALS.items()) +def test_pass_by_ref_guardrails_BANNED(approval_func, suffix): with pytest.raises(pt.TealInputError) as err: - pt.compileTeal(approval(), pt.Mode.Application, version=6) + pt.compileTeal(approval_func(), pt.Mode.Application, version=6) prefix = "ScratchVar arguments not allowed in recursive subroutines, but a recursive call-path was detected: " assert f"{prefix}{suffix}" in str(err) + + +def should_it_work() -> pt.Expr: + xs = [ + pt.ScratchVar(pt.TealType.uint64), + pt.ScratchVar(pt.TealType.uint64), + ] + + def store_initial_values(): + return [s.store(pt.Int(i + 1)) for i, s in enumerate(xs)] + + d = pt.DynamicScratchVar(pt.TealType.uint64) + + @pt.Subroutine(pt.TealType.none) + def retrieve_and_increment(s: pt.ScratchVar): + return pt.Seq(d.set_index(s), d.store(d.load() + pt.Int(1))) + + def asserts(): + return [pt.Assert(x.load() == pt.Int(i + 2)) for i, x in enumerate(xs)] + + return pt.Seq( + pt.Seq(store_initial_values()), + pt.Seq([retrieve_and_increment(x) for x in xs]), + pt.Seq(asserts()), + pt.Int(1), + ) + + +def test_cannot_set_index_with_dynamic(): + with pytest.raises(pt.TealInputError) as tie: + pt.compileTeal(should_it_work(), pt.Mode.Application, version=6) + + assert ( + "Only allowed to use ScratchVar objects for setting indices, but was given a" + in str(tie) + ) diff --git a/tests/unit/pre_v6_test.py b/tests/unit/pre_v6_test.py new file mode 100644 index 000000000..67e5efbe1 --- /dev/null +++ b/tests/unit/pre_v6_test.py @@ -0,0 +1,85 @@ +import pytest + +import pyteal as pt + +from tests.compile_asserts import assert_new_v_old + +# ---- TESTS FOR PyTEAL THAT PREDATE PASS-BY-REF - assert that changes to compiler don't affect the generated TEAL ---- # + + +@pt.Subroutine(pt.TealType.bytes) +def logcat(some_bytes, an_int): + catted = pt.ScratchVar(pt.TealType.bytes) + return pt.Seq( + catted.store(pt.Concat(some_bytes, pt.Itob(an_int))), + pt.Log(catted.load()), + catted.load(), + ) + + +def sub_logcat(): + return pt.Seq( + pt.Assert(logcat(pt.Bytes("hello"), pt.Int(42)) == pt.Bytes("hello42")), + pt.Int(1), + ) + + +@pt.Subroutine(pt.TealType.uint64) +def slow_fibonacci(n): + return ( + pt.If(n <= pt.Int(1)) + .Then(n) + .Else(slow_fibonacci(n - pt.Int(2)) + slow_fibonacci(n - pt.Int(1))) + ) + + +def sub_slowfib(): + return slow_fibonacci(pt.Int(3)) + + +@pt.Subroutine(pt.TealType.uint64) +def fast_fibonacci(n): + i = pt.ScratchVar(pt.TealType.uint64) + a = pt.ScratchVar(pt.TealType.uint64) + b = pt.ScratchVar(pt.TealType.uint64) + return pt.Seq( + a.store(pt.Int(0)), + b.store(pt.Int(1)), + pt.For(i.store(pt.Int(1)), i.load() <= n, i.store(i.load() + pt.Int(1))).Do( + pt.Seq( + b.store(a.load() + b.load()), + a.store(b.load() - a.load()), + ) + ), + a.load(), + ) + + +def sub_fastfib(): + return fast_fibonacci(pt.Int(3)) + + +@pt.Subroutine(pt.TealType.uint64) +def recursiveIsEven(i): + return ( + pt.If(i == pt.Int(0)) + .Then(pt.Int(1)) + .ElseIf(i == pt.Int(1)) + .Then(pt.Int(0)) + .Else(recursiveIsEven(i - pt.Int(2))) + ) + + +def sub_even(): + return pt.Seq( + pt.Pop(recursiveIsEven(pt.Int(1000))), + recursiveIsEven(pt.Int(1001)), + ) + + +PT_CASES = (sub_logcat, sub_slowfib, sub_fastfib, sub_even) + + +@pytest.mark.parametrize("pt_case", PT_CASES) +def test_old(pt_case): + assert_new_v_old(pt_case, 5, "pre_v6") diff --git a/tests/unit/teal/blackbox/app_utest_any.teal b/tests/unit/teal/blackbox/app_utest_any.teal new file mode 100644 index 000000000..a149e0cf8 --- /dev/null +++ b/tests/unit/teal/blackbox/app_utest_any.teal @@ -0,0 +1,16 @@ +#pragma version 6 +callsub utestany_0 +store 1 +byte "nada" +log +load 1 +store 2 +int 1337 +return + +// utest_any +utestany_0: +int 0 +store 0 +load 0 +retsub \ No newline at end of file diff --git a/tests/unit/teal/blackbox/app_utest_any_args.teal b/tests/unit/teal/blackbox/app_utest_any_args.teal new file mode 100644 index 000000000..2f3a9b418 --- /dev/null +++ b/tests/unit/teal/blackbox/app_utest_any_args.teal @@ -0,0 +1,23 @@ +#pragma version 6 +txna ApplicationArgs 0 +btoi +txna ApplicationArgs 1 +txna ApplicationArgs 2 +callsub utestanyargs_0 +store 4 +byte "nada" +log +load 4 +store 5 +int 1337 +return + +// utest_any_args +utestanyargs_0: +store 2 +store 1 +store 0 +int 0 +store 3 +load 3 +retsub \ No newline at end of file diff --git a/tests/unit/teal/blackbox/app_utest_bytes.teal b/tests/unit/teal/blackbox/app_utest_bytes.teal new file mode 100644 index 000000000..132223a91 --- /dev/null +++ b/tests/unit/teal/blackbox/app_utest_bytes.teal @@ -0,0 +1,13 @@ +#pragma version 6 +callsub utestbytes_0 +store 0 +load 0 +log +load 0 +len +return + +// utest_bytes +utestbytes_0: +byte "" +retsub \ No newline at end of file diff --git a/tests/unit/teal/blackbox/app_utest_bytes_args.teal b/tests/unit/teal/blackbox/app_utest_bytes_args.teal new file mode 100644 index 000000000..2e925bf39 --- /dev/null +++ b/tests/unit/teal/blackbox/app_utest_bytes_args.teal @@ -0,0 +1,20 @@ +#pragma version 6 +txna ApplicationArgs 0 +btoi +txna ApplicationArgs 1 +txna ApplicationArgs 2 +callsub utestbytesargs_0 +store 3 +load 3 +log +load 3 +len +return + +// utest_bytes_args +utestbytesargs_0: +store 2 +store 1 +store 0 +byte "" +retsub \ No newline at end of file diff --git a/tests/unit/teal/blackbox/app_utest_int.teal b/tests/unit/teal/blackbox/app_utest_int.teal new file mode 100644 index 000000000..71ecef4de --- /dev/null +++ b/tests/unit/teal/blackbox/app_utest_int.teal @@ -0,0 +1,13 @@ +#pragma version 6 +callsub utestint_0 +store 0 +load 0 +itob +log +load 0 +return + +// utest_int +utestint_0: +int 0 +retsub \ No newline at end of file diff --git a/tests/unit/teal/blackbox/app_utest_int_args.teal b/tests/unit/teal/blackbox/app_utest_int_args.teal new file mode 100644 index 000000000..5d74b6647 --- /dev/null +++ b/tests/unit/teal/blackbox/app_utest_int_args.teal @@ -0,0 +1,20 @@ +#pragma version 6 +txna ApplicationArgs 0 +btoi +txna ApplicationArgs 1 +txna ApplicationArgs 2 +callsub utestintargs_0 +store 3 +load 3 +itob +log +load 3 +return + +// utest_int_args +utestintargs_0: +store 2 +store 1 +store 0 +int 0 +retsub \ No newline at end of file diff --git a/tests/unit/teal/blackbox/app_utest_noop.teal b/tests/unit/teal/blackbox/app_utest_noop.teal new file mode 100644 index 000000000..ace6c48fd --- /dev/null +++ b/tests/unit/teal/blackbox/app_utest_noop.teal @@ -0,0 +1,15 @@ +#pragma version 6 +callsub utestnoop_0 +int 1337 +store 0 +load 0 +itob +log +load 0 +return + +// utest_noop +utestnoop_0: +int 0 +pop +retsub \ No newline at end of file diff --git a/tests/unit/teal/blackbox/app_utest_noop_args.teal b/tests/unit/teal/blackbox/app_utest_noop_args.teal new file mode 100644 index 000000000..7ecd69fab --- /dev/null +++ b/tests/unit/teal/blackbox/app_utest_noop_args.teal @@ -0,0 +1,22 @@ +#pragma version 6 +txna ApplicationArgs 0 +btoi +txna ApplicationArgs 1 +txna ApplicationArgs 2 +callsub utestnoopargs_0 +int 1337 +store 3 +load 3 +itob +log +load 3 +return + +// utest_noop_args +utestnoopargs_0: +store 2 +store 1 +store 0 +int 0 +pop +retsub \ No newline at end of file diff --git a/tests/unit/teal/blackbox/lsig_utest_any.teal b/tests/unit/teal/blackbox/lsig_utest_any.teal new file mode 100644 index 000000000..c3ed6ab75 --- /dev/null +++ b/tests/unit/teal/blackbox/lsig_utest_any.teal @@ -0,0 +1,12 @@ +#pragma version 6 +callsub utestany_0 +store 0 +int 1337 +return + +// utest_any +utestany_0: +int 0 +store 1 +load 1 +retsub \ No newline at end of file diff --git a/tests/unit/teal/blackbox/lsig_utest_any_args.teal b/tests/unit/teal/blackbox/lsig_utest_any_args.teal new file mode 100644 index 000000000..5e418a0b9 --- /dev/null +++ b/tests/unit/teal/blackbox/lsig_utest_any_args.teal @@ -0,0 +1,19 @@ +#pragma version 6 +arg 0 +btoi +arg 1 +arg 2 +callsub utestanyargs_0 +store 0 +int 1337 +return + +// utest_any_args +utestanyargs_0: +store 3 +store 2 +store 1 +int 0 +store 4 +load 4 +retsub \ No newline at end of file diff --git a/tests/unit/teal/blackbox/lsig_utest_bytes.teal b/tests/unit/teal/blackbox/lsig_utest_bytes.teal new file mode 100644 index 000000000..ee28e432e --- /dev/null +++ b/tests/unit/teal/blackbox/lsig_utest_bytes.teal @@ -0,0 +1,9 @@ +#pragma version 6 +callsub utestbytes_0 +len +return + +// utest_bytes +utestbytes_0: +byte "" +retsub \ No newline at end of file diff --git a/tests/unit/teal/blackbox/lsig_utest_bytes_args.teal b/tests/unit/teal/blackbox/lsig_utest_bytes_args.teal new file mode 100644 index 000000000..7318d379d --- /dev/null +++ b/tests/unit/teal/blackbox/lsig_utest_bytes_args.teal @@ -0,0 +1,16 @@ +#pragma version 6 +arg 0 +btoi +arg 1 +arg 2 +callsub utestbytesargs_0 +len +return + +// utest_bytes_args +utestbytesargs_0: +store 2 +store 1 +store 0 +byte "" +retsub \ No newline at end of file diff --git a/tests/unit/teal/blackbox/lsig_utest_int.teal b/tests/unit/teal/blackbox/lsig_utest_int.teal new file mode 100644 index 000000000..2526e6041 --- /dev/null +++ b/tests/unit/teal/blackbox/lsig_utest_int.teal @@ -0,0 +1,8 @@ +#pragma version 6 +callsub utestint_0 +return + +// utest_int +utestint_0: +int 0 +retsub \ No newline at end of file diff --git a/tests/unit/teal/blackbox/lsig_utest_int_args.teal b/tests/unit/teal/blackbox/lsig_utest_int_args.teal new file mode 100644 index 000000000..d84a9fb26 --- /dev/null +++ b/tests/unit/teal/blackbox/lsig_utest_int_args.teal @@ -0,0 +1,15 @@ +#pragma version 6 +arg 0 +btoi +arg 1 +arg 2 +callsub utestintargs_0 +return + +// utest_int_args +utestintargs_0: +store 2 +store 1 +store 0 +int 0 +retsub \ No newline at end of file diff --git a/tests/unit/teal/blackbox/lsig_utest_noop.teal b/tests/unit/teal/blackbox/lsig_utest_noop.teal new file mode 100644 index 000000000..10b0f6afe --- /dev/null +++ b/tests/unit/teal/blackbox/lsig_utest_noop.teal @@ -0,0 +1,10 @@ +#pragma version 6 +callsub utestnoop_0 +int 1337 +return + +// utest_noop +utestnoop_0: +int 0 +pop +retsub \ No newline at end of file diff --git a/tests/unit/teal/blackbox/lsig_utest_noop_args.teal b/tests/unit/teal/blackbox/lsig_utest_noop_args.teal new file mode 100644 index 000000000..724b89e3a --- /dev/null +++ b/tests/unit/teal/blackbox/lsig_utest_noop_args.teal @@ -0,0 +1,17 @@ +#pragma version 6 +arg 0 +btoi +arg 1 +arg 2 +callsub utestnoopargs_0 +int 1337 +return + +// utest_noop_args +utestnoopargs_0: +store 2 +store 1 +store 0 +int 0 +pop +retsub \ No newline at end of file diff --git a/tests/teal/sub_even_expected.teal b/tests/unit/teal/pre_v6/sub_even.teal similarity index 95% rename from tests/teal/sub_even_expected.teal rename to tests/unit/teal/pre_v6/sub_even.teal index 8cd15e13b..17d3e42b6 100644 --- a/tests/teal/sub_even_expected.teal +++ b/tests/unit/teal/pre_v6/sub_even.teal @@ -1,4 +1,4 @@ -#pragma version 6 +#pragma version 5 int 1000 callsub recursiveIsEven_0 pop diff --git a/tests/teal/sub_fastfib_expected.teal b/tests/unit/teal/pre_v6/sub_fastfib.teal similarity index 94% rename from tests/teal/sub_fastfib_expected.teal rename to tests/unit/teal/pre_v6/sub_fastfib.teal index dea466e36..e69e06add 100644 --- a/tests/teal/sub_fastfib_expected.teal +++ b/tests/unit/teal/pre_v6/sub_fastfib.teal @@ -1,4 +1,4 @@ -#pragma version 6 +#pragma version 5 int 3 callsub fastfibonacci_0 return diff --git a/tests/teal/sub_logcat_expected.teal b/tests/unit/teal/pre_v6/sub_logcat.teal similarity index 97% rename from tests/teal/sub_logcat_expected.teal rename to tests/unit/teal/pre_v6/sub_logcat.teal index 8acb5c4df..44fd39e8d 100644 --- a/tests/teal/sub_logcat_expected.teal +++ b/tests/unit/teal/pre_v6/sub_logcat.teal @@ -1,4 +1,4 @@ -#pragma version 6 +#pragma version 5 byte "hello" // >"hello" int 42 // >"hello",42 callsub logcat_0 // >"hello42" diff --git a/tests/teal/sub_slowfib_expected.teal b/tests/unit/teal/pre_v6/sub_slowfib.teal similarity index 94% rename from tests/teal/sub_slowfib_expected.teal rename to tests/unit/teal/pre_v6/sub_slowfib.teal index 53bf3cd0e..d0b08e6df 100644 --- a/tests/teal/sub_slowfib_expected.teal +++ b/tests/unit/teal/pre_v6/sub_slowfib.teal @@ -1,4 +1,4 @@ -#pragma version 6 +#pragma version 5 int 3 callsub slowfibonacci_0 return diff --git a/tests/teal/empty_scratches_expected.teal b/tests/unit/teal/unchanged/empty_scratches.teal similarity index 100% rename from tests/teal/empty_scratches_expected.teal rename to tests/unit/teal/unchanged/empty_scratches.teal diff --git a/tests/teal/lots_o_vars_expected.teal b/tests/unit/teal/unchanged/lots_o_vars.teal similarity index 100% rename from tests/teal/lots_o_vars_expected.teal rename to tests/unit/teal/unchanged/lots_o_vars.teal diff --git a/tests/teal/sub_logcat_dynamic_expected.teal b/tests/unit/teal/unchanged/sub_logcat_dynamic.teal similarity index 100% rename from tests/teal/sub_logcat_dynamic_expected.teal rename to tests/unit/teal/unchanged/sub_logcat_dynamic.teal diff --git a/tests/teal/sub_mixed_expected.teal b/tests/unit/teal/unchanged/sub_mixed.teal similarity index 100% rename from tests/teal/sub_mixed_expected.teal rename to tests/unit/teal/unchanged/sub_mixed.teal diff --git a/tests/teal/swapper_expected.teal b/tests/unit/teal/unchanged/swapper.teal similarity index 100% rename from tests/teal/swapper_expected.teal rename to tests/unit/teal/unchanged/swapper.teal diff --git a/tests/teal/wilt_the_stilt_expected.teal b/tests/unit/teal/unchanged/wilt_the_stilt.teal similarity index 100% rename from tests/teal/wilt_the_stilt_expected.teal rename to tests/unit/teal/unchanged/wilt_the_stilt.teal diff --git a/tests/unit/teal/user_guide/user_guide_snippet_dynamic_scratch_var.teal b/tests/unit/teal/user_guide/user_guide_snippet_dynamic_scratch_var.teal new file mode 100644 index 000000000..fe74bc0a3 --- /dev/null +++ b/tests/unit/teal/user_guide/user_guide_snippet_dynamic_scratch_var.teal @@ -0,0 +1,17 @@ +#pragma version 6 +int 0 +store 1 +int 7 +store 0 +load 1 +load 1 +loads +int 3 ++ +stores +load 0 +int 10 +== +assert +int 1 +return \ No newline at end of file diff --git a/tests/unit/teal/user_guide/user_guide_snippet_recursiveIsEven.teal b/tests/unit/teal/user_guide/user_guide_snippet_recursiveIsEven.teal new file mode 100644 index 000000000..61087d692 --- /dev/null +++ b/tests/unit/teal/user_guide/user_guide_snippet_recursiveIsEven.teal @@ -0,0 +1,32 @@ +#pragma version 6 +int 15 +callsub recursiveIsEven_0 +return + +// recursiveIsEven +recursiveIsEven_0: +store 0 +load 0 +int 0 +== +bnz recursiveIsEven_0_l4 +load 0 +int 1 +== +bnz recursiveIsEven_0_l3 +load 0 +int 2 +- +load 0 +swap +callsub recursiveIsEven_0 +swap +store 0 +b recursiveIsEven_0_l5 +recursiveIsEven_0_l3: +int 0 +b recursiveIsEven_0_l5 +recursiveIsEven_0_l4: +int 1 +recursiveIsEven_0_l5: +retsub \ No newline at end of file diff --git a/tests/unit/user_guide_test.py b/tests/unit/user_guide_test.py new file mode 100644 index 000000000..809e5137e --- /dev/null +++ b/tests/unit/user_guide_test.py @@ -0,0 +1,93 @@ +import pytest + +import pyteal as pt +from tests.compile_asserts import assert_new_v_old + + +def user_guide_snippet_dynamic_scratch_var() -> pt.Expr: + """ + The user guide docs use the test to illustrate `DynamicScratchVar` usage. If the test breaks, then the user guide docs must be updated along with the test. + """ + from pyteal import Assert, Int, DynamicScratchVar, ScratchVar, Seq, TealType + + s = ScratchVar(TealType.uint64) + d = DynamicScratchVar(TealType.uint64) + + return Seq( + d.set_index(s), + s.store(Int(7)), + d.store(d.load() + Int(3)), + Assert(s.load() == Int(10)), + Int(1), + ) + + +@pytest.mark.parametrize("snippet", [user_guide_snippet_dynamic_scratch_var]) +def test_user_guide_snippets(snippet): + assert_new_v_old(snippet, 6, "user_guide") + + +def user_guide_snippet_recursiveIsEven(): + from pyteal import If, Int, Subroutine, TealType + + @Subroutine(TealType.uint64) + def recursiveIsEven(i): + return ( + If(i == Int(0)) + .Then(Int(1)) + .ElseIf(i == Int(1)) + .Then(Int(0)) + .Else(recursiveIsEven(i - Int(2))) + ) + + return recursiveIsEven(Int(15)) + + +def user_guide_snippet_ILLEGAL_recursion(): + from pyteal import If, Int, ScratchVar, Seq, Subroutine, TealType + + @Subroutine(TealType.none) + def ILLEGAL_recursion(i: ScratchVar): + return ( + If(i.load() == Int(0)) + .Then(i.store(Int(1))) + .ElseIf(i.load() == Int(1)) + .Then(i.store(Int(0))) + .Else(Seq(i.store(i.load() - Int(2)), ILLEGAL_recursion(i))) + ) + + i = ScratchVar(TealType.uint64) + return Seq(i.store(Int(15)), ILLEGAL_recursion(i), Int(1)) + + +USER_GUIDE_SNIPPETS_COPACETIC = [ + user_guide_snippet_dynamic_scratch_var, + user_guide_snippet_recursiveIsEven, +] + + +@pytest.mark.parametrize("snippet", USER_GUIDE_SNIPPETS_COPACETIC) +def test_user_guide_snippets_good(snippet): + assert_new_v_old(snippet, 6, "user_guide") + + +USER_GUIDE_SNIPPETS_ERRORING = { + user_guide_snippet_ILLEGAL_recursion: ( + pt.TealInputError, + "ScratchVar arguments not allowed in recursive subroutines, but a recursive call-path was detected: ILLEGAL_recursion()-->ILLEGAL_recursion()", + ) +} + + +@pytest.mark.parametrize("snippet_etype_e", USER_GUIDE_SNIPPETS_ERRORING.items()) +def test_user_guide_snippets_bad(snippet_etype_e): + snippet, etype_e = snippet_etype_e + etype, e = etype_e + + print( + f"Test case function=[{snippet.__name__}]. Expecting error of type {etype} with message <{e}>" + ) + with pytest.raises(etype) as tie: + pt.compileTeal(snippet(), mode=pt.Mode.Application, version=6) + + assert e in str(tie) diff --git a/tests/user_guide_test.py b/tests/user_guide_test.py deleted file mode 100644 index feb309161..000000000 --- a/tests/user_guide_test.py +++ /dev/null @@ -1,84 +0,0 @@ -import pytest - -import pyteal as pt - -from .compile_asserts import assert_new_v_old - - -def user_guide_snippet_dynamic_scratch_var() -> pt.Expr: - """ - The user guide docs use the test to illustrate `pt.DynamicScratchVar` usage. pt.If the test breaks, then the user guide docs must be updated along with the test. - """ - - s = pt.ScratchVar(pt.TealType.uint64) - d = pt.DynamicScratchVar(pt.TealType.uint64) - - return pt.Seq( - d.set_index(s), - s.store(pt.Int(7)), - d.store(d.load() + pt.Int(3)), - pt.Assert(s.load() == pt.Int(10)), - pt.Int(1), - ) - - -def user_guide_snippet_recursiveIsEven(): - @pt.Subroutine(pt.TealType.uint64) - def recursiveIsEven(i): - return ( - pt.If(i == pt.Int(0)) - .Then(pt.Int(1)) - .ElseIf(i == pt.Int(1)) - .Then(pt.Int(0)) - .Else(recursiveIsEven(i - pt.Int(2))) - ) - - return recursiveIsEven(pt.Int(15)) - - -def user_guide_snippet_ILLEGAL_recursion(): - @pt.Subroutine(pt.TealType.none) - def ILLEGAL_recursion(i: pt.ScratchVar): - return ( - pt.If(i.load() == pt.Int(0)) - .Then(i.store(pt.Int(1))) - .ElseIf(i.load() == pt.Int(1)) - .Then(i.store(pt.Int(0))) - .Else(pt.Seq(i.store(i.load() - pt.Int(2)), ILLEGAL_recursion(i))) - ) - - i = pt.ScratchVar(pt.TealType.uint64) - return pt.Seq(i.store(pt.Int(15)), ILLEGAL_recursion(i), pt.Int(1)) - - -USER_GUIDE_SNIPPETS_COPACETIC = [ - user_guide_snippet_dynamic_scratch_var, - user_guide_snippet_recursiveIsEven, -] - - -@pytest.mark.parametrize("snippet", USER_GUIDE_SNIPPETS_COPACETIC) -def test_user_guide_snippets_good(snippet): - assert_new_v_old(snippet, 6) - - -USER_GUIDE_SNIPPETS_ERRORING = { - user_guide_snippet_ILLEGAL_recursion: ( - pt.TealInputError, - "ScratchVar arguments not allowed in recursive subroutines, but a recursive call-path was detected: ILLEGAL_recursion()-->ILLEGAL_recursion()", - ) -} - - -@pytest.mark.parametrize("snippet_etype_e", USER_GUIDE_SNIPPETS_ERRORING.items()) -def test_user_guide_snippets_bad(snippet_etype_e): - snippet, etype_e = snippet_etype_e - etype, e = etype_e - - print( - f"Test case function=[{snippet.__name__}]. Expecting error of type {etype} with message <{e}>" - ) - with pytest.raises(etype) as tie: - pt.compileTeal(snippet(), mode=pt.Mode.Application, version=6) - - assert e in str(tie) From d0737efe341b600f0b42d3dd5b96b4d08c6e5b31 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 3 May 2022 17:47:51 -0500 Subject: [PATCH 040/170] resolve conflicts --- .github/workflows/build.yml | 2 +- CHANGELOG.md | 5 +++++ Makefile | 3 ++- README.md | 3 +-- pyteal/ast/__init__.py | 2 +- pyteal/ast/subroutine.py | 2 +- setup.py | 2 +- 7 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b1ffbfbe1..593804daa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -95,7 +95,7 @@ jobs: upload-to-pypi: runs-on: ubuntu-20.04 container: python:3.10 - needs: ['build-test'] + needs: ['build-test', 'run-integration-tests', 'build-docset'] if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags') }} steps: - name: Check out code diff --git a/CHANGELOG.md b/CHANGELOG.md index c77a05892..a635985de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 0.12.1 + +## Fixed +* Resolve PyPi upload issue introduced in v0.12.0 ([#317](https://github.com/algorand/pyteal/pull/317)). + # 0.12.0 ## Added diff --git a/Makefile b/Makefile index 64ab76e30..17b8591af 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,8 @@ # ---- Setup ---- # setup-development: - pip install -e .[development] + pip install -e . + pip install -r requirements.txt setup-docs: setup-development pip install -r docs/requirements.txt diff --git a/README.md b/README.md index d96e4a031..3f4ac8585 100644 --- a/README.md +++ b/README.md @@ -57,8 +57,7 @@ Pip install PyTeal in editable state with dependencies: * `make setup-development` * OR if you don't have `make` installed: - * `pip install -e.[development]` - * Note, that if you're using `zsh` you'll need to escape the brackets: `pip install -e.\[development\]` + * `pip install -e . && pip install -r requirements.txt` Format code: diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index e13584043..81af8053e 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -102,7 +102,7 @@ from pyteal.ast.substring import Substring, Extract, Suffix # more ops -from pyteal.ast.naryexpr import NaryExpr, Add, Mul, And, Or, Concat +from pyteal.ast.naryexpr import NaryExpr, And, Or, Concat from pyteal.ast.widemath import WideRatio # control flow diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 51b9a1ca6..c916daee1 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -143,7 +143,7 @@ def _validate( f"Function has input type {in_type} for parameter {name} which is not a TealType" ) - if "return" in annotations and annotations["return"] is not Expr: + if "return" in anns and anns["return"] is not Expr: raise TealInputError( f"Function has return of disallowed type {annotations['return']}. Only Expr is allowed" ) diff --git a/setup.py b/setup.py index 82fcbf054..7cadf1b88 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setuptools.setup( name="pyteal", - version="0.12.1b1", + version="0.12.1", author="Algorand", author_email="pypiservice@algorand.com", description="Algorand Smart Contracts in Python", From 73b595f5ed64771a419795ba0dd97c3e07bd7146 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 3 May 2022 17:51:17 -0500 Subject: [PATCH 041/170] lint --- pyteal/ast/subroutine.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 4a7d83122..10c86396e 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -8,10 +8,8 @@ cast, Any, ) -from types import MappingProxyType from pyteal.ast import abi -from pyteal.ast.abi.type import TypeSpec from pyteal.ast.expr import Expr from pyteal.ast.return_ import Return from pyteal.ast.seq import Seq @@ -145,7 +143,7 @@ def _validate( f"Function has input type {in_type} for parameter {name} which is not a TealType" ) - if "return" in anns and anns["return"] is not Expr: + if "return" in annotations and annotations["return"] is not Expr: raise TealInputError( f"Function has return of disallowed type {annotations['return']}. Only Expr is allowed" ) From 7ace3fcc487e9e38e023821d2c1d7e40c99528bc Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 3 May 2022 18:53:20 -0400 Subject: [PATCH 042/170] minor reconstruct --- pyteal/ast/subroutine.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 5f3db5b77..10c86396e 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -8,10 +8,8 @@ cast, Any, ) -from types import MappingProxyType from pyteal.ast import abi -from pyteal.ast.abi.type import TypeSpec from pyteal.ast.expr import Expr from pyteal.ast.return_ import Return from pyteal.ast.seq import Seq From 386d57b8398e6728aa49e20899faa90fdc7e6ef3 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 3 May 2022 17:55:26 -0500 Subject: [PATCH 043/170] missing imports --- pyteal/ast/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py index 81af8053e..5394e7ff3 100644 --- a/pyteal/ast/__init__.py +++ b/pyteal/ast/__init__.py @@ -102,7 +102,7 @@ from pyteal.ast.substring import Substring, Extract, Suffix # more ops -from pyteal.ast.naryexpr import NaryExpr, And, Or, Concat +from pyteal.ast.naryexpr import NaryExpr, Add, And, Mul, Or, Concat from pyteal.ast.widemath import WideRatio # control flow From 939dea4e1c742c28e1e7ff809242b3cd18a38c80 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 3 May 2022 17:59:47 -0500 Subject: [PATCH 044/170] missing requirements from bad merge --- requirements.txt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..8735bd72e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,9 @@ +black==22.3.0 +flake8==4.0.1 +flake8-tidy-imports==4.6.0 +graviton@git+https://github.com/algorand/graviton@v0.1.0 +mypy==0.950 +pytest==7.1.1 +pytest-cov==3.0.0 +pytest-timeout==2.1.0 +pytest-xdist==2.5.0 From 6eacf07fc423cc3ade33f5944f83abfec3f8fbf8 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 3 May 2022 18:09:36 -0500 Subject: [PATCH 045/170] initial commit - failing --- tests/integration/abi_test.py | 169 ++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 tests/integration/abi_test.py diff --git a/tests/integration/abi_test.py b/tests/integration/abi_test.py new file mode 100644 index 000000000..eb02f8995 --- /dev/null +++ b/tests/integration/abi_test.py @@ -0,0 +1,169 @@ +""" +Placeholder for upcoming unit test +""" +from typing import Literal + +import pyteal as pt + +# ---- Simple Examples ---- # + + +@pt.ABIReturnSubroutine +def fn_0arg_0ret() -> pt.Expr: + return pt.Return() + + +@pt.ABIReturnSubroutine +def fn_0arg_uint64_ret(*, res: pt.abi.Uint64) -> pt.Expr: + return res.set(1) + + +@pt.ABIReturnSubroutine +def fn_1arg_0ret(a: pt.abi.Uint64) -> pt.Expr: + return pt.Return() + + +@pt.ABIReturnSubroutine +def fn_1arg_1ret(a: pt.abi.Uint64, *, out: pt.abi.Uint64) -> pt.Expr: + return out.set(a) + + +@pt.ABIReturnSubroutine +def fn_2arg_0ret( + a: pt.abi.Uint64, b: pt.abi.StaticArray[pt.abi.Byte, Literal[10]] +) -> pt.Expr: + return pt.Return() + + +@pt.ABIReturnSubroutine +def fn_2arg_1ret( + a: pt.abi.Uint64, + b: pt.abi.StaticArray[pt.abi.Byte, Literal[10]], + *, + out: pt.abi.Byte, +) -> pt.Expr: + return out.set(b[a.get() % pt.Int(10)]) + + +@pt.ABIReturnSubroutine +def fn_2arg_1ret_with_expr( + a: pt.Expr, + b: pt.abi.StaticArray[pt.abi.Byte, Literal[10]], + *, + out: pt.abi.Byte, +) -> pt.Expr: + return out.set(b[a % pt.Int(10)]) + + +# ---- doc test (headed into user_guide_test.py) + + +@pt.ABIReturnSubroutine +def abi_sum( + toSum: pt.abi.DynamicArray[pt.abi.Uint64], *, output: pt.abi.Uint64 +) -> pt.Expr: + i = pt.ScratchVar(pt.TealType.uint64) + valueAtIndex = pt.abi.Uint64() + return pt.Seq( + output.set(0), + pt.For( + i.store(pt.Int(0)), i.load() < toSum.length(), i.store(i.load() + pt.Int(1)) + ).Do( + pt.Seq( + toSum[i.load()].store_into(valueAtIndex), + output.set(output.get() + valueAtIndex.get()), + ) + ), + ) + + +# ---- subtraction example ---- # + + +import pyteal as pt + +Int65 = pt.abi.Tuple2[pt.abi.Bool, pt.abi.Uint64] + + +@pt.ABIReturnSubroutine +def minus(x: Int65, y: Int65, *, z: Int65): + x0 = pt.abi.Bool() + x1 = pt.abi.Uint64() + y0 = pt.abi.Bool() + y1 = pt.abi.Uint64() + z0 = pt.ScratchVar(pt.TealType.uint64) + z1 = pt.ScratchVar(pt.TealType.uint64) + return pt.Seq( + x0.set(x[0]), + x1.set(x[1]), + y0.set(y[0]), + y1.set(y[1]), + # Case I. x, y positive + pt.If(pt.And(x0.get(), y0.get())).Then( + pt.If(x1.get() >= y1.get()) + .Then( # +(x1 - y1) + pt.Seq(z0.store(pt.Int(1)), z1.Store(x1.get() - y1.get())) + ) + .Else( # -(y1 - x1) + pt.Seq(z0.store(pt.Int(0)), z1.Store(y1.get() - x1.get())) + ) + ) + # Case II. x positive, y negative + .ElseIf(pt.And(x0.get(), pt.Not(y0.get()))).Then( + pt.Seq(z0.store(pt.Int(1)), z1.Store(x1.get() + y1.get())) + ) # x1 + y1 + # Case III. x negative, y positive + .ElseIf(pt.And(pt.Not(x0.get()), y0.get())).Then( + pt.Seq(z0.store(pt.Int(0)), z1.Store(x1.get() + y1.get())) + ) # -(x1 + y1) + # Case IV. x, y negative + .Else( + pt.If(x1.get() >= y1.get()) + .Then( # -(x1 - y1) + pt.Seq(z0.store(pt.Int(0)), z1.Store(x1.get() - y1.get())) + ) + .Else( # +(y1 - x1) + pt.Seq(z0.store(pt.Int(1)), z1.Store(y1.get() - x1.get())) + ) + ), + z.set(z0.load(), z1.load()), + ) + + +def test_minus(): + program = Seq( + (to_sum_arr := abi.DynamicArray(abi.Uint64TypeSpec())).decode( + Txn.application_args[1] + ), + (res := abi.Uint64()).set(abi_sum(to_sum_arr)), + abi.MethodReturn(res), + Int(1), + ) + + teal = pt.compileTeal(minus, pt.Mode.Application, version=6) + + x = 42 + + +""" +so what does tests/blackbox.py::blackbox_abi() need to do? + +First decision points: +A) what happens when @Blackbox(input_types=...) is slapped on top of a @Subroutine with abi annotations? +B) do we need to allow (A), or do we just insist that user provides @ABIReturnSubroutine? +C) if we allow (A) [and even if not] should we still require input_types for the Expr's and ScratchVar's? +D) should we attempt to integrate Atomic Transaction Composer +.... leaning towards disallowing (A) for the lowest hanging fruit approach +.... leaning towards "No" to (D) because ATC doesn't play nice with Dry Run and we're only wrapping one + function at a time (not an app with several methods) + +Clear requirements. blackbox_abi() should: +1) handle apps and lsigs +2) handle Expr/ScratchVar/abi inputs (with the first two defined in input_types=...) +3) should handle output coming from the output_kwarg (but I believe this is automatic already) + +Convincing examples: +* matrix multiplication with very large number of bits to avoid overflow +* infinite precision signed integer that can handle addition, subtraction and multiplication +* complex/rational numbers with multiplication and addition, subtraction, multiplication and division +""" From c685f1b36d3808bfac6bbd13d74ab07920f8f18b Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Wed, 4 May 2022 09:03:18 -0500 Subject: [PATCH 046/170] user_guide_snippet_ABIReturnSubroutine() --- ...ser_guide_snippet_ABIReturnSubroutine.teal | 50 +++++++++++++++++++ tests/unit/user_guide_test.py | 42 ++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 tests/unit/teal/user_guide/user_guide_snippet_ABIReturnSubroutine.teal diff --git a/tests/unit/teal/user_guide/user_guide_snippet_ABIReturnSubroutine.teal b/tests/unit/teal/user_guide/user_guide_snippet_ABIReturnSubroutine.teal new file mode 100644 index 000000000..f2c8efb9b --- /dev/null +++ b/tests/unit/teal/user_guide/user_guide_snippet_ABIReturnSubroutine.teal @@ -0,0 +1,50 @@ +#pragma version 6 +txna ApplicationArgs 1 +store 0 +load 0 +callsub abisum_0 +store 1 +byte 0x151F7C75 +load 1 +itob +concat +log +int 1 +return + +// abi_sum +abisum_0: +store 2 +int 0 +store 3 +int 0 +store 4 +abisum_0_l1: +load 4 +load 2 +int 0 +extract_uint16 +store 6 +load 6 +< +bz abisum_0_l3 +load 2 +int 8 +load 4 +* +int 2 ++ +extract_uint64 +store 5 +load 3 +load 5 ++ +store 3 +load 4 +int 1 ++ +store 4 +b abisum_0_l1 +abisum_0_l3: +load 3 +retsub \ No newline at end of file diff --git a/tests/unit/user_guide_test.py b/tests/unit/user_guide_test.py index 809e5137e..3b006444e 100644 --- a/tests/unit/user_guide_test.py +++ b/tests/unit/user_guide_test.py @@ -60,9 +60,51 @@ def ILLEGAL_recursion(i: ScratchVar): return Seq(i.store(Int(15)), ILLEGAL_recursion(i), Int(1)) +def user_guide_snippet_ABIReturnSubroutine(): + from pyteal import ( + ABIReturnSubroutine, + Expr, + For, + Int, + ScratchVar, + Seq, + Txn, + TealType, + ) + from pyteal import abi + + @ABIReturnSubroutine + def abi_sum(toSum: abi.DynamicArray[abi.Uint64], *, output: abi.Uint64) -> Expr: + i = ScratchVar(TealType.uint64) + valueAtIndex = abi.Uint64() + return Seq( + output.set(0), + For( + i.store(Int(0)), i.load() < toSum.length(), i.store(i.load() + Int(1)) + ).Do( + Seq( + toSum[i.load()].store_into(valueAtIndex), + output.set(output.get() + valueAtIndex.get()), + ) + ), + ) + + program = Seq( + (to_sum_arr := abi.DynamicArray(abi.Uint64TypeSpec())).decode( + Txn.application_args[1] + ), + (res := abi.Uint64()).set(abi_sum(to_sum_arr)), + abi.MethodReturn(res), + Int(1), + ) + + return program + + USER_GUIDE_SNIPPETS_COPACETIC = [ user_guide_snippet_dynamic_scratch_var, user_guide_snippet_recursiveIsEven, + user_guide_snippet_ABIReturnSubroutine, ] From 3904b21fd57a8c50441d30ee6e8b9c51de5f77a5 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Wed, 4 May 2022 13:16:08 -0500 Subject: [PATCH 047/170] comments --- ...ser_guide_snippet_ABIReturnSubroutine.teal | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/tests/unit/teal/user_guide/user_guide_snippet_ABIReturnSubroutine.teal b/tests/unit/teal/user_guide/user_guide_snippet_ABIReturnSubroutine.teal index f2c8efb9b..a19e893be 100644 --- a/tests/unit/teal/user_guide/user_guide_snippet_ABIReturnSubroutine.teal +++ b/tests/unit/teal/user_guide/user_guide_snippet_ABIReturnSubroutine.teal @@ -1,7 +1,7 @@ #pragma version 6 -txna ApplicationArgs 1 -store 0 -load 0 +txna ApplicationArgs 1 // x = abi.DynamicArray(abi.Uint64TypeSpec()) +store 0 // 0: x +load 0 // [x] callsub abisum_0 store 1 byte 0x151F7C75 @@ -13,22 +13,22 @@ int 1 return // abi_sum -abisum_0: -store 2 -int 0 -store 3 -int 0 -store 4 -abisum_0_l1: -load 4 -load 2 -int 0 -extract_uint16 -store 6 -load 6 -< -bz abisum_0_l3 -load 2 +abisum_0: // [x] +store 2 // 2: x +int 0 // [0] +store 3 // 3: 0 +int 0 // [0] +store 4 // 4: 0 +abisum_0_l1: // [] +load 4 +load 2 +int 0 // [0, x, 0] +extract_uint16 // [0, len(x)] +store 6 // 6: len(x) +load 6 // [0, len(x)] +< // [1] +bz abisum_0_l3 // [0] +load 2 // ... looks promising ... int 8 load 4 * @@ -45,6 +45,6 @@ int 1 + store 4 b abisum_0_l1 -abisum_0_l3: -load 3 +abisum_0_l3: // [] +load 3 // [0] retsub \ No newline at end of file From 69d389b1d19a934d3a540c5cebc54425a21c4365 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 4 May 2022 14:21:38 -0400 Subject: [PATCH 048/170] cooperate with typespec n var def change --- pyteal/ast/subroutine_test.py | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index ef2e39476..0238d3ce3 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -140,19 +140,34 @@ def fn_2arg_1ret_with_expr( ), ABISubroutineTC( fn_2arg_0ret, - [pt.abi.Uint64(), pt.abi.StaticArray(pt.abi.ByteTypeSpec(), 10)], + [ + pt.abi.Uint64(), + pt.abi.StaticArray( + pt.abi.StaticArrayTypeSpec(pt.abi.ByteTypeSpec(), 10) + ), + ], "fn_2arg_0ret", "void", ), ABISubroutineTC( fn_2arg_1ret, - [pt.abi.Uint64(), pt.abi.StaticArray(pt.abi.ByteTypeSpec(), 10)], + [ + pt.abi.Uint64(), + pt.abi.StaticArray( + pt.abi.StaticArrayTypeSpec(pt.abi.ByteTypeSpec(), 10) + ), + ], "fn_2arg_1ret", pt.abi.ByteTypeSpec(), ), ABISubroutineTC( fn_2arg_1ret_with_expr, - [pt.Int(5), pt.abi.StaticArray(pt.abi.ByteTypeSpec(), 10)], + [ + pt.Int(5), + pt.abi.StaticArray( + pt.abi.StaticArrayTypeSpec(pt.abi.ByteTypeSpec(), 10) + ), + ], "fn_2arg_1ret_with_expr", pt.abi.ByteTypeSpec(), ), @@ -620,8 +635,12 @@ def fn_mixed_annotation_1_with_ret( abi_u64 = pt.abi.Uint64() abi_u32 = pt.abi.Uint32() abi_byte = pt.abi.Byte() - abi_static_u32_10 = pt.abi.StaticArray(pt.abi.Uint32TypeSpec(), 10) - abi_dynamic_bool = pt.abi.DynamicArray(pt.abi.BoolTypeSpec()) + abi_static_u32_10 = pt.abi.StaticArray( + pt.abi.StaticArrayTypeSpec(pt.abi.Uint32TypeSpec(), 10) + ) + abi_dynamic_bool = pt.abi.DynamicArray( + pt.abi.DynamicArrayTypeSpec(pt.abi.BoolTypeSpec()) + ) sv = pt.ScratchVar() expr_int = pt.Int(1) From 363865b243d86b15e8eab1ca47a08de3a1de5f6d Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Wed, 4 May 2022 14:37:06 -0500 Subject: [PATCH 049/170] wip --- tests/integration/abi_test.py | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/tests/integration/abi_test.py b/tests/integration/abi_test.py index eb02f8995..a707d5953 100644 --- a/tests/integration/abi_test.py +++ b/tests/integration/abi_test.py @@ -5,6 +5,8 @@ import pyteal as pt +from tests.blackbox import Blackbox + # ---- Simple Examples ---- # @@ -55,9 +57,10 @@ def fn_2arg_1ret_with_expr( return out.set(b[a % pt.Int(10)]) -# ---- doc test (headed into user_guide_test.py) +# ---- doc test (in our user_guide_test.py as well) +@Blackbox() @pt.ABIReturnSubroutine def abi_sum( toSum: pt.abi.DynamicArray[pt.abi.Uint64], *, output: pt.abi.Uint64 @@ -77,10 +80,12 @@ def abi_sum( ) -# ---- subtraction example ---- # +def test_abi_sum(): + pass -import pyteal as pt +# ---- subtraction example ---- # + Int65 = pt.abi.Tuple2[pt.abi.Bool, pt.abi.Uint64] @@ -130,19 +135,19 @@ def minus(x: Int65, y: Int65, *, z: Int65): ) -def test_minus(): - program = Seq( - (to_sum_arr := abi.DynamicArray(abi.Uint64TypeSpec())).decode( - Txn.application_args[1] - ), - (res := abi.Uint64()).set(abi_sum(to_sum_arr)), - abi.MethodReturn(res), - Int(1), - ) +# def test_minus(): +# program = Seq( +# (to_sum_arr := abi.DynamicArray(abi.Uint64TypeSpec())).decode( +# Txn.application_args[1] +# ), +# (res := abi.Uint64()).set(abi_sum(to_sum_arr)), +# abi.MethodReturn(res), +# Int(1), +# ) - teal = pt.compileTeal(minus, pt.Mode.Application, version=6) +# teal = pt.compileTeal(minus, pt.Mode.Application, version=6) - x = 42 +# x = 42 """ From fc67147f5d6940c44d899e087a499870257de492 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Wed, 4 May 2022 15:32:33 -0500 Subject: [PATCH 050/170] wip --- tests/unit/user_guide_test.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/unit/user_guide_test.py b/tests/unit/user_guide_test.py index 3b006444e..1a287f21d 100644 --- a/tests/unit/user_guide_test.py +++ b/tests/unit/user_guide_test.py @@ -90,6 +90,11 @@ def abi_sum(toSum: abi.DynamicArray[abi.Uint64], *, output: abi.Uint64) -> Expr: ) program = Seq( + # ( + # to_sum_arr := abi.DynamicArray( + # abi.DynamicArrayTypeSpec(abi.Uint64TypeSpec()) + # ).decode(Txn.application_args[1]) + # ), (to_sum_arr := abi.DynamicArray(abi.Uint64TypeSpec())).decode( Txn.application_args[1] ), From 10f910a6183c0c569b53251410299c767db4333e Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Wed, 4 May 2022 17:07:20 -0500 Subject: [PATCH 051/170] wip --- tests/blackbox.py | 1 + tests/integration/abi_test.py | 46 ++++++++++++++++++----------------- tests/unit/user_guide_test.py | 13 ++++------ 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/tests/blackbox.py b/tests/blackbox.py index 81bca9f69..249289fa0 100644 --- a/tests/blackbox.py +++ b/tests/blackbox.py @@ -41,6 +41,7 @@ def _algod_client( class BlackboxWrapper: + # TODO: this is clearly wrong... need to handle ABIReturnSubroutine as well def __init__(self, subr: SubroutineFnWrapper, input_types: list[TealType]): subr.subroutine._validate(input_types=input_types) self.subroutine = subr diff --git a/tests/integration/abi_test.py b/tests/integration/abi_test.py index a707d5953..2a76d8aa4 100644 --- a/tests/integration/abi_test.py +++ b/tests/integration/abi_test.py @@ -5,7 +5,7 @@ import pyteal as pt -from tests.blackbox import Blackbox +from tests.blackbox import Blackbox, blackbox_pyteal # ---- Simple Examples ---- # @@ -60,28 +60,30 @@ def fn_2arg_1ret_with_expr( # ---- doc test (in our user_guide_test.py as well) -@Blackbox() -@pt.ABIReturnSubroutine -def abi_sum( - toSum: pt.abi.DynamicArray[pt.abi.Uint64], *, output: pt.abi.Uint64 -) -> pt.Expr: - i = pt.ScratchVar(pt.TealType.uint64) - valueAtIndex = pt.abi.Uint64() - return pt.Seq( - output.set(0), - pt.For( - i.store(pt.Int(0)), i.load() < toSum.length(), i.store(i.load() + pt.Int(1)) - ).Do( - pt.Seq( - toSum[i.load()].store_into(valueAtIndex), - output.set(output.get() + valueAtIndex.get()), - ) - ), - ) - - def test_abi_sum(): - pass + @Blackbox(input_types=[None]) + @pt.ABIReturnSubroutine + def abi_sum( + toSum: pt.abi.DynamicArray[pt.abi.Uint64], *, output: pt.abi.Uint64 + ) -> pt.Expr: + i = pt.ScratchVar(pt.TealType.uint64) + valueAtIndex = pt.abi.Uint64() + return pt.Seq( + output.set(0), + pt.For( + i.store(pt.Int(0)), + i.load() < toSum.length(), + i.store(i.load() + pt.Int(1)), + ).Do( + pt.Seq( + toSum[i.load()].store_into(valueAtIndex), + output.set(output.get() + valueAtIndex.get()), + ) + ), + ) + + abi_sum_pt = blackbox_pyteal(abi_sum, pt.Mode.Application) + x = 42 # ---- subtraction example ---- # diff --git a/tests/unit/user_guide_test.py b/tests/unit/user_guide_test.py index 1a287f21d..e394d8454 100644 --- a/tests/unit/user_guide_test.py +++ b/tests/unit/user_guide_test.py @@ -90,14 +90,11 @@ def abi_sum(toSum: abi.DynamicArray[abi.Uint64], *, output: abi.Uint64) -> Expr: ) program = Seq( - # ( - # to_sum_arr := abi.DynamicArray( - # abi.DynamicArrayTypeSpec(abi.Uint64TypeSpec()) - # ).decode(Txn.application_args[1]) - # ), - (to_sum_arr := abi.DynamicArray(abi.Uint64TypeSpec())).decode( - Txn.application_args[1] - ), + ( + to_sum_arr := abi.DynamicArray( + abi.DynamicArrayTypeSpec(abi.Uint64TypeSpec()) + ) + ).decode(Txn.application_args[1]), (res := abi.Uint64()).set(abi_sum(to_sum_arr)), abi.MethodReturn(res), Int(1), From f42339d52acfb5f0559da52cbac68aa67f78353d Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 4 May 2022 18:07:32 -0400 Subject: [PATCH 052/170] update comments --- pyteal/ast/subroutine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 10c86396e..bfa8c1df5 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -484,7 +484,7 @@ def abi_sum(toSum: abi.DynamicArray[abi.Uint64], *, output: abi.Uint64) -> Expr: ) program = Seq( - (to_sum_arr := abi.DynamicArray(abi.Uint64TypeSpec())).decode( + (to_sum_arr := abi.make(abi.DynamicArray[abi.Uint64])).decode( Txn.application_args[1] ), (res := abi.Uint64()).set(abi_sum(to_sum_arr)), From 68694cd6c8e027e3ea329b1570c8a8bbba452898 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 5 May 2022 02:16:37 -0500 Subject: [PATCH 053/170] mostly there --- pyteal/ast/subroutine.py | 32 ++++++----- tests/blackbox.py | 100 +++++++++++++++++++++++++++++++--- tests/integration/abi_test.py | 5 +- tests/unit/user_guide_test.py | 15 +++-- 4 files changed, 125 insertions(+), 27 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index bfa8c1df5..82b54f32c 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from inspect import isclass, Parameter, signature, get_annotations -from types import MappingProxyType +from types import MappingProxyType, NoneType from typing import ( Callable, Optional, @@ -131,18 +131,6 @@ def _validate( abi_args: dict[str, abi.TypeSpec] = {} abi_output_kwarg: dict[str, abi.TypeSpec] = {} - if input_types: - if len(input_types) != len(impl_params): - raise TealInputError( - f"Provided number of input_types ({len(input_types)}) " - f"does not match detected number of parameters ({len(impl_params)})" - ) - for in_type, name in zip(input_types, impl_params): - if not isinstance(in_type, TealType): - raise TealInputError( - f"Function has input type {in_type} for parameter {name} which is not a TealType" - ) - if "return" in annotations and annotations["return"] is not Expr: raise TealInputError( f"Function has return of disallowed type {annotations['return']}. Only Expr is allowed" @@ -182,6 +170,24 @@ def _validate( if isinstance(expected_arg_type, abi.TypeSpec): abi_args[name] = expected_arg_type + if input_types: + input_arg_count = len(impl_params) - len(abi_output_kwarg) + if len(input_types) != input_arg_count: + raise TealInputError( + f"Provided number of input_types ({len(input_types)}) " + f"does not match detected number of input parameters ({input_arg_count})" + ) + for in_type, name in zip(input_types, impl_params): + if not isinstance(in_type, (TealType, NoneType)): + raise TealInputError( + f"Function has input type {in_type} for parameter {name} which is not a TealType" + ) + if in_type is None and name not in abi_args: + raise TealInputError( + f"input_type for {name} is unspecified i.e. None " + f"but this is only allowed for ABI arguments" + ) + return ( impl_params, annotations, diff --git a/tests/blackbox.py b/tests/blackbox.py index 249289fa0..d3116346f 100644 --- a/tests/blackbox.py +++ b/tests/blackbox.py @@ -5,6 +5,7 @@ from graviton import blackbox from pyteal import ( + abi, Arg, Btoi, Bytes, @@ -14,6 +15,7 @@ Len, Log, Mode, + Pop, ScratchVar, Seq, SubroutineFnWrapper, @@ -21,6 +23,8 @@ Txn, ) +from pyteal.ast.subroutine import ABIReturnSubroutine + # ---- Clients ---- # @@ -41,11 +45,14 @@ def _algod_client( class BlackboxWrapper: - # TODO: this is clearly wrong... need to handle ABIReturnSubroutine as well - def __init__(self, subr: SubroutineFnWrapper, input_types: list[TealType]): + def __init__( + self, + subr: SubroutineFnWrapper | ABIReturnSubroutine, + input_types: list[TealType | None], + ): subr.subroutine._validate(input_types=input_types) - self.subroutine = subr - self.input_types = input_types + self.subroutine: SubroutineFnWrapper | ABIReturnSubroutine = subr + self.input_types: list[TealType | None] = self._fill(input_types) def __call__(self, *args: Expr | ScratchVar, **kwargs) -> Expr: return self.subroutine(*args, **kwargs) @@ -53,6 +60,21 @@ def __call__(self, *args: Expr | ScratchVar, **kwargs) -> Expr: def name(self) -> str: return self.subroutine.name() + def _fill( + self, input_types: list[TealType | None] + ) -> list[TealType | abi.TypeSpec]: + match self.subroutine: + case SubroutineFnWrapper(): + return input_types + case ABIReturnSubroutine(): + args = self.subroutine.subroutine.arguments() + abis = self.subroutine.subroutine.abi_args + return [(x if x else abis[args[i]]) for i, x in enumerate(input_types)] + case _: + raise AssertionError( + f"Cannot produce handle subroutine of type {type(self.subroutine)}" + ) + def Blackbox(input_types: list[TealType]): def decorator_blackbox(func: SubroutineFnWrapper): @@ -77,8 +99,8 @@ def blackbox_pyteal(subr: BlackboxWrapper, mode: Mode) -> Callable[..., Expr]: """Functor producing ready-to-compile PyTeal programs from annotated subroutines Args: - subr: annotated subroutine to wrap inside program. - Note: the `input_types` parameters should be supplied to @Subroutine() annotation + subr: a Subroutine or ABIReturnSubroutine which has been decorated with @Blackbox. + Note: the `input_types` parameters should be supplied to the @Blackbox() decorator mode: type of program to produce: logic sig (Mode.Signature) or app (Mode.Application) Returns: @@ -131,12 +153,28 @@ def blackbox_pyteal(subr: BlackboxWrapper, mode: Mode) -> Callable[..., Expr]: * `blackbox_pyteal_example1()`: Using blackbox_pyteal() for a simple test of both an app and logic sig * `blackbox_pyteal_example2()`: Using blackbox_pyteal() to make 400 assertions and generate a CSV report with 400 dryrun rows * `blackbox_pyteal_example3()`: declarative Test Driven Development approach through Invariant's + * `blackbox_pyteal_example4()`: Using blackbox_pyteal() to debug an ABIReturnSubroutine """ input_types = subr.input_types assert ( input_types is not None ), "please provide input_types in your @Subroutine annotation (crucial for generating proper end-to-end testable PyTeal)" + match subr.subroutine: + case SubroutineFnWrapper(): + approval = _handle_SubroutineFnWrapper(subr, mode, input_types) + case ABIReturnSubroutine(): + approval = _handle_ABIReturnSubroutine(subr, mode, input_types) + case _: + raise AssertionError( + f"Cannot produce Blackbox pyteal for provided subroutine of type {type(subr.subroutine)}" + ) + + setattr(approval, "__name__", f"sem_{mode}_{subr.name()}") + return approval + + +def _handle_SubroutineFnWrapper(subr, mode, input_types): subdef = subr.subroutine.subroutine arg_names = subdef.arguments() @@ -198,5 +236,53 @@ def approval(): part2 = [make_log(result.load()), make_return(result.load())] return Seq(*(part1 + part2)) - setattr(approval, "__name__", f"sem_{mode}_{subr.name()}") + return approval + + +def _handle_ABIReturnSubroutine(subr, mode, input_types): + subdef = subr.subroutine.subroutine + arg_names = subdef.arguments() + + def arg_prep_n_call(i, p): + name = arg_names[i] + arg_expr = Txn.application_args[i] if mode == Mode.Application else Arg(i) + if p == TealType.uint64: + arg_expr = Btoi(arg_expr) + prep = None + arg_var = arg_expr + if name in subdef.by_ref_args: + arg_var = ScratchVar(p) + prep = arg_var.store(arg_expr) + elif name in subdef.abi_args: + arg_var = p.new_instance() + prep = arg_var.decode(arg_expr) + return prep, arg_var + + output = None + if subr.subroutine.output_kwarg_info: + output = subr.subroutine.output_kwarg_info.abi_type.new_instance() + + def approval(): + preps_n_calls = [*(arg_prep_n_call(i, p) for i, p in enumerate(input_types))] + preps, calls = zip(*preps_n_calls) if preps_n_calls else ([], []) + preps = [p for p in preps if p] + + # when @ABIReturnSubroutine is void: + # invocation is an Expr of TealType.none + # otherwise: + # it is a ComputedValue + invocation = subr(*calls) + if output: + invocation = output.set(invocation) + if mode == Mode.Signature: + results = [invocation, Pop(output.encode()), Int(1)] + else: + results = [invocation, abi.MethodReturn(output), Int(1)] + else: + results = [invocation, Int(1)] + + if preps: + return Seq(*(preps + results)) + return results + return approval diff --git a/tests/integration/abi_test.py b/tests/integration/abi_test.py index 2a76d8aa4..44205adaa 100644 --- a/tests/integration/abi_test.py +++ b/tests/integration/abi_test.py @@ -82,7 +82,10 @@ def abi_sum( ), ) - abi_sum_pt = blackbox_pyteal(abi_sum, pt.Mode.Application) + abi_sum_app_pt = blackbox_pyteal(abi_sum, pt.Mode.Application) + abi_sum_app_tl = pt.compileTeal(abi_sum_app_pt(), pt.Mode.Application, version=6) + abi_sum_lsig_pt = blackbox_pyteal(abi_sum, pt.Mode.Signature) + abi_sum_lsig_tl = pt.compileTeal(abi_sum_lsig_pt(), pt.Mode.Signature, version=6) x = 42 diff --git a/tests/unit/user_guide_test.py b/tests/unit/user_guide_test.py index e394d8454..de145daa2 100644 --- a/tests/unit/user_guide_test.py +++ b/tests/unit/user_guide_test.py @@ -89,13 +89,16 @@ def abi_sum(toSum: abi.DynamicArray[abi.Uint64], *, output: abi.Uint64) -> Expr: ), ) + to_sum_arr = abi.make(abi.DynamicArray[abi.Uint64]) + res = abi.Uint64() + program = Seq( - ( - to_sum_arr := abi.DynamicArray( - abi.DynamicArrayTypeSpec(abi.Uint64TypeSpec()) - ) - ).decode(Txn.application_args[1]), - (res := abi.Uint64()).set(abi_sum(to_sum_arr)), + # (to_sum_arr := abi.make(abi.DynamicArray[abi.Uint64])).decode( + # Txn.application_args[1] + # ), + to_sum_arr.decode(Txn.application_args[1]), + # (res := abi.Uint64()).set(abi_sum(to_sum_arr)), + res.set(abi_sum(to_sum_arr)), abi.MethodReturn(res), Int(1), ) From b94b286045b4f3d74a9c0be7c6c772ded17b473b Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 5 May 2022 08:46:36 -0500 Subject: [PATCH 054/170] pass thet test before pinning to new graviton --- .../{abi_test.py => graviton_abi_test.py} | 6 +- tests/integration/graviton_test.py | 131 ++++++++++-------- 2 files changed, 80 insertions(+), 57 deletions(-) rename tests/integration/{abi_test.py => graviton_abi_test.py} (96%) diff --git a/tests/integration/abi_test.py b/tests/integration/graviton_abi_test.py similarity index 96% rename from tests/integration/abi_test.py rename to tests/integration/graviton_abi_test.py index 44205adaa..29290aba4 100644 --- a/tests/integration/abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -61,6 +61,7 @@ def fn_2arg_1ret_with_expr( def test_abi_sum(): + # TODO: move the pure pyteal generative version of this to user_docs_test.py @Blackbox(input_types=[None]) @pt.ABIReturnSubroutine def abi_sum( @@ -86,7 +87,10 @@ def abi_sum( abi_sum_app_tl = pt.compileTeal(abi_sum_app_pt(), pt.Mode.Application, version=6) abi_sum_lsig_pt = blackbox_pyteal(abi_sum, pt.Mode.Signature) abi_sum_lsig_tl = pt.compileTeal(abi_sum_lsig_pt(), pt.Mode.Signature, version=6) - x = 42 + + todo_use_these_guys = abi_sum_app_tl + todo_use_these_guys = abi_sum_lsig_tl + _ = todo_use_these_guys # ---- subtraction example ---- # diff --git a/tests/integration/graviton_test.py b/tests/integration/graviton_test.py index b620962ff..0cf5880ab 100644 --- a/tests/integration/graviton_test.py +++ b/tests/integration/graviton_test.py @@ -4,20 +4,7 @@ import pytest -from pyteal import ( - Bytes, - Concat, - For, - If, - Int, - Mode, - ScratchVar, - Seq, - Subroutine, - SubroutineFnWrapper, - TealType, - compileTeal, -) +import pyteal as pt from tests.compile_asserts import assert_teal_as_expected from tests.blackbox import ( @@ -52,13 +39,13 @@ def wrap_compile_and_save( subr, mode, version, assemble_constants, test_name, case_name ): - is_app = mode == Mode.Application + is_app = mode == pt.Mode.Application # 1. PyTeal program Expr generation approval = blackbox_pyteal(subr, mode) # 2. TEAL generation - teal = compileTeal( + teal = pt.compileTeal( approval(), mode, version=version, assembleConstants=assemble_constants ) tealfile = f'{"app" if is_app else "lsig"}_{case_name}.teal' @@ -70,7 +57,7 @@ def wrap_compile_and_save( f.write(teal) print( - f"""subroutine {case_name}@{mode} generated TEAL. + f"""Subroutine {case_name}@{mode} generated TEAL. saved to {tealpath}: ------- {teal} @@ -84,60 +71,60 @@ def wrap_compile_and_save( @Blackbox(input_types=[]) -@Subroutine(TealType.uint64) +@pt.Subroutine(pt.TealType.uint64) def exp(): - return Int(2) ** Int(10) + return pt.Int(2) ** pt.Int(10) -@Blackbox(input_types=[TealType.uint64]) -@Subroutine(TealType.none) -def square_byref(x: ScratchVar): +@Blackbox(input_types=[pt.TealType.uint64]) +@pt.Subroutine(pt.TealType.none) +def square_byref(x: pt.ScratchVar): return x.store(x.load() * x.load()) -@Blackbox(input_types=[TealType.uint64]) -@Subroutine(TealType.uint64) +@Blackbox(input_types=[pt.TealType.uint64]) +@pt.Subroutine(pt.TealType.uint64) def square(x): - return x ** Int(2) + return x ** pt.Int(2) -@Blackbox(input_types=[TealType.anytype, TealType.anytype]) -@Subroutine(TealType.none) -def swap(x: ScratchVar, y: ScratchVar): - z = ScratchVar(TealType.anytype) - return Seq( +@Blackbox(input_types=[pt.TealType.anytype, pt.TealType.anytype]) +@pt.Subroutine(pt.TealType.none) +def swap(x: pt.ScratchVar, y: pt.ScratchVar): + z = pt.ScratchVar(pt.TealType.anytype) + return pt.Seq( z.store(x.load()), x.store(y.load()), y.store(z.load()), ) -@Blackbox(input_types=[TealType.bytes, TealType.uint64]) -@Subroutine(TealType.bytes) -def string_mult(s: ScratchVar, n): - i = ScratchVar(TealType.uint64) - tmp = ScratchVar(TealType.bytes) - start = Seq(i.store(Int(1)), tmp.store(s.load()), s.store(Bytes(""))) - step = i.store(i.load() + Int(1)) - return Seq( - For(start, i.load() <= n, step).Do(s.store(Concat(s.load(), tmp.load()))), +@Blackbox(input_types=[pt.TealType.bytes, pt.TealType.uint64]) +@pt.Subroutine(pt.TealType.bytes) +def string_mult(s: pt.ScratchVar, n): + i = pt.ScratchVar(pt.TealType.uint64) + tmp = pt.ScratchVar(pt.TealType.bytes) + start = pt.Seq(i.store(pt.Int(1)), tmp.store(s.load()), s.store(pt.Bytes(""))) + step = i.store(i.load() + pt.Int(1)) + return pt.Seq( + pt.For(start, i.load() <= n, step).Do(s.store(pt.Concat(s.load(), tmp.load()))), s.load(), ) -@Blackbox(input_types=[TealType.uint64]) -@Subroutine(TealType.uint64) +@Blackbox(input_types=[pt.TealType.uint64]) +@pt.Subroutine(pt.TealType.uint64) def oldfac(n): - return If(n < Int(2)).Then(Int(1)).Else(n * oldfac(n - Int(1))) + return pt.If(n < pt.Int(2)).Then(pt.Int(1)).Else(n * oldfac(n - pt.Int(1))) -@Blackbox(input_types=[TealType.uint64]) -@Subroutine(TealType.uint64) +@Blackbox(input_types=[pt.TealType.uint64]) +@pt.Subroutine(pt.TealType.uint64) def slow_fibonacci(n): return ( - If(n <= Int(1)) + pt.If(n <= pt.Int(1)) .Then(n) - .Else(slow_fibonacci(n - Int(2)) + slow_fibonacci(n - Int(1))) + .Else(slow_fibonacci(n - pt.Int(2)) + slow_fibonacci(n - pt.Int(1))) ) @@ -171,7 +158,7 @@ def fib_cost(args): "subr, mode", product( [exp, square_byref, square, swap, string_mult, oldfac, slow_fibonacci], - [Mode.Application, Mode.Signature], + [pt.Mode.Application, pt.Mode.Signature], ), ) def test_stable_teal_generation(subr, mode): @@ -506,8 +493,8 @@ def test_stable_teal_generation(subr, mode): def blackbox_test_runner( - subr: SubroutineFnWrapper, - mode: Mode, + subr: pt.SubroutineFnWrapper, + mode: pt.Mode, scenario: Dict[str, Any], version: int, assemble_constants: bool = True, @@ -518,7 +505,7 @@ def blackbox_test_runner( # 0. Validations assert isinstance(subr, BlackboxWrapper), f"unexpected subr type {type(subr)}" - assert isinstance(mode, Mode) + assert isinstance(mode, pt.Mode) # 1. Compile to TEAL teal, _, tealfile = wrap_compile_and_save( @@ -563,19 +550,19 @@ def blackbox_test_runner( @pytest.mark.parametrize("subr, scenario", APP_SCENARIOS.items()) -def test_blackbox_subroutines_as_apps( - subr: SubroutineFnWrapper, +def test_blackbox_Subroutines_as_apps( + subr: pt.SubroutineFnWrapper, scenario: Dict[str, Any], ): - blackbox_test_runner(subr, Mode.Application, scenario, 6) + blackbox_test_runner(subr, pt.Mode.Application, scenario, 6) @pytest.mark.parametrize("subr, scenario", LOGICSIG_SCENARIOS.items()) -def test_blackbox_subroutines_as_logic_sigs( - subr: SubroutineFnWrapper, +def test_blackbox_Subroutines_as_logic_sigs( + subr: pt.SubroutineFnWrapper, scenario: Dict[str, Any], ): - blackbox_test_runner(subr, Mode.Signature, scenario, 6) + blackbox_test_runner(subr, pt.Mode.Signature, scenario, 6) def blackbox_pyteal_example1(): @@ -770,6 +757,38 @@ def euclid(x, y): Invariant(predicate).validates(property, inputs, inspectors) +def test_abi_sum(): + @Blackbox(input_types=[None]) + @pt.ABIReturnSubroutine + def abi_sum( + toSum: pt.abi.DynamicArray[pt.abi.Uint64], *, output: pt.abi.Uint64 + ) -> pt.Expr: + i = pt.ScratchVar(pt.TealType.uint64) + valueAtIndex = pt.abi.Uint64() + return pt.Seq( + output.set(0), + pt.For( + i.store(pt.Int(0)), + i.load() < toSum.length(), + i.store(i.load() + pt.Int(1)), + ).Do( + pt.Seq( + toSum[i.load()].store_into(valueAtIndex), + output.set(output.get() + valueAtIndex.get()), + ) + ), + ) + + abi_sum_app_pt = blackbox_pyteal(abi_sum, pt.Mode.Application) + abi_sum_app_tl = pt.compileTeal(abi_sum_app_pt(), pt.Mode.Application, version=6) + abi_sum_lsig_pt = blackbox_pyteal(abi_sum, pt.Mode.Signature) + abi_sum_lsig_tl = pt.compileTeal(abi_sum_lsig_pt(), pt.Mode.Signature, version=6) + + todo_use_these_guys = abi_sum_app_tl + todo_use_these_guys = abi_sum_lsig_tl + _ = todo_use_these_guys + + @pytest.mark.parametrize( "example", [blackbox_pyteal_example1, blackbox_pyteal_example2, blackbox_pyteal_example3], From 11f86d292319119082480d299ba6124c52c994bf Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 5 May 2022 08:54:03 -0500 Subject: [PATCH 055/170] change expected error message --- pyteal/ast/subroutine_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index 0238d3ce3..3ccd6a457 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -222,7 +222,7 @@ def mock_subroutine_definition(implementation, abi_output_arg_name=None): three_params._validate(input_types=two_inputs) assert tie.value == pt.TealInputError( - "Provided number of input_types (2) does not match detected number of parameters (3)" + "Provided number of input_types (2) does not match detected number of input parameters (3)" ) three_inputs_with_a_wrong_type = [pt.TealType.uint64, pt.Expr, pt.TealType.bytes] From 59226b25ab7e07396f4fffd75c251606193f0748 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 5 May 2022 10:41:08 -0400 Subject: [PATCH 056/170] some comments resolving? --- pyteal/ast/subroutine.py | 8 ++++---- pyteal/ast/subroutine_test.py | 9 ++++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index bfa8c1df5..3a71d1525 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -131,7 +131,7 @@ def _validate( abi_args: dict[str, abi.TypeSpec] = {} abi_output_kwarg: dict[str, abi.TypeSpec] = {} - if input_types: + if input_types is not None: if len(input_types) != len(impl_params): raise TealInputError( f"Provided number of input_types ({len(input_types)}) " @@ -386,8 +386,9 @@ def __teal__(self, options: "CompileOptions"): 3. (ABI, or a special case in by-value) In this case, the storage of an ABI value are loaded to the stack and will be stored in a local ABI value for subroutine evaluation - 4. (ABI output keyword argument, or by-ref ABI value) In this case of returning ABI values, we do not place - ABI values on the stack, while in `evaluate_subroutine` we use an ABI typed instance for subroutine evaluation + 4. (ABI output keyword argument) In this case, we do not place ABI values (encoding) on the stack. + This is an *output-only* argument: in `evaluate_subroutine` an ABI typed instance for subroutine evaluation + will be generated, and gets in to construct the subroutine implementation. """ verifyTealVersion( Op.callsub.min_version, @@ -693,7 +694,6 @@ def var_n_loaded( ) args = subroutine.arguments() - args = [arg for arg in args if arg not in subroutine.output_kwarg] arg_vars: list[ScratchVar] = [] loaded_args: list[ScratchVar | Expr | abi.BaseType] = [] diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index 0238d3ce3..5b2814385 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -814,6 +814,11 @@ def fnWithMixedAnnsABIRet2( ) -> pt.abi.Uint64: return pt.abi.Uint64() + def fnWithTooManyABIOutputs( + a: pt.ScratchVar, b: pt.abi.Uint64, *, c: pt.abi.Bool, d: pt.abi.Bool + ) -> pt.Expr: + return pt.Int(1) + cases = ( ( 1, @@ -838,7 +843,7 @@ def fnWithMixedAnnsABIRet2( ( fnWithMultipleABIKeywordArgs, "TealInputError('Function has a parameter type that is not allowed in a subroutine: parameter b with type", - "TealInputError('multiple output arguments with type annotations", + "multiple output arguments (2) with type annotations", ), ( fnWithVariableArgs, @@ -895,6 +900,8 @@ def fnWithMixedAnnsABIRet2( print(f"case=[{abi_sub_def_msg}]") pt.ABIReturnSubroutine(fn) + assert abi_sub_def_msg in str(e), f"failed for case[{fn.__name__}]" + def test_subroutine_declaration(): cases = ( From 44003d6c751f4f3409ecab45b1f8436e119dad53 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 5 May 2022 10:44:19 -0400 Subject: [PATCH 057/170] trim --- pyteal/ast/subroutine_test.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index 5b2814385..399ebef6c 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -814,11 +814,6 @@ def fnWithMixedAnnsABIRet2( ) -> pt.abi.Uint64: return pt.abi.Uint64() - def fnWithTooManyABIOutputs( - a: pt.ScratchVar, b: pt.abi.Uint64, *, c: pt.abi.Bool, d: pt.abi.Bool - ) -> pt.Expr: - return pt.Int(1) - cases = ( ( 1, From 9f13409dbd9ceea12013ef3e8ae898f5dbb5efa1 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 5 May 2022 11:03:11 -0400 Subject: [PATCH 058/170] update some comments --- pyteal/ast/subroutine.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 3a71d1525..e8719d900 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -169,6 +169,9 @@ def _validate( expected_arg_type = self._validate_annotation(annotations, name) if param.kind is Parameter.KEYWORD_ONLY: + # this case is only entered when + # - `self.abi_output_arg_name is not None` + # - `name == self.abi_output_arg_name` if not isinstance(expected_arg_type, abi.TypeSpec): raise TealInputError( f"Function keyword parameter {name} has type {expected_arg_type}" From 2c1ccb30c6725f64f989a44ab58fee7e8d9496ba Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 5 May 2022 10:48:38 -0500 Subject: [PATCH 059/170] pin gravition to commit with working abi functionality --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8735bd72e..08ae9ad11 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ black==22.3.0 flake8==4.0.1 flake8-tidy-imports==4.6.0 -graviton@git+https://github.com/algorand/graviton@v0.1.0 +graviton@git+https://github.com/algorand/graviton@d2fd142acb6f101f9f91f113dd8d709de6a26b3d mypy==0.950 pytest==7.1.1 pytest-cov==3.0.0 From 594110823f3e0f7d1ea71f33c350f4042af488a3 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 5 May 2022 12:36:55 -0400 Subject: [PATCH 060/170] bring testcase back --- pyteal/ast/subroutine_test.py | 55 +++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index 399ebef6c..05fdd1d3a 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -408,6 +408,61 @@ def one_dynscratchvar_impl(x: pt.DynamicScratchVar): assert abi_args == {} assert output_kwarg == {} + # annotation / abi type handling: + abi_annotation_examples = { + pt.abi.Address: pt.abi.AddressTypeSpec(), + pt.abi.Bool: pt.abi.BoolTypeSpec(), + pt.abi.Byte: pt.abi.ByteTypeSpec(), + pt.abi.DynamicArray[pt.abi.Bool]: pt.abi.DynamicArrayTypeSpec( + pt.abi.BoolTypeSpec() + ), + pt.abi.StaticArray[pt.abi.Uint32, Literal[10]]: pt.abi.StaticArrayTypeSpec( + pt.abi.Uint32TypeSpec(), 10 + ), + pt.abi.String: pt.abi.StringTypeSpec(), + pt.abi.Tuple2[pt.abi.Bool, pt.abi.Uint32]: pt.abi.TupleTypeSpec( + pt.abi.BoolTypeSpec(), pt.abi.Uint32TypeSpec() + ), + pt.abi.Uint8: pt.abi.Uint8TypeSpec(), + pt.abi.Uint16: pt.abi.Uint16TypeSpec(), + pt.abi.Uint32: pt.abi.Uint32TypeSpec(), + pt.abi.Uint64: pt.abi.Uint64TypeSpec(), + } + + anns = (pt.Expr, pt.ScratchVar) + tuple(abi_annotation_examples.keys()) + for x_ann, z_ann in product(anns, anns): + + def mocker_impl(x: x_ann, y, z: z_ann): + return pt.Return(pt.Int(1)) + + mocker = mock_subroutine_definition(mocker_impl) + params, anns, arg_types, byrefs, abis, output_kwarg = mocker._validate() + print( + f"{x_ann=}, {z_ann=}, {params=}, {anns=}, {arg_types=}, {byrefs=}, {abis=}, {output_kwarg=}" + ) + + assert len(params) == 3 + + assert anns == {"x": x_ann, "z": z_ann} + + assert ( + (arg_types[0] is x_ann or arg_types[0] == abi_annotation_examples[x_ann]) + and arg_types[1] is pt.Expr + and ( + arg_types[2] is z_ann or arg_types[2] == abi_annotation_examples[z_ann] + ) + ), f"{arg_types[0]} -> {x_ann} and {arg_types[1]} -> {pt.Expr} and {arg_types[2]} -> {z_ann}" + + assert byrefs == set(["x"] if x_ann is pt.ScratchVar else []) | set( + ["z"] if z_ann is pt.ScratchVar else [] + ) + expected_abis = {} + if x_ann not in (pt.Expr, pt.ScratchVar): + expected_abis["x"] = abi_annotation_examples[x_ann] + if z_ann not in (pt.Expr, pt.ScratchVar): + expected_abis["z"] = abi_annotation_examples[z_ann] + assert abis == expected_abis + def test_subroutine_invocation_param_types(): def fnWithNoAnnotations(a, b): From c161e22e97ffd4e64f597cfe54889e412029879f Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 5 May 2022 14:01:17 -0400 Subject: [PATCH 061/170] restriction on output kwarg name --- pyteal/ast/subroutine.py | 7 +++++- pyteal/ast/subroutine_test.py | 46 +++++++++++++++++++++-------------- 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index e8719d900..0d43ab2c4 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -540,10 +540,15 @@ def _output_name_type_from_fn( case []: return None case [name]: + if name != "output": + raise TealInputError( + f"ABI return subroutine output-kwarg name must be `output` at this moment, " + f"while {name} is the keyword." + ) annotation = fn_annotations.get(name, None) if annotation is None: raise TealInputError( - f"ABI subroutine output-kwarg {name} must specify ABI type" + f"ABI return subroutine output-kwarg {name} must specify ABI type" ) type_spec = abi.type_spec_from_annotation(annotation) return OutputKwArgInfo(name, type_spec) diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index 05fdd1d3a..f42b88b04 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -94,16 +94,16 @@ def fn_0arg_0ret() -> pt.Expr: return pt.Return() @pt.ABIReturnSubroutine - def fn_0arg_uint64_ret(*, res: pt.abi.Uint64) -> pt.Expr: - return res.set(1) + def fn_0arg_uint64_ret(*, output: pt.abi.Uint64) -> pt.Expr: + return output.set(1) @pt.ABIReturnSubroutine def fn_1arg_0ret(a: pt.abi.Uint64) -> pt.Expr: return pt.Return() @pt.ABIReturnSubroutine - def fn_1arg_1ret(a: pt.abi.Uint64, *, out: pt.abi.Uint64) -> pt.Expr: - return out.set(a) + def fn_1arg_1ret(a: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: + return output.set(a) @pt.ABIReturnSubroutine def fn_2arg_0ret( @@ -116,18 +116,18 @@ def fn_2arg_1ret( a: pt.abi.Uint64, b: pt.abi.StaticArray[pt.abi.Byte, Literal[10]], *, - out: pt.abi.Byte, + output: pt.abi.Byte, ) -> pt.Expr: - return out.set(b[a.get() % pt.Int(10)]) + return output.set(b[a.get() % pt.Int(10)]) @pt.ABIReturnSubroutine def fn_2arg_1ret_with_expr( a: pt.Expr, b: pt.abi.StaticArray[pt.abi.Byte, Literal[10]], *, - out: pt.abi.Byte, + output: pt.abi.Byte, ) -> pt.Expr: - return out.set(b[a % pt.Int(10)]) + return output.set(b[a % pt.Int(10)]) cases = ( ABISubroutineTC(fn_0arg_0ret, [], "fn_0arg_0ret", "void"), @@ -634,8 +634,10 @@ def fn_log_add(a: pt.abi.Uint64, b: pt.abi.Uint32) -> pt.Expr: return pt.Seq(pt.Log(pt.Itob(a.get() + b.get())), pt.Return()) @pt.ABIReturnSubroutine - def fn_ret_add(a: pt.abi.Uint64, b: pt.abi.Uint32, *, c: pt.abi.Uint64) -> pt.Expr: - return c.set(a.get() + b.get() + pt.Int(0xA190)) + def fn_ret_add( + a: pt.abi.Uint64, b: pt.abi.Uint32, *, output: pt.abi.Uint64 + ) -> pt.Expr: + return output.set(a.get() + b.get() + pt.Int(0xA190)) @pt.ABIReturnSubroutine def fn_abi_annotations_0( @@ -651,9 +653,9 @@ def fn_abi_annotations_0_with_ret( b: pt.abi.StaticArray[pt.abi.Uint32, Literal[10]], c: pt.abi.DynamicArray[pt.abi.Bool], *, - out: pt.abi.Byte, + output: pt.abi.Byte, ): - return out.set(a) + return output.set(a) @pt.ABIReturnSubroutine def fn_mixed_annotations_0(a: pt.ScratchVar, b: pt.Expr, c: pt.abi.Byte) -> pt.Expr: @@ -664,11 +666,11 @@ def fn_mixed_annotations_0(a: pt.ScratchVar, b: pt.Expr, c: pt.abi.Byte) -> pt.E @pt.ABIReturnSubroutine def fn_mixed_annotations_0_with_ret( - a: pt.ScratchVar, b: pt.Expr, c: pt.abi.Byte, *, out: pt.abi.Uint64 + a: pt.ScratchVar, b: pt.Expr, c: pt.abi.Byte, *, output: pt.abi.Uint64 ) -> pt.Expr: return pt.Seq( a.store(c.get() * pt.Int(0x0FF1CE) * b), - out.set(a.load()), + output.set(a.load()), ) @pt.ABIReturnSubroutine @@ -683,9 +685,9 @@ def fn_mixed_annotation_1( @pt.ABIReturnSubroutine def fn_mixed_annotation_1_with_ret( - a: pt.ScratchVar, b: pt.abi.Uint64, *, c: pt.abi.Bool + a: pt.ScratchVar, b: pt.abi.Uint64, *, output: pt.abi.Bool ) -> pt.Expr: - return c.set((a.load() + b.get()) % pt.Int(2)) + return output.set((a.load() + b.get()) % pt.Int(2)) abi_u64 = pt.abi.Uint64() abi_u32 = pt.abi.Uint32() @@ -833,7 +835,10 @@ def test_subroutine_definition_invalid(): def fnWithDefaults(a, b=None): return pt.Return() - def fnWithKeywordArgs(a, *, b): + def fnWithKeywordArgs(a, *, output): + return pt.Return() + + def fnWithKeywordArgsWrongKWName(a, *, b: pt.abi.Uint64): return pt.Return() def fnWithMultipleABIKeywordArgs(a, *, b: pt.abi.Byte, c: pt.abi.Bool): @@ -887,8 +892,13 @@ def fnWithMixedAnnsABIRet2( ), ( fnWithKeywordArgs, + "TealInputError('Function has a parameter type that is not allowed in a subroutine: parameter output with type", + "TealInputError('ABI return subroutine output-kwarg output must specify ABI type')", + ), + ( + fnWithKeywordArgsWrongKWName, "TealInputError('Function has a parameter type that is not allowed in a subroutine: parameter b with type", - "TealInputError('ABI subroutine output-kwarg b must specify ABI type')", + "TealInputError('ABI return subroutine output-kwarg name must be `output` at this moment" ), ( fnWithMultipleABIKeywordArgs, From d2caed3c553f5d52c06bdd69f77871bf015071e9 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 5 May 2022 14:01:39 -0400 Subject: [PATCH 062/170] stop ci! i am reformatting --- pyteal/ast/subroutine_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index f42b88b04..3e66ecaf5 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -898,7 +898,7 @@ def fnWithMixedAnnsABIRet2( ( fnWithKeywordArgsWrongKWName, "TealInputError('Function has a parameter type that is not allowed in a subroutine: parameter b with type", - "TealInputError('ABI return subroutine output-kwarg name must be `output` at this moment" + "TealInputError('ABI return subroutine output-kwarg name must be `output` at this moment", ), ( fnWithMultipleABIKeywordArgs, From d62a1249d16a06be4fa6bdc438e844b735706d2a Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 5 May 2022 19:33:30 -0500 Subject: [PATCH 063/170] wip --- requirements.txt | 2 +- tests/blackbox.py | 273 ++++++++++++++++------------- tests/integration/graviton_test.py | 94 +++++++--- 3 files changed, 226 insertions(+), 143 deletions(-) diff --git a/requirements.txt b/requirements.txt index 08ae9ad11..9ecfe282f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ black==22.3.0 flake8==4.0.1 flake8-tidy-imports==4.6.0 -graviton@git+https://github.com/algorand/graviton@d2fd142acb6f101f9f91f113dd8d709de6a26b3d +graviton@git+https://github.com/algorand/graviton@8d12125802c1df29bc59a4221b94cd80f65a924b mypy==0.950 pytest==7.1.1 pytest-cov==3.0.0 diff --git a/tests/blackbox.py b/tests/blackbox.py index d3116346f..b380b6589 100644 --- a/tests/blackbox.py +++ b/tests/blackbox.py @@ -1,5 +1,6 @@ from typing import Callable +import algosdk.abi from algosdk.v2client import algod from graviton import blackbox @@ -153,136 +154,164 @@ def blackbox_pyteal(subr: BlackboxWrapper, mode: Mode) -> Callable[..., Expr]: * `blackbox_pyteal_example1()`: Using blackbox_pyteal() for a simple test of both an app and logic sig * `blackbox_pyteal_example2()`: Using blackbox_pyteal() to make 400 assertions and generate a CSV report with 400 dryrun rows * `blackbox_pyteal_example3()`: declarative Test Driven Development approach through Invariant's - * `blackbox_pyteal_example4()`: Using blackbox_pyteal() to debug an ABIReturnSubroutine + * `blackbox_pyteal_example4()`: Using BlackboxPyTealer.program() to debug an ABIReturnSubroutine with an app, logic sig and csv report """ - input_types = subr.input_types - assert ( - input_types is not None - ), "please provide input_types in your @Subroutine annotation (crucial for generating proper end-to-end testable PyTeal)" - - match subr.subroutine: - case SubroutineFnWrapper(): - approval = _handle_SubroutineFnWrapper(subr, mode, input_types) - case ABIReturnSubroutine(): - approval = _handle_ABIReturnSubroutine(subr, mode, input_types) - case _: - raise AssertionError( - f"Cannot produce Blackbox pyteal for provided subroutine of type {type(subr.subroutine)}" + return BlackboxPyTealer(subr, mode).program() + + +class BlackboxPyTealer: + def __init__(self, subr: BlackboxWrapper, mode: Mode): + input_types = subr.input_types + assert ( + input_types is not None + ), "please provide input_types in your @Subroutine annotation (crucial for generating proper end-to-end testable PyTeal)" + + self.subr, self.mode, self.input_types = subr, mode, input_types + match subr.subroutine: + case SubroutineFnWrapper(): + approval = self._handle_SubroutineFnWrapper() + case ABIReturnSubroutine(): + approval = self._handle_ABIReturnSubroutine() + case _: + raise AssertionError( + f"Cannot produce Blackbox pyteal for provided subroutine of type {type(subr.subroutine)}" + ) + + setattr(approval, "__name__", f"sem_{mode}_{subr.name()}") + self._pyteal_getter: Callable[..., Expr] = approval + + def is_abi(self) -> bool: + return isinstance(self.subr.subroutine, ABIReturnSubroutine) + + def abi_argument_types(self) -> list[None | algosdk.abi.ABIType]: + assert self.is_abi(), f"this {type(self)} is not A.B.I. compatible" + + def handle_arg(arg): + if isinstance(arg, abi.TypeSpec): + return arg.new_instance() + return None + + return [handle_arg(arg) for arg in self.input_types] + + def program(self) -> Callable[..., Expr]: + return self._pyteal_getter + + def _handle_SubroutineFnWrapper(self): + subdef = self.subr.subroutine.subroutine + arg_names = subdef.arguments() + + def arg_prep_n_call(i, p): + name = arg_names[i] + by_ref = name in subdef.by_ref_args + arg_expr = ( + Txn.application_args[i] if self.mode == Mode.Application else Arg(i) ) + if p == TealType.uint64: + arg_expr = Btoi(arg_expr) + prep = None + arg_var = arg_expr + if by_ref: + arg_var = ScratchVar(p) + prep = arg_var.store(arg_expr) + return prep, arg_var + + def subr_caller(): + preps_n_calls = [ + *(arg_prep_n_call(i, p) for i, p in enumerate(self.input_types)) + ] + preps, calls = zip(*preps_n_calls) if preps_n_calls else ([], []) + preps = [p for p in preps if p] + invocation = self.subr(*calls) + if preps: + return Seq(*(preps + [invocation])) + return invocation + + def make_return(e): + if e.type_of() == TealType.uint64: + return e + if e.type_of() == TealType.bytes: + return Len(e) + if e.type_of() == TealType.anytype: + x = ScratchVar(TealType.anytype) + return Seq(x.store(e), Int(1337)) + # TealType.none: + return Seq(e, Int(1337)) + + def make_log(e): + if e.type_of() == TealType.uint64: + return Log(Itob(e)) + if e.type_of() == TealType.bytes: + return Log(e) + return Log(Bytes("nada")) + + if self.mode == Mode.Signature: + + def approval(): + return make_return(subr_caller()) - setattr(approval, "__name__", f"sem_{mode}_{subr.name()}") - return approval - - -def _handle_SubroutineFnWrapper(subr, mode, input_types): - subdef = subr.subroutine.subroutine - arg_names = subdef.arguments() - - def arg_prep_n_call(i, p): - name = arg_names[i] - by_ref = name in subdef.by_ref_args - arg_expr = Txn.application_args[i] if mode == Mode.Application else Arg(i) - if p == TealType.uint64: - arg_expr = Btoi(arg_expr) - prep = None - arg_var = arg_expr - if by_ref: - arg_var = ScratchVar(p) - prep = arg_var.store(arg_expr) - return prep, arg_var - - def subr_caller(): - preps_n_calls = [*(arg_prep_n_call(i, p) for i, p in enumerate(input_types))] - preps, calls = zip(*preps_n_calls) if preps_n_calls else ([], []) - preps = [p for p in preps if p] - invocation = subr(*calls) - if preps: - return Seq(*(preps + [invocation])) - return invocation - - def make_return(e): - if e.type_of() == TealType.uint64: - return e - if e.type_of() == TealType.bytes: - return Len(e) - if e.type_of() == TealType.anytype: - x = ScratchVar(TealType.anytype) - return Seq(x.store(e), Int(1337)) - # TealType.none: - return Seq(e, Int(1337)) - - def make_log(e): - if e.type_of() == TealType.uint64: - return Log(Itob(e)) - if e.type_of() == TealType.bytes: - return Log(e) - return Log(Bytes("nada")) + else: - if mode == Mode.Signature: + def approval(): + if subdef.return_type == TealType.none: + result = ScratchVar(TealType.uint64) + part1 = [subr_caller(), result.store(Int(1337))] + else: + result = ScratchVar(subdef.return_type) + part1 = [result.store(subr_caller())] - def approval(): - return make_return(subr_caller()) + part2 = [make_log(result.load()), make_return(result.load())] + return Seq(*(part1 + part2)) + + return approval + + def _handle_ABIReturnSubroutine(self): + subdef = self.subr.subroutine.subroutine + arg_names = subdef.arguments() - else: + def arg_prep_n_call(i, p): + name = arg_names[i] + arg_expr = ( + Txn.application_args[i] if self.mode == Mode.Application else Arg(i) + ) + if p == TealType.uint64: + arg_expr = Btoi(arg_expr) + prep = None + arg_var = arg_expr + if name in subdef.by_ref_args: + arg_var = ScratchVar(p) + prep = arg_var.store(arg_expr) + elif name in subdef.abi_args: + arg_var = p.new_instance() + prep = arg_var.decode(arg_expr) + return prep, arg_var + + output = None + if self.subr.subroutine.output_kwarg_info: + output = self.subr.subroutine.output_kwarg_info.abi_type.new_instance() def approval(): - if subdef.return_type == TealType.none: - result = ScratchVar(TealType.uint64) - part1 = [subr_caller(), result.store(Int(1337))] - else: - result = ScratchVar(subdef.return_type) - part1 = [result.store(subr_caller())] - - part2 = [make_log(result.load()), make_return(result.load())] - return Seq(*(part1 + part2)) - - return approval - - -def _handle_ABIReturnSubroutine(subr, mode, input_types): - subdef = subr.subroutine.subroutine - arg_names = subdef.arguments() - - def arg_prep_n_call(i, p): - name = arg_names[i] - arg_expr = Txn.application_args[i] if mode == Mode.Application else Arg(i) - if p == TealType.uint64: - arg_expr = Btoi(arg_expr) - prep = None - arg_var = arg_expr - if name in subdef.by_ref_args: - arg_var = ScratchVar(p) - prep = arg_var.store(arg_expr) - elif name in subdef.abi_args: - arg_var = p.new_instance() - prep = arg_var.decode(arg_expr) - return prep, arg_var - - output = None - if subr.subroutine.output_kwarg_info: - output = subr.subroutine.output_kwarg_info.abi_type.new_instance() - - def approval(): - preps_n_calls = [*(arg_prep_n_call(i, p) for i, p in enumerate(input_types))] - preps, calls = zip(*preps_n_calls) if preps_n_calls else ([], []) - preps = [p for p in preps if p] - - # when @ABIReturnSubroutine is void: - # invocation is an Expr of TealType.none - # otherwise: - # it is a ComputedValue - invocation = subr(*calls) - if output: - invocation = output.set(invocation) - if mode == Mode.Signature: - results = [invocation, Pop(output.encode()), Int(1)] + preps_n_calls = [ + *(arg_prep_n_call(i, p) for i, p in enumerate(self.input_types)) + ] + preps, calls = zip(*preps_n_calls) if preps_n_calls else ([], []) + preps = [p for p in preps if p] + + # when @ABIReturnSubroutine is void: + # invocation is an Expr of TealType.none + # otherwise: + # it is a ComputedValue + invocation = self.subr(*calls) + if output: + invocation = output.set(invocation) + if self.mode == Mode.Signature: + results = [invocation, Pop(output.encode()), Int(1)] + else: + results = [invocation, abi.MethodReturn(output), Int(1)] else: - results = [invocation, abi.MethodReturn(output), Int(1)] - else: - results = [invocation, Int(1)] + results = [invocation, Int(1)] - if preps: - return Seq(*(preps + results)) - return results + if preps: + return Seq(*(preps + results)) + return results - return approval + return approval diff --git a/tests/integration/graviton_test.py b/tests/integration/graviton_test.py index 0cf5880ab..1f26b47e6 100644 --- a/tests/integration/graviton_test.py +++ b/tests/integration/graviton_test.py @@ -757,41 +757,95 @@ def euclid(x, y): Invariant(predicate).validates(property, inputs, inspectors) -def test_abi_sum(): +def blackbox_pyteal_example4(): + # Example 4: Using BlackboxPyTealer.program() to debug an ABIReturnSubroutine with an app, logic sig and csv report + from pathlib import Path + import random + + from graviton.blackbox import DryRunExecutor, DryRunInspector + + from pyteal import ( + abi, + compileTeal, + ABIReturnSubroutine, + Expr, + For, + Int, + Mode, + ScratchVar, + Seq, + TealType, + ) + + from tests.blackbox import Blackbox, BlackboxPyTealer, algod_with_assertion + + # Sum a dynamic uint64 array @Blackbox(input_types=[None]) - @pt.ABIReturnSubroutine - def abi_sum( - toSum: pt.abi.DynamicArray[pt.abi.Uint64], *, output: pt.abi.Uint64 - ) -> pt.Expr: - i = pt.ScratchVar(pt.TealType.uint64) - valueAtIndex = pt.abi.Uint64() - return pt.Seq( + @ABIReturnSubroutine + def abi_sum(toSum: abi.DynamicArray[abi.Uint64], *, output: abi.Uint64) -> Expr: + i = ScratchVar(TealType.uint64) + valueAtIndex = abi.Uint64() + return Seq( output.set(0), - pt.For( - i.store(pt.Int(0)), + For( + i.store(Int(0)), i.load() < toSum.length(), - i.store(i.load() + pt.Int(1)), + i.store(i.load() + Int(1)), ).Do( - pt.Seq( + Seq( toSum[i.load()].store_into(valueAtIndex), output.set(output.get() + valueAtIndex.get()), ) ), ) - abi_sum_app_pt = blackbox_pyteal(abi_sum, pt.Mode.Application) - abi_sum_app_tl = pt.compileTeal(abi_sum_app_pt(), pt.Mode.Application, version=6) - abi_sum_lsig_pt = blackbox_pyteal(abi_sum, pt.Mode.Signature) - abi_sum_lsig_tl = pt.compileTeal(abi_sum_lsig_pt(), pt.Mode.Signature, version=6) + # instantiate BlackboxPyTealer objects for the app and lsig: + app_pytealer = BlackboxPyTealer(abi_sum, Mode.Application) + lsig_pytealer = BlackboxPyTealer(abi_sum, Mode.Signature) + + # create approval PyTeal app and lsig: + pyteal_abi_sum_app = app_pytealer.program() + pyteal_abi_sum_lsig = lsig_pytealer.program() + + # compile the PyTeal's to Teal's: + teal_abi_sum_app = compileTeal(pyteal_abi_sum_app(), Mode.Application, version=6) + teal_abi_sum_lsig = compileTeal(pyteal_abi_sum_lsig(), Mode.Signature, version=6) + + # infer the abi types for encoding/decoding dry runs: + abi_argument_types = app_pytealer.abi_argument_types() + + # generate reports with the same random inputs (fix the randomness with a seed): + random.seed(42) + + choices = range(10_000) + inputs = [] + for n in range(100): + inputs.append(tuple(random.sample(choices, n))) + + # execute the dry-run sequence: + algod = algod_with_assertion() + + # abi_arg_types = + app_inspectors = DryRunExecutor.dryrun_app_on_sequence( + algod, + teal_abi_sum_app, + inputs, + ) + todo_use_these_guys = teal_abi_sum_app + todo_use_these_guys = teal_abi_sum_lsig - todo_use_these_guys = abi_sum_app_tl - todo_use_these_guys = abi_sum_lsig_tl - _ = todo_use_these_guys + def report(): + pass @pytest.mark.parametrize( "example", - [blackbox_pyteal_example1, blackbox_pyteal_example2, blackbox_pyteal_example3], + [ + blackbox_pyteal_example1, + blackbox_pyteal_example2, + blackbox_pyteal_example3, + blackbox_pyteal_example4, + ], ) def test_blackbox_pyteal_examples(example): example() From fce3c5c61f801263d8f562c7c092f4f19e125528 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 5 May 2022 22:07:03 -0500 Subject: [PATCH 064/170] utility function abi.algosdk_from_type_spec() --- pyteal/ast/abi/__init__.py | 2 +- pyteal/ast/abi/util.py | 6 ++ pyteal/ast/abi/util_test.py | 149 ++++++++++++++++++++++++++++++++++++ 3 files changed, 156 insertions(+), 1 deletion(-) diff --git a/pyteal/ast/abi/__init__.py b/pyteal/ast/abi/__init__.py index b9cb974ce..d6ff6433b 100644 --- a/pyteal/ast/abi/__init__.py +++ b/pyteal/ast/abi/__init__.py @@ -36,7 +36,7 @@ from pyteal.ast.abi.array_dynamic import DynamicArrayTypeSpec, DynamicArray from pyteal.ast.abi.method_return import MethodReturn -from pyteal.ast.abi.util import type_spec_from_annotation, make +from pyteal.ast.abi.util import algosdk_from_type_spec, make, type_spec_from_annotation __all__ = [ "String", diff --git a/pyteal/ast/abi/util.py b/pyteal/ast/abi/util.py index 6cd3aa7c9..e3f1df01a 100644 --- a/pyteal/ast/abi/util.py +++ b/pyteal/ast/abi/util.py @@ -1,5 +1,7 @@ from typing import TypeVar, Any, Literal, get_origin, get_args, cast +import algosdk.abi + from pyteal.errors import TealInputError from pyteal.ast.expr import Expr from pyteal.ast.int import Int @@ -232,3 +234,7 @@ def make(t: type[T]) -> T: A new instance of the given type class. """ return cast(T, type_spec_from_annotation(t).new_instance()) + + +def algosdk_from_type_spec(t: type[T]) -> algosdk.abi.ABIType: + return algosdk.abi.ABIType.from_string(str(t)) diff --git a/pyteal/ast/abi/util_test.py b/pyteal/ast/abi/util_test.py index 36db60676..084207d48 100644 --- a/pyteal/ast/abi/util_test.py +++ b/pyteal/ast/abi/util_test.py @@ -2,6 +2,8 @@ from inspect import isabstract import pytest +import algosdk.abi + import pyteal as pt from pyteal import abi from pyteal.ast.abi.util import ( @@ -313,3 +315,150 @@ def test_make(): assert actual.type_spec() == expected_type_spec assert type(actual) is abi.Tuple + + +def test_abi_type_translation(): + test_cases = [ + # Test for byte/bool/address/strings + (algosdk.abi.ByteType(), "byte", abi.ByteTypeSpec()), + (algosdk.abi.BoolType(), "bool", abi.BoolTypeSpec()), + (algosdk.abi.AddressType(), "address", abi.AddressTypeSpec()), + (algosdk.abi.StringType(), "string", abi.StringTypeSpec()), + # Test for dynamic array type + ( + algosdk.abi.ArrayDynamicType(algosdk.abi.UintType(32)), + "uint32[]", + abi.DynamicArrayTypeSpec(abi.Uint32TypeSpec()), + ), + ( + algosdk.abi.ArrayDynamicType( + algosdk.abi.ArrayDynamicType(algosdk.abi.ByteType()) + ), + "byte[][]", + abi.DynamicArrayTypeSpec(abi.DynamicArrayTypeSpec(abi.ByteTypeSpec())), + ), + # TODO: Turn these tests on when PyTeal supports ufixedx + # cf https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0004.md#types + # ( + # algosdk.abi.ArrayDynamicType(algosdk.abi.UfixedType(256, 64)), + # "ufixed256x64[]", + # abi.DynamicArrayTypeSpec(abi.UfixedTypeSpec(256, 64)), + # ), + # # Test for static array type + # ( + # algosdk.abi.ArrayStaticType(algosdk.abi.UfixedType(128, 10), 100), + # "ufixed128x10[100]", + # abi.ArrayStaticTypeSpec(abi.UfixedTypeSpec(128, 10), 100), + # ), + ( + algosdk.abi.ArrayStaticType( + algosdk.abi.ArrayStaticType(algosdk.abi.BoolType(), 256), + 100, + ), + "bool[256][100]", + abi.StaticArrayTypeSpec( + abi.StaticArrayTypeSpec(abi.BoolTypeSpec(), 256), + 100, + ), + ), + # Test for tuple + (algosdk.abi.TupleType([]), "()", abi.TupleTypeSpec()), + ( + algosdk.abi.TupleType( + [ + algosdk.abi.UintType(16), + algosdk.abi.TupleType( + [ + algosdk.abi.ByteType(), + algosdk.abi.ArrayStaticType(algosdk.abi.AddressType(), 10), + ] + ), + ] + ), + "(uint16,(byte,address[10]))", + abi.TupleTypeSpec( + abi.Uint16TypeSpec(), + abi.TupleTypeSpec( + abi.ByteTypeSpec(), + abi.StaticArrayTypeSpec(abi.AddressTypeSpec(), 10), + ), + ), + ), + ( + algosdk.abi.TupleType( + [ + algosdk.abi.UintType(64), + algosdk.abi.TupleType( + [ + algosdk.abi.ByteType(), + algosdk.abi.ArrayStaticType(algosdk.abi.AddressType(), 10), + ] + ), + algosdk.abi.TupleType([]), + algosdk.abi.BoolType(), + ] + ), + "(uint64,(byte,address[10]),(),bool)", + abi.TupleTypeSpec( + abi.Uint64TypeSpec(), + abi.TupleTypeSpec( + abi.ByteTypeSpec(), + abi.StaticArrayTypeSpec(abi.AddressTypeSpec(), 10), + ), + abi.TupleTypeSpec(), + abi.BoolTypeSpec(), + ), + ), + # ( + # algosdk.abi.TupleType( + # [ + # algosdk.abi.UfixedType(256, 16), + # algosdk.abi.TupleType( + # [ + # algosdk.abi.TupleType( + # [ + # algosdk.abi.StringType(), + # ] + # ), + # algosdk.abi.BoolType(), + # algosdk.abi.TupleType( + # [ + # algosdk.abi.AddressType(), + # algosdk.abi.UintType(8), + # ] + # ), + # ] + # ), + # ] + # ), + # "(ufixed256x16,((string),bool,(address,uint8)))", + # abi.TupleType( + # [ + # abi.UfixedType(256, 16), + # abi.TupleType( + # [ + # abi.TupleType( + # [ + # abi.StringType(), + # ] + # ), + # abi.BoolType(), + # abi.TupleType( + # [ + # abi.AddressType(), + # abi.UintType(8), + # ] + # ), + # ] + # ), + # ] + # ), + # ), + ] + + for algosdk_abi, abi_string, pyteal_abi_ts in test_cases: + print(f"({algosdk_abi}, {abi_string}, {pyteal_abi_ts}),") + assert str(algosdk_abi) == abi_string == str(pyteal_abi_ts) + assert algosdk_abi == algosdk.abi.ABIType.from_string(abi_string) + assert algosdk_abi == algosdk.abi.ABIType.from_string(str(pyteal_abi_ts)) + assert algosdk_abi == abi.algosdk_from_type_spec(pyteal_abi_ts) From 56f01d2df36ff2df36196bbc84e58f88560c0202 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 5 May 2022 23:59:34 -0500 Subject: [PATCH 065/170] abi pyteal blackbox - first good example --- pyteal/ast/abi/__init__.py | 1 + tests/blackbox.py | 16 +++++++-- tests/integration/graviton_test.py | 54 ++++++++++++++++++++++++------ 3 files changed, 58 insertions(+), 13 deletions(-) diff --git a/pyteal/ast/abi/__init__.py b/pyteal/ast/abi/__init__.py index d6ff6433b..85c251764 100644 --- a/pyteal/ast/abi/__init__.py +++ b/pyteal/ast/abi/__init__.py @@ -81,4 +81,5 @@ "MethodReturn", "type_spec_from_annotation", "make", + "algosdk_from_type_spec", ] diff --git a/tests/blackbox.py b/tests/blackbox.py index b380b6589..f55145eae 100644 --- a/tests/blackbox.py +++ b/tests/blackbox.py @@ -184,15 +184,27 @@ def is_abi(self) -> bool: return isinstance(self.subr.subroutine, ABIReturnSubroutine) def abi_argument_types(self) -> list[None | algosdk.abi.ABIType]: - assert self.is_abi(), f"this {type(self)} is not A.B.I. compatible" + if not self.is_abi(): + raise NotImplementedError("this {type(self)} is not A.B.I. compatible") def handle_arg(arg): if isinstance(arg, abi.TypeSpec): - return arg.new_instance() + return abi.algosdk_from_type_spec(arg) return None return [handle_arg(arg) for arg in self.input_types] + def abi_return_type(self) -> None | algosdk.abi.ABIType: + if not self.is_abi(): + raise NotImplementedError("this {type(self)} is not A.B.I. compatible") + + if not self.subr.subroutine.output_kwarg_info: + return None + + return abi.algosdk_from_type_spec( + self.subr.subroutine.output_kwarg_info.abi_type + ) + def program(self) -> Callable[..., Expr]: return self._pyteal_getter diff --git a/tests/integration/graviton_test.py b/tests/integration/graviton_test.py index 1f26b47e6..d1f5cd1aa 100644 --- a/tests/integration/graviton_test.py +++ b/tests/integration/graviton_test.py @@ -813,29 +813,61 @@ def abi_sum(toSum: abi.DynamicArray[abi.Uint64], *, output: abi.Uint64) -> Expr: # infer the abi types for encoding/decoding dry runs: abi_argument_types = app_pytealer.abi_argument_types() - + abi_return_type = app_pytealer.abi_return_type() # generate reports with the same random inputs (fix the randomness with a seed): random.seed(42) + N = 50 # the number of dry runs for each experiment choices = range(10_000) inputs = [] - for n in range(100): - inputs.append(tuple(random.sample(choices, n))) + for n in range(N): + inputs.append(tuple([random.sample(choices, n)])) # execute the dry-run sequence: algod = algod_with_assertion() - # abi_arg_types = app_inspectors = DryRunExecutor.dryrun_app_on_sequence( - algod, - teal_abi_sum_app, - inputs, + algod, teal_abi_sum_app, inputs, abi_argument_types, abi_return_type + ) + lsig_inspectors = DryRunExecutor.dryrun_logicsig_on_sequence( + algod, teal_abi_sum_lsig, inputs, abi_argument_types, abi_return_type ) - todo_use_these_guys = teal_abi_sum_app - todo_use_these_guys = teal_abi_sum_lsig + for i in range(N): + app_inspector = app_inspectors[i] + lsig_inspector = lsig_inspectors[i] + args = inputs[i] - def report(): - pass + def message(insp): + return insp.report(args, f"failed for {args}", row=i) + + # the app should pass exactly when it's cost was within the 700 budget: + assert app_inspector.passed() == (app_inspector.cost() <= 700), message( + app_inspector + ) + # the lsig always passes (never goes over budget): + assert lsig_inspector.passed(), message(lsig_inspector) + + expected = sum(args[0]) + actual4app = app_inspector.last_log() + assert expected == actual4app, message(app_inspector) + + if i > 0: + assert expected in app_inspector.final_scratch().values(), message( + app_inspector + ) + assert expected in lsig_inspector.final_scratch().values(), message( + lsig_inspector + ) + + def report(kind): + assert kind in ("app", "lsig") + insps = app_inspectors if kind == "app" else lsig_inspectors + csv_report = DryRunInspector.csv_report(inputs, insps) + with open(Path.cwd() / f"abi_sum_{kind}.csv", "w") as f: + f.write(csv_report) + + report("app") + report("lsig") @pytest.mark.parametrize( From d4c7371538e2cc030b2de6f48bfa6b0a5ae05712 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 6 May 2022 00:05:21 -0500 Subject: [PATCH 066/170] abi outputs must be named "output" --- tests/integration/graviton_abi_test.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index 29290aba4..876275877 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -16,7 +16,7 @@ def fn_0arg_0ret() -> pt.Expr: @pt.ABIReturnSubroutine -def fn_0arg_uint64_ret(*, res: pt.abi.Uint64) -> pt.Expr: +def fn_0arg_uint64_ret(*, output: pt.abi.Uint64) -> pt.Expr: return res.set(1) @@ -26,8 +26,8 @@ def fn_1arg_0ret(a: pt.abi.Uint64) -> pt.Expr: @pt.ABIReturnSubroutine -def fn_1arg_1ret(a: pt.abi.Uint64, *, out: pt.abi.Uint64) -> pt.Expr: - return out.set(a) +def fn_1arg_1ret(a: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: + return output.set(a) @pt.ABIReturnSubroutine @@ -42,9 +42,9 @@ def fn_2arg_1ret( a: pt.abi.Uint64, b: pt.abi.StaticArray[pt.abi.Byte, Literal[10]], *, - out: pt.abi.Byte, + output: pt.abi.Byte, ) -> pt.Expr: - return out.set(b[a.get() % pt.Int(10)]) + return output.set(b[a.get() % pt.Int(10)]) @pt.ABIReturnSubroutine @@ -52,9 +52,9 @@ def fn_2arg_1ret_with_expr( a: pt.Expr, b: pt.abi.StaticArray[pt.abi.Byte, Literal[10]], *, - out: pt.abi.Byte, + output: pt.abi.Byte, ) -> pt.Expr: - return out.set(b[a % pt.Int(10)]) + return output.set(b[a % pt.Int(10)]) # ---- doc test (in our user_guide_test.py as well) @@ -100,7 +100,7 @@ def abi_sum( @pt.ABIReturnSubroutine -def minus(x: Int65, y: Int65, *, z: Int65): +def minus(x: Int65, y: Int65, *, output: Int65): x0 = pt.abi.Bool() x1 = pt.abi.Uint64() y0 = pt.abi.Bool() @@ -140,7 +140,7 @@ def minus(x: Int65, y: Int65, *, z: Int65): pt.Seq(z0.store(pt.Int(1)), z1.Store(y1.get() - x1.get())) ) ), - z.set(z0.load(), z1.load()), + output.set(z0.load(), z1.load()), ) From 02bcdc76658555878d81a1287b8d948907e5cc8b Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 6 May 2022 00:26:56 -0500 Subject: [PATCH 067/170] pass existing tests --- tests/blackbox.py | 42 ++++++++++++++++++++++++++ tests/integration/graviton_abi_test.py | 21 ++++--------- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/tests/blackbox.py b/tests/blackbox.py index f55145eae..24eed7b44 100644 --- a/tests/blackbox.py +++ b/tests/blackbox.py @@ -78,6 +78,48 @@ def _fill( def Blackbox(input_types: list[TealType]): + """ + Decorator for trasforming @Subroutine and @ABIReturnSubroutine wrapped functions + into executable pyteal programs. + + input_types: list[TealType] (required) + List shadowing the input arguments of the decorated subroutine. In particular: + * the list needs to be the same length as the number of subroutine arguments + * if the subroutine argument is an ABI type, the shadowing input_type must be None + as it will be determined at compile time from the subroutine's annotation + * if the subroutine argument is an Expr or a ScratchVar, the shadowing input_type + must be a TealType of the same kind as expected in the argument + + Some Correct Examples: + + @Blackbox(input_types=[TealType.bytes, TealType.uint64]) + @Subroutine(TealType.bytes) + def string_mult(x, y): + ... + + @Blackbox(input_types=[TealType.bytes, TealType.uint64]) + @Subroutine(TealType.bytes) + def string_mult(x: Expr, y: Expr): + ... + + @Blackbox(input_types=[TealType.bytes, TealType.uint64]) + @Subroutine(TealType.bytes) + def string_mult(x: ScratchVar, y: Expr): + ... + + + @Blackbox(input_types=[None, None]) + @ABIReturnSubroutine + def string_mult(x: abi.String, y: abi.Uint16): + ... + + @Blackbox(input_types=[None, TealType.uint64]) + @ABIReturnSubroutine + def string_mult(x: abi.String, y): + ... + + """ + def decorator_blackbox(func: SubroutineFnWrapper): return BlackboxWrapper(func, input_types) diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index 876275877..dd96640cb 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -17,7 +17,7 @@ def fn_0arg_0ret() -> pt.Expr: @pt.ABIReturnSubroutine def fn_0arg_uint64_ret(*, output: pt.abi.Uint64) -> pt.Expr: - return res.set(1) + return output.set(1) @pt.ABIReturnSubroutine @@ -99,6 +99,7 @@ def abi_sum( Int65 = pt.abi.Tuple2[pt.abi.Bool, pt.abi.Uint64] +@Blackbox(input_types=[None, None]) @pt.ABIReturnSubroutine def minus(x: Int65, y: Int65, *, output: Int65): x0 = pt.abi.Bool() @@ -140,23 +141,13 @@ def minus(x: Int65, y: Int65, *, output: Int65): pt.Seq(z0.store(pt.Int(1)), z1.Store(y1.get() - x1.get())) ) ), - output.set(z0.load(), z1.load()), + pt.Return(output.set(z0.load(), z1.load())), ) -# def test_minus(): -# program = Seq( -# (to_sum_arr := abi.DynamicArray(abi.Uint64TypeSpec())).decode( -# Txn.application_args[1] -# ), -# (res := abi.Uint64()).set(abi_sum(to_sum_arr)), -# abi.MethodReturn(res), -# Int(1), -# ) - -# teal = pt.compileTeal(minus, pt.Mode.Application, version=6) - -# x = 42 +def test_minus(): + teal = pt.compileTeal(minus(), pt.Mode.Application, version=6) + _ = teal """ From ed8678fd8a3189d14203761a140e8fe4a294629d Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 6 May 2022 01:17:22 -0500 Subject: [PATCH 068/170] skip broken program --- tests/integration/graviton_abi_test.py | 69 +++++++++++++++++--------- 1 file changed, 46 insertions(+), 23 deletions(-) diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index dd96640cb..85895d764 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -1,11 +1,18 @@ -""" -Placeholder for upcoming unit test -""" +import random from typing import Literal +import pytest + +from graviton.blackbox import DryRunExecutor + import pyteal as pt -from tests.blackbox import Blackbox, blackbox_pyteal +from tests.blackbox import ( + Blackbox, + BlackboxPyTealer, + algod_with_assertion, + blackbox_pyteal, +) # ---- Simple Examples ---- # @@ -106,48 +113,64 @@ def minus(x: Int65, y: Int65, *, output: Int65): x1 = pt.abi.Uint64() y0 = pt.abi.Bool() y1 = pt.abi.Uint64() - z0 = pt.ScratchVar(pt.TealType.uint64) - z1 = pt.ScratchVar(pt.TealType.uint64) + z0 = pt.abi.Bool() + z1 = pt.abi.Uint64() return pt.Seq( x0.set(x[0]), x1.set(x[1]), y0.set(y[0]), y1.set(y[1]), + # z0.set(pt.Int(0)), + # z1.set(0), # Case I. x, y positive pt.If(pt.And(x0.get(), y0.get())).Then( pt.If(x1.get() >= y1.get()) - .Then( # +(x1 - y1) - pt.Seq(z0.store(pt.Int(1)), z1.Store(x1.get() - y1.get())) - ) - .Else( # -(y1 - x1) - pt.Seq(z0.store(pt.Int(0)), z1.Store(y1.get() - x1.get())) - ) + .Then(pt.Seq(z0.set(pt.Int(1)), z1.set(x1.get() - y1.get()))) # +(x1 - y1) + .Else(pt.Seq(z0.set(pt.Int(0)), z1.set(y1.get() - x1.get()))) # -(y1 - x1) ) # Case II. x positive, y negative .ElseIf(pt.And(x0.get(), pt.Not(y0.get()))).Then( - pt.Seq(z0.store(pt.Int(1)), z1.Store(x1.get() + y1.get())) + pt.Seq(z0.set(pt.Int(1)), z1.set(x1.get() + y1.get())) ) # x1 + y1 # Case III. x negative, y positive .ElseIf(pt.And(pt.Not(x0.get()), y0.get())).Then( - pt.Seq(z0.store(pt.Int(0)), z1.Store(x1.get() + y1.get())) + pt.Seq(z0.set(pt.Int(0)), z1.set(x1.get() + y1.get())) ) # -(x1 + y1) # Case IV. x, y negative .Else( pt.If(x1.get() >= y1.get()) - .Then( # -(x1 - y1) - pt.Seq(z0.store(pt.Int(0)), z1.Store(x1.get() - y1.get())) - ) - .Else( # +(y1 - x1) - pt.Seq(z0.store(pt.Int(1)), z1.Store(y1.get() - x1.get())) - ) + .Then(pt.Seq(z0.set(pt.Int(0)), z1.set(x1.get() - y1.get()))) # -(x1 - y1) + .Else(pt.Seq(z0.set(pt.Int(1)), z1.set(y1.get() - x1.get()))) # +(y1 - x1) ), - pt.Return(output.set(z0.load(), z1.load())), + output.set(z0.get(), z1.get()), ) +@pytest.mark.skip("problem on line 143") def test_minus(): - teal = pt.compileTeal(minus(), pt.Mode.Application, version=6) - _ = teal + bbpt = BlackboxPyTealer(minus, pt.Mode.Application) + approval = bbpt.program() + teal = pt.compileTeal(approval(), pt.Mode.Application, version=6) + abi_argument_types = bbpt.abi_argument_types() + abi_return_type = bbpt.abi_return_type() + + def pynum_to_tuple_rep(n): + return (n > 0, abs(n)) + + N = 100 + random.seed(42) + choices = range(-9_999, 10_000) + inputs = [ + (pynum_to_tuple_rep(x), pynum_to_tuple_rep(y)) + for x, y in zip(random.sample(choices, N), random.sample(choices, N)) + ] + + algod = algod_with_assertion() + inspectors = DryRunExecutor.dryrun_app_on_sequence( + algod, teal, inputs, abi_argument_types, abi_return_type + ) + + x = 42 """ From e89d347216a02a55b3dcaf4d4a29a38a942cd430 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 6 May 2022 01:18:07 -0500 Subject: [PATCH 069/170] lint --- tests/integration/graviton_abi_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index 85895d764..448ba70c1 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -170,7 +170,7 @@ def pynum_to_tuple_rep(n): algod, teal, inputs, abi_argument_types, abi_return_type ) - x = 42 + _ = inspectors """ From 591044592eebca2a6873a701d6cc4a034916ca13 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 6 May 2022 08:25:40 -0500 Subject: [PATCH 070/170] experiment --- tests/integration/graviton_abi_test.py | 34 ++++++++++++++++++-------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index 448ba70c1..75435fce4 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -113,9 +113,12 @@ def minus(x: Int65, y: Int65, *, output: Int65): x1 = pt.abi.Uint64() y0 = pt.abi.Bool() y1 = pt.abi.Uint64() - z0 = pt.abi.Bool() - z1 = pt.abi.Uint64() + t, f = pt.abi.Bool(), pt.abi.Bool() + # z0 = pt.abi.Bool() + # z1 = pt.abi.Uint64() return pt.Seq( + t.set(True), + f.set(False), x0.set(x[0]), x1.set(x[1]), y0.set(y[0]), @@ -125,28 +128,39 @@ def minus(x: Int65, y: Int65, *, output: Int65): # Case I. x, y positive pt.If(pt.And(x0.get(), y0.get())).Then( pt.If(x1.get() >= y1.get()) - .Then(pt.Seq(z0.set(pt.Int(1)), z1.set(x1.get() - y1.get()))) # +(x1 - y1) - .Else(pt.Seq(z0.set(pt.Int(0)), z1.set(y1.get() - x1.get()))) # -(y1 - x1) + # .Then(pt.Seq(z0.set(True), z1.set(x1.get() - y1.get()))) # +(x1 - y1) + # .Else(pt.Seq(z0.set(False), z1.set(y1.get() - x1.get()))) # -(y1 - x1) + .Then(output.set(t, x1.get() - y1.get())).Else( # +(x1 - y1) + output.set(f, y1.get() - x1.get()) + ) # -(y1 - x1) ) # Case II. x positive, y negative .ElseIf(pt.And(x0.get(), pt.Not(y0.get()))).Then( - pt.Seq(z0.set(pt.Int(1)), z1.set(x1.get() + y1.get())) + # pt.Seq(z0.set(True), z1.set(x1.get() + y1.get())) + output.set(t, x1.get() + y1.get()) ) # x1 + y1 # Case III. x negative, y positive .ElseIf(pt.And(pt.Not(x0.get()), y0.get())).Then( - pt.Seq(z0.set(pt.Int(0)), z1.set(x1.get() + y1.get())) + # pt.Seq(z0.set(False), z1.set(x1.get() + y1.get())) + output.set(f, set(x1.get() + y1.get())) ) # -(x1 + y1) # Case IV. x, y negative .Else( pt.If(x1.get() >= y1.get()) - .Then(pt.Seq(z0.set(pt.Int(0)), z1.set(x1.get() - y1.get()))) # -(x1 - y1) - .Else(pt.Seq(z0.set(pt.Int(1)), z1.set(y1.get() - x1.get()))) # +(y1 - x1) + # .Then(pt.Seq(z0.set(False), z1.set(x1.get() - y1.get()))) # -(x1 - y1) + # .Else(pt.Seq(z0.set(True), z1.set(y1.get() - x1.get()))) # +(y1 - x1) + .Then(output.set(f, x1.get() - y1.get())).Else( # -(x1 - y1) + output.set(t, y1.get() - x1.get()) + ) # +(y1 - x1) ), - output.set(z0.get(), z1.get()), + # output.set( + # z0.get(), + # z1.get(), + # ), ) -@pytest.mark.skip("problem on line 143") +# @pytest.mark.skip("problem on line 143") def test_minus(): bbpt = BlackboxPyTealer(minus, pt.Mode.Application) approval = bbpt.program() From 795182ba6a338cf0cdec653fed7cedb8c7fcebbb Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 6 May 2022 08:58:18 -0500 Subject: [PATCH 071/170] minus passes graviton --- tests/integration/graviton_abi_test.py | 48 ++++++++++---------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index 75435fce4..ba97b46e8 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -113,54 +113,37 @@ def minus(x: Int65, y: Int65, *, output: Int65): x1 = pt.abi.Uint64() y0 = pt.abi.Bool() y1 = pt.abi.Uint64() - t, f = pt.abi.Bool(), pt.abi.Bool() - # z0 = pt.abi.Bool() - # z1 = pt.abi.Uint64() + z0 = pt.abi.Bool() + z1 = pt.abi.Uint64() return pt.Seq( - t.set(True), - f.set(False), x0.set(x[0]), x1.set(x[1]), y0.set(y[0]), y1.set(y[1]), - # z0.set(pt.Int(0)), - # z1.set(0), # Case I. x, y positive pt.If(pt.And(x0.get(), y0.get())).Then( pt.If(x1.get() >= y1.get()) - # .Then(pt.Seq(z0.set(True), z1.set(x1.get() - y1.get()))) # +(x1 - y1) - # .Else(pt.Seq(z0.set(False), z1.set(y1.get() - x1.get()))) # -(y1 - x1) - .Then(output.set(t, x1.get() - y1.get())).Else( # +(x1 - y1) - output.set(f, y1.get() - x1.get()) - ) # -(y1 - x1) + .Then(pt.Seq(z0.set(True), z1.set(x1.get() - y1.get()))) # +(x1 - y1) + .Else(pt.Seq(z0.set(False), z1.set(y1.get() - x1.get()))) # -(y1 - x1) ) # Case II. x positive, y negative .ElseIf(pt.And(x0.get(), pt.Not(y0.get()))).Then( - # pt.Seq(z0.set(True), z1.set(x1.get() + y1.get())) - output.set(t, x1.get() + y1.get()) + pt.Seq(z0.set(True), z1.set(x1.get() + y1.get())) ) # x1 + y1 # Case III. x negative, y positive .ElseIf(pt.And(pt.Not(x0.get()), y0.get())).Then( - # pt.Seq(z0.set(False), z1.set(x1.get() + y1.get())) - output.set(f, set(x1.get() + y1.get())) + pt.Seq(z0.set(False), z1.set(x1.get() + y1.get())) ) # -(x1 + y1) # Case IV. x, y negative .Else( pt.If(x1.get() >= y1.get()) - # .Then(pt.Seq(z0.set(False), z1.set(x1.get() - y1.get()))) # -(x1 - y1) - # .Else(pt.Seq(z0.set(True), z1.set(y1.get() - x1.get()))) # +(y1 - x1) - .Then(output.set(f, x1.get() - y1.get())).Else( # -(x1 - y1) - output.set(t, y1.get() - x1.get()) - ) # +(y1 - x1) + .Then(pt.Seq(z0.set(False), z1.set(x1.get() - y1.get()))) # -(x1 - y1) + .Else(pt.Seq(z0.set(True), z1.set(y1.get() - x1.get()))) # +(y1 - x1) ), - # output.set( - # z0.get(), - # z1.get(), - # ), + output.set(z0, z1), ) -# @pytest.mark.skip("problem on line 143") def test_minus(): bbpt = BlackboxPyTealer(minus, pt.Mode.Application) approval = bbpt.program() @@ -168,14 +151,18 @@ def test_minus(): abi_argument_types = bbpt.abi_argument_types() abi_return_type = bbpt.abi_return_type() - def pynum_to_tuple_rep(n): + def pynum_to_tuple(n): return (n > 0, abs(n)) + def pytuple_to_num(t): + s, x = t + return x if s else -x + N = 100 random.seed(42) choices = range(-9_999, 10_000) inputs = [ - (pynum_to_tuple_rep(x), pynum_to_tuple_rep(y)) + (pynum_to_tuple(x), pynum_to_tuple(y)) for x, y in zip(random.sample(choices, N), random.sample(choices, N)) ] @@ -184,7 +171,10 @@ def pynum_to_tuple_rep(n): algod, teal, inputs, abi_argument_types, abi_return_type ) - _ = inspectors + for i, inspector in enumerate(inspectors): + args = inputs[i] + x, y = tuple(map(pytuple_to_num, args)) + assert x - y == pytuple_to_num(inspector.last_log()) """ From ae5d12108e6ab8bdd81c78ed45072d8152eafdc9 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 6 May 2022 09:03:31 -0500 Subject: [PATCH 072/170] lint + warning commentary --- tests/integration/graviton_abi_test.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index ba97b46e8..673bd099a 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -1,8 +1,6 @@ import random from typing import Literal -import pytest - from graviton.blackbox import DryRunExecutor import pyteal as pt @@ -109,6 +107,11 @@ def abi_sum( @Blackbox(input_types=[None, None]) @pt.ABIReturnSubroutine def minus(x: Int65, y: Int65, *, output: Int65): + """ + NOTE: this is example ONLY for the purpose of demo'ing ABISubroutine + Gravitons's capabilities + and not the recommended approach for implementing integers. + A better appraoch would stick to `Uint64` as the base type and use 2's complement arithmetic. + """ x0 = pt.abi.Bool() x1 = pt.abi.Uint64() y0 = pt.abi.Bool() From e0e1c5df44e860e8bfcba9bed77534b8b4912f51 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 6 May 2022 09:04:43 -0500 Subject: [PATCH 073/170] better wording --- tests/integration/graviton_abi_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index 673bd099a..f8aa50266 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -108,8 +108,8 @@ def abi_sum( @pt.ABIReturnSubroutine def minus(x: Int65, y: Int65, *, output: Int65): """ - NOTE: this is example ONLY for the purpose of demo'ing ABISubroutine + Gravitons's capabilities - and not the recommended approach for implementing integers. + WARNING: this example is ONLY for the purpose of demo'ing ABISubroutine + Gravitons's capabilities + and NOT the recommended approach for implementing integers. A better appraoch would stick to `Uint64` as the base type and use 2's complement arithmetic. """ x0 = pt.abi.Bool() From f0ff4594ef69fbc70780b01386f1161634abe581 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 6 May 2022 09:37:25 -0500 Subject: [PATCH 074/170] use Case() instead --- tests/integration/graviton_abi_test.py | 58 +++++++++++++++++--------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index f8aa50266..6ca3f469e 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -123,25 +123,41 @@ def minus(x: Int65, y: Int65, *, output: Int65): x1.set(x[1]), y0.set(y[0]), y1.set(y[1]), - # Case I. x, y positive - pt.If(pt.And(x0.get(), y0.get())).Then( - pt.If(x1.get() >= y1.get()) - .Then(pt.Seq(z0.set(True), z1.set(x1.get() - y1.get()))) # +(x1 - y1) - .Else(pt.Seq(z0.set(False), z1.set(y1.get() - x1.get()))) # -(y1 - x1) - ) - # Case II. x positive, y negative - .ElseIf(pt.And(x0.get(), pt.Not(y0.get()))).Then( - pt.Seq(z0.set(True), z1.set(x1.get() + y1.get())) - ) # x1 + y1 - # Case III. x negative, y positive - .ElseIf(pt.And(pt.Not(x0.get()), y0.get())).Then( - pt.Seq(z0.set(False), z1.set(x1.get() + y1.get())) - ) # -(x1 + y1) - # Case IV. x, y negative - .Else( - pt.If(x1.get() >= y1.get()) - .Then(pt.Seq(z0.set(False), z1.set(x1.get() - y1.get()))) # -(x1 - y1) - .Else(pt.Seq(z0.set(True), z1.set(y1.get() - x1.get()))) # +(y1 - x1) + pt.Cond( + # Case I. x, y positive + [ + pt.And(x0.get(), y0.get()), + pt.Seq( + z0.set(x1.get() >= y1.get()), + z1.set( + pt.If(x1.get() <= y1.get()) + .Then(y1.get() - x1.get()) + .Else(x1.get() - y1.get()) + ), + ), + ], + # Case II. x positive, y negative + [ + pt.And(x0.get(), pt.Not(y0.get())), + pt.Seq(z0.set(True), z1.set(x1.get() + y1.get())), + ], + # Case III. x negative, y positive + [ + pt.And(pt.Not(x0.get()), y0.get()), + pt.Seq(z0.set(False), z1.set(x1.get() + y1.get())), + ], + # Case IV. x, y negative + [ + pt.Int(1), + pt.Seq( + z0.set(x1.get() <= y1.get()), + z1.set( + pt.If(x1.get() <= y1.get()) + .Then(y1.get() - x1.get()) + .Else(x1.get() - y1.get()) + ), + ), + ], ), output.set(z0, z1), ) @@ -177,7 +193,9 @@ def pytuple_to_num(t): for i, inspector in enumerate(inspectors): args = inputs[i] x, y = tuple(map(pytuple_to_num, args)) - assert x - y == pytuple_to_num(inspector.last_log()) + assert x - y == pytuple_to_num(inspector.last_log()), inspector.report( + args, f"failed for {args}", row=i + ) """ From 299e523ef31d61e6f8b1613db568a1ea007c5f28 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 6 May 2022 10:17:29 -0500 Subject: [PATCH 075/170] add "slick" minus version --- tests/integration/graviton_abi_test.py | 80 ++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 12 deletions(-) diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index 6ca3f469e..99ba9bcb9 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -106,7 +106,7 @@ def abi_sum( @Blackbox(input_types=[None, None]) @pt.ABIReturnSubroutine -def minus(x: Int65, y: Int65, *, output: Int65): +def minus_cond(x: Int65, y: Int65, *, output: Int65): """ WARNING: this example is ONLY for the purpose of demo'ing ABISubroutine + Gravitons's capabilities and NOT the recommended approach for implementing integers. @@ -163,12 +163,58 @@ def minus(x: Int65, y: Int65, *, output: Int65): ) +@Blackbox(input_types=[None, None]) +@pt.ABIReturnSubroutine +def minus_slick(x: Int65, y: Int65, *, output: Int65): + """ + WARNING: this example is ONLY for the purpose of demo'ing ABISubroutine + Gravitons's capabilities + and NOT the recommended approach for implementing integers. + A better appraoch would stick to `Uint64` as the base type and use 2's complement arithmetic. + """ + x0 = pt.abi.Bool() + x1 = pt.abi.Uint64() + y0 = pt.abi.Bool() + y1 = pt.abi.Uint64() + z0 = pt.abi.Bool() + z1 = pt.abi.Uint64() + return pt.Seq( + x0.set(x[0]), + x1.set(x[1]), + y0.set(y[0]), + y1.set(y[1]), + pt.If(x0.get() == y0.get()) + .Then( # Case I. x, y same signature + pt.Seq( + z0.set(pt.Not(x0.get()) ^ (x1.get() >= y1.get())), + z1.set( + pt.If(x1.get() <= y1.get()) + .Then(y1.get() - x1.get()) + .Else(x1.get() - y1.get()) + ), + ) + ) + .Else( # Case II. x, y opposite signatures + pt.Seq( + z0.set(x0.get()), + z1.set(x1.get() + y1.get()), + ), + ), + output.set(z0, z1), + ) + + def test_minus(): - bbpt = BlackboxPyTealer(minus, pt.Mode.Application) - approval = bbpt.program() - teal = pt.compileTeal(approval(), pt.Mode.Application, version=6) - abi_argument_types = bbpt.abi_argument_types() - abi_return_type = bbpt.abi_return_type() + bbpt_slick = BlackboxPyTealer(minus_slick, pt.Mode.Application) + approval_slick = bbpt_slick.program() + teal_slick = pt.compileTeal(approval_slick(), pt.Mode.Application, version=6) + + bbpt_cond = BlackboxPyTealer(minus_cond, pt.Mode.Application) + approval_cond = bbpt_cond.program() + teal_cond = pt.compileTeal(approval_cond(), pt.Mode.Application, version=6) + + # same types, so no need to dupe: + abi_argument_types = bbpt_slick.abi_argument_types() + abi_return_type = bbpt_slick.abi_return_type() def pynum_to_tuple(n): return (n > 0, abs(n)) @@ -186,16 +232,26 @@ def pytuple_to_num(t): ] algod = algod_with_assertion() - inspectors = DryRunExecutor.dryrun_app_on_sequence( - algod, teal, inputs, abi_argument_types, abi_return_type + inspectors_slick = DryRunExecutor.dryrun_app_on_sequence( + algod, teal_slick, inputs, abi_argument_types, abi_return_type + ) + inspectors_cond = DryRunExecutor.dryrun_app_on_sequence( + algod, teal_cond, inputs, abi_argument_types, abi_return_type ) - for i, inspector in enumerate(inspectors): + for i, inspector_slick in enumerate(inspectors_slick): + inspector_cond = inspectors_cond[i] + args = inputs[i] x, y = tuple(map(pytuple_to_num, args)) - assert x - y == pytuple_to_num(inspector.last_log()), inspector.report( - args, f"failed for {args}", row=i - ) + + assert x - y == pytuple_to_num( + inspector_slick.last_log() + ), inspector_slick.report(args, f"failed for {args}", row=i) + + assert x - y == pytuple_to_num( + inspector_cond.last_log() + ), inspector_cond.report(args, f"failed for {args}", row=i) """ From 39426a4f70216f46ba2836c81fcfa296d05e9468 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 6 May 2022 11:20:55 -0500 Subject: [PATCH 076/170] integer multiplication --- tests/integration/graviton_abi_test.py | 82 +++++++++++++++++++------- 1 file changed, 62 insertions(+), 20 deletions(-) diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index 99ba9bcb9..2ae76ce1e 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -165,7 +165,7 @@ def minus_cond(x: Int65, y: Int65, *, output: Int65): @Blackbox(input_types=[None, None]) @pt.ABIReturnSubroutine -def minus_slick(x: Int65, y: Int65, *, output: Int65): +def subtract(x: Int65, y: Int65, *, output: Int65): """ WARNING: this example is ONLY for the purpose of demo'ing ABISubroutine + Gravitons's capabilities and NOT the recommended approach for implementing integers. @@ -203,18 +203,52 @@ def minus_slick(x: Int65, y: Int65, *, output: Int65): ) -def test_minus(): - bbpt_slick = BlackboxPyTealer(minus_slick, pt.Mode.Application) - approval_slick = bbpt_slick.program() - teal_slick = pt.compileTeal(approval_slick(), pt.Mode.Application, version=6) +@Blackbox(input_types=[None, None]) +@pt.ABIReturnSubroutine +def mult(x: Int65, y: Int65, *, output: Int65): + """ + WARNING: this example is ONLY for the purpose of demo'ing ABISubroutine + Gravitons's capabilities + and NOT the recommended approach for implementing integers. + A better appraoch would stick to `Uint64` as the base type and use 2's complement arithmetic. + """ + # return output.set(pt.Not(x[0].get() ^ y[0].get()), x[1].get() * y[1].get()) + x0 = pt.abi.Bool() + x1 = pt.abi.Uint64() + y0 = pt.abi.Bool() + y1 = pt.abi.Uint64() + z0 = pt.abi.Bool() + z1 = pt.abi.Uint64() + return pt.Seq( + x0.set(x[0]), + x1.set(x[1]), + y0.set(y[0]), + y1.set(y[1]), + z0.set(pt.Not(x0.get() ^ y0.get())), + z1.set(x1.get() * y1.get()), + output.set(z0, z1), + ) - bbpt_cond = BlackboxPyTealer(minus_cond, pt.Mode.Application) - approval_cond = bbpt_cond.program() - teal_cond = pt.compileTeal(approval_cond(), pt.Mode.Application, version=6) + +def test_integer65(): + bbpt_subtract_slick = BlackboxPyTealer(subtract, pt.Mode.Application) + approval_subtract_slick = bbpt_subtract_slick.program() + teal_subtract_slick = pt.compileTeal( + approval_subtract_slick(), pt.Mode.Application, version=6 + ) + + bbpt_subtract_cond = BlackboxPyTealer(minus_cond, pt.Mode.Application) + approval_subtract_cond = bbpt_subtract_cond.program() + teal_subtract_cond = pt.compileTeal( + approval_subtract_cond(), pt.Mode.Application, version=6 + ) + + bbpt_mult = BlackboxPyTealer(mult, pt.Mode.Application) + approval_mult = bbpt_mult.program() + teal_mult = pt.compileTeal(approval_mult(), pt.Mode.Application, version=6) # same types, so no need to dupe: - abi_argument_types = bbpt_slick.abi_argument_types() - abi_return_type = bbpt_slick.abi_return_type() + abi_argument_types = bbpt_subtract_slick.abi_argument_types() + abi_return_type = bbpt_subtract_slick.abi_return_type() def pynum_to_tuple(n): return (n > 0, abs(n)) @@ -232,26 +266,34 @@ def pytuple_to_num(t): ] algod = algod_with_assertion() - inspectors_slick = DryRunExecutor.dryrun_app_on_sequence( - algod, teal_slick, inputs, abi_argument_types, abi_return_type + inspectors_subtract_slick = DryRunExecutor.dryrun_app_on_sequence( + algod, teal_subtract_slick, inputs, abi_argument_types, abi_return_type + ) + inspectors_subtract_cond = DryRunExecutor.dryrun_app_on_sequence( + algod, teal_subtract_cond, inputs, abi_argument_types, abi_return_type ) - inspectors_cond = DryRunExecutor.dryrun_app_on_sequence( - algod, teal_cond, inputs, abi_argument_types, abi_return_type + inspectors_mult = DryRunExecutor.dryrun_app_on_sequence( + algod, teal_mult, inputs, abi_argument_types, abi_return_type ) - for i, inspector_slick in enumerate(inspectors_slick): - inspector_cond = inspectors_cond[i] + for i, inspector_subtract_slick in enumerate(inspectors_subtract_slick): + inspector_subtract_cond = inspectors_subtract_cond[i] + inspector_mult = inspectors_mult[i] args = inputs[i] x, y = tuple(map(pytuple_to_num, args)) assert x - y == pytuple_to_num( - inspector_slick.last_log() - ), inspector_slick.report(args, f"failed for {args}", row=i) + inspector_subtract_slick.last_log() + ), inspector_subtract_slick.report(args, f"failed for {args}", row=i) assert x - y == pytuple_to_num( - inspector_cond.last_log() - ), inspector_cond.report(args, f"failed for {args}", row=i) + inspector_subtract_cond.last_log() + ), inspector_subtract_cond.report(args, f"failed for {args}", row=i) + + assert x * y == pytuple_to_num( + inspector_mult.last_log() + ), inspectors_mult.report(args, f"failed for {args}", row=i) """ From edbd2c111a1797da1656f35d3448a125921d0ecd Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 6 May 2022 12:01:39 -0500 Subject: [PATCH 077/170] negation --- tests/integration/graviton_abi_test.py | 78 ++++++++++++++++++++++---- 1 file changed, 66 insertions(+), 12 deletions(-) diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index 2ae76ce1e..a1e370da2 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -1,9 +1,11 @@ +from pydoc import ispackage import random from typing import Literal from graviton.blackbox import DryRunExecutor import pyteal as pt +from pyteal.ast.subroutine import ABIReturnSubroutine from tests.blackbox import ( Blackbox, @@ -98,10 +100,11 @@ def abi_sum( _ = todo_use_these_guys -# ---- subtraction example ---- # +# ---- Integers and Complex Integral Numbers (aka Gaussian Integers) ---- # Int65 = pt.abi.Tuple2[pt.abi.Bool, pt.abi.Uint64] +Complex130 = pt.abi.Tuple2[Int65, Int65] @Blackbox(input_types=[None, None]) @@ -211,6 +214,7 @@ def mult(x: Int65, y: Int65, *, output: Int65): and NOT the recommended approach for implementing integers. A better appraoch would stick to `Uint64` as the base type and use 2's complement arithmetic. """ + # TODO: can we get something like the following one-liner working? # return output.set(pt.Not(x[0].get() ^ y[0].get()), x[1].get() * y[1].get()) x0 = pt.abi.Bool() x1 = pt.abi.Uint64() @@ -229,6 +233,23 @@ def mult(x: Int65, y: Int65, *, output: Int65): ) +@Blackbox(input_types=[None]) +@ABIReturnSubroutine +def negate(x: Int65, *, output: Int65): + # TODO: can I haz a one-liner pls???? + x0 = pt.abi.Bool() + x1 = pt.abi.Uint64() + z0 = pt.abi.Bool() + z1 = pt.abi.Uint64() + return pt.Seq( + x0.set(x[0]), + x1.set(x[1]), + z0.set(pt.Not(x0.get())), + z1.set(x1.get()), + output.set(z0, z1), + ) + + def test_integer65(): bbpt_subtract_slick = BlackboxPyTealer(subtract, pt.Mode.Application) approval_subtract_slick = bbpt_subtract_slick.program() @@ -246,8 +267,13 @@ def test_integer65(): approval_mult = bbpt_mult.program() teal_mult = pt.compileTeal(approval_mult(), pt.Mode.Application, version=6) + bbpt_negate = BlackboxPyTealer(negate, pt.Mode.Application) + approval_negate = bbpt_negate.program() + teal_negate = pt.compileTeal(approval_negate(), pt.Mode.Application, version=6) + # same types, so no need to dupe: - abi_argument_types = bbpt_subtract_slick.abi_argument_types() + unary_abi_argument_types = bbpt_negate.abi_argument_types() + binary_abi_argument_types = bbpt_subtract_slick.abi_argument_types() abi_return_type = bbpt_subtract_slick.abi_return_type() def pynum_to_tuple(n): @@ -259,41 +285,69 @@ def pytuple_to_num(t): N = 100 random.seed(42) + choices = range(-9_999, 10_000) - inputs = [ + unary_inputs = [(pynum_to_tuple(x),) for x in random.sample(choices, N)] + + binary_inputs = [ (pynum_to_tuple(x), pynum_to_tuple(y)) for x, y in zip(random.sample(choices, N), random.sample(choices, N)) ] algod = algod_with_assertion() + inspectors_subtract_slick = DryRunExecutor.dryrun_app_on_sequence( - algod, teal_subtract_slick, inputs, abi_argument_types, abi_return_type + algod, + teal_subtract_slick, + binary_inputs, + binary_abi_argument_types, + abi_return_type, ) inspectors_subtract_cond = DryRunExecutor.dryrun_app_on_sequence( - algod, teal_subtract_cond, inputs, abi_argument_types, abi_return_type + algod, + teal_subtract_cond, + binary_inputs, + binary_abi_argument_types, + abi_return_type, ) inspectors_mult = DryRunExecutor.dryrun_app_on_sequence( - algod, teal_mult, inputs, abi_argument_types, abi_return_type + algod, teal_mult, binary_inputs, binary_abi_argument_types, abi_return_type + ) + inspectors_negate = DryRunExecutor.dryrun_app_on_sequence( + algod, teal_negate, unary_inputs, unary_abi_argument_types, abi_return_type ) - for i, inspector_subtract_slick in enumerate(inspectors_subtract_slick): + for i in range(N): + inspector_subtract_slick = inspectors_subtract_slick[i] inspector_subtract_cond = inspectors_subtract_cond[i] inspector_mult = inspectors_mult[i] + inspector_negate = inspectors_negate[i] + + binary_args = binary_inputs[i] + x, y = tuple(map(pytuple_to_num, binary_args)) - args = inputs[i] - x, y = tuple(map(pytuple_to_num, args)) + unary_args = unary_inputs[i] + u = pytuple_to_num(unary_args[0]) assert x - y == pytuple_to_num( inspector_subtract_slick.last_log() - ), inspector_subtract_slick.report(args, f"failed for {args}", row=i) + ), inspector_subtract_slick.report( + binary_args, f"failed for {binary_args}", row=i + ) assert x - y == pytuple_to_num( inspector_subtract_cond.last_log() - ), inspector_subtract_cond.report(args, f"failed for {args}", row=i) + ), inspector_subtract_cond.report( + binary_args, f"failed for {binary_args}", row=i + ) assert x * y == pytuple_to_num( inspector_mult.last_log() - ), inspectors_mult.report(args, f"failed for {args}", row=i) + ), inspector_mult.report(binary_args, f"failed for {binary_args}", row=i) + + assert -u == pytuple_to_num( + inspector_negate.last_log() + ), inspector_negate.report(unary_args, f"failed for {unary_args}", row=i) """ From f7af7663f33cc642599927c070f24bbc250c81f2 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 6 May 2022 12:04:18 -0500 Subject: [PATCH 078/170] vscode is too smart --- tests/integration/graviton_abi_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index a1e370da2..52807a14a 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -1,4 +1,3 @@ -from pydoc import ispackage import random from typing import Literal From 3905d2d3792b70068a88edcda388568355f6a99d Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 6 May 2022 12:30:00 -0500 Subject: [PATCH 079/170] integer addition --- tests/integration/graviton_abi_test.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index 52807a14a..3f813553f 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -249,6 +249,12 @@ def negate(x: Int65, *, output: Int65): ) +@Blackbox(input_types=[None, None]) +@ABIReturnSubroutine +def add(x: Int65, y: Int65, *, output: Int65): + return pt.Seq(y.set(negate(y)), output.set(subtract(x, y))) + + def test_integer65(): bbpt_subtract_slick = BlackboxPyTealer(subtract, pt.Mode.Application) approval_subtract_slick = bbpt_subtract_slick.program() @@ -270,6 +276,10 @@ def test_integer65(): approval_negate = bbpt_negate.program() teal_negate = pt.compileTeal(approval_negate(), pt.Mode.Application, version=6) + bbpt_add = BlackboxPyTealer(add, pt.Mode.Application) + approval_add = bbpt_add.program() + teal_add = pt.compileTeal(approval_add(), pt.Mode.Application, version=6) + # same types, so no need to dupe: unary_abi_argument_types = bbpt_negate.abi_argument_types() binary_abi_argument_types = bbpt_subtract_slick.abi_argument_types() @@ -295,6 +305,7 @@ def pytuple_to_num(t): algod = algod_with_assertion() + # Binary: inspectors_subtract_slick = DryRunExecutor.dryrun_app_on_sequence( algod, teal_subtract_slick, @@ -312,6 +323,11 @@ def pytuple_to_num(t): inspectors_mult = DryRunExecutor.dryrun_app_on_sequence( algod, teal_mult, binary_inputs, binary_abi_argument_types, abi_return_type ) + inspectors_add = DryRunExecutor.dryrun_app_on_sequence( + algod, teal_add, binary_inputs, binary_abi_argument_types, abi_return_type + ) + + # Unary: inspectors_negate = DryRunExecutor.dryrun_app_on_sequence( algod, teal_negate, unary_inputs, unary_abi_argument_types, abi_return_type ) @@ -320,6 +336,8 @@ def pytuple_to_num(t): inspector_subtract_slick = inspectors_subtract_slick[i] inspector_subtract_cond = inspectors_subtract_cond[i] inspector_mult = inspectors_mult[i] + inspector_add = inspectors_add[i] + inspector_negate = inspectors_negate[i] binary_args = binary_inputs[i] @@ -344,6 +362,10 @@ def pytuple_to_num(t): inspector_mult.last_log() ), inspector_mult.report(binary_args, f"failed for {binary_args}", row=i) + assert x + y == pytuple_to_num(inspector_add.last_log()), inspector_add.report( + binary_args, f"failed for {binary_args}", row=i + ) + assert -u == pytuple_to_num( inspector_negate.last_log() ), inspector_negate.report(unary_args, f"failed for {unary_args}", row=i) From a61f67252df69ad13568c4fb38a5be96824188c4 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 6 May 2022 14:24:51 -0500 Subject: [PATCH 080/170] complex addition works --- tests/integration/graviton_abi_test.py | 138 ++++++++++++++++++------- 1 file changed, 99 insertions(+), 39 deletions(-) diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index 3f813553f..239b52b36 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -108,7 +108,7 @@ def abi_sum( @Blackbox(input_types=[None, None]) @pt.ABIReturnSubroutine -def minus_cond(x: Int65, y: Int65, *, output: Int65): +def int65_minus_cond(x: Int65, y: Int65, *, output: Int65): """ WARNING: this example is ONLY for the purpose of demo'ing ABISubroutine + Gravitons's capabilities and NOT the recommended approach for implementing integers. @@ -167,7 +167,7 @@ def minus_cond(x: Int65, y: Int65, *, output: Int65): @Blackbox(input_types=[None, None]) @pt.ABIReturnSubroutine -def subtract(x: Int65, y: Int65, *, output: Int65): +def int65_sub(x: Int65, y: Int65, *, output: Int65): """ WARNING: this example is ONLY for the purpose of demo'ing ABISubroutine + Gravitons's capabilities and NOT the recommended approach for implementing integers. @@ -207,7 +207,7 @@ def subtract(x: Int65, y: Int65, *, output: Int65): @Blackbox(input_types=[None, None]) @pt.ABIReturnSubroutine -def mult(x: Int65, y: Int65, *, output: Int65): +def int65_mult(x: Int65, y: Int65, *, output: Int65): """ WARNING: this example is ONLY for the purpose of demo'ing ABISubroutine + Gravitons's capabilities and NOT the recommended approach for implementing integers. @@ -234,7 +234,7 @@ def mult(x: Int65, y: Int65, *, output: Int65): @Blackbox(input_types=[None]) @ABIReturnSubroutine -def negate(x: Int65, *, output: Int65): +def int65_negate(x: Int65, *, output: Int65): # TODO: can I haz a one-liner pls???? x0 = pt.abi.Bool() x1 = pt.abi.Uint64() @@ -251,32 +251,52 @@ def negate(x: Int65, *, output: Int65): @Blackbox(input_types=[None, None]) @ABIReturnSubroutine -def add(x: Int65, y: Int65, *, output: Int65): - return pt.Seq(y.set(negate(y)), output.set(subtract(x, y))) +def int65_add(x: Int65, y: Int65, *, output: Int65): + return pt.Seq(y.set(int65_negate(y)), output.set(int65_sub(x, y))) + + +@Blackbox(input_types=[None, None]) +@ABIReturnSubroutine +def complex130_add(x: Complex130, y: Complex130, *, output: Complex130): + x0 = pt.abi.make(Int65) + x1 = pt.abi.make(Int65) + y0 = pt.abi.make(Int65) + y1 = pt.abi.make(Int65) + z0 = pt.abi.make(Int65) + z1 = pt.abi.make(Int65) + return pt.Seq( + x0.set(x[0]), + x1.set(x[1]), + y0.set(y[0]), + y1.set(y[1]), + z0.set(int65_add(x0, y0)), + z1.set(int65_add(x1, y1)), + output.set(z0, z1), + ) def test_integer65(): - bbpt_subtract_slick = BlackboxPyTealer(subtract, pt.Mode.Application) + bbpt_subtract_slick = BlackboxPyTealer(int65_sub, pt.Mode.Application) approval_subtract_slick = bbpt_subtract_slick.program() teal_subtract_slick = pt.compileTeal( approval_subtract_slick(), pt.Mode.Application, version=6 ) - bbpt_subtract_cond = BlackboxPyTealer(minus_cond, pt.Mode.Application) + bbpt_subtract_cond = BlackboxPyTealer(int65_minus_cond, pt.Mode.Application) approval_subtract_cond = bbpt_subtract_cond.program() teal_subtract_cond = pt.compileTeal( approval_subtract_cond(), pt.Mode.Application, version=6 ) - bbpt_mult = BlackboxPyTealer(mult, pt.Mode.Application) + bbpt_mult = BlackboxPyTealer(int65_mult, pt.Mode.Application) approval_mult = bbpt_mult.program() teal_mult = pt.compileTeal(approval_mult(), pt.Mode.Application, version=6) - bbpt_negate = BlackboxPyTealer(negate, pt.Mode.Application) + bbpt_negate = BlackboxPyTealer(int65_negate, pt.Mode.Application) approval_negate = bbpt_negate.program() teal_negate = pt.compileTeal(approval_negate(), pt.Mode.Application, version=6) - bbpt_add = BlackboxPyTealer(add, pt.Mode.Application) + bbpt_add = BlackboxPyTealer(int65_add, pt.Mode.Application) approval_add = bbpt_add.program() teal_add = pt.compileTeal(approval_add(), pt.Mode.Application, version=6) @@ -333,6 +353,12 @@ def pytuple_to_num(t): ) for i in range(N): + binary_args = binary_inputs[i] + x, y = tuple(map(pytuple_to_num, binary_args)) + + unary_args = unary_inputs[i] + u = pytuple_to_num(unary_args[0]) + inspector_subtract_slick = inspectors_subtract_slick[i] inspector_subtract_cond = inspectors_subtract_cond[i] inspector_mult = inspectors_mult[i] @@ -340,12 +366,6 @@ def pytuple_to_num(t): inspector_negate = inspectors_negate[i] - binary_args = binary_inputs[i] - x, y = tuple(map(pytuple_to_num, binary_args)) - - unary_args = unary_inputs[i] - u = pytuple_to_num(unary_args[0]) - assert x - y == pytuple_to_num( inspector_subtract_slick.last_log() ), inspector_subtract_slick.report( @@ -371,25 +391,65 @@ def pytuple_to_num(t): ), inspector_negate.report(unary_args, f"failed for {unary_args}", row=i) -""" -so what does tests/blackbox.py::blackbox_abi() need to do? - -First decision points: -A) what happens when @Blackbox(input_types=...) is slapped on top of a @Subroutine with abi annotations? -B) do we need to allow (A), or do we just insist that user provides @ABIReturnSubroutine? -C) if we allow (A) [and even if not] should we still require input_types for the Expr's and ScratchVar's? -D) should we attempt to integrate Atomic Transaction Composer -.... leaning towards disallowing (A) for the lowest hanging fruit approach -.... leaning towards "No" to (D) because ATC doesn't play nice with Dry Run and we're only wrapping one - function at a time (not an app with several methods) - -Clear requirements. blackbox_abi() should: -1) handle apps and lsigs -2) handle Expr/ScratchVar/abi inputs (with the first two defined in input_types=...) -3) should handle output coming from the output_kwarg (but I believe this is automatic already) - -Convincing examples: -* matrix multiplication with very large number of bits to avoid overflow -* infinite precision signed integer that can handle addition, subtraction and multiplication -* complex/rational numbers with multiplication and addition, subtraction, multiplication and division -""" +def test_complex130(): + bbpt_cplx_add = BlackboxPyTealer(complex130_add, pt.Mode.Application) + approval_cplx_add = bbpt_cplx_add.program() + teal_cplx_add = pt.compileTeal(approval_cplx_add(), pt.Mode.Application, version=6) + + # unary_abi + binary_abi_arguments = bbpt_cplx_add.abi_argument_types() + abi_return_type = bbpt_cplx_add.abi_return_type() + + def pyint_to_tuple(n): + return (n > 0, abs(n)) + + def pycomplex_to_tuple(z): + return (pyint_to_tuple(int(z.real)), pyint_to_tuple(int(z.imag))) + + def pytuple_to_int(t): + s, x = t + return x if s else -x + + def pytuple_to_complex(tt): + tx, ty = tt + return complex(pytuple_to_int(tx), pytuple_to_int(ty)) + + N = 100 + # just for fun - no random seed - but this shouldn't be flakey + + choices = range(-999_999, 1_000_000) + + unary_inputs = [ + pycomplex_to_tuple(complex(x, y)) + for x, y in zip(random.sample(choices, N), random.sample(choices, N)) + ] + + binary_inputs = [ + (pycomplex_to_tuple(complex(x, y)), pycomplex_to_tuple(complex(z, w))) + for x, y, z, w in zip( + random.sample(choices, N), + random.sample(choices, N), + random.sample(choices, N), + random.sample(choices, N), + ) + ] + + algod = algod_with_assertion() + + # Binary: + inspectors_cplx_add = DryRunExecutor.dryrun_app_on_sequence( + algod, teal_cplx_add, binary_inputs, binary_abi_arguments, abi_return_type + ) + + for i in range(N): + binary_args = binary_inputs[i] + x, y = tuple(map(pytuple_to_complex, binary_args)) + + # unary_args = unary_inputs[i] + # u = pytuple_to_num(unary_args[0]) + + inspector_cplx_add = inspectors_cplx_add[i] + + assert x + y == pytuple_to_complex( + inspector_cplx_add.last_log() + ), inspector_cplx_add.report(binary_args, f"failed for {binary_args}", row=i) From 0440587625881ece78f019cba41fe813870da176 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 6 May 2022 14:28:12 -0500 Subject: [PATCH 081/170] lint --- tests/integration/graviton_abi_test.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index 239b52b36..0885f2882 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -275,6 +275,12 @@ def complex130_add(x: Complex130, y: Complex130, *, output: Complex130): ) +@Blackbox(input_types=[None]) +@ABIReturnSubroutine +def complex130_real(x: Complex130, *, output: Int65): + return output.set(x[0]) + + def test_integer65(): bbpt_subtract_slick = BlackboxPyTealer(int65_sub, pt.Mode.Application) approval_subtract_slick = bbpt_subtract_slick.program() @@ -423,6 +429,7 @@ def pytuple_to_complex(tt): pycomplex_to_tuple(complex(x, y)) for x, y in zip(random.sample(choices, N), random.sample(choices, N)) ] + _ = unary_inputs binary_inputs = [ (pycomplex_to_tuple(complex(x, y)), pycomplex_to_tuple(complex(z, w))) From 4469c11063e070efdd300ff5d078b9b718ee3622 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 6 May 2022 14:45:26 -0500 Subject: [PATCH 082/170] pass first unary complex function test --- tests/integration/graviton_abi_test.py | 42 +++++++++++++++++++++----- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index 0885f2882..1b6e76f79 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -402,9 +402,17 @@ def test_complex130(): approval_cplx_add = bbpt_cplx_add.program() teal_cplx_add = pt.compileTeal(approval_cplx_add(), pt.Mode.Application, version=6) - # unary_abi - binary_abi_arguments = bbpt_cplx_add.abi_argument_types() - abi_return_type = bbpt_cplx_add.abi_return_type() + bbpt_complex_real = BlackboxPyTealer(complex130_real, pt.Mode.Application) + approval_cplx_real = bbpt_complex_real.program() + teal_cplx_real = pt.compileTeal( + approval_cplx_real(), pt.Mode.Application, version=6 + ) + + unary_abi_argument_types = bbpt_complex_real.abi_argument_types() + binary_abi_argument_types = bbpt_cplx_add.abi_argument_types() + + real_abi_return_type = bbpt_complex_real.abi_return_type() + complex_abi_return_type = bbpt_cplx_add.abi_return_type() def pyint_to_tuple(n): return (n > 0, abs(n)) @@ -426,10 +434,9 @@ def pytuple_to_complex(tt): choices = range(-999_999, 1_000_000) unary_inputs = [ - pycomplex_to_tuple(complex(x, y)) + (pycomplex_to_tuple(complex(x, y)),) for x, y in zip(random.sample(choices, N), random.sample(choices, N)) ] - _ = unary_inputs binary_inputs = [ (pycomplex_to_tuple(complex(x, y)), pycomplex_to_tuple(complex(z, w))) @@ -445,18 +452,37 @@ def pytuple_to_complex(tt): # Binary: inspectors_cplx_add = DryRunExecutor.dryrun_app_on_sequence( - algod, teal_cplx_add, binary_inputs, binary_abi_arguments, abi_return_type + algod, + teal_cplx_add, + binary_inputs, + binary_abi_argument_types, + complex_abi_return_type, + ) + + # Unary: + inspectors_cplx_real = DryRunExecutor.dryrun_app_on_sequence( + algod, + teal_cplx_real, + unary_inputs, + unary_abi_argument_types, + real_abi_return_type, ) for i in range(N): binary_args = binary_inputs[i] x, y = tuple(map(pytuple_to_complex, binary_args)) - # unary_args = unary_inputs[i] - # u = pytuple_to_num(unary_args[0]) + unary_args = unary_inputs[i] + u = pytuple_to_complex(unary_args[0]) inspector_cplx_add = inspectors_cplx_add[i] + inspector_cplx_real = inspectors_cplx_real[i] + assert x + y == pytuple_to_complex( inspector_cplx_add.last_log() ), inspector_cplx_add.report(binary_args, f"failed for {binary_args}", row=i) + + assert u.real == pytuple_to_int( + inspector_cplx_real.last_log() + ), inspector_cplx_real.report(unary_args, f"failed for {unary_args}", row=i) From df13773e49417ab08a9963c992a42b0daac8c8ee Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 6 May 2022 18:02:20 -0500 Subject: [PATCH 083/170] complex multiplication --- tests/integration/graviton_abi_test.py | 85 ++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index 1b6e76f79..3a3c58f69 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -275,12 +275,49 @@ def complex130_add(x: Complex130, y: Complex130, *, output: Complex130): ) +@Blackbox(input_types=[None, None]) +@ABIReturnSubroutine +def complex130_mult(x: Complex130, y: Complex130, *, output: Complex130): + x0 = pt.abi.make(Int65) + x1 = pt.abi.make(Int65) + y0 = pt.abi.make(Int65) + y1 = pt.abi.make(Int65) + t1 = pt.abi.make(Int65) + t2 = pt.abi.make(Int65) + t3 = pt.abi.make(Int65) + t4 = pt.abi.make(Int65) + z0 = pt.abi.make(Int65) + z1 = pt.abi.make(Int65) + return pt.Seq( + x0.set(x[0]), + x1.set(x[1]), + y0.set(y[0]), + y1.set(y[1]), + # TODO: why can't I chain ABI calls? + # z0.set(int65_sub(int65_mult(x0, y0), int65_mult(x1, y1))), + # z1.set(int65_add(int65_mult(x0, y1), int65_mult(x1, y0))), + t1.set(int65_mult(x0, y0)), + t2.set(int65_mult(x1, y1)), + t3.set(int65_mult(x0, y1)), + t4.set(int65_mult(x1, y0)), + z0.set(int65_sub(t1, t2)), + z1.set(int65_add(t3, t4)), + output.set(z0, z1), + ) + + @Blackbox(input_types=[None]) @ABIReturnSubroutine def complex130_real(x: Complex130, *, output: Int65): return output.set(x[0]) +@Blackbox(input_types=[None]) +@ABIReturnSubroutine +def complex130_imag(x: Complex130, *, output: Int65): + return output.set(x[1]) + + def test_integer65(): bbpt_subtract_slick = BlackboxPyTealer(int65_sub, pt.Mode.Application) approval_subtract_slick = bbpt_subtract_slick.program() @@ -398,16 +435,32 @@ def pytuple_to_num(t): def test_complex130(): + # Binary: + bbpt_cplx_add = BlackboxPyTealer(complex130_add, pt.Mode.Application) approval_cplx_add = bbpt_cplx_add.program() teal_cplx_add = pt.compileTeal(approval_cplx_add(), pt.Mode.Application, version=6) + bbpt_cplx_mult = BlackboxPyTealer(complex130_mult, pt.Mode.Application) + approval_cplx_mult = bbpt_cplx_mult.program() + teal_cplx_mult = pt.compileTeal( + approval_cplx_mult(), pt.Mode.Application, version=6 + ) + + # Unary: + bbpt_complex_real = BlackboxPyTealer(complex130_real, pt.Mode.Application) approval_cplx_real = bbpt_complex_real.program() teal_cplx_real = pt.compileTeal( approval_cplx_real(), pt.Mode.Application, version=6 ) + bbpt_complex_imag = BlackboxPyTealer(complex130_imag, pt.Mode.Application) + approval_cplx_imag = bbpt_complex_imag.program() + teal_cplx_imag = pt.compileTeal( + approval_cplx_imag(), pt.Mode.Application, version=6 + ) + unary_abi_argument_types = bbpt_complex_real.abi_argument_types() binary_abi_argument_types = bbpt_cplx_add.abi_argument_types() @@ -459,6 +512,14 @@ def pytuple_to_complex(tt): complex_abi_return_type, ) + inspectors_cplx_mult = DryRunExecutor.dryrun_app_on_sequence( + algod, + teal_cplx_mult, + binary_inputs, + binary_abi_argument_types, + complex_abi_return_type, + ) + # Unary: inspectors_cplx_real = DryRunExecutor.dryrun_app_on_sequence( algod, @@ -468,6 +529,14 @@ def pytuple_to_complex(tt): real_abi_return_type, ) + inspectors_cplx_imag = DryRunExecutor.dryrun_app_on_sequence( + algod, + teal_cplx_imag, + unary_inputs, + unary_abi_argument_types, + real_abi_return_type, + ) + for i in range(N): binary_args = binary_inputs[i] x, y = tuple(map(pytuple_to_complex, binary_args)) @@ -475,14 +544,30 @@ def pytuple_to_complex(tt): unary_args = unary_inputs[i] u = pytuple_to_complex(unary_args[0]) + # Binary: + inspector_cplx_add = inspectors_cplx_add[i] + inspector_cplx_mult = inspectors_cplx_mult[i] + + # Unary: + inspector_cplx_real = inspectors_cplx_real[i] + inspector_cplx_imag = inspectors_cplx_imag[i] + assert x + y == pytuple_to_complex( inspector_cplx_add.last_log() ), inspector_cplx_add.report(binary_args, f"failed for {binary_args}", row=i) + assert x * y == pytuple_to_complex( + inspector_cplx_mult.last_log() + ), inspector_cplx_mult.report(binary_args, f"failed for {binary_args}", row=i) + assert u.real == pytuple_to_int( inspector_cplx_real.last_log() ), inspector_cplx_real.report(unary_args, f"failed for {unary_args}", row=i) + + assert u.imag == pytuple_to_int( + inspector_cplx_imag.last_log() + ), inspector_cplx_imag.report(unary_args, f"failed for {unary_args}", row=i) From da163d6ef441983af8cc750596ac045ffee7db7a Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 6 May 2022 18:15:22 -0500 Subject: [PATCH 084/170] complex conjugate --- tests/integration/graviton_abi_test.py | 35 ++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index 3a3c58f69..0b9ed169e 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -318,6 +318,19 @@ def complex130_imag(x: Complex130, *, output: Int65): return output.set(x[1]) +@Blackbox(input_types=[None]) +@ABIReturnSubroutine +def complex130_conjugate(x: Complex130, *, output: Complex130): + z0 = pt.abi.make(Int65) + z1 = pt.abi.make(Int65) + return pt.Seq( + z0.set(complex130_real(x)), + z1.set(complex130_imag(x)), + z1.set(int65_negate(z1)), + output.set(z0, z1), + ) + + def test_integer65(): bbpt_subtract_slick = BlackboxPyTealer(int65_sub, pt.Mode.Application) approval_subtract_slick = bbpt_subtract_slick.program() @@ -461,6 +474,12 @@ def test_complex130(): approval_cplx_imag(), pt.Mode.Application, version=6 ) + bbpt_complex_conjugate = BlackboxPyTealer(complex130_conjugate, pt.Mode.Application) + approval_cplx_conjugate = bbpt_complex_conjugate.program() + teal_cplx_conjugate = pt.compileTeal( + approval_cplx_conjugate(), pt.Mode.Application, version=6 + ) + unary_abi_argument_types = bbpt_complex_real.abi_argument_types() binary_abi_argument_types = bbpt_cplx_add.abi_argument_types() @@ -537,6 +556,14 @@ def pytuple_to_complex(tt): real_abi_return_type, ) + inspectors_cplx_conjugate = DryRunExecutor.dryrun_app_on_sequence( + algod, + teal_cplx_conjugate, + unary_inputs, + unary_abi_argument_types, + complex_abi_return_type, + ) + for i in range(N): binary_args = binary_inputs[i] x, y = tuple(map(pytuple_to_complex, binary_args)) @@ -556,6 +583,8 @@ def pytuple_to_complex(tt): inspector_cplx_imag = inspectors_cplx_imag[i] + inspector_cplx_conjugate = inspectors_cplx_conjugate[i] + assert x + y == pytuple_to_complex( inspector_cplx_add.last_log() ), inspector_cplx_add.report(binary_args, f"failed for {binary_args}", row=i) @@ -571,3 +600,9 @@ def pytuple_to_complex(tt): assert u.imag == pytuple_to_int( inspector_cplx_imag.last_log() ), inspector_cplx_imag.report(unary_args, f"failed for {unary_args}", row=i) + + assert u.conjugate() == pytuple_to_complex( + inspector_cplx_conjugate.last_log() + ), inspector_cplx_conjugate.report( + unary_args, f"failed for {unary_args}", row=i + ) From ee4e4bb56fa48d18afd7fbdbb755fbaaff6bf03c Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 6 May 2022 18:38:34 -0500 Subject: [PATCH 085/170] norm_squared --- tests/integration/graviton_abi_test.py | 35 ++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index 0b9ed169e..7e68f793e 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -331,6 +331,17 @@ def complex130_conjugate(x: Complex130, *, output: Complex130): ) +@Blackbox(input_types=[None]) +@ABIReturnSubroutine +def complex130_norm_squared(x: Complex130, *, output: Int65): + t = pt.abi.make(Complex130) + return pt.Seq( + t.set(complex130_conjugate(x)), + t.set(complex130_mult(t, x)), + output.set(complex130_real(t)), + ) + + def test_integer65(): bbpt_subtract_slick = BlackboxPyTealer(int65_sub, pt.Mode.Application) approval_subtract_slick = bbpt_subtract_slick.program() @@ -480,6 +491,14 @@ def test_complex130(): approval_cplx_conjugate(), pt.Mode.Application, version=6 ) + bbpt_complex_norm_squared = BlackboxPyTealer( + complex130_norm_squared, pt.Mode.Application + ) + approval_cplx_norm_squared = bbpt_complex_norm_squared.program() + teal_cplx_norm_squared = pt.compileTeal( + approval_cplx_norm_squared(), pt.Mode.Application, version=6 + ) + unary_abi_argument_types = bbpt_complex_real.abi_argument_types() binary_abi_argument_types = bbpt_cplx_add.abi_argument_types() @@ -564,6 +583,14 @@ def pytuple_to_complex(tt): complex_abi_return_type, ) + inspectors_cplx_norm_squared = DryRunExecutor.dryrun_app_on_sequence( + algod, + teal_cplx_norm_squared, + unary_inputs, + unary_abi_argument_types, + real_abi_return_type, + ) + for i in range(N): binary_args = binary_inputs[i] x, y = tuple(map(pytuple_to_complex, binary_args)) @@ -585,6 +612,8 @@ def pytuple_to_complex(tt): inspector_cplx_conjugate = inspectors_cplx_conjugate[i] + inspector_cplx_norm_squared = inspectors_cplx_norm_squared[i] + assert x + y == pytuple_to_complex( inspector_cplx_add.last_log() ), inspector_cplx_add.report(binary_args, f"failed for {binary_args}", row=i) @@ -606,3 +635,9 @@ def pytuple_to_complex(tt): ), inspector_cplx_conjugate.report( unary_args, f"failed for {unary_args}", row=i ) + + assert u * u.conjugate() == pytuple_to_int( + inspector_cplx_norm_squared.last_log() + ), inspector_cplx_norm_squared.report( + unary_args, f"failed for {unary_args}", row=i + ) From 78282d74f3dee4351833aeba98a672a84c0434aa Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Sun, 8 May 2022 08:01:34 -0400 Subject: [PATCH 086/170] remove comment --- pyteal/ast/subroutine.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index c891cb82d..2165d937d 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -733,10 +733,10 @@ def var_n_loaded( ) # if there is an output keyword argument for ABI, place the storing on the stack if output_carrying_abi: - if subroutine_body.has_return(): - raise TealInputError( - "ABI returning subroutine definition should have no return" - ) + # if subroutine_body.has_return(): + # raise TealInputError( + # "ABI returning subroutine definition should have no return" + # ) if subroutine_body.type_of() != TealType.none: raise TealInputError( f"ABI returning subroutine definition should evaluate to TealType.none, " From cdddc20a6475c1fce041437dda503dd4e54465ec Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Sun, 8 May 2022 08:02:36 -0400 Subject: [PATCH 087/170] oops - wait for future PR to remove this --- pyteal/ast/subroutine.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 2165d937d..c891cb82d 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -733,10 +733,10 @@ def var_n_loaded( ) # if there is an output keyword argument for ABI, place the storing on the stack if output_carrying_abi: - # if subroutine_body.has_return(): - # raise TealInputError( - # "ABI returning subroutine definition should have no return" - # ) + if subroutine_body.has_return(): + raise TealInputError( + "ABI returning subroutine definition should have no return" + ) if subroutine_body.type_of() != TealType.none: raise TealInputError( f"ABI returning subroutine definition should evaluate to TealType.none, " From 5d7dc9d296d094595a97bb7e37f1800f86888046 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Sun, 8 May 2022 08:08:29 -0400 Subject: [PATCH 088/170] pin to new graviton commit --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9ecfe282f..847f900a8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ black==22.3.0 flake8==4.0.1 flake8-tidy-imports==4.6.0 -graviton@git+https://github.com/algorand/graviton@8d12125802c1df29bc59a4221b94cd80f65a924b +graviton@git+https://github.com/algorand/graviton@b4c506d70aef1053a3b7eba5aae156465a4dbfe5 mypy==0.950 pytest==7.1.1 pytest-cov==3.0.0 From 92dcf03488614f1645ad99f7bcb047d3290a20bd Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Sun, 8 May 2022 10:20:14 -0400 Subject: [PATCH 089/170] Update pyteal/ast/abi/util_test.py --- pyteal/ast/abi/util_test.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyteal/ast/abi/util_test.py b/pyteal/ast/abi/util_test.py index 084207d48..f68320694 100644 --- a/pyteal/ast/abi/util_test.py +++ b/pyteal/ast/abi/util_test.py @@ -459,6 +459,5 @@ def test_abi_type_translation(): for algosdk_abi, abi_string, pyteal_abi_ts in test_cases: print(f"({algosdk_abi}, {abi_string}, {pyteal_abi_ts}),") assert str(algosdk_abi) == abi_string == str(pyteal_abi_ts) - assert algosdk_abi == algosdk.abi.ABIType.from_string(abi_string) - assert algosdk_abi == algosdk.abi.ABIType.from_string(str(pyteal_abi_ts)) + assert algosdk_abi == algosdk.abi.ABIType.from_string(abi_string) == algosdk.abi.ABIType.from_string(str(pyteal_abi_ts)) assert algosdk_abi == abi.algosdk_from_type_spec(pyteal_abi_ts) From bc8e7cec9a50d4ba328b95f9a0469ff9336cc715 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Sun, 8 May 2022 10:20:50 -0400 Subject: [PATCH 090/170] black --- pyteal/ast/abi/util_test.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyteal/ast/abi/util_test.py b/pyteal/ast/abi/util_test.py index f68320694..cfcf16f51 100644 --- a/pyteal/ast/abi/util_test.py +++ b/pyteal/ast/abi/util_test.py @@ -459,5 +459,9 @@ def test_abi_type_translation(): for algosdk_abi, abi_string, pyteal_abi_ts in test_cases: print(f"({algosdk_abi}, {abi_string}, {pyteal_abi_ts}),") assert str(algosdk_abi) == abi_string == str(pyteal_abi_ts) - assert algosdk_abi == algosdk.abi.ABIType.from_string(abi_string) == algosdk.abi.ABIType.from_string(str(pyteal_abi_ts)) + assert ( + algosdk_abi + == algosdk.abi.ABIType.from_string(abi_string) + == algosdk.abi.ABIType.from_string(str(pyteal_abi_ts)) + ) assert algosdk_abi == abi.algosdk_from_type_spec(pyteal_abi_ts) From dfd4687307951f9e081763ef6f841163cfec492e Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 9 May 2022 09:07:58 -0500 Subject: [PATCH 091/170] add coverage and pass ci --- Makefile | 2 +- pyteal/ast/subroutine.py | 18 ++++++-- pyteal/ast/subroutine_test.py | 85 ++++++++++++++++++++++------------- tests/blackbox.py | 6 +-- tests/unit/blackbox_test.py | 7 --- 5 files changed, 74 insertions(+), 44 deletions(-) diff --git a/Makefile b/Makefile index 17b8591af..07bc340f3 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ black: flake8: flake8 $(ALLPY) -# TODO: add `tests` to $MYPY when graviton respects mypy (version 🐗) +# TODO: add `tests` to $MYPY when graviton respects mypy (version 🐈) MYPY = pyteal scripts mypy: mypy $(MYPY) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index c891cb82d..0b78caf83 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -84,6 +84,12 @@ def _validate( ]: """Validate the full function signature and annotations for subroutine definition. + TODO: does this doc-comment still make sense after all the refactoring in this branch, + `abi-subroutine` and `feature/abi` is complete? + + NOTE: `self.implementation` and `self.abi_output_arg_name` should have been properly set + before calling `_validate()` + This function iterates through `sig.parameters.items()`, and checks each of subroutine arguments. On each of the subroutine arguments, the following checks are performed: - If argument is not POSITION_ONLY or not POSITIONAL_OR_KEYWORD, error @@ -104,8 +110,7 @@ def _validate( We load the ABI scratch space stored value to stack, and store them later in subroutine's local ABI values. Args: - input_types (optional): for testing purposes - expected `TealType`s of each parameter - Returns: + Returns: impl_params: a map from python function implementation's argument name, to argument's parameter. annotations: a dict whose keys are names of type-annotated arguments, and values are appearing type-annotations. @@ -173,7 +178,7 @@ def _validate( if isinstance(expected_arg_type, abi.TypeSpec): abi_args[name] = expected_arg_type - if input_types: + if input_types is not None: input_arg_count = len(impl_params) - len(abi_output_kwarg) if len(input_types) != input_arg_count: raise TealInputError( @@ -507,6 +512,7 @@ def __init__( self, fn_implementation: Callable[..., Expr], ) -> None: + # TODO: this should become _output_info() self.output_kwarg_info: Optional[ OutputKwArgInfo ] = self._output_name_type_from_fn(fn_implementation) @@ -517,6 +523,7 @@ def __init__( self.output_kwarg_info.abi_type.storage_type() ) + # TODO: this should be simplified output_kwarg_name = None if self.output_kwarg_info: output_kwarg_name = self.output_kwarg_info.name @@ -533,6 +540,10 @@ def __init__( def _output_name_type_from_fn( fn_implementation: Callable[..., Expr] ) -> Optional[OutputKwArgInfo]: + """ + TODO: This probably should become _output_info() + and if there is a kw-only arg it is unique and its name is "output" + """ if not callable(fn_implementation): raise TealInputError("Input to ABIReturnSubroutine is not callable") sig = signature(fn_implementation) @@ -546,6 +557,7 @@ def _output_name_type_from_fn( case []: return None case [name]: + # TODO: should be class variable OUTPUT_KEYWORD_ARG_NAME = "output" if name != "output": raise TealInputError( f"ABI return subroutine output-kwarg name must be `output` at this moment, " diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index 7a7b16b5e..c588e2266 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -199,6 +199,8 @@ def test_subroutine_definition_validate(): DFS through SubroutineDefinition.validate()'s logic """ + # TODO: shouldn't really have abi_output_arg_name optionality. Remove + # this when the optionality in the the codebase is removed def mock_subroutine_definition(implementation, abi_output_arg_name=None): mock = pt.SubroutineDefinition(lambda: pt.Return(pt.Int(1)), pt.TealType.uint64) mock._validate() # haven't failed with dummy implementation @@ -214,25 +216,7 @@ def mock_subroutine_definition(implementation, abi_output_arg_name=None): "Input to SubroutineDefinition is not callable" ) - # input_types: - three_params = mock_subroutine_definition(lambda x, y, z: pt.Return(pt.Int(1))) - two_inputs = [pt.TealType.uint64, pt.TealType.bytes] - with pytest.raises(pt.TealInputError) as tie: - three_params._validate(input_types=two_inputs) - - assert tie.value == pt.TealInputError( - "Provided number of input_types (2) does not match detected number of input parameters (3)" - ) - - three_inputs_with_a_wrong_type = [pt.TealType.uint64, pt.Expr, pt.TealType.bytes] - - with pytest.raises(pt.TealInputError) as tie: - three_params._validate(input_types=three_inputs_with_a_wrong_type) - - assert tie.value == pt.TealInputError( - "Function has input type for parameter y which is not a TealType" - ) params, anns, arg_types, byrefs, abi_args, output_kwarg = three_params._validate() assert len(params) == 3 @@ -255,6 +239,8 @@ def bad_return_impl() -> str: # now we iterate through the implementation params validating each as we go + # TODO: what's going on? output_kwarg_name must be "output"!!!! + # We shouldn't even have to deal with this here!!! def var_abi_output_impl(*, z: pt.abi.Uint16): pt.Return(pt.Int(1)) # this is wrong but ignored @@ -322,15 +308,6 @@ def var_abi_output_impl(*, z: pt.abi.Uint16): "Function has a parameter with a default value, which is not allowed in a subroutine: x" ) - with pytest.raises(pt.TealInputError) as tie: - three_params._validate( - input_types=[pt.TealType.uint64, pt.Expr, pt.TealType.anytype] - ) - - assert tie.value == pt.TealInputError( - "Function has input type for parameter y which is not a TealType" - ) - # Now we get to _validate_annotation(): one_vanilla = mock_subroutine_definition(lambda x: pt.Return(pt.Int(1))) @@ -366,8 +343,6 @@ def one_scratchvar_impl(x: pt.ScratchVar): assert abi_args == {} assert output_kwarg == {} - # for _is_abi_annotation() cf. copacetic x,y,z product below - # not is_class() def one_nontype_impl(x: "blahBlah"): # type: ignore # noqa: F821 return pt.Return(pt.Int(1)) @@ -391,7 +366,57 @@ def one_dynscratchvar_impl(x: pt.DynamicScratchVar): "Function has parameter x of disallowed type . Only the types (, , 'ABI') are allowed" ) - # Now we're back to validate() and everything should be copacetic + # Now we're back to _validate() main body and looking at input_types + + three_params_with_output = mock_subroutine_definition( + lambda x, y, z, *, output: pt.Return(pt.Int(1)), abi_output_arg_name="output" + ) + four_inputs = [ + pt.TealType.uint64, + pt.TealType.uint64, + pt.TealType.bytes, + pt.TealType.uint64, + ] + + two_inputs = [pt.TealType.uint64, pt.TealType.bytes] + with pytest.raises(pt.TealInputError) as tie: + three_params._validate(input_types=two_inputs) + + assert tie.value == pt.TealInputError( + "Provided number of input_types (2) does not match detected number of input parameters (3)" + ) + + three_inputs_with_a_wrong_type = [pt.TealType.uint64, pt.Expr, pt.TealType.bytes] + + with pytest.raises(pt.TealInputError) as tie: + three_params._validate(input_types=three_inputs_with_a_wrong_type) + + assert tie.value == pt.TealInputError( + "Function has input type for parameter y which is not a TealType" + ) + + with pytest.raises(pt.TealInputError) as tie: + three_params._validate( + input_types=[pt.TealType.uint64, pt.Expr, pt.TealType.anytype] + ) + + assert tie.value == pt.TealInputError( + "Function has input type for parameter y which is not a TealType" + ) + + with pytest.raises(pt.TealInputError) as tie: + three_params._validate( + input_types=[pt.TealType.uint64, None, pt.TealType.anytype] + ) + assert tie.value == pt.TealInputError( + "input_type for y is unspecified i.e. None but this is only allowed for ABI arguments" + ) + + # this one gets caught inside of _validate_annotation() + with pytest.raises(pt.TealInputError) as tie: + three_params_with_output._validate(input_types=four_inputs) + + # everything should be copacetic for x, y, z in product(pt.TealType, pt.TealType, pt.TealType): ( params, diff --git a/tests/blackbox.py b/tests/blackbox.py index 24eed7b44..711b2b259 100644 --- a/tests/blackbox.py +++ b/tests/blackbox.py @@ -77,7 +77,7 @@ def _fill( ) -def Blackbox(input_types: list[TealType]): +def Blackbox(input_types: list[TealType | None]): """ Decorator for trasforming @Subroutine and @ABIReturnSubroutine wrapped functions into executable pyteal programs. @@ -90,7 +90,7 @@ def Blackbox(input_types: list[TealType]): * if the subroutine argument is an Expr or a ScratchVar, the shadowing input_type must be a TealType of the same kind as expected in the argument - Some Correct Examples: + Some _Correct_ Examples: @Blackbox(input_types=[TealType.bytes, TealType.uint64]) @Subroutine(TealType.bytes) @@ -120,7 +120,7 @@ def string_mult(x: abi.String, y): """ - def decorator_blackbox(func: SubroutineFnWrapper): + def decorator_blackbox(func: SubroutineFnWrapper | ABIReturnSubroutine): return BlackboxWrapper(func, input_types) return decorator_blackbox diff --git a/tests/unit/blackbox_test.py b/tests/unit/blackbox_test.py index 0cb71c80b..bc8702b52 100644 --- a/tests/unit/blackbox_test.py +++ b/tests/unit/blackbox_test.py @@ -77,13 +77,6 @@ def utest_any_args(x, y, z): @pytest.mark.parametrize("subr, mode", product(UNITS, pt.Mode)) def test_blackbox_pyteal(subr, mode): - """ - TODO: here's an example of issue #199 at play - (the thread-safety aspect): - compare the following! - % pytest -n 2 tests/unit/blackbox_test.py::test_blackbox_pyteal - vs - % pytest -n 1 tests/unit/blackbox_test.py::test_blackbox_pyteal - """ is_app = mode == pt.Mode.Application name = f"{'app' if is_app else 'lsig'}_{subr.name()}" From 5d9335b791874d657fce3bb7a4b588147ed71bc0 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 9 May 2022 09:13:15 -0500 Subject: [PATCH 092/170] Update tests/blackbox.py --- tests/blackbox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/blackbox.py b/tests/blackbox.py index 711b2b259..1d0004d85 100644 --- a/tests/blackbox.py +++ b/tests/blackbox.py @@ -73,7 +73,7 @@ def _fill( return [(x if x else abis[args[i]]) for i, x in enumerate(input_types)] case _: raise AssertionError( - f"Cannot produce handle subroutine of type {type(self.subroutine)}" + f"Cannot handle subroutine of type {type(self.subroutine)}" ) From bba1709ef8ea06c769b9c0ba482b08bae2a602a4 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 9 May 2022 11:01:39 -0400 Subject: [PATCH 093/170] simplify name constrain --- pyteal/ast/subroutine.py | 51 ++++++++++++----------------------- pyteal/ast/subroutine_test.py | 25 +++++------------ 2 files changed, 24 insertions(+), 52 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 0d43ab2c4..e5b9bee80 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -1,13 +1,7 @@ from dataclasses import dataclass from inspect import isclass, Parameter, signature, get_annotations from types import MappingProxyType -from typing import ( - Callable, - Optional, - TYPE_CHECKING, - cast, - Any, -) +from typing import Callable, Optional, TYPE_CHECKING, cast, Any, Final from pyteal.ast import abi from pyteal.ast.expr import Expr @@ -34,7 +28,7 @@ def __init__( implementation: Callable[..., Expr], return_type: TealType, name_str: Optional[str] = None, - abi_output_arg_name: Optional[str] = None, + has_abi_output: bool = False, ) -> None: """ Args: @@ -42,7 +36,7 @@ def __init__( return_type: the TealType to be returned by the subroutine name_str (optional): the name that is used to identify the subroutine. If omitted, the name defaults to the implementation's __name__ attribute - abi_output_arg_name (optional): the name that is used to identify ABI output kwarg for subroutine. + has_abi_output (optional): the boolean that tells if ABI output kwarg for subroutine is used. """ super().__init__() self.id = SubroutineDefinition.nextSubroutineId @@ -52,7 +46,7 @@ def __init__( self.declaration: Optional["SubroutineDeclaration"] = None self.implementation: Callable = implementation - self.abi_output_arg_name: Optional[str] = abi_output_arg_name + self.has_abi_output: bool = has_abi_output self.implementation_params: MappingProxyType[str, Parameter] self.annotations: dict[str, type] @@ -154,8 +148,8 @@ def _validate( Parameter.POSITIONAL_OR_KEYWORD, ) and not ( param.kind is Parameter.KEYWORD_ONLY - and self.abi_output_arg_name is not None - and name == self.abi_output_arg_name + and self.has_abi_output + and name == ABIReturnSubroutine.OUTPUT_ARG_NAME ): raise TealInputError( f"Function has a parameter type that is not allowed in a subroutine: parameter {name} with type {param.kind}" @@ -497,35 +491,24 @@ def abi_sum(toSum: abi.DynamicArray[abi.Uint64], *, output: abi.Uint64) -> Expr: ) """ + OUTPUT_ARG_NAME: Final = "output" + def __init__( self, fn_implementation: Callable[..., Expr], ) -> None: - self.output_kwarg_info: Optional[ - OutputKwArgInfo - ] = self._output_name_type_from_fn(fn_implementation) - - internal_subroutine_ret_type = TealType.none - if self.output_kwarg_info: - internal_subroutine_ret_type = ( - self.output_kwarg_info.abi_type.storage_type() - ) - - output_kwarg_name = None - if self.output_kwarg_info: - output_kwarg_name = self.output_kwarg_info.name - - # output ABI type is void, return_type = TealType.none - # otherwise, return_type = ABI value's storage_type() + self.output_kwarg_info: Optional[OutputKwArgInfo] = self._get_output_kwarg_info( + fn_implementation + ) self.subroutine = SubroutineDefinition( fn_implementation, - return_type=internal_subroutine_ret_type, - abi_output_arg_name=output_kwarg_name, + return_type=TealType.none, + has_abi_output=self.output_kwarg_info is not None, ) - @staticmethod - def _output_name_type_from_fn( - fn_implementation: Callable[..., Expr] + @classmethod + def _get_output_kwarg_info( + cls, fn_implementation: Callable[..., Expr] ) -> Optional[OutputKwArgInfo]: if not callable(fn_implementation): raise TealInputError("Input to ABIReturnSubroutine is not callable") @@ -540,7 +523,7 @@ def _output_name_type_from_fn( case []: return None case [name]: - if name != "output": + if name != cls.OUTPUT_ARG_NAME: raise TealInputError( f"ABI return subroutine output-kwarg name must be `output` at this moment, " f"while {name} is the keyword." diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index 3e66ecaf5..0a90f33ee 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -199,11 +199,11 @@ def test_subroutine_definition_validate(): DFS through SubroutineDefinition.validate()'s logic """ - def mock_subroutine_definition(implementation, abi_output_arg_name=None): + def mock_subroutine_definition(implementation, has_abi_output=False): mock = pt.SubroutineDefinition(lambda: pt.Return(pt.Int(1)), pt.TealType.uint64) mock._validate() # haven't failed with dummy implementation mock.implementation = implementation - mock.abi_output_arg_name = abi_output_arg_name + mock.has_abi_output = has_abi_output return mock not_callable = mock_subroutine_definition("I'm not callable") @@ -255,7 +255,7 @@ def bad_return_impl() -> str: # now we iterate through the implementation params validating each as we go - def var_abi_output_impl(*, z: pt.abi.Uint16): + def var_abi_output_impl(*, output: pt.abi.Uint16): pt.Return(pt.Int(1)) # this is wrong but ignored # raises without abi_output_arg_name: @@ -264,31 +264,20 @@ def var_abi_output_impl(*, z: pt.abi.Uint16): var_abi_output_noname._validate() assert tie.value == pt.TealInputError( - "Function has a parameter type that is not allowed in a subroutine: parameter z with type KEYWORD_ONLY" - ) - - # raises with wrong name - var_abi_output = mock_subroutine_definition( - var_abi_output_impl, abi_output_arg_name="foo" - ) - with pytest.raises(pt.TealInputError) as tie: - var_abi_output._validate() - - assert tie.value == pt.TealInputError( - "Function has a parameter type that is not allowed in a subroutine: parameter z with type KEYWORD_ONLY" + "Function has a parameter type that is not allowed in a subroutine: parameter output with type KEYWORD_ONLY" ) # copacetic abi output: var_abi_output = mock_subroutine_definition( - var_abi_output_impl, abi_output_arg_name="z" + var_abi_output_impl, has_abi_output=True ) params, anns, arg_types, byrefs, abi_args, output_kwarg = var_abi_output._validate() assert len(params) == 1 - assert anns == {"z": pt.abi.Uint16} + assert anns == {"output": pt.abi.Uint16} assert all(at is pt.Expr for at in arg_types) assert byrefs == set() assert abi_args == {} - assert output_kwarg == {"z": pt.abi.Uint16TypeSpec()} + assert output_kwarg == {"output": pt.abi.Uint16TypeSpec()} var_positional = mock_subroutine_definition(lambda *args: pt.Return(pt.Int(1))) with pytest.raises(pt.TealInputError) as tie: From 4c0858fdca9b7e78b6da455dec982029b44f13b0 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 9 May 2022 11:10:48 -0400 Subject: [PATCH 094/170] resolving comments --- pyteal/ast/subroutine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index e5b9bee80..fce10fee8 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -491,7 +491,7 @@ def abi_sum(toSum: abi.DynamicArray[abi.Uint64], *, output: abi.Uint64) -> Expr: ) """ - OUTPUT_ARG_NAME: Final = "output" + OUTPUT_ARG_NAME: Final[str] = "output" def __init__( self, From d13bc84410e3f3f84e503ac39ed12f88c01b4fc0 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 9 May 2022 11:30:56 -0500 Subject: [PATCH 095/170] Update tests/blackbox.py --- tests/blackbox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/blackbox.py b/tests/blackbox.py index 1d0004d85..153e3aa67 100644 --- a/tests/blackbox.py +++ b/tests/blackbox.py @@ -53,7 +53,7 @@ def __init__( ): subr.subroutine._validate(input_types=input_types) self.subroutine: SubroutineFnWrapper | ABIReturnSubroutine = subr - self.input_types: list[TealType | None] = self._fill(input_types) + self.input_types: list[TealType | abi.TypeSpec] = self._fill(input_types) def __call__(self, *args: Expr | ScratchVar, **kwargs) -> Expr: return self.subroutine(*args, **kwargs) From 95a98421b25ebbba7f6bbffddfcb1808e23a86f3 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 9 May 2022 11:34:23 -0500 Subject: [PATCH 096/170] Update tests/blackbox.py --- tests/blackbox.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/blackbox.py b/tests/blackbox.py index 153e3aa67..749dc0e7f 100644 --- a/tests/blackbox.py +++ b/tests/blackbox.py @@ -79,8 +79,8 @@ def _fill( def Blackbox(input_types: list[TealType | None]): """ - Decorator for trasforming @Subroutine and @ABIReturnSubroutine wrapped functions - into executable pyteal programs. + Decorator for transforming @Subroutine and @ABIReturnSubroutine wrapped functions + into PyTeal expressions that compile into executable Teal programs. input_types: list[TealType] (required) List shadowing the input arguments of the decorated subroutine. In particular: From f4e4e9b68eb2b2e4741f73f0303eeff139b656c5 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 9 May 2022 12:32:37 -0500 Subject: [PATCH 097/170] pass unit tests of _validate() --- pyteal/ast/subroutine_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index 5299d5d8e..07b530f5a 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -354,7 +354,7 @@ def one_dynscratchvar_impl(x: pt.DynamicScratchVar): # Now we're back to _validate() main body and looking at input_types three_params_with_output = mock_subroutine_definition( - lambda x, y, z, *, output: pt.Return(pt.Int(1)), abi_output_arg_name="output" + lambda x, y, z, *, output: pt.Return(pt.Int(1)), has_abi_output=True ) four_inputs = [ pt.TealType.uint64, From 390a6793420965ee6858ef351fdc344a3a0aff22 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 9 May 2022 12:48:51 -0500 Subject: [PATCH 098/170] skip broken abirouter user guide test that should be fixed soon --- tests/unit/user_guide_test.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/tests/unit/user_guide_test.py b/tests/unit/user_guide_test.py index de145daa2..e89c70f47 100644 --- a/tests/unit/user_guide_test.py +++ b/tests/unit/user_guide_test.py @@ -73,6 +73,7 @@ def user_guide_snippet_ABIReturnSubroutine(): ) from pyteal import abi + # --- BEGIN doc-comment --- # @ABIReturnSubroutine def abi_sum(toSum: abi.DynamicArray[abi.Uint64], *, output: abi.Uint64) -> Expr: i = ScratchVar(TealType.uint64) @@ -89,27 +90,22 @@ def abi_sum(toSum: abi.DynamicArray[abi.Uint64], *, output: abi.Uint64) -> Expr: ), ) - to_sum_arr = abi.make(abi.DynamicArray[abi.Uint64]) - res = abi.Uint64() - program = Seq( - # (to_sum_arr := abi.make(abi.DynamicArray[abi.Uint64])).decode( - # Txn.application_args[1] - # ), - to_sum_arr.decode(Txn.application_args[1]), - # (res := abi.Uint64()).set(abi_sum(to_sum_arr)), - res.set(abi_sum(to_sum_arr)), + (to_sum_arr := abi.make(abi.DynamicArray[abi.Uint64])).decode( + Txn.application_args[1] + ), + (res := abi.Uint64()).set(abi_sum(to_sum_arr)), abi.MethodReturn(res), Int(1), ) - + # --- END doc-comment --- # return program USER_GUIDE_SNIPPETS_COPACETIC = [ user_guide_snippet_dynamic_scratch_var, user_guide_snippet_recursiveIsEven, - user_guide_snippet_ABIReturnSubroutine, + # TODO: turn this back on before merging --> user_guide_snippet_ABIReturnSubroutine, ] From 95caac58819cacc00dbbca743f97a7091f7b417d Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Mon, 9 May 2022 11:02:11 -0700 Subject: [PATCH 099/170] Use deferred subroutine expression for ABI returns (#328) * Add subroutine deferred expr * Allow multiple deferred blocks * Remove error * flake8 * Make test more realistic * Add second test * clean up code and comments * remove deferred blocks function * Add coverage for multiple ops in block error --- pyteal/ast/subroutine.py | 22 ++-- pyteal/compiler/compiler.py | 57 ++++++++-- pyteal/compiler/compiler_test.py | 188 +++++++++++++++++++++++++++++++ 3 files changed, 247 insertions(+), 20 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index fce10fee8..98feedccc 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -5,7 +5,6 @@ from pyteal.ast import abi from pyteal.ast.expr import Expr -from pyteal.ast.return_ import Return from pyteal.ast.seq import Seq from pyteal.ast.scratchvar import DynamicScratchVar, ScratchVar from pyteal.errors import TealInputError, verifyTealVersion @@ -296,10 +295,16 @@ def __hash__(self): class SubroutineDeclaration(Expr): - def __init__(self, subroutine: SubroutineDefinition, body: Expr) -> None: + def __init__( + self, + subroutine: SubroutineDefinition, + body: Expr, + deferred_expr: Optional[Expr] = None, + ) -> None: super().__init__() self.subroutine = subroutine self.body = body + self.deferred_expr = deferred_expr def __teal__(self, options: "CompileOptions"): return self.body.__teal__(options) @@ -708,24 +713,21 @@ def var_n_loaded( raise TealInputError( f"Subroutine function does not return a PyTeal expression. Got type {type(subroutine_body)}." ) + + deferred_expr: Optional[Expr] = None + # if there is an output keyword argument for ABI, place the storing on the stack if output_carrying_abi: - if subroutine_body.has_return(): - raise TealInputError( - "ABI returning subroutine definition should have no return" - ) if subroutine_body.type_of() != TealType.none: raise TealInputError( f"ABI returning subroutine definition should evaluate to TealType.none, " f"while evaluate to {subroutine_body.type_of()}." ) - subroutine_body = Seq( - subroutine_body, Return(output_carrying_abi.stored_value.load()) - ) + deferred_expr = output_carrying_abi.stored_value.load() # Arg usage "A" to be pick up and store in scratch parameters that have been placed on the stack # need to reverse order of argumentVars because the last argument will be on top of the stack body_ops = [var.slot.store() for var in arg_vars[::-1]] body_ops.append(subroutine_body) - return SubroutineDeclaration(subroutine, Seq(body_ops)) + return SubroutineDeclaration(subroutine, Seq(body_ops), deferred_expr) diff --git a/pyteal/compiler/compiler.py b/pyteal/compiler/compiler.py index dd33941a7..f09eb2c95 100644 --- a/pyteal/compiler/compiler.py +++ b/pyteal/compiler/compiler.py @@ -10,7 +10,7 @@ SubroutineDefinition, SubroutineDeclaration, ) -from pyteal.ir import Mode, TealComponent, TealOp, TealBlock, TealSimpleBlock +from pyteal.ir import Mode, Op, TealComponent, TealOp, TealBlock, TealSimpleBlock from pyteal.errors import TealInputError, TealInternalError from pyteal.compiler.sort import sortBlocks @@ -133,26 +133,60 @@ def compileSubroutine( ast = Return(ast) options.setSubroutine(currentSubroutine) + start, end = ast.__teal__(options) start.addIncoming() start.validateTree() - start = TealBlock.NormalizeBlocks(start) - start.validateTree() + if ( + currentSubroutine is not None + and currentSubroutine.get_declaration().deferred_expr is not None + ): + # this represents code that should be inserted before each retsub op + deferred_expr = cast(Expr, currentSubroutine.get_declaration().deferred_expr) + + for block in TealBlock.Iterate(start): + if not any(op.getOp() == Op.retsub for op in block.ops): + continue + + if len(block.ops) != 1: + # we expect all retsub ops to be in their own block at this point since + # TealBlock.NormalizeBlocks has not yet been used + raise TealInternalError( + f"Expected retsub to be the only op in the block, but there are {len(block.ops)} ops" + ) - order = sortBlocks(start, end) - teal = flattenBlocks(order) + # we invoke __teal__ here and not outside of this loop because the same block cannot be + # added in multiple places to the control flow graph + deferred_start, deferred_end = deferred_expr.__teal__(options) + deferred_start.addIncoming() + deferred_start.validateTree() - verifyOpsForVersion(teal, options.version) - verifyOpsForMode(teal, options.mode) + # insert deferred blocks between the previous block(s) and this one + deferred_start.incoming = block.incoming + block.incoming = [deferred_end] + deferred_end.nextBlock = block + + for prev in deferred_start.incoming: + prev.replaceOutgoing(block, deferred_start) + + if block is start: + # this is the start block, replace start + start = deferred_start + + start.validateTree() + + start = TealBlock.NormalizeBlocks(start) + start.validateTree() subroutine_start_blocks[currentSubroutine] = start subroutine_end_blocks[currentSubroutine] = end referencedSubroutines: Set[SubroutineDefinition] = set() - for stmt in teal: - for subroutine in stmt.getSubroutines(): - referencedSubroutines.add(subroutine) + for block in TealBlock.Iterate(start): + for stmt in block.ops: + for subroutine in stmt.getSubroutines(): + referencedSubroutines.add(subroutine) if currentSubroutine is not None: subroutineGraph[currentSubroutine] = referencedSubroutines @@ -256,6 +290,9 @@ def compileTeal( subroutineLabels = resolveSubroutines(subroutineMapping) teal = flattenSubroutines(subroutineMapping, subroutineLabels) + verifyOpsForVersion(teal, options.version) + verifyOpsForMode(teal, options.mode) + if assembleConstants: if version < 3: raise TealInternalError( diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index 24f30dc18..64ed2d669 100644 --- a/pyteal/compiler/compiler_test.py +++ b/pyteal/compiler/compiler_test.py @@ -1627,6 +1627,194 @@ def storeValue(key: pt.Expr, t1: pt.Expr, t2: pt.Expr, t3: pt.Expr) -> pt.Expr: assert actual == expected +def test_compile_subroutine_deferred_expr(): + @pt.Subroutine(pt.TealType.none) + def deferredExample(value: pt.Expr) -> pt.Expr: + return pt.Seq( + pt.If(value == pt.Int(0)).Then(pt.Return()), + pt.If(value == pt.Int(1)).Then(pt.Approve()), + pt.If(value == pt.Int(2)).Then(pt.Reject()), + pt.If(value == pt.Int(3)).Then(pt.Err()), + ) + + program = pt.Seq(deferredExample(pt.Int(10)), pt.Approve()) + + expected_no_deferred = """#pragma version 6 +int 10 +callsub deferredExample_0 +int 1 +return + +// deferredExample +deferredExample_0: +store 0 +load 0 +int 0 +== +bnz deferredExample_0_l7 +load 0 +int 1 +== +bnz deferredExample_0_l6 +load 0 +int 2 +== +bnz deferredExample_0_l5 +load 0 +int 3 +== +bz deferredExample_0_l8 +err +deferredExample_0_l5: +int 0 +return +deferredExample_0_l6: +int 1 +return +deferredExample_0_l7: +retsub +deferredExample_0_l8: +retsub + """.strip() + actual_no_deferred = pt.compileTeal( + program, pt.Mode.Application, version=6, assembleConstants=False + ) + assert actual_no_deferred == expected_no_deferred + + # manually add deferred expression to SubroutineDefinition + declaration = deferredExample.subroutine.get_declaration() + declaration.deferred_expr = pt.Pop(pt.Bytes("deferred")) + + expected_deferred = """#pragma version 6 +int 10 +callsub deferredExample_0 +int 1 +return + +// deferredExample +deferredExample_0: +store 0 +load 0 +int 0 +== +bnz deferredExample_0_l7 +load 0 +int 1 +== +bnz deferredExample_0_l6 +load 0 +int 2 +== +bnz deferredExample_0_l5 +load 0 +int 3 +== +bz deferredExample_0_l8 +err +deferredExample_0_l5: +int 0 +return +deferredExample_0_l6: +int 1 +return +deferredExample_0_l7: +byte "deferred" +pop +retsub +deferredExample_0_l8: +byte "deferred" +pop +retsub + """.strip() + actual_deferred = pt.compileTeal( + program, pt.Mode.Application, version=6, assembleConstants=False + ) + assert actual_deferred == expected_deferred + + +def test_compile_subroutine_deferred_expr_empty(): + @pt.Subroutine(pt.TealType.none) + def empty() -> pt.Expr: + return pt.Return() + + program = pt.Seq(empty(), pt.Approve()) + + expected_no_deferred = """#pragma version 6 +callsub empty_0 +int 1 +return + +// empty +empty_0: +retsub + """.strip() + actual_no_deferred = pt.compileTeal( + program, pt.Mode.Application, version=6, assembleConstants=False + ) + assert actual_no_deferred == expected_no_deferred + + # manually add deferred expression to SubroutineDefinition + declaration = empty.subroutine.get_declaration() + declaration.deferred_expr = pt.Pop(pt.Bytes("deferred")) + + expected_deferred = """#pragma version 6 +callsub empty_0 +int 1 +return + +// empty +empty_0: +byte "deferred" +pop +retsub + """.strip() + actual_deferred = pt.compileTeal( + program, pt.Mode.Application, version=6, assembleConstants=False + ) + assert actual_deferred == expected_deferred + + +def test_compileSubroutine_deferred_block_malformed(): + class BadRetsub(pt.Expr): + def type_of(self) -> pt.TealType: + return pt.TealType.none + + def has_return(self) -> bool: + return True + + def __str__(self) -> str: + return "(BadRetsub)" + + def __teal__( + self, options: pt.CompileOptions + ) -> tuple[pt.TealBlock, pt.TealSimpleBlock]: + block = pt.TealSimpleBlock( + [ + pt.TealOp(self, pt.Op.int, 1), + pt.TealOp(self, pt.Op.pop), + pt.TealOp(self, pt.Op.retsub), + ] + ) + + return block, block + + @pt.Subroutine(pt.TealType.none) + def bad() -> pt.Expr: + return BadRetsub() + + program = pt.Seq(bad(), pt.Approve()) + + # manually add deferred expression to SubroutineDefinition + declaration = bad.subroutine.get_declaration() + declaration.deferred_expr = pt.Pop(pt.Bytes("deferred")) + + with pytest.raises( + pt.TealInternalError, + match=r"^Expected retsub to be the only op in the block, but there are 3 ops$", + ): + pt.compileTeal(program, pt.Mode.Application, version=6, assembleConstants=False) + + def test_compile_wide_ratio(): cases = ( ( From 1beeeb4e2459e8f8047cda4cb82301dd328097e8 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 9 May 2022 13:05:18 -0500 Subject: [PATCH 100/170] wip --- tests/unit/user_guide_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/user_guide_test.py b/tests/unit/user_guide_test.py index e89c70f47..8760914b6 100644 --- a/tests/unit/user_guide_test.py +++ b/tests/unit/user_guide_test.py @@ -105,6 +105,7 @@ def abi_sum(toSum: abi.DynamicArray[abi.Uint64], *, output: abi.Uint64) -> Expr: USER_GUIDE_SNIPPETS_COPACETIC = [ user_guide_snippet_dynamic_scratch_var, user_guide_snippet_recursiveIsEven, + user_guide_snippet_ABIReturnSubroutine, # TODO: turn this back on before merging --> user_guide_snippet_ABIReturnSubroutine, ] From fad77c6efa6ac7c2be32213aa60fe90336ae9560 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 9 May 2022 15:14:02 -0400 Subject: [PATCH 101/170] hack return from abi var --- pyteal/ast/abi/type.py | 7 +++++-- pyteal/ast/subroutine.py | 13 ++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/pyteal/ast/abi/type.py b/pyteal/ast/abi/type.py index 4832545e8..c7809825c 100644 --- a/pyteal/ast/abi/type.py +++ b/pyteal/ast/abi/type.py @@ -185,9 +185,12 @@ def use(self, action: Callable[[T], Expr]) -> Expr: class ReturnedValue(ComputedValue): - def __init__(self, type_spec: TypeSpec, computation_expr: Expr): + def __init__( + self, type_spec: TypeSpec, computation_expr: Expr, deferred_expr: Expr + ): self.type_spec = type_spec self.computation = computation_expr + self.deferred = deferred_expr def produced_type_spec(self) -> TypeSpec: return self.type_spec @@ -197,7 +200,7 @@ def store_into(self, output: BaseType) -> Expr: raise TealInputError( f"expected type_spec {self.produced_type_spec()} but get {output.type_spec()}" ) - return output.stored_value.store(self.computation) + return Seq(self.computation, output.stored_value.store(self.deferred)) ReturnedValue.__module__ = "pyteal" diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 98feedccc..f352c73f6 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -563,7 +563,18 @@ def __call__( ) return invoked - return abi.ReturnedValue(self.output_kwarg_info.abi_type, invoked) + self.subroutine.get_declaration() + + return abi.ReturnedValue( + self.output_kwarg_info.abi_type, + invoked, + cast( + Expr, + cast( + SubroutineDeclaration, invoked.subroutine.declaration + ).deferred_expr, + ), + ) def name(self) -> str: return self.subroutine.name() From aa6f858c75b30450a06d957fcd38ca1ef97994b7 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 9 May 2022 15:28:33 -0400 Subject: [PATCH 102/170] a hack --- pyteal/ast/abi/type.py | 7 ++----- pyteal/ast/subroutine.py | 6 ------ 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/pyteal/ast/abi/type.py b/pyteal/ast/abi/type.py index c7809825c..99de2f32f 100644 --- a/pyteal/ast/abi/type.py +++ b/pyteal/ast/abi/type.py @@ -185,12 +185,9 @@ def use(self, action: Callable[[T], Expr]) -> Expr: class ReturnedValue(ComputedValue): - def __init__( - self, type_spec: TypeSpec, computation_expr: Expr, deferred_expr: Expr - ): + def __init__(self, type_spec: TypeSpec, computation_expr: Expr): self.type_spec = type_spec self.computation = computation_expr - self.deferred = deferred_expr def produced_type_spec(self) -> TypeSpec: return self.type_spec @@ -200,7 +197,7 @@ def store_into(self, output: BaseType) -> Expr: raise TealInputError( f"expected type_spec {self.produced_type_spec()} but get {output.type_spec()}" ) - return Seq(self.computation, output.stored_value.store(self.deferred)) + return output.stored_value.slot.store(self.computation) ReturnedValue.__module__ = "pyteal" diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index f352c73f6..eb53211c4 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -568,12 +568,6 @@ def __call__( return abi.ReturnedValue( self.output_kwarg_info.abi_type, invoked, - cast( - Expr, - cast( - SubroutineDeclaration, invoked.subroutine.declaration - ).deferred_expr, - ), ) def name(self) -> str: From 9a71adfb7e53d58cd45cc46c8e3b8b922085edf8 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 9 May 2022 14:38:36 -0500 Subject: [PATCH 103/170] Update pyteal/ast/subroutine.py --- pyteal/ast/subroutine.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 5293caccb..c29f46d54 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -526,10 +526,6 @@ def __init__( def _get_output_kwarg_info( cls, fn_implementation: Callable[..., Expr] ) -> Optional[OutputKwArgInfo]: - """ - TODO: This probably should become _output_info() - and if there is a kw-only arg it is unique and its name is "output" - """ if not callable(fn_implementation): raise TealInputError("Input to ABIReturnSubroutine is not callable") sig = signature(fn_implementation) From 48fd8be12382220d0c054b423753b5d84fc496ef Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 9 May 2022 14:42:56 -0500 Subject: [PATCH 104/170] Update pyteal/ast/subroutine.py --- pyteal/ast/subroutine.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index c29f46d54..1781794a1 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -80,8 +80,7 @@ def _validate( TODO: does this doc-comment still make sense after all the refactoring in this branch, `abi-subroutine` and `feature/abi` is complete? - NOTE: `self.implementation` and `self.abi_output_arg_name` should have been properly set - before calling `_validate()` + NOTE: `self.implementation` should be set before calling `_validate()` This function iterates through `sig.parameters.items()`, and checks each of subroutine arguments. On each of the subroutine arguments, the following checks are performed: From a556e28ef0a665d9cf4f37d14754d36d57b3645b Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 9 May 2022 14:43:07 -0500 Subject: [PATCH 105/170] Update pyteal/ast/subroutine.py --- pyteal/ast/subroutine.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 1781794a1..862880384 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -102,7 +102,8 @@ def _validate( We load the ABI scratch space stored value to stack, and store them later in subroutine's local ABI values. Args: - Returns: + input_types (optional): for testing purposes - expected `TealType`s of each parameter + Returns: impl_params: a map from python function implementation's argument name, to argument's parameter. annotations: a dict whose keys are names of type-annotated arguments, and values are appearing type-annotations. From e33a9d669f6e2340a225cc7db5d9a3d9454c0169 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 9 May 2022 14:44:27 -0500 Subject: [PATCH 106/170] Update pyteal/ast/subroutine.py --- pyteal/ast/subroutine.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 862880384..e99ea4724 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -102,8 +102,9 @@ def _validate( We load the ABI scratch space stored value to stack, and store them later in subroutine's local ABI values. Args: - input_types (optional): for testing purposes - expected `TealType`s of each parameter - Returns: + input_types (optional): for testing purposes - expected `TealType`s of each parameter + + Returns: impl_params: a map from python function implementation's argument name, to argument's parameter. annotations: a dict whose keys are names of type-annotated arguments, and values are appearing type-annotations. From 4c148f707e882501cbccd9a01c1901a7c8e9c5ad Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 9 May 2022 16:50:26 -0400 Subject: [PATCH 107/170] better returned type --- pyteal/ast/abi/type.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/pyteal/ast/abi/type.py b/pyteal/ast/abi/type.py index 99de2f32f..56b8d5edb 100644 --- a/pyteal/ast/abi/type.py +++ b/pyteal/ast/abi/type.py @@ -186,17 +186,40 @@ def use(self, action: Callable[[T], Expr]) -> Expr: class ReturnedValue(ComputedValue): def __init__(self, type_spec: TypeSpec, computation_expr: Expr): + from pyteal.ast.subroutine import SubroutineCall + self.type_spec = type_spec + if not isinstance(computation_expr, SubroutineCall): + raise TealInputError( + f"Expecting computation_expr to be SubroutineCall but get {type(computation_expr)}" + ) self.computation = computation_expr def produced_type_spec(self) -> TypeSpec: return self.type_spec def store_into(self, output: BaseType) -> Expr: + from pyteal.ast.subroutine import SubroutineDeclaration + if output.type_spec() != self.produced_type_spec(): raise TealInputError( f"expected type_spec {self.produced_type_spec()} but get {output.type_spec()}" ) + + self.computation.subroutine.get_declaration() + declaration = cast( + SubroutineDeclaration, self.computation.subroutine.declaration + ) + + if declaration.deferred_expr is None: + raise TealInputError( + "ABI return subroutine must have deferred_expr to be not-None." + ) + if declaration.deferred_expr.type_of() != output.type_spec().storage_type(): + raise TealInputError( + f"ABI return subroutine deferred_expr is expected to be typed {output.type_spec().storage_type()}, " + f"but has type {declaration.deferred_expr.type_of()}." + ) return output.stored_value.slot.store(self.computation) From b35ce213e24d716b43d8258494d49b45278c64ad Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 9 May 2022 17:56:10 -0400 Subject: [PATCH 108/170] simplify --- pyteal/ast/abi/type.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pyteal/ast/abi/type.py b/pyteal/ast/abi/type.py index 56b8d5edb..52a57fec8 100644 --- a/pyteal/ast/abi/type.py +++ b/pyteal/ast/abi/type.py @@ -199,17 +199,12 @@ def produced_type_spec(self) -> TypeSpec: return self.type_spec def store_into(self, output: BaseType) -> Expr: - from pyteal.ast.subroutine import SubroutineDeclaration - if output.type_spec() != self.produced_type_spec(): raise TealInputError( f"expected type_spec {self.produced_type_spec()} but get {output.type_spec()}" ) - self.computation.subroutine.get_declaration() - declaration = cast( - SubroutineDeclaration, self.computation.subroutine.declaration - ) + declaration = self.computation.subroutine.get_declaration() if declaration.deferred_expr is None: raise TealInputError( From 6c28188329357862b3f4fddc15523179f2fed979 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 9 May 2022 19:21:27 -0500 Subject: [PATCH 109/170] remove comment --- tests/unit/user_guide_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/user_guide_test.py b/tests/unit/user_guide_test.py index 8760914b6..b28b8ccd6 100644 --- a/tests/unit/user_guide_test.py +++ b/tests/unit/user_guide_test.py @@ -106,7 +106,6 @@ def abi_sum(toSum: abi.DynamicArray[abi.Uint64], *, output: abi.Uint64) -> Expr: user_guide_snippet_dynamic_scratch_var, user_guide_snippet_recursiveIsEven, user_guide_snippet_ABIReturnSubroutine, - # TODO: turn this back on before merging --> user_guide_snippet_ABIReturnSubroutine, ] From 9df640e228c869dd2098bd13c874a2217fad7a05 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 10 May 2022 07:42:14 -0500 Subject: [PATCH 110/170] add abi examples to unit/blackbox_test but don't yet run them --- tests/unit/blackbox_test.py | 56 +++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/tests/unit/blackbox_test.py b/tests/unit/blackbox_test.py index bc8702b52..e82c9a02c 100644 --- a/tests/unit/blackbox_test.py +++ b/tests/unit/blackbox_test.py @@ -1,6 +1,7 @@ from itertools import product from pathlib import Path import pytest +from typing import Literal import pyteal as pt @@ -12,6 +13,8 @@ FIXTURES = PATH / "teal" GENERATED = PATH / "generated" +# ---- Subroutine Unit Test Examples ---- # + @Blackbox(input_types=[]) @pt.Subroutine(pt.TealType.none) @@ -63,6 +66,49 @@ def utest_any_args(x, y, z): return pt.Seq(x.store(pt.Int(0)), x.load()) +# ---- ABI Return Subroutine Unit Test Examples ---- # + + +@Blackbox(input_types=[]) +@pt.ABIReturnSubroutine +def fn_0arg_0ret() -> pt.Expr: + return pt.Return() + + +@Blackbox(input_types=[]) +@pt.ABIReturnSubroutine +def fn_0arg_uint64_ret(*, output: pt.abi.Uint64) -> pt.Expr: + return output.set(1) + + +@Blackbox(input_types=[None]) +@pt.ABIReturnSubroutine +def fn_1arg_0ret(a: pt.abi.Uint64) -> pt.Expr: + return pt.Return() + + +@Blackbox(input_types=[None]) +@pt.ABIReturnSubroutine +def fn_1arg_1ret(a: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: + return output.set(a) + + +@Blackbox(input_types=[None, None]) +@pt.ABIReturnSubroutine +def fn_2arg_0ret( + a: pt.abi.Uint64, b: pt.abi.StaticArray[pt.abi.Byte, Literal[10]] +) -> pt.Expr: + return pt.Return() + + +@Blackbox(input_types=[None, None]) +@pt.ABIReturnSubroutine +def fn_3mixed_args_0ret( + a: pt.abi.Uint64, b: pt.ScratchVar, C: pt.abi.StaticArray[pt.abi.Byte, Literal[10]] +) -> pt.Expr: + return pt.Return() + + UNITS = [ utest_noop, utest_noop_args, @@ -75,6 +121,16 @@ def utest_any_args(x, y, z): ] +ABI_UNITS = [ + fn_0arg_0ret, + fn_0arg_uint64_ret, + fn_1arg_0ret, + fn_1arg_1ret, + fn_2arg_0ret, + fn_3mixed_args_0ret, +] + + @pytest.mark.parametrize("subr, mode", product(UNITS, pt.Mode)) def test_blackbox_pyteal(subr, mode): is_app = mode == pt.Mode.Application From 63b0432c890bbe3b330eca784ec32c81b4921c15 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 10 May 2022 07:42:19 -0500 Subject: [PATCH 111/170] add abi examples to unit/blackbox_test but don't yet run them --- tests/unit/blackbox_test.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/unit/blackbox_test.py b/tests/unit/blackbox_test.py index e82c9a02c..e8c9da6d8 100644 --- a/tests/unit/blackbox_test.py +++ b/tests/unit/blackbox_test.py @@ -144,3 +144,18 @@ def test_blackbox_pyteal(subr, mode): f.write(compiled) assert_teal_as_expected(save_to, FIXTURES / "blackbox" / (name + ".teal")) + + +@pytest.mark.parametrize("subr, mode", product(UNITS, pt.Mode)) +def test_abi_blackbox_pyteal(subr, mode): + is_app = mode == pt.Mode.Application + name = f"{'app' if is_app else 'lsig'}_{subr.name()}" + + compiled = pt.compileTeal(blackbox_pyteal(subr, mode)(), mode, version=6) + tealdir = GENERATED / "blackbox" + tealdir.mkdir(parents=True, exist_ok=True) + save_to = tealdir / (name + ".teal") + with open(save_to, "w") as f: + f.write(compiled) + + assert_teal_as_expected(save_to, FIXTURES / "blackbox" / (name + ".teal")) From 062680f4b741fad22b0821d9fa6318fae2d84504 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 10 May 2022 08:17:09 -0500 Subject: [PATCH 112/170] simplify Blackbox API: program is just an Expr now --- tests/blackbox.py | 4 ++-- tests/integration/ecdsa_test.py | 8 ++++---- tests/integration/graviton_abi_test.py | 26 +++++++++++++------------- tests/integration/graviton_test.py | 14 +++++++------- tests/unit/blackbox_test.py | 26 +++++++++++++------------- 5 files changed, 39 insertions(+), 39 deletions(-) diff --git a/tests/blackbox.py b/tests/blackbox.py index 749dc0e7f..f2c4a84cb 100644 --- a/tests/blackbox.py +++ b/tests/blackbox.py @@ -220,7 +220,7 @@ def __init__(self, subr: BlackboxWrapper, mode: Mode): ) setattr(approval, "__name__", f"sem_{mode}_{subr.name()}") - self._pyteal_getter: Callable[..., Expr] = approval + self._pyteal: Expr = approval() def is_abi(self) -> bool: return isinstance(self.subr.subroutine, ABIReturnSubroutine) @@ -248,7 +248,7 @@ def abi_return_type(self) -> None | algosdk.abi.ABIType: ) def program(self) -> Callable[..., Expr]: - return self._pyteal_getter + return self._pyteal def _handle_SubroutineFnWrapper(self): subdef = self.subr.subroutine.subroutine diff --git a/tests/integration/ecdsa_test.py b/tests/integration/ecdsa_test.py index 9bdeaeae6..efbcac07d 100644 --- a/tests/integration/ecdsa_test.py +++ b/tests/integration/ecdsa_test.py @@ -50,7 +50,7 @@ def verify(): ) approval_app = blackbox_pyteal(verify, Mode.Application) - app_teal = compileTeal(approval_app(), Mode.Application, version=5) + app_teal = compileTeal(approval_app, Mode.Application, version=5) args = [] algod = algod_with_assertion() app_result = DryRunExecutor.dryrun_app(algod, app_teal, args) @@ -86,7 +86,7 @@ def verify_fail(): ) approval_app = blackbox_pyteal(verify_fail, Mode.Application) - app_teal = compileTeal(approval_app(), Mode.Application, version=5) + app_teal = compileTeal(approval_app, Mode.Application, version=5) args = [] algod = algod_with_assertion() app_result = DryRunExecutor.dryrun_app(algod, app_teal, args) @@ -123,7 +123,7 @@ def decompress(): ) approval_app = blackbox_pyteal(decompress, Mode.Application) - app_teal = compileTeal(approval_app(), Mode.Application, version=5) + app_teal = compileTeal(approval_app, Mode.Application, version=5) args = [] algod = algod_with_assertion() app_result = DryRunExecutor.dryrun_app(algod, app_teal, args) @@ -165,7 +165,7 @@ def recover(): ) approval_app = blackbox_pyteal(recover, Mode.Application) - app_teal = compileTeal(approval_app(), Mode.Application, version=5) + app_teal = compileTeal(approval_app, Mode.Application, version=5) args = [] algod = algod_with_assertion() app_result = DryRunExecutor.dryrun_app(algod, app_teal, args) diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index 7e68f793e..7e86bcf15 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -90,9 +90,9 @@ def abi_sum( ) abi_sum_app_pt = blackbox_pyteal(abi_sum, pt.Mode.Application) - abi_sum_app_tl = pt.compileTeal(abi_sum_app_pt(), pt.Mode.Application, version=6) + abi_sum_app_tl = pt.compileTeal(abi_sum_app_pt, pt.Mode.Application, version=6) abi_sum_lsig_pt = blackbox_pyteal(abi_sum, pt.Mode.Signature) - abi_sum_lsig_tl = pt.compileTeal(abi_sum_lsig_pt(), pt.Mode.Signature, version=6) + abi_sum_lsig_tl = pt.compileTeal(abi_sum_lsig_pt, pt.Mode.Signature, version=6) todo_use_these_guys = abi_sum_app_tl todo_use_these_guys = abi_sum_lsig_tl @@ -344,27 +344,27 @@ def complex130_norm_squared(x: Complex130, *, output: Int65): def test_integer65(): bbpt_subtract_slick = BlackboxPyTealer(int65_sub, pt.Mode.Application) - approval_subtract_slick = bbpt_subtract_slick.program() + approval_subtract_slick = bbpt_subtract_slick.program teal_subtract_slick = pt.compileTeal( approval_subtract_slick(), pt.Mode.Application, version=6 ) bbpt_subtract_cond = BlackboxPyTealer(int65_minus_cond, pt.Mode.Application) - approval_subtract_cond = bbpt_subtract_cond.program() + approval_subtract_cond = bbpt_subtract_cond.program teal_subtract_cond = pt.compileTeal( approval_subtract_cond(), pt.Mode.Application, version=6 ) bbpt_mult = BlackboxPyTealer(int65_mult, pt.Mode.Application) - approval_mult = bbpt_mult.program() + approval_mult = bbpt_mult.program teal_mult = pt.compileTeal(approval_mult(), pt.Mode.Application, version=6) bbpt_negate = BlackboxPyTealer(int65_negate, pt.Mode.Application) - approval_negate = bbpt_negate.program() + approval_negate = bbpt_negate.program teal_negate = pt.compileTeal(approval_negate(), pt.Mode.Application, version=6) bbpt_add = BlackboxPyTealer(int65_add, pt.Mode.Application) - approval_add = bbpt_add.program() + approval_add = bbpt_add.program teal_add = pt.compileTeal(approval_add(), pt.Mode.Application, version=6) # same types, so no need to dupe: @@ -462,11 +462,11 @@ def test_complex130(): # Binary: bbpt_cplx_add = BlackboxPyTealer(complex130_add, pt.Mode.Application) - approval_cplx_add = bbpt_cplx_add.program() + approval_cplx_add = bbpt_cplx_add.program teal_cplx_add = pt.compileTeal(approval_cplx_add(), pt.Mode.Application, version=6) bbpt_cplx_mult = BlackboxPyTealer(complex130_mult, pt.Mode.Application) - approval_cplx_mult = bbpt_cplx_mult.program() + approval_cplx_mult = bbpt_cplx_mult.program teal_cplx_mult = pt.compileTeal( approval_cplx_mult(), pt.Mode.Application, version=6 ) @@ -474,19 +474,19 @@ def test_complex130(): # Unary: bbpt_complex_real = BlackboxPyTealer(complex130_real, pt.Mode.Application) - approval_cplx_real = bbpt_complex_real.program() + approval_cplx_real = bbpt_complex_real.program teal_cplx_real = pt.compileTeal( approval_cplx_real(), pt.Mode.Application, version=6 ) bbpt_complex_imag = BlackboxPyTealer(complex130_imag, pt.Mode.Application) - approval_cplx_imag = bbpt_complex_imag.program() + approval_cplx_imag = bbpt_complex_imag.program teal_cplx_imag = pt.compileTeal( approval_cplx_imag(), pt.Mode.Application, version=6 ) bbpt_complex_conjugate = BlackboxPyTealer(complex130_conjugate, pt.Mode.Application) - approval_cplx_conjugate = bbpt_complex_conjugate.program() + approval_cplx_conjugate = bbpt_complex_conjugate.program teal_cplx_conjugate = pt.compileTeal( approval_cplx_conjugate(), pt.Mode.Application, version=6 ) @@ -494,7 +494,7 @@ def test_complex130(): bbpt_complex_norm_squared = BlackboxPyTealer( complex130_norm_squared, pt.Mode.Application ) - approval_cplx_norm_squared = bbpt_complex_norm_squared.program() + approval_cplx_norm_squared = bbpt_complex_norm_squared.program teal_cplx_norm_squared = pt.compileTeal( approval_cplx_norm_squared(), pt.Mode.Application, version=6 ) diff --git a/tests/integration/graviton_test.py b/tests/integration/graviton_test.py index d1f5cd1aa..28c1b2276 100644 --- a/tests/integration/graviton_test.py +++ b/tests/integration/graviton_test.py @@ -46,7 +46,7 @@ def wrap_compile_and_save( # 2. TEAL generation teal = pt.compileTeal( - approval(), mode, version=version, assembleConstants=assemble_constants + approval, mode, version=version, assembleConstants=assemble_constants ) tealfile = f'{"app" if is_app else "lsig"}_{case_name}.teal' @@ -582,8 +582,8 @@ def square(x): approval_lsig = blackbox_pyteal(square, Mode.Signature) # compile the evaluated approvals to generate TEAL: - app_teal = compileTeal(approval_app(), Mode.Application, version=6) - lsig_teal = compileTeal(approval_lsig(), Mode.Signature, version=6) + app_teal = compileTeal(approval_app, Mode.Application, version=6) + lsig_teal = compileTeal(approval_lsig, Mode.Signature, version=6) # provide args for evaluation (will compute x^2) x = 9 @@ -648,7 +648,7 @@ def euclid(x, y): # create approval PyTeal and compile it to TEAL: euclid_app = blackbox_pyteal(euclid, Mode.Application) - euclid_app_teal = compileTeal(euclid_app(), Mode.Application, version=6) + euclid_app_teal = compileTeal(euclid_app, Mode.Application, version=6) # generate a report with 400 = 20*20 dry run rows: N = 20 @@ -746,7 +746,7 @@ def euclid(x, y): # Generate PyTeal and TEAL for the recursive Euclidean algorithm: euclid_app = blackbox_pyteal(euclid, Mode.Application) - euclid_app_teal = compileTeal(euclid_app(), Mode.Application, version=6) + euclid_app_teal = compileTeal(euclid_app, Mode.Application, version=6) # Execute on the input sequence to get a dry-run inspectors: algod = algod_with_assertion() @@ -808,8 +808,8 @@ def abi_sum(toSum: abi.DynamicArray[abi.Uint64], *, output: abi.Uint64) -> Expr: pyteal_abi_sum_lsig = lsig_pytealer.program() # compile the PyTeal's to Teal's: - teal_abi_sum_app = compileTeal(pyteal_abi_sum_app(), Mode.Application, version=6) - teal_abi_sum_lsig = compileTeal(pyteal_abi_sum_lsig(), Mode.Signature, version=6) + teal_abi_sum_app = compileTeal(pyteal_abi_sum_app, Mode.Application, version=6) + teal_abi_sum_lsig = compileTeal(pyteal_abi_sum_lsig, Mode.Signature, version=6) # infer the abi types for encoding/decoding dry runs: abi_argument_types = app_pytealer.abi_argument_types() diff --git a/tests/unit/blackbox_test.py b/tests/unit/blackbox_test.py index e8c9da6d8..367f80ff8 100644 --- a/tests/unit/blackbox_test.py +++ b/tests/unit/blackbox_test.py @@ -101,7 +101,7 @@ def fn_2arg_0ret( return pt.Return() -@Blackbox(input_types=[None, None]) +@Blackbox(input_types=[None, pt.TealType.uint64, None]) @pt.ABIReturnSubroutine def fn_3mixed_args_0ret( a: pt.abi.Uint64, b: pt.ScratchVar, C: pt.abi.StaticArray[pt.abi.Byte, Literal[10]] @@ -136,7 +136,7 @@ def test_blackbox_pyteal(subr, mode): is_app = mode == pt.Mode.Application name = f"{'app' if is_app else 'lsig'}_{subr.name()}" - compiled = pt.compileTeal(blackbox_pyteal(subr, mode)(), mode, version=6) + compiled = pt.compileTeal(blackbox_pyteal(subr, mode), mode, version=6) tealdir = GENERATED / "blackbox" tealdir.mkdir(parents=True, exist_ok=True) save_to = tealdir / (name + ".teal") @@ -146,16 +146,16 @@ def test_blackbox_pyteal(subr, mode): assert_teal_as_expected(save_to, FIXTURES / "blackbox" / (name + ".teal")) -@pytest.mark.parametrize("subr, mode", product(UNITS, pt.Mode)) -def test_abi_blackbox_pyteal(subr, mode): - is_app = mode == pt.Mode.Application - name = f"{'app' if is_app else 'lsig'}_{subr.name()}" +# @pytest.mark.parametrize("subr, mode", product(UNITS, pt.Mode)) +# def test_abi_blackbox_pyteal(subr, mode): +# is_app = mode == pt.Mode.Application +# name = f"{'app' if is_app else 'lsig'}_{subr.name()}" - compiled = pt.compileTeal(blackbox_pyteal(subr, mode)(), mode, version=6) - tealdir = GENERATED / "blackbox" - tealdir.mkdir(parents=True, exist_ok=True) - save_to = tealdir / (name + ".teal") - with open(save_to, "w") as f: - f.write(compiled) +# compiled = pt.compileTeal(blackbox_pyteal(subr, mode)(), mode, version=6) +# tealdir = GENERATED / "blackbox" +# tealdir.mkdir(parents=True, exist_ok=True) +# save_to = tealdir / (name + ".teal") +# with open(save_to, "w") as f: +# f.write(compiled) - assert_teal_as_expected(save_to, FIXTURES / "blackbox" / (name + ".teal")) +# assert_teal_as_expected(save_to, FIXTURES / "blackbox" / (name + ".teal")) From c3960e6aecc5e92bb3d975b5e727157e3bd89b2a Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 10 May 2022 08:23:21 -0500 Subject: [PATCH 113/170] really we should hook mypy into tests/ --- tests/blackbox.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/blackbox.py b/tests/blackbox.py index f2c4a84cb..bde1e0be3 100644 --- a/tests/blackbox.py +++ b/tests/blackbox.py @@ -1,5 +1,3 @@ -from typing import Callable - import algosdk.abi from algosdk.v2client import algod @@ -138,7 +136,7 @@ def mode_to_execution_mode(mode: Mode) -> blackbox.ExecutionMode: raise Exception(f"Unknown mode {mode} of type {type(mode)}") -def blackbox_pyteal(subr: BlackboxWrapper, mode: Mode) -> Callable[..., Expr]: +def blackbox_pyteal(subr: BlackboxWrapper, mode: Mode) -> Expr: """Functor producing ready-to-compile PyTeal programs from annotated subroutines Args: @@ -247,7 +245,7 @@ def abi_return_type(self) -> None | algosdk.abi.ABIType: self.subr.subroutine.output_kwarg_info.abi_type ) - def program(self) -> Callable[..., Expr]: + def program(self) -> Expr: return self._pyteal def _handle_SubroutineFnWrapper(self): From 4401a411960d51b08b2294fe5d0a23c6f31859f5 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 10 May 2022 08:27:12 -0500 Subject: [PATCH 114/170] but should still lazy-eval the program --- tests/blackbox.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/blackbox.py b/tests/blackbox.py index bde1e0be3..9d0f57f3f 100644 --- a/tests/blackbox.py +++ b/tests/blackbox.py @@ -1,3 +1,5 @@ +from typing import Callable + import algosdk.abi from algosdk.v2client import algod @@ -218,7 +220,7 @@ def __init__(self, subr: BlackboxWrapper, mode: Mode): ) setattr(approval, "__name__", f"sem_{mode}_{subr.name()}") - self._pyteal: Expr = approval() + self._pyteal_lambda: Callable[..., Expr] = approval def is_abi(self) -> bool: return isinstance(self.subr.subroutine, ABIReturnSubroutine) @@ -246,7 +248,7 @@ def abi_return_type(self) -> None | algosdk.abi.ABIType: ) def program(self) -> Expr: - return self._pyteal + return self._pyteal_lambda() def _handle_SubroutineFnWrapper(self): subdef = self.subr.subroutine.subroutine From a05de2fa086de303a8d1ed9144e064cda34ce520 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 10 May 2022 10:30:11 -0500 Subject: [PATCH 115/170] Update tests/blackbox.py Co-authored-by: Michael Diamant --- tests/blackbox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/blackbox.py b/tests/blackbox.py index 9d0f57f3f..322a57f61 100644 --- a/tests/blackbox.py +++ b/tests/blackbox.py @@ -227,7 +227,7 @@ def is_abi(self) -> bool: def abi_argument_types(self) -> list[None | algosdk.abi.ABIType]: if not self.is_abi(): - raise NotImplementedError("this {type(self)} is not A.B.I. compatible") + raise NotImplementedError(f"this {type(self)} is not A.B.I. compatible") def handle_arg(arg): if isinstance(arg, abi.TypeSpec): From a0c6a42c5c3ff274b30facd8a3127e12e3fc3240 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 10 May 2022 14:26:58 -0400 Subject: [PATCH 116/170] add some compiler test --- pyteal/compiler/compiler_test.py | 189 +++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index ee2f9ce82..062b88ed0 100644 --- a/pyteal/compiler/compiler_test.py +++ b/pyteal/compiler/compiler_test.py @@ -2004,3 +2004,192 @@ def test_compile_wide_ratio(): program, pt.Mode.Application, version=5, assembleConstants=False ) assert actual == expected.strip() + + +def test_compile_abi_subroutine_return(): + @pt.ABIReturnSubroutine + def abi_sum( + toSum: pt.abi.DynamicArray[pt.abi.Uint64], *, output: pt.abi.Uint64 + ) -> pt.Expr: + i = pt.ScratchVar(pt.TealType.uint64) + valueAtIndex = pt.abi.Uint64() + return pt.Seq( + output.set(0), + pt.For( + i.store(pt.Int(0)), + i.load() < toSum.length(), + i.store(i.load() + pt.Int(1)), + ).Do( + pt.Seq( + toSum[i.load()].store_into(valueAtIndex), + output.set(output.get() + valueAtIndex.get()), + ) + ), + ) + + program = pt.Seq( + (to_sum_arr := pt.abi.make(pt.abi.DynamicArray[pt.abi.Uint64])).decode( + pt.Txn.application_args[1] + ), + (res := pt.abi.Uint64()).set(abi_sum(to_sum_arr)), + pt.abi.MethodReturn(res), + pt.Approve(), + ) + + expected_sum = """#pragma version 6 +txna ApplicationArgs 1 +store 0 +load 0 +callsub abisum_0 +store 1 +byte 0x151F7C75 +load 1 +itob +concat +log +int 1 +return + +// abi_sum +abisum_0: +store 2 +int 0 +store 3 +int 0 +store 4 +abisum_0_l1: +load 4 +load 2 +int 0 +extract_uint16 +store 6 +load 6 +< +bz abisum_0_l3 +load 2 +int 8 +load 4 +* +int 2 ++ +extract_uint64 +store 5 +load 3 +load 5 ++ +store 3 +load 4 +int 1 ++ +store 4 +b abisum_0_l1 +abisum_0_l3: +load 3 +retsub + """.strip() + + actual_sum = pt.compileTeal(program, pt.Mode.Application, version=6) + assert expected_sum == actual_sum + + @pt.ABIReturnSubroutine + def conditional_factorial( + _factor: pt.abi.Uint64, *, output: pt.abi.Uint64 + ) -> pt.Expr: + i = pt.ScratchVar(pt.TealType.uint64) + + return pt.Seq( + output.set(1), + pt.If(_factor.get() <= pt.Int(1)) + .Then(pt.Return()) + .Else( + pt.For( + i.store(_factor.get()), + i.load() > pt.Int(1), + i.store(i.load() - pt.Int(1)), + ).Do(output.set(output.get() * i.load())), + ), + ) + + program_cond_factorial = pt.Seq( + (factor := pt.abi.Uint64()).decode(pt.Txn.application_args[1]), + (res := pt.abi.Uint64()).set(conditional_factorial(factor)), + pt.abi.MethodReturn(res), + pt.Approve(), + ) + + expected_conditional_factorial = """#pragma version 6 +txna ApplicationArgs 1 +btoi +store 0 +load 0 +callsub conditionalfactorial_0 +store 1 +byte 0x151F7C75 +load 1 +itob +concat +log +int 1 +return + +// conditional_factorial +conditionalfactorial_0: +store 2 +int 1 +store 3 +load 2 +int 1 +<= +bnz conditionalfactorial_0_l4 +load 2 +store 4 +conditionalfactorial_0_l2: +load 4 +int 1 +> +bz conditionalfactorial_0_l5 +load 3 +load 4 +* +store 3 +load 4 +int 1 +- +store 4 +b conditionalfactorial_0_l2 +conditionalfactorial_0_l4: +load 3 +retsub +conditionalfactorial_0_l5: +load 3 +retsub + """.strip() + + actual_conditional_factorial = pt.compileTeal( + program_cond_factorial, pt.Mode.Application, version=6 + ) + assert actual_conditional_factorial == expected_conditional_factorial + + @pt.ABIReturnSubroutine + def load_b4_set(*, output: pt.abi.Bool): + return pt.Return() + + program_load_b4_set_broken = pt.Seq( + (pseudo_rand := pt.abi.Bool()).set(load_b4_set()), pt.Approve() + ) + + with pytest.raises(pt.TealInternalError): + pt.compileTeal(program_load_b4_set_broken, pt.Mode.Application, version=6) + + @pt.ABIReturnSubroutine + def access_b4_store(magic_num: pt.abi.Uint64, *, output: pt.abi.Uint64): + return pt.Seq(output.set(output.get() ^ magic_num.get())) + + program_access_b4_store_broken = pt.Seq( + (other_party_magic := pt.abi.Uint64()).decode(pt.Txn.application_args[1]), + (pseudo_rand := pt.abi.Uint64()).set(access_b4_store(other_party_magic)), + pt.Approve(), + ) + + with pytest.raises(pt.TealInternalError): + pt.compileTeal(program_access_b4_store_broken, pt.Mode.Application, version=6) From a14f27161a9c4bb4ae209869da467a46bfd3402f Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 10 May 2022 14:30:12 -0400 Subject: [PATCH 117/170] flake break no way --- pyteal/compiler/compiler_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyteal/compiler/compiler_test.py b/pyteal/compiler/compiler_test.py index 062b88ed0..a89f075fc 100644 --- a/pyteal/compiler/compiler_test.py +++ b/pyteal/compiler/compiler_test.py @@ -2175,7 +2175,7 @@ def load_b4_set(*, output: pt.abi.Bool): return pt.Return() program_load_b4_set_broken = pt.Seq( - (pseudo_rand := pt.abi.Bool()).set(load_b4_set()), pt.Approve() + (_ := pt.abi.Bool()).set(load_b4_set()), pt.Approve() ) with pytest.raises(pt.TealInternalError): @@ -2187,7 +2187,7 @@ def access_b4_store(magic_num: pt.abi.Uint64, *, output: pt.abi.Uint64): program_access_b4_store_broken = pt.Seq( (other_party_magic := pt.abi.Uint64()).decode(pt.Txn.application_args[1]), - (pseudo_rand := pt.abi.Uint64()).set(access_b4_store(other_party_magic)), + (_ := pt.abi.Uint64()).set(access_b4_store(other_party_magic)), pt.Approve(), ) From 96e11f4f679a171f73c84a860d27dea0e50e3e61 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 10 May 2022 14:50:48 -0500 Subject: [PATCH 118/170] bugfix for case of no abi params + add abi unit test fixtures and pass the unit test --- tests/blackbox.py | 130 +++++++++--------- tests/unit/blackbox_test.py | 70 +++++++--- tests/unit/teal/abi/app_fn_0arg_0ret.teal | 8 ++ .../unit/teal/abi/app_fn_0arg_uint64_ret.teal | 17 +++ tests/unit/teal/abi/app_fn_1arg_0ret.teal | 13 ++ tests/unit/teal/abi/app_fn_1arg_1ret.teal | 22 +++ .../teal/abi/app_fn_1tt_arg_uint64_ret.teal | 19 +++ tests/unit/teal/abi/app_fn_2arg_0ret.teal | 17 +++ .../unit/teal/abi/app_fn_2mixed_arg_1ret.teal | 30 ++++ .../teal/abi/app_fn_3mixed_args_0ret.teal | 22 +++ tests/unit/teal/abi/lsig_fn_0arg_0ret.teal | 8 ++ .../teal/abi/lsig_fn_0arg_uint64_ret.teal | 15 ++ tests/unit/teal/abi/lsig_fn_1arg_0ret.teal | 13 ++ tests/unit/teal/abi/lsig_fn_1arg_1ret.teal | 20 +++ .../teal/abi/lsig_fn_1tt_arg_uint64_ret.teal | 17 +++ tests/unit/teal/abi/lsig_fn_2arg_0ret.teal | 17 +++ .../teal/abi/lsig_fn_2mixed_arg_1ret.teal | 28 ++++ .../teal/abi/lsig_fn_3mixed_args_0ret.teal | 22 +++ 18 files changed, 407 insertions(+), 81 deletions(-) create mode 100644 tests/unit/teal/abi/app_fn_0arg_0ret.teal create mode 100644 tests/unit/teal/abi/app_fn_0arg_uint64_ret.teal create mode 100644 tests/unit/teal/abi/app_fn_1arg_0ret.teal create mode 100644 tests/unit/teal/abi/app_fn_1arg_1ret.teal create mode 100644 tests/unit/teal/abi/app_fn_1tt_arg_uint64_ret.teal create mode 100644 tests/unit/teal/abi/app_fn_2arg_0ret.teal create mode 100644 tests/unit/teal/abi/app_fn_2mixed_arg_1ret.teal create mode 100644 tests/unit/teal/abi/app_fn_3mixed_args_0ret.teal create mode 100644 tests/unit/teal/abi/lsig_fn_0arg_0ret.teal create mode 100644 tests/unit/teal/abi/lsig_fn_0arg_uint64_ret.teal create mode 100644 tests/unit/teal/abi/lsig_fn_1arg_0ret.teal create mode 100644 tests/unit/teal/abi/lsig_fn_1arg_1ret.teal create mode 100644 tests/unit/teal/abi/lsig_fn_1tt_arg_uint64_ret.teal create mode 100644 tests/unit/teal/abi/lsig_fn_2arg_0ret.teal create mode 100644 tests/unit/teal/abi/lsig_fn_2mixed_arg_1ret.teal create mode 100644 tests/unit/teal/abi/lsig_fn_3mixed_args_0ret.teal diff --git a/tests/blackbox.py b/tests/blackbox.py index 9d0f57f3f..da6ba4228 100644 --- a/tests/blackbox.py +++ b/tests/blackbox.py @@ -139,74 +139,26 @@ def mode_to_execution_mode(mode: Mode) -> blackbox.ExecutionMode: def blackbox_pyteal(subr: BlackboxWrapper, mode: Mode) -> Expr: - """Functor producing ready-to-compile PyTeal programs from annotated subroutines - - Args: - subr: a Subroutine or ABIReturnSubroutine which has been decorated with @Blackbox. - Note: the `input_types` parameters should be supplied to the @Blackbox() decorator - mode: type of program to produce: logic sig (Mode.Signature) or app (Mode.Application) - - Returns: - a function that called with no parameters -e.g. result()- - returns a PyTeal expression compiling to a ready-to-test TEAL program. - - The return type is callable in order to adhere to the API of blackbox tests. - - Generated TEAL code depends on the mode, subroutine input types, and subroutine output types. - * logic sigs: - * input received via `arg i` - * args are converted (cf. "input conversion" below) and passed to the subroutine - * subroutine output is not logged (log is not available) - * subroutine output is converted (cf "output conversion" below) - * apps: - * input received via `txna ApplicationArgs i` - * args are converted (cf. "input conversion" below) and passed to the subroutine - * subroutine output is logged after possible conversion (cf. "logging conversion") - * subroutine output is converted (cf "output conversion" below) - * input conversion: - * Empty input array: - do not read any args and call subroutine immediately - * arg of TealType.bytes and TealType.anytype: - read arg and pass to subroutine as is - * arg of TealType.uint64: - convert arg to int using Btoi() when received - * pass-by-ref ScratchVar arguments: - in addition to the above - - o store the arg (or converted arg) in a ScratchVar - o invoke the subroutine using this ScratchVar instead of the arg (or converted arg) - * output conversion: - * TealType.uint64: - provide subroutine's result to the top of the stack when exiting program - * TealType.bytes: - convert subroutine's result to the top of the stack to its length and then exit - * TealType.none or TealType.anytype: - push Int(1337) to the stack as it is either impossible (TealType.none), - or unknown at compile time (TealType.anytype) to convert to an Int - * logging conversion: - * TealType.uint64: - convert subroutine's output using Itob() and log the result - * TealType.bytes: - log the subroutine's result - * TealType.none or TealType.anytype: - log Itob(Int(1337)) as it is either impossible (TealType.none), - or unknown at compile time (TealType.anytype) how to convert to Bytes - - For illustrative examples of how to use this function please refer to the integration test file `graviton_test.py` and especially: - - * `blackbox_pyteal_example1()`: Using blackbox_pyteal() for a simple test of both an app and logic sig - * `blackbox_pyteal_example2()`: Using blackbox_pyteal() to make 400 assertions and generate a CSV report with 400 dryrun rows - * `blackbox_pyteal_example3()`: declarative Test Driven Development approach through Invariant's - * `blackbox_pyteal_example4()`: Using BlackboxPyTealer.program() to debug an ABIReturnSubroutine with an app, logic sig and csv report + """ + cf. BlackboxPyTealer.program() for futher details """ return BlackboxPyTealer(subr, mode).program() class BlackboxPyTealer: def __init__(self, subr: BlackboxWrapper, mode: Mode): + """ + Args: + subr: a Subroutine or ABIReturnSubroutine which has been decorated with @Blackbox. + Note: the `input_types` parameters should be supplied to the @Blackbox() decorator + cf. the Blackbox class for futher details about acceptable `input_types` + + mode: type of program to produce: logic sig (Mode.Signature) or app (Mode.Application) + """ input_types = subr.input_types assert ( input_types is not None - ), "please provide input_types in your @Subroutine annotation (crucial for generating proper end-to-end testable PyTeal)" + ), "please provide input_types in your @Subroutine or @ABIReturnSubroutine annotation (this is crucial for generating proper end-to-end testable PyTeal)" self.subr, self.mode, self.input_types = subr, mode, input_types match subr.subroutine: @@ -219,7 +171,6 @@ def __init__(self, subr: BlackboxWrapper, mode: Mode): f"Cannot produce Blackbox pyteal for provided subroutine of type {type(subr.subroutine)}" ) - setattr(approval, "__name__", f"sem_{mode}_{subr.name()}") self._pyteal_lambda: Callable[..., Expr] = approval def is_abi(self) -> bool: @@ -248,6 +199,61 @@ def abi_return_type(self) -> None | algosdk.abi.ABIType: ) def program(self) -> Expr: + """Functor producing ready-to-compile PyTeal programs from annotated subroutines + + Returns: + a function that called with no parameters -e.g. result()- + returns a PyTeal expression compiling to a ready-to-test TEAL program. + + The return type is callable in order to adhere to the API of blackbox tests. + + Generated TEAL code depends on the mode, subroutine input types, and subroutine output types. + * logic sigs: + * input received via `arg i` + * args are converted (cf. "input conversion" below) and passed to the subroutine + * subroutine output is not logged (log is not available) + * subroutine output is converted (cf "output conversion" below) + * apps: + * input received via `txna ApplicationArgs i` + * args are converted (cf. "input conversion" below) and passed to the subroutine + * subroutine output is logged after possible conversion (cf. "logging conversion") + * subroutine output is converted (cf "output conversion" below) + * input conversion: + * Empty input array: + do not read any args and call subroutine immediately + * arg of TealType.bytes and TealType.anytype: + read arg and pass to subroutine as is + * arg of TealType.uint64: + convert arg to int using Btoi() when received + * pass-by-ref ScratchVar arguments: + in addition to the above - + o store the arg (or converted arg) in a ScratchVar + o invoke the subroutine using this ScratchVar instead of the arg (or converted arg) + * output conversion: + * TealType.uint64: + provide subroutine's result to the top of the stack when exiting program + * TealType.bytes: + convert subroutine's result to the top of the stack to its length and then exit + * TealType.none or TealType.anytype: + push Int(1337) to the stack as it is either impossible (TealType.none), + or unknown at compile time (TealType.anytype) to convert to an Int + * logging conversion: + * TealType.uint64: + convert subroutine's output using Itob() and log the result + * TealType.bytes: + log the subroutine's result + * TealType.none or TealType.anytype: + log Itob(Int(1337)) as it is either impossible (TealType.none), + or unknown at compile time (TealType.anytype) how to convert to Bytes + + For illustrative examples of how to use this function please refer to the integration test file `graviton_test.py` and especially: + + * `blackbox_pyteal_example1()`: Using blackbox_pyteal() for a simple test of both an app and logic sig + * `blackbox_pyteal_example2()`: Using blackbox_pyteal() to make 400 assertions and generate a CSV report with 400 dryrun rows + * `blackbox_pyteal_example3()`: declarative Test Driven Development approach through Invariant's + * `blackbox_pyteal_example4()`: Using BlackboxPyTealer.program() to debug an ABIReturnSubroutine with an app, logic sig and csv report + """ + return self._pyteal_lambda() def _handle_SubroutineFnWrapper(self): @@ -364,8 +370,6 @@ def approval(): else: results = [invocation, Int(1)] - if preps: - return Seq(*(preps + results)) - return results + return Seq(*(preps + results)) return approval diff --git a/tests/unit/blackbox_test.py b/tests/unit/blackbox_test.py index 367f80ff8..36ed58682 100644 --- a/tests/unit/blackbox_test.py +++ b/tests/unit/blackbox_test.py @@ -5,7 +5,7 @@ import pyteal as pt -from tests.blackbox import Blackbox, blackbox_pyteal +from tests.blackbox import Blackbox, BlackboxPyTealer, blackbox_pyteal from tests.compile_asserts import assert_teal_as_expected @@ -101,6 +101,12 @@ def fn_2arg_0ret( return pt.Return() +@Blackbox(input_types=[pt.TealType.bytes]) +@pt.ABIReturnSubroutine +def fn_1tt_arg_uint64_ret(x, *, output: pt.abi.Uint64) -> pt.Expr: + return output.set(1) + + @Blackbox(input_types=[None, pt.TealType.uint64, None]) @pt.ABIReturnSubroutine def fn_3mixed_args_0ret( @@ -109,6 +115,14 @@ def fn_3mixed_args_0ret( return pt.Return() +@Blackbox(input_types=[None, pt.TealType.bytes]) +@pt.ABIReturnSubroutine +def fn_2mixed_arg_1ret( + a: pt.abi.Uint64, b: pt.ScratchVar, *, output: pt.abi.Uint64 +) -> pt.Expr: + return pt.Seq(b.store(a.encode()), output.set(a)) + + UNITS = [ utest_noop, utest_noop_args, @@ -122,12 +136,14 @@ def fn_3mixed_args_0ret( ABI_UNITS = [ - fn_0arg_0ret, - fn_0arg_uint64_ret, - fn_1arg_0ret, - fn_1arg_1ret, - fn_2arg_0ret, - fn_3mixed_args_0ret, + (fn_0arg_0ret, None), + (fn_0arg_uint64_ret, pt.abi.Uint64()), + (fn_1arg_0ret, None), + (fn_1arg_1ret, pt.abi.Uint64()), + (fn_2arg_0ret, None), + (fn_1tt_arg_uint64_ret, pt.abi.Uint64()), + (fn_3mixed_args_0ret, None), + (fn_2mixed_arg_1ret, pt.abi.Uint64()), ] @@ -146,16 +162,34 @@ def test_blackbox_pyteal(subr, mode): assert_teal_as_expected(save_to, FIXTURES / "blackbox" / (name + ".teal")) -# @pytest.mark.parametrize("subr, mode", product(UNITS, pt.Mode)) -# def test_abi_blackbox_pyteal(subr, mode): -# is_app = mode == pt.Mode.Application -# name = f"{'app' if is_app else 'lsig'}_{subr.name()}" +@pytest.mark.parametrize("subr_abi, mode", product(ABI_UNITS, pt.Mode)) +def test_abi_blackbox_pyteal(subr_abi, mode): + subr, abi_return_type = subr_abi + name = f"{'app' if mode == pt.Mode.Application else 'lsig'}_{subr.name()}" + print(f"Case {subr.name()=}, {abi_return_type=}, {mode=} ------> {name=}") -# compiled = pt.compileTeal(blackbox_pyteal(subr, mode)(), mode, version=6) -# tealdir = GENERATED / "blackbox" -# tealdir.mkdir(parents=True, exist_ok=True) -# save_to = tealdir / (name + ".teal") -# with open(save_to, "w") as f: -# f.write(compiled) + pytealer = BlackboxPyTealer(subr, mode) + assert pytealer.is_abi(), "should be an ABI subroutine" + + arg_types = pytealer.abi_argument_types() + if subr.name() != "fn_1tt_arg_uint64_ret": + assert not arg_types or any( + arg_types + ), "abi_argument_types() should have had some abi info" + + if abi_return_type: + expected_sdk_return_type = pt.abi.algosdk_from_type_spec( + abi_return_type.type_spec() + ) + assert expected_sdk_return_type == pytealer.abi_return_type() + else: + assert pytealer.abi_return_type() is None + + compiled = pt.compileTeal(blackbox_pyteal(subr, mode), mode, version=6) + tealdir = GENERATED / "abi" + tealdir.mkdir(parents=True, exist_ok=True) + save_to = tealdir / (name + ".teal") + with open(save_to, "w") as f: + f.write(compiled) -# assert_teal_as_expected(save_to, FIXTURES / "blackbox" / (name + ".teal")) + assert_teal_as_expected(save_to, FIXTURES / "abi" / (name + ".teal")) diff --git a/tests/unit/teal/abi/app_fn_0arg_0ret.teal b/tests/unit/teal/abi/app_fn_0arg_0ret.teal new file mode 100644 index 000000000..93cb677df --- /dev/null +++ b/tests/unit/teal/abi/app_fn_0arg_0ret.teal @@ -0,0 +1,8 @@ +#pragma version 6 +callsub fn0arg0ret_0 +int 1 +return + +// fn_0arg_0ret +fn0arg0ret_0: +retsub \ No newline at end of file diff --git a/tests/unit/teal/abi/app_fn_0arg_uint64_ret.teal b/tests/unit/teal/abi/app_fn_0arg_uint64_ret.teal new file mode 100644 index 000000000..54a1a0a58 --- /dev/null +++ b/tests/unit/teal/abi/app_fn_0arg_uint64_ret.teal @@ -0,0 +1,17 @@ +#pragma version 6 +callsub fn0arguint64ret_0 +store 1 +byte 0x151F7C75 +load 1 +itob +concat +log +int 1 +return + +// fn_0arg_uint64_ret +fn0arguint64ret_0: +int 1 +store 0 +load 0 +retsub \ No newline at end of file diff --git a/tests/unit/teal/abi/app_fn_1arg_0ret.teal b/tests/unit/teal/abi/app_fn_1arg_0ret.teal new file mode 100644 index 000000000..a21e04c5d --- /dev/null +++ b/tests/unit/teal/abi/app_fn_1arg_0ret.teal @@ -0,0 +1,13 @@ +#pragma version 6 +txna ApplicationArgs 0 +btoi +store 1 +load 1 +callsub fn1arg0ret_0 +int 1 +return + +// fn_1arg_0ret +fn1arg0ret_0: +store 0 +retsub \ No newline at end of file diff --git a/tests/unit/teal/abi/app_fn_1arg_1ret.teal b/tests/unit/teal/abi/app_fn_1arg_1ret.teal new file mode 100644 index 000000000..6ae354dac --- /dev/null +++ b/tests/unit/teal/abi/app_fn_1arg_1ret.teal @@ -0,0 +1,22 @@ +#pragma version 6 +txna ApplicationArgs 0 +btoi +store 3 +load 3 +callsub fn1arg1ret_0 +store 2 +byte 0x151F7C75 +load 2 +itob +concat +log +int 1 +return + +// fn_1arg_1ret +fn1arg1ret_0: +store 0 +load 0 +store 1 +load 1 +retsub \ No newline at end of file diff --git a/tests/unit/teal/abi/app_fn_1tt_arg_uint64_ret.teal b/tests/unit/teal/abi/app_fn_1tt_arg_uint64_ret.teal new file mode 100644 index 000000000..bae9169fc --- /dev/null +++ b/tests/unit/teal/abi/app_fn_1tt_arg_uint64_ret.teal @@ -0,0 +1,19 @@ +#pragma version 6 +txna ApplicationArgs 0 +callsub fn1ttarguint64ret_0 +store 2 +byte 0x151F7C75 +load 2 +itob +concat +log +int 1 +return + +// fn_1tt_arg_uint64_ret +fn1ttarguint64ret_0: +store 0 +int 1 +store 1 +load 1 +retsub \ No newline at end of file diff --git a/tests/unit/teal/abi/app_fn_2arg_0ret.teal b/tests/unit/teal/abi/app_fn_2arg_0ret.teal new file mode 100644 index 000000000..aec416290 --- /dev/null +++ b/tests/unit/teal/abi/app_fn_2arg_0ret.teal @@ -0,0 +1,17 @@ +#pragma version 6 +txna ApplicationArgs 0 +btoi +store 2 +txna ApplicationArgs 1 +store 3 +load 2 +load 3 +callsub fn2arg0ret_0 +int 1 +return + +// fn_2arg_0ret +fn2arg0ret_0: +store 1 +store 0 +retsub \ No newline at end of file diff --git a/tests/unit/teal/abi/app_fn_2mixed_arg_1ret.teal b/tests/unit/teal/abi/app_fn_2mixed_arg_1ret.teal new file mode 100644 index 000000000..ed0ce47e3 --- /dev/null +++ b/tests/unit/teal/abi/app_fn_2mixed_arg_1ret.teal @@ -0,0 +1,30 @@ +#pragma version 6 +txna ApplicationArgs 0 +btoi +store 4 +txna ApplicationArgs 1 +store 5 +load 4 +int 5 +callsub fn2mixedarg1ret_0 +store 3 +byte 0x151F7C75 +load 3 +itob +concat +log +int 1 +return + +// fn_2mixed_arg_1ret +fn2mixedarg1ret_0: +store 1 +store 0 +load 1 +load 0 +itob +stores +load 0 +store 2 +load 2 +retsub \ No newline at end of file diff --git a/tests/unit/teal/abi/app_fn_3mixed_args_0ret.teal b/tests/unit/teal/abi/app_fn_3mixed_args_0ret.teal new file mode 100644 index 000000000..0264a222e --- /dev/null +++ b/tests/unit/teal/abi/app_fn_3mixed_args_0ret.teal @@ -0,0 +1,22 @@ +#pragma version 6 +txna ApplicationArgs 0 +btoi +store 3 +txna ApplicationArgs 1 +btoi +store 4 +txna ApplicationArgs 2 +store 5 +load 3 +int 4 +load 5 +callsub fn3mixedargs0ret_0 +int 1 +return + +// fn_3mixed_args_0ret +fn3mixedargs0ret_0: +store 2 +store 1 +store 0 +retsub \ No newline at end of file diff --git a/tests/unit/teal/abi/lsig_fn_0arg_0ret.teal b/tests/unit/teal/abi/lsig_fn_0arg_0ret.teal new file mode 100644 index 000000000..93cb677df --- /dev/null +++ b/tests/unit/teal/abi/lsig_fn_0arg_0ret.teal @@ -0,0 +1,8 @@ +#pragma version 6 +callsub fn0arg0ret_0 +int 1 +return + +// fn_0arg_0ret +fn0arg0ret_0: +retsub \ No newline at end of file diff --git a/tests/unit/teal/abi/lsig_fn_0arg_uint64_ret.teal b/tests/unit/teal/abi/lsig_fn_0arg_uint64_ret.teal new file mode 100644 index 000000000..e9355f259 --- /dev/null +++ b/tests/unit/teal/abi/lsig_fn_0arg_uint64_ret.teal @@ -0,0 +1,15 @@ +#pragma version 6 +callsub fn0arguint64ret_0 +store 0 +load 0 +itob +pop +int 1 +return + +// fn_0arg_uint64_ret +fn0arguint64ret_0: +int 1 +store 1 +load 1 +retsub \ No newline at end of file diff --git a/tests/unit/teal/abi/lsig_fn_1arg_0ret.teal b/tests/unit/teal/abi/lsig_fn_1arg_0ret.teal new file mode 100644 index 000000000..cf4a713c8 --- /dev/null +++ b/tests/unit/teal/abi/lsig_fn_1arg_0ret.teal @@ -0,0 +1,13 @@ +#pragma version 6 +arg 0 +btoi +store 0 +load 0 +callsub fn1arg0ret_0 +int 1 +return + +// fn_1arg_0ret +fn1arg0ret_0: +store 1 +retsub \ No newline at end of file diff --git a/tests/unit/teal/abi/lsig_fn_1arg_1ret.teal b/tests/unit/teal/abi/lsig_fn_1arg_1ret.teal new file mode 100644 index 000000000..b9316e7df --- /dev/null +++ b/tests/unit/teal/abi/lsig_fn_1arg_1ret.teal @@ -0,0 +1,20 @@ +#pragma version 6 +arg 0 +btoi +store 1 +load 1 +callsub fn1arg1ret_0 +store 0 +load 0 +itob +pop +int 1 +return + +// fn_1arg_1ret +fn1arg1ret_0: +store 2 +load 2 +store 3 +load 3 +retsub \ No newline at end of file diff --git a/tests/unit/teal/abi/lsig_fn_1tt_arg_uint64_ret.teal b/tests/unit/teal/abi/lsig_fn_1tt_arg_uint64_ret.teal new file mode 100644 index 000000000..8ea2f5585 --- /dev/null +++ b/tests/unit/teal/abi/lsig_fn_1tt_arg_uint64_ret.teal @@ -0,0 +1,17 @@ +#pragma version 6 +arg 0 +callsub fn1ttarguint64ret_0 +store 0 +load 0 +itob +pop +int 1 +return + +// fn_1tt_arg_uint64_ret +fn1ttarguint64ret_0: +store 1 +int 1 +store 2 +load 2 +retsub \ No newline at end of file diff --git a/tests/unit/teal/abi/lsig_fn_2arg_0ret.teal b/tests/unit/teal/abi/lsig_fn_2arg_0ret.teal new file mode 100644 index 000000000..c097d4e6c --- /dev/null +++ b/tests/unit/teal/abi/lsig_fn_2arg_0ret.teal @@ -0,0 +1,17 @@ +#pragma version 6 +arg 0 +btoi +store 0 +arg 1 +store 1 +load 0 +load 1 +callsub fn2arg0ret_0 +int 1 +return + +// fn_2arg_0ret +fn2arg0ret_0: +store 3 +store 2 +retsub \ No newline at end of file diff --git a/tests/unit/teal/abi/lsig_fn_2mixed_arg_1ret.teal b/tests/unit/teal/abi/lsig_fn_2mixed_arg_1ret.teal new file mode 100644 index 000000000..5d2ae86b2 --- /dev/null +++ b/tests/unit/teal/abi/lsig_fn_2mixed_arg_1ret.teal @@ -0,0 +1,28 @@ +#pragma version 6 +arg 0 // [a] +btoi // [btoi(a)] +store 1 // 1 -> btoi(a) +arg 1 // [b] +store 2 // 2 -> b +load 1 // [btoi(a)] +int 2 // [btoi(a), 2] +callsub fn2mixedarg1ret_0 // [btoi(a)] +store 0 // 0 -> btoi(a) +load 0 // [btoi(a)] +itob // [a] +pop // [] +int 1 // [1] +return + +// fn_2mixed_arg_1ret +fn2mixedarg1ret_0: // [btoi(a), 2] +store 4 // 4 -> 2 +store 3 // 3 -> btoi(a) +load 4 // [2] +load 3 // [2, btoi(a)] +itob // [2, a] +stores // 2 -> a +load 3 // [btoi(a)] +store 5 // 5 -> btoi(a) +load 5 // [btoi(a)] +retsub \ No newline at end of file diff --git a/tests/unit/teal/abi/lsig_fn_3mixed_args_0ret.teal b/tests/unit/teal/abi/lsig_fn_3mixed_args_0ret.teal new file mode 100644 index 000000000..fca5fd198 --- /dev/null +++ b/tests/unit/teal/abi/lsig_fn_3mixed_args_0ret.teal @@ -0,0 +1,22 @@ +#pragma version 6 +arg 0 +btoi +store 0 +arg 1 +btoi +store 1 // 1 -> btoi(a) +arg 2 +store 2 +load 0 // [a] +int 1 // [a, 1] +load 2 // [a, 1, b] +callsub fn3mixedargs0ret_0 +int 1 +return + +// fn_3mixed_args_0ret +fn3mixedargs0ret_0: +store 5 +store 4 +store 3 +retsub \ No newline at end of file From 8e186be57b5bf46b8d19d320af05c5fc8c36d2b3 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 10 May 2022 16:29:05 -0500 Subject: [PATCH 119/170] comment --- pyteal/ast/abi/util_test.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyteal/ast/abi/util_test.py b/pyteal/ast/abi/util_test.py index cfcf16f51..f6a2fd0af 100644 --- a/pyteal/ast/abi/util_test.py +++ b/pyteal/ast/abi/util_test.py @@ -409,6 +409,8 @@ def test_abi_type_translation(): abi.BoolTypeSpec(), ), ), + # TODO: Turn these tests on when PyTeal supports ufixedx + # cf https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0004.md#types # ( # algosdk.abi.TupleType( # [ From eb4989a5af2a2e3bae51c1678d8ae267cdbdc16f Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 10 May 2022 23:16:37 -0500 Subject: [PATCH 120/170] pass and clean up abi unit tests --- .gitignore | 3 + tests/integration/graviton_abi_test.py | 137 ++++--------------------- tests/unit/blackbox_test.py | 37 +++++-- 3 files changed, 53 insertions(+), 124 deletions(-) diff --git a/.gitignore b/.gitignore index f930cdc2f..7b4ccab8a 100644 --- a/.gitignore +++ b/.gitignore @@ -138,3 +138,6 @@ dmypy.json # mac OS .DS_Store + +# asdf +.tool-versions \ No newline at end of file diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index 7e86bcf15..5e5b336a1 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -1,5 +1,4 @@ import random -from typing import Literal from graviton.blackbox import DryRunExecutor @@ -10,94 +9,8 @@ Blackbox, BlackboxPyTealer, algod_with_assertion, - blackbox_pyteal, ) -# ---- Simple Examples ---- # - - -@pt.ABIReturnSubroutine -def fn_0arg_0ret() -> pt.Expr: - return pt.Return() - - -@pt.ABIReturnSubroutine -def fn_0arg_uint64_ret(*, output: pt.abi.Uint64) -> pt.Expr: - return output.set(1) - - -@pt.ABIReturnSubroutine -def fn_1arg_0ret(a: pt.abi.Uint64) -> pt.Expr: - return pt.Return() - - -@pt.ABIReturnSubroutine -def fn_1arg_1ret(a: pt.abi.Uint64, *, output: pt.abi.Uint64) -> pt.Expr: - return output.set(a) - - -@pt.ABIReturnSubroutine -def fn_2arg_0ret( - a: pt.abi.Uint64, b: pt.abi.StaticArray[pt.abi.Byte, Literal[10]] -) -> pt.Expr: - return pt.Return() - - -@pt.ABIReturnSubroutine -def fn_2arg_1ret( - a: pt.abi.Uint64, - b: pt.abi.StaticArray[pt.abi.Byte, Literal[10]], - *, - output: pt.abi.Byte, -) -> pt.Expr: - return output.set(b[a.get() % pt.Int(10)]) - - -@pt.ABIReturnSubroutine -def fn_2arg_1ret_with_expr( - a: pt.Expr, - b: pt.abi.StaticArray[pt.abi.Byte, Literal[10]], - *, - output: pt.abi.Byte, -) -> pt.Expr: - return output.set(b[a % pt.Int(10)]) - - -# ---- doc test (in our user_guide_test.py as well) - - -def test_abi_sum(): - # TODO: move the pure pyteal generative version of this to user_docs_test.py - @Blackbox(input_types=[None]) - @pt.ABIReturnSubroutine - def abi_sum( - toSum: pt.abi.DynamicArray[pt.abi.Uint64], *, output: pt.abi.Uint64 - ) -> pt.Expr: - i = pt.ScratchVar(pt.TealType.uint64) - valueAtIndex = pt.abi.Uint64() - return pt.Seq( - output.set(0), - pt.For( - i.store(pt.Int(0)), - i.load() < toSum.length(), - i.store(i.load() + pt.Int(1)), - ).Do( - pt.Seq( - toSum[i.load()].store_into(valueAtIndex), - output.set(output.get() + valueAtIndex.get()), - ) - ), - ) - - abi_sum_app_pt = blackbox_pyteal(abi_sum, pt.Mode.Application) - abi_sum_app_tl = pt.compileTeal(abi_sum_app_pt, pt.Mode.Application, version=6) - abi_sum_lsig_pt = blackbox_pyteal(abi_sum, pt.Mode.Signature) - abi_sum_lsig_tl = pt.compileTeal(abi_sum_lsig_pt, pt.Mode.Signature, version=6) - - todo_use_these_guys = abi_sum_app_tl - todo_use_these_guys = abi_sum_lsig_tl - _ = todo_use_these_guys - # ---- Integers and Complex Integral Numbers (aka Gaussian Integers) ---- # @@ -344,28 +257,28 @@ def complex130_norm_squared(x: Complex130, *, output: Int65): def test_integer65(): bbpt_subtract_slick = BlackboxPyTealer(int65_sub, pt.Mode.Application) - approval_subtract_slick = bbpt_subtract_slick.program + approval_subtract_slick = bbpt_subtract_slick.program() teal_subtract_slick = pt.compileTeal( - approval_subtract_slick(), pt.Mode.Application, version=6 + approval_subtract_slick, pt.Mode.Application, version=6 ) bbpt_subtract_cond = BlackboxPyTealer(int65_minus_cond, pt.Mode.Application) - approval_subtract_cond = bbpt_subtract_cond.program + approval_subtract_cond = bbpt_subtract_cond.program() teal_subtract_cond = pt.compileTeal( - approval_subtract_cond(), pt.Mode.Application, version=6 + approval_subtract_cond, pt.Mode.Application, version=6 ) bbpt_mult = BlackboxPyTealer(int65_mult, pt.Mode.Application) - approval_mult = bbpt_mult.program - teal_mult = pt.compileTeal(approval_mult(), pt.Mode.Application, version=6) + approval_mult = bbpt_mult.program() + teal_mult = pt.compileTeal(approval_mult, pt.Mode.Application, version=6) bbpt_negate = BlackboxPyTealer(int65_negate, pt.Mode.Application) - approval_negate = bbpt_negate.program - teal_negate = pt.compileTeal(approval_negate(), pt.Mode.Application, version=6) + approval_negate = bbpt_negate.program() + teal_negate = pt.compileTeal(approval_negate, pt.Mode.Application, version=6) bbpt_add = BlackboxPyTealer(int65_add, pt.Mode.Application) - approval_add = bbpt_add.program - teal_add = pt.compileTeal(approval_add(), pt.Mode.Application, version=6) + approval_add = bbpt_add.program() + teal_add = pt.compileTeal(approval_add, pt.Mode.Application, version=6) # same types, so no need to dupe: unary_abi_argument_types = bbpt_negate.abi_argument_types() @@ -462,41 +375,35 @@ def test_complex130(): # Binary: bbpt_cplx_add = BlackboxPyTealer(complex130_add, pt.Mode.Application) - approval_cplx_add = bbpt_cplx_add.program - teal_cplx_add = pt.compileTeal(approval_cplx_add(), pt.Mode.Application, version=6) + approval_cplx_add = bbpt_cplx_add.program() + teal_cplx_add = pt.compileTeal(approval_cplx_add, pt.Mode.Application, version=6) bbpt_cplx_mult = BlackboxPyTealer(complex130_mult, pt.Mode.Application) - approval_cplx_mult = bbpt_cplx_mult.program - teal_cplx_mult = pt.compileTeal( - approval_cplx_mult(), pt.Mode.Application, version=6 - ) + approval_cplx_mult = bbpt_cplx_mult.program() + teal_cplx_mult = pt.compileTeal(approval_cplx_mult, pt.Mode.Application, version=6) # Unary: bbpt_complex_real = BlackboxPyTealer(complex130_real, pt.Mode.Application) - approval_cplx_real = bbpt_complex_real.program - teal_cplx_real = pt.compileTeal( - approval_cplx_real(), pt.Mode.Application, version=6 - ) + approval_cplx_real = bbpt_complex_real.program() + teal_cplx_real = pt.compileTeal(approval_cplx_real, pt.Mode.Application, version=6) bbpt_complex_imag = BlackboxPyTealer(complex130_imag, pt.Mode.Application) - approval_cplx_imag = bbpt_complex_imag.program - teal_cplx_imag = pt.compileTeal( - approval_cplx_imag(), pt.Mode.Application, version=6 - ) + approval_cplx_imag = bbpt_complex_imag.program() + teal_cplx_imag = pt.compileTeal(approval_cplx_imag, pt.Mode.Application, version=6) bbpt_complex_conjugate = BlackboxPyTealer(complex130_conjugate, pt.Mode.Application) - approval_cplx_conjugate = bbpt_complex_conjugate.program + approval_cplx_conjugate = bbpt_complex_conjugate.program() teal_cplx_conjugate = pt.compileTeal( - approval_cplx_conjugate(), pt.Mode.Application, version=6 + approval_cplx_conjugate, pt.Mode.Application, version=6 ) bbpt_complex_norm_squared = BlackboxPyTealer( complex130_norm_squared, pt.Mode.Application ) - approval_cplx_norm_squared = bbpt_complex_norm_squared.program + approval_cplx_norm_squared = bbpt_complex_norm_squared.program() teal_cplx_norm_squared = pt.compileTeal( - approval_cplx_norm_squared(), pt.Mode.Application, version=6 + approval_cplx_norm_squared, pt.Mode.Application, version=6 ) unary_abi_argument_types = bbpt_complex_real.abi_argument_types() diff --git a/tests/unit/blackbox_test.py b/tests/unit/blackbox_test.py index 36ed58682..d082124f0 100644 --- a/tests/unit/blackbox_test.py +++ b/tests/unit/blackbox_test.py @@ -66,6 +66,18 @@ def utest_any_args(x, y, z): return pt.Seq(x.store(pt.Int(0)), x.load()) +UNITS = [ + utest_noop, + utest_noop_args, + utest_int, + utest_int_args, + utest_bytes, + utest_bytes_args, + utest_any, + utest_any_args, +] + + # ---- ABI Return Subroutine Unit Test Examples ---- # @@ -123,18 +135,22 @@ def fn_2mixed_arg_1ret( return pt.Seq(b.store(a.encode()), output.set(a)) -UNITS = [ - utest_noop, - utest_noop_args, - utest_int, - utest_int_args, - utest_bytes, - utest_bytes_args, - utest_any, - utest_any_args, +CompoundType = pt.abi.Tuple4[ + pt.abi.Uint64, + pt.abi.Tuple2[pt.abi.Byte, pt.abi.StaticArray[pt.abi.Address, 10]], + pt.abi.Tuple0, + pt.abi.Bool, ] +# @Blackbox(input_types=[None]) +# @pt.ABIReturnSubroutine +# def complicated_abi_round_trip( +# x: CompoundType, *, output: pt.abi.Tuple2[CompoundType, CompoundType] +# ): +# return pt.Seq + + ABI_UNITS = [ (fn_0arg_0ret, None), (fn_0arg_uint64_ret, pt.abi.Uint64()), @@ -147,6 +163,9 @@ def fn_2mixed_arg_1ret( ] +# ---- test functions ---- # + + @pytest.mark.parametrize("subr, mode", product(UNITS, pt.Mode)) def test_blackbox_pyteal(subr, mode): is_app = mode == pt.Mode.Application From 072989ec2b4edd19628ec449bfa1a82b693d3492 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Wed, 11 May 2022 16:27:08 -0500 Subject: [PATCH 121/170] closer to the 1 -liner --- tests/integration/graviton_abi_test.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index 5e5b336a1..7f8079fbb 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -128,19 +128,12 @@ def int65_mult(x: Int65, y: Int65, *, output: Int65): """ # TODO: can we get something like the following one-liner working? # return output.set(pt.Not(x[0].get() ^ y[0].get()), x[1].get() * y[1].get()) - x0 = pt.abi.Bool() - x1 = pt.abi.Uint64() - y0 = pt.abi.Bool() - y1 = pt.abi.Uint64() - z0 = pt.abi.Bool() - z1 = pt.abi.Uint64() + def get(x): + return x.use(lambda ni: ni.get()) + return pt.Seq( - x0.set(x[0]), - x1.set(x[1]), - y0.set(y[0]), - y1.set(y[1]), - z0.set(pt.Not(x0.get() ^ y0.get())), - z1.set(x1.get() * y1.get()), + (z0 := pt.abi.Bool()).set(pt.Not(get(x[0]) ^ get(y[0]))), + (z1 := pt.abi.Uint64()).set(get(x[1]) * get(y[1])), output.set(z0, z1), ) From 1b933479fd602a64161c4752c9cf3824fa55a1d0 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Wed, 11 May 2022 16:28:52 -0500 Subject: [PATCH 122/170] Update .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7b4ccab8a..cdb471aae 100644 --- a/.gitignore +++ b/.gitignore @@ -140,4 +140,4 @@ dmypy.json .DS_Store # asdf -.tool-versions \ No newline at end of file +.tool-versions From 5a17d0d1930a84b25b7fda53502393c3d78eeee1 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Wed, 11 May 2022 16:56:10 -0500 Subject: [PATCH 123/170] Update tests/integration/graviton_abi_test.py --- tests/integration/graviton_abi_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index 7f8079fbb..fd947416d 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -279,7 +279,7 @@ def test_integer65(): abi_return_type = bbpt_subtract_slick.abi_return_type() def pynum_to_tuple(n): - return (n > 0, abs(n)) + return (n >= 0, abs(n)) def pytuple_to_num(t): s, x = t From 3e7b3ff6ff099b10692b69e6349fa9b1521fb428 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Wed, 11 May 2022 17:15:07 -0500 Subject: [PATCH 124/170] Update tests/integration/graviton_abi_test.py --- tests/integration/graviton_abi_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index fd947416d..4592b8099 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -406,7 +406,7 @@ def test_complex130(): complex_abi_return_type = bbpt_cplx_add.abi_return_type() def pyint_to_tuple(n): - return (n > 0, abs(n)) + return (n >= 0, abs(n)) def pycomplex_to_tuple(z): return (pyint_to_tuple(int(z.real)), pyint_to_tuple(int(z.imag))) From 3553065473473ad27ad6afbaaab0f375d50e9180 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Wed, 11 May 2022 17:16:03 -0500 Subject: [PATCH 125/170] Update tests/integration/graviton_test.py --- tests/integration/graviton_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/graviton_test.py b/tests/integration/graviton_test.py index 983f0bf27..2ce09f669 100644 --- a/tests/integration/graviton_test.py +++ b/tests/integration/graviton_test.py @@ -550,7 +550,7 @@ def blackbox_test_runner( @pytest.mark.parametrize("subr, scenario", APP_SCENARIOS.items()) -def test_blackbox_Subroutines_as_apps( +def test_blackbox_subroutines_as_apps( subr: pt.SubroutineFnWrapper, scenario: Dict[str, Any], ): From c3b3a586d7de050e3f053e754b80eb440be4452b Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Wed, 11 May 2022 17:16:34 -0500 Subject: [PATCH 126/170] Update tests/integration/graviton_test.py --- tests/integration/graviton_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/graviton_test.py b/tests/integration/graviton_test.py index 2ce09f669..deb67aea7 100644 --- a/tests/integration/graviton_test.py +++ b/tests/integration/graviton_test.py @@ -558,7 +558,7 @@ def test_blackbox_subroutines_as_apps( @pytest.mark.parametrize("subr, scenario", LOGICSIG_SCENARIOS.items()) -def test_blackbox_Subroutines_as_logic_sigs( +def test_blackbox_subroutines_as_logic_sigs( subr: pt.SubroutineFnWrapper, scenario: Dict[str, Any], ): From 70c5a34aa81bd6848f99a29509613f948911b355 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Wed, 11 May 2022 17:30:38 -0500 Subject: [PATCH 127/170] build-and-test is really lint-and-test + separate unit tests from integration tests --- .github/workflows/build.yml | 6 +++--- Makefile | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 593804daa..b08b2190f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,7 @@ jobs: - name: Install python dependencies run: make setup-development - name: Build and Test - run: make build-and-test + run: make lint-and-test run-integration-tests: runs-on: ubuntu-20.04 @@ -63,8 +63,8 @@ jobs: run: make sandbox-dev-up - name: Install python dependencies run: make setup-development - - name: Build, Unit Tests and Integration Tests - run: make all-tests + - name: Integration Tests Only + run: make test-integration - name: Stop running images run: make sandbox-dev-stop diff --git a/Makefile b/Makefile index 07bc340f3..9e6c0a3bd 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,7 @@ test-unit: pytest -n $(NUM_PROCS) --durations=10 -sv pyteal tests/unit --ignore tests/unit/blackbox_test.py --ignore tests/unit/user_guide_test.py pytest -n 1 -sv tests/unit/blackbox_test.py tests/unit/user_guide_test.py -build-and-test: check-generate-init lint test-unit +lint-and-test: check-generate-init lint test-unit # ---- Integration Test (algod required) ---- # @@ -70,7 +70,7 @@ integration-run: test-integration: integration-run -all-tests: build-and-test test-integration +all-tests: lint-and-test test-integration # ---- Local Github Actions Simulation via `act` ---- # # assumes act is installed, e.g. via `brew install act` From 4a104138edafa78f661c1ab7e70762bdbe8d428f Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Wed, 11 May 2022 23:29:42 -0500 Subject: [PATCH 128/170] util.algosdk_from_annotation + begin round-trip tests --- pyteal/ast/abi/__init__.py | 8 +- pyteal/ast/abi/util.py | 6 +- pyteal/ast/abi/util_test.py | 33 +++++++-- tests/integration/abi_roundtrip_test.py | 99 +++++++++++++++++++++++++ 4 files changed, 138 insertions(+), 8 deletions(-) create mode 100644 tests/integration/abi_roundtrip_test.py diff --git a/pyteal/ast/abi/__init__.py b/pyteal/ast/abi/__init__.py index 85c251764..9144bac02 100644 --- a/pyteal/ast/abi/__init__.py +++ b/pyteal/ast/abi/__init__.py @@ -36,7 +36,12 @@ from pyteal.ast.abi.array_dynamic import DynamicArrayTypeSpec, DynamicArray from pyteal.ast.abi.method_return import MethodReturn -from pyteal.ast.abi.util import algosdk_from_type_spec, make, type_spec_from_annotation +from pyteal.ast.abi.util import ( + algosdk_from_annotation, + algosdk_from_type_spec, + make, + type_spec_from_annotation, +) __all__ = [ "String", @@ -81,5 +86,6 @@ "MethodReturn", "type_spec_from_annotation", "make", + "algosdk_from_annotation", "algosdk_from_type_spec", ] diff --git a/pyteal/ast/abi/util.py b/pyteal/ast/abi/util.py index e3f1df01a..e4aac6a9e 100644 --- a/pyteal/ast/abi/util.py +++ b/pyteal/ast/abi/util.py @@ -236,5 +236,9 @@ def make(t: type[T]) -> T: return cast(T, type_spec_from_annotation(t).new_instance()) -def algosdk_from_type_spec(t: type[T]) -> algosdk.abi.ABIType: +def algosdk_from_type_spec(t: T) -> algosdk.abi.ABIType: return algosdk.abi.ABIType.from_string(str(t)) + + +def algosdk_from_annotation(t: type[T]) -> algosdk.abi.ABIType: + return algosdk_from_type_spec(type_spec_from_annotation(t)) diff --git a/pyteal/ast/abi/util_test.py b/pyteal/ast/abi/util_test.py index f6a2fd0af..25ff3d631 100644 --- a/pyteal/ast/abi/util_test.py +++ b/pyteal/ast/abi/util_test.py @@ -320,15 +320,16 @@ def test_make(): def test_abi_type_translation(): test_cases = [ # Test for byte/bool/address/strings - (algosdk.abi.ByteType(), "byte", abi.ByteTypeSpec()), - (algosdk.abi.BoolType(), "bool", abi.BoolTypeSpec()), - (algosdk.abi.AddressType(), "address", abi.AddressTypeSpec()), - (algosdk.abi.StringType(), "string", abi.StringTypeSpec()), + (algosdk.abi.ByteType(), "byte", abi.ByteTypeSpec(), abi.Byte), + (algosdk.abi.BoolType(), "bool", abi.BoolTypeSpec(), abi.Bool), + (algosdk.abi.AddressType(), "address", abi.AddressTypeSpec(), abi.Address), + (algosdk.abi.StringType(), "string", abi.StringTypeSpec(), abi.String), # Test for dynamic array type ( algosdk.abi.ArrayDynamicType(algosdk.abi.UintType(32)), "uint32[]", abi.DynamicArrayTypeSpec(abi.Uint32TypeSpec()), + abi.DynamicArray[abi.Uint32], ), ( algosdk.abi.ArrayDynamicType( @@ -336,6 +337,7 @@ def test_abi_type_translation(): ), "byte[][]", abi.DynamicArrayTypeSpec(abi.DynamicArrayTypeSpec(abi.ByteTypeSpec())), + abi.DynamicArray[abi.DynamicArray[abi.Byte]], ), # TODO: Turn these tests on when PyTeal supports ufixedx # cf https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0004.md#types @@ -360,9 +362,10 @@ def test_abi_type_translation(): abi.StaticArrayTypeSpec(abi.BoolTypeSpec(), 256), 100, ), + abi.StaticArray[abi.StaticArray[abi.Bool, Literal[256]], Literal[100]], ), # Test for tuple - (algosdk.abi.TupleType([]), "()", abi.TupleTypeSpec()), + (algosdk.abi.TupleType([]), "()", abi.TupleTypeSpec(), abi.Tuple0), ( algosdk.abi.TupleType( [ @@ -383,6 +386,13 @@ def test_abi_type_translation(): abi.StaticArrayTypeSpec(abi.AddressTypeSpec(), 10), ), ), + abi.Tuple2[ + abi.Uint16, + abi.Tuple2[ + abi.Byte, + abi.StaticArray[abi.Address, Literal[10]], + ], + ], ), ( algosdk.abi.TupleType( @@ -408,6 +418,15 @@ def test_abi_type_translation(): abi.TupleTypeSpec(), abi.BoolTypeSpec(), ), + abi.Tuple4[ + abi.Uint64, + abi.Tuple2[ + abi.Byte, + abi.StaticArray[abi.Address, Literal[10]], + ], + abi.Tuple, + abi.Bool, + ], ), # TODO: Turn these tests on when PyTeal supports ufixedx # cf https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0004.md#types @@ -458,7 +477,7 @@ def test_abi_type_translation(): # ), ] - for algosdk_abi, abi_string, pyteal_abi_ts in test_cases: + for algosdk_abi, abi_string, pyteal_abi_ts, pyteal_abi in test_cases: print(f"({algosdk_abi}, {abi_string}, {pyteal_abi_ts}),") assert str(algosdk_abi) == abi_string == str(pyteal_abi_ts) assert ( @@ -467,3 +486,5 @@ def test_abi_type_translation(): == algosdk.abi.ABIType.from_string(str(pyteal_abi_ts)) ) assert algosdk_abi == abi.algosdk_from_type_spec(pyteal_abi_ts) + assert pyteal_abi_ts == abi.type_spec_from_annotation(pyteal_abi) + assert algosdk_abi == abi.algosdk_from_annotation(pyteal_abi) diff --git a/tests/integration/abi_roundtrip_test.py b/tests/integration/abi_roundtrip_test.py new file mode 100644 index 000000000..9495d7190 --- /dev/null +++ b/tests/integration/abi_roundtrip_test.py @@ -0,0 +1,99 @@ +import pytest +from typing import Callable, TypeVar + +import algosdk.abi + +from pyteal import abi +import pyteal as pt + +from tests.blackbox import Blackbox, BlackboxPyTealer + +T = TypeVar("T", bound=abi.BaseType) + + +def max_int(bit_size): + return (1 << bit_size) - 1 + + +@pt.ABIReturnSubroutine +def bool_comp(x: abi.Bool, *, output: abi.Bool): + return output.set(pt.Not(x.get())) + + +def numerical_comp_factory(t: type[T], bit_size: int) -> Callable: + @pt.ABIReturnSubroutine + def func(x: t, *, output: t): + max_uint = pt.Int(max_int(bit_size)) + return output.set(max_uint - x.get()) + + return func + + +def tuple_comp_factory(t: type[T], value_type_specs: list[abi.TypeSpec]) -> Callable: + @pt.ABIReturnSubroutine + def tuple_complement(x: t, *, output: t): + value_types = [vts.new_instance() for vts in value_type_specs] + setters = [vts.set(x[i]) for i, vts in enumerate(value_types)] + comp_funcs = [complement_factory(type(vts)) for vts in value_types] + compers = [vts.set(comp_funcs[i](vts)) for i, vts in enumerate(value_types)] + return pt.Seq(*(setters + compers + [output.set(*value_types)])) + + return tuple_complement + + +def complement_factory(t: T) -> Callable: + ts = abi.type_spec_from_annotation(t) + if isinstance(ts, abi.BoolTypeSpec): + return bool_comp + if isinstance(ts, abi.UintTypeSpec): + return numerical_comp_factory(t, ts.bit_size()) + if isinstance(ts, abi.TupleTypeSpec): + return tuple_comp_factory(t, ts.value_type_specs()) + + raise ValueError(f"uh-oh!!! didn't handle type {t}") + + +def roundtrip_factory(t: type[T]) -> Callable: + comp = complement_factory(t) + + @Blackbox(input_types=[None]) + @pt.ABIReturnSubroutine + def round_tripper(x: t, *, output: abi.Tuple2[t, t]): + y = abi.make(t) + z = abi.make(t) + return pt.Seq(y.set(comp(x)), z.set(comp(y)), output.set(y, z)) + + return round_tripper + + +def roundtrip_pytealer(t: type[T]): + roundtrip = roundtrip_factory(t) + return BlackboxPyTealer(roundtrip, pt.Mode.Application) + + +ABI_TYPES = [ + abi.Bool, + abi.Byte, + abi.Uint8, + abi.Uint16, + abi.Uint32, + abi.Uint64, +] + + +@pytest.mark.parametrize("abi_type", ABI_TYPES) +def test_pure_compilation(abi_type): + print(f"Testing {abi_type=}") + sdk_abi_type = abi.algosdk_from_annotation(abi_type) + + roundtripper = roundtrip_pytealer(abi_type) + + teal = roundtripper.program() + + abi_arg_types = roundtripper.abi_argument_types() + assert [sdk_abi_type] == abi_arg_types + + abi_ret_type = roundtripper.abi_return_type() + assert algosdk.abi.TupleType([sdk_abi_type] * 2) == abi_ret_type + + _ = teal From 6e60dd2289a2049f79c4832c96db00530c20c559 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Wed, 11 May 2022 23:33:15 -0500 Subject: [PATCH 129/170] mypy --- pyteal/ast/abi/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/abi/util.py b/pyteal/ast/abi/util.py index e4aac6a9e..594b9ab73 100644 --- a/pyteal/ast/abi/util.py +++ b/pyteal/ast/abi/util.py @@ -236,7 +236,7 @@ def make(t: type[T]) -> T: return cast(T, type_spec_from_annotation(t).new_instance()) -def algosdk_from_type_spec(t: T) -> algosdk.abi.ABIType: +def algosdk_from_type_spec(t: TypeSpec) -> algosdk.abi.ABIType: return algosdk.abi.ABIType.from_string(str(t)) From 055bf67c100bbd734ab5e895e7a92c13902a628b Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 12 May 2022 00:16:03 -0500 Subject: [PATCH 130/170] round trip unit test without arrays --- tests/integration/abi_roundtrip_test.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/integration/abi_roundtrip_test.py b/tests/integration/abi_roundtrip_test.py index 9495d7190..09b1bab6b 100644 --- a/tests/integration/abi_roundtrip_test.py +++ b/tests/integration/abi_roundtrip_test.py @@ -78,6 +78,19 @@ def roundtrip_pytealer(t: type[T]): abi.Uint16, abi.Uint32, abi.Uint64, + abi.Tuple0, + abi.Tuple1[abi.Bool], + abi.Tuple1[abi.Byte], + abi.Tuple1[abi.Uint8], + abi.Tuple1[abi.Uint16], + abi.Tuple1[abi.Uint32], + abi.Tuple1[abi.Uint64], + abi.Tuple3[abi.Bool, abi.Uint64, abi.Uint32], + abi.Tuple3[abi.Byte, abi.Bool, abi.Uint64], + abi.Tuple3[abi.Uint8, abi.Byte, abi.Bool], + abi.Tuple3[abi.Uint16, abi.Uint8, abi.Byte], + abi.Tuple3[abi.Uint32, abi.Uint16, abi.Uint8], + abi.Tuple3[abi.Uint64, abi.Uint32, abi.Uint16], ] From afdd2cccd347b78b8e42103ca1af91dc1216a4d9 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 12 May 2022 12:23:03 -0500 Subject: [PATCH 131/170] round trip is broken b/c not handling arrays properly --- tests/integration/abi_roundtrip_test.py | 26 ++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/tests/integration/abi_roundtrip_test.py b/tests/integration/abi_roundtrip_test.py index 09b1bab6b..2d64497ce 100644 --- a/tests/integration/abi_roundtrip_test.py +++ b/tests/integration/abi_roundtrip_test.py @@ -1,5 +1,5 @@ import pytest -from typing import Callable, TypeVar +from typing import Callable, Literal, TypeVar import algosdk.abi @@ -41,6 +41,27 @@ def tuple_complement(x: t, *, output: t): return tuple_complement +def array_comp_factory(t: type[T], value_type_spec: abi.TypeSpec) -> Callable: + comp_func = complement_factory(type(value_type_spec.new_instance())) + + @pt.ABIReturnSubroutine + def array_complement(arr: t, *, output: t): + i = pt.ScratchVar(pt.TealType.uint64) + val = value_type_spec.new_instance() + return pt.For( + i.store(pt.Int(0)), + i.load() < arr.length(), + i.store(i.load() + pt.Int(1)), + ).Do( + pt.Seq( + arr[i.load()].store_into(val), + output[i.load()].store_into(comp_func(val)), + ) + ) + + return array_complement + + def complement_factory(t: T) -> Callable: ts = abi.type_spec_from_annotation(t) if isinstance(ts, abi.BoolTypeSpec): @@ -49,6 +70,8 @@ def complement_factory(t: T) -> Callable: return numerical_comp_factory(t, ts.bit_size()) if isinstance(ts, abi.TupleTypeSpec): return tuple_comp_factory(t, ts.value_type_specs()) + if isinstance(ts, abi.ArrayTypeSpec): + return array_comp_factory(t, ts.value_type_spec()) raise ValueError(f"uh-oh!!! didn't handle type {t}") @@ -91,6 +114,7 @@ def roundtrip_pytealer(t: type[T]): abi.Tuple3[abi.Uint16, abi.Uint8, abi.Byte], abi.Tuple3[abi.Uint32, abi.Uint16, abi.Uint8], abi.Tuple3[abi.Uint64, abi.Uint32, abi.Uint16], + abi.StaticArray[abi.Bool, Literal[1337]], ] From f9d341f96ff1de58b79c7d25d26702f5df6712c0 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 12 May 2022 12:27:37 -0500 Subject: [PATCH 132/170] comments --- tests/blackbox.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/blackbox.py b/tests/blackbox.py index 34c987170..687c4b4a1 100644 --- a/tests/blackbox.py +++ b/tests/blackbox.py @@ -200,6 +200,7 @@ def abi_return_type(self) -> None | algosdk.abi.ABIType: def program(self) -> Expr: """Functor producing ready-to-compile PyTeal programs from annotated subroutines + TODO: add explanation for ABI var's/output as well Returns: a function that called with no parameters -e.g. result()- @@ -246,7 +247,7 @@ def program(self) -> Expr: log Itob(Int(1337)) as it is either impossible (TealType.none), or unknown at compile time (TealType.anytype) how to convert to Bytes - For illustrative examples of how to use this function please refer to the integration test file `graviton_test.py` and especially: + For illustrative examples of how to use this method please refer to the integration test file `graviton_test.py` and especially: * `blackbox_pyteal_example1()`: Using blackbox_pyteal() for a simple test of both an app and logic sig * `blackbox_pyteal_example2()`: Using blackbox_pyteal() to make 400 assertions and generate a CSV report with 400 dryrun rows From 914fe7e2b4e5491d92fc624c4451508094098168 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 12 May 2022 13:28:25 -0500 Subject: [PATCH 133/170] git revert'able commit - experimental map()-like functionality git revert'able commit - experimental map()-like functionality --- pyteal/ast/abi/array_base.py | 5 +++-- pyteal/ast/abi/bool.py | 6 ++++-- pyteal/ast/abi/tuple.py | 17 +++++++++++------ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/pyteal/ast/abi/array_base.py b/pyteal/ast/abi/array_base.py index 3ed714574..c5e9995d9 100644 --- a/pyteal/ast/abi/array_base.py +++ b/pyteal/ast/abi/array_base.py @@ -1,4 +1,5 @@ from typing import ( + Callable, Union, Sequence, TypeVar, @@ -106,7 +107,7 @@ def decode( ) return self.stored_value.store(extracted) - def set(self, values: Sequence[T]) -> Expr: + def set(self, values: Sequence[T], abi_subr: Callable[..., Expr] = None) -> Expr: """Set the ABI array with a sequence of ABI type variables. The function first type-check the argument `values` to make sure the sequence of ABI type @@ -135,7 +136,7 @@ def set(self, values: Sequence[T]) -> Expr: ) ) - encoded = encodeTuple(values) + encoded = encodeTuple(values, abi_subr=abi_subr) if self.type_spec().is_length_dynamic(): length_tmp = Uint16() diff --git a/pyteal/ast/abi/bool.py b/pyteal/ast/abi/bool.py index f1b8a6065..81a2644a3 100644 --- a/pyteal/ast/abi/bool.py +++ b/pyteal/ast/abi/bool.py @@ -141,7 +141,9 @@ def boolSequenceLength(num_bools: int) -> int: return (num_bools + NUM_BITS_IN_BYTE - 1) // NUM_BITS_IN_BYTE -def encodeBoolSequence(values: Sequence[Bool]) -> Expr: +def encodeBoolSequence( + values: Sequence[Bool], abi_subr: Callable[..., Expr] = None +) -> Expr: """Encoding a sequences of boolean values into a byte string. Args: @@ -154,6 +156,6 @@ def encodeBoolSequence(values: Sequence[Bool]) -> Expr: expr: Expr = Bytes(b"\x00" * length) for i, value in enumerate(values): - expr = SetBit(expr, Int(i), value.get()) + expr = SetBit(expr, Int(i), abi_subr(value) if abi_subr else value.get()) return expr diff --git a/pyteal/ast/abi/tuple.py b/pyteal/ast/abi/tuple.py index 3824e701e..e28bf3b5c 100644 --- a/pyteal/ast/abi/tuple.py +++ b/pyteal/ast/abi/tuple.py @@ -1,4 +1,5 @@ from typing import ( + Callable, List, Sequence, Dict, @@ -33,7 +34,9 @@ from pyteal.ast.abi.util import substringForDecoding -def encodeTuple(values: Sequence[BaseType]) -> Expr: +def encodeTuple( + values: Sequence[BaseType], abi_subr: Callable[..., Expr] = None +) -> Expr: heads: List[Expr] = [] head_length_static: int = 0 @@ -51,7 +54,9 @@ def encodeTuple(values: Sequence[BaseType]) -> Expr: ignoreNext = numBools - 1 head_length_static += boolSequenceLength(numBools) heads.append( - encodeBoolSequence(cast(Sequence[Bool], values[i : i + numBools])) + encodeBoolSequence( + cast(Sequence[Bool], values[i : i + numBools]), abi_subr=abi_subr + ) ) continue @@ -62,7 +67,7 @@ def encodeTuple(values: Sequence[BaseType]) -> Expr: continue head_length_static += elemType.byte_length_static() - heads.append(elem.encode()) + heads.append((abi_subr(elem) if abi_subr else elem).encode()) tail_offset = Uint16() tail_offset_accumulator = Uint16() @@ -95,7 +100,7 @@ def encodeTuple(values: Sequence[BaseType]) -> Expr: updateAccumulator = Seq() heads[dynamicValueIndexToHeadIndex[i]] = Seq( - encoded_tail.store(elem.encode()), + encoded_tail.store((abi_subr(elem) if abi_subr else elem).encode()), updateVars, updateAccumulator, tail_offset.encode(), @@ -275,7 +280,7 @@ def set(self, *values: BaseType) -> Expr: def set(self: T, value: ComputedValue[T]) -> Expr: ... - def set(self, *values): + def set(self, *values, abi_subr: Callable[..., Expr] = None): if len(values) == 1 and isinstance(values[0], ComputedValue): return self._set_with_computed_type(values[0]) @@ -290,7 +295,7 @@ def set(self, *values): ) if not all(myTypes[i] == values[i].type_spec() for i in range(len(myTypes))): raise TealInputError("Input values do not match type") - return self.stored_value.store(encodeTuple(values)) + return self.stored_value.store(encodeTuple(values, abi_subr=abi_subr)) def encode(self) -> Expr: return self.stored_value.load() From b8242376d1dbea5dc20766b4b9e11c86782a35e7 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 12 May 2022 14:02:33 -0500 Subject: [PATCH 134/170] Revert "git revert'able commit - experimental map()-like functionality" This reverts commit 914fe7e2b4e5491d92fc624c4451508094098168. --- pyteal/ast/abi/array_base.py | 5 ++--- pyteal/ast/abi/bool.py | 6 ++---- pyteal/ast/abi/tuple.py | 17 ++++++----------- 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/pyteal/ast/abi/array_base.py b/pyteal/ast/abi/array_base.py index c5e9995d9..3ed714574 100644 --- a/pyteal/ast/abi/array_base.py +++ b/pyteal/ast/abi/array_base.py @@ -1,5 +1,4 @@ from typing import ( - Callable, Union, Sequence, TypeVar, @@ -107,7 +106,7 @@ def decode( ) return self.stored_value.store(extracted) - def set(self, values: Sequence[T], abi_subr: Callable[..., Expr] = None) -> Expr: + def set(self, values: Sequence[T]) -> Expr: """Set the ABI array with a sequence of ABI type variables. The function first type-check the argument `values` to make sure the sequence of ABI type @@ -136,7 +135,7 @@ def set(self, values: Sequence[T], abi_subr: Callable[..., Expr] = None) -> Expr ) ) - encoded = encodeTuple(values, abi_subr=abi_subr) + encoded = encodeTuple(values) if self.type_spec().is_length_dynamic(): length_tmp = Uint16() diff --git a/pyteal/ast/abi/bool.py b/pyteal/ast/abi/bool.py index 81a2644a3..f1b8a6065 100644 --- a/pyteal/ast/abi/bool.py +++ b/pyteal/ast/abi/bool.py @@ -141,9 +141,7 @@ def boolSequenceLength(num_bools: int) -> int: return (num_bools + NUM_BITS_IN_BYTE - 1) // NUM_BITS_IN_BYTE -def encodeBoolSequence( - values: Sequence[Bool], abi_subr: Callable[..., Expr] = None -) -> Expr: +def encodeBoolSequence(values: Sequence[Bool]) -> Expr: """Encoding a sequences of boolean values into a byte string. Args: @@ -156,6 +154,6 @@ def encodeBoolSequence( expr: Expr = Bytes(b"\x00" * length) for i, value in enumerate(values): - expr = SetBit(expr, Int(i), abi_subr(value) if abi_subr else value.get()) + expr = SetBit(expr, Int(i), value.get()) return expr diff --git a/pyteal/ast/abi/tuple.py b/pyteal/ast/abi/tuple.py index e28bf3b5c..3824e701e 100644 --- a/pyteal/ast/abi/tuple.py +++ b/pyteal/ast/abi/tuple.py @@ -1,5 +1,4 @@ from typing import ( - Callable, List, Sequence, Dict, @@ -34,9 +33,7 @@ from pyteal.ast.abi.util import substringForDecoding -def encodeTuple( - values: Sequence[BaseType], abi_subr: Callable[..., Expr] = None -) -> Expr: +def encodeTuple(values: Sequence[BaseType]) -> Expr: heads: List[Expr] = [] head_length_static: int = 0 @@ -54,9 +51,7 @@ def encodeTuple( ignoreNext = numBools - 1 head_length_static += boolSequenceLength(numBools) heads.append( - encodeBoolSequence( - cast(Sequence[Bool], values[i : i + numBools]), abi_subr=abi_subr - ) + encodeBoolSequence(cast(Sequence[Bool], values[i : i + numBools])) ) continue @@ -67,7 +62,7 @@ def encodeTuple( continue head_length_static += elemType.byte_length_static() - heads.append((abi_subr(elem) if abi_subr else elem).encode()) + heads.append(elem.encode()) tail_offset = Uint16() tail_offset_accumulator = Uint16() @@ -100,7 +95,7 @@ def encodeTuple( updateAccumulator = Seq() heads[dynamicValueIndexToHeadIndex[i]] = Seq( - encoded_tail.store((abi_subr(elem) if abi_subr else elem).encode()), + encoded_tail.store(elem.encode()), updateVars, updateAccumulator, tail_offset.encode(), @@ -280,7 +275,7 @@ def set(self, *values: BaseType) -> Expr: def set(self: T, value: ComputedValue[T]) -> Expr: ... - def set(self, *values, abi_subr: Callable[..., Expr] = None): + def set(self, *values): if len(values) == 1 and isinstance(values[0], ComputedValue): return self._set_with_computed_type(values[0]) @@ -295,7 +290,7 @@ def set(self, *values, abi_subr: Callable[..., Expr] = None): ) if not all(myTypes[i] == values[i].type_spec() for i in range(len(myTypes))): raise TealInputError("Input values do not match type") - return self.stored_value.store(encodeTuple(values, abi_subr=abi_subr)) + return self.stored_value.store(encodeTuple(values)) def encode(self) -> Expr: return self.stored_value.load() From 95782a139d35e0f00fd8e8a140bd924e5470c40c Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 12 May 2022 16:17:30 -0500 Subject: [PATCH 135/170] wip --- tests/integration/abi_roundtrip_test.py | 38 +++++++++++++++++-------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/tests/integration/abi_roundtrip_test.py b/tests/integration/abi_roundtrip_test.py index 2d64497ce..cd35dd34e 100644 --- a/tests/integration/abi_roundtrip_test.py +++ b/tests/integration/abi_roundtrip_test.py @@ -34,15 +34,21 @@ def tuple_comp_factory(t: type[T], value_type_specs: list[abi.TypeSpec]) -> Call def tuple_complement(x: t, *, output: t): value_types = [vts.new_instance() for vts in value_type_specs] setters = [vts.set(x[i]) for i, vts in enumerate(value_types)] - comp_funcs = [complement_factory(type(vts)) for vts in value_types] + comp_funcs = [complement_factory(type(vts), -1) for vts in value_types] compers = [vts.set(comp_funcs[i](vts)) for i, vts in enumerate(value_types)] return pt.Seq(*(setters + compers + [output.set(*value_types)])) return tuple_complement -def array_comp_factory(t: type[T], value_type_spec: abi.TypeSpec) -> Callable: - comp_func = complement_factory(type(value_type_spec.new_instance())) +def array_comp_factory( + t: type[T], value_type_spec: abi.TypeSpec, length: int +) -> Callable: + comp_func = complement_factory(type(value_type_spec.new_instance()), -1) + if length != -1: + assert t.type_spec().is_length_dynamic() + else: + length = t.length_static() @pt.ABIReturnSubroutine def array_complement(arr: t, *, output: t): @@ -55,14 +61,14 @@ def array_complement(arr: t, *, output: t): ).Do( pt.Seq( arr[i.load()].store_into(val), - output[i.load()].store_into(comp_func(val)), + output.set()[i.load()].store_into(comp_func(val)), ) ) return array_complement -def complement_factory(t: T) -> Callable: +def complement_factory(t: T, dynamic_length: int) -> Callable: ts = abi.type_spec_from_annotation(t) if isinstance(ts, abi.BoolTypeSpec): return bool_comp @@ -71,13 +77,13 @@ def complement_factory(t: T) -> Callable: if isinstance(ts, abi.TupleTypeSpec): return tuple_comp_factory(t, ts.value_type_specs()) if isinstance(ts, abi.ArrayTypeSpec): - return array_comp_factory(t, ts.value_type_spec()) + return array_comp_factory(t, ts.value_type_spec(), dynamic_length) raise ValueError(f"uh-oh!!! didn't handle type {t}") -def roundtrip_factory(t: type[T]) -> Callable: - comp = complement_factory(t) +def roundtrip_factory(t: type[T], dynamic_length: int) -> Callable: + comp = complement_factory(t, dynamic_length) @Blackbox(input_types=[None]) @pt.ABIReturnSubroutine @@ -89,8 +95,8 @@ def round_tripper(x: t, *, output: abi.Tuple2[t, t]): return round_tripper -def roundtrip_pytealer(t: type[T]): - roundtrip = roundtrip_factory(t) +def roundtrip_pytealer(t: type[T], dynamic_length: int): + roundtrip = roundtrip_factory(t, dynamic_length) return BlackboxPyTealer(roundtrip, pt.Mode.Application) @@ -114,16 +120,24 @@ def roundtrip_pytealer(t: type[T]): abi.Tuple3[abi.Uint16, abi.Uint8, abi.Byte], abi.Tuple3[abi.Uint32, abi.Uint16, abi.Uint8], abi.Tuple3[abi.Uint64, abi.Uint32, abi.Uint16], - abi.StaticArray[abi.Bool, Literal[1337]], + abi.StaticArray[abi.Bool, Literal[0]], + abi.StaticArray[abi.Bool, Literal[42]], + (abi.DynamicArray[abi.Bool], 0), + (abi.DynamicArray[abi.Bool], 42), ] +@pytest.mark.skip("not ready") @pytest.mark.parametrize("abi_type", ABI_TYPES) def test_pure_compilation(abi_type): print(f"Testing {abi_type=}") + dynamic_length = -1 + if isinstance(abi_type, tuple): + abi_type, dynamic_length = abi_type + sdk_abi_type = abi.algosdk_from_annotation(abi_type) - roundtripper = roundtrip_pytealer(abi_type) + roundtripper = roundtrip_pytealer(abi_type, dynamic_length) teal = roundtripper.program() From 47db977f1bc660e2e9c63c21466cb8db8695b2a7 Mon Sep 17 00:00:00 2001 From: Michael Diamant Date: Thu, 12 May 2022 17:33:26 -0400 Subject: [PATCH 136/170] Expand BlackboxPyTealer in an attempt to simplify dryrun request generation (#335) PyTealDryRunExecutor Co-authored-by: Zeph Grunschlag --- tests/blackbox.py | 45 ++++++- tests/integration/abi_roundtrip_test.py | 8 +- tests/integration/graviton_abi_test.py | 156 ++++++------------------ tests/integration/graviton_test.py | 35 ++---- tests/unit/blackbox_test.py | 4 +- 5 files changed, 90 insertions(+), 158 deletions(-) diff --git a/tests/blackbox.py b/tests/blackbox.py index 687c4b4a1..6f93ee03f 100644 --- a/tests/blackbox.py +++ b/tests/blackbox.py @@ -1,15 +1,18 @@ -from typing import Callable +from typing import Callable, Sequence import algosdk.abi from algosdk.v2client import algod from graviton import blackbox +from graviton.blackbox import DryRunInspector, DryRunExecutor +from graviton.dryrun import ZERO_ADDRESS from pyteal import ( abi, Arg, Btoi, Bytes, + compileTeal, Expr, Int, Itob, @@ -140,12 +143,12 @@ def mode_to_execution_mode(mode: Mode) -> blackbox.ExecutionMode: def blackbox_pyteal(subr: BlackboxWrapper, mode: Mode) -> Expr: """ - cf. BlackboxPyTealer.program() for futher details + cf. PyTealDryRunExecutor.program() for futher details """ - return BlackboxPyTealer(subr, mode).program() + return PyTealDryRunExecutor(subr, mode).program() -class BlackboxPyTealer: +class PyTealDryRunExecutor: def __init__(self, subr: BlackboxWrapper, mode: Mode): """ Args: @@ -252,7 +255,7 @@ def program(self) -> Expr: * `blackbox_pyteal_example1()`: Using blackbox_pyteal() for a simple test of both an app and logic sig * `blackbox_pyteal_example2()`: Using blackbox_pyteal() to make 400 assertions and generate a CSV report with 400 dryrun rows * `blackbox_pyteal_example3()`: declarative Test Driven Development approach through Invariant's - * `blackbox_pyteal_example4()`: Using BlackboxPyTealer.program() to debug an ABIReturnSubroutine with an app, logic sig and csv report + * `blackbox_pyteal_example4()`: Using PyTealDryRunExecutor to debug an ABIReturnSubroutine with an app, logic sig and csv report """ return self._pyteal_lambda() @@ -374,3 +377,35 @@ def approval(): return Seq(*(preps + results)) return approval + + def dryrun_on_sequence( + self, + inputs: list[Sequence[str | int]], + compiler_version=6, + sender: str = ZERO_ADDRESS, + ) -> list[DryRunInspector]: + match self.mode: + case Mode.Application: + return DryRunExecutor.dryrun_app_on_sequence( + algod_with_assertion(), + compileTeal( + self.program(), Mode.Application, version=compiler_version + ), + inputs, + self.abi_argument_types(), + self.abi_return_type(), + sender, + ) + case Mode.Signature: + return DryRunExecutor.dryrun_logicsig_on_sequence( + algod_with_assertion(), + compileTeal( + self.program(), Mode.Signature, version=compiler_version + ), + inputs, + self.abi_argument_types(), + self.abi_return_type(), + sender, + ) + case _: + raise Exception(f"Unknown mode {self.mode} of type {type(self.mode)}") diff --git a/tests/integration/abi_roundtrip_test.py b/tests/integration/abi_roundtrip_test.py index cd35dd34e..044659470 100644 --- a/tests/integration/abi_roundtrip_test.py +++ b/tests/integration/abi_roundtrip_test.py @@ -6,7 +6,7 @@ from pyteal import abi import pyteal as pt -from tests.blackbox import Blackbox, BlackboxPyTealer +from tests.blackbox import Blackbox, PyTealDryRunExecutor T = TypeVar("T", bound=abi.BaseType) @@ -95,9 +95,9 @@ def round_tripper(x: t, *, output: abi.Tuple2[t, t]): return round_tripper -def roundtrip_pytealer(t: type[T], dynamic_length: int): - roundtrip = roundtrip_factory(t, dynamic_length) - return BlackboxPyTealer(roundtrip, pt.Mode.Application) +def roundtrip_pytealer(t: type[T]): + roundtrip = roundtrip_factory(t) + return PyTealDryRunExecutor(roundtrip, pt.Mode.Application) ABI_TYPES = [ diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index 4592b8099..1ec10432b 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -1,14 +1,13 @@ import random -from graviton.blackbox import DryRunExecutor +from graviton.blackbox import DryRunInspector import pyteal as pt from pyteal.ast.subroutine import ABIReturnSubroutine from tests.blackbox import ( Blackbox, - BlackboxPyTealer, - algod_with_assertion, + PyTealDryRunExecutor, ) @@ -249,34 +248,15 @@ def complex130_norm_squared(x: Complex130, *, output: Int65): def test_integer65(): - bbpt_subtract_slick = BlackboxPyTealer(int65_sub, pt.Mode.Application) - approval_subtract_slick = bbpt_subtract_slick.program() - teal_subtract_slick = pt.compileTeal( - approval_subtract_slick, pt.Mode.Application, version=6 - ) - - bbpt_subtract_cond = BlackboxPyTealer(int65_minus_cond, pt.Mode.Application) - approval_subtract_cond = bbpt_subtract_cond.program() - teal_subtract_cond = pt.compileTeal( - approval_subtract_cond, pt.Mode.Application, version=6 - ) + bbpt_subtract_slick = PyTealDryRunExecutor(int65_sub, pt.Mode.Application) - bbpt_mult = BlackboxPyTealer(int65_mult, pt.Mode.Application) - approval_mult = bbpt_mult.program() - teal_mult = pt.compileTeal(approval_mult, pt.Mode.Application, version=6) + bbpt_subtract_cond = PyTealDryRunExecutor(int65_minus_cond, pt.Mode.Application) - bbpt_negate = BlackboxPyTealer(int65_negate, pt.Mode.Application) - approval_negate = bbpt_negate.program() - teal_negate = pt.compileTeal(approval_negate, pt.Mode.Application, version=6) + bbpt_mult = PyTealDryRunExecutor(int65_mult, pt.Mode.Application) - bbpt_add = BlackboxPyTealer(int65_add, pt.Mode.Application) - approval_add = bbpt_add.program() - teal_add = pt.compileTeal(approval_add, pt.Mode.Application, version=6) + bbpt_negate = PyTealDryRunExecutor(int65_negate, pt.Mode.Application) - # same types, so no need to dupe: - unary_abi_argument_types = bbpt_negate.abi_argument_types() - binary_abi_argument_types = bbpt_subtract_slick.abi_argument_types() - abi_return_type = bbpt_subtract_slick.abi_return_type() + bbpt_add = PyTealDryRunExecutor(int65_add, pt.Mode.Application) def pynum_to_tuple(n): return (n >= 0, abs(n)) @@ -296,34 +276,20 @@ def pytuple_to_num(t): for x, y in zip(random.sample(choices, N), random.sample(choices, N)) ] - algod = algod_with_assertion() + def binary_dryrun(p: PyTealDryRunExecutor) -> list[DryRunInspector]: + return p.dryrun_on_sequence(binary_inputs) # Binary: - inspectors_subtract_slick = DryRunExecutor.dryrun_app_on_sequence( - algod, - teal_subtract_slick, - binary_inputs, - binary_abi_argument_types, - abi_return_type, - ) - inspectors_subtract_cond = DryRunExecutor.dryrun_app_on_sequence( - algod, - teal_subtract_cond, - binary_inputs, - binary_abi_argument_types, - abi_return_type, - ) - inspectors_mult = DryRunExecutor.dryrun_app_on_sequence( - algod, teal_mult, binary_inputs, binary_abi_argument_types, abi_return_type - ) - inspectors_add = DryRunExecutor.dryrun_app_on_sequence( - algod, teal_add, binary_inputs, binary_abi_argument_types, abi_return_type - ) + inspectors_subtract_slick = binary_dryrun(bbpt_subtract_slick) + + inspectors_subtract_cond = binary_dryrun(bbpt_subtract_cond) + + inspectors_mult = binary_dryrun(bbpt_mult) + + inspectors_add = binary_dryrun(bbpt_add) # Unary: - inspectors_negate = DryRunExecutor.dryrun_app_on_sequence( - algod, teal_negate, unary_inputs, unary_abi_argument_types, abi_return_type - ) + inspectors_negate = bbpt_negate.dryrun_on_sequence(unary_inputs) for i in range(N): binary_args = binary_inputs[i] @@ -367,43 +333,23 @@ def pytuple_to_num(t): def test_complex130(): # Binary: - bbpt_cplx_add = BlackboxPyTealer(complex130_add, pt.Mode.Application) - approval_cplx_add = bbpt_cplx_add.program() - teal_cplx_add = pt.compileTeal(approval_cplx_add, pt.Mode.Application, version=6) + bbpt_cplx_add = PyTealDryRunExecutor(complex130_add, pt.Mode.Application) - bbpt_cplx_mult = BlackboxPyTealer(complex130_mult, pt.Mode.Application) - approval_cplx_mult = bbpt_cplx_mult.program() - teal_cplx_mult = pt.compileTeal(approval_cplx_mult, pt.Mode.Application, version=6) + bbpt_cplx_mult = PyTealDryRunExecutor(complex130_mult, pt.Mode.Application) # Unary: - bbpt_complex_real = BlackboxPyTealer(complex130_real, pt.Mode.Application) - approval_cplx_real = bbpt_complex_real.program() - teal_cplx_real = pt.compileTeal(approval_cplx_real, pt.Mode.Application, version=6) + bbpt_complex_real = PyTealDryRunExecutor(complex130_real, pt.Mode.Application) - bbpt_complex_imag = BlackboxPyTealer(complex130_imag, pt.Mode.Application) - approval_cplx_imag = bbpt_complex_imag.program() - teal_cplx_imag = pt.compileTeal(approval_cplx_imag, pt.Mode.Application, version=6) + bbpt_complex_imag = PyTealDryRunExecutor(complex130_imag, pt.Mode.Application) - bbpt_complex_conjugate = BlackboxPyTealer(complex130_conjugate, pt.Mode.Application) - approval_cplx_conjugate = bbpt_complex_conjugate.program() - teal_cplx_conjugate = pt.compileTeal( - approval_cplx_conjugate, pt.Mode.Application, version=6 + bbpt_complex_conjugate = PyTealDryRunExecutor( + complex130_conjugate, pt.Mode.Application ) - bbpt_complex_norm_squared = BlackboxPyTealer( + bbpt_complex_norm_squared = PyTealDryRunExecutor( complex130_norm_squared, pt.Mode.Application ) - approval_cplx_norm_squared = bbpt_complex_norm_squared.program() - teal_cplx_norm_squared = pt.compileTeal( - approval_cplx_norm_squared, pt.Mode.Application, version=6 - ) - - unary_abi_argument_types = bbpt_complex_real.abi_argument_types() - binary_abi_argument_types = bbpt_cplx_add.abi_argument_types() - - real_abi_return_type = bbpt_complex_real.abi_return_type() - complex_abi_return_type = bbpt_cplx_add.abi_return_type() def pyint_to_tuple(n): return (n >= 0, abs(n)) @@ -439,57 +385,25 @@ def pytuple_to_complex(tt): ) ] - algod = algod_with_assertion() + def binary_dryrun(p: PyTealDryRunExecutor) -> list[DryRunInspector]: + return p.dryrun_on_sequence(binary_inputs) # Binary: - inspectors_cplx_add = DryRunExecutor.dryrun_app_on_sequence( - algod, - teal_cplx_add, - binary_inputs, - binary_abi_argument_types, - complex_abi_return_type, - ) + inspectors_cplx_add = binary_dryrun(bbpt_cplx_add) - inspectors_cplx_mult = DryRunExecutor.dryrun_app_on_sequence( - algod, - teal_cplx_mult, - binary_inputs, - binary_abi_argument_types, - complex_abi_return_type, - ) + inspectors_cplx_mult = binary_dryrun(bbpt_cplx_mult) + + def unary_dryrun(p: PyTealDryRunExecutor) -> list[DryRunInspector]: + return p.dryrun_on_sequence(unary_inputs) # Unary: - inspectors_cplx_real = DryRunExecutor.dryrun_app_on_sequence( - algod, - teal_cplx_real, - unary_inputs, - unary_abi_argument_types, - real_abi_return_type, - ) + inspectors_cplx_real = unary_dryrun(bbpt_complex_real) - inspectors_cplx_imag = DryRunExecutor.dryrun_app_on_sequence( - algod, - teal_cplx_imag, - unary_inputs, - unary_abi_argument_types, - real_abi_return_type, - ) + inspectors_cplx_imag = unary_dryrun(bbpt_complex_imag) - inspectors_cplx_conjugate = DryRunExecutor.dryrun_app_on_sequence( - algod, - teal_cplx_conjugate, - unary_inputs, - unary_abi_argument_types, - complex_abi_return_type, - ) + inspectors_cplx_conjugate = unary_dryrun(bbpt_complex_conjugate) - inspectors_cplx_norm_squared = DryRunExecutor.dryrun_app_on_sequence( - algod, - teal_cplx_norm_squared, - unary_inputs, - unary_abi_argument_types, - real_abi_return_type, - ) + inspectors_cplx_norm_squared = unary_dryrun(bbpt_complex_norm_squared) for i in range(N): binary_args = binary_inputs[i] diff --git a/tests/integration/graviton_test.py b/tests/integration/graviton_test.py index deb67aea7..3e905afe1 100644 --- a/tests/integration/graviton_test.py +++ b/tests/integration/graviton_test.py @@ -758,15 +758,14 @@ def euclid(x, y): def blackbox_pyteal_example4(): - # Example 4: Using BlackboxPyTealer.program() to debug an ABIReturnSubroutine with an app, logic sig and csv report + # Example 4: Using PyTealDryRunExecutor to debug an ABIReturnSubroutine with an app, logic sig and csv report from pathlib import Path import random - from graviton.blackbox import DryRunExecutor, DryRunInspector + from graviton.blackbox import DryRunInspector from pyteal import ( abi, - compileTeal, ABIReturnSubroutine, Expr, For, @@ -777,7 +776,7 @@ def blackbox_pyteal_example4(): TealType, ) - from tests.blackbox import Blackbox, BlackboxPyTealer, algod_with_assertion + from tests.blackbox import Blackbox, PyTealDryRunExecutor # Sum a dynamic uint64 array @Blackbox(input_types=[None]) @@ -799,21 +798,10 @@ def abi_sum(toSum: abi.DynamicArray[abi.Uint64], *, output: abi.Uint64) -> Expr: ), ) - # instantiate BlackboxPyTealer objects for the app and lsig: - app_pytealer = BlackboxPyTealer(abi_sum, Mode.Application) - lsig_pytealer = BlackboxPyTealer(abi_sum, Mode.Signature) - - # create approval PyTeal app and lsig: - pyteal_abi_sum_app = app_pytealer.program() - pyteal_abi_sum_lsig = lsig_pytealer.program() + # instantiate PyTealDryRunExecutor objects for the app and lsig: + app_pytealer = PyTealDryRunExecutor(abi_sum, Mode.Application) + lsig_pytealer = PyTealDryRunExecutor(abi_sum, Mode.Signature) - # compile the PyTeal's to Teal's: - teal_abi_sum_app = compileTeal(pyteal_abi_sum_app, Mode.Application, version=6) - teal_abi_sum_lsig = compileTeal(pyteal_abi_sum_lsig, Mode.Signature, version=6) - - # infer the abi types for encoding/decoding dry runs: - abi_argument_types = app_pytealer.abi_argument_types() - abi_return_type = app_pytealer.abi_return_type() # generate reports with the same random inputs (fix the randomness with a seed): random.seed(42) @@ -823,15 +811,10 @@ def abi_sum(toSum: abi.DynamicArray[abi.Uint64], *, output: abi.Uint64) -> Expr: for n in range(N): inputs.append(tuple([random.sample(choices, n)])) - # execute the dry-run sequence: - algod = algod_with_assertion() + app_inspectors = app_pytealer.dryrun_on_sequence(inputs) + + lsig_inspectors = lsig_pytealer.dryrun_on_sequence(inputs) - app_inspectors = DryRunExecutor.dryrun_app_on_sequence( - algod, teal_abi_sum_app, inputs, abi_argument_types, abi_return_type - ) - lsig_inspectors = DryRunExecutor.dryrun_logicsig_on_sequence( - algod, teal_abi_sum_lsig, inputs, abi_argument_types, abi_return_type - ) for i in range(N): app_inspector = app_inspectors[i] lsig_inspector = lsig_inspectors[i] diff --git a/tests/unit/blackbox_test.py b/tests/unit/blackbox_test.py index d082124f0..eb7a1b04c 100644 --- a/tests/unit/blackbox_test.py +++ b/tests/unit/blackbox_test.py @@ -5,7 +5,7 @@ import pyteal as pt -from tests.blackbox import Blackbox, BlackboxPyTealer, blackbox_pyteal +from tests.blackbox import Blackbox, PyTealDryRunExecutor, blackbox_pyteal from tests.compile_asserts import assert_teal_as_expected @@ -187,7 +187,7 @@ def test_abi_blackbox_pyteal(subr_abi, mode): name = f"{'app' if mode == pt.Mode.Application else 'lsig'}_{subr.name()}" print(f"Case {subr.name()=}, {abi_return_type=}, {mode=} ------> {name=}") - pytealer = BlackboxPyTealer(subr, mode) + pytealer = PyTealDryRunExecutor(subr, mode) assert pytealer.is_abi(), "should be an ABI subroutine" arg_types = pytealer.abi_argument_types() From b8d41d2a502ecb3993d008e228be154d90b2bb84 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 12 May 2022 19:01:00 -0500 Subject: [PATCH 137/170] wip --- tests/integration/abi_roundtrip_test.py | 53 +++++++++++++++---------- tests/unit/blackbox_test.py | 8 ---- 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/tests/integration/abi_roundtrip_test.py b/tests/integration/abi_roundtrip_test.py index 044659470..edb145bc4 100644 --- a/tests/integration/abi_roundtrip_test.py +++ b/tests/integration/abi_roundtrip_test.py @@ -1,3 +1,4 @@ +from pathlib import Path import pytest from typing import Callable, Literal, TypeVar @@ -7,9 +8,14 @@ import pyteal as pt from tests.blackbox import Blackbox, PyTealDryRunExecutor +from tests.compile_asserts import assert_teal_as_expected T = TypeVar("T", bound=abi.BaseType) +PATH = Path.cwd() / "tests" / "unit" +FIXTURES = PATH / "teal" +GENERATED = PATH / "generated" + def max_int(bit_size): return (1 << bit_size) - 1 @@ -45,25 +51,18 @@ def array_comp_factory( t: type[T], value_type_spec: abi.TypeSpec, length: int ) -> Callable: comp_func = complement_factory(type(value_type_spec.new_instance()), -1) + ts = abi.type_spec_from_annotation(t) if length != -1: - assert t.type_spec().is_length_dynamic() + assert ts.is_length_dynamic() else: - length = t.length_static() + length = ts.length_static() @pt.ABIReturnSubroutine - def array_complement(arr: t, *, output: t): - i = pt.ScratchVar(pt.TealType.uint64) - val = value_type_spec.new_instance() - return pt.For( - i.store(pt.Int(0)), - i.load() < arr.length(), - i.store(i.load() + pt.Int(1)), - ).Do( - pt.Seq( - arr[i.load()].store_into(val), - output.set()[i.load()].store_into(comp_func(val)), - ) - ) + def array_complement(x: t, *, output: t): + value_types = [value_type_spec.new_instance() for _ in range(length)] + setters = [vts.set(x[i]) for i, vts in enumerate(value_types)] + compers = [vts.set(comp_func(vts)) for vts in value_types] + return pt.Seq(*(setters + compers + [output.set(value_types)])) return array_complement @@ -95,8 +94,8 @@ def round_tripper(x: t, *, output: abi.Tuple2[t, t]): return round_tripper -def roundtrip_pytealer(t: type[T]): - roundtrip = roundtrip_factory(t) +def roundtrip_pytealer(t: type[T], dynamic_length: int): + roundtrip = roundtrip_factory(t, dynamic_length) return PyTealDryRunExecutor(roundtrip, pt.Mode.Application) @@ -120,17 +119,19 @@ def roundtrip_pytealer(t: type[T]): abi.Tuple3[abi.Uint16, abi.Uint8, abi.Byte], abi.Tuple3[abi.Uint32, abi.Uint16, abi.Uint8], abi.Tuple3[abi.Uint64, abi.Uint32, abi.Uint16], - abi.StaticArray[abi.Bool, Literal[0]], + abi.StaticArray[abi.Bool, Literal[1]], abi.StaticArray[abi.Bool, Literal[42]], (abi.DynamicArray[abi.Bool], 0), + (abi.DynamicArray[abi.Bool], 1), (abi.DynamicArray[abi.Bool], 42), ] -@pytest.mark.skip("not ready") +# @pytest.mark.skip("not ready") @pytest.mark.parametrize("abi_type", ABI_TYPES) def test_pure_compilation(abi_type): print(f"Testing {abi_type=}") + dynamic_length = -1 if isinstance(abi_type, tuple): abi_type, dynamic_length = abi_type @@ -139,12 +140,20 @@ def test_pure_compilation(abi_type): roundtripper = roundtrip_pytealer(abi_type, dynamic_length) - teal = roundtripper.program() - abi_arg_types = roundtripper.abi_argument_types() assert [sdk_abi_type] == abi_arg_types abi_ret_type = roundtripper.abi_return_type() assert algosdk.abi.TupleType([sdk_abi_type] * 2) == abi_ret_type - _ = teal + program = roundtripper.program() + + filename = f"app_roundtrip_{sdk_abi_type}.teal" + tealdir = GENERATED / "roundtrip" + tealdir.mkdir(parents=True, exist_ok=True) + + save_to = tealdir / filename + with open(save_to, "w") as f: + f.write() + + assert_teal_as_expected(save_to, FIXTURES / "roundtrip" / filename) diff --git a/tests/unit/blackbox_test.py b/tests/unit/blackbox_test.py index eb7a1b04c..ad77bd568 100644 --- a/tests/unit/blackbox_test.py +++ b/tests/unit/blackbox_test.py @@ -143,14 +143,6 @@ def fn_2mixed_arg_1ret( ] -# @Blackbox(input_types=[None]) -# @pt.ABIReturnSubroutine -# def complicated_abi_round_trip( -# x: CompoundType, *, output: pt.abi.Tuple2[CompoundType, CompoundType] -# ): -# return pt.Seq - - ABI_UNITS = [ (fn_0arg_0ret, None), (fn_0arg_uint64_ret, pt.abi.Uint64()), From 3c9fdceca8402dfc9b51c2eed360948af82aa79f Mon Sep 17 00:00:00 2001 From: Michael Diamant Date: Thu, 12 May 2022 23:39:05 -0400 Subject: [PATCH 138/170] Replace blackbox_pyteal with PyTealDryRunExecutor (#338) * Expand BlackboxPyTealer in an attempt to simplify dryrun request generation --- tests/blackbox.py | 128 ++++++++++++++++++++--------- tests/integration/ecdsa_test.py | 34 +++----- tests/integration/graviton_test.py | 68 +++++---------- tests/unit/blackbox_test.py | 72 ++++++++++++++-- 4 files changed, 185 insertions(+), 117 deletions(-) diff --git a/tests/blackbox.py b/tests/blackbox.py index 6f93ee03f..c5460a302 100644 --- a/tests/blackbox.py +++ b/tests/blackbox.py @@ -1,4 +1,5 @@ -from typing import Callable, Sequence +from typing import Callable, Generic, Sequence, TypeVar +from dataclasses import dataclass import algosdk.abi from algosdk.v2client import algod @@ -132,20 +133,33 @@ def decorator_blackbox(func: SubroutineFnWrapper | ABIReturnSubroutine): # ---- API ---- # -def mode_to_execution_mode(mode: Mode) -> blackbox.ExecutionMode: - if mode == Mode.Application: - return blackbox.ExecutionMode.Application - if mode == Mode.Signature: - return blackbox.ExecutionMode.Signature +def _unknown_mode_exception(mode: Mode) -> Exception: + return Exception(f"Unknown mode {mode} of type {type(mode)}") - raise Exception(f"Unknown mode {mode} of type {type(mode)}") +Output = TypeVar("Output") -def blackbox_pyteal(subr: BlackboxWrapper, mode: Mode) -> Expr: - """ - cf. PyTealDryRunExecutor.program() for futher details - """ - return PyTealDryRunExecutor(subr, mode).program() + +@dataclass(frozen=True) +class _match_mode(Generic[Output]): + app_case: Callable[[], Output] + signature_case: Callable[[], Output] + + def __call__(self, mode: Mode, *args, **kwargs) -> Output: + match mode: + case Mode.Application: + return self.app_case() + case Mode.Signature: + return self.signature_case() + case _: + raise Exception(f"Unknown mode {mode} of type {type(mode)}") + + +def mode_to_execution_mode(mode: Mode) -> blackbox.ExecutionMode: + return _match_mode( + app_case=lambda: blackbox.ExecutionMode.Application, + signature_case=lambda: blackbox.ExecutionMode.Signature, + )(mode) class PyTealDryRunExecutor: @@ -179,9 +193,9 @@ def __init__(self, subr: BlackboxWrapper, mode: Mode): def is_abi(self) -> bool: return isinstance(self.subr.subroutine, ABIReturnSubroutine) - def abi_argument_types(self) -> list[None | algosdk.abi.ABIType]: + def abi_argument_types(self) -> None | list[algosdk.abi.ABIType]: if not self.is_abi(): - raise NotImplementedError(f"this {type(self)} is not A.B.I. compatible") + return None def handle_arg(arg): if isinstance(arg, abi.TypeSpec): @@ -192,7 +206,7 @@ def handle_arg(arg): def abi_return_type(self) -> None | algosdk.abi.ABIType: if not self.is_abi(): - raise NotImplementedError("this {type(self)} is not A.B.I. compatible") + return None if not self.subr.subroutine.output_kwarg_info: return None @@ -378,34 +392,68 @@ def approval(): return approval + def compile(self, version: int, assemble_constants: bool = False) -> str: + return _match_mode( + app_case=lambda: compileTeal( + self.program(), + self.mode, + version=version, + assembleConstants=assemble_constants, + ), + signature_case=lambda: compileTeal( + self.program(), + self.mode, + version=version, + assembleConstants=assemble_constants, + ), + )(self.mode) + def dryrun_on_sequence( self, inputs: list[Sequence[str | int]], compiler_version=6, sender: str = ZERO_ADDRESS, ) -> list[DryRunInspector]: - match self.mode: - case Mode.Application: - return DryRunExecutor.dryrun_app_on_sequence( - algod_with_assertion(), - compileTeal( - self.program(), Mode.Application, version=compiler_version - ), - inputs, - self.abi_argument_types(), - self.abi_return_type(), - sender, - ) - case Mode.Signature: - return DryRunExecutor.dryrun_logicsig_on_sequence( - algod_with_assertion(), - compileTeal( - self.program(), Mode.Signature, version=compiler_version - ), - inputs, - self.abi_argument_types(), - self.abi_return_type(), - sender, - ) - case _: - raise Exception(f"Unknown mode {self.mode} of type {type(self.mode)}") + return _match_mode( + app_case=lambda: DryRunExecutor.dryrun_app_on_sequence( + algod_with_assertion(), + self.compile(compiler_version), + inputs, + self.abi_argument_types(), + self.abi_return_type(), + sender, + ), + signature_case=lambda: DryRunExecutor.dryrun_logicsig_on_sequence( + algod_with_assertion(), + self.compile(compiler_version), + inputs, + self.abi_argument_types(), + self.abi_return_type(), + sender, + ), + )(self.mode) + + def dryrun( + self, + inputs: Sequence[str | int], + compiler_version=6, + sender: str = ZERO_ADDRESS, + ) -> DryRunInspector: + return _match_mode( + app_case=lambda: DryRunExecutor.dryrun_app( + algod_with_assertion(), + self.compile(compiler_version), + inputs, + self.abi_argument_types(), + self.abi_return_type(), + sender, + ), + signature_case=lambda: DryRunExecutor.dryrun_logicsig( + algod_with_assertion(), + self.compile(compiler_version), + inputs, + self.abi_argument_types(), + self.abi_return_type(), + sender, + ), + )(self.mode) diff --git a/tests/integration/ecdsa_test.py b/tests/integration/ecdsa_test.py index efbcac07d..4efceccb7 100644 --- a/tests/integration/ecdsa_test.py +++ b/tests/integration/ecdsa_test.py @@ -1,5 +1,3 @@ -from graviton.blackbox import DryRunExecutor - from pyteal import ( Bytes, EcdsaCurve, @@ -10,15 +8,13 @@ And, Subroutine, Sha512_256, - compileTeal, Mode, TealType, ) from tests.blackbox import ( Blackbox, - algod_with_assertion, - blackbox_pyteal, + PyTealDryRunExecutor, ) @@ -49,11 +45,10 @@ def verify(): ), ) - approval_app = blackbox_pyteal(verify, Mode.Application) - app_teal = compileTeal(approval_app, Mode.Application, version=5) args = [] - algod = algod_with_assertion() - app_result = DryRunExecutor.dryrun_app(algod, app_teal, args) + app_result = PyTealDryRunExecutor(verify, Mode.Application).dryrun( + args, compiler_version=5 + ) assert app_result.stack_top() == 1, app_result.report( args, "stack_top() is not equal to 1, indicating ecdsa verification failed." @@ -85,11 +80,10 @@ def verify_fail(): ), ) - approval_app = blackbox_pyteal(verify_fail, Mode.Application) - app_teal = compileTeal(approval_app, Mode.Application, version=5) args = [] - algod = algod_with_assertion() - app_result = DryRunExecutor.dryrun_app(algod, app_teal, args) + app_result = PyTealDryRunExecutor(verify_fail, Mode.Application).dryrun( + args, compiler_version=5 + ) assert app_result.stack_top() == 0, app_result.report( args, @@ -122,11 +116,10 @@ def decompress(): ) ) - approval_app = blackbox_pyteal(decompress, Mode.Application) - app_teal = compileTeal(approval_app, Mode.Application, version=5) args = [] - algod = algod_with_assertion() - app_result = DryRunExecutor.dryrun_app(algod, app_teal, args) + app_result = PyTealDryRunExecutor(decompress, Mode.Application).dryrun( + args, compiler_version=5 + ) assert app_result.stack_top() == 1, app_result.report( args, "stack_top() is not equal to 1, indicating ecdsa verification failed." @@ -164,11 +157,10 @@ def recover(): ) ) - approval_app = blackbox_pyteal(recover, Mode.Application) - app_teal = compileTeal(approval_app, Mode.Application, version=5) args = [] - algod = algod_with_assertion() - app_result = DryRunExecutor.dryrun_app(algod, app_teal, args) + app_result = PyTealDryRunExecutor(recover, Mode.Application).dryrun( + args, compiler_version=5 + ) assert app_result.stack_top() == 1, app_result.report( args, "stack_top() is not equal to 1, indicating ecdsa verification failed." diff --git a/tests/integration/graviton_test.py b/tests/integration/graviton_test.py index 3e905afe1..b85622597 100644 --- a/tests/integration/graviton_test.py +++ b/tests/integration/graviton_test.py @@ -11,8 +11,8 @@ Blackbox, BlackboxWrapper, algod_with_assertion, - blackbox_pyteal, mode_to_execution_mode, + PyTealDryRunExecutor, ) from graviton.blackbox import ( @@ -41,13 +41,7 @@ def wrap_compile_and_save( ): is_app = mode == pt.Mode.Application - # 1. PyTeal program Expr generation - approval = blackbox_pyteal(subr, mode) - - # 2. TEAL generation - teal = pt.compileTeal( - approval, mode, version=version, assembleConstants=assemble_constants - ) + teal = PyTealDryRunExecutor(subr, mode).compile(version, assemble_constants) tealfile = f'{"app" if is_app else "lsig"}_{case_name}.teal' tealdir = GENERATED / test_name @@ -567,32 +561,23 @@ def test_blackbox_subroutines_as_logic_sigs( def blackbox_pyteal_example1(): # Example 1: Using blackbox_pyteal for a simple test of both an app and logic sig: - from graviton.blackbox import DryRunEncoder, DryRunExecutor + from graviton.blackbox import DryRunEncoder - from pyteal import compileTeal, Int, Mode, Subroutine, TealType - from tests.blackbox import Blackbox, algod_with_assertion, blackbox_pyteal + from pyteal import Int, Mode, Subroutine, TealType + from tests.blackbox import Blackbox @Blackbox(input_types=[TealType.uint64]) @Subroutine(TealType.uint64) def square(x): return x ** Int(2) - # create pyteal app and logic sig approvals: - approval_app = blackbox_pyteal(square, Mode.Application) - approval_lsig = blackbox_pyteal(square, Mode.Signature) - - # compile the evaluated approvals to generate TEAL: - app_teal = compileTeal(approval_app, Mode.Application, version=6) - lsig_teal = compileTeal(approval_lsig, Mode.Signature, version=6) - # provide args for evaluation (will compute x^2) x = 9 args = [x] # evaluate the programs - algod = algod_with_assertion() - app_result = DryRunExecutor.dryrun_app(algod, app_teal, args) - lsig_result = DryRunExecutor.dryrun_logicsig(algod, lsig_teal, args) + app_result = PyTealDryRunExecutor(square, Mode.Application).dryrun(args) + lsig_result = PyTealDryRunExecutor(square, Mode.Signature).dryrun(args) # check to see that x^2 is at the top of the stack as expected assert app_result.stack_top() == x**2, app_result.report( @@ -615,10 +600,9 @@ def blackbox_pyteal_example2(): from pathlib import Path import random - from graviton.blackbox import DryRunExecutor, DryRunInspector + from graviton.blackbox import DryRunInspector from pyteal import ( - compileTeal, For, If, Int, @@ -630,7 +614,7 @@ def blackbox_pyteal_example2(): TealType, ) - from tests.blackbox import Blackbox, algod_with_assertion, blackbox_pyteal + from tests.blackbox import Blackbox # GCD via the Euclidean Algorithm (iterative version): @Blackbox(input_types=[TealType.uint64, TealType.uint64]) @@ -646,10 +630,6 @@ def euclid(x, y): ) return Seq(For(start, cond, step).Do(Seq()), a.load()) - # create approval PyTeal and compile it to TEAL: - euclid_app = blackbox_pyteal(euclid, Mode.Application) - euclid_app_teal = compileTeal(euclid_app, Mode.Application, version=6) - # generate a report with 400 = 20*20 dry run rows: N = 20 inputs = list( @@ -659,11 +639,10 @@ def euclid(x, y): ) ) - # execute the dry-run sequence: - algod = algod_with_assertion() - # assert that each result is that same as what Python's math.gcd() computes - inspectors = DryRunExecutor.dryrun_app_on_sequence(algod, euclid_app_teal, inputs) + inspectors = PyTealDryRunExecutor(euclid, Mode.Application).dryrun_on_sequence( + inputs + ) for i, result in enumerate(inspectors): args = inputs[i] assert result.stack_top() == math.gcd(*args), result.report( @@ -684,14 +663,13 @@ def blackbox_pyteal_example3(): from graviton.blackbox import ( DryRunEncoder, - DryRunExecutor, DryRunProperty as DRProp, ) from graviton.invariant import Invariant - from pyteal import compileTeal, If, Int, Mod, Mode, Subroutine, TealType + from pyteal import If, Int, Mod, Mode, Subroutine, TealType - from tests.blackbox import Blackbox, algod_with_assertion, blackbox_pyteal + from tests.blackbox import Blackbox # avoid flaky tests just in case I was wrong about the stack height invariant... random.seed(42) @@ -744,13 +722,10 @@ def euclid(x, y): .Else(If(y == Int(0)).Then(x).Else(euclid(y, Mod(x, y)))) ) - # Generate PyTeal and TEAL for the recursive Euclidean algorithm: - euclid_app = blackbox_pyteal(euclid, Mode.Application) - euclid_app_teal = compileTeal(euclid_app, Mode.Application, version=6) - # Execute on the input sequence to get a dry-run inspectors: - algod = algod_with_assertion() - inspectors = DryRunExecutor.dryrun_app_on_sequence(algod, euclid_app_teal, inputs) + inspectors = PyTealDryRunExecutor(euclid, Mode.Application).dryrun_on_sequence( + inputs + ) # Assert that each invariant holds on the sequences of inputs and dry-runs: for property, predicate in predicates.items(): @@ -856,7 +831,6 @@ def report(kind): def blackbox_pyteal_while_continue_test(): from tests.blackbox import Blackbox from pyteal import ( - compileTeal, Continue, Int, Mode, @@ -883,13 +857,11 @@ def while_continue_accumulation(n): Return(i.load()), ) - approval_lsig = blackbox_pyteal(while_continue_accumulation, Mode.Signature) - lsig_teal = compileTeal(approval_lsig, Mode.Signature, version=6) - algod = algod_with_assertion() - for x in range(30): args = [x] - lsig_result = DryRunExecutor.dryrun_logicsig(algod, lsig_teal, args) + lsig_result = PyTealDryRunExecutor( + while_continue_accumulation, Mode.Signature + ).dryrun(args) if x == 0: assert not lsig_result.passed() else: diff --git a/tests/unit/blackbox_test.py b/tests/unit/blackbox_test.py index eb7a1b04c..88395c635 100644 --- a/tests/unit/blackbox_test.py +++ b/tests/unit/blackbox_test.py @@ -5,7 +5,7 @@ import pyteal as pt -from tests.blackbox import Blackbox, PyTealDryRunExecutor, blackbox_pyteal +from tests.blackbox import Blackbox, BlackboxWrapper, PyTealDryRunExecutor from tests.compile_asserts import assert_teal_as_expected @@ -171,7 +171,7 @@ def test_blackbox_pyteal(subr, mode): is_app = mode == pt.Mode.Application name = f"{'app' if is_app else 'lsig'}_{subr.name()}" - compiled = pt.compileTeal(blackbox_pyteal(subr, mode), mode, version=6) + compiled = PyTealDryRunExecutor(subr, mode).compile(version=6) tealdir = GENERATED / "blackbox" tealdir.mkdir(parents=True, exist_ok=True) save_to = tealdir / (name + ".teal") @@ -187,10 +187,10 @@ def test_abi_blackbox_pyteal(subr_abi, mode): name = f"{'app' if mode == pt.Mode.Application else 'lsig'}_{subr.name()}" print(f"Case {subr.name()=}, {abi_return_type=}, {mode=} ------> {name=}") - pytealer = PyTealDryRunExecutor(subr, mode) - assert pytealer.is_abi(), "should be an ABI subroutine" + pdre = PyTealDryRunExecutor(subr, mode) + assert pdre.is_abi(), "should be an ABI subroutine" - arg_types = pytealer.abi_argument_types() + arg_types = pdre.abi_argument_types() if subr.name() != "fn_1tt_arg_uint64_ret": assert not arg_types or any( arg_types @@ -200,11 +200,11 @@ def test_abi_blackbox_pyteal(subr_abi, mode): expected_sdk_return_type = pt.abi.algosdk_from_type_spec( abi_return_type.type_spec() ) - assert expected_sdk_return_type == pytealer.abi_return_type() + assert expected_sdk_return_type == pdre.abi_return_type() else: - assert pytealer.abi_return_type() is None + assert pdre.abi_return_type() is None - compiled = pt.compileTeal(blackbox_pyteal(subr, mode), mode, version=6) + compiled = pdre.compile(version=6) tealdir = GENERATED / "abi" tealdir.mkdir(parents=True, exist_ok=True) save_to = tealdir / (name + ".teal") @@ -212,3 +212,59 @@ def test_abi_blackbox_pyteal(subr_abi, mode): f.write(compiled) assert_teal_as_expected(save_to, FIXTURES / "abi" / (name + ".teal")) + + +@pytest.mark.parametrize("mode", (pt.Mode.Application, pt.Mode.Signature)) +@pytest.mark.parametrize( + "fn, expected_is_abi", ((utest_noop, False), (fn_0arg_uint64_ret, True)) +) +def test_PyTealBlackboxExecutor_is_abi( + mode: pt.Mode, fn: BlackboxWrapper, expected_is_abi: bool +): + p = PyTealDryRunExecutor(fn, mode) + assert p.is_abi() == expected_is_abi + if expected_is_abi: + assert p.abi_argument_types() is not None + assert p.abi_return_type() is not None + else: + assert p.abi_argument_types() is None + assert p.abi_return_type() is None + + +@pytest.mark.parametrize("mode", (pt.Mode.Application, pt.Mode.Signature)) +@pytest.mark.parametrize( + "fn, expected_arg_count", + ( + (fn_0arg_uint64_ret, 0), + (fn_1arg_0ret, 1), + (fn_1arg_1ret, 1), + (fn_2arg_0ret, 2), + (fn_2mixed_arg_1ret, 2), + ), +) +def test_PyTealBlackboxExecutor_abi_argument_types( + mode: pt.Mode, fn: BlackboxWrapper, expected_arg_count: int +): + assert ( + len(PyTealDryRunExecutor(fn, mode).abi_argument_types()) == expected_arg_count + ) + + +@pytest.mark.parametrize("mode", (pt.Mode.Application, pt.Mode.Signature)) +@pytest.mark.parametrize( + "fn, expected_does_produce_type", + ( + (fn_0arg_uint64_ret, True), + (fn_1arg_0ret, False), + (fn_1arg_1ret, True), + (fn_2arg_0ret, False), + (fn_2mixed_arg_1ret, True), + ), +) +def test_PyTealBlackboxExecutor_abi_return_type( + mode: pt.Mode, fn: BlackboxWrapper, expected_does_produce_type: bool +): + if expected_does_produce_type: + assert PyTealDryRunExecutor(fn, mode).abi_return_type() is not None + else: + assert PyTealDryRunExecutor(fn, mode).abi_return_type() is None From 4d59b5c40208afc688fc131f857d99fa5d5839ed Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 12 May 2022 22:41:01 -0500 Subject: [PATCH 139/170] wip --- tests/blackbox.py | 2 +- tests/integration/abi_roundtrip_test.py | 21 +++++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/tests/blackbox.py b/tests/blackbox.py index 6f93ee03f..b2b891105 100644 --- a/tests/blackbox.py +++ b/tests/blackbox.py @@ -202,7 +202,7 @@ def abi_return_type(self) -> None | algosdk.abi.ABIType: ) def program(self) -> Expr: - """Functor producing ready-to-compile PyTeal programs from annotated subroutines + """Get ready-to-compile PyTeal program from Subroutines and ABIReturnSubroutines TODO: add explanation for ABI var's/output as well Returns: diff --git a/tests/integration/abi_roundtrip_test.py b/tests/integration/abi_roundtrip_test.py index edb145bc4..129826223 100644 --- a/tests/integration/abi_roundtrip_test.py +++ b/tests/integration/abi_roundtrip_test.py @@ -12,7 +12,7 @@ T = TypeVar("T", bound=abi.BaseType) -PATH = Path.cwd() / "tests" / "unit" +PATH = Path.cwd() / "tests" / "integration" FIXTURES = PATH / "teal" GENERATED = PATH / "generated" @@ -100,8 +100,12 @@ def roundtrip_pytealer(t: type[T], dynamic_length: int): ABI_TYPES = [ + abi.Address, abi.Bool, abi.Byte, + (abi.String, 0), + (abi.String, 1), + (abi.String, 13), abi.Uint8, abi.Uint16, abi.Uint32, @@ -121,13 +125,17 @@ def roundtrip_pytealer(t: type[T], dynamic_length: int): abi.Tuple3[abi.Uint64, abi.Uint32, abi.Uint16], abi.StaticArray[abi.Bool, Literal[1]], abi.StaticArray[abi.Bool, Literal[42]], + abi.StaticArray[abi.Uint64, Literal[1]], + abi.StaticArray[abi.Uint64, Literal[42]], (abi.DynamicArray[abi.Bool], 0), (abi.DynamicArray[abi.Bool], 1), (abi.DynamicArray[abi.Bool], 42), + (abi.DynamicArray[abi.Uint64], 0), + (abi.DynamicArray[abi.Uint64], 1), + (abi.DynamicArray[abi.Uint64], 42), ] -# @pytest.mark.skip("not ready") @pytest.mark.parametrize("abi_type", ABI_TYPES) def test_pure_compilation(abi_type): print(f"Testing {abi_type=}") @@ -147,13 +155,18 @@ def test_pure_compilation(abi_type): assert algosdk.abi.TupleType([sdk_abi_type] * 2) == abi_ret_type program = roundtripper.program() + teal = pt.compileTeal(program, pt.Mode.Application, version=6) - filename = f"app_roundtrip_{sdk_abi_type}.teal" + filename = ( + f"app_roundtrip_{sdk_abi_type}" + + (f"_<{dynamic_length}>" if dynamic_length >= 0 else "") + + ".teal" + ) tealdir = GENERATED / "roundtrip" tealdir.mkdir(parents=True, exist_ok=True) save_to = tealdir / filename with open(save_to, "w") as f: - f.write() + f.write(teal) assert_teal_as_expected(save_to, FIXTURES / "roundtrip" / filename) From 0df1cd5616207f1a3defbc8fda98382c43d343b6 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 13 May 2022 08:28:36 -0500 Subject: [PATCH 140/170] temporary revertable commit --- pyteal/ast/subroutine_test.py | 88 +++++++++++++++++++++-------------- 1 file changed, 53 insertions(+), 35 deletions(-) diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index 29fa8cf6c..17d2432f1 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -129,46 +129,64 @@ def fn_2arg_1ret_with_expr( ) -> pt.Expr: return output.set(b[a % pt.Int(10)]) + @pt.ABIReturnSubroutine + def fn_w_tuple1arg( + a: pt.Expr, + b: pt.abi.Tuple1[pt.abi.Byte], + *, + output: pt.abi.Byte, + ) -> pt.Expr: + return output.set(b[a % pt.Int(10)]) + cases = ( - ABISubroutineTC(fn_0arg_0ret, [], "fn_0arg_0ret", "void"), - ABISubroutineTC( - fn_0arg_uint64_ret, [], "fn_0arg_uint64_ret", pt.abi.Uint64TypeSpec() - ), - ABISubroutineTC(fn_1arg_0ret, [pt.abi.Uint64()], "fn_1arg_0ret", "void"), - ABISubroutineTC( - fn_1arg_1ret, [pt.abi.Uint64()], "fn_1arg_1ret", pt.abi.Uint64TypeSpec() - ), - ABISubroutineTC( - fn_2arg_0ret, - [ - pt.abi.Uint64(), - pt.abi.StaticArray( - pt.abi.StaticArrayTypeSpec(pt.abi.ByteTypeSpec(), 10) - ), - ], - "fn_2arg_0ret", - "void", - ), - ABISubroutineTC( - fn_2arg_1ret, - [ - pt.abi.Uint64(), - pt.abi.StaticArray( - pt.abi.StaticArrayTypeSpec(pt.abi.ByteTypeSpec(), 10) - ), - ], - "fn_2arg_1ret", - pt.abi.ByteTypeSpec(), - ), + # ABISubroutineTC(fn_0arg_0ret, [], "fn_0arg_0ret", "void"), + # ABISubroutineTC( + # fn_0arg_uint64_ret, [], "fn_0arg_uint64_ret", pt.abi.Uint64TypeSpec() + # ), + # ABISubroutineTC(fn_1arg_0ret, [pt.abi.Uint64()], "fn_1arg_0ret", "void"), + # ABISubroutineTC( + # fn_1arg_1ret, [pt.abi.Uint64()], "fn_1arg_1ret", pt.abi.Uint64TypeSpec() + # ), + # ABISubroutineTC( + # fn_2arg_0ret, + # [ + # pt.abi.Uint64(), + # pt.abi.StaticArray( + # pt.abi.StaticArrayTypeSpec(pt.abi.ByteTypeSpec(), 10) + # ), + # ], + # "fn_2arg_0ret", + # "void", + # ), + # ABISubroutineTC( + # fn_2arg_1ret, + # [ + # pt.abi.Uint64(), + # pt.abi.StaticArray( + # pt.abi.StaticArrayTypeSpec(pt.abi.ByteTypeSpec(), 10) + # ), + # ], + # "fn_2arg_1ret", + # pt.abi.ByteTypeSpec(), + # ), + # ABISubroutineTC( + # fn_2arg_1ret_with_expr, + # [ + # pt.Int(5), + # pt.abi.StaticArray( + # pt.abi.StaticArrayTypeSpec(pt.abi.ByteTypeSpec(), 10) + # ), + # ], + # "fn_2arg_1ret_with_expr", + # pt.abi.ByteTypeSpec(), + # ), ABISubroutineTC( - fn_2arg_1ret_with_expr, + fn_w_tuple1arg, [ pt.Int(5), - pt.abi.StaticArray( - pt.abi.StaticArrayTypeSpec(pt.abi.ByteTypeSpec(), 10) - ), + pt.abi.TupleTypeSpec(pt.abi.ByteTypeSpec()), ], - "fn_2arg_1ret_with_expr", + "fn_w_tuple1arg", pt.abi.ByteTypeSpec(), ), ) From 0654598177afac3dcd670c0f9f78465d2c087e05 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 13 May 2022 09:31:10 -0500 Subject: [PATCH 141/170] wip --- pyteal/ast/subroutine_test.py | 4 +- requirements.txt | 2 +- tests/integration/abi_roundtrip_test.py | 78 ++++++++++++++----------- 3 files changed, 48 insertions(+), 36 deletions(-) diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index 17d2432f1..c47a31b18 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -136,7 +136,7 @@ def fn_w_tuple1arg( *, output: pt.abi.Byte, ) -> pt.Expr: - return output.set(b[a % pt.Int(10)]) + return output.set(pt.Int(1)) cases = ( # ABISubroutineTC(fn_0arg_0ret, [], "fn_0arg_0ret", "void"), @@ -184,7 +184,7 @@ def fn_w_tuple1arg( fn_w_tuple1arg, [ pt.Int(5), - pt.abi.TupleTypeSpec(pt.abi.ByteTypeSpec()), + pt.abi.make(pt.abi.Tuple1[pt.abi.Byte]), ], "fn_w_tuple1arg", pt.abi.ByteTypeSpec(), diff --git a/requirements.txt b/requirements.txt index 847f900a8..e237b51e2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ black==22.3.0 flake8==4.0.1 flake8-tidy-imports==4.6.0 -graviton@git+https://github.com/algorand/graviton@b4c506d70aef1053a3b7eba5aae156465a4dbfe5 +graviton@git+https://github.com/algorand/graviton@v0.2.0 mypy==0.950 pytest==7.1.1 pytest-cov==3.0.0 diff --git a/tests/integration/abi_roundtrip_test.py b/tests/integration/abi_roundtrip_test.py index 129826223..b54509ede 100644 --- a/tests/integration/abi_roundtrip_test.py +++ b/tests/integration/abi_roundtrip_test.py @@ -100,39 +100,51 @@ def roundtrip_pytealer(t: type[T], dynamic_length: int): ABI_TYPES = [ - abi.Address, - abi.Bool, - abi.Byte, - (abi.String, 0), - (abi.String, 1), - (abi.String, 13), - abi.Uint8, - abi.Uint16, - abi.Uint32, - abi.Uint64, - abi.Tuple0, - abi.Tuple1[abi.Bool], - abi.Tuple1[abi.Byte], - abi.Tuple1[abi.Uint8], - abi.Tuple1[abi.Uint16], - abi.Tuple1[abi.Uint32], - abi.Tuple1[abi.Uint64], - abi.Tuple3[abi.Bool, abi.Uint64, abi.Uint32], - abi.Tuple3[abi.Byte, abi.Bool, abi.Uint64], - abi.Tuple3[abi.Uint8, abi.Byte, abi.Bool], - abi.Tuple3[abi.Uint16, abi.Uint8, abi.Byte], - abi.Tuple3[abi.Uint32, abi.Uint16, abi.Uint8], - abi.Tuple3[abi.Uint64, abi.Uint32, abi.Uint16], - abi.StaticArray[abi.Bool, Literal[1]], - abi.StaticArray[abi.Bool, Literal[42]], - abi.StaticArray[abi.Uint64, Literal[1]], - abi.StaticArray[abi.Uint64, Literal[42]], - (abi.DynamicArray[abi.Bool], 0), - (abi.DynamicArray[abi.Bool], 1), - (abi.DynamicArray[abi.Bool], 42), - (abi.DynamicArray[abi.Uint64], 0), - (abi.DynamicArray[abi.Uint64], 1), - (abi.DynamicArray[abi.Uint64], 42), + # abi.Address, + # abi.Bool, + # abi.Byte, + # (abi.String, 0), + # (abi.String, 1), + # (abi.String, 13), + # abi.Uint8, + # abi.Uint16, + # abi.Uint32, + # abi.Uint64, + # abi.Tuple0, + # abi.Tuple1[abi.Bool], + # abi.Tuple1[abi.Byte], + # abi.Tuple1[abi.Uint8], + # abi.Tuple1[abi.Uint16], + # abi.Tuple1[abi.Uint32], + # abi.Tuple1[abi.Uint64], + # abi.Tuple3[abi.Bool, abi.Uint64, abi.Uint32], + # abi.Tuple3[abi.Byte, abi.Bool, abi.Uint64], + # abi.Tuple3[abi.Uint8, abi.Byte, abi.Bool], + # abi.Tuple3[abi.Uint16, abi.Uint8, abi.Byte], + # abi.Tuple3[abi.Uint32, abi.Uint16, abi.Uint8], + # abi.Tuple3[abi.Uint64, abi.Uint32, abi.Uint16], + # abi.StaticArray[abi.Bool, Literal[1]], + # abi.StaticArray[abi.Bool, Literal[42]], + # abi.StaticArray[abi.Uint64, Literal[1]], + # abi.StaticArray[abi.Uint64, Literal[42]], + # (abi.DynamicArray[abi.Bool], 0), + # (abi.DynamicArray[abi.Bool], 1), + # (abi.DynamicArray[abi.Bool], 42), + # (abi.DynamicArray[abi.Uint64], 0), + # (abi.DynamicArray[abi.Uint64], 1), + # (abi.DynamicArray[abi.Uint64], 42), + abi.StaticArray[abi.Tuple1[abi.Bool], Literal[10]], + # ( + # abi.DynamicArray[ + # abi.Tuple4[ + # abi.StaticArray[abi.Byte, Literal[4]], + # abi.Tuple2[abi.Bool, abi.Bool], + # abi.Uint64, + # abi.Address, + # ] + # ], + # 13, + # ), ] From aa14bbe9ec1d1493d622a76a97a53fb5b6288214 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Sat, 14 May 2022 18:54:56 -0500 Subject: [PATCH 142/170] test against head of graviton --- .gitignore | 1 + Makefile | 4 +- pyteal/ast/subroutine.py | 2 +- requirements.txt | 2 +- tests/integration/pure_logicsig_test.py | 123 ++++++++++++++++++++++++ tests/unit/blackbox_test.py | 8 +- 6 files changed, 132 insertions(+), 8 deletions(-) create mode 100644 tests/integration/pure_logicsig_test.py diff --git a/.gitignore b/.gitignore index cdb471aae..26d6609eb 100644 --- a/.gitignore +++ b/.gitignore @@ -102,6 +102,7 @@ celerybeat-schedule *.sage.py # Environments +_env .env .venv env/ diff --git a/Makefile b/Makefile index 9e6c0a3bd..a796378f8 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,7 @@ flake8: flake8 $(ALLPY) # TODO: add `tests` to $MYPY when graviton respects mypy (version 🐈) -MYPY = pyteal scripts +MYPY = pyteal scripts tests mypy: mypy $(MYPY) @@ -57,7 +57,7 @@ test-unit: lint-and-test: check-generate-init lint test-unit -# ---- Integration Test (algod required) ---- # +# ---- Integration Tests (algod required) ---- # sandbox-dev-up: docker-compose up -d algod diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index a972ed48f..28ec2b5ca 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -66,7 +66,7 @@ def __init__( self.__name: str = name_str if name_str else self.implementation.__name__ def _validate( - self, input_types: list[TealType] = None + self, input_types: list[TealType | None] = None ) -> tuple[ MappingProxyType[str, Parameter], dict[str, type], diff --git a/requirements.txt b/requirements.txt index e237b51e2..e28ed98a5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ black==22.3.0 flake8==4.0.1 flake8-tidy-imports==4.6.0 -graviton@git+https://github.com/algorand/graviton@v0.2.0 +graviton@git+https://github.com/algorand/graviton@adc86ee8a25ddb898a592d525b65b697030fd0a4 mypy==0.950 pytest==7.1.1 pytest-cov==3.0.0 diff --git a/tests/integration/pure_logicsig_test.py b/tests/integration/pure_logicsig_test.py new file mode 100644 index 000000000..dbf605252 --- /dev/null +++ b/tests/integration/pure_logicsig_test.py @@ -0,0 +1,123 @@ +from itertools import product +from os import environ +from pathlib import Path +import pytest + +from pyteal import compileTeal, Mode + +import examples.signature.factorizer_game as factorizer + +from tests.blackbox import algod_with_assertion +from graviton.blackbox import ( + DryRunExecutor as Executor, + DryRunInspector as Inspector, + DryRunProperty as DRProp, +) +from graviton.invariant import Invariant + +REPORTS_DIR = Path.cwd() / "tests" / "integration" / "reports" +ALGOD = algod_with_assertion() + +DEFAULT = { + "A": 3, + "P": 13, + "Q": 13, + "M": 10, + "N": 10, +} + + +def get_param_bounds(): + """ + Allow setting the bounds either from the environment via something like: + + % A=3 P=13 Q=13 M=10 N=10 pytest tests/integration/pure_logicsig_test.py::test_many_factorizer_games + + OR - when any of the above is missing, replace with the default version + """ + vars = [] + for var in ("A", "P", "Q", "M", "N"): + val = environ.get(var) + if val is None: + val = DEFAULT[var] + vars.append(int(val)) + return vars + + +def get_factorizer_param_sequence(): + A, P, Q, M, N = get_param_bounds() + return [(a, p, q, M, N) for a in range(A) for p in range(P) for q in range(Q)] + + +def inputs_for_coefficients(a, p, q, M, N): + # TODO: this should really be focused around the roots p and q + return product(range(M), range(N)) + + +def factorizer_game_check(a: int, p: int, q: int, M: int, N: int): + ae = None + if a <= 0 or p < 0 or q <= p: + with pytest.raises(AssertionError) as ae: + factorizer.logicsig(a, p, q), + + if ae: + return + + compiled = compileTeal( + factorizer.logicsig(a, p, q), + version=6, + mode=Mode.Signature, + assembleConstants=True, + ) + inputs = list(inputs_for_coefficients(a, p, q, M, N)) + N = len(inputs) + + def poly(x): + return abs(a * x**2 - a * (p + q) * x + a * p * q) + + def naive_prize(x, y): + return 1_000_000 * max(10 - (sum(map(poly, (x, y))) + 1) // 2, 0) + + def payment_amount(x, y): + return 0 if x == y else naive_prize(x, y) + + amts = list(map(lambda args: payment_amount(*args), inputs)) + + inspectors, txns = [], [] + for args, amt in zip(inputs, amts): + txn = {"amt": amt} + txns.append(txn) + inspectors.append(Executor.dryrun_logicsig(ALGOD, compiled, args, **txn)) + + print( + f"generating a report for (a,p,q) = {a,p,q} with {M, N} dry-run calls and spreadsheet rows" + ) + filebase = f"factorizer_game_{a}_{p}_{q}" + csvpath = REPORTS_DIR / f"{filebase}.csv" + with open(csvpath, "w") as f: + f.write(Inspector.csv_report(inputs, inspectors, txns=txns)) + + print(f"validating passing_invariant for (a,p,q) = {a,p,q} over {N} dry-run calls") + passing_invariant = Invariant( + lambda args: bool(payment_amount(*args)), + name=f"passing invariant for coeffs {a, p, q}", + ) + passing_invariant.validates(DRProp.passed, inputs, inspectors) + + print( + f"validate procedurally that payment amount as expected for (a,p,q) = {a,p,q} over {M, N} dry-rundry-run calls" + ) + + for args, inspector in zip(inputs, inspectors): + x, y = args + eprize = naive_prize(x, y) + final_scratches = inspector.final_scratch().values() + assert eprize == 0 or eprize in final_scratches, inspector.report( + args, + f"(a, p, q, x, y) = {a, p, q, x, y}. final scratch slots expected to contain {eprize} v. actual={final_scratches}", + ) + + +@pytest.mark.parametrize("a, p, q, M, N", get_factorizer_param_sequence()) +def test_many_factorizer_games(a: int, p: int, q: int, M: int, N: int): + factorizer_game_check(a, p, q, M, N) diff --git a/tests/unit/blackbox_test.py b/tests/unit/blackbox_test.py index 26b601140..02108fdd8 100644 --- a/tests/unit/blackbox_test.py +++ b/tests/unit/blackbox_test.py @@ -137,7 +137,7 @@ def fn_2mixed_arg_1ret( CompoundType = pt.abi.Tuple4[ pt.abi.Uint64, - pt.abi.Tuple2[pt.abi.Byte, pt.abi.StaticArray[pt.abi.Address, 10]], + pt.abi.Tuple2[pt.abi.Byte, pt.abi.StaticArray[pt.abi.Address, Literal[10]]], pt.abi.Tuple0, pt.abi.Bool, ] @@ -237,9 +237,9 @@ def test_PyTealBlackboxExecutor_is_abi( def test_PyTealBlackboxExecutor_abi_argument_types( mode: pt.Mode, fn: BlackboxWrapper, expected_arg_count: int ): - assert ( - len(PyTealDryRunExecutor(fn, mode).abi_argument_types()) == expected_arg_count - ) + actual = PyTealDryRunExecutor(fn, mode).abi_argument_types() + assert actual is not None + assert len(actual) == expected_arg_count @pytest.mark.parametrize("mode", (pt.Mode.Application, pt.Mode.Signature)) From 322c88e578a08ba55f2e91eca6b0cbcf16f6bcc5 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Sat, 14 May 2022 18:55:46 -0500 Subject: [PATCH 143/170] make tuple generic just as array alread is --- pyteal/ast/abi/tuple.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/pyteal/ast/abi/tuple.py b/pyteal/ast/abi/tuple.py index 3824e701e..a7d09065e 100644 --- a/pyteal/ast/abi/tuple.py +++ b/pyteal/ast/abi/tuple.py @@ -32,8 +32,10 @@ from pyteal.ast.abi.uint import NUM_BITS_IN_BYTE, Uint16 from pyteal.ast.abi.util import substringForDecoding +T = TypeVar("T", bound=BaseType) -def encodeTuple(values: Sequence[BaseType]) -> Expr: + +def encodeTuple(values: Sequence[T]) -> Expr: heads: List[Expr] = [] head_length_static: int = 0 @@ -112,7 +114,7 @@ def encodeTuple(values: Sequence[BaseType]) -> Expr: def indexTuple( - valueTypes: Sequence[TypeSpec], encoded: Expr, index: int, output: BaseType + valueTypes: Sequence[TypeSpec], encoded: Expr, index: int, output: T ) -> Expr: if not (0 <= index < len(valueTypes)): raise ValueError("Index outside of range") @@ -204,7 +206,7 @@ def indexTuple( return output.decode(encoded, startIndex=startIndex, length=length) -class TupleTypeSpec(TypeSpec): +class TupleTypeSpec(TypeSpec, Generic[T]): def __init__(self, *value_type_specs: TypeSpec) -> None: super().__init__() self.value_specs = list(value_type_specs) @@ -244,14 +246,11 @@ def __str__(self) -> str: TupleTypeSpec.__module__ = "pyteal" -T = TypeVar("T", bound="Tuple") - - -class Tuple(BaseType): +class Tuple(BaseType, Generic[T]): def __init__(self, tuple_type_spec: TupleTypeSpec) -> None: super().__init__(tuple_type_spec) - def type_spec(self) -> TupleTypeSpec: + def type_spec(self) -> TupleTypeSpec[T]: return cast(TupleTypeSpec, super().type_spec()) def decode( @@ -268,7 +267,7 @@ def decode( return self.stored_value.store(extracted) @overload - def set(self, *values: BaseType) -> Expr: + def set(self, *values: T) -> Expr: ... @overload @@ -308,10 +307,10 @@ def __getitem__(self, index: int) -> "TupleElement": Tuple.__module__ = "pyteal" -class TupleElement(ComputedValue[BaseType]): +class TupleElement(ComputedValue[T]): """Represents the extraction of a specific element from a Tuple.""" - def __init__(self, tuple: Tuple, index: int) -> None: + def __init__(self, tuple: Tuple[T], index: int) -> None: super().__init__() self.tuple = tuple self.index = index @@ -319,7 +318,7 @@ def __init__(self, tuple: Tuple, index: int) -> None: def produced_type_spec(self) -> TypeSpec: return self.tuple.type_spec().value_type_specs()[self.index] - def store_into(self, output: BaseType) -> Expr: + def store_into(self, output: T) -> Expr: return indexTuple( self.tuple.type_spec().value_type_specs(), self.tuple.encode(), From bb43bfe53888c18fc77f9ff3c408310463f27750 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Sun, 15 May 2022 11:59:04 -0500 Subject: [PATCH 144/170] abi-round-trip test --- tests/abi_roundtrip.py | 107 +++ tests/blackbox.py | 45 +- tests/integration/abi_roundtrip_test.py | 208 +++--- .../teal/roundtrip/app_roundtrip_().teal | 38 + .../teal/roundtrip/app_roundtrip_(bool).teal | 61 ++ .../app_roundtrip_(bool,uint64,uint32).teal | 106 +++ .../teal/roundtrip/app_roundtrip_(byte).teal | 62 ++ .../app_roundtrip_(byte,bool,uint64).teal | 107 +++ .../roundtrip/app_roundtrip_(uint16).teal | 61 ++ .../app_roundtrip_(uint16,uint8,byte).teal | 113 +++ .../roundtrip/app_roundtrip_(uint32).teal | 61 ++ .../app_roundtrip_(uint32,uint16,uint8).teal | 112 +++ .../roundtrip/app_roundtrip_(uint64).teal | 55 ++ .../app_roundtrip_(uint64,uint32,uint16).teal | 106 +++ .../teal/roundtrip/app_roundtrip_(uint8).teal | 62 ++ .../app_roundtrip_(uint8,byte,bool).teal | 113 +++ .../teal/roundtrip/app_roundtrip_address.teal | 498 +++++++++++++ .../teal/roundtrip/app_roundtrip_bool.teal | 52 ++ .../teal/roundtrip/app_roundtrip_bool[1].teal | 61 ++ .../roundtrip/app_roundtrip_bool[42].teal | 471 ++++++++++++ .../roundtrip/app_roundtrip_bool[]_<0>.teal | 92 +++ .../roundtrip/app_roundtrip_bool[]_<1>.teal | 117 +++ .../roundtrip/app_roundtrip_bool[]_<42>.teal | 609 ++++++++++++++++ .../teal/roundtrip/app_roundtrip_byte.teal | 55 ++ .../roundtrip/app_roundtrip_string_<0>.teal | 92 +++ .../roundtrip/app_roundtrip_string_<13>.teal | 312 ++++++++ .../roundtrip/app_roundtrip_string_<1>.teal | 120 +++ .../teal/roundtrip/app_roundtrip_uint16.teal | 52 ++ .../teal/roundtrip/app_roundtrip_uint32.teal | 52 ++ .../teal/roundtrip/app_roundtrip_uint64.teal | 44 ++ .../roundtrip/app_roundtrip_uint64[1].teal | 58 ++ .../roundtrip/app_roundtrip_uint64[42].teal | 550 ++++++++++++++ .../roundtrip/app_roundtrip_uint64[]_<0>.teal | 92 +++ .../roundtrip/app_roundtrip_uint64[]_<1>.teal | 114 +++ .../app_roundtrip_uint64[]_<42>.teal | 688 ++++++++++++++++++ .../teal/roundtrip/app_roundtrip_uint8.teal | 55 ++ 36 files changed, 5475 insertions(+), 126 deletions(-) create mode 100644 tests/abi_roundtrip.py create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_().teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_(bool).teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_(bool,uint64,uint32).teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_(byte).teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_(byte,bool,uint64).teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_(uint16).teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_(uint16,uint8,byte).teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_(uint32).teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_(uint32,uint16,uint8).teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_(uint64).teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_(uint64,uint32,uint16).teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_(uint8).teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_(uint8,byte,bool).teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_address.teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_bool.teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_bool[1].teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_bool[42].teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_bool[]_<0>.teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_bool[]_<1>.teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_bool[]_<42>.teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_byte.teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_string_<0>.teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_string_<13>.teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_string_<1>.teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_uint16.teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_uint32.teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_uint64.teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_uint64[1].teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_uint64[42].teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_uint64[]_<0>.teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_uint64[]_<1>.teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_uint64[]_<42>.teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_uint8.teal diff --git a/tests/abi_roundtrip.py b/tests/abi_roundtrip.py new file mode 100644 index 000000000..c89fd297b --- /dev/null +++ b/tests/abi_roundtrip.py @@ -0,0 +1,107 @@ +from typing import Generic, TypeVar + +import pyteal as pt +from pyteal import abi + +from tests.blackbox import Blackbox, BlackboxWrapper, PyTealDryRunExecutor + +T = TypeVar("T", bound=abi.BaseType) + +DEFAULT_DYNAMIC_ARRAY_LENGTH = 3 + + +class ABIRoundtrip(Generic[T]): + def __init__(self, t: type[T], length: int | None = None): + self.annotation: type[T] = t + self.type_spec: abi.TypeSpec = abi.type_spec_from_annotation(self.annotation) + self.instance: abi.BaseType = self.type_spec.new_instance() + + self.length: int | None = length + + def pytealer(self) -> PyTealDryRunExecutor: + roundtrip = self.roundtrip_factory() + return PyTealDryRunExecutor(roundtrip, pt.Mode.Application) + + def roundtrip_factory(self) -> BlackboxWrapper: + comp = self.complement_factory() + + ann_out = abi.Tuple3[self.annotation, self.annotation, self.annotation] # type: ignore[misc,name-defined] + + @Blackbox(input_types=[None]) + @pt.ABIReturnSubroutine + def round_tripper(x: self.annotation, *, output: ann_out): # type: ignore[name-defined] + y = abi.make(self.annotation) + z = abi.make(self.annotation) + return pt.Seq(y.set(comp(x)), z.set(comp(y)), output.set(x, y, z)) # type: ignore[attr-defined] + + return round_tripper + + def complement_factory(self) -> pt.ABIReturnSubroutine: + if isinstance(self.type_spec, abi.BoolTypeSpec): + return self.bool_comp_factory() + if isinstance(self.type_spec, abi.UintTypeSpec): + return self.numerical_comp_factory() + if isinstance(self.type_spec, abi.TupleTypeSpec): + return self.tuple_comp_factory() + if isinstance(self.type_spec, abi.ArrayTypeSpec): + return self.array_comp_factory() + + raise ValueError(f"uh-oh!!! didn't handle type {self.instance}") + + def bool_comp_factory(self) -> pt.ABIReturnSubroutine: + @pt.ABIReturnSubroutine + def bool_comp(x: abi.Bool, *, output: abi.Bool): + return output.set(pt.Not(x.get())) + + return bool_comp + + @classmethod + def max_int(cls, bit_size): + return (1 << bit_size) - 1 + + def numerical_comp_factory(self) -> pt.ABIReturnSubroutine: + @pt.ABIReturnSubroutine + def numerical_comp(x: self.annotation, *, output: self.annotation): # type: ignore[name-defined] + max_uint = pt.Int(self.max_int(self.type_spec.bit_size())) # type: ignore[attr-defined] + return output.set(max_uint - x.get()) + + return numerical_comp + + def tuple_comp_factory(self) -> pt.ABIReturnSubroutine: + value_type_specs: list[abi.TypeSpec] = self.type_spec.value_type_specs() # type: ignore[attr-defined] + insts = [vts.new_instance() for vts in value_type_specs] + roundtrips = [ABIRoundtrip(type(inst)) for inst in insts] + + @pt.ABIReturnSubroutine + def tuple_complement(x: self.annotation, *, output: self.annotation): # type: ignore[name-defined] + setters = [inst.set(x[i]) for i, inst in enumerate(insts)] # type: ignore[attr-defined] + comp_funcs = [rtrip.complement_factory() for rtrip in roundtrips] + compers = [inst.set(comp_funcs[i](inst)) for i, inst in enumerate(insts)] # type: ignore[attr-defined] + return pt.Seq(*(setters + compers + [output.set(*insts)])) + + return tuple_complement + + def array_comp_factory(self) -> pt.ABIReturnSubroutine: + """ + When the length has not been provided for a dynamic array, + default to DEFAULT_DYNAMIC_ARRAY_LENGTH + """ + if self.length is not None: + assert self.type_spec.is_length_dynamic() # type: ignore[attr-defined] + elif not self.type_spec.is_length_dynamic(): # type: ignore[attr-defined] + self.length = self.type_spec.length_static() # type: ignore[attr-defined] + else: + self.length = DEFAULT_DYNAMIC_ARRAY_LENGTH + + internal_type_spec = self.type_spec.value_type_spec() # type: ignore[attr-defined] + internal_ann = type(internal_type_spec.new_instance()) + comp_func = ABIRoundtrip(internal_ann).complement_factory() + + @pt.ABIReturnSubroutine + def array_complement(x: self.annotation, *, output: self.annotation): # type: ignore[name-defined] + insts = [internal_type_spec.new_instance() for _ in range(self.length)] # type: ignore[arg-type] + setters = [inst.set(x[i]) for i, inst in enumerate(insts)] + compers = [inst.set(comp_func(inst)) for inst in insts] + return pt.Seq(*(setters + compers + [output.set(insts)])) + + return array_complement diff --git a/tests/blackbox.py b/tests/blackbox.py index 2d6a0e293..39cd5aaff 100644 --- a/tests/blackbox.py +++ b/tests/blackbox.py @@ -1,4 +1,4 @@ -from typing import Callable, Generic, Sequence, TypeVar +from typing import Callable, Generic, Sequence, TypeVar, cast from dataclasses import dataclass import algosdk.abi @@ -6,7 +6,8 @@ from graviton import blackbox from graviton.blackbox import DryRunInspector, DryRunExecutor -from graviton.dryrun import ZERO_ADDRESS + +from pyteal.ast.subroutine import OutputKwArgInfo from pyteal import ( abi, @@ -57,9 +58,9 @@ def __init__( ): subr.subroutine._validate(input_types=input_types) self.subroutine: SubroutineFnWrapper | ABIReturnSubroutine = subr - self.input_types: list[TealType | abi.TypeSpec] = self._fill(input_types) + self.input_types: list[TealType | abi.TypeSpec | None] = self._fill(input_types) - def __call__(self, *args: Expr | ScratchVar, **kwargs) -> Expr: + def __call__(self, *args: Expr | ScratchVar, **kwargs) -> Expr | abi.ReturnedValue: return self.subroutine(*args, **kwargs) def name(self) -> str: @@ -67,10 +68,10 @@ def name(self) -> str: def _fill( self, input_types: list[TealType | None] - ) -> list[TealType | abi.TypeSpec]: + ) -> list[TealType | abi.TypeSpec | None]: match self.subroutine: case SubroutineFnWrapper(): - return input_types + return cast(list[TealType | abi.TypeSpec | None], input_types) case ABIReturnSubroutine(): args = self.subroutine.subroutine.arguments() abis = self.subroutine.subroutine.abi_args @@ -133,17 +134,16 @@ def decorator_blackbox(func: SubroutineFnWrapper | ABIReturnSubroutine): # ---- API ---- # -def _unknown_mode_exception(mode: Mode) -> Exception: - return Exception(f"Unknown mode {mode} of type {type(mode)}") +Output = TypeVar("Output") -Output = TypeVar("Output") +Lazy = Callable[[], Output] @dataclass(frozen=True) -class _match_mode(Generic[Output]): - app_case: Callable[[], Output] - signature_case: Callable[[], Output] +class _MatchMode(Generic[Output]): + app_case: Lazy + signature_case: Lazy def __call__(self, mode: Mode, *args, **kwargs) -> Output: match mode: @@ -156,7 +156,7 @@ def __call__(self, mode: Mode, *args, **kwargs) -> Output: def mode_to_execution_mode(mode: Mode) -> blackbox.ExecutionMode: - return _match_mode( + return _MatchMode( app_case=lambda: blackbox.ExecutionMode.Application, signature_case=lambda: blackbox.ExecutionMode.Signature, )(mode) @@ -208,12 +208,11 @@ def abi_return_type(self) -> None | algosdk.abi.ABIType: if not self.is_abi(): return None - if not self.subr.subroutine.output_kwarg_info: + out_info = getattr(self.subr.subroutine, "output_kwarg_info") + if not out_info: return None - return abi.algosdk_from_type_spec( - self.subr.subroutine.output_kwarg_info.abi_type - ) + return abi.algosdk_from_type_spec(cast(OutputKwArgInfo, out_info).abi_type) def program(self) -> Expr: """Get ready-to-compile PyTeal program from Subroutines and ABIReturnSubroutines @@ -393,7 +392,7 @@ def approval(): return approval def compile(self, version: int, assemble_constants: bool = False) -> str: - return _match_mode( + return _MatchMode( app_case=lambda: compileTeal( self.program(), self.mode, @@ -412,16 +411,14 @@ def dryrun_on_sequence( self, inputs: list[Sequence[str | int]], compiler_version=6, - sender: str = ZERO_ADDRESS, ) -> list[DryRunInspector]: - return _match_mode( + return _MatchMode( app_case=lambda: DryRunExecutor.dryrun_app_on_sequence( algod_with_assertion(), self.compile(compiler_version), inputs, self.abi_argument_types(), self.abi_return_type(), - sender, ), signature_case=lambda: DryRunExecutor.dryrun_logicsig_on_sequence( algod_with_assertion(), @@ -429,7 +426,6 @@ def dryrun_on_sequence( inputs, self.abi_argument_types(), self.abi_return_type(), - sender, ), )(self.mode) @@ -437,16 +433,14 @@ def dryrun( self, inputs: Sequence[str | int], compiler_version=6, - sender: str = ZERO_ADDRESS, ) -> DryRunInspector: - return _match_mode( + return _MatchMode( app_case=lambda: DryRunExecutor.dryrun_app( algod_with_assertion(), self.compile(compiler_version), inputs, self.abi_argument_types(), self.abi_return_type(), - sender, ), signature_case=lambda: DryRunExecutor.dryrun_logicsig( algod_with_assertion(), @@ -454,6 +448,5 @@ def dryrun( inputs, self.abi_argument_types(), self.abi_return_type(), - sender, ), )(self.mode) diff --git a/tests/integration/abi_roundtrip_test.py b/tests/integration/abi_roundtrip_test.py index b54509ede..eec7bc75e 100644 --- a/tests/integration/abi_roundtrip_test.py +++ b/tests/integration/abi_roundtrip_test.py @@ -1,139 +1,147 @@ from pathlib import Path import pytest -from typing import Callable, Literal, TypeVar +from typing import Literal import algosdk.abi from pyteal import abi import pyteal as pt -from tests.blackbox import Blackbox, PyTealDryRunExecutor +# from tests.blackbox import Blackbox, PyTealDryRunExecutor from tests.compile_asserts import assert_teal_as_expected -T = TypeVar("T", bound=abi.BaseType) +from tests.abi_roundtrip import ABIRoundtrip PATH = Path.cwd() / "tests" / "integration" FIXTURES = PATH / "teal" GENERATED = PATH / "generated" -def max_int(bit_size): - return (1 << bit_size) - 1 +# def max_int(bit_size): +# return (1 << bit_size) - 1 -@pt.ABIReturnSubroutine -def bool_comp(x: abi.Bool, *, output: abi.Bool): - return output.set(pt.Not(x.get())) +# @pt.ABIReturnSubroutine +# def bool_comp(x: abi.Bool, *, output: abi.Bool): +# return output.set(pt.Not(x.get())) -def numerical_comp_factory(t: type[T], bit_size: int) -> Callable: - @pt.ABIReturnSubroutine - def func(x: t, *, output: t): - max_uint = pt.Int(max_int(bit_size)) - return output.set(max_uint - x.get()) +# def numerical_comp_factory(t: type[T], bit_size: int) -> Callable: +# @pt.ABIReturnSubroutine +# def func(x: t, *, output: t): # type: ignore[valid-type] +# x = cast(abi.BaseType, x) +# max_uint = pt.Int(max_int(bit_size)) +# return output.set(max_uint - x.get()) # type: ignore[attr-defined] - return func +# return func -def tuple_comp_factory(t: type[T], value_type_specs: list[abi.TypeSpec]) -> Callable: - @pt.ABIReturnSubroutine - def tuple_complement(x: t, *, output: t): - value_types = [vts.new_instance() for vts in value_type_specs] - setters = [vts.set(x[i]) for i, vts in enumerate(value_types)] - comp_funcs = [complement_factory(type(vts), -1) for vts in value_types] - compers = [vts.set(comp_funcs[i](vts)) for i, vts in enumerate(value_types)] - return pt.Seq(*(setters + compers + [output.set(*value_types)])) +# def tuple_comp_factory(t: type[T], value_type_specs: list[abi.TypeSpec]) -> Callable: +# def fix_type(e): +# return cast(abi.Tuple[T], e) - return tuple_complement +# @pt.ABIReturnSubroutine +# def tuple_complement(x: t, *, output: t): # type: ignore[valid-type] +# value_types = [vts.new_instance() for vts in value_type_specs] +# setters = [ +# fix_type(vts).set(fix_type(x)[i]) for i, vts in enumerate(value_types) +# ] +# comp_funcs = [ +# complement_factory(type(fix_type(vts)), -1) for vts in value_types +# ] +# compers = [vts.set(comp_funcs[i](vts)) for i, vts in enumerate(value_types)] +# return pt.Seq(*(setters + compers + [output.set(*value_types)])) +# return tuple_complement -def array_comp_factory( - t: type[T], value_type_spec: abi.TypeSpec, length: int -) -> Callable: - comp_func = complement_factory(type(value_type_spec.new_instance()), -1) - ts = abi.type_spec_from_annotation(t) - if length != -1: - assert ts.is_length_dynamic() - else: - length = ts.length_static() - @pt.ABIReturnSubroutine - def array_complement(x: t, *, output: t): - value_types = [value_type_spec.new_instance() for _ in range(length)] - setters = [vts.set(x[i]) for i, vts in enumerate(value_types)] - compers = [vts.set(comp_func(vts)) for vts in value_types] - return pt.Seq(*(setters + compers + [output.set(value_types)])) +# def array_comp_factory( +# t: type[T], value_type_spec: abi.TypeSpec, length: int +# ) -> Callable: +# comp_func = complement_factory(type(value_type_spec.new_instance()), -1) +# ts = abi.type_spec_from_annotation(t) +# if length != -1: +# assert ts.is_length_dynamic() +# else: +# length = ts.length_static() - return array_complement +# @pt.ABIReturnSubroutine +# def array_complement(x: t, *, output: t): +# value_types = [value_type_spec.new_instance() for _ in range(length)] +# setters = [vts.set(x[i]) for i, vts in enumerate(value_types)] +# compers = [vts.set(comp_func(vts)) for vts in value_types] +# return pt.Seq(*(setters + compers + [output.set(value_types)])) +# return array_complement -def complement_factory(t: T, dynamic_length: int) -> Callable: - ts = abi.type_spec_from_annotation(t) - if isinstance(ts, abi.BoolTypeSpec): - return bool_comp - if isinstance(ts, abi.UintTypeSpec): - return numerical_comp_factory(t, ts.bit_size()) - if isinstance(ts, abi.TupleTypeSpec): - return tuple_comp_factory(t, ts.value_type_specs()) - if isinstance(ts, abi.ArrayTypeSpec): - return array_comp_factory(t, ts.value_type_spec(), dynamic_length) - raise ValueError(f"uh-oh!!! didn't handle type {t}") +# def complement_factory(t: T, dynamic_length: int) -> Callable: +# ts = abi.type_spec_from_annotation(t) +# if isinstance(ts, abi.BoolTypeSpec): +# return bool_comp +# if isinstance(ts, abi.UintTypeSpec): +# return numerical_comp_factory(t, ts.bit_size()) +# if isinstance(ts, abi.TupleTypeSpec): +# return tuple_comp_factory(t, ts.value_type_specs()) +# if isinstance(ts, abi.ArrayTypeSpec): +# return array_comp_factory(t, ts.value_type_spec(), dynamic_length) +# raise ValueError(f"uh-oh!!! didn't handle type {t}") -def roundtrip_factory(t: type[T], dynamic_length: int) -> Callable: - comp = complement_factory(t, dynamic_length) - @Blackbox(input_types=[None]) - @pt.ABIReturnSubroutine - def round_tripper(x: t, *, output: abi.Tuple2[t, t]): - y = abi.make(t) - z = abi.make(t) - return pt.Seq(y.set(comp(x)), z.set(comp(y)), output.set(y, z)) +# def roundtrip_factory(t: type[T], dynamic_length: int) -> Callable: +# comp = complement_factory(t, dynamic_length) - return round_tripper +# @Blackbox(input_types=[None]) +# @pt.ABIReturnSubroutine +# def round_tripper(x: t, *, output: abi.Tuple2[t, t]): +# y = abi.make(t) +# z = abi.make(t) +# return pt.Seq(y.set(comp(x)), z.set(comp(y)), output.set(y, z)) +# return round_tripper -def roundtrip_pytealer(t: type[T], dynamic_length: int): - roundtrip = roundtrip_factory(t, dynamic_length) - return PyTealDryRunExecutor(roundtrip, pt.Mode.Application) + +# def roundtrip_pytealer(t: type[T], dynamic_length: int): +# roundtrip = roundtrip_factory(t, dynamic_length) +# return PyTealDryRunExecutor(roundtrip, pt.Mode.Application) ABI_TYPES = [ - # abi.Address, - # abi.Bool, - # abi.Byte, - # (abi.String, 0), - # (abi.String, 1), - # (abi.String, 13), - # abi.Uint8, - # abi.Uint16, - # abi.Uint32, - # abi.Uint64, - # abi.Tuple0, - # abi.Tuple1[abi.Bool], - # abi.Tuple1[abi.Byte], - # abi.Tuple1[abi.Uint8], - # abi.Tuple1[abi.Uint16], - # abi.Tuple1[abi.Uint32], - # abi.Tuple1[abi.Uint64], - # abi.Tuple3[abi.Bool, abi.Uint64, abi.Uint32], - # abi.Tuple3[abi.Byte, abi.Bool, abi.Uint64], - # abi.Tuple3[abi.Uint8, abi.Byte, abi.Bool], - # abi.Tuple3[abi.Uint16, abi.Uint8, abi.Byte], - # abi.Tuple3[abi.Uint32, abi.Uint16, abi.Uint8], - # abi.Tuple3[abi.Uint64, abi.Uint32, abi.Uint16], - # abi.StaticArray[abi.Bool, Literal[1]], - # abi.StaticArray[abi.Bool, Literal[42]], - # abi.StaticArray[abi.Uint64, Literal[1]], - # abi.StaticArray[abi.Uint64, Literal[42]], - # (abi.DynamicArray[abi.Bool], 0), - # (abi.DynamicArray[abi.Bool], 1), - # (abi.DynamicArray[abi.Bool], 42), - # (abi.DynamicArray[abi.Uint64], 0), - # (abi.DynamicArray[abi.Uint64], 1), - # (abi.DynamicArray[abi.Uint64], 42), - abi.StaticArray[abi.Tuple1[abi.Bool], Literal[10]], + abi.Address, + abi.Bool, + abi.Byte, + (abi.String, 0), + (abi.String, 1), + (abi.String, 13), + abi.Uint8, + abi.Uint16, + abi.Uint32, + abi.Uint64, + abi.Tuple0, + abi.Tuple1[abi.Bool], + abi.Tuple1[abi.Byte], + abi.Tuple1[abi.Uint8], + abi.Tuple1[abi.Uint16], + abi.Tuple1[abi.Uint32], + abi.Tuple1[abi.Uint64], + abi.Tuple3[abi.Bool, abi.Uint64, abi.Uint32], + abi.Tuple3[abi.Byte, abi.Bool, abi.Uint64], + abi.Tuple3[abi.Uint8, abi.Byte, abi.Bool], + abi.Tuple3[abi.Uint16, abi.Uint8, abi.Byte], + abi.Tuple3[abi.Uint32, abi.Uint16, abi.Uint8], + abi.Tuple3[abi.Uint64, abi.Uint32, abi.Uint16], + abi.StaticArray[abi.Bool, Literal[1]], + abi.StaticArray[abi.Bool, Literal[42]], + abi.StaticArray[abi.Uint64, Literal[1]], + abi.StaticArray[abi.Uint64, Literal[42]], + (abi.DynamicArray[abi.Bool], 0), + (abi.DynamicArray[abi.Bool], 1), + (abi.DynamicArray[abi.Bool], 42), + (abi.DynamicArray[abi.Uint64], 0), + (abi.DynamicArray[abi.Uint64], 1), + (abi.DynamicArray[abi.Uint64], 42), + # abi.StaticArray[abi.Tuple1[abi.Bool], Literal[10]], # ( # abi.DynamicArray[ # abi.Tuple4[ @@ -152,26 +160,26 @@ def roundtrip_pytealer(t: type[T], dynamic_length: int): def test_pure_compilation(abi_type): print(f"Testing {abi_type=}") - dynamic_length = -1 + dynamic_length = None if isinstance(abi_type, tuple): abi_type, dynamic_length = abi_type sdk_abi_type = abi.algosdk_from_annotation(abi_type) - roundtripper = roundtrip_pytealer(abi_type, dynamic_length) + roundtripper = ABIRoundtrip(abi_type, dynamic_length).pytealer() abi_arg_types = roundtripper.abi_argument_types() assert [sdk_abi_type] == abi_arg_types abi_ret_type = roundtripper.abi_return_type() - assert algosdk.abi.TupleType([sdk_abi_type] * 2) == abi_ret_type + assert algosdk.abi.TupleType([sdk_abi_type] * 3) == abi_ret_type program = roundtripper.program() teal = pt.compileTeal(program, pt.Mode.Application, version=6) filename = ( f"app_roundtrip_{sdk_abi_type}" - + (f"_<{dynamic_length}>" if dynamic_length >= 0 else "") + + ("" if dynamic_length is None else f"_<{dynamic_length}>") + ".teal" ) tealdir = GENERATED / "roundtrip" diff --git a/tests/integration/teal/roundtrip/app_roundtrip_().teal b/tests/integration/teal/roundtrip/app_roundtrip_().teal new file mode 100644 index 000000000..b59fbd275 --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_().teal @@ -0,0 +1,38 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 1 +load 1 +callsub roundtripper_1 +store 0 +byte 0x151F7C75 +load 0 +concat +log +int 1 +return + +// tuple_complement +tuplecomplement_0: +store 6 +byte "" +store 7 +load 7 +retsub + +// round_tripper +roundtripper_1: +store 2 +load 2 +callsub tuplecomplement_0 +store 4 +load 4 +callsub tuplecomplement_0 +store 5 +load 2 +load 4 +concat +load 5 +concat +store 3 +load 3 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(bool).teal b/tests/integration/teal/roundtrip/app_roundtrip_(bool).teal new file mode 100644 index 000000000..6344eb38b --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_(bool).teal @@ -0,0 +1,61 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 2 +load 2 +callsub roundtripper_1 +store 1 +byte 0x151F7C75 +load 1 +concat +log +int 1 +return + +// tuple_complement +tuplecomplement_0: +store 7 +load 7 +int 0 +getbit +store 0 +load 0 +callsub boolcomp_2 +store 0 +byte 0x00 +int 0 +load 0 +setbit +store 8 +load 8 +retsub + +// round_tripper +roundtripper_1: +store 3 +load 3 +callsub tuplecomplement_0 +store 5 +load 5 +callsub tuplecomplement_0 +store 6 +load 3 +load 5 +concat +load 6 +concat +store 4 +load 4 +retsub + +// bool_comp +boolcomp_2: +store 9 +load 9 +! +store 10 +load 10 +int 2 +< +assert +load 10 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(bool,uint64,uint32).teal b/tests/integration/teal/roundtrip/app_roundtrip_(bool,uint64,uint32).teal new file mode 100644 index 000000000..7f8aabc87 --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_(bool,uint64,uint32).teal @@ -0,0 +1,106 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 4 +load 4 +callsub roundtripper_1 +store 3 +byte 0x151F7C75 +load 3 +concat +log +int 1 +return + +// tuple_complement +tuplecomplement_0: +store 9 +load 9 +int 0 +getbit +store 0 +load 9 +int 1 +extract_uint64 +store 1 +load 9 +int 9 +extract_uint32 +store 2 +load 0 +callsub boolcomp_2 +store 0 +load 1 +callsub numericalcomp_3 +store 1 +load 2 +callsub numericalcomp_4 +store 2 +byte 0x00 +int 0 +load 0 +setbit +load 1 +itob +concat +load 2 +itob +extract 4 0 +concat +store 10 +load 10 +retsub + +// round_tripper +roundtripper_1: +store 5 +load 5 +callsub tuplecomplement_0 +store 7 +load 7 +callsub tuplecomplement_0 +store 8 +load 5 +load 7 +concat +load 8 +concat +store 6 +load 6 +retsub + +// bool_comp +boolcomp_2: +store 11 +load 11 +! +store 12 +load 12 +int 2 +< +assert +load 12 +retsub + +// numerical_comp +numericalcomp_3: +store 13 +int 18446744073709551615 +load 13 +- +store 14 +load 14 +retsub + +// numerical_comp +numericalcomp_4: +store 15 +int 4294967295 +load 15 +- +store 16 +load 16 +int 4294967296 +< +assert +load 16 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(byte).teal b/tests/integration/teal/roundtrip/app_roundtrip_(byte).teal new file mode 100644 index 000000000..2a4c05465 --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_(byte).teal @@ -0,0 +1,62 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 2 +load 2 +callsub roundtripper_1 +store 1 +byte 0x151F7C75 +load 1 +concat +log +int 1 +return + +// tuple_complement +tuplecomplement_0: +store 7 +load 7 +int 0 +getbyte +store 0 +load 0 +callsub numericalcomp_2 +store 0 +byte 0x00 +int 0 +load 0 +setbyte +store 8 +load 8 +retsub + +// round_tripper +roundtripper_1: +store 3 +load 3 +callsub tuplecomplement_0 +store 5 +load 5 +callsub tuplecomplement_0 +store 6 +load 3 +load 5 +concat +load 6 +concat +store 4 +load 4 +retsub + +// numerical_comp +numericalcomp_2: +store 9 +int 255 +load 9 +- +store 10 +load 10 +int 256 +< +assert +load 10 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(byte,bool,uint64).teal b/tests/integration/teal/roundtrip/app_roundtrip_(byte,bool,uint64).teal new file mode 100644 index 000000000..cf613fa8c --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_(byte,bool,uint64).teal @@ -0,0 +1,107 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 4 +load 4 +callsub roundtripper_1 +store 3 +byte 0x151F7C75 +load 3 +concat +log +int 1 +return + +// tuple_complement +tuplecomplement_0: +store 9 +load 9 +int 0 +getbyte +store 0 +load 9 +int 8 +getbit +store 1 +load 9 +int 2 +extract_uint64 +store 2 +load 0 +callsub numericalcomp_2 +store 0 +load 1 +callsub boolcomp_3 +store 1 +load 2 +callsub numericalcomp_4 +store 2 +byte 0x00 +int 0 +load 0 +setbyte +byte 0x00 +int 0 +load 1 +setbit +concat +load 2 +itob +concat +store 10 +load 10 +retsub + +// round_tripper +roundtripper_1: +store 5 +load 5 +callsub tuplecomplement_0 +store 7 +load 7 +callsub tuplecomplement_0 +store 8 +load 5 +load 7 +concat +load 8 +concat +store 6 +load 6 +retsub + +// numerical_comp +numericalcomp_2: +store 11 +int 255 +load 11 +- +store 12 +load 12 +int 256 +< +assert +load 12 +retsub + +// bool_comp +boolcomp_3: +store 13 +load 13 +! +store 14 +load 14 +int 2 +< +assert +load 14 +retsub + +// numerical_comp +numericalcomp_4: +store 15 +int 18446744073709551615 +load 15 +- +store 16 +load 16 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(uint16).teal b/tests/integration/teal/roundtrip/app_roundtrip_(uint16).teal new file mode 100644 index 000000000..57d92721d --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_(uint16).teal @@ -0,0 +1,61 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 2 +load 2 +callsub roundtripper_1 +store 1 +byte 0x151F7C75 +load 1 +concat +log +int 1 +return + +// tuple_complement +tuplecomplement_0: +store 7 +load 7 +int 0 +extract_uint16 +store 0 +load 0 +callsub numericalcomp_2 +store 0 +load 0 +itob +extract 6 0 +store 8 +load 8 +retsub + +// round_tripper +roundtripper_1: +store 3 +load 3 +callsub tuplecomplement_0 +store 5 +load 5 +callsub tuplecomplement_0 +store 6 +load 3 +load 5 +concat +load 6 +concat +store 4 +load 4 +retsub + +// numerical_comp +numericalcomp_2: +store 9 +int 65535 +load 9 +- +store 10 +load 10 +int 65536 +< +assert +load 10 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(uint16,uint8,byte).teal b/tests/integration/teal/roundtrip/app_roundtrip_(uint16,uint8,byte).teal new file mode 100644 index 000000000..4af961953 --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_(uint16,uint8,byte).teal @@ -0,0 +1,113 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 4 +load 4 +callsub roundtripper_1 +store 3 +byte 0x151F7C75 +load 3 +concat +log +int 1 +return + +// tuple_complement +tuplecomplement_0: +store 9 +load 9 +int 0 +extract_uint16 +store 0 +load 9 +int 2 +getbyte +store 1 +load 9 +int 3 +getbyte +store 2 +load 0 +callsub numericalcomp_2 +store 0 +load 1 +callsub numericalcomp_3 +store 1 +load 2 +callsub numericalcomp_4 +store 2 +load 0 +itob +extract 6 0 +byte 0x00 +int 0 +load 1 +setbyte +concat +byte 0x00 +int 0 +load 2 +setbyte +concat +store 10 +load 10 +retsub + +// round_tripper +roundtripper_1: +store 5 +load 5 +callsub tuplecomplement_0 +store 7 +load 7 +callsub tuplecomplement_0 +store 8 +load 5 +load 7 +concat +load 8 +concat +store 6 +load 6 +retsub + +// numerical_comp +numericalcomp_2: +store 11 +int 65535 +load 11 +- +store 12 +load 12 +int 65536 +< +assert +load 12 +retsub + +// numerical_comp +numericalcomp_3: +store 13 +int 255 +load 13 +- +store 14 +load 14 +int 256 +< +assert +load 14 +retsub + +// numerical_comp +numericalcomp_4: +store 15 +int 255 +load 15 +- +store 16 +load 16 +int 256 +< +assert +load 16 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(uint32).teal b/tests/integration/teal/roundtrip/app_roundtrip_(uint32).teal new file mode 100644 index 000000000..e8f3179f1 --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_(uint32).teal @@ -0,0 +1,61 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 2 +load 2 +callsub roundtripper_1 +store 1 +byte 0x151F7C75 +load 1 +concat +log +int 1 +return + +// tuple_complement +tuplecomplement_0: +store 7 +load 7 +int 0 +extract_uint32 +store 0 +load 0 +callsub numericalcomp_2 +store 0 +load 0 +itob +extract 4 0 +store 8 +load 8 +retsub + +// round_tripper +roundtripper_1: +store 3 +load 3 +callsub tuplecomplement_0 +store 5 +load 5 +callsub tuplecomplement_0 +store 6 +load 3 +load 5 +concat +load 6 +concat +store 4 +load 4 +retsub + +// numerical_comp +numericalcomp_2: +store 9 +int 4294967295 +load 9 +- +store 10 +load 10 +int 4294967296 +< +assert +load 10 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(uint32,uint16,uint8).teal b/tests/integration/teal/roundtrip/app_roundtrip_(uint32,uint16,uint8).teal new file mode 100644 index 000000000..0194d3410 --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_(uint32,uint16,uint8).teal @@ -0,0 +1,112 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 4 +load 4 +callsub roundtripper_1 +store 3 +byte 0x151F7C75 +load 3 +concat +log +int 1 +return + +// tuple_complement +tuplecomplement_0: +store 9 +load 9 +int 0 +extract_uint32 +store 0 +load 9 +int 4 +extract_uint16 +store 1 +load 9 +int 6 +getbyte +store 2 +load 0 +callsub numericalcomp_2 +store 0 +load 1 +callsub numericalcomp_3 +store 1 +load 2 +callsub numericalcomp_4 +store 2 +load 0 +itob +extract 4 0 +load 1 +itob +extract 6 0 +concat +byte 0x00 +int 0 +load 2 +setbyte +concat +store 10 +load 10 +retsub + +// round_tripper +roundtripper_1: +store 5 +load 5 +callsub tuplecomplement_0 +store 7 +load 7 +callsub tuplecomplement_0 +store 8 +load 5 +load 7 +concat +load 8 +concat +store 6 +load 6 +retsub + +// numerical_comp +numericalcomp_2: +store 11 +int 4294967295 +load 11 +- +store 12 +load 12 +int 4294967296 +< +assert +load 12 +retsub + +// numerical_comp +numericalcomp_3: +store 13 +int 65535 +load 13 +- +store 14 +load 14 +int 65536 +< +assert +load 14 +retsub + +// numerical_comp +numericalcomp_4: +store 15 +int 255 +load 15 +- +store 16 +load 16 +int 256 +< +assert +load 16 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(uint64).teal b/tests/integration/teal/roundtrip/app_roundtrip_(uint64).teal new file mode 100644 index 000000000..2ef9adcec --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_(uint64).teal @@ -0,0 +1,55 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 2 +load 2 +callsub roundtripper_1 +store 1 +byte 0x151F7C75 +load 1 +concat +log +int 1 +return + +// tuple_complement +tuplecomplement_0: +store 7 +load 7 +btoi +store 0 +load 0 +callsub numericalcomp_2 +store 0 +load 0 +itob +store 8 +load 8 +retsub + +// round_tripper +roundtripper_1: +store 3 +load 3 +callsub tuplecomplement_0 +store 5 +load 5 +callsub tuplecomplement_0 +store 6 +load 3 +load 5 +concat +load 6 +concat +store 4 +load 4 +retsub + +// numerical_comp +numericalcomp_2: +store 9 +int 18446744073709551615 +load 9 +- +store 10 +load 10 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(uint64,uint32,uint16).teal b/tests/integration/teal/roundtrip/app_roundtrip_(uint64,uint32,uint16).teal new file mode 100644 index 000000000..7e33bb6ac --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_(uint64,uint32,uint16).teal @@ -0,0 +1,106 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 4 +load 4 +callsub roundtripper_1 +store 3 +byte 0x151F7C75 +load 3 +concat +log +int 1 +return + +// tuple_complement +tuplecomplement_0: +store 9 +load 9 +int 0 +extract_uint64 +store 0 +load 9 +int 8 +extract_uint32 +store 1 +load 9 +int 12 +extract_uint16 +store 2 +load 0 +callsub numericalcomp_2 +store 0 +load 1 +callsub numericalcomp_3 +store 1 +load 2 +callsub numericalcomp_4 +store 2 +load 0 +itob +load 1 +itob +extract 4 0 +concat +load 2 +itob +extract 6 0 +concat +store 10 +load 10 +retsub + +// round_tripper +roundtripper_1: +store 5 +load 5 +callsub tuplecomplement_0 +store 7 +load 7 +callsub tuplecomplement_0 +store 8 +load 5 +load 7 +concat +load 8 +concat +store 6 +load 6 +retsub + +// numerical_comp +numericalcomp_2: +store 11 +int 18446744073709551615 +load 11 +- +store 12 +load 12 +retsub + +// numerical_comp +numericalcomp_3: +store 13 +int 4294967295 +load 13 +- +store 14 +load 14 +int 4294967296 +< +assert +load 14 +retsub + +// numerical_comp +numericalcomp_4: +store 15 +int 65535 +load 15 +- +store 16 +load 16 +int 65536 +< +assert +load 16 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(uint8).teal b/tests/integration/teal/roundtrip/app_roundtrip_(uint8).teal new file mode 100644 index 000000000..2a4c05465 --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_(uint8).teal @@ -0,0 +1,62 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 2 +load 2 +callsub roundtripper_1 +store 1 +byte 0x151F7C75 +load 1 +concat +log +int 1 +return + +// tuple_complement +tuplecomplement_0: +store 7 +load 7 +int 0 +getbyte +store 0 +load 0 +callsub numericalcomp_2 +store 0 +byte 0x00 +int 0 +load 0 +setbyte +store 8 +load 8 +retsub + +// round_tripper +roundtripper_1: +store 3 +load 3 +callsub tuplecomplement_0 +store 5 +load 5 +callsub tuplecomplement_0 +store 6 +load 3 +load 5 +concat +load 6 +concat +store 4 +load 4 +retsub + +// numerical_comp +numericalcomp_2: +store 9 +int 255 +load 9 +- +store 10 +load 10 +int 256 +< +assert +load 10 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(uint8,byte,bool).teal b/tests/integration/teal/roundtrip/app_roundtrip_(uint8,byte,bool).teal new file mode 100644 index 000000000..45b8b2652 --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_(uint8,byte,bool).teal @@ -0,0 +1,113 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 4 +load 4 +callsub roundtripper_1 +store 3 +byte 0x151F7C75 +load 3 +concat +log +int 1 +return + +// tuple_complement +tuplecomplement_0: +store 9 +load 9 +int 0 +getbyte +store 0 +load 9 +int 1 +getbyte +store 1 +load 9 +int 16 +getbit +store 2 +load 0 +callsub numericalcomp_2 +store 0 +load 1 +callsub numericalcomp_3 +store 1 +load 2 +callsub boolcomp_4 +store 2 +byte 0x00 +int 0 +load 0 +setbyte +byte 0x00 +int 0 +load 1 +setbyte +concat +byte 0x00 +int 0 +load 2 +setbit +concat +store 10 +load 10 +retsub + +// round_tripper +roundtripper_1: +store 5 +load 5 +callsub tuplecomplement_0 +store 7 +load 7 +callsub tuplecomplement_0 +store 8 +load 5 +load 7 +concat +load 8 +concat +store 6 +load 6 +retsub + +// numerical_comp +numericalcomp_2: +store 11 +int 255 +load 11 +- +store 12 +load 12 +int 256 +< +assert +load 12 +retsub + +// numerical_comp +numericalcomp_3: +store 13 +int 255 +load 13 +- +store 14 +load 14 +int 256 +< +assert +load 14 +retsub + +// bool_comp +boolcomp_4: +store 15 +load 15 +! +store 16 +load 16 +int 2 +< +assert +load 16 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_address.teal b/tests/integration/teal/roundtrip/app_roundtrip_address.teal new file mode 100644 index 000000000..0bc930d45 --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_address.teal @@ -0,0 +1,498 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 1 +load 1 +callsub roundtripper_2 +store 0 +byte 0x151F7C75 +load 0 +concat +log +int 1 +return + +// numerical_comp +numericalcomp_0: +store 40 +int 255 +load 40 +- +store 41 +load 41 +int 256 +< +assert +load 41 +retsub + +// array_complement +arraycomplement_1: +store 6 +load 6 +int 1 +int 0 +* +getbyte +store 8 +load 6 +int 1 +int 1 +* +getbyte +store 9 +load 6 +int 1 +int 2 +* +getbyte +store 10 +load 6 +int 1 +int 3 +* +getbyte +store 11 +load 6 +int 1 +int 4 +* +getbyte +store 12 +load 6 +int 1 +int 5 +* +getbyte +store 13 +load 6 +int 1 +int 6 +* +getbyte +store 14 +load 6 +int 1 +int 7 +* +getbyte +store 15 +load 6 +int 1 +int 8 +* +getbyte +store 16 +load 6 +int 1 +int 9 +* +getbyte +store 17 +load 6 +int 1 +int 10 +* +getbyte +store 18 +load 6 +int 1 +int 11 +* +getbyte +store 19 +load 6 +int 1 +int 12 +* +getbyte +store 20 +load 6 +int 1 +int 13 +* +getbyte +store 21 +load 6 +int 1 +int 14 +* +getbyte +store 22 +load 6 +int 1 +int 15 +* +getbyte +store 23 +load 6 +int 1 +int 16 +* +getbyte +store 24 +load 6 +int 1 +int 17 +* +getbyte +store 25 +load 6 +int 1 +int 18 +* +getbyte +store 26 +load 6 +int 1 +int 19 +* +getbyte +store 27 +load 6 +int 1 +int 20 +* +getbyte +store 28 +load 6 +int 1 +int 21 +* +getbyte +store 29 +load 6 +int 1 +int 22 +* +getbyte +store 30 +load 6 +int 1 +int 23 +* +getbyte +store 31 +load 6 +int 1 +int 24 +* +getbyte +store 32 +load 6 +int 1 +int 25 +* +getbyte +store 33 +load 6 +int 1 +int 26 +* +getbyte +store 34 +load 6 +int 1 +int 27 +* +getbyte +store 35 +load 6 +int 1 +int 28 +* +getbyte +store 36 +load 6 +int 1 +int 29 +* +getbyte +store 37 +load 6 +int 1 +int 30 +* +getbyte +store 38 +load 6 +int 1 +int 31 +* +getbyte +store 39 +load 8 +callsub numericalcomp_0 +store 8 +load 9 +callsub numericalcomp_0 +store 9 +load 10 +callsub numericalcomp_0 +store 10 +load 11 +callsub numericalcomp_0 +store 11 +load 12 +callsub numericalcomp_0 +store 12 +load 13 +callsub numericalcomp_0 +store 13 +load 14 +callsub numericalcomp_0 +store 14 +load 15 +callsub numericalcomp_0 +store 15 +load 16 +callsub numericalcomp_0 +store 16 +load 17 +callsub numericalcomp_0 +store 17 +load 18 +callsub numericalcomp_0 +store 18 +load 19 +callsub numericalcomp_0 +store 19 +load 20 +callsub numericalcomp_0 +store 20 +load 21 +callsub numericalcomp_0 +store 21 +load 22 +callsub numericalcomp_0 +store 22 +load 23 +callsub numericalcomp_0 +store 23 +load 24 +callsub numericalcomp_0 +store 24 +load 25 +callsub numericalcomp_0 +store 25 +load 26 +callsub numericalcomp_0 +store 26 +load 27 +callsub numericalcomp_0 +store 27 +load 28 +callsub numericalcomp_0 +store 28 +load 29 +callsub numericalcomp_0 +store 29 +load 30 +callsub numericalcomp_0 +store 30 +load 31 +callsub numericalcomp_0 +store 31 +load 32 +callsub numericalcomp_0 +store 32 +load 33 +callsub numericalcomp_0 +store 33 +load 34 +callsub numericalcomp_0 +store 34 +load 35 +callsub numericalcomp_0 +store 35 +load 36 +callsub numericalcomp_0 +store 36 +load 37 +callsub numericalcomp_0 +store 37 +load 38 +callsub numericalcomp_0 +store 38 +load 39 +callsub numericalcomp_0 +store 39 +byte 0x00 +int 0 +load 8 +setbyte +byte 0x00 +int 0 +load 9 +setbyte +concat +byte 0x00 +int 0 +load 10 +setbyte +concat +byte 0x00 +int 0 +load 11 +setbyte +concat +byte 0x00 +int 0 +load 12 +setbyte +concat +byte 0x00 +int 0 +load 13 +setbyte +concat +byte 0x00 +int 0 +load 14 +setbyte +concat +byte 0x00 +int 0 +load 15 +setbyte +concat +byte 0x00 +int 0 +load 16 +setbyte +concat +byte 0x00 +int 0 +load 17 +setbyte +concat +byte 0x00 +int 0 +load 18 +setbyte +concat +byte 0x00 +int 0 +load 19 +setbyte +concat +byte 0x00 +int 0 +load 20 +setbyte +concat +byte 0x00 +int 0 +load 21 +setbyte +concat +byte 0x00 +int 0 +load 22 +setbyte +concat +byte 0x00 +int 0 +load 23 +setbyte +concat +byte 0x00 +int 0 +load 24 +setbyte +concat +byte 0x00 +int 0 +load 25 +setbyte +concat +byte 0x00 +int 0 +load 26 +setbyte +concat +byte 0x00 +int 0 +load 27 +setbyte +concat +byte 0x00 +int 0 +load 28 +setbyte +concat +byte 0x00 +int 0 +load 29 +setbyte +concat +byte 0x00 +int 0 +load 30 +setbyte +concat +byte 0x00 +int 0 +load 31 +setbyte +concat +byte 0x00 +int 0 +load 32 +setbyte +concat +byte 0x00 +int 0 +load 33 +setbyte +concat +byte 0x00 +int 0 +load 34 +setbyte +concat +byte 0x00 +int 0 +load 35 +setbyte +concat +byte 0x00 +int 0 +load 36 +setbyte +concat +byte 0x00 +int 0 +load 37 +setbyte +concat +byte 0x00 +int 0 +load 38 +setbyte +concat +byte 0x00 +int 0 +load 39 +setbyte +concat +store 7 +load 7 +retsub + +// round_tripper +roundtripper_2: +store 2 +load 2 +callsub arraycomplement_1 +store 4 +load 4 +callsub arraycomplement_1 +store 5 +load 2 +load 4 +concat +load 5 +concat +store 3 +load 3 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_bool.teal b/tests/integration/teal/roundtrip/app_roundtrip_bool.teal new file mode 100644 index 000000000..bc91494eb --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_bool.teal @@ -0,0 +1,52 @@ +#pragma version 6 +txna ApplicationArgs 0 +int 0 +int 8 +* +getbit +store 1 +load 1 +callsub roundtripper_1 +store 0 +byte 0x151F7C75 +load 0 +concat +log +int 1 +return + +// bool_comp +boolcomp_0: +store 6 +load 6 +! +store 7 +load 7 +int 2 +< +assert +load 7 +retsub + +// round_tripper +roundtripper_1: +store 2 +load 2 +callsub boolcomp_0 +store 4 +load 4 +callsub boolcomp_0 +store 5 +byte 0x00 +int 0 +load 2 +setbit +int 1 +load 4 +setbit +int 2 +load 5 +setbit +store 3 +load 3 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_bool[1].teal b/tests/integration/teal/roundtrip/app_roundtrip_bool[1].teal new file mode 100644 index 000000000..399bcf910 --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_bool[1].teal @@ -0,0 +1,61 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 1 +load 1 +callsub roundtripper_2 +store 0 +byte 0x151F7C75 +load 0 +concat +log +int 1 +return + +// bool_comp +boolcomp_0: +store 9 +load 9 +! +store 10 +load 10 +int 2 +< +assert +load 10 +retsub + +// array_complement +arraycomplement_1: +store 6 +load 6 +int 0 +getbit +store 8 +load 8 +callsub boolcomp_0 +store 8 +byte 0x00 +int 0 +load 8 +setbit +store 7 +load 7 +retsub + +// round_tripper +roundtripper_2: +store 2 +load 2 +callsub arraycomplement_1 +store 4 +load 4 +callsub arraycomplement_1 +store 5 +load 2 +load 4 +concat +load 5 +concat +store 3 +load 3 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_bool[42].teal b/tests/integration/teal/roundtrip/app_roundtrip_bool[42].teal new file mode 100644 index 000000000..c0026b483 --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_bool[42].teal @@ -0,0 +1,471 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 1 +load 1 +callsub roundtripper_2 +store 0 +byte 0x151F7C75 +load 0 +concat +log +int 1 +return + +// bool_comp +boolcomp_0: +store 50 +load 50 +! +store 51 +load 51 +int 2 +< +assert +load 51 +retsub + +// array_complement +arraycomplement_1: +store 6 +load 6 +int 0 +getbit +store 8 +load 6 +int 1 +getbit +store 9 +load 6 +int 2 +getbit +store 10 +load 6 +int 3 +getbit +store 11 +load 6 +int 4 +getbit +store 12 +load 6 +int 5 +getbit +store 13 +load 6 +int 6 +getbit +store 14 +load 6 +int 7 +getbit +store 15 +load 6 +int 8 +getbit +store 16 +load 6 +int 9 +getbit +store 17 +load 6 +int 10 +getbit +store 18 +load 6 +int 11 +getbit +store 19 +load 6 +int 12 +getbit +store 20 +load 6 +int 13 +getbit +store 21 +load 6 +int 14 +getbit +store 22 +load 6 +int 15 +getbit +store 23 +load 6 +int 16 +getbit +store 24 +load 6 +int 17 +getbit +store 25 +load 6 +int 18 +getbit +store 26 +load 6 +int 19 +getbit +store 27 +load 6 +int 20 +getbit +store 28 +load 6 +int 21 +getbit +store 29 +load 6 +int 22 +getbit +store 30 +load 6 +int 23 +getbit +store 31 +load 6 +int 24 +getbit +store 32 +load 6 +int 25 +getbit +store 33 +load 6 +int 26 +getbit +store 34 +load 6 +int 27 +getbit +store 35 +load 6 +int 28 +getbit +store 36 +load 6 +int 29 +getbit +store 37 +load 6 +int 30 +getbit +store 38 +load 6 +int 31 +getbit +store 39 +load 6 +int 32 +getbit +store 40 +load 6 +int 33 +getbit +store 41 +load 6 +int 34 +getbit +store 42 +load 6 +int 35 +getbit +store 43 +load 6 +int 36 +getbit +store 44 +load 6 +int 37 +getbit +store 45 +load 6 +int 38 +getbit +store 46 +load 6 +int 39 +getbit +store 47 +load 6 +int 40 +getbit +store 48 +load 6 +int 41 +getbit +store 49 +load 8 +callsub boolcomp_0 +store 8 +load 9 +callsub boolcomp_0 +store 9 +load 10 +callsub boolcomp_0 +store 10 +load 11 +callsub boolcomp_0 +store 11 +load 12 +callsub boolcomp_0 +store 12 +load 13 +callsub boolcomp_0 +store 13 +load 14 +callsub boolcomp_0 +store 14 +load 15 +callsub boolcomp_0 +store 15 +load 16 +callsub boolcomp_0 +store 16 +load 17 +callsub boolcomp_0 +store 17 +load 18 +callsub boolcomp_0 +store 18 +load 19 +callsub boolcomp_0 +store 19 +load 20 +callsub boolcomp_0 +store 20 +load 21 +callsub boolcomp_0 +store 21 +load 22 +callsub boolcomp_0 +store 22 +load 23 +callsub boolcomp_0 +store 23 +load 24 +callsub boolcomp_0 +store 24 +load 25 +callsub boolcomp_0 +store 25 +load 26 +callsub boolcomp_0 +store 26 +load 27 +callsub boolcomp_0 +store 27 +load 28 +callsub boolcomp_0 +store 28 +load 29 +callsub boolcomp_0 +store 29 +load 30 +callsub boolcomp_0 +store 30 +load 31 +callsub boolcomp_0 +store 31 +load 32 +callsub boolcomp_0 +store 32 +load 33 +callsub boolcomp_0 +store 33 +load 34 +callsub boolcomp_0 +store 34 +load 35 +callsub boolcomp_0 +store 35 +load 36 +callsub boolcomp_0 +store 36 +load 37 +callsub boolcomp_0 +store 37 +load 38 +callsub boolcomp_0 +store 38 +load 39 +callsub boolcomp_0 +store 39 +load 40 +callsub boolcomp_0 +store 40 +load 41 +callsub boolcomp_0 +store 41 +load 42 +callsub boolcomp_0 +store 42 +load 43 +callsub boolcomp_0 +store 43 +load 44 +callsub boolcomp_0 +store 44 +load 45 +callsub boolcomp_0 +store 45 +load 46 +callsub boolcomp_0 +store 46 +load 47 +callsub boolcomp_0 +store 47 +load 48 +callsub boolcomp_0 +store 48 +load 49 +callsub boolcomp_0 +store 49 +byte 0x000000000000 +int 0 +load 8 +setbit +int 1 +load 9 +setbit +int 2 +load 10 +setbit +int 3 +load 11 +setbit +int 4 +load 12 +setbit +int 5 +load 13 +setbit +int 6 +load 14 +setbit +int 7 +load 15 +setbit +int 8 +load 16 +setbit +int 9 +load 17 +setbit +int 10 +load 18 +setbit +int 11 +load 19 +setbit +int 12 +load 20 +setbit +int 13 +load 21 +setbit +int 14 +load 22 +setbit +int 15 +load 23 +setbit +int 16 +load 24 +setbit +int 17 +load 25 +setbit +int 18 +load 26 +setbit +int 19 +load 27 +setbit +int 20 +load 28 +setbit +int 21 +load 29 +setbit +int 22 +load 30 +setbit +int 23 +load 31 +setbit +int 24 +load 32 +setbit +int 25 +load 33 +setbit +int 26 +load 34 +setbit +int 27 +load 35 +setbit +int 28 +load 36 +setbit +int 29 +load 37 +setbit +int 30 +load 38 +setbit +int 31 +load 39 +setbit +int 32 +load 40 +setbit +int 33 +load 41 +setbit +int 34 +load 42 +setbit +int 35 +load 43 +setbit +int 36 +load 44 +setbit +int 37 +load 45 +setbit +int 38 +load 46 +setbit +int 39 +load 47 +setbit +int 40 +load 48 +setbit +int 41 +load 49 +setbit +store 7 +load 7 +retsub + +// round_tripper +roundtripper_2: +store 2 +load 2 +callsub arraycomplement_1 +store 4 +load 4 +callsub arraycomplement_1 +store 5 +load 2 +load 4 +concat +load 5 +concat +store 3 +load 3 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_bool[]_<0>.teal b/tests/integration/teal/roundtrip/app_roundtrip_bool[]_<0>.teal new file mode 100644 index 000000000..213f6c9af --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_bool[]_<0>.teal @@ -0,0 +1,92 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 1 +load 1 +callsub roundtripper_1 +store 0 +byte 0x151F7C75 +load 0 +concat +log +int 1 +return + +// array_complement +arraycomplement_0: +store 6 +int 0 +store 8 +load 8 +itob +extract 6 0 +byte "" +concat +store 7 +load 7 +retsub + +// round_tripper +roundtripper_1: +store 2 +load 2 +callsub arraycomplement_0 +store 4 +load 4 +callsub arraycomplement_0 +store 5 +load 2 +store 12 +load 12 +store 11 +int 6 +store 9 +load 9 +load 12 +len ++ +store 10 +load 10 +int 65536 +< +assert +load 9 +itob +extract 6 0 +load 4 +store 12 +load 11 +load 12 +concat +store 11 +load 10 +store 9 +load 9 +load 12 +len ++ +store 10 +load 10 +int 65536 +< +assert +load 9 +itob +extract 6 0 +concat +load 5 +store 12 +load 11 +load 12 +concat +store 11 +load 10 +store 9 +load 9 +itob +extract 6 0 +concat +load 11 +concat +store 3 +load 3 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_bool[]_<1>.teal b/tests/integration/teal/roundtrip/app_roundtrip_bool[]_<1>.teal new file mode 100644 index 000000000..3371ff0cd --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_bool[]_<1>.teal @@ -0,0 +1,117 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 1 +load 1 +callsub roundtripper_2 +store 0 +byte 0x151F7C75 +load 0 +concat +log +int 1 +return + +// bool_comp +boolcomp_0: +store 9 +load 9 +! +store 10 +load 10 +int 2 +< +assert +load 10 +retsub + +// array_complement +arraycomplement_1: +store 6 +load 6 +int 0 +int 16 ++ +getbit +store 8 +load 8 +callsub boolcomp_0 +store 8 +int 1 +store 11 +load 11 +itob +extract 6 0 +byte 0x00 +int 0 +load 8 +setbit +concat +store 7 +load 7 +retsub + +// round_tripper +roundtripper_2: +store 2 +load 2 +callsub arraycomplement_1 +store 4 +load 4 +callsub arraycomplement_1 +store 5 +load 2 +store 15 +load 15 +store 14 +int 6 +store 12 +load 12 +load 15 +len ++ +store 13 +load 13 +int 65536 +< +assert +load 12 +itob +extract 6 0 +load 4 +store 15 +load 14 +load 15 +concat +store 14 +load 13 +store 12 +load 12 +load 15 +len ++ +store 13 +load 13 +int 65536 +< +assert +load 12 +itob +extract 6 0 +concat +load 5 +store 15 +load 14 +load 15 +concat +store 14 +load 13 +store 12 +load 12 +itob +extract 6 0 +concat +load 14 +concat +store 3 +load 3 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_bool[]_<42>.teal b/tests/integration/teal/roundtrip/app_roundtrip_bool[]_<42>.teal new file mode 100644 index 000000000..8b4204e83 --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_bool[]_<42>.teal @@ -0,0 +1,609 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 1 +load 1 +callsub roundtripper_2 +store 0 +byte 0x151F7C75 +load 0 +concat +log +int 1 +return + +// bool_comp +boolcomp_0: +store 50 +load 50 +! +store 51 +load 51 +int 2 +< +assert +load 51 +retsub + +// array_complement +arraycomplement_1: +store 6 +load 6 +int 0 +int 16 ++ +getbit +store 8 +load 6 +int 1 +int 16 ++ +getbit +store 9 +load 6 +int 2 +int 16 ++ +getbit +store 10 +load 6 +int 3 +int 16 ++ +getbit +store 11 +load 6 +int 4 +int 16 ++ +getbit +store 12 +load 6 +int 5 +int 16 ++ +getbit +store 13 +load 6 +int 6 +int 16 ++ +getbit +store 14 +load 6 +int 7 +int 16 ++ +getbit +store 15 +load 6 +int 8 +int 16 ++ +getbit +store 16 +load 6 +int 9 +int 16 ++ +getbit +store 17 +load 6 +int 10 +int 16 ++ +getbit +store 18 +load 6 +int 11 +int 16 ++ +getbit +store 19 +load 6 +int 12 +int 16 ++ +getbit +store 20 +load 6 +int 13 +int 16 ++ +getbit +store 21 +load 6 +int 14 +int 16 ++ +getbit +store 22 +load 6 +int 15 +int 16 ++ +getbit +store 23 +load 6 +int 16 +int 16 ++ +getbit +store 24 +load 6 +int 17 +int 16 ++ +getbit +store 25 +load 6 +int 18 +int 16 ++ +getbit +store 26 +load 6 +int 19 +int 16 ++ +getbit +store 27 +load 6 +int 20 +int 16 ++ +getbit +store 28 +load 6 +int 21 +int 16 ++ +getbit +store 29 +load 6 +int 22 +int 16 ++ +getbit +store 30 +load 6 +int 23 +int 16 ++ +getbit +store 31 +load 6 +int 24 +int 16 ++ +getbit +store 32 +load 6 +int 25 +int 16 ++ +getbit +store 33 +load 6 +int 26 +int 16 ++ +getbit +store 34 +load 6 +int 27 +int 16 ++ +getbit +store 35 +load 6 +int 28 +int 16 ++ +getbit +store 36 +load 6 +int 29 +int 16 ++ +getbit +store 37 +load 6 +int 30 +int 16 ++ +getbit +store 38 +load 6 +int 31 +int 16 ++ +getbit +store 39 +load 6 +int 32 +int 16 ++ +getbit +store 40 +load 6 +int 33 +int 16 ++ +getbit +store 41 +load 6 +int 34 +int 16 ++ +getbit +store 42 +load 6 +int 35 +int 16 ++ +getbit +store 43 +load 6 +int 36 +int 16 ++ +getbit +store 44 +load 6 +int 37 +int 16 ++ +getbit +store 45 +load 6 +int 38 +int 16 ++ +getbit +store 46 +load 6 +int 39 +int 16 ++ +getbit +store 47 +load 6 +int 40 +int 16 ++ +getbit +store 48 +load 6 +int 41 +int 16 ++ +getbit +store 49 +load 8 +callsub boolcomp_0 +store 8 +load 9 +callsub boolcomp_0 +store 9 +load 10 +callsub boolcomp_0 +store 10 +load 11 +callsub boolcomp_0 +store 11 +load 12 +callsub boolcomp_0 +store 12 +load 13 +callsub boolcomp_0 +store 13 +load 14 +callsub boolcomp_0 +store 14 +load 15 +callsub boolcomp_0 +store 15 +load 16 +callsub boolcomp_0 +store 16 +load 17 +callsub boolcomp_0 +store 17 +load 18 +callsub boolcomp_0 +store 18 +load 19 +callsub boolcomp_0 +store 19 +load 20 +callsub boolcomp_0 +store 20 +load 21 +callsub boolcomp_0 +store 21 +load 22 +callsub boolcomp_0 +store 22 +load 23 +callsub boolcomp_0 +store 23 +load 24 +callsub boolcomp_0 +store 24 +load 25 +callsub boolcomp_0 +store 25 +load 26 +callsub boolcomp_0 +store 26 +load 27 +callsub boolcomp_0 +store 27 +load 28 +callsub boolcomp_0 +store 28 +load 29 +callsub boolcomp_0 +store 29 +load 30 +callsub boolcomp_0 +store 30 +load 31 +callsub boolcomp_0 +store 31 +load 32 +callsub boolcomp_0 +store 32 +load 33 +callsub boolcomp_0 +store 33 +load 34 +callsub boolcomp_0 +store 34 +load 35 +callsub boolcomp_0 +store 35 +load 36 +callsub boolcomp_0 +store 36 +load 37 +callsub boolcomp_0 +store 37 +load 38 +callsub boolcomp_0 +store 38 +load 39 +callsub boolcomp_0 +store 39 +load 40 +callsub boolcomp_0 +store 40 +load 41 +callsub boolcomp_0 +store 41 +load 42 +callsub boolcomp_0 +store 42 +load 43 +callsub boolcomp_0 +store 43 +load 44 +callsub boolcomp_0 +store 44 +load 45 +callsub boolcomp_0 +store 45 +load 46 +callsub boolcomp_0 +store 46 +load 47 +callsub boolcomp_0 +store 47 +load 48 +callsub boolcomp_0 +store 48 +load 49 +callsub boolcomp_0 +store 49 +int 42 +store 52 +load 52 +itob +extract 6 0 +byte 0x000000000000 +int 0 +load 8 +setbit +int 1 +load 9 +setbit +int 2 +load 10 +setbit +int 3 +load 11 +setbit +int 4 +load 12 +setbit +int 5 +load 13 +setbit +int 6 +load 14 +setbit +int 7 +load 15 +setbit +int 8 +load 16 +setbit +int 9 +load 17 +setbit +int 10 +load 18 +setbit +int 11 +load 19 +setbit +int 12 +load 20 +setbit +int 13 +load 21 +setbit +int 14 +load 22 +setbit +int 15 +load 23 +setbit +int 16 +load 24 +setbit +int 17 +load 25 +setbit +int 18 +load 26 +setbit +int 19 +load 27 +setbit +int 20 +load 28 +setbit +int 21 +load 29 +setbit +int 22 +load 30 +setbit +int 23 +load 31 +setbit +int 24 +load 32 +setbit +int 25 +load 33 +setbit +int 26 +load 34 +setbit +int 27 +load 35 +setbit +int 28 +load 36 +setbit +int 29 +load 37 +setbit +int 30 +load 38 +setbit +int 31 +load 39 +setbit +int 32 +load 40 +setbit +int 33 +load 41 +setbit +int 34 +load 42 +setbit +int 35 +load 43 +setbit +int 36 +load 44 +setbit +int 37 +load 45 +setbit +int 38 +load 46 +setbit +int 39 +load 47 +setbit +int 40 +load 48 +setbit +int 41 +load 49 +setbit +concat +store 7 +load 7 +retsub + +// round_tripper +roundtripper_2: +store 2 +load 2 +callsub arraycomplement_1 +store 4 +load 4 +callsub arraycomplement_1 +store 5 +load 2 +store 56 +load 56 +store 55 +int 6 +store 53 +load 53 +load 56 +len ++ +store 54 +load 54 +int 65536 +< +assert +load 53 +itob +extract 6 0 +load 4 +store 56 +load 55 +load 56 +concat +store 55 +load 54 +store 53 +load 53 +load 56 +len ++ +store 54 +load 54 +int 65536 +< +assert +load 53 +itob +extract 6 0 +concat +load 5 +store 56 +load 55 +load 56 +concat +store 55 +load 54 +store 53 +load 53 +itob +extract 6 0 +concat +load 55 +concat +store 3 +load 3 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_byte.teal b/tests/integration/teal/roundtrip/app_roundtrip_byte.teal new file mode 100644 index 000000000..851809b8e --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_byte.teal @@ -0,0 +1,55 @@ +#pragma version 6 +txna ApplicationArgs 0 +int 0 +getbyte +store 1 +load 1 +callsub roundtripper_1 +store 0 +byte 0x151F7C75 +load 0 +concat +log +int 1 +return + +// numerical_comp +numericalcomp_0: +store 6 +int 255 +load 6 +- +store 7 +load 7 +int 256 +< +assert +load 7 +retsub + +// round_tripper +roundtripper_1: +store 2 +load 2 +callsub numericalcomp_0 +store 4 +load 4 +callsub numericalcomp_0 +store 5 +byte 0x00 +int 0 +load 2 +setbyte +byte 0x00 +int 0 +load 4 +setbyte +concat +byte 0x00 +int 0 +load 5 +setbyte +concat +store 3 +load 3 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_string_<0>.teal b/tests/integration/teal/roundtrip/app_roundtrip_string_<0>.teal new file mode 100644 index 000000000..213f6c9af --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_string_<0>.teal @@ -0,0 +1,92 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 1 +load 1 +callsub roundtripper_1 +store 0 +byte 0x151F7C75 +load 0 +concat +log +int 1 +return + +// array_complement +arraycomplement_0: +store 6 +int 0 +store 8 +load 8 +itob +extract 6 0 +byte "" +concat +store 7 +load 7 +retsub + +// round_tripper +roundtripper_1: +store 2 +load 2 +callsub arraycomplement_0 +store 4 +load 4 +callsub arraycomplement_0 +store 5 +load 2 +store 12 +load 12 +store 11 +int 6 +store 9 +load 9 +load 12 +len ++ +store 10 +load 10 +int 65536 +< +assert +load 9 +itob +extract 6 0 +load 4 +store 12 +load 11 +load 12 +concat +store 11 +load 10 +store 9 +load 9 +load 12 +len ++ +store 10 +load 10 +int 65536 +< +assert +load 9 +itob +extract 6 0 +concat +load 5 +store 12 +load 11 +load 12 +concat +store 11 +load 10 +store 9 +load 9 +itob +extract 6 0 +concat +load 11 +concat +store 3 +load 3 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_string_<13>.teal b/tests/integration/teal/roundtrip/app_roundtrip_string_<13>.teal new file mode 100644 index 000000000..1a42eabb2 --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_string_<13>.teal @@ -0,0 +1,312 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 1 +load 1 +callsub roundtripper_2 +store 0 +byte 0x151F7C75 +load 0 +concat +log +int 1 +return + +// numerical_comp +numericalcomp_0: +store 21 +int 255 +load 21 +- +store 22 +load 22 +int 256 +< +assert +load 22 +retsub + +// array_complement +arraycomplement_1: +store 6 +load 6 +int 1 +int 0 +* +int 2 ++ +getbyte +store 8 +load 6 +int 1 +int 1 +* +int 2 ++ +getbyte +store 9 +load 6 +int 1 +int 2 +* +int 2 ++ +getbyte +store 10 +load 6 +int 1 +int 3 +* +int 2 ++ +getbyte +store 11 +load 6 +int 1 +int 4 +* +int 2 ++ +getbyte +store 12 +load 6 +int 1 +int 5 +* +int 2 ++ +getbyte +store 13 +load 6 +int 1 +int 6 +* +int 2 ++ +getbyte +store 14 +load 6 +int 1 +int 7 +* +int 2 ++ +getbyte +store 15 +load 6 +int 1 +int 8 +* +int 2 ++ +getbyte +store 16 +load 6 +int 1 +int 9 +* +int 2 ++ +getbyte +store 17 +load 6 +int 1 +int 10 +* +int 2 ++ +getbyte +store 18 +load 6 +int 1 +int 11 +* +int 2 ++ +getbyte +store 19 +load 6 +int 1 +int 12 +* +int 2 ++ +getbyte +store 20 +load 8 +callsub numericalcomp_0 +store 8 +load 9 +callsub numericalcomp_0 +store 9 +load 10 +callsub numericalcomp_0 +store 10 +load 11 +callsub numericalcomp_0 +store 11 +load 12 +callsub numericalcomp_0 +store 12 +load 13 +callsub numericalcomp_0 +store 13 +load 14 +callsub numericalcomp_0 +store 14 +load 15 +callsub numericalcomp_0 +store 15 +load 16 +callsub numericalcomp_0 +store 16 +load 17 +callsub numericalcomp_0 +store 17 +load 18 +callsub numericalcomp_0 +store 18 +load 19 +callsub numericalcomp_0 +store 19 +load 20 +callsub numericalcomp_0 +store 20 +int 13 +store 23 +load 23 +itob +extract 6 0 +byte 0x00 +int 0 +load 8 +setbyte +byte 0x00 +int 0 +load 9 +setbyte +concat +byte 0x00 +int 0 +load 10 +setbyte +concat +byte 0x00 +int 0 +load 11 +setbyte +concat +byte 0x00 +int 0 +load 12 +setbyte +concat +byte 0x00 +int 0 +load 13 +setbyte +concat +byte 0x00 +int 0 +load 14 +setbyte +concat +byte 0x00 +int 0 +load 15 +setbyte +concat +byte 0x00 +int 0 +load 16 +setbyte +concat +byte 0x00 +int 0 +load 17 +setbyte +concat +byte 0x00 +int 0 +load 18 +setbyte +concat +byte 0x00 +int 0 +load 19 +setbyte +concat +byte 0x00 +int 0 +load 20 +setbyte +concat +concat +store 7 +load 7 +retsub + +// round_tripper +roundtripper_2: +store 2 +load 2 +callsub arraycomplement_1 +store 4 +load 4 +callsub arraycomplement_1 +store 5 +load 2 +store 27 +load 27 +store 26 +int 6 +store 24 +load 24 +load 27 +len ++ +store 25 +load 25 +int 65536 +< +assert +load 24 +itob +extract 6 0 +load 4 +store 27 +load 26 +load 27 +concat +store 26 +load 25 +store 24 +load 24 +load 27 +len ++ +store 25 +load 25 +int 65536 +< +assert +load 24 +itob +extract 6 0 +concat +load 5 +store 27 +load 26 +load 27 +concat +store 26 +load 25 +store 24 +load 24 +itob +extract 6 0 +concat +load 26 +concat +store 3 +load 3 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_string_<1>.teal b/tests/integration/teal/roundtrip/app_roundtrip_string_<1>.teal new file mode 100644 index 000000000..c4eb825aa --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_string_<1>.teal @@ -0,0 +1,120 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 1 +load 1 +callsub roundtripper_2 +store 0 +byte 0x151F7C75 +load 0 +concat +log +int 1 +return + +// numerical_comp +numericalcomp_0: +store 9 +int 255 +load 9 +- +store 10 +load 10 +int 256 +< +assert +load 10 +retsub + +// array_complement +arraycomplement_1: +store 6 +load 6 +int 1 +int 0 +* +int 2 ++ +getbyte +store 8 +load 8 +callsub numericalcomp_0 +store 8 +int 1 +store 11 +load 11 +itob +extract 6 0 +byte 0x00 +int 0 +load 8 +setbyte +concat +store 7 +load 7 +retsub + +// round_tripper +roundtripper_2: +store 2 +load 2 +callsub arraycomplement_1 +store 4 +load 4 +callsub arraycomplement_1 +store 5 +load 2 +store 15 +load 15 +store 14 +int 6 +store 12 +load 12 +load 15 +len ++ +store 13 +load 13 +int 65536 +< +assert +load 12 +itob +extract 6 0 +load 4 +store 15 +load 14 +load 15 +concat +store 14 +load 13 +store 12 +load 12 +load 15 +len ++ +store 13 +load 13 +int 65536 +< +assert +load 12 +itob +extract 6 0 +concat +load 5 +store 15 +load 14 +load 15 +concat +store 14 +load 13 +store 12 +load 12 +itob +extract 6 0 +concat +load 14 +concat +store 3 +load 3 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_uint16.teal b/tests/integration/teal/roundtrip/app_roundtrip_uint16.teal new file mode 100644 index 000000000..dc7d82530 --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_uint16.teal @@ -0,0 +1,52 @@ +#pragma version 6 +txna ApplicationArgs 0 +int 0 +extract_uint16 +store 1 +load 1 +callsub roundtripper_1 +store 0 +byte 0x151F7C75 +load 0 +concat +log +int 1 +return + +// numerical_comp +numericalcomp_0: +store 6 +int 65535 +load 6 +- +store 7 +load 7 +int 65536 +< +assert +load 7 +retsub + +// round_tripper +roundtripper_1: +store 2 +load 2 +callsub numericalcomp_0 +store 4 +load 4 +callsub numericalcomp_0 +store 5 +load 2 +itob +extract 6 0 +load 4 +itob +extract 6 0 +concat +load 5 +itob +extract 6 0 +concat +store 3 +load 3 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_uint32.teal b/tests/integration/teal/roundtrip/app_roundtrip_uint32.teal new file mode 100644 index 000000000..a46200138 --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_uint32.teal @@ -0,0 +1,52 @@ +#pragma version 6 +txna ApplicationArgs 0 +int 0 +extract_uint32 +store 1 +load 1 +callsub roundtripper_1 +store 0 +byte 0x151F7C75 +load 0 +concat +log +int 1 +return + +// numerical_comp +numericalcomp_0: +store 6 +int 4294967295 +load 6 +- +store 7 +load 7 +int 4294967296 +< +assert +load 7 +retsub + +// round_tripper +roundtripper_1: +store 2 +load 2 +callsub numericalcomp_0 +store 4 +load 4 +callsub numericalcomp_0 +store 5 +load 2 +itob +extract 4 0 +load 4 +itob +extract 4 0 +concat +load 5 +itob +extract 4 0 +concat +store 3 +load 3 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_uint64.teal b/tests/integration/teal/roundtrip/app_roundtrip_uint64.teal new file mode 100644 index 000000000..9a9169df4 --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_uint64.teal @@ -0,0 +1,44 @@ +#pragma version 6 +txna ApplicationArgs 0 +btoi +store 1 +load 1 +callsub roundtripper_1 +store 0 +byte 0x151F7C75 +load 0 +concat +log +int 1 +return + +// numerical_comp +numericalcomp_0: +store 6 +int 18446744073709551615 +load 6 +- +store 7 +load 7 +retsub + +// round_tripper +roundtripper_1: +store 2 +load 2 +callsub numericalcomp_0 +store 4 +load 4 +callsub numericalcomp_0 +store 5 +load 2 +itob +load 4 +itob +concat +load 5 +itob +concat +store 3 +load 3 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_uint64[1].teal b/tests/integration/teal/roundtrip/app_roundtrip_uint64[1].teal new file mode 100644 index 000000000..3252a6442 --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_uint64[1].teal @@ -0,0 +1,58 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 1 +load 1 +callsub roundtripper_2 +store 0 +byte 0x151F7C75 +load 0 +concat +log +int 1 +return + +// numerical_comp +numericalcomp_0: +store 9 +int 18446744073709551615 +load 9 +- +store 10 +load 10 +retsub + +// array_complement +arraycomplement_1: +store 6 +load 6 +int 8 +int 0 +* +extract_uint64 +store 8 +load 8 +callsub numericalcomp_0 +store 8 +load 8 +itob +store 7 +load 7 +retsub + +// round_tripper +roundtripper_2: +store 2 +load 2 +callsub arraycomplement_1 +store 4 +load 4 +callsub arraycomplement_1 +store 5 +load 2 +load 4 +concat +load 5 +concat +store 3 +load 3 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_uint64[42].teal b/tests/integration/teal/roundtrip/app_roundtrip_uint64[42].teal new file mode 100644 index 000000000..6afe66a2f --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_uint64[42].teal @@ -0,0 +1,550 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 1 +load 1 +callsub roundtripper_2 +store 0 +byte 0x151F7C75 +load 0 +concat +log +int 1 +return + +// numerical_comp +numericalcomp_0: +store 50 +int 18446744073709551615 +load 50 +- +store 51 +load 51 +retsub + +// array_complement +arraycomplement_1: +store 6 +load 6 +int 8 +int 0 +* +extract_uint64 +store 8 +load 6 +int 8 +int 1 +* +extract_uint64 +store 9 +load 6 +int 8 +int 2 +* +extract_uint64 +store 10 +load 6 +int 8 +int 3 +* +extract_uint64 +store 11 +load 6 +int 8 +int 4 +* +extract_uint64 +store 12 +load 6 +int 8 +int 5 +* +extract_uint64 +store 13 +load 6 +int 8 +int 6 +* +extract_uint64 +store 14 +load 6 +int 8 +int 7 +* +extract_uint64 +store 15 +load 6 +int 8 +int 8 +* +extract_uint64 +store 16 +load 6 +int 8 +int 9 +* +extract_uint64 +store 17 +load 6 +int 8 +int 10 +* +extract_uint64 +store 18 +load 6 +int 8 +int 11 +* +extract_uint64 +store 19 +load 6 +int 8 +int 12 +* +extract_uint64 +store 20 +load 6 +int 8 +int 13 +* +extract_uint64 +store 21 +load 6 +int 8 +int 14 +* +extract_uint64 +store 22 +load 6 +int 8 +int 15 +* +extract_uint64 +store 23 +load 6 +int 8 +int 16 +* +extract_uint64 +store 24 +load 6 +int 8 +int 17 +* +extract_uint64 +store 25 +load 6 +int 8 +int 18 +* +extract_uint64 +store 26 +load 6 +int 8 +int 19 +* +extract_uint64 +store 27 +load 6 +int 8 +int 20 +* +extract_uint64 +store 28 +load 6 +int 8 +int 21 +* +extract_uint64 +store 29 +load 6 +int 8 +int 22 +* +extract_uint64 +store 30 +load 6 +int 8 +int 23 +* +extract_uint64 +store 31 +load 6 +int 8 +int 24 +* +extract_uint64 +store 32 +load 6 +int 8 +int 25 +* +extract_uint64 +store 33 +load 6 +int 8 +int 26 +* +extract_uint64 +store 34 +load 6 +int 8 +int 27 +* +extract_uint64 +store 35 +load 6 +int 8 +int 28 +* +extract_uint64 +store 36 +load 6 +int 8 +int 29 +* +extract_uint64 +store 37 +load 6 +int 8 +int 30 +* +extract_uint64 +store 38 +load 6 +int 8 +int 31 +* +extract_uint64 +store 39 +load 6 +int 8 +int 32 +* +extract_uint64 +store 40 +load 6 +int 8 +int 33 +* +extract_uint64 +store 41 +load 6 +int 8 +int 34 +* +extract_uint64 +store 42 +load 6 +int 8 +int 35 +* +extract_uint64 +store 43 +load 6 +int 8 +int 36 +* +extract_uint64 +store 44 +load 6 +int 8 +int 37 +* +extract_uint64 +store 45 +load 6 +int 8 +int 38 +* +extract_uint64 +store 46 +load 6 +int 8 +int 39 +* +extract_uint64 +store 47 +load 6 +int 8 +int 40 +* +extract_uint64 +store 48 +load 6 +int 8 +int 41 +* +extract_uint64 +store 49 +load 8 +callsub numericalcomp_0 +store 8 +load 9 +callsub numericalcomp_0 +store 9 +load 10 +callsub numericalcomp_0 +store 10 +load 11 +callsub numericalcomp_0 +store 11 +load 12 +callsub numericalcomp_0 +store 12 +load 13 +callsub numericalcomp_0 +store 13 +load 14 +callsub numericalcomp_0 +store 14 +load 15 +callsub numericalcomp_0 +store 15 +load 16 +callsub numericalcomp_0 +store 16 +load 17 +callsub numericalcomp_0 +store 17 +load 18 +callsub numericalcomp_0 +store 18 +load 19 +callsub numericalcomp_0 +store 19 +load 20 +callsub numericalcomp_0 +store 20 +load 21 +callsub numericalcomp_0 +store 21 +load 22 +callsub numericalcomp_0 +store 22 +load 23 +callsub numericalcomp_0 +store 23 +load 24 +callsub numericalcomp_0 +store 24 +load 25 +callsub numericalcomp_0 +store 25 +load 26 +callsub numericalcomp_0 +store 26 +load 27 +callsub numericalcomp_0 +store 27 +load 28 +callsub numericalcomp_0 +store 28 +load 29 +callsub numericalcomp_0 +store 29 +load 30 +callsub numericalcomp_0 +store 30 +load 31 +callsub numericalcomp_0 +store 31 +load 32 +callsub numericalcomp_0 +store 32 +load 33 +callsub numericalcomp_0 +store 33 +load 34 +callsub numericalcomp_0 +store 34 +load 35 +callsub numericalcomp_0 +store 35 +load 36 +callsub numericalcomp_0 +store 36 +load 37 +callsub numericalcomp_0 +store 37 +load 38 +callsub numericalcomp_0 +store 38 +load 39 +callsub numericalcomp_0 +store 39 +load 40 +callsub numericalcomp_0 +store 40 +load 41 +callsub numericalcomp_0 +store 41 +load 42 +callsub numericalcomp_0 +store 42 +load 43 +callsub numericalcomp_0 +store 43 +load 44 +callsub numericalcomp_0 +store 44 +load 45 +callsub numericalcomp_0 +store 45 +load 46 +callsub numericalcomp_0 +store 46 +load 47 +callsub numericalcomp_0 +store 47 +load 48 +callsub numericalcomp_0 +store 48 +load 49 +callsub numericalcomp_0 +store 49 +load 8 +itob +load 9 +itob +concat +load 10 +itob +concat +load 11 +itob +concat +load 12 +itob +concat +load 13 +itob +concat +load 14 +itob +concat +load 15 +itob +concat +load 16 +itob +concat +load 17 +itob +concat +load 18 +itob +concat +load 19 +itob +concat +load 20 +itob +concat +load 21 +itob +concat +load 22 +itob +concat +load 23 +itob +concat +load 24 +itob +concat +load 25 +itob +concat +load 26 +itob +concat +load 27 +itob +concat +load 28 +itob +concat +load 29 +itob +concat +load 30 +itob +concat +load 31 +itob +concat +load 32 +itob +concat +load 33 +itob +concat +load 34 +itob +concat +load 35 +itob +concat +load 36 +itob +concat +load 37 +itob +concat +load 38 +itob +concat +load 39 +itob +concat +load 40 +itob +concat +load 41 +itob +concat +load 42 +itob +concat +load 43 +itob +concat +load 44 +itob +concat +load 45 +itob +concat +load 46 +itob +concat +load 47 +itob +concat +load 48 +itob +concat +load 49 +itob +concat +store 7 +load 7 +retsub + +// round_tripper +roundtripper_2: +store 2 +load 2 +callsub arraycomplement_1 +store 4 +load 4 +callsub arraycomplement_1 +store 5 +load 2 +load 4 +concat +load 5 +concat +store 3 +load 3 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_uint64[]_<0>.teal b/tests/integration/teal/roundtrip/app_roundtrip_uint64[]_<0>.teal new file mode 100644 index 000000000..213f6c9af --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_uint64[]_<0>.teal @@ -0,0 +1,92 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 1 +load 1 +callsub roundtripper_1 +store 0 +byte 0x151F7C75 +load 0 +concat +log +int 1 +return + +// array_complement +arraycomplement_0: +store 6 +int 0 +store 8 +load 8 +itob +extract 6 0 +byte "" +concat +store 7 +load 7 +retsub + +// round_tripper +roundtripper_1: +store 2 +load 2 +callsub arraycomplement_0 +store 4 +load 4 +callsub arraycomplement_0 +store 5 +load 2 +store 12 +load 12 +store 11 +int 6 +store 9 +load 9 +load 12 +len ++ +store 10 +load 10 +int 65536 +< +assert +load 9 +itob +extract 6 0 +load 4 +store 12 +load 11 +load 12 +concat +store 11 +load 10 +store 9 +load 9 +load 12 +len ++ +store 10 +load 10 +int 65536 +< +assert +load 9 +itob +extract 6 0 +concat +load 5 +store 12 +load 11 +load 12 +concat +store 11 +load 10 +store 9 +load 9 +itob +extract 6 0 +concat +load 11 +concat +store 3 +load 3 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_uint64[]_<1>.teal b/tests/integration/teal/roundtrip/app_roundtrip_uint64[]_<1>.teal new file mode 100644 index 000000000..70c2016c9 --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_uint64[]_<1>.teal @@ -0,0 +1,114 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 1 +load 1 +callsub roundtripper_2 +store 0 +byte 0x151F7C75 +load 0 +concat +log +int 1 +return + +// numerical_comp +numericalcomp_0: +store 9 +int 18446744073709551615 +load 9 +- +store 10 +load 10 +retsub + +// array_complement +arraycomplement_1: +store 6 +load 6 +int 8 +int 0 +* +int 2 ++ +extract_uint64 +store 8 +load 8 +callsub numericalcomp_0 +store 8 +int 1 +store 11 +load 11 +itob +extract 6 0 +load 8 +itob +concat +store 7 +load 7 +retsub + +// round_tripper +roundtripper_2: +store 2 +load 2 +callsub arraycomplement_1 +store 4 +load 4 +callsub arraycomplement_1 +store 5 +load 2 +store 15 +load 15 +store 14 +int 6 +store 12 +load 12 +load 15 +len ++ +store 13 +load 13 +int 65536 +< +assert +load 12 +itob +extract 6 0 +load 4 +store 15 +load 14 +load 15 +concat +store 14 +load 13 +store 12 +load 12 +load 15 +len ++ +store 13 +load 13 +int 65536 +< +assert +load 12 +itob +extract 6 0 +concat +load 5 +store 15 +load 14 +load 15 +concat +store 14 +load 13 +store 12 +load 12 +itob +extract 6 0 +concat +load 14 +concat +store 3 +load 3 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_uint64[]_<42>.teal b/tests/integration/teal/roundtrip/app_roundtrip_uint64[]_<42>.teal new file mode 100644 index 000000000..d2ae51113 --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_uint64[]_<42>.teal @@ -0,0 +1,688 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 1 +load 1 +callsub roundtripper_2 +store 0 +byte 0x151F7C75 +load 0 +concat +log +int 1 +return + +// numerical_comp +numericalcomp_0: +store 50 +int 18446744073709551615 +load 50 +- +store 51 +load 51 +retsub + +// array_complement +arraycomplement_1: +store 6 +load 6 +int 8 +int 0 +* +int 2 ++ +extract_uint64 +store 8 +load 6 +int 8 +int 1 +* +int 2 ++ +extract_uint64 +store 9 +load 6 +int 8 +int 2 +* +int 2 ++ +extract_uint64 +store 10 +load 6 +int 8 +int 3 +* +int 2 ++ +extract_uint64 +store 11 +load 6 +int 8 +int 4 +* +int 2 ++ +extract_uint64 +store 12 +load 6 +int 8 +int 5 +* +int 2 ++ +extract_uint64 +store 13 +load 6 +int 8 +int 6 +* +int 2 ++ +extract_uint64 +store 14 +load 6 +int 8 +int 7 +* +int 2 ++ +extract_uint64 +store 15 +load 6 +int 8 +int 8 +* +int 2 ++ +extract_uint64 +store 16 +load 6 +int 8 +int 9 +* +int 2 ++ +extract_uint64 +store 17 +load 6 +int 8 +int 10 +* +int 2 ++ +extract_uint64 +store 18 +load 6 +int 8 +int 11 +* +int 2 ++ +extract_uint64 +store 19 +load 6 +int 8 +int 12 +* +int 2 ++ +extract_uint64 +store 20 +load 6 +int 8 +int 13 +* +int 2 ++ +extract_uint64 +store 21 +load 6 +int 8 +int 14 +* +int 2 ++ +extract_uint64 +store 22 +load 6 +int 8 +int 15 +* +int 2 ++ +extract_uint64 +store 23 +load 6 +int 8 +int 16 +* +int 2 ++ +extract_uint64 +store 24 +load 6 +int 8 +int 17 +* +int 2 ++ +extract_uint64 +store 25 +load 6 +int 8 +int 18 +* +int 2 ++ +extract_uint64 +store 26 +load 6 +int 8 +int 19 +* +int 2 ++ +extract_uint64 +store 27 +load 6 +int 8 +int 20 +* +int 2 ++ +extract_uint64 +store 28 +load 6 +int 8 +int 21 +* +int 2 ++ +extract_uint64 +store 29 +load 6 +int 8 +int 22 +* +int 2 ++ +extract_uint64 +store 30 +load 6 +int 8 +int 23 +* +int 2 ++ +extract_uint64 +store 31 +load 6 +int 8 +int 24 +* +int 2 ++ +extract_uint64 +store 32 +load 6 +int 8 +int 25 +* +int 2 ++ +extract_uint64 +store 33 +load 6 +int 8 +int 26 +* +int 2 ++ +extract_uint64 +store 34 +load 6 +int 8 +int 27 +* +int 2 ++ +extract_uint64 +store 35 +load 6 +int 8 +int 28 +* +int 2 ++ +extract_uint64 +store 36 +load 6 +int 8 +int 29 +* +int 2 ++ +extract_uint64 +store 37 +load 6 +int 8 +int 30 +* +int 2 ++ +extract_uint64 +store 38 +load 6 +int 8 +int 31 +* +int 2 ++ +extract_uint64 +store 39 +load 6 +int 8 +int 32 +* +int 2 ++ +extract_uint64 +store 40 +load 6 +int 8 +int 33 +* +int 2 ++ +extract_uint64 +store 41 +load 6 +int 8 +int 34 +* +int 2 ++ +extract_uint64 +store 42 +load 6 +int 8 +int 35 +* +int 2 ++ +extract_uint64 +store 43 +load 6 +int 8 +int 36 +* +int 2 ++ +extract_uint64 +store 44 +load 6 +int 8 +int 37 +* +int 2 ++ +extract_uint64 +store 45 +load 6 +int 8 +int 38 +* +int 2 ++ +extract_uint64 +store 46 +load 6 +int 8 +int 39 +* +int 2 ++ +extract_uint64 +store 47 +load 6 +int 8 +int 40 +* +int 2 ++ +extract_uint64 +store 48 +load 6 +int 8 +int 41 +* +int 2 ++ +extract_uint64 +store 49 +load 8 +callsub numericalcomp_0 +store 8 +load 9 +callsub numericalcomp_0 +store 9 +load 10 +callsub numericalcomp_0 +store 10 +load 11 +callsub numericalcomp_0 +store 11 +load 12 +callsub numericalcomp_0 +store 12 +load 13 +callsub numericalcomp_0 +store 13 +load 14 +callsub numericalcomp_0 +store 14 +load 15 +callsub numericalcomp_0 +store 15 +load 16 +callsub numericalcomp_0 +store 16 +load 17 +callsub numericalcomp_0 +store 17 +load 18 +callsub numericalcomp_0 +store 18 +load 19 +callsub numericalcomp_0 +store 19 +load 20 +callsub numericalcomp_0 +store 20 +load 21 +callsub numericalcomp_0 +store 21 +load 22 +callsub numericalcomp_0 +store 22 +load 23 +callsub numericalcomp_0 +store 23 +load 24 +callsub numericalcomp_0 +store 24 +load 25 +callsub numericalcomp_0 +store 25 +load 26 +callsub numericalcomp_0 +store 26 +load 27 +callsub numericalcomp_0 +store 27 +load 28 +callsub numericalcomp_0 +store 28 +load 29 +callsub numericalcomp_0 +store 29 +load 30 +callsub numericalcomp_0 +store 30 +load 31 +callsub numericalcomp_0 +store 31 +load 32 +callsub numericalcomp_0 +store 32 +load 33 +callsub numericalcomp_0 +store 33 +load 34 +callsub numericalcomp_0 +store 34 +load 35 +callsub numericalcomp_0 +store 35 +load 36 +callsub numericalcomp_0 +store 36 +load 37 +callsub numericalcomp_0 +store 37 +load 38 +callsub numericalcomp_0 +store 38 +load 39 +callsub numericalcomp_0 +store 39 +load 40 +callsub numericalcomp_0 +store 40 +load 41 +callsub numericalcomp_0 +store 41 +load 42 +callsub numericalcomp_0 +store 42 +load 43 +callsub numericalcomp_0 +store 43 +load 44 +callsub numericalcomp_0 +store 44 +load 45 +callsub numericalcomp_0 +store 45 +load 46 +callsub numericalcomp_0 +store 46 +load 47 +callsub numericalcomp_0 +store 47 +load 48 +callsub numericalcomp_0 +store 48 +load 49 +callsub numericalcomp_0 +store 49 +int 42 +store 52 +load 52 +itob +extract 6 0 +load 8 +itob +load 9 +itob +concat +load 10 +itob +concat +load 11 +itob +concat +load 12 +itob +concat +load 13 +itob +concat +load 14 +itob +concat +load 15 +itob +concat +load 16 +itob +concat +load 17 +itob +concat +load 18 +itob +concat +load 19 +itob +concat +load 20 +itob +concat +load 21 +itob +concat +load 22 +itob +concat +load 23 +itob +concat +load 24 +itob +concat +load 25 +itob +concat +load 26 +itob +concat +load 27 +itob +concat +load 28 +itob +concat +load 29 +itob +concat +load 30 +itob +concat +load 31 +itob +concat +load 32 +itob +concat +load 33 +itob +concat +load 34 +itob +concat +load 35 +itob +concat +load 36 +itob +concat +load 37 +itob +concat +load 38 +itob +concat +load 39 +itob +concat +load 40 +itob +concat +load 41 +itob +concat +load 42 +itob +concat +load 43 +itob +concat +load 44 +itob +concat +load 45 +itob +concat +load 46 +itob +concat +load 47 +itob +concat +load 48 +itob +concat +load 49 +itob +concat +concat +store 7 +load 7 +retsub + +// round_tripper +roundtripper_2: +store 2 +load 2 +callsub arraycomplement_1 +store 4 +load 4 +callsub arraycomplement_1 +store 5 +load 2 +store 56 +load 56 +store 55 +int 6 +store 53 +load 53 +load 56 +len ++ +store 54 +load 54 +int 65536 +< +assert +load 53 +itob +extract 6 0 +load 4 +store 56 +load 55 +load 56 +concat +store 55 +load 54 +store 53 +load 53 +load 56 +len ++ +store 54 +load 54 +int 65536 +< +assert +load 53 +itob +extract 6 0 +concat +load 5 +store 56 +load 55 +load 56 +concat +store 55 +load 54 +store 53 +load 53 +itob +extract 6 0 +concat +load 55 +concat +store 3 +load 3 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_uint8.teal b/tests/integration/teal/roundtrip/app_roundtrip_uint8.teal new file mode 100644 index 000000000..851809b8e --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_uint8.teal @@ -0,0 +1,55 @@ +#pragma version 6 +txna ApplicationArgs 0 +int 0 +getbyte +store 1 +load 1 +callsub roundtripper_1 +store 0 +byte 0x151F7C75 +load 0 +concat +log +int 1 +return + +// numerical_comp +numericalcomp_0: +store 6 +int 255 +load 6 +- +store 7 +load 7 +int 256 +< +assert +load 7 +retsub + +// round_tripper +roundtripper_1: +store 2 +load 2 +callsub numericalcomp_0 +store 4 +load 4 +callsub numericalcomp_0 +store 5 +byte 0x00 +int 0 +load 2 +setbyte +byte 0x00 +int 0 +load 4 +setbyte +concat +byte 0x00 +int 0 +load 5 +setbyte +concat +store 3 +load 3 +retsub \ No newline at end of file From 34824e1c7e20fdc5c0bb31e1c93efb316a2d467e Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Sun, 15 May 2022 11:59:31 -0500 Subject: [PATCH 145/170] abi.address lengths need converting to int's --- pyteal/ast/abi/address.py | 6 +++--- pyteal/ast/abi/address_test.py | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pyteal/ast/abi/address.py b/pyteal/ast/abi/address.py index db21b1adc..e68b4ebfc 100644 --- a/pyteal/ast/abi/address.py +++ b/pyteal/ast/abi/address.py @@ -22,7 +22,7 @@ class AddressLength(IntEnum): class AddressTypeSpec(StaticArrayTypeSpec): def __init__(self) -> None: - super().__init__(ByteTypeSpec(), AddressLength.Bytes) + super().__init__(ByteTypeSpec(), int(AddressLength.Bytes)) def new_instance(self) -> "Address": return Address() @@ -64,7 +64,7 @@ def set( case ComputedValue(): pts = value.produced_type_spec() if pts == AddressTypeSpec() or pts == StaticArrayTypeSpec( - ByteTypeSpec(), AddressLength.Bytes + ByteTypeSpec(), int(AddressLength.Bytes) ): return value.store_into(self) @@ -75,7 +75,7 @@ def set( if ( value.type_spec() == AddressTypeSpec() or value.type_spec() - == StaticArrayTypeSpec(ByteTypeSpec(), AddressLength.Bytes) + == StaticArrayTypeSpec(ByteTypeSpec(), int(AddressLength.Bytes)) ): return self.stored_value.store(value.stored_value.load()) diff --git a/pyteal/ast/abi/address_test.py b/pyteal/ast/abi/address_test.py index c3ce39510..90c0e2d66 100644 --- a/pyteal/ast/abi/address_test.py +++ b/pyteal/ast/abi/address_test.py @@ -21,6 +21,10 @@ def test_AddressTypeSpec_byte_length_static(): assert (abi.AddressTypeSpec()).byte_length_static() == abi.AddressLength.Bytes +def test_AddressTypeSpec_lenvth_static(): + assert (abi.AddressTypeSpec()).length_static() == abi.AddressLength.Bytes + + def test_AddressTypeSpec_new_instance(): assert isinstance(abi.AddressTypeSpec().new_instance(), abi.Address) From 733c0dae131969405729b2d392152d5045952b64 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Sun, 15 May 2022 12:12:35 -0500 Subject: [PATCH 146/170] Update Makefile --- Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile b/Makefile index a796378f8..3a8bb1d18 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,6 @@ black: flake8: flake8 $(ALLPY) -# TODO: add `tests` to $MYPY when graviton respects mypy (version 🐈) MYPY = pyteal scripts tests mypy: mypy $(MYPY) From 63e46a4f742f2db76a61b0c1ca105d836a9768fc Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Sun, 15 May 2022 12:19:40 -0500 Subject: [PATCH 147/170] created reports_dir if needed --- tests/integration/pure_logicsig_test.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/integration/pure_logicsig_test.py b/tests/integration/pure_logicsig_test.py index dbf605252..f21442caa 100644 --- a/tests/integration/pure_logicsig_test.py +++ b/tests/integration/pure_logicsig_test.py @@ -93,7 +93,10 @@ def payment_amount(x, y): f"generating a report for (a,p,q) = {a,p,q} with {M, N} dry-run calls and spreadsheet rows" ) filebase = f"factorizer_game_{a}_{p}_{q}" - csvpath = REPORTS_DIR / f"{filebase}.csv" + + reports_dir = REPORTS_DIR / "pure_logicsig" + reports_dir.mkdir(parents=True, exist_ok=True) + csvpath = reports_dir / f"{filebase}.csv" with open(csvpath, "w") as f: f.write(Inspector.csv_report(inputs, inspectors, txns=txns)) From d2af819dfb39fc2d1a685bfe1636af6fb92181ff Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Sun, 15 May 2022 12:29:26 -0500 Subject: [PATCH 148/170] slimmer logic sig integration test --- tests/integration/pure_logicsig_test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/integration/pure_logicsig_test.py b/tests/integration/pure_logicsig_test.py index f21442caa..40e322447 100644 --- a/tests/integration/pure_logicsig_test.py +++ b/tests/integration/pure_logicsig_test.py @@ -20,10 +20,10 @@ DEFAULT = { "A": 3, - "P": 13, - "Q": 13, - "M": 10, - "N": 10, + "P": 5, # 13 + "Q": 7, # 13 + "M": 5, # 10 + "N": 5, # 10 } From 4665c99fece69f4a1b562b90933a7adad8f8f399 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Sun, 15 May 2022 20:49:13 -0500 Subject: [PATCH 149/170] add annotation_type() to TypeSpec hierarchy + roundtrip unit tests passing --- pyteal/ast/abi/address.py | 3 + pyteal/ast/abi/array_dynamic.py | 3 + pyteal/ast/abi/array_static.py | 14 +- pyteal/ast/abi/bool.py | 3 + pyteal/ast/abi/string.py | 3 + pyteal/ast/abi/tuple.py | 28 + pyteal/ast/abi/type.py | 7 +- pyteal/ast/abi/uint.py | 19 + tests/abi_roundtrip.py | 35 +- tests/integration/abi_roundtrip_test.py | 119 +-- .../roundtrip/app_roundtrip_(bool)[10].teal | 187 ++++ ...4],(bool,bool),uint64,address)[]_<13>.teal | 920 ++++++++++++++++++ .../app_roundtrip_address[]_<11>.teal | 712 ++++++++++++++ .../app_roundtrip_bool[3][]_<11>.teal | 295 ++++++ 14 files changed, 2226 insertions(+), 122 deletions(-) create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_(bool)[10].teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_(byte[4],(bool,bool),uint64,address)[]_<13>.teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_address[]_<11>.teal create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_bool[3][]_<11>.teal diff --git a/pyteal/ast/abi/address.py b/pyteal/ast/abi/address.py index e68b4ebfc..10a8ac6bc 100644 --- a/pyteal/ast/abi/address.py +++ b/pyteal/ast/abi/address.py @@ -27,6 +27,9 @@ def __init__(self) -> None: def new_instance(self) -> "Address": return Address() + def annotation_type(self) -> "type[Address]": + return Address + def __str__(self) -> str: return "address" diff --git a/pyteal/ast/abi/array_dynamic.py b/pyteal/ast/abi/array_dynamic.py index 99d0ffffe..33c8e8309 100644 --- a/pyteal/ast/abi/array_dynamic.py +++ b/pyteal/ast/abi/array_dynamic.py @@ -22,6 +22,9 @@ class DynamicArrayTypeSpec(ArrayTypeSpec[T]): def new_instance(self) -> "DynamicArray[T]": return DynamicArray(self) + def annotation_type(self) -> "type[DynamicArray[T]]": + return DynamicArray[self.value_type_spec().annotation_type()] # type: ignore[misc] + def is_length_dynamic(self) -> bool: return True diff --git a/pyteal/ast/abi/array_static.py b/pyteal/ast/abi/array_static.py index 6f8ba4468..7c4ca1ab0 100644 --- a/pyteal/ast/abi/array_static.py +++ b/pyteal/ast/abi/array_static.py @@ -1,11 +1,4 @@ -from typing import ( - Union, - Sequence, - TypeVar, - Generic, - Final, - cast, -) +from typing import Final, Generic, Literal, Sequence, TypeVar, Union, cast from pyteal.errors import TealInputError from pyteal.ast.expr import Expr @@ -30,6 +23,11 @@ def __init__(self, value_type_spec: TypeSpec, array_length: int) -> None: def new_instance(self) -> "StaticArray[T, N]": return StaticArray(self) + def annotation_type(self) -> "type[StaticArray[T, N]]": + return StaticArray[ # type: ignore[misc] + self.value_spec.annotation_type(), Literal[self.array_length] + ] + def length_static(self) -> int: """Get the size of this static array type. diff --git a/pyteal/ast/abi/bool.py b/pyteal/ast/abi/bool.py index f1b8a6065..52b8e513c 100644 --- a/pyteal/ast/abi/bool.py +++ b/pyteal/ast/abi/bool.py @@ -17,6 +17,9 @@ class BoolTypeSpec(TypeSpec): def new_instance(self) -> "Bool": return Bool() + def annotation_type(self) -> "type[Bool]": + return Bool + def is_dynamic(self) -> bool: # Only accurate if this value is alone, since up to 8 consecutive bools will fit into a single byte return False diff --git a/pyteal/ast/abi/string.py b/pyteal/ast/abi/string.py index cddaa4e7d..0f125db43 100644 --- a/pyteal/ast/abi/string.py +++ b/pyteal/ast/abi/string.py @@ -29,6 +29,9 @@ def __init__(self) -> None: def new_instance(self) -> "String": return String() + def annotation_type(self) -> "type[String]": + return String + def __str__(self) -> str: return "string" diff --git a/pyteal/ast/abi/tuple.py b/pyteal/ast/abi/tuple.py index a7d09065e..78c4b0b80 100644 --- a/pyteal/ast/abi/tuple.py +++ b/pyteal/ast/abi/tuple.py @@ -1,3 +1,4 @@ +from __future__ import annotations from typing import ( List, Sequence, @@ -222,6 +223,33 @@ def length_static(self) -> int: def new_instance(self) -> "Tuple": return Tuple(self) + def annotation_type(self) -> type[Tuple]: + vtses = self.value_type_specs() + + def annotater(): + return [x.annotation_type() for x in vtses] + + match len(vtses): + case 0: + return Tuple0 + case 1: + v0 = annotater()[0] + return Tuple1[v0] # type: ignore[valid-type] + case 2: + v0, v1 = annotater() + return Tuple2[v0, v1] # type: ignore[valid-type] + case 3: + v0, v1, v2 = annotater() + return Tuple3[v0, v1, v2] # type: ignore[valid-type] + case 4: + v0, v1, v2, v3 = annotater() + return Tuple4[v0, v1, v2, v3] # type: ignore[valid-type] + case 5: + v0, v1, v2, v3, v4 = annotater() + return Tuple5[v0, v1, v2, v3, v4] # type: ignore[valid-type] + + raise TypeError(f"Cannot annotate tuple of length {len(vtses)}") + def is_dynamic(self) -> bool: return any(type_spec.is_dynamic() for type_spec in self.value_type_specs()) diff --git a/pyteal/ast/abi/type.py b/pyteal/ast/abi/type.py index 52a57fec8..55bd1b3ab 100644 --- a/pyteal/ast/abi/type.py +++ b/pyteal/ast/abi/type.py @@ -19,6 +19,11 @@ def new_instance(self) -> "BaseType": """Create a new instance of the specified type.""" pass + @abstractmethod + def annotation_type(self) -> "type[BaseType]": + """Get the annotation type associated with this spec""" + pass + @abstractmethod def is_dynamic(self) -> bool: """Check if this ABI type is dynamic. @@ -140,7 +145,7 @@ def _set_with_computed_type(self, value: "ComputedValue") -> Expr: BaseType.__module__ = "pyteal" -T = TypeVar("T", bound=BaseType) +T = TypeVar("T", bound=BaseType, contravariant=True) class ComputedValue(ABC, Generic[T]): diff --git a/pyteal/ast/abi/uint.py b/pyteal/ast/abi/uint.py index ea8e8eded..1474ab7b2 100644 --- a/pyteal/ast/abi/uint.py +++ b/pyteal/ast/abi/uint.py @@ -123,6 +123,10 @@ def __init__(self, bit_size: int) -> None: def new_instance(self) -> "Uint": pass + @abstractmethod + def annotation_type(self) -> "type[Uint]": + pass + def bit_size(self) -> int: """Get the bit size of this uint type""" return self.size @@ -157,6 +161,9 @@ def __init__(self) -> None: def new_instance(self) -> "Byte": return Byte() + def annotation_type(self) -> "type[Byte]": + return Byte + def __str__(self) -> str: return "byte" @@ -171,6 +178,9 @@ def __init__(self) -> None: def new_instance(self) -> "Uint8": return Uint8() + def annotation_type(self) -> "type[Uint8]": + return Uint8 + Uint8TypeSpec.__module__ = "pyteal" @@ -182,6 +192,9 @@ def __init__(self) -> None: def new_instance(self) -> "Uint16": return Uint16() + def annotation_type(self) -> "type[Uint16]": + return Uint16 + Uint16TypeSpec.__module__ = "pyteal" @@ -193,6 +206,9 @@ def __init__(self) -> None: def new_instance(self) -> "Uint32": return Uint32() + def annotation_type(self) -> "type[Uint32]": + return Uint32 + Uint32TypeSpec.__module__ = "pyteal" @@ -204,6 +220,9 @@ def __init__(self) -> None: def new_instance(self) -> "Uint64": return Uint64() + def annotation_type(self) -> "type[Uint64]": + return Uint64 + Uint32TypeSpec.__module__ = "pyteal" diff --git a/tests/abi_roundtrip.py b/tests/abi_roundtrip.py index c89fd297b..30aa2507b 100644 --- a/tests/abi_roundtrip.py +++ b/tests/abi_roundtrip.py @@ -1,4 +1,4 @@ -from typing import Generic, TypeVar +from typing import Generic, TypeVar, cast import pyteal as pt from pyteal import abi @@ -11,10 +11,25 @@ class ABIRoundtrip(Generic[T]): - def __init__(self, t: type[T], length: int | None = None): - self.annotation: type[T] = t - self.type_spec: abi.TypeSpec = abi.type_spec_from_annotation(self.annotation) - self.instance: abi.BaseType = self.type_spec.new_instance() + def __init__( + self, + t: type[T] | None, + length: int | None = None, + annotation_instance: T = None, + ): + self.annotation: type[T] + self.type_spec: abi.TypeSpec + self.instance: abi.BaseType + + if annotation_instance: + assert t is None + self.instance = annotation_instance + self.type_spec = annotation_instance.type_spec() + self.annotation = self.type_spec.annotation_type() + else: + self.annotation = cast(type[T], t) + self.type_spec = abi.type_spec_from_annotation(self.annotation) + self.instance = self.type_spec.new_instance() self.length: int | None = length @@ -70,7 +85,9 @@ def numerical_comp(x: self.annotation, *, output: self.annotation): # type: ign def tuple_comp_factory(self) -> pt.ABIReturnSubroutine: value_type_specs: list[abi.TypeSpec] = self.type_spec.value_type_specs() # type: ignore[attr-defined] insts = [vts.new_instance() for vts in value_type_specs] - roundtrips = [ABIRoundtrip(type(inst)) for inst in insts] + roundtrips: list[ABIRoundtrip[T]] = [ + ABIRoundtrip(None, annotation_instance=inst) for inst in insts # type: ignore[arg-type] + ] @pt.ABIReturnSubroutine def tuple_complement(x: self.annotation, *, output: self.annotation): # type: ignore[name-defined] @@ -94,8 +111,10 @@ def array_comp_factory(self) -> pt.ABIReturnSubroutine: self.length = DEFAULT_DYNAMIC_ARRAY_LENGTH internal_type_spec = self.type_spec.value_type_spec() # type: ignore[attr-defined] - internal_ann = type(internal_type_spec.new_instance()) - comp_func = ABIRoundtrip(internal_ann).complement_factory() + internal_ann_inst = internal_type_spec.new_instance() + comp_func = ABIRoundtrip( + None, annotation_instance=internal_ann_inst + ).complement_factory() @pt.ABIReturnSubroutine def array_complement(x: self.annotation, *, output: self.annotation): # type: ignore[name-defined] diff --git a/tests/integration/abi_roundtrip_test.py b/tests/integration/abi_roundtrip_test.py index eec7bc75e..895f94a8e 100644 --- a/tests/integration/abi_roundtrip_test.py +++ b/tests/integration/abi_roundtrip_test.py @@ -7,7 +7,6 @@ from pyteal import abi import pyteal as pt -# from tests.blackbox import Blackbox, PyTealDryRunExecutor from tests.compile_asserts import assert_teal_as_expected from tests.abi_roundtrip import ABIRoundtrip @@ -15,98 +14,6 @@ PATH = Path.cwd() / "tests" / "integration" FIXTURES = PATH / "teal" GENERATED = PATH / "generated" - - -# def max_int(bit_size): -# return (1 << bit_size) - 1 - - -# @pt.ABIReturnSubroutine -# def bool_comp(x: abi.Bool, *, output: abi.Bool): -# return output.set(pt.Not(x.get())) - - -# def numerical_comp_factory(t: type[T], bit_size: int) -> Callable: -# @pt.ABIReturnSubroutine -# def func(x: t, *, output: t): # type: ignore[valid-type] -# x = cast(abi.BaseType, x) -# max_uint = pt.Int(max_int(bit_size)) -# return output.set(max_uint - x.get()) # type: ignore[attr-defined] - -# return func - - -# def tuple_comp_factory(t: type[T], value_type_specs: list[abi.TypeSpec]) -> Callable: -# def fix_type(e): -# return cast(abi.Tuple[T], e) - -# @pt.ABIReturnSubroutine -# def tuple_complement(x: t, *, output: t): # type: ignore[valid-type] -# value_types = [vts.new_instance() for vts in value_type_specs] -# setters = [ -# fix_type(vts).set(fix_type(x)[i]) for i, vts in enumerate(value_types) -# ] -# comp_funcs = [ -# complement_factory(type(fix_type(vts)), -1) for vts in value_types -# ] -# compers = [vts.set(comp_funcs[i](vts)) for i, vts in enumerate(value_types)] -# return pt.Seq(*(setters + compers + [output.set(*value_types)])) - -# return tuple_complement - - -# def array_comp_factory( -# t: type[T], value_type_spec: abi.TypeSpec, length: int -# ) -> Callable: -# comp_func = complement_factory(type(value_type_spec.new_instance()), -1) -# ts = abi.type_spec_from_annotation(t) -# if length != -1: -# assert ts.is_length_dynamic() -# else: -# length = ts.length_static() - -# @pt.ABIReturnSubroutine -# def array_complement(x: t, *, output: t): -# value_types = [value_type_spec.new_instance() for _ in range(length)] -# setters = [vts.set(x[i]) for i, vts in enumerate(value_types)] -# compers = [vts.set(comp_func(vts)) for vts in value_types] -# return pt.Seq(*(setters + compers + [output.set(value_types)])) - -# return array_complement - - -# def complement_factory(t: T, dynamic_length: int) -> Callable: -# ts = abi.type_spec_from_annotation(t) -# if isinstance(ts, abi.BoolTypeSpec): -# return bool_comp -# if isinstance(ts, abi.UintTypeSpec): -# return numerical_comp_factory(t, ts.bit_size()) -# if isinstance(ts, abi.TupleTypeSpec): -# return tuple_comp_factory(t, ts.value_type_specs()) -# if isinstance(ts, abi.ArrayTypeSpec): -# return array_comp_factory(t, ts.value_type_spec(), dynamic_length) - -# raise ValueError(f"uh-oh!!! didn't handle type {t}") - - -# def roundtrip_factory(t: type[T], dynamic_length: int) -> Callable: -# comp = complement_factory(t, dynamic_length) - -# @Blackbox(input_types=[None]) -# @pt.ABIReturnSubroutine -# def round_tripper(x: t, *, output: abi.Tuple2[t, t]): -# y = abi.make(t) -# z = abi.make(t) -# return pt.Seq(y.set(comp(x)), z.set(comp(y)), output.set(y, z)) - -# return round_tripper - - -# def roundtrip_pytealer(t: type[T], dynamic_length: int): -# roundtrip = roundtrip_factory(t, dynamic_length) -# return PyTealDryRunExecutor(roundtrip, pt.Mode.Application) - - ABI_TYPES = [ abi.Address, abi.Bool, @@ -141,18 +48,20 @@ (abi.DynamicArray[abi.Uint64], 0), (abi.DynamicArray[abi.Uint64], 1), (abi.DynamicArray[abi.Uint64], 42), - # abi.StaticArray[abi.Tuple1[abi.Bool], Literal[10]], - # ( - # abi.DynamicArray[ - # abi.Tuple4[ - # abi.StaticArray[abi.Byte, Literal[4]], - # abi.Tuple2[abi.Bool, abi.Bool], - # abi.Uint64, - # abi.Address, - # ] - # ], - # 13, - # ), + (abi.DynamicArray[abi.Address], 11), + (abi.DynamicArray[abi.StaticArray[abi.Bool, Literal[3]]], 11), + abi.StaticArray[abi.Tuple1[abi.Bool], Literal[10]], + ( + abi.DynamicArray[ + abi.Tuple4[ + abi.StaticArray[abi.Byte, Literal[4]], + abi.Tuple2[abi.Bool, abi.Bool], + abi.Uint64, + abi.Address, + ] + ], + 13, + ), ] diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(bool)[10].teal b/tests/integration/teal/roundtrip/app_roundtrip_(bool)[10].teal new file mode 100644 index 000000000..56210aa2a --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_(bool)[10].teal @@ -0,0 +1,187 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 2 +load 2 +callsub roundtripper_2 +store 1 +byte 0x151F7C75 +load 1 +concat +log +int 1 +return + +// tuple_complement +tuplecomplement_0: +store 19 +load 19 +int 0 +getbit +store 0 +load 0 +callsub boolcomp_3 +store 0 +byte 0x00 +int 0 +load 0 +setbit +store 20 +load 20 +retsub + +// array_complement +arraycomplement_1: +store 7 +load 7 +int 1 +int 0 +* +int 1 +extract3 +store 9 +load 7 +int 1 +int 1 +* +int 1 +extract3 +store 10 +load 7 +int 1 +int 2 +* +int 1 +extract3 +store 11 +load 7 +int 1 +int 3 +* +int 1 +extract3 +store 12 +load 7 +int 1 +int 4 +* +int 1 +extract3 +store 13 +load 7 +int 1 +int 5 +* +int 1 +extract3 +store 14 +load 7 +int 1 +int 6 +* +int 1 +extract3 +store 15 +load 7 +int 1 +int 7 +* +int 1 +extract3 +store 16 +load 7 +int 1 +int 8 +* +int 1 +extract3 +store 17 +load 7 +int 1 +int 9 +* +int 1 +extract3 +store 18 +load 9 +callsub tuplecomplement_0 +store 9 +load 10 +callsub tuplecomplement_0 +store 10 +load 11 +callsub tuplecomplement_0 +store 11 +load 12 +callsub tuplecomplement_0 +store 12 +load 13 +callsub tuplecomplement_0 +store 13 +load 14 +callsub tuplecomplement_0 +store 14 +load 15 +callsub tuplecomplement_0 +store 15 +load 16 +callsub tuplecomplement_0 +store 16 +load 17 +callsub tuplecomplement_0 +store 17 +load 18 +callsub tuplecomplement_0 +store 18 +load 9 +load 10 +concat +load 11 +concat +load 12 +concat +load 13 +concat +load 14 +concat +load 15 +concat +load 16 +concat +load 17 +concat +load 18 +concat +store 8 +load 8 +retsub + +// round_tripper +roundtripper_2: +store 3 +load 3 +callsub arraycomplement_1 +store 5 +load 5 +callsub arraycomplement_1 +store 6 +load 3 +load 5 +concat +load 6 +concat +store 4 +load 4 +retsub + +// bool_comp +boolcomp_3: +store 21 +load 21 +! +store 22 +load 22 +int 2 +< +assert +load 22 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(byte[4],(bool,bool),uint64,address)[]_<13>.teal b/tests/integration/teal/roundtrip/app_roundtrip_(byte[4],(bool,bool),uint64,address)[]_<13>.teal new file mode 100644 index 000000000..040c40a30 --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_(byte[4],(bool,bool),uint64,address)[]_<13>.teal @@ -0,0 +1,920 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 5 +load 5 +callsub roundtripper_2 +store 4 +byte 0x151F7C75 +load 4 +concat +log +int 1 +return + +// tuple_complement +tuplecomplement_0: +store 25 +load 25 +extract 0 4 +store 0 +load 25 +extract 4 1 +store 1 +load 25 +int 5 +extract_uint64 +store 2 +load 25 +extract 13 0 +store 3 +load 0 +callsub arraycomplement_4 +store 0 +load 1 +callsub tuplecomplement_5 +store 1 +load 2 +callsub numericalcomp_6 +store 2 +load 3 +callsub arraycomplement_8 +store 3 +load 0 +load 1 +concat +load 2 +itob +concat +load 3 +concat +store 26 +load 26 +retsub + +// array_complement +arraycomplement_1: +store 10 +load 10 +int 45 +int 0 +* +int 2 ++ +int 45 +extract3 +store 12 +load 10 +int 45 +int 1 +* +int 2 ++ +int 45 +extract3 +store 13 +load 10 +int 45 +int 2 +* +int 2 ++ +int 45 +extract3 +store 14 +load 10 +int 45 +int 3 +* +int 2 ++ +int 45 +extract3 +store 15 +load 10 +int 45 +int 4 +* +int 2 ++ +int 45 +extract3 +store 16 +load 10 +int 45 +int 5 +* +int 2 ++ +int 45 +extract3 +store 17 +load 10 +int 45 +int 6 +* +int 2 ++ +int 45 +extract3 +store 18 +load 10 +int 45 +int 7 +* +int 2 ++ +int 45 +extract3 +store 19 +load 10 +int 45 +int 8 +* +int 2 ++ +int 45 +extract3 +store 20 +load 10 +int 45 +int 9 +* +int 2 ++ +int 45 +extract3 +store 21 +load 10 +int 45 +int 10 +* +int 2 ++ +int 45 +extract3 +store 22 +load 10 +int 45 +int 11 +* +int 2 ++ +int 45 +extract3 +store 23 +load 10 +int 45 +int 12 +* +int 2 ++ +int 45 +extract3 +store 24 +load 12 +callsub tuplecomplement_0 +store 12 +load 13 +callsub tuplecomplement_0 +store 13 +load 14 +callsub tuplecomplement_0 +store 14 +load 15 +callsub tuplecomplement_0 +store 15 +load 16 +callsub tuplecomplement_0 +store 16 +load 17 +callsub tuplecomplement_0 +store 17 +load 18 +callsub tuplecomplement_0 +store 18 +load 19 +callsub tuplecomplement_0 +store 19 +load 20 +callsub tuplecomplement_0 +store 20 +load 21 +callsub tuplecomplement_0 +store 21 +load 22 +callsub tuplecomplement_0 +store 22 +load 23 +callsub tuplecomplement_0 +store 23 +load 24 +callsub tuplecomplement_0 +store 24 +int 13 +store 81 +load 81 +itob +extract 6 0 +load 12 +load 13 +concat +load 14 +concat +load 15 +concat +load 16 +concat +load 17 +concat +load 18 +concat +load 19 +concat +load 20 +concat +load 21 +concat +load 22 +concat +load 23 +concat +load 24 +concat +concat +store 11 +load 11 +retsub + +// round_tripper +roundtripper_2: +store 6 +load 6 +callsub arraycomplement_1 +store 8 +load 8 +callsub arraycomplement_1 +store 9 +load 6 +store 85 +load 85 +store 84 +int 6 +store 82 +load 82 +load 85 +len ++ +store 83 +load 83 +int 65536 +< +assert +load 82 +itob +extract 6 0 +load 8 +store 85 +load 84 +load 85 +concat +store 84 +load 83 +store 82 +load 82 +load 85 +len ++ +store 83 +load 83 +int 65536 +< +assert +load 82 +itob +extract 6 0 +concat +load 9 +store 85 +load 84 +load 85 +concat +store 84 +load 83 +store 82 +load 82 +itob +extract 6 0 +concat +load 84 +concat +store 7 +load 7 +retsub + +// numerical_comp +numericalcomp_3: +store 35 +int 255 +load 35 +- +store 36 +load 36 +int 256 +< +assert +load 36 +retsub + +// array_complement +arraycomplement_4: +store 29 +load 29 +int 1 +int 0 +* +getbyte +store 31 +load 29 +int 1 +int 1 +* +getbyte +store 32 +load 29 +int 1 +int 2 +* +getbyte +store 33 +load 29 +int 1 +int 3 +* +getbyte +store 34 +load 31 +callsub numericalcomp_3 +store 31 +load 32 +callsub numericalcomp_3 +store 32 +load 33 +callsub numericalcomp_3 +store 33 +load 34 +callsub numericalcomp_3 +store 34 +byte 0x00 +int 0 +load 31 +setbyte +byte 0x00 +int 0 +load 32 +setbyte +concat +byte 0x00 +int 0 +load 33 +setbyte +concat +byte 0x00 +int 0 +load 34 +setbyte +concat +store 30 +load 30 +retsub + +// tuple_complement +tuplecomplement_5: +store 37 +load 37 +int 0 +getbit +store 27 +load 37 +int 1 +getbit +store 28 +load 27 +callsub boolcomp_9 +store 27 +load 28 +callsub boolcomp_10 +store 28 +byte 0x00 +int 0 +load 27 +setbit +int 1 +load 28 +setbit +store 38 +load 38 +retsub + +// numerical_comp +numericalcomp_6: +store 43 +int 18446744073709551615 +load 43 +- +store 44 +load 44 +retsub + +// numerical_comp +numericalcomp_7: +store 79 +int 255 +load 79 +- +store 80 +load 80 +int 256 +< +assert +load 80 +retsub + +// array_complement +arraycomplement_8: +store 45 +load 45 +int 1 +int 0 +* +getbyte +store 47 +load 45 +int 1 +int 1 +* +getbyte +store 48 +load 45 +int 1 +int 2 +* +getbyte +store 49 +load 45 +int 1 +int 3 +* +getbyte +store 50 +load 45 +int 1 +int 4 +* +getbyte +store 51 +load 45 +int 1 +int 5 +* +getbyte +store 52 +load 45 +int 1 +int 6 +* +getbyte +store 53 +load 45 +int 1 +int 7 +* +getbyte +store 54 +load 45 +int 1 +int 8 +* +getbyte +store 55 +load 45 +int 1 +int 9 +* +getbyte +store 56 +load 45 +int 1 +int 10 +* +getbyte +store 57 +load 45 +int 1 +int 11 +* +getbyte +store 58 +load 45 +int 1 +int 12 +* +getbyte +store 59 +load 45 +int 1 +int 13 +* +getbyte +store 60 +load 45 +int 1 +int 14 +* +getbyte +store 61 +load 45 +int 1 +int 15 +* +getbyte +store 62 +load 45 +int 1 +int 16 +* +getbyte +store 63 +load 45 +int 1 +int 17 +* +getbyte +store 64 +load 45 +int 1 +int 18 +* +getbyte +store 65 +load 45 +int 1 +int 19 +* +getbyte +store 66 +load 45 +int 1 +int 20 +* +getbyte +store 67 +load 45 +int 1 +int 21 +* +getbyte +store 68 +load 45 +int 1 +int 22 +* +getbyte +store 69 +load 45 +int 1 +int 23 +* +getbyte +store 70 +load 45 +int 1 +int 24 +* +getbyte +store 71 +load 45 +int 1 +int 25 +* +getbyte +store 72 +load 45 +int 1 +int 26 +* +getbyte +store 73 +load 45 +int 1 +int 27 +* +getbyte +store 74 +load 45 +int 1 +int 28 +* +getbyte +store 75 +load 45 +int 1 +int 29 +* +getbyte +store 76 +load 45 +int 1 +int 30 +* +getbyte +store 77 +load 45 +int 1 +int 31 +* +getbyte +store 78 +load 47 +callsub numericalcomp_7 +store 47 +load 48 +callsub numericalcomp_7 +store 48 +load 49 +callsub numericalcomp_7 +store 49 +load 50 +callsub numericalcomp_7 +store 50 +load 51 +callsub numericalcomp_7 +store 51 +load 52 +callsub numericalcomp_7 +store 52 +load 53 +callsub numericalcomp_7 +store 53 +load 54 +callsub numericalcomp_7 +store 54 +load 55 +callsub numericalcomp_7 +store 55 +load 56 +callsub numericalcomp_7 +store 56 +load 57 +callsub numericalcomp_7 +store 57 +load 58 +callsub numericalcomp_7 +store 58 +load 59 +callsub numericalcomp_7 +store 59 +load 60 +callsub numericalcomp_7 +store 60 +load 61 +callsub numericalcomp_7 +store 61 +load 62 +callsub numericalcomp_7 +store 62 +load 63 +callsub numericalcomp_7 +store 63 +load 64 +callsub numericalcomp_7 +store 64 +load 65 +callsub numericalcomp_7 +store 65 +load 66 +callsub numericalcomp_7 +store 66 +load 67 +callsub numericalcomp_7 +store 67 +load 68 +callsub numericalcomp_7 +store 68 +load 69 +callsub numericalcomp_7 +store 69 +load 70 +callsub numericalcomp_7 +store 70 +load 71 +callsub numericalcomp_7 +store 71 +load 72 +callsub numericalcomp_7 +store 72 +load 73 +callsub numericalcomp_7 +store 73 +load 74 +callsub numericalcomp_7 +store 74 +load 75 +callsub numericalcomp_7 +store 75 +load 76 +callsub numericalcomp_7 +store 76 +load 77 +callsub numericalcomp_7 +store 77 +load 78 +callsub numericalcomp_7 +store 78 +byte 0x00 +int 0 +load 47 +setbyte +byte 0x00 +int 0 +load 48 +setbyte +concat +byte 0x00 +int 0 +load 49 +setbyte +concat +byte 0x00 +int 0 +load 50 +setbyte +concat +byte 0x00 +int 0 +load 51 +setbyte +concat +byte 0x00 +int 0 +load 52 +setbyte +concat +byte 0x00 +int 0 +load 53 +setbyte +concat +byte 0x00 +int 0 +load 54 +setbyte +concat +byte 0x00 +int 0 +load 55 +setbyte +concat +byte 0x00 +int 0 +load 56 +setbyte +concat +byte 0x00 +int 0 +load 57 +setbyte +concat +byte 0x00 +int 0 +load 58 +setbyte +concat +byte 0x00 +int 0 +load 59 +setbyte +concat +byte 0x00 +int 0 +load 60 +setbyte +concat +byte 0x00 +int 0 +load 61 +setbyte +concat +byte 0x00 +int 0 +load 62 +setbyte +concat +byte 0x00 +int 0 +load 63 +setbyte +concat +byte 0x00 +int 0 +load 64 +setbyte +concat +byte 0x00 +int 0 +load 65 +setbyte +concat +byte 0x00 +int 0 +load 66 +setbyte +concat +byte 0x00 +int 0 +load 67 +setbyte +concat +byte 0x00 +int 0 +load 68 +setbyte +concat +byte 0x00 +int 0 +load 69 +setbyte +concat +byte 0x00 +int 0 +load 70 +setbyte +concat +byte 0x00 +int 0 +load 71 +setbyte +concat +byte 0x00 +int 0 +load 72 +setbyte +concat +byte 0x00 +int 0 +load 73 +setbyte +concat +byte 0x00 +int 0 +load 74 +setbyte +concat +byte 0x00 +int 0 +load 75 +setbyte +concat +byte 0x00 +int 0 +load 76 +setbyte +concat +byte 0x00 +int 0 +load 77 +setbyte +concat +byte 0x00 +int 0 +load 78 +setbyte +concat +store 46 +load 46 +retsub + +// bool_comp +boolcomp_9: +store 39 +load 39 +! +store 40 +load 40 +int 2 +< +assert +load 40 +retsub + +// bool_comp +boolcomp_10: +store 41 +load 41 +! +store 42 +load 42 +int 2 +< +assert +load 42 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_address[]_<11>.teal b/tests/integration/teal/roundtrip/app_roundtrip_address[]_<11>.teal new file mode 100644 index 000000000..6d972713e --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_address[]_<11>.teal @@ -0,0 +1,712 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 1 +load 1 +callsub roundtripper_3 +store 0 +byte 0x151F7C75 +load 0 +concat +log +int 1 +return + +// numerical_comp +numericalcomp_0: +store 53 +int 255 +load 53 +- +store 54 +load 54 +int 256 +< +assert +load 54 +retsub + +// array_complement +arraycomplement_1: +store 19 +load 19 +int 1 +int 0 +* +getbyte +store 21 +load 19 +int 1 +int 1 +* +getbyte +store 22 +load 19 +int 1 +int 2 +* +getbyte +store 23 +load 19 +int 1 +int 3 +* +getbyte +store 24 +load 19 +int 1 +int 4 +* +getbyte +store 25 +load 19 +int 1 +int 5 +* +getbyte +store 26 +load 19 +int 1 +int 6 +* +getbyte +store 27 +load 19 +int 1 +int 7 +* +getbyte +store 28 +load 19 +int 1 +int 8 +* +getbyte +store 29 +load 19 +int 1 +int 9 +* +getbyte +store 30 +load 19 +int 1 +int 10 +* +getbyte +store 31 +load 19 +int 1 +int 11 +* +getbyte +store 32 +load 19 +int 1 +int 12 +* +getbyte +store 33 +load 19 +int 1 +int 13 +* +getbyte +store 34 +load 19 +int 1 +int 14 +* +getbyte +store 35 +load 19 +int 1 +int 15 +* +getbyte +store 36 +load 19 +int 1 +int 16 +* +getbyte +store 37 +load 19 +int 1 +int 17 +* +getbyte +store 38 +load 19 +int 1 +int 18 +* +getbyte +store 39 +load 19 +int 1 +int 19 +* +getbyte +store 40 +load 19 +int 1 +int 20 +* +getbyte +store 41 +load 19 +int 1 +int 21 +* +getbyte +store 42 +load 19 +int 1 +int 22 +* +getbyte +store 43 +load 19 +int 1 +int 23 +* +getbyte +store 44 +load 19 +int 1 +int 24 +* +getbyte +store 45 +load 19 +int 1 +int 25 +* +getbyte +store 46 +load 19 +int 1 +int 26 +* +getbyte +store 47 +load 19 +int 1 +int 27 +* +getbyte +store 48 +load 19 +int 1 +int 28 +* +getbyte +store 49 +load 19 +int 1 +int 29 +* +getbyte +store 50 +load 19 +int 1 +int 30 +* +getbyte +store 51 +load 19 +int 1 +int 31 +* +getbyte +store 52 +load 21 +callsub numericalcomp_0 +store 21 +load 22 +callsub numericalcomp_0 +store 22 +load 23 +callsub numericalcomp_0 +store 23 +load 24 +callsub numericalcomp_0 +store 24 +load 25 +callsub numericalcomp_0 +store 25 +load 26 +callsub numericalcomp_0 +store 26 +load 27 +callsub numericalcomp_0 +store 27 +load 28 +callsub numericalcomp_0 +store 28 +load 29 +callsub numericalcomp_0 +store 29 +load 30 +callsub numericalcomp_0 +store 30 +load 31 +callsub numericalcomp_0 +store 31 +load 32 +callsub numericalcomp_0 +store 32 +load 33 +callsub numericalcomp_0 +store 33 +load 34 +callsub numericalcomp_0 +store 34 +load 35 +callsub numericalcomp_0 +store 35 +load 36 +callsub numericalcomp_0 +store 36 +load 37 +callsub numericalcomp_0 +store 37 +load 38 +callsub numericalcomp_0 +store 38 +load 39 +callsub numericalcomp_0 +store 39 +load 40 +callsub numericalcomp_0 +store 40 +load 41 +callsub numericalcomp_0 +store 41 +load 42 +callsub numericalcomp_0 +store 42 +load 43 +callsub numericalcomp_0 +store 43 +load 44 +callsub numericalcomp_0 +store 44 +load 45 +callsub numericalcomp_0 +store 45 +load 46 +callsub numericalcomp_0 +store 46 +load 47 +callsub numericalcomp_0 +store 47 +load 48 +callsub numericalcomp_0 +store 48 +load 49 +callsub numericalcomp_0 +store 49 +load 50 +callsub numericalcomp_0 +store 50 +load 51 +callsub numericalcomp_0 +store 51 +load 52 +callsub numericalcomp_0 +store 52 +byte 0x00 +int 0 +load 21 +setbyte +byte 0x00 +int 0 +load 22 +setbyte +concat +byte 0x00 +int 0 +load 23 +setbyte +concat +byte 0x00 +int 0 +load 24 +setbyte +concat +byte 0x00 +int 0 +load 25 +setbyte +concat +byte 0x00 +int 0 +load 26 +setbyte +concat +byte 0x00 +int 0 +load 27 +setbyte +concat +byte 0x00 +int 0 +load 28 +setbyte +concat +byte 0x00 +int 0 +load 29 +setbyte +concat +byte 0x00 +int 0 +load 30 +setbyte +concat +byte 0x00 +int 0 +load 31 +setbyte +concat +byte 0x00 +int 0 +load 32 +setbyte +concat +byte 0x00 +int 0 +load 33 +setbyte +concat +byte 0x00 +int 0 +load 34 +setbyte +concat +byte 0x00 +int 0 +load 35 +setbyte +concat +byte 0x00 +int 0 +load 36 +setbyte +concat +byte 0x00 +int 0 +load 37 +setbyte +concat +byte 0x00 +int 0 +load 38 +setbyte +concat +byte 0x00 +int 0 +load 39 +setbyte +concat +byte 0x00 +int 0 +load 40 +setbyte +concat +byte 0x00 +int 0 +load 41 +setbyte +concat +byte 0x00 +int 0 +load 42 +setbyte +concat +byte 0x00 +int 0 +load 43 +setbyte +concat +byte 0x00 +int 0 +load 44 +setbyte +concat +byte 0x00 +int 0 +load 45 +setbyte +concat +byte 0x00 +int 0 +load 46 +setbyte +concat +byte 0x00 +int 0 +load 47 +setbyte +concat +byte 0x00 +int 0 +load 48 +setbyte +concat +byte 0x00 +int 0 +load 49 +setbyte +concat +byte 0x00 +int 0 +load 50 +setbyte +concat +byte 0x00 +int 0 +load 51 +setbyte +concat +byte 0x00 +int 0 +load 52 +setbyte +concat +store 20 +load 20 +retsub + +// array_complement +arraycomplement_2: +store 6 +load 6 +int 32 +int 0 +* +int 2 ++ +int 32 +extract3 +store 8 +load 6 +int 32 +int 1 +* +int 2 ++ +int 32 +extract3 +store 9 +load 6 +int 32 +int 2 +* +int 2 ++ +int 32 +extract3 +store 10 +load 6 +int 32 +int 3 +* +int 2 ++ +int 32 +extract3 +store 11 +load 6 +int 32 +int 4 +* +int 2 ++ +int 32 +extract3 +store 12 +load 6 +int 32 +int 5 +* +int 2 ++ +int 32 +extract3 +store 13 +load 6 +int 32 +int 6 +* +int 2 ++ +int 32 +extract3 +store 14 +load 6 +int 32 +int 7 +* +int 2 ++ +int 32 +extract3 +store 15 +load 6 +int 32 +int 8 +* +int 2 ++ +int 32 +extract3 +store 16 +load 6 +int 32 +int 9 +* +int 2 ++ +int 32 +extract3 +store 17 +load 6 +int 32 +int 10 +* +int 2 ++ +int 32 +extract3 +store 18 +load 8 +callsub arraycomplement_1 +store 8 +load 9 +callsub arraycomplement_1 +store 9 +load 10 +callsub arraycomplement_1 +store 10 +load 11 +callsub arraycomplement_1 +store 11 +load 12 +callsub arraycomplement_1 +store 12 +load 13 +callsub arraycomplement_1 +store 13 +load 14 +callsub arraycomplement_1 +store 14 +load 15 +callsub arraycomplement_1 +store 15 +load 16 +callsub arraycomplement_1 +store 16 +load 17 +callsub arraycomplement_1 +store 17 +load 18 +callsub arraycomplement_1 +store 18 +int 11 +store 55 +load 55 +itob +extract 6 0 +load 8 +load 9 +concat +load 10 +concat +load 11 +concat +load 12 +concat +load 13 +concat +load 14 +concat +load 15 +concat +load 16 +concat +load 17 +concat +load 18 +concat +concat +store 7 +load 7 +retsub + +// round_tripper +roundtripper_3: +store 2 +load 2 +callsub arraycomplement_2 +store 4 +load 4 +callsub arraycomplement_2 +store 5 +load 2 +store 59 +load 59 +store 58 +int 6 +store 56 +load 56 +load 59 +len ++ +store 57 +load 57 +int 65536 +< +assert +load 56 +itob +extract 6 0 +load 4 +store 59 +load 58 +load 59 +concat +store 58 +load 57 +store 56 +load 56 +load 59 +len ++ +store 57 +load 57 +int 65536 +< +assert +load 56 +itob +extract 6 0 +concat +load 5 +store 59 +load 58 +load 59 +concat +store 58 +load 57 +store 56 +load 56 +itob +extract 6 0 +concat +load 58 +concat +store 3 +load 3 +retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_bool[3][]_<11>.teal b/tests/integration/teal/roundtrip/app_roundtrip_bool[3][]_<11>.teal new file mode 100644 index 000000000..0c8f9c611 --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_bool[3][]_<11>.teal @@ -0,0 +1,295 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 1 +load 1 +callsub roundtripper_3 +store 0 +byte 0x151F7C75 +load 0 +concat +log +int 1 +return + +// bool_comp +boolcomp_0: +store 24 +load 24 +! +store 25 +load 25 +int 2 +< +assert +load 25 +retsub + +// array_complement +arraycomplement_1: +store 19 +load 19 +int 0 +getbit +store 21 +load 19 +int 1 +getbit +store 22 +load 19 +int 2 +getbit +store 23 +load 21 +callsub boolcomp_0 +store 21 +load 22 +callsub boolcomp_0 +store 22 +load 23 +callsub boolcomp_0 +store 23 +byte 0x00 +int 0 +load 21 +setbit +int 1 +load 22 +setbit +int 2 +load 23 +setbit +store 20 +load 20 +retsub + +// array_complement +arraycomplement_2: +store 6 +load 6 +int 1 +int 0 +* +int 2 ++ +int 1 +extract3 +store 8 +load 6 +int 1 +int 1 +* +int 2 ++ +int 1 +extract3 +store 9 +load 6 +int 1 +int 2 +* +int 2 ++ +int 1 +extract3 +store 10 +load 6 +int 1 +int 3 +* +int 2 ++ +int 1 +extract3 +store 11 +load 6 +int 1 +int 4 +* +int 2 ++ +int 1 +extract3 +store 12 +load 6 +int 1 +int 5 +* +int 2 ++ +int 1 +extract3 +store 13 +load 6 +int 1 +int 6 +* +int 2 ++ +int 1 +extract3 +store 14 +load 6 +int 1 +int 7 +* +int 2 ++ +int 1 +extract3 +store 15 +load 6 +int 1 +int 8 +* +int 2 ++ +int 1 +extract3 +store 16 +load 6 +int 1 +int 9 +* +int 2 ++ +int 1 +extract3 +store 17 +load 6 +int 1 +int 10 +* +int 2 ++ +int 1 +extract3 +store 18 +load 8 +callsub arraycomplement_1 +store 8 +load 9 +callsub arraycomplement_1 +store 9 +load 10 +callsub arraycomplement_1 +store 10 +load 11 +callsub arraycomplement_1 +store 11 +load 12 +callsub arraycomplement_1 +store 12 +load 13 +callsub arraycomplement_1 +store 13 +load 14 +callsub arraycomplement_1 +store 14 +load 15 +callsub arraycomplement_1 +store 15 +load 16 +callsub arraycomplement_1 +store 16 +load 17 +callsub arraycomplement_1 +store 17 +load 18 +callsub arraycomplement_1 +store 18 +int 11 +store 26 +load 26 +itob +extract 6 0 +load 8 +load 9 +concat +load 10 +concat +load 11 +concat +load 12 +concat +load 13 +concat +load 14 +concat +load 15 +concat +load 16 +concat +load 17 +concat +load 18 +concat +concat +store 7 +load 7 +retsub + +// round_tripper +roundtripper_3: +store 2 +load 2 +callsub arraycomplement_2 +store 4 +load 4 +callsub arraycomplement_2 +store 5 +load 2 +store 30 +load 30 +store 29 +int 6 +store 27 +load 27 +load 30 +len ++ +store 28 +load 28 +int 65536 +< +assert +load 27 +itob +extract 6 0 +load 4 +store 30 +load 29 +load 30 +concat +store 29 +load 28 +store 27 +load 27 +load 30 +len ++ +store 28 +load 28 +int 65536 +< +assert +load 27 +itob +extract 6 0 +concat +load 5 +store 30 +load 29 +load 30 +concat +store 29 +load 28 +store 27 +load 27 +itob +extract 6 0 +concat +load 29 +concat +store 3 +load 3 +retsub \ No newline at end of file From f48a32954086929a68459470fdbb60f4ff4539dc Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Sun, 15 May 2022 21:03:23 -0500 Subject: [PATCH 150/170] =?UTF-8?q?mypy=20=F0=9F=A4=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/abi_roundtrip.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/abi_roundtrip.py b/tests/abi_roundtrip.py index 30aa2507b..4ef728ea1 100644 --- a/tests/abi_roundtrip.py +++ b/tests/abi_roundtrip.py @@ -15,9 +15,9 @@ def __init__( self, t: type[T] | None, length: int | None = None, - annotation_instance: T = None, + annotation_instance: abi.BaseType = None, ): - self.annotation: type[T] + self.annotation: type[abi.BaseType] self.type_spec: abi.TypeSpec self.instance: abi.BaseType From 344232e175eac9b1917941cee481e47828971694 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Sun, 15 May 2022 22:25:40 -0500 Subject: [PATCH 151/170] comments --- .../app_roundtrip_(uint8,byte,bool).teal | 194 +++++++++--------- 1 file changed, 97 insertions(+), 97 deletions(-) diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(uint8,byte,bool).teal b/tests/integration/teal/roundtrip/app_roundtrip_(uint8,byte,bool).teal index 45b8b2652..8f7115164 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_(uint8,byte,bool).teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_(uint8,byte,bool).teal @@ -1,113 +1,113 @@ #pragma version 6 -txna ApplicationArgs 0 -store 4 -load 4 -callsub roundtripper_1 -store 3 -byte 0x151F7C75 -load 3 -concat -log -int 1 -return +txna ApplicationArgs 0 // [uint8|byte|bool] +store 4 // 4 -> uint8|byte|bool +load 4 // [uint8|byte|bool] +callsub roundtripper_1 // [uint8|byte|bool | 255 - uint8 | 255 - byte | !bool | uint8|byte|bool] +store 3 // 3 -> uint8|byte|bool | 255 - uint8 | 255 - byte | !bool | uint8|byte|bool +byte 0x151F7C75 // [0x151F7C75] +load 3 // [0x151F7C75, uint8|byte|bool | 255 - uint8 | 255 - byte | !bool | uint8|byte|bool] +concat // [0x151F7C75 | uint8|byte|bool | 255 - uint8 | 255 - byte | !bool | uint8|byte|bool] +log // log(0x151F7C75 | uint8|byte|bool | 255 - uint8 | 255 - byte | !bool | uint8|byte|bool) +int 1 // [1] +return // PASSED // tuple_complement -tuplecomplement_0: -store 9 -load 9 -int 0 -getbyte -store 0 -load 9 -int 1 -getbyte -store 1 -load 9 -int 16 -getbit -store 2 -load 0 -callsub numericalcomp_2 -store 0 -load 1 -callsub numericalcomp_3 -store 1 -load 2 -callsub boolcomp_4 -store 2 -byte 0x00 -int 0 -load 0 -setbyte -byte 0x00 -int 0 -load 1 -setbyte -concat -byte 0x00 -int 0 -load 2 -setbit -concat -store 10 -load 10 +tuplecomplement_0: // [uint8|byte|bool] +store 9 // 9 -> uint8|byte|bool +load 9 // [uint8|byte|bool] +int 0 // [uint8|byte|bool, 0] +getbyte // [uint8] +store 0 // 0 -> uint8 +load 9 // [uint8|byte|bool] +int 1 // [uint8|byte|bool, 1] +getbyte // [byte] +store 1 // 1 -> byte +load 9 // [uint8|byte|bool] +int 16 // [uint8|byte|bool, 16] +getbit // bool +store 2 // 2 -> bool +load 0 // [uint8] +callsub numericalcomp_2 // [255 - uint8] +store 0 // 0 -> 255 - uint8 +load 1 // [byte] +callsub numericalcomp_3 // [255 - byte] +store 1 // 1 -> 255 - byte +load 2 // [bool] +callsub boolcomp_4 // [!bool] +store 2 // 2 -> !bool +byte 0x00 // [0x00] +int 0 // [0x00, 0] +load 0 // [0x00, 0, 255 - uint8] +setbyte // [255 - uint8] +byte 0x00 // [255 - uint8, 0x00] +int 0 // [255 - uint8, 0x00, 0] +load 1 // [255 - uint8, 0x00, 0, 255 - byte] +setbyte // [255 - uint8, 255 - byte] +concat // [255 - uint8 | 255 - byte] +byte 0x00 // [255 - uint8 | 255 - byte, 0x00] +int 0 // [255 - uint8 | 255 - byte, 0x00, 0] +load 2 // [255 - uint8 | 255 - byte, 0x00, 0, !bool] +setbit // [255 - uint8 | 255 - byte, !bool] +concat // [255 - uint8 | 255 - byte | !bool] +store 10 // 10 -> 255 - uint8 | 255 - byte | !bool +load 10 // [255 - uint8 | 255 - byte | !bool] retsub // round_tripper -roundtripper_1: -store 5 -load 5 -callsub tuplecomplement_0 -store 7 -load 7 -callsub tuplecomplement_0 -store 8 -load 5 -load 7 -concat -load 8 -concat -store 6 -load 6 +roundtripper_1: // [uint8|byte|bool] +store 5 // 5 -> uint8|byte|bool +load 5 // [uint8|byte|bool] +callsub tuplecomplement_0 // [255 - uint8 | 255 - byte | !bool] +store 7 // 7 -> 255 - uint8 | 255 - byte | !bool +load 7 // [255 - uint8 | 255 - byte | !bool] +callsub tuplecomplement_0 // [255 - (255 - uint8) | 255 - (255 - byte) | !!bool] +store 8 // 8 -> uint8|byte|bool +load 5 // [uint8|byte|bool] +load 7 // [uint8|byte|bool, 255 - uint8 | 255 - byte | !bool] +concat // [uint8|byte|bool | 255 - uint8 | 255 - byte | !bool] +load 8 // [uint8|byte|bool | 255 - uint8 | 255 - byte | !bool, uint8|byte|bool] +concat // [uint8|byte|bool | 255 - uint8 | 255 - byte | !bool | uint8|byte|bool] +store 6 // 6 -> uint8|byte|bool | 255 - uint8 | 255 - byte | !bool | uint8|byte|bool +load 6 // [uint8|byte|bool | 255 - uint8 | 255 - byte | !bool | uint8|byte|bool] retsub // numerical_comp -numericalcomp_2: -store 11 -int 255 -load 11 -- -store 12 -load 12 -int 256 -< -assert -load 12 +numericalcomp_2: // [uint8] +store 11 // 11 -> uint8 +int 255 // [255] +load 11 // [255, uint8] +- // [255 - uint8] +store 12 // 12 -> 255 - uint8 +load 12 // [255 - uint8] +int 256 // [255 - uint8, 256] +< // [1] +assert // [] +load 12 // [255 - uint8] retsub // numerical_comp -numericalcomp_3: -store 13 -int 255 -load 13 -- -store 14 -load 14 -int 256 -< -assert -load 14 +numericalcomp_3: // [byte] +store 13 // 13 -> byte +int 255 // [255] +load 13 // [255, byte] +- // [255 - byte] +store 14 // 14 -> 255 - byte +load 14 // [255 - byte] +int 256 // [255 - byte, 256] +< // [1] +assert // [] +load 14 // [255 - byte] retsub // bool_comp -boolcomp_4: -store 15 -load 15 -! -store 16 -load 16 -int 2 -< -assert -load 16 +boolcomp_4: // [bool] +store 15 // 15 -> bool +load 15 // [bool] +! // [!bool] +store 16 // 16 -> !bool +load 16 // [!bool] +int 2 // [!bool, 2] +< // [1] +assert // [] +load 16 // [!bool] retsub \ No newline at end of file From 014143af10465c3e2324d480d927a0f6da58068e Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 16 May 2022 23:28:23 -0500 Subject: [PATCH 152/170] pass real integration tests that mirror the graviton roundtripts --- requirements.txt | 2 +- tests/abi_roundtrip.py | 27 +- tests/blackbox.py | 10 +- tests/integration/abi_roundtrip_test.py | 96 +++- tests/integration/graviton_abi_test.py | 4 +- tests/integration/graviton_test.py | 3 +- ...4],(bool,bool),uint64,address)[]_<7>.teal} | 514 ++++++++---------- ...teal => app_roundtrip_address[]_<10>.teal} | 226 ++++---- .../roundtrip/app_roundtrip_string_<0>.teal | 8 +- .../roundtrip/app_roundtrip_string_<13>.teal | 143 ++--- .../roundtrip/app_roundtrip_string_<1>.teal | 85 ++- 11 files changed, 517 insertions(+), 601 deletions(-) rename tests/integration/teal/roundtrip/{app_roundtrip_(byte[4],(bool,bool),uint64,address)[]_<13>.teal => app_roundtrip_(byte[4],(bool,bool),uint64,address)[]_<7>.teal} (84%) rename tests/integration/teal/roundtrip/{app_roundtrip_address[]_<11>.teal => app_roundtrip_address[]_<10>.teal} (92%) diff --git a/requirements.txt b/requirements.txt index e28ed98a5..c7e31b162 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ black==22.3.0 flake8==4.0.1 flake8-tidy-imports==4.6.0 -graviton@git+https://github.com/algorand/graviton@adc86ee8a25ddb898a592d525b65b697030fd0a4 +graviton@git+https://github.com/algorand/graviton@482258cc0a8a8efb071502b3f5e1768bee295702 mypy==0.950 pytest==7.1.1 pytest-cov==3.0.0 diff --git a/tests/abi_roundtrip.py b/tests/abi_roundtrip.py index 4ef728ea1..d6a408ed4 100644 --- a/tests/abi_roundtrip.py +++ b/tests/abi_roundtrip.py @@ -38,7 +38,7 @@ def pytealer(self) -> PyTealDryRunExecutor: return PyTealDryRunExecutor(roundtrip, pt.Mode.Application) def roundtrip_factory(self) -> BlackboxWrapper: - comp = self.complement_factory() + comp = self.mutator_factory() ann_out = abi.Tuple3[self.annotation, self.annotation, self.annotation] # type: ignore[misc,name-defined] @@ -51,11 +51,13 @@ def round_tripper(x: self.annotation, *, output: ann_out): # type: ignore[name- return round_tripper - def complement_factory(self) -> pt.ABIReturnSubroutine: + def mutator_factory(self) -> pt.ABIReturnSubroutine: if isinstance(self.type_spec, abi.BoolTypeSpec): return self.bool_comp_factory() if isinstance(self.type_spec, abi.UintTypeSpec): return self.numerical_comp_factory() + if isinstance(self.type_spec, abi.StringTypeSpec): + return self.string_reverse_factory() if isinstance(self.type_spec, abi.TupleTypeSpec): return self.tuple_comp_factory() if isinstance(self.type_spec, abi.ArrayTypeSpec): @@ -82,6 +84,23 @@ def numerical_comp(x: self.annotation, *, output: self.annotation): # type: ign return numerical_comp + def string_reverse_factory(self) -> pt.ABIReturnSubroutine: + """ + Assume strings are python utf-8 compliant and therefore each byte value is at most 127 + """ + if self.length is None: + self.length = DEFAULT_DYNAMIC_ARRAY_LENGTH + + char_type_spec = abi.ByteTypeSpec() + + @pt.ABIReturnSubroutine + def string_reverse(x: self.annotation, *, output: self.annotation): + insts = [char_type_spec.new_instance() for _ in range(self.length)] + setters = [inst.set(x[i]) for i, inst in enumerate(reversed(insts))] + return pt.Seq(*(setters + [output.set(insts)])) + + return string_reverse + def tuple_comp_factory(self) -> pt.ABIReturnSubroutine: value_type_specs: list[abi.TypeSpec] = self.type_spec.value_type_specs() # type: ignore[attr-defined] insts = [vts.new_instance() for vts in value_type_specs] @@ -92,7 +111,7 @@ def tuple_comp_factory(self) -> pt.ABIReturnSubroutine: @pt.ABIReturnSubroutine def tuple_complement(x: self.annotation, *, output: self.annotation): # type: ignore[name-defined] setters = [inst.set(x[i]) for i, inst in enumerate(insts)] # type: ignore[attr-defined] - comp_funcs = [rtrip.complement_factory() for rtrip in roundtrips] + comp_funcs = [rtrip.mutator_factory() for rtrip in roundtrips] compers = [inst.set(comp_funcs[i](inst)) for i, inst in enumerate(insts)] # type: ignore[attr-defined] return pt.Seq(*(setters + compers + [output.set(*insts)])) @@ -114,7 +133,7 @@ def array_comp_factory(self) -> pt.ABIReturnSubroutine: internal_ann_inst = internal_type_spec.new_instance() comp_func = ABIRoundtrip( None, annotation_instance=internal_ann_inst - ).complement_factory() + ).mutator_factory() @pt.ABIReturnSubroutine def array_complement(x: self.annotation, *, output: self.annotation): # type: ignore[name-defined] diff --git a/tests/blackbox.py b/tests/blackbox.py index 39cd5aaff..77cce21f0 100644 --- a/tests/blackbox.py +++ b/tests/blackbox.py @@ -135,8 +135,6 @@ def decorator_blackbox(func: SubroutineFnWrapper | ABIReturnSubroutine): Output = TypeVar("Output") - - Lazy = Callable[[], Output] @@ -268,7 +266,7 @@ def program(self) -> Expr: * `blackbox_pyteal_example1()`: Using blackbox_pyteal() for a simple test of both an app and logic sig * `blackbox_pyteal_example2()`: Using blackbox_pyteal() to make 400 assertions and generate a CSV report with 400 dryrun rows * `blackbox_pyteal_example3()`: declarative Test Driven Development approach through Invariant's - * `blackbox_pyteal_example4()`: Using PyTealDryRunExecutor to debug an ABIReturnSubroutine with an app, logic sig and csv report + * `blackbox_pyteal_example4()`: Using PyTealDryRunExecutor to debug an ABIReturnSubroutine with an app, logic sig and csv reports """ return self._pyteal_lambda() @@ -431,21 +429,21 @@ def dryrun_on_sequence( def dryrun( self, - inputs: Sequence[str | int], + args: Sequence[bytes | str | int], compiler_version=6, ) -> DryRunInspector: return _MatchMode( app_case=lambda: DryRunExecutor.dryrun_app( algod_with_assertion(), self.compile(compiler_version), - inputs, + args, self.abi_argument_types(), self.abi_return_type(), ), signature_case=lambda: DryRunExecutor.dryrun_logicsig( algod_with_assertion(), self.compile(compiler_version), - inputs, + args, self.abi_argument_types(), self.abi_return_type(), ), diff --git a/tests/integration/abi_roundtrip_test.py b/tests/integration/abi_roundtrip_test.py index 895f94a8e..7fcefb166 100644 --- a/tests/integration/abi_roundtrip_test.py +++ b/tests/integration/abi_roundtrip_test.py @@ -4,12 +4,12 @@ import algosdk.abi -from pyteal import abi -import pyteal as pt +from graviton.abi_strategy import ABIStrategy -from tests.compile_asserts import assert_teal_as_expected +from pyteal import abi from tests.abi_roundtrip import ABIRoundtrip +from tests.compile_asserts import assert_teal_as_expected PATH = Path.cwd() / "tests" / "integration" FIXTURES = PATH / "teal" @@ -48,7 +48,7 @@ (abi.DynamicArray[abi.Uint64], 0), (abi.DynamicArray[abi.Uint64], 1), (abi.DynamicArray[abi.Uint64], 42), - (abi.DynamicArray[abi.Address], 11), + (abi.DynamicArray[abi.Address], 10), (abi.DynamicArray[abi.StaticArray[abi.Bool, Literal[3]]], 11), abi.StaticArray[abi.Tuple1[abi.Bool], Literal[10]], ( @@ -60,31 +60,32 @@ abi.Address, ] ], - 13, + 7, ), ] -@pytest.mark.parametrize("abi_type", ABI_TYPES) -def test_pure_compilation(abi_type): - print(f"Testing {abi_type=}") - +def roundtrip_setup(abi_type): dynamic_length = None if isinstance(abi_type, tuple): abi_type, dynamic_length = abi_type - sdk_abi_type = abi.algosdk_from_annotation(abi_type) + return abi_type, dynamic_length, ABIRoundtrip(abi_type, dynamic_length).pytealer() - roundtripper = ABIRoundtrip(abi_type, dynamic_length).pytealer() - abi_arg_types = roundtripper.abi_argument_types() - assert [sdk_abi_type] == abi_arg_types +@pytest.mark.parametrize("abi_type", ABI_TYPES) +def test_pure_compilation(abi_type): + print(f"Pure Compilation Test for {abi_type=}") + abi_type, dynamic_length, roundtripper = roundtrip_setup(abi_type) + sdk_abi_type = abi.algosdk_from_annotation(abi_type) + + abi_arg_types = roundtripper.abi_argument_types() abi_ret_type = roundtripper.abi_return_type() + assert [sdk_abi_type] == abi_arg_types assert algosdk.abi.TupleType([sdk_abi_type] * 3) == abi_ret_type - program = roundtripper.program() - teal = pt.compileTeal(program, pt.Mode.Application, version=6) + teal = roundtripper.compile(version=6) filename = ( f"app_roundtrip_{sdk_abi_type}" @@ -99,3 +100,68 @@ def test_pure_compilation(abi_type): f.write(teal) assert_teal_as_expected(save_to, FIXTURES / "roundtrip" / filename) + + +GAI_ISSUE_2050 = "https://github.com/algorand/go-algorand-internal/issues/2050" + +BAD_TEALS = { + "()": GAI_ISSUE_2050, +} + + +@pytest.mark.parametrize("abi_type", ABI_TYPES) +def test_roundtrip(abi_type): + print(f"Round Trip Test for {abi_type=}") + + _, dynamic_length, roundtripper = roundtrip_setup(abi_type) + + sdk_abi_types = roundtripper.abi_argument_types() + sdk_ret_type = roundtripper.abi_return_type() + + sdk_abi_str = str(sdk_abi_types[0]) + if sdk_abi_str in BAD_TEALS: + print( + f"Skipping encoding roundtrip test of '{sdk_abi_str}' because of {BAD_TEALS[sdk_abi_str]}" + ) + return + + abi_strat = ABIStrategy(sdk_abi_types[0], dynamic_length=dynamic_length) + rand_abi_instance = abi_strat.get_random() + args = (rand_abi_instance,) + inspector = roundtripper.dryrun(args) + + cost = inspector.cost() + passed = inspector.passed() + original, mut, mut_mut = inspector.last_log() + + print( + f""" +{abi_type=} +{sdk_abi_str=} +{dynamic_length=} +{sdk_abi_types=} +{sdk_ret_type=} +{rand_abi_instance=} +{cost=} +{original=} +{mut=} +{mut_mut=} +""" + ) + + last_rows = 2 + + assert passed == (cost <= 700), inspector.report( + args, f"passed={passed} contradicted cost={cost}", last_rows=last_rows + ) + assert rand_abi_instance == original, inspector.report( + args, "rand_abi_instance v. original", last_rows=last_rows + ) + assert original == mut_mut, inspector.report( + args, "orginal v. mut_mut", last_rows=last_rows + ) + + expected_mut = abi_strat.mutate_for_roundtrip(rand_abi_instance) + assert expected_mut == mut, inspector.report( + args, "expected_mut v. mut", last_rows=last_rows + ) diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index 1ec10432b..adc77eb5a 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -385,18 +385,18 @@ def pytuple_to_complex(tt): ) ] + # Binary: def binary_dryrun(p: PyTealDryRunExecutor) -> list[DryRunInspector]: return p.dryrun_on_sequence(binary_inputs) - # Binary: inspectors_cplx_add = binary_dryrun(bbpt_cplx_add) inspectors_cplx_mult = binary_dryrun(bbpt_cplx_mult) + # Unary: def unary_dryrun(p: PyTealDryRunExecutor) -> list[DryRunInspector]: return p.dryrun_on_sequence(unary_inputs) - # Unary: inspectors_cplx_real = unary_dryrun(bbpt_complex_real) inspectors_cplx_imag = unary_dryrun(bbpt_complex_imag) diff --git a/tests/integration/graviton_test.py b/tests/integration/graviton_test.py index b85622597..ad43e62f9 100644 --- a/tests/integration/graviton_test.py +++ b/tests/integration/graviton_test.py @@ -791,9 +791,10 @@ def abi_sum(toSum: abi.DynamicArray[abi.Uint64], *, output: abi.Uint64) -> Expr: lsig_inspectors = lsig_pytealer.dryrun_on_sequence(inputs) for i in range(N): + args = inputs[i] + app_inspector = app_inspectors[i] lsig_inspector = lsig_inspectors[i] - args = inputs[i] def message(insp): return insp.report(args, f"failed for {args}", row=i) diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(byte[4],(bool,bool),uint64,address)[]_<13>.teal b/tests/integration/teal/roundtrip/app_roundtrip_(byte[4],(bool,bool),uint64,address)[]_<7>.teal similarity index 84% rename from tests/integration/teal/roundtrip/app_roundtrip_(byte[4],(bool,bool),uint64,address)[]_<13>.teal rename to tests/integration/teal/roundtrip/app_roundtrip_(byte[4],(bool,bool),uint64,address)[]_<7>.teal index 040c40a30..c9b93fc85 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_(byte[4],(bool,bool),uint64,address)[]_<13>.teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_(byte[4],(bool,bool),uint64,address)[]_<7>.teal @@ -13,18 +13,18 @@ return // tuple_complement tuplecomplement_0: -store 25 -load 25 +store 19 +load 19 extract 0 4 store 0 -load 25 +load 19 extract 4 1 store 1 -load 25 +load 19 int 5 extract_uint64 store 2 -load 25 +load 19 extract 13 0 store 3 load 0 @@ -47,8 +47,8 @@ itob concat load 3 concat -store 26 -load 26 +store 20 +load 20 retsub // array_complement @@ -117,60 +117,6 @@ int 2 int 45 extract3 store 18 -load 10 -int 45 -int 7 -* -int 2 -+ -int 45 -extract3 -store 19 -load 10 -int 45 -int 8 -* -int 2 -+ -int 45 -extract3 -store 20 -load 10 -int 45 -int 9 -* -int 2 -+ -int 45 -extract3 -store 21 -load 10 -int 45 -int 10 -* -int 2 -+ -int 45 -extract3 -store 22 -load 10 -int 45 -int 11 -* -int 2 -+ -int 45 -extract3 -store 23 -load 10 -int 45 -int 12 -* -int 2 -+ -int 45 -extract3 -store 24 load 12 callsub tuplecomplement_0 store 12 @@ -192,27 +138,9 @@ store 17 load 18 callsub tuplecomplement_0 store 18 -load 19 -callsub tuplecomplement_0 -store 19 -load 20 -callsub tuplecomplement_0 -store 20 -load 21 -callsub tuplecomplement_0 -store 21 -load 22 -callsub tuplecomplement_0 -store 22 -load 23 -callsub tuplecomplement_0 -store 23 -load 24 -callsub tuplecomplement_0 -store 24 -int 13 -store 81 -load 81 +int 7 +store 75 +load 75 itob extract 6 0 load 12 @@ -228,18 +156,6 @@ load 17 concat load 18 concat -load 19 -concat -load 20 -concat -load 21 -concat -load 22 -concat -load 23 -concat -load 24 -concat concat store 11 load 11 @@ -255,57 +171,57 @@ load 8 callsub arraycomplement_1 store 9 load 6 -store 85 -load 85 -store 84 +store 79 +load 79 +store 78 int 6 -store 82 -load 82 -load 85 +store 76 +load 76 +load 79 len + -store 83 -load 83 +store 77 +load 77 int 65536 < assert -load 82 +load 76 itob extract 6 0 load 8 -store 85 -load 84 -load 85 -concat -store 84 -load 83 -store 82 -load 82 -load 85 +store 79 +load 78 +load 79 +concat +store 78 +load 77 +store 76 +load 76 +load 79 len + -store 83 -load 83 +store 77 +load 77 int 65536 < assert -load 82 +load 76 itob extract 6 0 concat load 9 -store 85 -load 84 -load 85 -concat -store 84 -load 83 -store 82 -load 82 +store 79 +load 78 +load 79 +concat +store 78 +load 77 +store 76 +load 76 itob extract 6 0 concat -load 84 +load 78 concat store 7 load 7 @@ -313,327 +229,345 @@ retsub // numerical_comp numericalcomp_3: -store 35 +store 29 int 255 -load 35 +load 29 - -store 36 -load 36 +store 30 +load 30 int 256 < assert -load 36 +load 30 retsub // array_complement arraycomplement_4: -store 29 -load 29 +store 23 +load 23 int 1 int 0 * getbyte -store 31 -load 29 +store 25 +load 23 int 1 int 1 * getbyte -store 32 -load 29 +store 26 +load 23 int 1 int 2 * getbyte -store 33 -load 29 +store 27 +load 23 int 1 int 3 * getbyte -store 34 -load 31 +store 28 +load 25 callsub numericalcomp_3 -store 31 -load 32 +store 25 +load 26 callsub numericalcomp_3 -store 32 -load 33 +store 26 +load 27 callsub numericalcomp_3 -store 33 -load 34 +store 27 +load 28 callsub numericalcomp_3 -store 34 +store 28 byte 0x00 int 0 -load 31 +load 25 setbyte byte 0x00 int 0 -load 32 +load 26 setbyte concat byte 0x00 int 0 -load 33 +load 27 setbyte concat byte 0x00 int 0 -load 34 +load 28 setbyte concat -store 30 -load 30 +store 24 +load 24 retsub // tuple_complement tuplecomplement_5: -store 37 -load 37 +store 31 +load 31 int 0 getbit -store 27 -load 37 +store 21 +load 31 int 1 getbit -store 28 -load 27 +store 22 +load 21 callsub boolcomp_9 -store 27 -load 28 +store 21 +load 22 callsub boolcomp_10 -store 28 +store 22 byte 0x00 int 0 -load 27 +load 21 setbit int 1 -load 28 +load 22 setbit -store 38 -load 38 +store 32 +load 32 retsub // numerical_comp numericalcomp_6: -store 43 +store 37 int 18446744073709551615 -load 43 +load 37 - -store 44 -load 44 +store 38 +load 38 retsub // numerical_comp numericalcomp_7: -store 79 +store 73 int 255 -load 79 +load 73 - -store 80 -load 80 +store 74 +load 74 int 256 < assert -load 80 +load 74 retsub // array_complement arraycomplement_8: -store 45 -load 45 +store 39 +load 39 int 1 int 0 * getbyte -store 47 -load 45 +store 41 +load 39 int 1 int 1 * getbyte -store 48 -load 45 +store 42 +load 39 int 1 int 2 * getbyte -store 49 -load 45 +store 43 +load 39 int 1 int 3 * getbyte -store 50 -load 45 +store 44 +load 39 int 1 int 4 * getbyte -store 51 -load 45 +store 45 +load 39 int 1 int 5 * getbyte -store 52 -load 45 +store 46 +load 39 int 1 int 6 * getbyte -store 53 -load 45 +store 47 +load 39 int 1 int 7 * getbyte -store 54 -load 45 +store 48 +load 39 int 1 int 8 * getbyte -store 55 -load 45 +store 49 +load 39 int 1 int 9 * getbyte -store 56 -load 45 +store 50 +load 39 int 1 int 10 * getbyte -store 57 -load 45 +store 51 +load 39 int 1 int 11 * getbyte -store 58 -load 45 +store 52 +load 39 int 1 int 12 * getbyte -store 59 -load 45 +store 53 +load 39 int 1 int 13 * getbyte -store 60 -load 45 +store 54 +load 39 int 1 int 14 * getbyte -store 61 -load 45 +store 55 +load 39 int 1 int 15 * getbyte -store 62 -load 45 +store 56 +load 39 int 1 int 16 * getbyte -store 63 -load 45 +store 57 +load 39 int 1 int 17 * getbyte -store 64 -load 45 +store 58 +load 39 int 1 int 18 * getbyte -store 65 -load 45 +store 59 +load 39 int 1 int 19 * getbyte -store 66 -load 45 +store 60 +load 39 int 1 int 20 * getbyte -store 67 -load 45 +store 61 +load 39 int 1 int 21 * getbyte -store 68 -load 45 +store 62 +load 39 int 1 int 22 * getbyte -store 69 -load 45 +store 63 +load 39 int 1 int 23 * getbyte -store 70 -load 45 +store 64 +load 39 int 1 int 24 * getbyte -store 71 -load 45 +store 65 +load 39 int 1 int 25 * getbyte -store 72 -load 45 +store 66 +load 39 int 1 int 26 * getbyte -store 73 -load 45 +store 67 +load 39 int 1 int 27 * getbyte -store 74 -load 45 +store 68 +load 39 int 1 int 28 * getbyte -store 75 -load 45 +store 69 +load 39 int 1 int 29 * getbyte -store 76 -load 45 +store 70 +load 39 int 1 int 30 * getbyte -store 77 -load 45 +store 71 +load 39 int 1 int 31 * getbyte -store 78 +store 72 +load 41 +callsub numericalcomp_7 +store 41 +load 42 +callsub numericalcomp_7 +store 42 +load 43 +callsub numericalcomp_7 +store 43 +load 44 +callsub numericalcomp_7 +store 44 +load 45 +callsub numericalcomp_7 +store 45 +load 46 +callsub numericalcomp_7 +store 46 load 47 callsub numericalcomp_7 store 47 @@ -712,28 +646,40 @@ store 71 load 72 callsub numericalcomp_7 store 72 -load 73 -callsub numericalcomp_7 -store 73 -load 74 -callsub numericalcomp_7 -store 74 -load 75 -callsub numericalcomp_7 -store 75 -load 76 -callsub numericalcomp_7 -store 76 -load 77 -callsub numericalcomp_7 -store 77 -load 78 -callsub numericalcomp_7 -store 78 +byte 0x00 +int 0 +load 41 +setbyte +byte 0x00 +int 0 +load 42 +setbyte +concat +byte 0x00 +int 0 +load 43 +setbyte +concat +byte 0x00 +int 0 +load 44 +setbyte +concat +byte 0x00 +int 0 +load 45 +setbyte +concat +byte 0x00 +int 0 +load 46 +setbyte +concat byte 0x00 int 0 load 47 setbyte +concat byte 0x00 int 0 load 48 @@ -859,62 +805,32 @@ int 0 load 72 setbyte concat -byte 0x00 -int 0 -load 73 -setbyte -concat -byte 0x00 -int 0 -load 74 -setbyte -concat -byte 0x00 -int 0 -load 75 -setbyte -concat -byte 0x00 -int 0 -load 76 -setbyte -concat -byte 0x00 -int 0 -load 77 -setbyte -concat -byte 0x00 -int 0 -load 78 -setbyte -concat -store 46 -load 46 +store 40 +load 40 retsub // bool_comp boolcomp_9: -store 39 -load 39 +store 33 +load 33 ! -store 40 -load 40 +store 34 +load 34 int 2 < assert -load 40 +load 34 retsub // bool_comp boolcomp_10: -store 41 -load 41 +store 35 +load 35 ! -store 42 -load 42 +store 36 +load 36 int 2 < assert -load 42 +load 36 retsub \ No newline at end of file diff --git a/tests/integration/teal/roundtrip/app_roundtrip_address[]_<11>.teal b/tests/integration/teal/roundtrip/app_roundtrip_address[]_<10>.teal similarity index 92% rename from tests/integration/teal/roundtrip/app_roundtrip_address[]_<11>.teal rename to tests/integration/teal/roundtrip/app_roundtrip_address[]_<10>.teal index 6d972713e..c406dcc4c 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_address[]_<11>.teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_address[]_<10>.teal @@ -13,213 +13,216 @@ return // numerical_comp numericalcomp_0: -store 53 +store 52 int 255 -load 53 +load 52 - -store 54 -load 54 +store 53 +load 53 int 256 < assert -load 54 +load 53 retsub // array_complement arraycomplement_1: -store 19 -load 19 +store 18 +load 18 int 1 int 0 * getbyte -store 21 -load 19 +store 20 +load 18 int 1 int 1 * getbyte -store 22 -load 19 +store 21 +load 18 int 1 int 2 * getbyte -store 23 -load 19 +store 22 +load 18 int 1 int 3 * getbyte -store 24 -load 19 +store 23 +load 18 int 1 int 4 * getbyte -store 25 -load 19 +store 24 +load 18 int 1 int 5 * getbyte -store 26 -load 19 +store 25 +load 18 int 1 int 6 * getbyte -store 27 -load 19 +store 26 +load 18 int 1 int 7 * getbyte -store 28 -load 19 +store 27 +load 18 int 1 int 8 * getbyte -store 29 -load 19 +store 28 +load 18 int 1 int 9 * getbyte -store 30 -load 19 +store 29 +load 18 int 1 int 10 * getbyte -store 31 -load 19 +store 30 +load 18 int 1 int 11 * getbyte -store 32 -load 19 +store 31 +load 18 int 1 int 12 * getbyte -store 33 -load 19 +store 32 +load 18 int 1 int 13 * getbyte -store 34 -load 19 +store 33 +load 18 int 1 int 14 * getbyte -store 35 -load 19 +store 34 +load 18 int 1 int 15 * getbyte -store 36 -load 19 +store 35 +load 18 int 1 int 16 * getbyte -store 37 -load 19 +store 36 +load 18 int 1 int 17 * getbyte -store 38 -load 19 +store 37 +load 18 int 1 int 18 * getbyte -store 39 -load 19 +store 38 +load 18 int 1 int 19 * getbyte -store 40 -load 19 +store 39 +load 18 int 1 int 20 * getbyte -store 41 -load 19 +store 40 +load 18 int 1 int 21 * getbyte -store 42 -load 19 +store 41 +load 18 int 1 int 22 * getbyte -store 43 -load 19 +store 42 +load 18 int 1 int 23 * getbyte -store 44 -load 19 +store 43 +load 18 int 1 int 24 * getbyte -store 45 -load 19 +store 44 +load 18 int 1 int 25 * getbyte -store 46 -load 19 +store 45 +load 18 int 1 int 26 * getbyte -store 47 -load 19 +store 46 +load 18 int 1 int 27 * getbyte -store 48 -load 19 +store 47 +load 18 int 1 int 28 * getbyte -store 49 -load 19 +store 48 +load 18 int 1 int 29 * getbyte -store 50 -load 19 +store 49 +load 18 int 1 int 30 * getbyte -store 51 -load 19 +store 50 +load 18 int 1 int 31 * getbyte -store 52 +store 51 +load 20 +callsub numericalcomp_0 +store 20 load 21 callsub numericalcomp_0 store 21 @@ -313,13 +316,15 @@ store 50 load 51 callsub numericalcomp_0 store 51 -load 52 -callsub numericalcomp_0 -store 52 +byte 0x00 +int 0 +load 20 +setbyte byte 0x00 int 0 load 21 setbyte +concat byte 0x00 int 0 load 22 @@ -470,13 +475,8 @@ int 0 load 51 setbyte concat -byte 0x00 -int 0 -load 52 -setbyte -concat -store 20 -load 20 +store 19 +load 19 retsub // array_complement @@ -572,15 +572,6 @@ int 2 int 32 extract3 store 17 -load 6 -int 32 -int 10 -* -int 2 -+ -int 32 -extract3 -store 18 load 8 callsub arraycomplement_1 store 8 @@ -611,12 +602,9 @@ store 16 load 17 callsub arraycomplement_1 store 17 -load 18 -callsub arraycomplement_1 -store 18 -int 11 -store 55 -load 55 +int 10 +store 54 +load 54 itob extract 6 0 load 8 @@ -638,8 +626,6 @@ load 16 concat load 17 concat -load 18 -concat concat store 7 load 7 @@ -655,57 +641,57 @@ load 4 callsub arraycomplement_2 store 5 load 2 -store 59 -load 59 store 58 +load 58 +store 57 int 6 -store 56 -load 56 -load 59 +store 55 +load 55 +load 58 len + -store 57 -load 57 +store 56 +load 56 int 65536 < assert -load 56 +load 55 itob extract 6 0 load 4 -store 59 -load 58 -load 59 -concat store 58 load 57 -store 56 +load 58 +concat +store 57 load 56 -load 59 +store 55 +load 55 +load 58 len + -store 57 -load 57 +store 56 +load 56 int 65536 < assert -load 56 +load 55 itob extract 6 0 concat load 5 -store 59 -load 58 -load 59 -concat store 58 load 57 -store 56 +load 58 +concat +store 57 load 56 +store 55 +load 55 itob extract 6 0 concat -load 58 +load 57 concat store 3 load 3 diff --git a/tests/integration/teal/roundtrip/app_roundtrip_string_<0>.teal b/tests/integration/teal/roundtrip/app_roundtrip_string_<0>.teal index 213f6c9af..5ae3edff2 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_string_<0>.teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_string_<0>.teal @@ -11,8 +11,8 @@ log int 1 return -// array_complement -arraycomplement_0: +// string_reverse +stringreverse_0: store 6 int 0 store 8 @@ -29,10 +29,10 @@ retsub roundtripper_1: store 2 load 2 -callsub arraycomplement_0 +callsub stringreverse_0 store 4 load 4 -callsub arraycomplement_0 +callsub stringreverse_0 store 5 load 2 store 12 diff --git a/tests/integration/teal/roundtrip/app_roundtrip_string_<13>.teal b/tests/integration/teal/roundtrip/app_roundtrip_string_<13>.teal index 1a42eabb2..6350008aa 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_string_<13>.teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_string_<13>.teal @@ -2,7 +2,7 @@ txna ApplicationArgs 0 store 1 load 1 -callsub roundtripper_2 +callsub roundtripper_1 store 0 byte 0x151F7C75 load 0 @@ -11,22 +11,8 @@ log int 1 return -// numerical_comp -numericalcomp_0: -store 21 -int 255 -load 21 -- -store 22 -load 22 -int 256 -< -assert -load 22 -retsub - -// array_complement -arraycomplement_1: +// string_reverse +stringreverse_0: store 6 load 6 int 1 @@ -35,7 +21,7 @@ int 0 int 2 + getbyte -store 8 +store 20 load 6 int 1 int 1 @@ -43,7 +29,7 @@ int 1 int 2 + getbyte -store 9 +store 19 load 6 int 1 int 2 @@ -51,7 +37,7 @@ int 2 int 2 + getbyte -store 10 +store 18 load 6 int 1 int 3 @@ -59,7 +45,7 @@ int 3 int 2 + getbyte -store 11 +store 17 load 6 int 1 int 4 @@ -67,7 +53,7 @@ int 4 int 2 + getbyte -store 12 +store 16 load 6 int 1 int 5 @@ -75,7 +61,7 @@ int 5 int 2 + getbyte -store 13 +store 15 load 6 int 1 int 6 @@ -91,7 +77,7 @@ int 7 int 2 + getbyte -store 15 +store 13 load 6 int 1 int 8 @@ -99,7 +85,7 @@ int 8 int 2 + getbyte -store 16 +store 12 load 6 int 1 int 9 @@ -107,7 +93,7 @@ int 9 int 2 + getbyte -store 17 +store 11 load 6 int 1 int 10 @@ -115,7 +101,7 @@ int 10 int 2 + getbyte -store 18 +store 10 load 6 int 1 int 11 @@ -123,7 +109,7 @@ int 11 int 2 + getbyte -store 19 +store 9 load 6 int 1 int 12 @@ -131,49 +117,10 @@ int 12 int 2 + getbyte -store 20 -load 8 -callsub numericalcomp_0 store 8 -load 9 -callsub numericalcomp_0 -store 9 -load 10 -callsub numericalcomp_0 -store 10 -load 11 -callsub numericalcomp_0 -store 11 -load 12 -callsub numericalcomp_0 -store 12 -load 13 -callsub numericalcomp_0 -store 13 -load 14 -callsub numericalcomp_0 -store 14 -load 15 -callsub numericalcomp_0 -store 15 -load 16 -callsub numericalcomp_0 -store 16 -load 17 -callsub numericalcomp_0 -store 17 -load 18 -callsub numericalcomp_0 -store 18 -load 19 -callsub numericalcomp_0 -store 19 -load 20 -callsub numericalcomp_0 -store 20 int 13 -store 23 -load 23 +store 21 +load 21 itob extract 6 0 byte 0x00 @@ -246,66 +193,66 @@ load 7 retsub // round_tripper -roundtripper_2: +roundtripper_1: store 2 load 2 -callsub arraycomplement_1 +callsub stringreverse_0 store 4 load 4 -callsub arraycomplement_1 +callsub stringreverse_0 store 5 load 2 -store 27 -load 27 -store 26 -int 6 +store 25 +load 25 store 24 -load 24 -load 27 +int 6 +store 22 +load 22 +load 25 len + -store 25 -load 25 +store 23 +load 23 int 65536 < assert -load 24 +load 22 itob extract 6 0 load 4 -store 27 -load 26 -load 27 -concat -store 26 +store 25 +load 24 load 25 +concat store 24 -load 24 -load 27 +load 23 +store 22 +load 22 +load 25 len + -store 25 -load 25 +store 23 +load 23 int 65536 < assert -load 24 +load 22 itob extract 6 0 concat load 5 -store 27 -load 26 -load 27 -concat -store 26 +store 25 +load 24 load 25 +concat store 24 -load 24 +load 23 +store 22 +load 22 itob extract 6 0 concat -load 26 +load 24 concat store 3 load 3 diff --git a/tests/integration/teal/roundtrip/app_roundtrip_string_<1>.teal b/tests/integration/teal/roundtrip/app_roundtrip_string_<1>.teal index c4eb825aa..779c6fc1d 100644 --- a/tests/integration/teal/roundtrip/app_roundtrip_string_<1>.teal +++ b/tests/integration/teal/roundtrip/app_roundtrip_string_<1>.teal @@ -2,7 +2,7 @@ txna ApplicationArgs 0 store 1 load 1 -callsub roundtripper_2 +callsub roundtripper_1 store 0 byte 0x151F7C75 load 0 @@ -11,22 +11,8 @@ log int 1 return -// numerical_comp -numericalcomp_0: -store 9 -int 255 -load 9 -- -store 10 -load 10 -int 256 -< -assert -load 10 -retsub - -// array_complement -arraycomplement_1: +// string_reverse +stringreverse_0: store 6 load 6 int 1 @@ -36,12 +22,9 @@ int 2 + getbyte store 8 -load 8 -callsub numericalcomp_0 -store 8 int 1 -store 11 -load 11 +store 9 +load 9 itob extract 6 0 byte 0x00 @@ -54,66 +37,66 @@ load 7 retsub // round_tripper -roundtripper_2: +roundtripper_1: store 2 load 2 -callsub arraycomplement_1 +callsub stringreverse_0 store 4 load 4 -callsub arraycomplement_1 +callsub stringreverse_0 store 5 load 2 -store 15 -load 15 -store 14 -int 6 +store 13 +load 13 store 12 -load 12 -load 15 +int 6 +store 10 +load 10 +load 13 len + -store 13 -load 13 +store 11 +load 11 int 65536 < assert -load 12 +load 10 itob extract 6 0 load 4 -store 15 -load 14 -load 15 -concat -store 14 +store 13 +load 12 load 13 +concat store 12 -load 12 -load 15 +load 11 +store 10 +load 10 +load 13 len + -store 13 -load 13 +store 11 +load 11 int 65536 < assert -load 12 +load 10 itob extract 6 0 concat load 5 -store 15 -load 14 -load 15 -concat -store 14 +store 13 +load 12 load 13 +concat store 12 -load 12 +load 11 +store 10 +load 10 itob extract 6 0 concat -load 14 +load 12 concat store 3 load 3 From ee8a69d897f7e3cd648c4ac575cdd1dd3647244f Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 16 May 2022 23:35:14 -0500 Subject: [PATCH 153/170] mypy --- requirements.txt | 2 +- tests/abi_roundtrip.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index c7e31b162..a20629b8b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ black==22.3.0 flake8==4.0.1 flake8-tidy-imports==4.6.0 -graviton@git+https://github.com/algorand/graviton@482258cc0a8a8efb071502b3f5e1768bee295702 +graviton@git+https://github.com/algorand/graviton@299c0f4f2c2de4dd79aebaccbcceeef7bd93235f mypy==0.950 pytest==7.1.1 pytest-cov==3.0.0 diff --git a/tests/abi_roundtrip.py b/tests/abi_roundtrip.py index d6a408ed4..fd26e9d83 100644 --- a/tests/abi_roundtrip.py +++ b/tests/abi_roundtrip.py @@ -94,14 +94,14 @@ def string_reverse_factory(self) -> pt.ABIReturnSubroutine: char_type_spec = abi.ByteTypeSpec() @pt.ABIReturnSubroutine - def string_reverse(x: self.annotation, *, output: self.annotation): - insts = [char_type_spec.new_instance() for _ in range(self.length)] + def string_reverse(x: self.annotation, *, output: self.annotation): # type: ignore[name-defined] + insts = [char_type_spec.new_instance() for _ in range(self.length)] # type: ignore[arg-type] setters = [inst.set(x[i]) for i, inst in enumerate(reversed(insts))] return pt.Seq(*(setters + [output.set(insts)])) return string_reverse - def tuple_comp_factory(self) -> pt.ABIReturnSubroutine: + def tuple_comp_factory(self) -> pt.ABIReturnSubroutine: # type: ignore[name-defined] value_type_specs: list[abi.TypeSpec] = self.type_spec.value_type_specs() # type: ignore[attr-defined] insts = [vts.new_instance() for vts in value_type_specs] roundtrips: list[ABIRoundtrip[T]] = [ From 71f3285715a74368c36c887ed257f15209a9eab2 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 16 May 2022 23:43:54 -0500 Subject: [PATCH 154/170] ignore literal of non-literal --- pyteal/ast/abi/array_static.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/abi/array_static.py b/pyteal/ast/abi/array_static.py index 7c4ca1ab0..0b26a0db0 100644 --- a/pyteal/ast/abi/array_static.py +++ b/pyteal/ast/abi/array_static.py @@ -25,7 +25,7 @@ def new_instance(self) -> "StaticArray[T, N]": def annotation_type(self) -> "type[StaticArray[T, N]]": return StaticArray[ # type: ignore[misc] - self.value_spec.annotation_type(), Literal[self.array_length] + self.value_spec.annotation_type(), Literal[self.array_length] # type: ignore ] def length_static(self) -> int: From d3d68639f38a8e1378bd8c8f1052b79641a5e5d0 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 16 May 2022 23:46:35 -0500 Subject: [PATCH 155/170] un-comment out --- pyteal/ast/subroutine_test.py | 82 +++++++++++++++++------------------ 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/pyteal/ast/subroutine_test.py b/pyteal/ast/subroutine_test.py index c47a31b18..e01f2910a 100644 --- a/pyteal/ast/subroutine_test.py +++ b/pyteal/ast/subroutine_test.py @@ -139,47 +139,47 @@ def fn_w_tuple1arg( return output.set(pt.Int(1)) cases = ( - # ABISubroutineTC(fn_0arg_0ret, [], "fn_0arg_0ret", "void"), - # ABISubroutineTC( - # fn_0arg_uint64_ret, [], "fn_0arg_uint64_ret", pt.abi.Uint64TypeSpec() - # ), - # ABISubroutineTC(fn_1arg_0ret, [pt.abi.Uint64()], "fn_1arg_0ret", "void"), - # ABISubroutineTC( - # fn_1arg_1ret, [pt.abi.Uint64()], "fn_1arg_1ret", pt.abi.Uint64TypeSpec() - # ), - # ABISubroutineTC( - # fn_2arg_0ret, - # [ - # pt.abi.Uint64(), - # pt.abi.StaticArray( - # pt.abi.StaticArrayTypeSpec(pt.abi.ByteTypeSpec(), 10) - # ), - # ], - # "fn_2arg_0ret", - # "void", - # ), - # ABISubroutineTC( - # fn_2arg_1ret, - # [ - # pt.abi.Uint64(), - # pt.abi.StaticArray( - # pt.abi.StaticArrayTypeSpec(pt.abi.ByteTypeSpec(), 10) - # ), - # ], - # "fn_2arg_1ret", - # pt.abi.ByteTypeSpec(), - # ), - # ABISubroutineTC( - # fn_2arg_1ret_with_expr, - # [ - # pt.Int(5), - # pt.abi.StaticArray( - # pt.abi.StaticArrayTypeSpec(pt.abi.ByteTypeSpec(), 10) - # ), - # ], - # "fn_2arg_1ret_with_expr", - # pt.abi.ByteTypeSpec(), - # ), + ABISubroutineTC(fn_0arg_0ret, [], "fn_0arg_0ret", "void"), + ABISubroutineTC( + fn_0arg_uint64_ret, [], "fn_0arg_uint64_ret", pt.abi.Uint64TypeSpec() + ), + ABISubroutineTC(fn_1arg_0ret, [pt.abi.Uint64()], "fn_1arg_0ret", "void"), + ABISubroutineTC( + fn_1arg_1ret, [pt.abi.Uint64()], "fn_1arg_1ret", pt.abi.Uint64TypeSpec() + ), + ABISubroutineTC( + fn_2arg_0ret, + [ + pt.abi.Uint64(), + pt.abi.StaticArray( + pt.abi.StaticArrayTypeSpec(pt.abi.ByteTypeSpec(), 10) + ), + ], + "fn_2arg_0ret", + "void", + ), + ABISubroutineTC( + fn_2arg_1ret, + [ + pt.abi.Uint64(), + pt.abi.StaticArray( + pt.abi.StaticArrayTypeSpec(pt.abi.ByteTypeSpec(), 10) + ), + ], + "fn_2arg_1ret", + pt.abi.ByteTypeSpec(), + ), + ABISubroutineTC( + fn_2arg_1ret_with_expr, + [ + pt.Int(5), + pt.abi.StaticArray( + pt.abi.StaticArrayTypeSpec(pt.abi.ByteTypeSpec(), 10) + ), + ], + "fn_2arg_1ret_with_expr", + pt.abi.ByteTypeSpec(), + ), ABISubroutineTC( fn_w_tuple1arg, [ From 6653de812196f3e7cae005cd7139597eed35fdf7 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 16 May 2022 23:58:31 -0500 Subject: [PATCH 156/170] no contravariance needed --- pyteal/ast/abi/type.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/abi/type.py b/pyteal/ast/abi/type.py index 55bd1b3ab..adcba417a 100644 --- a/pyteal/ast/abi/type.py +++ b/pyteal/ast/abi/type.py @@ -145,7 +145,7 @@ def _set_with_computed_type(self, value: "ComputedValue") -> Expr: BaseType.__module__ = "pyteal" -T = TypeVar("T", bound=BaseType, contravariant=True) +T = TypeVar("T", bound=BaseType) class ComputedValue(ABC, Generic[T]): From 836ab30151375ffa258bb0abeae747801cc7227d Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 17 May 2022 09:44:31 -0500 Subject: [PATCH 157/170] Update pyteal/ast/abi/address_test.py --- pyteal/ast/abi/address_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyteal/ast/abi/address_test.py b/pyteal/ast/abi/address_test.py index 90c0e2d66..a33b5b123 100644 --- a/pyteal/ast/abi/address_test.py +++ b/pyteal/ast/abi/address_test.py @@ -21,7 +21,7 @@ def test_AddressTypeSpec_byte_length_static(): assert (abi.AddressTypeSpec()).byte_length_static() == abi.AddressLength.Bytes -def test_AddressTypeSpec_lenvth_static(): +def test_AddressTypeSpec_length_static(): assert (abi.AddressTypeSpec()).length_static() == abi.AddressLength.Bytes From 15a938c884d7c521787987073fa7165e73f52a08 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 17 May 2022 10:09:03 -0500 Subject: [PATCH 158/170] don't genericize tuples with a single typevar --- pyteal/ast/abi/tuple.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pyteal/ast/abi/tuple.py b/pyteal/ast/abi/tuple.py index 78c4b0b80..b1b56bf1f 100644 --- a/pyteal/ast/abi/tuple.py +++ b/pyteal/ast/abi/tuple.py @@ -1,4 +1,4 @@ -from __future__ import annotations +from lib2to3.pytree import Base from typing import ( List, Sequence, @@ -33,10 +33,8 @@ from pyteal.ast.abi.uint import NUM_BITS_IN_BYTE, Uint16 from pyteal.ast.abi.util import substringForDecoding -T = TypeVar("T", bound=BaseType) - -def encodeTuple(values: Sequence[T]) -> Expr: +def encodeTuple(values: Sequence[BaseType]) -> Expr: heads: List[Expr] = [] head_length_static: int = 0 @@ -115,7 +113,7 @@ def encodeTuple(values: Sequence[T]) -> Expr: def indexTuple( - valueTypes: Sequence[TypeSpec], encoded: Expr, index: int, output: T + valueTypes: Sequence[TypeSpec], encoded: Expr, index: int, output: BaseType ) -> Expr: if not (0 <= index < len(valueTypes)): raise ValueError("Index outside of range") @@ -207,7 +205,7 @@ def indexTuple( return output.decode(encoded, startIndex=startIndex, length=length) -class TupleTypeSpec(TypeSpec, Generic[T]): +class TupleTypeSpec(TypeSpec): def __init__(self, *value_type_specs: TypeSpec) -> None: super().__init__() self.value_specs = list(value_type_specs) @@ -223,7 +221,7 @@ def length_static(self) -> int: def new_instance(self) -> "Tuple": return Tuple(self) - def annotation_type(self) -> type[Tuple]: + def annotation_type(self) -> "type[Tuple]": vtses = self.value_type_specs() def annotater(): @@ -274,11 +272,11 @@ def __str__(self) -> str: TupleTypeSpec.__module__ = "pyteal" -class Tuple(BaseType, Generic[T]): +class Tuple(BaseType): def __init__(self, tuple_type_spec: TupleTypeSpec) -> None: super().__init__(tuple_type_spec) - def type_spec(self) -> TupleTypeSpec[T]: + def type_spec(self) -> TupleTypeSpec: return cast(TupleTypeSpec, super().type_spec()) def decode( @@ -295,7 +293,7 @@ def decode( return self.stored_value.store(extracted) @overload - def set(self, *values: T) -> Expr: + def set(self, *values: BaseType) -> Expr: ... @overload @@ -334,11 +332,13 @@ def __getitem__(self, index: int) -> "TupleElement": Tuple.__module__ = "pyteal" +T = TypeVar("T", bound=BaseType) + class TupleElement(ComputedValue[T]): """Represents the extraction of a specific element from a Tuple.""" - def __init__(self, tuple: Tuple[T], index: int) -> None: + def __init__(self, tuple: Tuple, index: int) -> None: super().__init__() self.tuple = tuple self.index = index From a8294ad7411b5743e763214db5488bc1bd4a0038 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 17 May 2022 10:10:15 -0500 Subject: [PATCH 159/170] vs code is too damn smart --- pyteal/ast/abi/tuple.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyteal/ast/abi/tuple.py b/pyteal/ast/abi/tuple.py index b1b56bf1f..191fd600a 100644 --- a/pyteal/ast/abi/tuple.py +++ b/pyteal/ast/abi/tuple.py @@ -1,4 +1,3 @@ -from lib2to3.pytree import Base from typing import ( List, Sequence, From d21678e7444cd4e4481c7ccd0e7f3c641fb269f7 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 17 May 2022 10:16:21 -0500 Subject: [PATCH 160/170] still need the origin generic type, but rename to disambiguate --- pyteal/ast/abi/tuple.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyteal/ast/abi/tuple.py b/pyteal/ast/abi/tuple.py index 191fd600a..17c9bb631 100644 --- a/pyteal/ast/abi/tuple.py +++ b/pyteal/ast/abi/tuple.py @@ -270,6 +270,8 @@ def __str__(self) -> str: TupleTypeSpec.__module__ = "pyteal" +T_tuple = TypeVar("T_tuple", bound="Tuple") + class Tuple(BaseType): def __init__(self, tuple_type_spec: TupleTypeSpec) -> None: @@ -296,7 +298,7 @@ def set(self, *values: BaseType) -> Expr: ... @overload - def set(self: T, value: ComputedValue[T]) -> Expr: + def set(self: T_tuple, value: ComputedValue[T_tuple]) -> Expr: ... def set(self, *values): From 3625176eb0074daf6a6ff4eddf7e38d23d79d614 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 17 May 2022 10:27:45 -0500 Subject: [PATCH 161/170] update _validate() doc-comment --- pyteal/ast/subroutine.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyteal/ast/subroutine.py b/pyteal/ast/subroutine.py index 28ec2b5ca..1861f4284 100644 --- a/pyteal/ast/subroutine.py +++ b/pyteal/ast/subroutine.py @@ -77,9 +77,6 @@ def _validate( ]: """Validate the full function signature and annotations for subroutine definition. - TODO: does this doc-comment still make sense after all the refactoring in this branch, - `abi-subroutine` and `feature/abi` is complete? - NOTE: `self.implementation` should be set before calling `_validate()` This function iterates through `sig.parameters.items()`, and checks each of subroutine arguments. @@ -100,9 +97,12 @@ def _validate( We put the scratch slot id on the stack, rather than the value itself. - `abi_args` - a set of argument names, which are type annotated by ABI types. We load the ABI scratch space stored value to stack, and store them later in subroutine's local ABI values. + - `abi_output_kwarg` - a possibly empty dict which when non-empty contains exactly one key + `ABIReturnSubroutine.OUTPUT_ARG_NAME` with a value that gives abi-tye information about the output Args: input_types (optional): for testing purposes - expected `TealType`s of each parameter + Returns: impl_params: a map from python function implementation's argument name, to argument's parameter. annotations: a dict whose keys are names of type-annotated arguments, From 7404ee1f80e37ddf4722f101f6b2893655616589 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 17 May 2022 12:05:10 -0500 Subject: [PATCH 162/170] add to program() doc-comment - abi variant --- tests/blackbox.py | 61 ++++++++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/tests/blackbox.py b/tests/blackbox.py index 77cce21f0..73be9bc44 100644 --- a/tests/blackbox.py +++ b/tests/blackbox.py @@ -214,52 +214,63 @@ def abi_return_type(self) -> None | algosdk.abi.ABIType: def program(self) -> Expr: """Get ready-to-compile PyTeal program from Subroutines and ABIReturnSubroutines - TODO: add explanation for ABI var's/output as well Returns: - a function that called with no parameters -e.g. result()- - returns a PyTeal expression compiling to a ready-to-test TEAL program. + a PyTeal expression representing a ready-to-run TEAL program - The return type is callable in order to adhere to the API of blackbox tests. - - Generated TEAL code depends on the mode, subroutine input types, and subroutine output types. + Generated TEAL code depends on the self.subr's type, the mode, the input types, and output type * logic sigs: * input received via `arg i` * args are converted (cf. "input conversion" below) and passed to the subroutine * subroutine output is not logged (log is not available) + * in the case of ABIReturnSubroutine: the output is encoded on to the stack an then popped off * subroutine output is converted (cf "output conversion" below) * apps: * input received via `txna ApplicationArgs i` * args are converted (cf. "input conversion" below) and passed to the subroutine - * subroutine output is logged after possible conversion (cf. "logging conversion") - * subroutine output is converted (cf "output conversion" below) + * the output is logged in the following ways: + * Subroutine: logged after possible conversion (cf. "logging conversion") + * ABIReturnSubroutine: the encoded output is concatenated to the return method selector and then logged + * subroutine output is converted (cf "output conversion" below) (Subroutine case only) * input conversion: * Empty input array: do not read any args and call subroutine immediately - * arg of TealType.bytes and TealType.anytype: + * Expr arg of TealType.bytes and TealType.anytype: read arg and pass to subroutine as is - * arg of TealType.uint64: + * Expr arg of TealType.uint64: convert arg to int using Btoi() when received - * pass-by-ref ScratchVar arguments: + * pass-by-ref ScratchVar arguments (Subroutine case only): in addition to the above - o store the arg (or converted arg) in a ScratchVar o invoke the subroutine using this ScratchVar instead of the arg (or converted arg) + * ABI arguments (ABIReturnSubroutine case only): + in addition to the above - + o store the decoded arg into the ScratchVar of an ABI Type instance + o invoke the subroutine using this ABI Type instead of the arg * output conversion: - * TealType.uint64: - provide subroutine's result to the top of the stack when exiting program - * TealType.bytes: - convert subroutine's result to the top of the stack to its length and then exit - * TealType.none or TealType.anytype: - push Int(1337) to the stack as it is either impossible (TealType.none), - or unknown at compile time (TealType.anytype) to convert to an Int + * Subroutine case: + * TealType.uint64: + provide subroutine's result to the top of the stack when exiting program + * TealType.bytes: + convert subroutine's result to the top of the stack to its length and then exit + * TealType.none or TealType.anytype: + push Int(1337) to the stack as it is either impossible (TealType.none), + or unknown at compile time (TealType.anytype) to convert to an Int + * ABIReturnSubroutine case: + * when present, the output is encoded as TealType.bytes which can be decoded by the receiver using + appropriate ABI-libraries * logging conversion: - * TealType.uint64: - convert subroutine's output using Itob() and log the result - * TealType.bytes: - log the subroutine's result - * TealType.none or TealType.anytype: - log Itob(Int(1337)) as it is either impossible (TealType.none), - or unknown at compile time (TealType.anytype) how to convert to Bytes + * Subroutine case: + * TealType.uint64: + convert subroutine's output using Itob() and log the result + * TealType.bytes: + log the subroutine's result + * TealType.none or TealType.anytype: + log Itob(Int(1337)) as it is either impossible (TealType.none), + or unknown at compile time (TealType.anytype) how to convert to Bytes + * ABIReturnSubroutine case: + * when present, the output is encoded as TealType.bytes and concatenated to the rewturn + method selector. This can be decoded by the receiver using appropriate ABI-libraries For illustrative examples of how to use this method please refer to the integration test file `graviton_test.py` and especially: From 1ee615117da7617078fa2d68ad8546966a734eb7 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 17 May 2022 12:41:57 -0500 Subject: [PATCH 163/170] remove unused type --- tests/unit/blackbox_test.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/unit/blackbox_test.py b/tests/unit/blackbox_test.py index 02108fdd8..f74b2f883 100644 --- a/tests/unit/blackbox_test.py +++ b/tests/unit/blackbox_test.py @@ -135,14 +135,6 @@ def fn_2mixed_arg_1ret( return pt.Seq(b.store(a.encode()), output.set(a)) -CompoundType = pt.abi.Tuple4[ - pt.abi.Uint64, - pt.abi.Tuple2[pt.abi.Byte, pt.abi.StaticArray[pt.abi.Address, Literal[10]]], - pt.abi.Tuple0, - pt.abi.Bool, -] - - ABI_UNITS = [ (fn_0arg_0ret, None), (fn_0arg_uint64_ret, pt.abi.Uint64()), From 00971c4e95d605ed07958bbb91eaa44e6fbf0a39 Mon Sep 17 00:00:00 2001 From: Michael Diamant Date: Tue, 17 May 2022 19:14:21 -0400 Subject: [PATCH 164/170] Optionally refactor handling of array_length int subclasses in StaticArrayTypeSpec (#347) --- pyteal/ast/abi/address.py | 6 +++--- pyteal/ast/abi/array_static.py | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pyteal/ast/abi/address.py b/pyteal/ast/abi/address.py index 10a8ac6bc..1e8da1f0f 100644 --- a/pyteal/ast/abi/address.py +++ b/pyteal/ast/abi/address.py @@ -22,7 +22,7 @@ class AddressLength(IntEnum): class AddressTypeSpec(StaticArrayTypeSpec): def __init__(self) -> None: - super().__init__(ByteTypeSpec(), int(AddressLength.Bytes)) + super().__init__(ByteTypeSpec(), AddressLength.Bytes) def new_instance(self) -> "Address": return Address() @@ -67,7 +67,7 @@ def set( case ComputedValue(): pts = value.produced_type_spec() if pts == AddressTypeSpec() or pts == StaticArrayTypeSpec( - ByteTypeSpec(), int(AddressLength.Bytes) + ByteTypeSpec(), AddressLength.Bytes ): return value.store_into(self) @@ -78,7 +78,7 @@ def set( if ( value.type_spec() == AddressTypeSpec() or value.type_spec() - == StaticArrayTypeSpec(ByteTypeSpec(), int(AddressLength.Bytes)) + == StaticArrayTypeSpec(ByteTypeSpec(), AddressLength.Bytes) ): return self.stored_value.store(value.stored_value.load()) diff --git a/pyteal/ast/abi/array_static.py b/pyteal/ast/abi/array_static.py index 0b26a0db0..b829237b7 100644 --- a/pyteal/ast/abi/array_static.py +++ b/pyteal/ast/abi/array_static.py @@ -18,7 +18,9 @@ def __init__(self, value_type_spec: TypeSpec, array_length: int) -> None: super().__init__(value_type_spec) if not isinstance(array_length, int) or array_length < 0: raise TypeError(f"Unsupported StaticArray length: {array_length}") - self.array_length: Final = array_length + + # Casts to `int` to handle downstream usage where value is a subclass of int like `IntEnum`. + self.array_length: Final = int(array_length) def new_instance(self) -> "StaticArray[T, N]": return StaticArray(self) From c8dfc17c46934eed8cb8a0a468a62c2a7968941b Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Wed, 18 May 2022 00:09:15 -0500 Subject: [PATCH 165/170] monster abi type for greater confidence in PR #345 --- tests/integration/abi_roundtrip_test.py | 26 + ...byte),uint8)[2],string,bool[]))[]_<2>.teal | 2039 +++++++++++++++++ 2 files changed, 2065 insertions(+) create mode 100644 tests/integration/teal/roundtrip/app_roundtrip_(bool,byte,address,string,(address,(uint32,string[],bool[2],(byte),uint8)[2],string,bool[]))[]_<2>.teal diff --git a/tests/integration/abi_roundtrip_test.py b/tests/integration/abi_roundtrip_test.py index 7fcefb166..73b22e21d 100644 --- a/tests/integration/abi_roundtrip_test.py +++ b/tests/integration/abi_roundtrip_test.py @@ -62,6 +62,32 @@ ], 7, ), + ( + abi.DynamicArray[ + abi.Tuple5[ + abi.Bool, + abi.Byte, + abi.Address, + abi.String, + abi.Tuple4[ + abi.Address, + abi.StaticArray[ + abi.Tuple5[ + abi.Uint32, + abi.DynamicArray[abi.String], + abi.StaticArray[abi.Bool, Literal[2]], + abi.Tuple1[abi.Byte], + abi.Uint8, + ], + Literal[2], + ], + abi.String, + abi.DynamicArray[abi.Bool], + ], + ] + ], + 2, + ), ] diff --git a/tests/integration/teal/roundtrip/app_roundtrip_(bool,byte,address,string,(address,(uint32,string[],bool[2],(byte),uint8)[2],string,bool[]))[]_<2>.teal b/tests/integration/teal/roundtrip/app_roundtrip_(bool,byte,address,string,(address,(uint32,string[],bool[2],(byte),uint8)[2],string,bool[]))[]_<2>.teal new file mode 100644 index 000000000..f66d64459 --- /dev/null +++ b/tests/integration/teal/roundtrip/app_roundtrip_(bool,byte,address,string,(address,(uint32,string[],bool[2],(byte),uint8)[2],string,bool[]))[]_<2>.teal @@ -0,0 +1,2039 @@ +#pragma version 6 +txna ApplicationArgs 0 +store 6 +load 6 +callsub roundtripper_2 +store 5 +byte 0x151F7C75 +load 5 +concat +log +int 1 +return + +// tuple_complement +tuplecomplement_0: +store 17 +load 17 +int 0 +getbit +store 0 +load 17 +int 1 +getbyte +store 1 +load 17 +extract 2 32 +store 2 +load 17 +load 17 +int 34 +extract_uint16 +load 17 +int 36 +extract_uint16 +substring3 +store 3 +load 17 +load 17 +int 36 +extract_uint16 +dig 1 +len +substring3 +store 4 +load 0 +callsub boolcomp_3 +store 0 +load 1 +callsub numericalcomp_4 +store 1 +load 2 +callsub arraycomplement_6 +store 2 +load 3 +callsub stringreverse_7 +store 3 +load 4 +callsub tuplecomplement_8 +store 4 +byte 0x00 +int 0 +load 0 +setbit +byte 0x00 +int 0 +load 1 +setbyte +concat +load 2 +concat +load 3 +store 180 +load 180 +store 179 +int 38 +store 177 +load 177 +load 180 +len ++ +store 178 +load 178 +int 65536 +< +assert +load 177 +itob +extract 6 0 +concat +load 4 +store 180 +load 179 +load 180 +concat +store 179 +load 178 +store 177 +load 177 +itob +extract 6 0 +concat +load 179 +concat +store 18 +load 18 +retsub + +// array_complement +arraycomplement_1: +store 11 +load 11 +load 11 +int 2 +int 0 +* +int 2 ++ +extract_uint16 +int 2 ++ +int 0 +int 1 ++ +load 11 +int 0 +extract_uint16 +store 15 +load 15 +== +bnz arraycomplement_1_l5 +load 11 +int 2 +int 0 +* +int 2 ++ +int 2 ++ +extract_uint16 +int 2 ++ +arraycomplement_1_l2: +substring3 +store 13 +load 11 +load 11 +int 2 +int 1 +* +int 2 ++ +extract_uint16 +int 2 ++ +int 1 +int 1 ++ +load 11 +int 0 +extract_uint16 +store 16 +load 16 +== +bnz arraycomplement_1_l4 +load 11 +int 2 +int 1 +* +int 2 ++ +int 2 ++ +extract_uint16 +int 2 ++ +b arraycomplement_1_l6 +arraycomplement_1_l4: +load 11 +len +b arraycomplement_1_l6 +arraycomplement_1_l5: +load 11 +len +b arraycomplement_1_l2 +arraycomplement_1_l6: +substring3 +store 14 +load 13 +callsub tuplecomplement_0 +store 13 +load 14 +callsub tuplecomplement_0 +store 14 +int 2 +store 185 +load 185 +itob +extract 6 0 +load 13 +store 184 +load 184 +store 183 +int 4 +store 181 +load 181 +load 184 +len ++ +store 182 +load 182 +int 65536 +< +assert +load 181 +itob +extract 6 0 +load 14 +store 184 +load 183 +load 184 +concat +store 183 +load 182 +store 181 +load 181 +itob +extract 6 0 +concat +load 183 +concat +concat +store 12 +load 12 +retsub + +// round_tripper +roundtripper_2: +store 7 +load 7 +callsub arraycomplement_1 +store 9 +load 9 +callsub arraycomplement_1 +store 10 +load 7 +store 189 +load 189 +store 188 +int 6 +store 186 +load 186 +load 189 +len ++ +store 187 +load 187 +int 65536 +< +assert +load 186 +itob +extract 6 0 +load 9 +store 189 +load 188 +load 189 +concat +store 188 +load 187 +store 186 +load 186 +load 189 +len ++ +store 187 +load 187 +int 65536 +< +assert +load 186 +itob +extract 6 0 +concat +load 10 +store 189 +load 188 +load 189 +concat +store 188 +load 187 +store 186 +load 186 +itob +extract 6 0 +concat +load 188 +concat +store 8 +load 8 +retsub + +// bool_comp +boolcomp_3: +store 23 +load 23 +! +store 24 +load 24 +int 2 +< +assert +load 24 +retsub + +// numerical_comp +numericalcomp_4: +store 25 +int 255 +load 25 +- +store 26 +load 26 +int 256 +< +assert +load 26 +retsub + +// numerical_comp +numericalcomp_5: +store 61 +int 255 +load 61 +- +store 62 +load 62 +int 256 +< +assert +load 62 +retsub + +// array_complement +arraycomplement_6: +store 27 +load 27 +int 1 +int 0 +* +getbyte +store 29 +load 27 +int 1 +int 1 +* +getbyte +store 30 +load 27 +int 1 +int 2 +* +getbyte +store 31 +load 27 +int 1 +int 3 +* +getbyte +store 32 +load 27 +int 1 +int 4 +* +getbyte +store 33 +load 27 +int 1 +int 5 +* +getbyte +store 34 +load 27 +int 1 +int 6 +* +getbyte +store 35 +load 27 +int 1 +int 7 +* +getbyte +store 36 +load 27 +int 1 +int 8 +* +getbyte +store 37 +load 27 +int 1 +int 9 +* +getbyte +store 38 +load 27 +int 1 +int 10 +* +getbyte +store 39 +load 27 +int 1 +int 11 +* +getbyte +store 40 +load 27 +int 1 +int 12 +* +getbyte +store 41 +load 27 +int 1 +int 13 +* +getbyte +store 42 +load 27 +int 1 +int 14 +* +getbyte +store 43 +load 27 +int 1 +int 15 +* +getbyte +store 44 +load 27 +int 1 +int 16 +* +getbyte +store 45 +load 27 +int 1 +int 17 +* +getbyte +store 46 +load 27 +int 1 +int 18 +* +getbyte +store 47 +load 27 +int 1 +int 19 +* +getbyte +store 48 +load 27 +int 1 +int 20 +* +getbyte +store 49 +load 27 +int 1 +int 21 +* +getbyte +store 50 +load 27 +int 1 +int 22 +* +getbyte +store 51 +load 27 +int 1 +int 23 +* +getbyte +store 52 +load 27 +int 1 +int 24 +* +getbyte +store 53 +load 27 +int 1 +int 25 +* +getbyte +store 54 +load 27 +int 1 +int 26 +* +getbyte +store 55 +load 27 +int 1 +int 27 +* +getbyte +store 56 +load 27 +int 1 +int 28 +* +getbyte +store 57 +load 27 +int 1 +int 29 +* +getbyte +store 58 +load 27 +int 1 +int 30 +* +getbyte +store 59 +load 27 +int 1 +int 31 +* +getbyte +store 60 +load 29 +callsub numericalcomp_5 +store 29 +load 30 +callsub numericalcomp_5 +store 30 +load 31 +callsub numericalcomp_5 +store 31 +load 32 +callsub numericalcomp_5 +store 32 +load 33 +callsub numericalcomp_5 +store 33 +load 34 +callsub numericalcomp_5 +store 34 +load 35 +callsub numericalcomp_5 +store 35 +load 36 +callsub numericalcomp_5 +store 36 +load 37 +callsub numericalcomp_5 +store 37 +load 38 +callsub numericalcomp_5 +store 38 +load 39 +callsub numericalcomp_5 +store 39 +load 40 +callsub numericalcomp_5 +store 40 +load 41 +callsub numericalcomp_5 +store 41 +load 42 +callsub numericalcomp_5 +store 42 +load 43 +callsub numericalcomp_5 +store 43 +load 44 +callsub numericalcomp_5 +store 44 +load 45 +callsub numericalcomp_5 +store 45 +load 46 +callsub numericalcomp_5 +store 46 +load 47 +callsub numericalcomp_5 +store 47 +load 48 +callsub numericalcomp_5 +store 48 +load 49 +callsub numericalcomp_5 +store 49 +load 50 +callsub numericalcomp_5 +store 50 +load 51 +callsub numericalcomp_5 +store 51 +load 52 +callsub numericalcomp_5 +store 52 +load 53 +callsub numericalcomp_5 +store 53 +load 54 +callsub numericalcomp_5 +store 54 +load 55 +callsub numericalcomp_5 +store 55 +load 56 +callsub numericalcomp_5 +store 56 +load 57 +callsub numericalcomp_5 +store 57 +load 58 +callsub numericalcomp_5 +store 58 +load 59 +callsub numericalcomp_5 +store 59 +load 60 +callsub numericalcomp_5 +store 60 +byte 0x00 +int 0 +load 29 +setbyte +byte 0x00 +int 0 +load 30 +setbyte +concat +byte 0x00 +int 0 +load 31 +setbyte +concat +byte 0x00 +int 0 +load 32 +setbyte +concat +byte 0x00 +int 0 +load 33 +setbyte +concat +byte 0x00 +int 0 +load 34 +setbyte +concat +byte 0x00 +int 0 +load 35 +setbyte +concat +byte 0x00 +int 0 +load 36 +setbyte +concat +byte 0x00 +int 0 +load 37 +setbyte +concat +byte 0x00 +int 0 +load 38 +setbyte +concat +byte 0x00 +int 0 +load 39 +setbyte +concat +byte 0x00 +int 0 +load 40 +setbyte +concat +byte 0x00 +int 0 +load 41 +setbyte +concat +byte 0x00 +int 0 +load 42 +setbyte +concat +byte 0x00 +int 0 +load 43 +setbyte +concat +byte 0x00 +int 0 +load 44 +setbyte +concat +byte 0x00 +int 0 +load 45 +setbyte +concat +byte 0x00 +int 0 +load 46 +setbyte +concat +byte 0x00 +int 0 +load 47 +setbyte +concat +byte 0x00 +int 0 +load 48 +setbyte +concat +byte 0x00 +int 0 +load 49 +setbyte +concat +byte 0x00 +int 0 +load 50 +setbyte +concat +byte 0x00 +int 0 +load 51 +setbyte +concat +byte 0x00 +int 0 +load 52 +setbyte +concat +byte 0x00 +int 0 +load 53 +setbyte +concat +byte 0x00 +int 0 +load 54 +setbyte +concat +byte 0x00 +int 0 +load 55 +setbyte +concat +byte 0x00 +int 0 +load 56 +setbyte +concat +byte 0x00 +int 0 +load 57 +setbyte +concat +byte 0x00 +int 0 +load 58 +setbyte +concat +byte 0x00 +int 0 +load 59 +setbyte +concat +byte 0x00 +int 0 +load 60 +setbyte +concat +store 28 +load 28 +retsub + +// string_reverse +stringreverse_7: +store 63 +load 63 +int 1 +int 0 +* +int 2 ++ +getbyte +store 67 +load 63 +int 1 +int 1 +* +int 2 ++ +getbyte +store 66 +load 63 +int 1 +int 2 +* +int 2 ++ +getbyte +store 65 +int 3 +store 68 +load 68 +itob +extract 6 0 +byte 0x00 +int 0 +load 65 +setbyte +byte 0x00 +int 0 +load 66 +setbyte +concat +byte 0x00 +int 0 +load 67 +setbyte +concat +concat +store 64 +load 64 +retsub + +// tuple_complement +tuplecomplement_8: +store 69 +load 69 +extract 0 32 +store 19 +load 69 +load 69 +int 32 +extract_uint16 +load 69 +int 34 +extract_uint16 +substring3 +store 20 +load 69 +load 69 +int 34 +extract_uint16 +load 69 +int 36 +extract_uint16 +substring3 +store 21 +load 69 +load 69 +int 36 +extract_uint16 +dig 1 +len +substring3 +store 22 +load 19 +callsub arraycomplement_10 +store 19 +load 20 +callsub arraycomplement_12 +store 20 +load 21 +callsub stringreverse_13 +store 21 +load 22 +callsub arraycomplement_15 +store 22 +load 19 +load 20 +store 176 +load 176 +store 175 +int 38 +store 173 +load 173 +load 176 +len ++ +store 174 +load 174 +int 65536 +< +assert +load 173 +itob +extract 6 0 +concat +load 21 +store 176 +load 175 +load 176 +concat +store 175 +load 174 +store 173 +load 173 +load 176 +len ++ +store 174 +load 174 +int 65536 +< +assert +load 173 +itob +extract 6 0 +concat +load 22 +store 176 +load 175 +load 176 +concat +store 175 +load 174 +store 173 +load 173 +itob +extract 6 0 +concat +load 175 +concat +store 70 +load 70 +retsub + +// numerical_comp +numericalcomp_9: +store 110 +int 255 +load 110 +- +store 111 +load 111 +int 256 +< +assert +load 111 +retsub + +// array_complement +arraycomplement_10: +store 76 +load 76 +int 1 +int 0 +* +getbyte +store 78 +load 76 +int 1 +int 1 +* +getbyte +store 79 +load 76 +int 1 +int 2 +* +getbyte +store 80 +load 76 +int 1 +int 3 +* +getbyte +store 81 +load 76 +int 1 +int 4 +* +getbyte +store 82 +load 76 +int 1 +int 5 +* +getbyte +store 83 +load 76 +int 1 +int 6 +* +getbyte +store 84 +load 76 +int 1 +int 7 +* +getbyte +store 85 +load 76 +int 1 +int 8 +* +getbyte +store 86 +load 76 +int 1 +int 9 +* +getbyte +store 87 +load 76 +int 1 +int 10 +* +getbyte +store 88 +load 76 +int 1 +int 11 +* +getbyte +store 89 +load 76 +int 1 +int 12 +* +getbyte +store 90 +load 76 +int 1 +int 13 +* +getbyte +store 91 +load 76 +int 1 +int 14 +* +getbyte +store 92 +load 76 +int 1 +int 15 +* +getbyte +store 93 +load 76 +int 1 +int 16 +* +getbyte +store 94 +load 76 +int 1 +int 17 +* +getbyte +store 95 +load 76 +int 1 +int 18 +* +getbyte +store 96 +load 76 +int 1 +int 19 +* +getbyte +store 97 +load 76 +int 1 +int 20 +* +getbyte +store 98 +load 76 +int 1 +int 21 +* +getbyte +store 99 +load 76 +int 1 +int 22 +* +getbyte +store 100 +load 76 +int 1 +int 23 +* +getbyte +store 101 +load 76 +int 1 +int 24 +* +getbyte +store 102 +load 76 +int 1 +int 25 +* +getbyte +store 103 +load 76 +int 1 +int 26 +* +getbyte +store 104 +load 76 +int 1 +int 27 +* +getbyte +store 105 +load 76 +int 1 +int 28 +* +getbyte +store 106 +load 76 +int 1 +int 29 +* +getbyte +store 107 +load 76 +int 1 +int 30 +* +getbyte +store 108 +load 76 +int 1 +int 31 +* +getbyte +store 109 +load 78 +callsub numericalcomp_9 +store 78 +load 79 +callsub numericalcomp_9 +store 79 +load 80 +callsub numericalcomp_9 +store 80 +load 81 +callsub numericalcomp_9 +store 81 +load 82 +callsub numericalcomp_9 +store 82 +load 83 +callsub numericalcomp_9 +store 83 +load 84 +callsub numericalcomp_9 +store 84 +load 85 +callsub numericalcomp_9 +store 85 +load 86 +callsub numericalcomp_9 +store 86 +load 87 +callsub numericalcomp_9 +store 87 +load 88 +callsub numericalcomp_9 +store 88 +load 89 +callsub numericalcomp_9 +store 89 +load 90 +callsub numericalcomp_9 +store 90 +load 91 +callsub numericalcomp_9 +store 91 +load 92 +callsub numericalcomp_9 +store 92 +load 93 +callsub numericalcomp_9 +store 93 +load 94 +callsub numericalcomp_9 +store 94 +load 95 +callsub numericalcomp_9 +store 95 +load 96 +callsub numericalcomp_9 +store 96 +load 97 +callsub numericalcomp_9 +store 97 +load 98 +callsub numericalcomp_9 +store 98 +load 99 +callsub numericalcomp_9 +store 99 +load 100 +callsub numericalcomp_9 +store 100 +load 101 +callsub numericalcomp_9 +store 101 +load 102 +callsub numericalcomp_9 +store 102 +load 103 +callsub numericalcomp_9 +store 103 +load 104 +callsub numericalcomp_9 +store 104 +load 105 +callsub numericalcomp_9 +store 105 +load 106 +callsub numericalcomp_9 +store 106 +load 107 +callsub numericalcomp_9 +store 107 +load 108 +callsub numericalcomp_9 +store 108 +load 109 +callsub numericalcomp_9 +store 109 +byte 0x00 +int 0 +load 78 +setbyte +byte 0x00 +int 0 +load 79 +setbyte +concat +byte 0x00 +int 0 +load 80 +setbyte +concat +byte 0x00 +int 0 +load 81 +setbyte +concat +byte 0x00 +int 0 +load 82 +setbyte +concat +byte 0x00 +int 0 +load 83 +setbyte +concat +byte 0x00 +int 0 +load 84 +setbyte +concat +byte 0x00 +int 0 +load 85 +setbyte +concat +byte 0x00 +int 0 +load 86 +setbyte +concat +byte 0x00 +int 0 +load 87 +setbyte +concat +byte 0x00 +int 0 +load 88 +setbyte +concat +byte 0x00 +int 0 +load 89 +setbyte +concat +byte 0x00 +int 0 +load 90 +setbyte +concat +byte 0x00 +int 0 +load 91 +setbyte +concat +byte 0x00 +int 0 +load 92 +setbyte +concat +byte 0x00 +int 0 +load 93 +setbyte +concat +byte 0x00 +int 0 +load 94 +setbyte +concat +byte 0x00 +int 0 +load 95 +setbyte +concat +byte 0x00 +int 0 +load 96 +setbyte +concat +byte 0x00 +int 0 +load 97 +setbyte +concat +byte 0x00 +int 0 +load 98 +setbyte +concat +byte 0x00 +int 0 +load 99 +setbyte +concat +byte 0x00 +int 0 +load 100 +setbyte +concat +byte 0x00 +int 0 +load 101 +setbyte +concat +byte 0x00 +int 0 +load 102 +setbyte +concat +byte 0x00 +int 0 +load 103 +setbyte +concat +byte 0x00 +int 0 +load 104 +setbyte +concat +byte 0x00 +int 0 +load 105 +setbyte +concat +byte 0x00 +int 0 +load 106 +setbyte +concat +byte 0x00 +int 0 +load 107 +setbyte +concat +byte 0x00 +int 0 +load 108 +setbyte +concat +byte 0x00 +int 0 +load 109 +setbyte +concat +store 77 +load 77 +retsub + +// tuple_complement +tuplecomplement_11: +store 116 +load 116 +int 0 +extract_uint32 +store 71 +load 116 +load 116 +int 4 +extract_uint16 +dig 1 +len +substring3 +store 72 +load 116 +extract 6 1 +store 73 +load 116 +extract 7 1 +store 74 +load 116 +int 8 +getbyte +store 75 +load 71 +callsub numericalcomp_16 +store 71 +load 72 +callsub arraycomplement_18 +store 72 +load 73 +callsub arraycomplement_20 +store 73 +load 74 +callsub tuplecomplement_21 +store 74 +load 75 +callsub numericalcomp_22 +store 75 +load 71 +itob +extract 4 0 +load 72 +store 154 +load 154 +store 153 +int 9 +store 152 +load 152 +itob +extract 6 0 +concat +load 73 +concat +load 74 +concat +byte 0x00 +int 0 +load 75 +setbyte +concat +load 153 +concat +store 117 +load 117 +retsub + +// array_complement +arraycomplement_12: +store 112 +load 112 +load 112 +int 2 +int 0 +* +extract_uint16 +int 0 +int 1 ++ +int 2 +== +bnz arraycomplement_12_l5 +load 112 +int 2 +int 0 +* +int 2 ++ +extract_uint16 +arraycomplement_12_l2: +substring3 +store 114 +load 112 +load 112 +int 2 +int 1 +* +extract_uint16 +int 1 +int 1 ++ +int 2 +== +bnz arraycomplement_12_l4 +load 112 +int 2 +int 1 +* +int 2 ++ +extract_uint16 +b arraycomplement_12_l6 +arraycomplement_12_l4: +load 112 +len +b arraycomplement_12_l6 +arraycomplement_12_l5: +load 112 +len +b arraycomplement_12_l2 +arraycomplement_12_l6: +substring3 +store 115 +load 114 +callsub tuplecomplement_11 +store 114 +load 115 +callsub tuplecomplement_11 +store 115 +load 114 +store 158 +load 158 +store 157 +int 4 +store 155 +load 155 +load 158 +len ++ +store 156 +load 156 +int 65536 +< +assert +load 155 +itob +extract 6 0 +load 115 +store 158 +load 157 +load 158 +concat +store 157 +load 156 +store 155 +load 155 +itob +extract 6 0 +concat +load 157 +concat +store 113 +load 113 +retsub + +// string_reverse +stringreverse_13: +store 159 +load 159 +int 1 +int 0 +* +int 2 ++ +getbyte +store 163 +load 159 +int 1 +int 1 +* +int 2 ++ +getbyte +store 162 +load 159 +int 1 +int 2 +* +int 2 ++ +getbyte +store 161 +int 3 +store 164 +load 164 +itob +extract 6 0 +byte 0x00 +int 0 +load 161 +setbyte +byte 0x00 +int 0 +load 162 +setbyte +concat +byte 0x00 +int 0 +load 163 +setbyte +concat +concat +store 160 +load 160 +retsub + +// bool_comp +boolcomp_14: +store 170 +load 170 +! +store 171 +load 171 +int 2 +< +assert +load 171 +retsub + +// array_complement +arraycomplement_15: +store 165 +load 165 +int 0 +int 16 ++ +getbit +store 167 +load 165 +int 1 +int 16 ++ +getbit +store 168 +load 165 +int 2 +int 16 ++ +getbit +store 169 +load 167 +callsub boolcomp_14 +store 167 +load 168 +callsub boolcomp_14 +store 168 +load 169 +callsub boolcomp_14 +store 169 +int 3 +store 172 +load 172 +itob +extract 6 0 +byte 0x00 +int 0 +load 167 +setbit +int 1 +load 168 +setbit +int 2 +load 169 +setbit +concat +store 166 +load 166 +retsub + +// numerical_comp +numericalcomp_16: +store 119 +int 4294967295 +load 119 +- +store 120 +load 120 +int 4294967296 +< +assert +load 120 +retsub + +// string_reverse +stringreverse_17: +store 129 +load 129 +int 1 +int 0 +* +int 2 ++ +getbyte +store 133 +load 129 +int 1 +int 1 +* +int 2 ++ +getbyte +store 132 +load 129 +int 1 +int 2 +* +int 2 ++ +getbyte +store 131 +int 3 +store 134 +load 134 +itob +extract 6 0 +byte 0x00 +int 0 +load 131 +setbyte +byte 0x00 +int 0 +load 132 +setbyte +concat +byte 0x00 +int 0 +load 133 +setbyte +concat +concat +store 130 +load 130 +retsub + +// array_complement +arraycomplement_18: +store 121 +load 121 +load 121 +int 2 +int 0 +* +int 2 ++ +extract_uint16 +int 2 ++ +int 0 +int 1 ++ +load 121 +int 0 +extract_uint16 +store 126 +load 126 +== +bnz arraycomplement_18_l8 +load 121 +int 2 +int 0 +* +int 2 ++ +int 2 ++ +extract_uint16 +int 2 ++ +arraycomplement_18_l2: +substring3 +store 123 +load 121 +load 121 +int 2 +int 1 +* +int 2 ++ +extract_uint16 +int 2 ++ +int 1 +int 1 ++ +load 121 +int 0 +extract_uint16 +store 127 +load 127 +== +bnz arraycomplement_18_l7 +load 121 +int 2 +int 1 +* +int 2 ++ +int 2 ++ +extract_uint16 +int 2 ++ +arraycomplement_18_l4: +substring3 +store 124 +load 121 +load 121 +int 2 +int 2 +* +int 2 ++ +extract_uint16 +int 2 ++ +int 2 +int 1 ++ +load 121 +int 0 +extract_uint16 +store 128 +load 128 +== +bnz arraycomplement_18_l6 +load 121 +int 2 +int 2 +* +int 2 ++ +int 2 ++ +extract_uint16 +int 2 ++ +b arraycomplement_18_l9 +arraycomplement_18_l6: +load 121 +len +b arraycomplement_18_l9 +arraycomplement_18_l7: +load 121 +len +b arraycomplement_18_l4 +arraycomplement_18_l8: +load 121 +len +b arraycomplement_18_l2 +arraycomplement_18_l9: +substring3 +store 125 +load 123 +callsub stringreverse_17 +store 123 +load 124 +callsub stringreverse_17 +store 124 +load 125 +callsub stringreverse_17 +store 125 +int 3 +store 139 +load 139 +itob +extract 6 0 +load 123 +store 138 +load 138 +store 137 +int 6 +store 135 +load 135 +load 138 +len ++ +store 136 +load 136 +int 65536 +< +assert +load 135 +itob +extract 6 0 +load 124 +store 138 +load 137 +load 138 +concat +store 137 +load 136 +store 135 +load 135 +load 138 +len ++ +store 136 +load 136 +int 65536 +< +assert +load 135 +itob +extract 6 0 +concat +load 125 +store 138 +load 137 +load 138 +concat +store 137 +load 136 +store 135 +load 135 +itob +extract 6 0 +concat +load 137 +concat +concat +store 122 +load 122 +retsub + +// bool_comp +boolcomp_19: +store 144 +load 144 +! +store 145 +load 145 +int 2 +< +assert +load 145 +retsub + +// array_complement +arraycomplement_20: +store 140 +load 140 +int 0 +getbit +store 142 +load 140 +int 1 +getbit +store 143 +load 142 +callsub boolcomp_19 +store 142 +load 143 +callsub boolcomp_19 +store 143 +byte 0x00 +int 0 +load 142 +setbit +int 1 +load 143 +setbit +store 141 +load 141 +retsub + +// tuple_complement +tuplecomplement_21: +store 146 +load 146 +int 0 +getbyte +store 118 +load 118 +callsub numericalcomp_23 +store 118 +byte 0x00 +int 0 +load 118 +setbyte +store 147 +load 147 +retsub + +// numerical_comp +numericalcomp_22: +store 150 +int 255 +load 150 +- +store 151 +load 151 +int 256 +< +assert +load 151 +retsub + +// numerical_comp +numericalcomp_23: +store 148 +int 255 +load 148 +- +store 149 +load 149 +int 256 +< +assert +load 149 +retsub \ No newline at end of file From 50748998fe7fd01d82e2094dd38e4f5325a91ece Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Wed, 18 May 2022 08:16:42 -0500 Subject: [PATCH 166/170] better comments --- tests/integration/graviton_abi_test.py | 45 ++++++++++++++++++++------ 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index adc77eb5a..7e404fab6 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -14,6 +14,12 @@ # ---- Integers and Complex Integral Numbers (aka Gaussian Integers) ---- # +""" +WARNING: The following ABI Types Int65 and Complex130 are ONLY for the purpose of testing/demo'ing +ABISubroutine and Gravitons, and are NOT the recommended approach for implementing integers and +complex integers. A better appraoch probably leverages `Uint64` without additional types use 2's complement arithmetic. +""" + Int65 = pt.abi.Tuple2[pt.abi.Bool, pt.abi.Uint64] Complex130 = pt.abi.Tuple2[Int65, Int65] @@ -22,9 +28,7 @@ @pt.ABIReturnSubroutine def int65_minus_cond(x: Int65, y: Int65, *, output: Int65): """ - WARNING: this example is ONLY for the purpose of demo'ing ABISubroutine + Gravitons's capabilities - and NOT the recommended approach for implementing integers. - A better appraoch would stick to `Uint64` as the base type and use 2's complement arithmetic. + WARNING: not an ideal implementation. See explanation in first WARNING above. """ x0 = pt.abi.Bool() x1 = pt.abi.Uint64() @@ -81,9 +85,7 @@ def int65_minus_cond(x: Int65, y: Int65, *, output: Int65): @pt.ABIReturnSubroutine def int65_sub(x: Int65, y: Int65, *, output: Int65): """ - WARNING: this example is ONLY for the purpose of demo'ing ABISubroutine + Gravitons's capabilities - and NOT the recommended approach for implementing integers. - A better appraoch would stick to `Uint64` as the base type and use 2's complement arithmetic. + WARNING: not an ideal implementation. See explanation in first WARNING above. """ x0 = pt.abi.Bool() x1 = pt.abi.Uint64() @@ -121,9 +123,7 @@ def int65_sub(x: Int65, y: Int65, *, output: Int65): @pt.ABIReturnSubroutine def int65_mult(x: Int65, y: Int65, *, output: Int65): """ - WARNING: this example is ONLY for the purpose of demo'ing ABISubroutine + Gravitons's capabilities - and NOT the recommended approach for implementing integers. - A better appraoch would stick to `Uint64` as the base type and use 2's complement arithmetic. + WARNING: not an ideal implementation. See explanation in first WARNING above. """ # TODO: can we get something like the following one-liner working? # return output.set(pt.Not(x[0].get() ^ y[0].get()), x[1].get() * y[1].get()) @@ -140,6 +140,9 @@ def get(x): @Blackbox(input_types=[None]) @ABIReturnSubroutine def int65_negate(x: Int65, *, output: Int65): + """ + WARNING: not an ideal implementation. See explanation in first WARNING above. + """ # TODO: can I haz a one-liner pls???? x0 = pt.abi.Bool() x1 = pt.abi.Uint64() @@ -157,12 +160,18 @@ def int65_negate(x: Int65, *, output: Int65): @Blackbox(input_types=[None, None]) @ABIReturnSubroutine def int65_add(x: Int65, y: Int65, *, output: Int65): + """ + WARNING: not an ideal implementation. See explanation in first WARNING above. + """ return pt.Seq(y.set(int65_negate(y)), output.set(int65_sub(x, y))) @Blackbox(input_types=[None, None]) @ABIReturnSubroutine def complex130_add(x: Complex130, y: Complex130, *, output: Complex130): + """ + WARNING: not an ideal implementation. See explanation in first WARNING above. + """ x0 = pt.abi.make(Int65) x1 = pt.abi.make(Int65) y0 = pt.abi.make(Int65) @@ -183,6 +192,9 @@ def complex130_add(x: Complex130, y: Complex130, *, output: Complex130): @Blackbox(input_types=[None, None]) @ABIReturnSubroutine def complex130_mult(x: Complex130, y: Complex130, *, output: Complex130): + """ + WARNING: not an ideal implementation. See explanation in first WARNING above. + """ x0 = pt.abi.make(Int65) x1 = pt.abi.make(Int65) y0 = pt.abi.make(Int65) @@ -214,18 +226,27 @@ def complex130_mult(x: Complex130, y: Complex130, *, output: Complex130): @Blackbox(input_types=[None]) @ABIReturnSubroutine def complex130_real(x: Complex130, *, output: Int65): + """ + WARNING: not an ideal implementation. See explanation in first WARNING above. + """ return output.set(x[0]) @Blackbox(input_types=[None]) @ABIReturnSubroutine def complex130_imag(x: Complex130, *, output: Int65): + """ + WARNING: not an ideal implementation. See explanation in first WARNING above. + """ return output.set(x[1]) @Blackbox(input_types=[None]) @ABIReturnSubroutine def complex130_conjugate(x: Complex130, *, output: Complex130): + """ + WARNING: not an ideal implementation. See explanation in first WARNING above. + """ z0 = pt.abi.make(Int65) z1 = pt.abi.make(Int65) return pt.Seq( @@ -239,6 +260,9 @@ def complex130_conjugate(x: Complex130, *, output: Complex130): @Blackbox(input_types=[None]) @ABIReturnSubroutine def complex130_norm_squared(x: Complex130, *, output: Int65): + """ + WARNING: not an ideal implementation. See explanation in first WARNING above. + """ t = pt.abi.make(Complex130) return pt.Seq( t.set(complex130_conjugate(x)), @@ -247,6 +271,9 @@ def complex130_norm_squared(x: Complex130, *, output: Int65): ) +# ---- integration test functions ---- # + + def test_integer65(): bbpt_subtract_slick = PyTealDryRunExecutor(int65_sub, pt.Mode.Application) From 7d2022f7bd1bc0d9200a097f6d2379cb7db76dba Mon Sep 17 00:00:00 2001 From: Michael Diamant Date: Wed, 18 May 2022 09:50:00 -0400 Subject: [PATCH 167/170] Optional refactorings to #322 (#346) * pep'ish improvements --- tests/unit/blackbox_test.py | 8 +++++--- tests/unit/user_guide_test.py | 10 +++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/unit/blackbox_test.py b/tests/unit/blackbox_test.py index f74b2f883..69ee59187 100644 --- a/tests/unit/blackbox_test.py +++ b/tests/unit/blackbox_test.py @@ -1,7 +1,7 @@ from itertools import product from pathlib import Path import pytest -from typing import Literal +from typing import Literal, Optional, Tuple import pyteal as pt @@ -151,7 +151,7 @@ def fn_2mixed_arg_1ret( @pytest.mark.parametrize("subr, mode", product(UNITS, pt.Mode)) -def test_blackbox_pyteal(subr, mode): +def test_blackbox_pyteal(subr: BlackboxWrapper, mode: pt.Mode): is_app = mode == pt.Mode.Application name = f"{'app' if is_app else 'lsig'}_{subr.name()}" @@ -166,7 +166,9 @@ def test_blackbox_pyteal(subr, mode): @pytest.mark.parametrize("subr_abi, mode", product(ABI_UNITS, pt.Mode)) -def test_abi_blackbox_pyteal(subr_abi, mode): +def test_abi_blackbox_pyteal( + subr_abi: Tuple[BlackboxWrapper, Optional[pt.ast.abi.BaseType]], mode: pt.Mode +): subr, abi_return_type = subr_abi name = f"{'app' if mode == pt.Mode.Application else 'lsig'}_{subr.name()}" print(f"Case {subr.name()=}, {abi_return_type=}, {mode=} ------> {name=}") diff --git a/tests/unit/user_guide_test.py b/tests/unit/user_guide_test.py index b28b8ccd6..5f4e8c0ae 100644 --- a/tests/unit/user_guide_test.py +++ b/tests/unit/user_guide_test.py @@ -75,17 +75,17 @@ def user_guide_snippet_ABIReturnSubroutine(): # --- BEGIN doc-comment --- # @ABIReturnSubroutine - def abi_sum(toSum: abi.DynamicArray[abi.Uint64], *, output: abi.Uint64) -> Expr: + def abi_sum(to_sum: abi.DynamicArray[abi.Uint64], *, output: abi.Uint64) -> Expr: i = ScratchVar(TealType.uint64) - valueAtIndex = abi.Uint64() + value_at_index = abi.Uint64() return Seq( output.set(0), For( - i.store(Int(0)), i.load() < toSum.length(), i.store(i.load() + Int(1)) + i.store(Int(0)), i.load() < to_sum.length(), i.store(i.load() + Int(1)) ).Do( Seq( - toSum[i.load()].store_into(valueAtIndex), - output.set(output.get() + valueAtIndex.get()), + to_sum[i.load()].store_into(value_at_index), + output.set(output.get() + value_at_index.get()), ) ), ) From c721c06a7ddde82891ffda870ebd8015b860ed0d Mon Sep 17 00:00:00 2001 From: Michael Diamant Date: Wed, 18 May 2022 11:35:49 -0400 Subject: [PATCH 168/170] Refactor ABIRoundtrip to always use annotation_type (#349) --- tests/abi_roundtrip.py | 27 +++++++------------------ tests/integration/abi_roundtrip_test.py | 6 +++++- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/tests/abi_roundtrip.py b/tests/abi_roundtrip.py index fd26e9d83..6c6a3bf69 100644 --- a/tests/abi_roundtrip.py +++ b/tests/abi_roundtrip.py @@ -1,4 +1,4 @@ -from typing import Generic, TypeVar, cast +from typing import Generic, TypeVar import pyteal as pt from pyteal import abi @@ -13,23 +13,12 @@ class ABIRoundtrip(Generic[T]): def __init__( self, - t: type[T] | None, + annotation_instance: abi.BaseType, length: int | None = None, - annotation_instance: abi.BaseType = None, ): - self.annotation: type[abi.BaseType] - self.type_spec: abi.TypeSpec - self.instance: abi.BaseType - - if annotation_instance: - assert t is None - self.instance = annotation_instance - self.type_spec = annotation_instance.type_spec() - self.annotation = self.type_spec.annotation_type() - else: - self.annotation = cast(type[T], t) - self.type_spec = abi.type_spec_from_annotation(self.annotation) - self.instance = self.type_spec.new_instance() + self.instance: abi.BaseType = annotation_instance + self.type_spec: abi.TypeSpec = annotation_instance.type_spec() + self.annotation: type[abi.BaseType] = self.type_spec.annotation_type() self.length: int | None = length @@ -105,7 +94,7 @@ def tuple_comp_factory(self) -> pt.ABIReturnSubroutine: # type: ignore[name-def value_type_specs: list[abi.TypeSpec] = self.type_spec.value_type_specs() # type: ignore[attr-defined] insts = [vts.new_instance() for vts in value_type_specs] roundtrips: list[ABIRoundtrip[T]] = [ - ABIRoundtrip(None, annotation_instance=inst) for inst in insts # type: ignore[arg-type] + ABIRoundtrip(inst, length=None) for inst in insts # type: ignore[arg-type] ] @pt.ABIReturnSubroutine @@ -131,9 +120,7 @@ def array_comp_factory(self) -> pt.ABIReturnSubroutine: internal_type_spec = self.type_spec.value_type_spec() # type: ignore[attr-defined] internal_ann_inst = internal_type_spec.new_instance() - comp_func = ABIRoundtrip( - None, annotation_instance=internal_ann_inst - ).mutator_factory() + comp_func = ABIRoundtrip(internal_ann_inst, length=None).mutator_factory() @pt.ABIReturnSubroutine def array_complement(x: self.annotation, *, output: self.annotation): # type: ignore[name-defined] diff --git a/tests/integration/abi_roundtrip_test.py b/tests/integration/abi_roundtrip_test.py index 73b22e21d..2350f56c0 100644 --- a/tests/integration/abi_roundtrip_test.py +++ b/tests/integration/abi_roundtrip_test.py @@ -96,7 +96,11 @@ def roundtrip_setup(abi_type): if isinstance(abi_type, tuple): abi_type, dynamic_length = abi_type - return abi_type, dynamic_length, ABIRoundtrip(abi_type, dynamic_length).pytealer() + return ( + abi_type, + dynamic_length, + ABIRoundtrip(abi.make(abi_type), length=dynamic_length).pytealer(), + ) @pytest.mark.parametrize("abi_type", ABI_TYPES) From c9c01d744e76158dc6a03c79a694352ff1d6707b Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Wed, 18 May 2022 10:48:59 -0500 Subject: [PATCH 169/170] Update tests/integration/graviton_abi_test.py --- tests/integration/graviton_abi_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/graviton_abi_test.py b/tests/integration/graviton_abi_test.py index 7e404fab6..2f6acaa12 100644 --- a/tests/integration/graviton_abi_test.py +++ b/tests/integration/graviton_abi_test.py @@ -16,7 +16,7 @@ """ WARNING: The following ABI Types Int65 and Complex130 are ONLY for the purpose of testing/demo'ing -ABISubroutine and Gravitons, and are NOT the recommended approach for implementing integers and +ABISubroutine and graviton-abi, and are NOT the recommended approach for implementing integers and complex integers. A better appraoch probably leverages `Uint64` without additional types use 2's complement arithmetic. """ From dd415b9ff0c83a23270e2f0595979b04a68899db Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Wed, 18 May 2022 16:18:41 -0500 Subject: [PATCH 170/170] pin to v0.3.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a20629b8b..5238ceca9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ black==22.3.0 flake8==4.0.1 flake8-tidy-imports==4.6.0 -graviton@git+https://github.com/algorand/graviton@299c0f4f2c2de4dd79aebaccbcceeef7bd93235f +graviton@git+https://github.com/algorand/graviton@v0.3.0 mypy==0.950 pytest==7.1.1 pytest-cov==3.0.0